compact-agent 1.9.0 → 1.10.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/config.js +10 -1
- package/dist/config.js.map +1 -1
- package/dist/index.js +171 -5
- package/dist/index.js.map +1 -1
- package/dist/mempalace/index.d.ts +111 -0
- package/dist/mempalace/index.js +221 -0
- package/dist/mempalace/index.js.map +1 -0
- package/dist/mempalace/search.d.ts +38 -0
- package/dist/mempalace/search.js +133 -0
- package/dist/mempalace/search.js.map +1 -0
- package/dist/mempalace/store.d.ts +77 -0
- package/dist/mempalace/store.js +332 -0
- package/dist/mempalace/store.js.map +1 -0
- package/dist/mempalace/types.d.ts +140 -0
- package/dist/mempalace/types.js +27 -0
- package/dist/mempalace/types.js.map +1 -0
- package/dist/system-prompt.js +28 -0
- package/dist/system-prompt.js.map +1 -1
- package/dist/tools/index.js +9 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/memory.d.ts +30 -0
- package/dist/tools/memory.js +319 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemPalace — high-level API.
|
|
3
|
+
*
|
|
4
|
+
* Combines the global (cross-project) and project (per-repo) stores into
|
|
5
|
+
* one façade. Most callers use these functions rather than poking the
|
|
6
|
+
* JsonStore directly because they handle the scope selection:
|
|
7
|
+
*
|
|
8
|
+
* - Reads with scope: 'both' search both stores and merge results
|
|
9
|
+
* - Writes with scope: 'global' or 'project' target one
|
|
10
|
+
* - Writes with scope: 'both' rejected at runtime — every drawer lives
|
|
11
|
+
* in exactly one store
|
|
12
|
+
*
|
|
13
|
+
* This module is the boundary between the storage internals (types.ts,
|
|
14
|
+
* store.ts, search.ts) and the agent-facing tools (src/tools/memory.ts)
|
|
15
|
+
* + slash commands.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { JsonStore, globalStorePath, projectStorePath } from './store.js';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
// Lazily-constructed singletons — we don't want to create the global file
|
|
22
|
+
// just by importing the module. They materialize on first read or write.
|
|
23
|
+
let _global = null;
|
|
24
|
+
let _project = null;
|
|
25
|
+
let _projectCwd = null;
|
|
26
|
+
export function getGlobalStore() {
|
|
27
|
+
if (!_global)
|
|
28
|
+
_global = new JsonStore(globalStorePath(), 'global');
|
|
29
|
+
return _global;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Is MemPalace enabled in the user's config? Read directly from
|
|
33
|
+
* ~/.crowcoder/config.json so this can be checked at module-load time
|
|
34
|
+
* by src/tools/index.ts without creating an import cycle through
|
|
35
|
+
* src/config.ts (which imports types.ts which we import here).
|
|
36
|
+
*
|
|
37
|
+
* Treats missing config / missing memory block / undefined enabled as
|
|
38
|
+
* TRUE (default-on for the featured capability). Only explicit false
|
|
39
|
+
* disables.
|
|
40
|
+
*/
|
|
41
|
+
export function isMemoryEnabled() {
|
|
42
|
+
try {
|
|
43
|
+
const cfgPath = join(process.env.CROWCODER_HOME || join(homedir(), '.crowcoder'), 'config.json');
|
|
44
|
+
if (!existsSync(cfgPath))
|
|
45
|
+
return true;
|
|
46
|
+
const raw = readFileSync(cfgPath, 'utf-8');
|
|
47
|
+
const cfg = JSON.parse(raw);
|
|
48
|
+
return cfg.memory?.enabled !== false;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Per-project store keyed by `cwd`. If the cwd changes mid-session (the
|
|
56
|
+
* user `/cd`s elsewhere), the next call gets a fresh store pointing at
|
|
57
|
+
* the new location. The old one stays valid for any held references.
|
|
58
|
+
*/
|
|
59
|
+
export function getProjectStore(cwd) {
|
|
60
|
+
if (!_project || _projectCwd !== cwd) {
|
|
61
|
+
_project = new JsonStore(projectStorePath(cwd), 'project');
|
|
62
|
+
_projectCwd = cwd;
|
|
63
|
+
}
|
|
64
|
+
return _project;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Heuristic: should a piece of content live in the global or project
|
|
68
|
+
* store? Used when the agent calls memory_add with scope: 'auto'.
|
|
69
|
+
*
|
|
70
|
+
* Signals pushing GLOBAL: content mentions the user by name, talks about
|
|
71
|
+
* preferences, recurring patterns, identity, or cross-project facts.
|
|
72
|
+
* Default lean is PROJECT since per-repo facts dominate by volume.
|
|
73
|
+
*/
|
|
74
|
+
export function inferScope(content, tags) {
|
|
75
|
+
const lc = content.toLowerCase();
|
|
76
|
+
const tagsLc = tags.map((t) => t.toLowerCase());
|
|
77
|
+
// User-modeling signals → global
|
|
78
|
+
const userKeywords = [
|
|
79
|
+
'i prefer', 'i like', 'i always', 'i never', 'the user',
|
|
80
|
+
'my style', 'my workflow', 'rsfit',
|
|
81
|
+
];
|
|
82
|
+
for (const k of userKeywords) {
|
|
83
|
+
if (lc.includes(k))
|
|
84
|
+
return 'global';
|
|
85
|
+
}
|
|
86
|
+
if (tagsLc.some((t) => ['preference', 'user', 'identity', 'workflow'].includes(t))) {
|
|
87
|
+
return 'global';
|
|
88
|
+
}
|
|
89
|
+
// Default: project-scoped. Codebase-specific facts outnumber cross-
|
|
90
|
+
// project facts in most workflows.
|
|
91
|
+
return 'project';
|
|
92
|
+
}
|
|
93
|
+
export function addDrawer(input) {
|
|
94
|
+
const tags = input.tags || [];
|
|
95
|
+
const resolvedScope = input.scope === 'auto' || !input.scope
|
|
96
|
+
? inferScope(input.content, tags)
|
|
97
|
+
: input.scope;
|
|
98
|
+
const store = resolvedScope === 'global' ? getGlobalStore() : getProjectStore(input.cwd);
|
|
99
|
+
return store.addDrawer({
|
|
100
|
+
wing: input.wing,
|
|
101
|
+
room: input.room,
|
|
102
|
+
content: input.content,
|
|
103
|
+
tags,
|
|
104
|
+
importance: input.importance ?? 0.5,
|
|
105
|
+
sourceSessionId: input.sourceSessionId,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export function getDrawer(id, cwd) {
|
|
109
|
+
return getGlobalStore().getDrawer(id) ?? getProjectStore(cwd).getDrawer(id);
|
|
110
|
+
}
|
|
111
|
+
export function listDrawers(opts) {
|
|
112
|
+
const { wing, room, tag, cwd, scope = 'both' } = opts;
|
|
113
|
+
const filter = { wing, room, tag };
|
|
114
|
+
if (scope === 'global')
|
|
115
|
+
return getGlobalStore().listDrawers(filter);
|
|
116
|
+
if (scope === 'project')
|
|
117
|
+
return getProjectStore(cwd).listDrawers(filter);
|
|
118
|
+
return [...getGlobalStore().listDrawers(filter), ...getProjectStore(cwd).listDrawers(filter)];
|
|
119
|
+
}
|
|
120
|
+
// ── Search (the main agent-facing query) ──────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Search both stores by default and merge results, re-sorted by score.
|
|
123
|
+
* Specify scope to limit to one. Returns the top `limit` hits across
|
|
124
|
+
* the union.
|
|
125
|
+
*/
|
|
126
|
+
export function search(query, cwd, opts = {}) {
|
|
127
|
+
const scope = opts.scope ?? 'both';
|
|
128
|
+
const limit = opts.limit ?? 20;
|
|
129
|
+
const hits = [];
|
|
130
|
+
if (scope === 'global' || scope === 'both') {
|
|
131
|
+
hits.push(...getGlobalStore().search(query, { ...opts, limit: limit * 2 }));
|
|
132
|
+
}
|
|
133
|
+
if (scope === 'project' || scope === 'both') {
|
|
134
|
+
hits.push(...getProjectStore(cwd).search(query, { ...opts, limit: limit * 2 }));
|
|
135
|
+
}
|
|
136
|
+
hits.sort((a, b) => b.score - a.score);
|
|
137
|
+
return hits.slice(0, limit);
|
|
138
|
+
}
|
|
139
|
+
// ── Tunnels (drawer relationships) ────────────────────────
|
|
140
|
+
/**
|
|
141
|
+
* Link two drawers with a labelled relation. Both drawers must live in
|
|
142
|
+
* the same store — we don't cross global ↔ project boundaries because
|
|
143
|
+
* the link would silently break if either side is moved/deleted.
|
|
144
|
+
*/
|
|
145
|
+
export function linkDrawers(fromId, toId, relation, cwd) {
|
|
146
|
+
// Try both stores to find the source drawer's scope
|
|
147
|
+
if (getGlobalStore().getDrawer(fromId)) {
|
|
148
|
+
if (!getGlobalStore().getDrawer(toId)) {
|
|
149
|
+
throw new Error(`linkDrawers: ${toId} not found in global scope (cross-scope tunnels not supported)`);
|
|
150
|
+
}
|
|
151
|
+
return getGlobalStore().addTunnel(fromId, toId, relation);
|
|
152
|
+
}
|
|
153
|
+
if (getProjectStore(cwd).getDrawer(fromId)) {
|
|
154
|
+
if (!getProjectStore(cwd).getDrawer(toId)) {
|
|
155
|
+
throw new Error(`linkDrawers: ${toId} not found in project scope (cross-scope tunnels not supported)`);
|
|
156
|
+
}
|
|
157
|
+
return getProjectStore(cwd).addTunnel(fromId, toId, relation);
|
|
158
|
+
}
|
|
159
|
+
throw new Error(`linkDrawers: source drawer ${fromId} not found in either scope`);
|
|
160
|
+
}
|
|
161
|
+
export function traverseTunnels(startId, cwd, maxDepth = 3) {
|
|
162
|
+
// Detect which scope the start drawer lives in
|
|
163
|
+
if (getGlobalStore().getDrawer(startId)) {
|
|
164
|
+
return getGlobalStore().traverse(startId, maxDepth);
|
|
165
|
+
}
|
|
166
|
+
return getProjectStore(cwd).traverse(startId, maxDepth);
|
|
167
|
+
}
|
|
168
|
+
// ── Knowledge graph ───────────────────────────────────────
|
|
169
|
+
export function kgAdd(triple, scope, cwd) {
|
|
170
|
+
if (scope === 'both')
|
|
171
|
+
throw new Error('kgAdd: scope must be global or project');
|
|
172
|
+
const store = scope === 'global' ? getGlobalStore() : getProjectStore(cwd);
|
|
173
|
+
return store.addTriple({
|
|
174
|
+
subject: triple.subject,
|
|
175
|
+
predicate: triple.predicate,
|
|
176
|
+
object: triple.object,
|
|
177
|
+
confidence: triple.confidence ?? 1.0,
|
|
178
|
+
sourceSessionId: triple.sourceSessionId,
|
|
179
|
+
sourceDrawerId: triple.sourceDrawerId,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
export function kgQuery(q, cwd, scope = 'both') {
|
|
183
|
+
const out = [];
|
|
184
|
+
if (scope === 'global' || scope === 'both')
|
|
185
|
+
out.push(...getGlobalStore().queryTriples(q));
|
|
186
|
+
if (scope === 'project' || scope === 'both')
|
|
187
|
+
out.push(...getProjectStore(cwd).queryTriples(q));
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
export function kgTimeline(cwd, limit = 20, scope = 'both') {
|
|
191
|
+
const out = [];
|
|
192
|
+
if (scope === 'global' || scope === 'both')
|
|
193
|
+
out.push(...getGlobalStore().recentTriples(limit * 2));
|
|
194
|
+
if (scope === 'project' || scope === 'both')
|
|
195
|
+
out.push(...getProjectStore(cwd).recentTriples(limit * 2));
|
|
196
|
+
out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
197
|
+
return out.slice(0, limit);
|
|
198
|
+
}
|
|
199
|
+
// ── Inventory / status ────────────────────────────────────
|
|
200
|
+
export function listWings(cwd, scope = 'both') {
|
|
201
|
+
return {
|
|
202
|
+
global: scope !== 'project' ? getGlobalStore().listWings() : [],
|
|
203
|
+
project: scope !== 'global' ? getProjectStore(cwd).listWings() : [],
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
export function listRooms(cwd, wing, scope = 'both') {
|
|
207
|
+
return {
|
|
208
|
+
global: scope !== 'project' ? getGlobalStore().listRooms(wing) : [],
|
|
209
|
+
project: scope !== 'global' ? getProjectStore(cwd).listRooms(wing) : [],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export function stats(cwd) {
|
|
213
|
+
return {
|
|
214
|
+
global: getGlobalStore().stats(),
|
|
215
|
+
project: getProjectStore(cwd).stats(),
|
|
216
|
+
globalPath: globalStorePath(),
|
|
217
|
+
projectPath: projectStorePath(cwd),
|
|
218
|
+
projectExists: existsSync(projectStorePath(cwd)),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mempalace/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,0EAA0E;AAC1E,yEAAyE;AACzE,IAAI,OAAO,GAAqB,IAAI,CAAC;AACrC,IAAI,QAAQ,GAAqB,IAAI,CAAC;AACtC,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,GAAG,IAAI,SAAS,CAAC,eAAe,EAAE,EAAE,QAAQ,CAAC,CAAC;IACnE,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,EAAE,aAAa,CAAC,CAAC;QACjG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuC,CAAC;QAClE,OAAO,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,KAAK,CAAC;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC,QAAQ,IAAI,WAAW,KAAK,GAAG,EAAE,CAAC;QACrC,QAAQ,GAAG,IAAI,SAAS,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC;QAC3D,WAAW,GAAG,GAAG,CAAC;IACpB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,IAAc;IACxD,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAEhD,iCAAiC;IACjC,MAAM,YAAY,GAAG;QACnB,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU;QACvD,UAAU,EAAE,aAAa,EAAE,OAAO;KACnC,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,IAAI,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO,QAAQ,CAAC;IACtC,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACnF,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,oEAAoE;IACpE,mCAAmC;IACnC,OAAO,SAAS,CAAC;AACnB,CAAC;AAcD,MAAM,UAAU,SAAS,CAAC,KAAqB;IAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;IAC9B,MAAM,aAAa,GAAU,KAAK,CAAC,KAAK,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK;QACjE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC;QACjC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IAEhB,MAAM,KAAK,GAAG,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzF,OAAO,KAAK,CAAC,SAAS,CAAC;QACrB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI;QACJ,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,GAAG;QACnC,eAAe,EAAE,KAAK,CAAC,eAAe;KACvC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,EAAU,EAAE,GAAW;IAC/C,OAAO,cAAc,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAgF;IAC1G,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC;IACtD,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACnC,IAAI,KAAK,KAAK,QAAQ;QAAE,OAAO,cAAc,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACpE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,cAAc,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;AAChG,CAAC;AAED,6DAA6D;AAC7D;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,GAAW,EAAE,OAAsB,EAAE;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAE/B,MAAM,IAAI,GAAgB,EAAE,CAAC;IAC7B,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,GAAG,cAAc,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,6DAA6D;AAC7D;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,IAAY,EACZ,QAAgB,EAChB,GAAW;IAEX,oDAAoD;IACpD,IAAI,cAAc,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,cAAc,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,gEAAgE,CAAC,CAAC;QACxG,CAAC;QACD,OAAO,cAAc,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,eAAe,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,iEAAiE,CAAC,CAAC;QACzG,CAAC;QACD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,8BAA8B,MAAM,4BAA4B,CAAC,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,GAAW,EACX,QAAQ,GAAG,CAAC;IAEZ,+CAA+C;IAC/C,IAAI,cAAc,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,OAAO,cAAc,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC1D,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,KAAK,CACnB,MAAsI,EACtI,KAAY,EACZ,GAAW;IAEX,IAAI,KAAK,KAAK,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAChF,MAAM,KAAK,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC3E,OAAO,KAAK,CAAC,SAAS,CAAC;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,GAAG;QACpC,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,cAAc,EAAE,MAAM,CAAC,cAAc;KACtC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,CAA4D,EAC5D,GAAW,EACX,QAAe,MAAM;IAErB,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,cAAc,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1F,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,KAAK,GAAG,EAAE,EAAE,QAAe,MAAM;IACvE,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,cAAc,EAAE,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACnG,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM;QAAE,GAAG,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACxG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3D,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,QAAe,MAAM;IAC1D,OAAO;QACL,MAAM,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE;QAC/D,OAAO,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE;KACpE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,IAAa,EAAE,QAAe,MAAM;IACzE,OAAO;QACL,MAAM,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;QACnE,OAAO,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;KACxE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,GAAW;IAO/B,OAAO;QACL,MAAM,EAAE,cAAc,EAAE,CAAC,KAAK,EAAE;QAChC,OAAO,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE;QACrC,UAAU,EAAE,eAAe,EAAE;QAC7B,WAAW,EAAE,gBAAgB,CAAC,GAAG,CAAC;QAClC,aAAa,EAAE,UAAU,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;KACjD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemPalace text search — tokenized, field-weighted scoring.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* 1. Zero dependencies (no FTS index, no embedding model)
|
|
6
|
+
* 2. Reasonable ranking out of the box: title-ish fields outrank body
|
|
7
|
+
* 3. Tolerant to short queries (single keyword should still rank well)
|
|
8
|
+
* 4. Tolerant to plural / case variations via simple stemming
|
|
9
|
+
*
|
|
10
|
+
* We tokenize on non-alphanumeric, lowercase everything, drop tokens
|
|
11
|
+
* shorter than 2 chars and a small stopword list, and score each drawer
|
|
12
|
+
* by summing per-token field weights:
|
|
13
|
+
*
|
|
14
|
+
* tags × 4 (semantic tags are the strongest signal)
|
|
15
|
+
* room × 3
|
|
16
|
+
* wing × 2
|
|
17
|
+
* content × 1 (matches anywhere in body)
|
|
18
|
+
*
|
|
19
|
+
* Importance acts as a multiplier on the final score so the user can
|
|
20
|
+
* boost high-signal drawers via the importance field. Recency also
|
|
21
|
+
* lightly boosts via a half-life of 90 days.
|
|
22
|
+
*
|
|
23
|
+
* This is good enough until the store gets large; the search() entry
|
|
24
|
+
* point is the swap-in for a real FTS / embedding search later.
|
|
25
|
+
*/
|
|
26
|
+
import type { Drawer, SearchOptions, SearchHit } from './types.js';
|
|
27
|
+
/**
|
|
28
|
+
* Tokenize a string into lowercase, deduplicated, non-stopword tokens of
|
|
29
|
+
* length ≥ 2. Very lightweight stemming: trailing 's' is stripped to fold
|
|
30
|
+
* "drawer"/"drawers" together. Aggressive stemming (Porter, etc.) would
|
|
31
|
+
* cost a lot for marginal recall gains at this scale.
|
|
32
|
+
*/
|
|
33
|
+
export declare function tokenize(s: string): string[];
|
|
34
|
+
/**
|
|
35
|
+
* Top-level search. Filters by scope/wing/room/tags first, then scores
|
|
36
|
+
* each remaining drawer and returns the top `limit` by score.
|
|
37
|
+
*/
|
|
38
|
+
export declare function searchDrawers(drawers: Drawer[], query: string, opts?: SearchOptions): SearchHit[];
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemPalace text search — tokenized, field-weighted scoring.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* 1. Zero dependencies (no FTS index, no embedding model)
|
|
6
|
+
* 2. Reasonable ranking out of the box: title-ish fields outrank body
|
|
7
|
+
* 3. Tolerant to short queries (single keyword should still rank well)
|
|
8
|
+
* 4. Tolerant to plural / case variations via simple stemming
|
|
9
|
+
*
|
|
10
|
+
* We tokenize on non-alphanumeric, lowercase everything, drop tokens
|
|
11
|
+
* shorter than 2 chars and a small stopword list, and score each drawer
|
|
12
|
+
* by summing per-token field weights:
|
|
13
|
+
*
|
|
14
|
+
* tags × 4 (semantic tags are the strongest signal)
|
|
15
|
+
* room × 3
|
|
16
|
+
* wing × 2
|
|
17
|
+
* content × 1 (matches anywhere in body)
|
|
18
|
+
*
|
|
19
|
+
* Importance acts as a multiplier on the final score so the user can
|
|
20
|
+
* boost high-signal drawers via the importance field. Recency also
|
|
21
|
+
* lightly boosts via a half-life of 90 days.
|
|
22
|
+
*
|
|
23
|
+
* This is good enough until the store gets large; the search() entry
|
|
24
|
+
* point is the swap-in for a real FTS / embedding search later.
|
|
25
|
+
*/
|
|
26
|
+
const STOPWORDS = new Set([
|
|
27
|
+
'a', 'an', 'and', 'or', 'but', 'the', 'is', 'are', 'was', 'were', 'be',
|
|
28
|
+
'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will',
|
|
29
|
+
'would', 'should', 'could', 'may', 'might', 'must', 'can', 'this',
|
|
30
|
+
'that', 'these', 'those', 'of', 'in', 'on', 'at', 'to', 'for', 'with',
|
|
31
|
+
'by', 'from', 'as', 'into', 'about', 'it', 'its', 'i', 'you', 'we',
|
|
32
|
+
'they', 'them', 'me', 'my', 'your', 'our', 'their', 'his', 'her',
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Tokenize a string into lowercase, deduplicated, non-stopword tokens of
|
|
36
|
+
* length ≥ 2. Very lightweight stemming: trailing 's' is stripped to fold
|
|
37
|
+
* "drawer"/"drawers" together. Aggressive stemming (Porter, etc.) would
|
|
38
|
+
* cost a lot for marginal recall gains at this scale.
|
|
39
|
+
*/
|
|
40
|
+
export function tokenize(s) {
|
|
41
|
+
const out = new Set();
|
|
42
|
+
for (const raw of s.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
43
|
+
if (raw.length < 2)
|
|
44
|
+
continue;
|
|
45
|
+
if (STOPWORDS.has(raw))
|
|
46
|
+
continue;
|
|
47
|
+
// Tiny stemmer: strip trailing 's' on words ≥ 4 chars (avoids "is"/"as" issues)
|
|
48
|
+
const stem = raw.length >= 4 && raw.endsWith('s') ? raw.slice(0, -1) : raw;
|
|
49
|
+
out.add(stem);
|
|
50
|
+
}
|
|
51
|
+
return [...out];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Score a single drawer against a tokenized query. Returns 0 if no match,
|
|
55
|
+
* otherwise a positive score (higher = better).
|
|
56
|
+
*
|
|
57
|
+
* The `matchedFields` array tracks which fields contributed so callers
|
|
58
|
+
* can use it for diagnostics / highlighting.
|
|
59
|
+
*/
|
|
60
|
+
function scoreDrawer(drawer, queryTokens, matchedFields) {
|
|
61
|
+
if (queryTokens.length === 0)
|
|
62
|
+
return 0;
|
|
63
|
+
const contentTokens = new Set(tokenize(drawer.content));
|
|
64
|
+
const tagSet = new Set(drawer.tags.flatMap(tokenize));
|
|
65
|
+
const wingTokens = new Set(tokenize(drawer.wing));
|
|
66
|
+
const roomTokens = new Set(tokenize(drawer.room));
|
|
67
|
+
let score = 0;
|
|
68
|
+
let anyMatch = false;
|
|
69
|
+
for (const qt of queryTokens) {
|
|
70
|
+
if (tagSet.has(qt)) {
|
|
71
|
+
score += 4;
|
|
72
|
+
matchedFields.add('tags');
|
|
73
|
+
anyMatch = true;
|
|
74
|
+
}
|
|
75
|
+
if (roomTokens.has(qt)) {
|
|
76
|
+
score += 3;
|
|
77
|
+
matchedFields.add('room');
|
|
78
|
+
anyMatch = true;
|
|
79
|
+
}
|
|
80
|
+
if (wingTokens.has(qt)) {
|
|
81
|
+
score += 2;
|
|
82
|
+
matchedFields.add('wing');
|
|
83
|
+
anyMatch = true;
|
|
84
|
+
}
|
|
85
|
+
if (contentTokens.has(qt)) {
|
|
86
|
+
score += 1;
|
|
87
|
+
matchedFields.add('content');
|
|
88
|
+
anyMatch = true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (!anyMatch)
|
|
92
|
+
return 0;
|
|
93
|
+
// Importance multiplier — 0.5 default gives no boost, 1.0 → 1.5×, 0 → 0.5×
|
|
94
|
+
score *= 1 + (drawer.importance - 0.5);
|
|
95
|
+
// Recency bonus — half-life of 90 days. Drawers from today get +50%,
|
|
96
|
+
// 90-day-old drawers get +25%, older fade toward baseline.
|
|
97
|
+
const ageDays = (Date.now() - new Date(drawer.updatedAt).getTime()) / (1000 * 60 * 60 * 24);
|
|
98
|
+
const recencyBoost = 0.5 * Math.pow(0.5, ageDays / 90);
|
|
99
|
+
score *= 1 + recencyBoost;
|
|
100
|
+
return score;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Top-level search. Filters by scope/wing/room/tags first, then scores
|
|
104
|
+
* each remaining drawer and returns the top `limit` by score.
|
|
105
|
+
*/
|
|
106
|
+
export function searchDrawers(drawers, query, opts = {}) {
|
|
107
|
+
const tokens = tokenize(query);
|
|
108
|
+
if (tokens.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
let candidates = drawers;
|
|
111
|
+
if (opts.wing)
|
|
112
|
+
candidates = candidates.filter((d) => d.wing === opts.wing.toLowerCase());
|
|
113
|
+
if (opts.room)
|
|
114
|
+
candidates = candidates.filter((d) => d.room === opts.room.toLowerCase());
|
|
115
|
+
if (opts.tags && opts.tags.length > 0) {
|
|
116
|
+
const required = opts.tags.map((t) => t.toLowerCase());
|
|
117
|
+
candidates = candidates.filter((d) => required.every((t) => d.tags.includes(t)));
|
|
118
|
+
}
|
|
119
|
+
if (opts.minImportance !== undefined) {
|
|
120
|
+
candidates = candidates.filter((d) => d.importance >= opts.minImportance);
|
|
121
|
+
}
|
|
122
|
+
const hits = [];
|
|
123
|
+
for (const d of candidates) {
|
|
124
|
+
const matchedFields = new Set();
|
|
125
|
+
const score = scoreDrawer(d, tokens, matchedFields);
|
|
126
|
+
if (score > 0) {
|
|
127
|
+
hits.push({ drawer: d, score, matchedFields: [...matchedFields] });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
hits.sort((a, b) => b.score - a.score);
|
|
131
|
+
return hits.slice(0, opts.limit ?? 20);
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/mempalace/search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Drawer, Tunnel, KGTriple, Scope, WingMeta, RoomMeta, SearchOptions, SearchHit } from './types.js';
|
|
2
|
+
/** ~/.crowcoder/memory/store.json — cross-project knowledge */
|
|
3
|
+
export declare function globalStorePath(): string;
|
|
4
|
+
/** <cwd>/.crowcoder/memory/store.json — per-repo knowledge */
|
|
5
|
+
export declare function projectStorePath(cwd: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Time-ordered ID: hex timestamp + 8 random hex chars. Sorts naturally by
|
|
8
|
+
* creation time, no UUID library needed. ~96 bits of entropy total which
|
|
9
|
+
* is overkill for a single-user store but keeps collision chances at
|
|
10
|
+
* effective zero even across machines.
|
|
11
|
+
*/
|
|
12
|
+
export declare function newId(prefix: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* A single-file MemPalace store backed by JSON. Mutations are immediately
|
|
15
|
+
* persisted to disk — there's no in-memory cache to flush. Trades a small
|
|
16
|
+
* amount of throughput for crash safety, which matters more for a memory
|
|
17
|
+
* system.
|
|
18
|
+
*/
|
|
19
|
+
export declare class JsonStore {
|
|
20
|
+
private readonly path;
|
|
21
|
+
readonly scope: Scope;
|
|
22
|
+
constructor(path: string, scope: Scope);
|
|
23
|
+
private read;
|
|
24
|
+
private write;
|
|
25
|
+
addDrawer(input: Omit<Drawer, 'id' | 'createdAt' | 'updatedAt' | 'scope'>): Drawer;
|
|
26
|
+
getDrawer(id: string): Drawer | null;
|
|
27
|
+
updateDrawer(id: string, patch: Partial<Omit<Drawer, 'id' | 'createdAt' | 'scope'>>): Drawer | null;
|
|
28
|
+
deleteDrawer(id: string): boolean;
|
|
29
|
+
listDrawers(filter?: {
|
|
30
|
+
wing?: string;
|
|
31
|
+
room?: string;
|
|
32
|
+
tag?: string;
|
|
33
|
+
}): Drawer[];
|
|
34
|
+
listWings(): WingMeta[];
|
|
35
|
+
listRooms(wing?: string): RoomMeta[];
|
|
36
|
+
addTunnel(fromDrawerId: string, toDrawerId: string, relation: string): Tunnel;
|
|
37
|
+
/**
|
|
38
|
+
* Find all tunnels touching a drawer, in either direction. Useful for
|
|
39
|
+
* "what is this drawer related to?" queries.
|
|
40
|
+
*/
|
|
41
|
+
findTunnels(drawerId: string): {
|
|
42
|
+
outgoing: Tunnel[];
|
|
43
|
+
incoming: Tunnel[];
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Walk outgoing tunnels from a start drawer for up to maxDepth hops,
|
|
47
|
+
* returning every drawer reached and the path that got us there.
|
|
48
|
+
* Bounded by visited-set so cycles don't loop forever.
|
|
49
|
+
*/
|
|
50
|
+
traverse(startId: string, maxDepth?: number): {
|
|
51
|
+
drawer: Drawer;
|
|
52
|
+
depth: number;
|
|
53
|
+
via: string[];
|
|
54
|
+
}[];
|
|
55
|
+
deleteTunnel(id: string): boolean;
|
|
56
|
+
addTriple(t: Omit<KGTriple, 'id' | 'createdAt' | 'scope'>): KGTriple;
|
|
57
|
+
/**
|
|
58
|
+
* Query triples with optional s/p/o filters. Any field left undefined
|
|
59
|
+
* acts as a wildcard. Matching is case-insensitive equality on each
|
|
60
|
+
* specified field.
|
|
61
|
+
*/
|
|
62
|
+
queryTriples(q: {
|
|
63
|
+
subject?: string;
|
|
64
|
+
predicate?: string;
|
|
65
|
+
object?: string;
|
|
66
|
+
}): KGTriple[];
|
|
67
|
+
/** Most-recent triples first. For "what have I been learning?" views. */
|
|
68
|
+
recentTriples(limit?: number): KGTriple[];
|
|
69
|
+
search(query: string, opts?: SearchOptions): SearchHit[];
|
|
70
|
+
stats(): {
|
|
71
|
+
drawers: number;
|
|
72
|
+
tunnels: number;
|
|
73
|
+
triples: number;
|
|
74
|
+
wings: number;
|
|
75
|
+
rooms: number;
|
|
76
|
+
};
|
|
77
|
+
}
|