@north-light/crouter 0.3.271 → 0.3.272

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.
@@ -283,6 +283,42 @@ test('a failing lint returns its declared rows and fails by exit code', async ()
283
283
  process.exitCode = 0;
284
284
  }
285
285
  });
286
+ test('initializing a repository-root store does not lint its valid namespace as misplaced', async () => {
287
+ const fresh = join(base, 'fresh-root');
288
+ mkdirSync(fresh, { recursive: true });
289
+ const result = (await writeLeaf.run({
290
+ name: 'fresh-root',
291
+ kind: 'knowledge',
292
+ dir: fresh,
293
+ body: 'FRESH ROOT BODY',
294
+ whenAndWhyToRead: 'When opening this repository, read this because it explains the workspace.',
295
+ surface: ['{on: workspace-open, at: content}', '{on: read, match: "./**", at: content}'],
296
+ }));
297
+ assert.equal(result.findings, undefined, JSON.stringify(result.findings));
298
+ });
299
+ test('a targeted read suppresses malformed descendant-store warnings', async () => {
300
+ const noisy = join(base, 'noisy');
301
+ mkdirSync(storeDir(noisy), { recursive: true });
302
+ writeDoc(join(home, '.crouter', 'memory', 'quiet-target.md'), 'kind: knowledge', 'QUIET TARGET BODY');
303
+ writeDoc(join(noisy, 'unrelated', '.crouter', 'memory', 'doc.md'), 'kind: knowledge', 'UNRELATED BODY');
304
+ process.chdir(noisy);
305
+ resetScopeCache();
306
+ const stderr = process.stderr;
307
+ const originalWrite = stderr.write;
308
+ const writes = [];
309
+ stderr.write = ((chunk) => {
310
+ writes.push(String(chunk));
311
+ return true;
312
+ });
313
+ try {
314
+ const read = (await readLeaf.run({ name: 'quiet-target', kind: undefined, frontmatter: false, scope: undefined, dir: undefined }));
315
+ assert.match(read.content, /QUIET TARGET BODY/);
316
+ }
317
+ finally {
318
+ stderr.write = originalWrite;
319
+ }
320
+ assert.equal(writes.some((line) => line.includes('memory store not mounted')), false, writes.join(''));
321
+ });
286
322
  test('deleted history points at the revision holding the retained text, never a read that cannot answer', async () => {
287
323
  await deleteLeaf.run({ name: 'acme/disposable', dir: root });
288
324
  const result = (await historyLeaf.run({ name: 'acme/disposable', dir: root }));
@@ -4,7 +4,7 @@ import { defineLeaf } from '../../core/command.js';
4
4
  import { CrtrError } from '../../core/errors.js';
5
5
  import { memoryExtensionEffectiveCatalog, projectEffectiveMemoryExtensions } from '../../core/memory/extensions.js';
6
6
  import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
7
- import { createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
7
+ import { ambientMemoryTarget, createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
8
8
  import { expandShellBlocks, hasShellBlocks, makeNodeShellRunner } from '../../core/runtime/shell-expansion.js';
9
9
  import { emptyContextExposureState, exposureTarget, loadContextExposureState, registerDocumentExposure, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
10
10
  import { dirDedupKey, docsByName, isDirName, renderDirListing } from '../../core/substrate/listings.js';
@@ -121,7 +121,10 @@ export const readLeaf = defineLeaf({
121
121
  ...(selector.scope === undefined ? {} : { scope: selector.scope }),
122
122
  includeDescendants: true,
123
123
  };
124
- listingByName = linkByName = docsByName(listAllMemoryDocs(selector.scope, false, true));
124
+ // A named read must not turn malformed stores elsewhere in the corpus
125
+ // into warnings about the requested document. `memory lint` owns that
126
+ // corpus health report; resolution still includes every candidate.
127
+ listingByName = linkByName = docsByName(listAllMemoryDocs(selector.scope, true, true));
125
128
  try {
126
129
  set = resolveMemoryCandidates(nameRaw, resolveOpts);
127
130
  }
@@ -190,6 +193,7 @@ export const readLeaf = defineLeaf({
190
193
  subject,
191
194
  target,
192
195
  byName: listingByName,
196
+ memoryTarget: ambientMemoryTarget(),
193
197
  listings: 'blocks',
194
198
  explicit: true,
195
199
  });
@@ -1,12 +1,12 @@
1
1
  import { envNodeId, envProfileId } from '../../shared/env.js';
2
- import { join } from 'node:path';
2
+ import { dirname, join } from 'node:path';
3
3
  import { defineLeaf } from '../../core/command.js';
4
4
  import { usage } from '../../core/errors.js';
5
5
  import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
6
6
  import { lintMemoryDocument, resolvableCanonicalNames } from '../../core/memory/lint.js';
7
7
  import { descendantStoreRoots } from '../../core/nested-stores.js';
8
8
  import { projectScopeRoots } from '../../core/scope.js';
9
- import { loadMemoryTargetView } from '../../core/memory-resolver.js';
9
+ import { loadMemoryTargetView, openProjectMemoryStore } from '../../core/memory-resolver.js';
10
10
  import { parsedSubstrateSurfaces } from '../../core/substrate/frontmatter-validation.js';
11
11
  import { ensureDir, realpathOrSelf, walkFiles, writeText } from '../../core/fs-utils.js';
12
12
  import { associateRepository } from '../../core/memory/repository-association.js';
@@ -187,7 +187,11 @@ export const writeLeaf = defineLeaf({
187
187
  const surfaceEntries = parsedSubstrateSurfaces(frontmatter);
188
188
  const carriesReference = docLinkNames(body).length > 0 || surfaceEntries.some((entry) => entry.on === 'memory-read');
189
189
  const carriesBootEntry = surfaceEntries.some((entry) => entry.on === 'boot');
190
- const lint = lintMemoryDocument(store, path, {
190
+ // Initializing created the root declaration after selector resolution, so
191
+ // validate against a freshly described store: the stale descriptor has no
192
+ // repository-root association yet and would reject its valid namespace.
193
+ const lintStore = initializing ? openProjectMemoryStore(store.ownerDir ?? dirname(dirname(memoryDir))) : store;
194
+ const lint = lintMemoryDocument(lintStore, path, {
191
195
  ...(carriesReference
192
196
  ? {
193
197
  resolvable: resolvableCanonicalNames(loadMemoryTargetView({
@@ -195,7 +195,7 @@ export declare function openProjectMemoryStore(ownerDir: string): MemoryStoreDes
195
195
  /** Every store a target sees, in precedence order: node, each project store
196
196
  * nearest-first (its enabled plugins after it), then profile, user, builtin.
197
197
  * One real store reached twice contributes once, at its first position. */
198
- export declare function memoryStoresInPrecedence(target: MemoryTarget, scope?: MemoryScope, includeDescendants?: boolean): MemoryStoreDescriptor[];
198
+ export declare function memoryStoresInPrecedence(target: MemoryTarget, scope?: MemoryScope, includeDescendants?: boolean, quiet?: boolean): MemoryStoreDescriptor[];
199
199
  /** The native (non-plugin) MOUNTED store dirs in resolution precedence.
200
200
  * Addresses a store directly when the document itself may be absent, which is
201
201
  * how a DELETED doc's revision log is still found: history outlives the doc,
@@ -193,7 +193,7 @@ export function openProjectMemoryStore(ownerDir) {
193
193
  /** Every store a target sees, in precedence order: node, each project store
194
194
  * nearest-first (its enabled plugins after it), then profile, user, builtin.
195
195
  * One real store reached twice contributes once, at its first position. */
196
- export function memoryStoresInPrecedence(target, scope, includeDescendants = false) {
196
+ export function memoryStoresInPrecedence(target, scope, includeDescendants = false, quiet = false) {
197
197
  const out = [];
198
198
  const projectStores = (crtrRoot, projectMemory) => {
199
199
  out.push(projectStoreDescriptor(dirname(crtrRoot), join(crtrRoot, 'memory'), projectMemory));
@@ -211,7 +211,7 @@ export function memoryStoresInPrecedence(target, scope, includeDescendants = fal
211
211
  // nested one. They carry the neutral ceiling — no profile entry names
212
212
  // them, and descendant discovery is off for every automatic delivery path.
213
213
  if (includeDescendants) {
214
- for (const root of descendantStoreRoots(annotated.map(({ root: r }) => r))) {
214
+ for (const root of descendantStoreRoots(annotated.map(({ root: r }) => r), quiet)) {
215
215
  projectStores(root, NEUTRAL_PROJECT_MEMORY);
216
216
  }
217
217
  }
@@ -438,7 +438,7 @@ function makeView(stores, docs, scope) {
438
438
  * with its migration remedy and contributes nothing. */
439
439
  export function loadMemoryTargetView(target, opts = {}) {
440
440
  const quiet = opts.quiet ?? false;
441
- const stores = memoryStoresInPrecedence(target, opts.scope, opts.includeDescendants ?? false);
441
+ const stores = memoryStoresInPrecedence(target, opts.scope, opts.includeDescendants ?? false, quiet);
442
442
  const docs = [];
443
443
  for (const store of stores) {
444
444
  if (store.mountStatus === 'absent')
@@ -9,7 +9,7 @@
9
9
  * excluding centrally gives the resolver and lint the identical call with no
10
10
  * duplicated dedupe logic.
11
11
  */
12
- export declare function descendantStoreRoots(ancestorRoots: string[]): string[];
12
+ export declare function descendantStoreRoots(ancestorRoots: string[], quiet?: boolean): string[];
13
13
  /** Complete descendant-store discovery for state migrations. Unlike the
14
14
  * interactive addressing lane, this filesystem walk has no time or depth
15
15
  * budget, crosses embedded repositories, admits plugin-only `.crouter` roots,
@@ -39,7 +39,7 @@ const cache = new Map();
39
39
  * excluding centrally gives the resolver and lint the identical call with no
40
40
  * duplicated dedupe logic.
41
41
  */
42
- export function descendantStoreRoots(ancestorRoots) {
42
+ export function descendantStoreRoots(ancestorRoots, quiet = false) {
43
43
  const key = [...ancestorRoots].sort().join('\n');
44
44
  const cached = cache.get(key);
45
45
  if (cached !== undefined)
@@ -56,7 +56,9 @@ export function descendantStoreRoots(ancestorRoots) {
56
56
  owners.push(owner);
57
57
  }
58
58
  const tripBudget = (owner) => {
59
- warn(`memory: nested-store discovery under ${owner} exceeded its ${TIME_BUDGET_MS}ms budget — nested stores may be missing from this listing; register deep stores explicitly (add their dirs to the selected profile's projects)`);
59
+ if (!quiet) {
60
+ warn(`memory: nested-store discovery under ${owner} exceeded its ${TIME_BUDGET_MS}ms budget — nested stores may be missing from this listing; register deep stores explicitly (add their dirs to the selected profile's projects)`);
61
+ }
60
62
  };
61
63
  const found = [];
62
64
  const foundSeen = new Set();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.271",
3
+ "version": "0.3.272",
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.271",
3
+ "version": "0.3.272",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.271",
9
+ "version": "0.3.272",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {