@north-light/crouter 0.3.201 → 0.3.202

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.
@@ -11,6 +11,20 @@ import { effectiveDocKind, normalizeDocName, normalizeNameSegment, resolveDocNam
11
11
  import { loadProfileManifest, profileMemoryDir } from './profiles/manifest.js';
12
12
  import { memoryDir as nodeMemoryDir } from './runtime/memory.js';
13
13
  import { descendantStoreRoots } from './nested-stores.js';
14
+ /** The native (non-plugin) store dirs in resolution precedence — node >
15
+ * project stack > profile > user, builtin excluded as a read-only corpus.
16
+ * Addresses a store directly when the document itself may be absent, which is
17
+ * how a DELETED doc's revision log is still found: history outlives the doc,
18
+ * so its lookup cannot go through document resolution. */
19
+ export function nativeMemoryStoresInPrecedence(scope, includeDescendants = false) {
20
+ const out = [];
21
+ for (const source of memorySourcesInPrecedence(ambientTarget(), scope, includeDescendants)) {
22
+ if (source.scope === 'builtin' || source.memoryDir === null)
23
+ continue;
24
+ out.push({ scope: source.scope, memoryDir: source.memoryDir });
25
+ }
26
+ return out;
27
+ }
14
28
  /** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
15
29
  export function memoryDocId(doc) {
16
30
  return `${doc.scope}/${doc.name}`;
@@ -109,9 +123,9 @@ function memorySourcesInPrecedence(target, scope, includeDescendants = false) {
109
123
  }
110
124
  return out;
111
125
  }
112
- function loadMemoryDoc(scope, path, fallbackName) {
126
+ function loadMemoryDoc(scope, root, path, fallbackName) {
113
127
  const { data, body } = parseFrontmatterGeneric(readText(path));
114
- return { name: resolveDocName(data, fallbackName), scope, path, frontmatter: data, body };
128
+ return { name: resolveDocName(data, fallbackName), scope, path, root, frontmatter: data, body };
115
129
  }
116
130
  /** All memory docs in one memory/ dir, scanned recursively for *.md (topical
117
131
  * subdirs supported), sorted by resolver identity (explicit frontmatter name
@@ -157,7 +171,7 @@ function listMemoryDocsInDir(scope, dir, quiet = false) {
157
171
  // `quiet` suppresses the notice for a targeted resolve (a leaf-name read),
158
172
  // where another doc's health is irrelevant noise before the result.
159
173
  try {
160
- docs.push(loadMemoryDoc(scope, path, name));
174
+ docs.push(loadMemoryDoc(scope, dir, path, name));
161
175
  }
162
176
  catch (e) {
163
177
  const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
@@ -211,7 +225,7 @@ export function listPluginMemoryDocs(plugin, scope, quiet = false) {
211
225
  continue;
212
226
  const name = `${plugin.name}/${derived}`;
213
227
  try {
214
- docs.push({ ...loadMemoryDoc(scope, file, name), plugin: plugin.name });
228
+ docs.push({ ...loadMemoryDoc(scope, dir, file, name), plugin: plugin.name });
215
229
  }
216
230
  catch (e) {
217
231
  const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
@@ -250,13 +264,13 @@ export function listProjectMemoryDocs(startDir = process.cwd(), profileId = sele
250
264
  export function listAllMemoryDocs(scope, quiet = false, includeDescendants = false) {
251
265
  return memorySourcesInPrecedence(ambientTarget(), scope, includeDescendants).flatMap((source) => sourceMemoryDocs(source, quiet));
252
266
  }
253
- /** Find the direct child of `dir` — a `.md` file (matched on name minus
267
+ /** Find the direct child of `dir` — an `ext` file (matched on name minus
254
268
  * extension) or a directory — whose NORMALIZED display name equals
255
269
  * `segment`. An exact literal match (no prefix to strip) wins over a
256
270
  * normalized match, so a `spine` dir sitting beside a `01-spine` dir (a
257
271
  * malformed corpus) never masks the literal one; a well-formed corpus has at
258
272
  * most one candidate either way. Returns the absolute child path, or null. */
259
- function matchNormalizedChild(dir, segment, want) {
273
+ function matchNormalizedChild(dir, segment, want, ext = '.md') {
260
274
  let entries;
261
275
  try {
262
276
  entries = readdirSync(dir, { withFileTypes: true });
@@ -276,9 +290,9 @@ function matchNormalizedChild(dir, segment, want) {
276
290
  }
277
291
  }
278
292
  else {
279
- if (!(e.isFile() && e.name.endsWith('.md')))
293
+ if (!(e.isFile() && e.name.endsWith(ext)))
280
294
  continue;
281
- const base = e.name.slice(0, -3);
295
+ const base = e.name.slice(0, -ext.length);
282
296
  if (base === segment)
283
297
  return join(dir, e.name);
284
298
  if (normalizedHit === null && normalizeNameSegment(base) === segment) {
@@ -297,20 +311,37 @@ function matchNormalizedChild(dir, segment, want) {
297
311
  * `00-runtime-base.md` findable as `runtime-base` and `01-spine/00-has-manager`
298
312
  * findable as `spine/has-manager` — the physical path keeps its pins, only
299
313
  * lookup is prefix-blind. */
300
- function resolveNormalizedPath(baseDir, segments) {
314
+ function resolveNormalizedPath(baseDir, segments, ext = '.md') {
301
315
  let curDir = baseDir;
302
316
  for (let i = 0; i < segments.length - 1; i++) {
303
- const next = matchNormalizedChild(curDir, segments[i], 'dir');
317
+ const next = matchNormalizedChild(curDir, segments[i], 'dir', ext);
304
318
  if (!next)
305
319
  return { filePath: null, dirPath: null };
306
320
  curDir = next;
307
321
  }
308
322
  const last = segments[segments.length - 1];
309
323
  return {
310
- filePath: matchNormalizedChild(curDir, last, 'file'),
311
- dirPath: matchNormalizedChild(curDir, last, 'dir'),
324
+ filePath: matchNormalizedChild(curDir, last, 'file', ext),
325
+ dirPath: matchNormalizedChild(curDir, last, 'dir', ext),
312
326
  };
313
327
  }
328
+ /** Resolve a document NAME against a `.history` tree, which mirrors the doc
329
+ * tree segment for segment with `.jsonl` in place of `.md`. Reuses the doc
330
+ * resolution rules so a log outlives its doc under the same name the doc had:
331
+ * numeric prefixes stay prefix-blind (`00-topic.md` logged at
332
+ * `.history/00-topic.jsonl` still answers to `topic`) and a bare directory
333
+ * name falls back to its INDEX log. Returns null when nothing resolves. */
334
+ export function resolveHistoryLogPath(historyDir, segments) {
335
+ const { filePath, dirPath } = resolveNormalizedPath(historyDir, segments, '.jsonl');
336
+ if (filePath)
337
+ return filePath;
338
+ if (dirPath) {
339
+ const index = matchNormalizedChild(dirPath, 'INDEX', 'file', '.jsonl');
340
+ if (index)
341
+ return index;
342
+ }
343
+ return null;
344
+ }
314
345
  /** Direct full-path lookup of memory/<name>.md within ONE source. Returns that
315
346
  * source's single hit, or undefined — a source can produce at most one direct
316
347
  * match (native wins over plugin within the source, and at most one plugin's
@@ -333,11 +364,11 @@ function findMemoryMatchInSource(name, segments, source) {
333
364
  if (dir) {
334
365
  const { filePath, dirPath } = resolveNormalizedPath(dir, segments);
335
366
  if (!isLegacySkillDoc && filePath !== null)
336
- return loadMemoryDoc(source.scope, filePath, name);
367
+ return loadMemoryDoc(source.scope, dir, filePath, name);
337
368
  if (dirPath !== null) {
338
369
  const indexPath = join(dirPath, 'INDEX.md');
339
370
  if (pathExists(indexPath))
340
- return loadMemoryDoc(source.scope, indexPath, name);
371
+ return loadMemoryDoc(source.scope, dir, indexPath, name);
341
372
  }
342
373
  }
343
374
  // Plugin memory dir: a `<plugin>/<rest>` name resolves against that enabled
@@ -356,14 +387,14 @@ function findMemoryMatchInSource(name, segments, source) {
356
387
  if (rest) {
357
388
  const { filePath } = resolveNormalizedPath(pdir, restSegments);
358
389
  if (restSegments.at(-1) !== 'SKILL' && filePath !== null)
359
- return loadMemoryDoc(source.scope, filePath, name);
390
+ return loadMemoryDoc(source.scope, pdir, filePath, name);
360
391
  }
361
392
  // Bare name -> <plugin>/memory/INDEX.md; slashed name -> dir INDEX.
362
393
  const pIndexDir = rest ? resolveNormalizedPath(pdir, restSegments).dirPath : pdir;
363
394
  if (pIndexDir !== null) {
364
395
  const pindex = join(pIndexDir, 'INDEX.md');
365
396
  if (pathExists(pindex))
366
- return loadMemoryDoc(source.scope, pindex, name);
397
+ return loadMemoryDoc(source.scope, pdir, pindex, name);
367
398
  }
368
399
  }
369
400
  return undefined;
@@ -114,6 +114,8 @@ const LEAF_ICON_CP = {
114
114
  'memory read': 0xf02d,
115
115
  'memory find': 0xf002,
116
116
  'memory write': 0xf040,
117
+ 'memory edit': 0xf044,
118
+ 'memory history': 0xf1da,
117
119
  'memory origin': 0xf1bb,
118
120
  'memory lint': 0xf14a,
119
121
  'node new': 0xf067,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.201",
3
+ "version": "0.3.202",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.201",
3
+ "version": "0.3.202",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.201",
9
+ "version": "0.3.202",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {