@jacklika/dsh-memory-curator 0.1.0-alpha.5 → 0.1.0-alpha.7
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/lib/index.js +34 -6
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -32,20 +32,40 @@ export const Config = z.object({
|
|
|
32
32
|
function foldTitle(title) {
|
|
33
33
|
return title.normalize('NFC').toLowerCase().replace(/[^\p{L}\p{N}]+/gu, ' ').trim();
|
|
34
34
|
}
|
|
35
|
+
const MAX_SLUG_LENGTH = 100;
|
|
36
|
+
function hashTail(title, length = 12) {
|
|
37
|
+
return createHash('sha256').update(title.normalize('NFC')).digest('hex').slice(0, length);
|
|
38
|
+
}
|
|
35
39
|
function slugify(title) {
|
|
36
|
-
|
|
40
|
+
let slug = foldTitle(title).replace(/ /g, '-');
|
|
37
41
|
// Emoji-only and punctuation-only titles fold to nothing; a stable content
|
|
38
42
|
// hash keeps them addressable and separate rather than sharing a bare ".md".
|
|
39
43
|
if (slug === '')
|
|
40
|
-
return `note-${
|
|
44
|
+
return `note-${hashTail(title)}`;
|
|
45
|
+
// Keep generated filenames bounded on all platforms. A long CJK title would
|
|
46
|
+
// otherwise create a very long filename; truncate and append a hash so the
|
|
47
|
+
// id remains deterministic and collision-resistant.
|
|
48
|
+
if (slug.length > MAX_SLUG_LENGTH) {
|
|
49
|
+
slug = `${slug.slice(0, 64)}-${hashTail(title)}`;
|
|
50
|
+
}
|
|
41
51
|
return slug;
|
|
42
52
|
}
|
|
43
53
|
function ensureMdExtension(id) {
|
|
44
54
|
return id.endsWith('.md') ? id : `${id}.md`;
|
|
45
55
|
}
|
|
56
|
+
function isSafeNoteId(id) {
|
|
57
|
+
if (id === '' || id.startsWith('/') || id.startsWith('\\') || /^[A-Za-z]:/.test(id))
|
|
58
|
+
return false;
|
|
59
|
+
if (id.includes('\\'))
|
|
60
|
+
return false;
|
|
61
|
+
return !id.split('/').some(segment => segment === '..' || segment === '.');
|
|
62
|
+
}
|
|
46
63
|
function resolveNoteId(id, title, scope, notesDir) {
|
|
47
64
|
if (id !== undefined && id !== '') {
|
|
48
65
|
const withExt = ensureMdExtension(id);
|
|
66
|
+
if (!isSafeNoteId(withExt)) {
|
|
67
|
+
throw new Error(`memory-curator: invalid note id ${JSON.stringify(withExt)}: ids must be vault-relative paths without '..', '.', backslashes, leading slashes, or drive letters`);
|
|
68
|
+
}
|
|
49
69
|
if (scope === 'private')
|
|
50
70
|
return withExt;
|
|
51
71
|
return withExt.startsWith('shared/') ? withExt : `${notesDir}/${withExt.replace(/^\//, '')}`;
|
|
@@ -150,12 +170,20 @@ export function apply(ctx, config) {
|
|
|
150
170
|
},
|
|
151
171
|
async execute(args, exec) {
|
|
152
172
|
const search = await callTool(ctx, exec, 'wiki_search', { query: args.query });
|
|
173
|
+
// Mechanical per-turn summaries are useful for session replay, but they
|
|
174
|
+
// are not curated knowledge. Keep them out of proactive recall unless a
|
|
175
|
+
// caller explicitly searches the wiki. The prefix covers both the legacy
|
|
176
|
+
// flat form `auto-capture-<session>.md` and the new isolated subdir
|
|
177
|
+
// `auto-capture/<session>.md`.
|
|
178
|
+
const curated = search.hits.filter(hit => !hit.id.startsWith('shared/notes/auto-capture'));
|
|
179
|
+
// `search.total` is the vault-wide count before the search-layer cap. If
|
|
180
|
+
// any auto-capture hits made it into the capped `hits` list, remove them
|
|
181
|
+
// from the total so curated numbers stay truthful.
|
|
182
|
+
const total = search.total - (search.hits.length - curated.length);
|
|
153
183
|
return {
|
|
154
184
|
query: args.query,
|
|
155
|
-
hits:
|
|
156
|
-
|
|
157
|
-
// its length would understate how much actually matched.
|
|
158
|
-
total: search.total,
|
|
185
|
+
hits: curated.slice(0, 20),
|
|
186
|
+
total,
|
|
159
187
|
};
|
|
160
188
|
},
|
|
161
189
|
})));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jacklika/dsh-memory-curator",
|
|
3
3
|
"description": "Curator tools for the DSH memory vault: memory_recall before acting and memory_capture after significant work, with conflict detection and optional user approval.",
|
|
4
|
-
"version": "0.1.0-alpha.
|
|
4
|
+
"version": "0.1.0-alpha.7",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
"@deepseek-ai/dsh-util-values": ">=0.1.7-rc.1 <0.3.0-0 || >=0.2.0-rc.1 <0.3.0-0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@jacklika/dsh-tool-memory-filesystem": "^0.1.3-alpha.
|
|
47
|
+
"@jacklika/dsh-tool-memory-filesystem": "^0.1.3-alpha.9"
|
|
48
48
|
}
|
|
49
49
|
}
|