@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.
Files changed (2) hide show
  1. package/lib/index.js +34 -6
  2. 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
- const slug = foldTitle(title).replace(/ /g, '-');
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-${createHash('sha256').update(title.normalize('NFC')).digest('hex').slice(0, 12)}`;
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: search.hits.slice(0, 20),
156
- // Forward the ranking's own count: `hits` is already capped upstream, so
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.5",
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.8"
47
+ "@jacklika/dsh-tool-memory-filesystem": "^0.1.3-alpha.9"
48
48
  }
49
49
  }