@north-light/crouter 0.3.208 → 0.3.209

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 (138) hide show
  1. package/dist/api/client.d.ts +3 -1
  2. package/dist/api/client.js +4 -0
  3. package/dist/api/dto/broker-ops.d.ts +2 -12
  4. package/dist/api/dto/nodes.d.ts +16 -0
  5. package/dist/api/routes.d.ts +1 -0
  6. package/dist/api/routes.js +1 -0
  7. package/dist/builtin-memory/00-runtime-base.md +3 -2
  8. package/dist/builtin-memory/01-spine/00-has-manager.md +3 -2
  9. package/dist/builtin-memory/01-spine/01-no-manager.md +3 -2
  10. package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -2
  11. package/dist/builtin-memory/02-lifecycle/01-resident.md +3 -2
  12. package/dist/builtin-memory/04-base-worker.md +3 -2
  13. package/dist/builtin-memory/04-orchestration-kernel.md +3 -2
  14. package/dist/builtin-memory/05-kinds/advisor/00-base.md +3 -2
  15. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +3 -2
  16. package/dist/builtin-memory/05-kinds/design/00-base.md +3 -2
  17. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +3 -2
  18. package/dist/builtin-memory/05-kinds/developer/00-base.md +3 -2
  19. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +3 -2
  20. package/dist/builtin-memory/05-kinds/explore/00-base.md +3 -2
  21. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +3 -2
  22. package/dist/builtin-memory/05-kinds/general/00-base.md +3 -2
  23. package/dist/builtin-memory/05-kinds/general/01-orchestrator.md +3 -2
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -2
  25. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +3 -2
  26. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +3 -2
  27. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +3 -2
  28. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +3 -2
  29. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +3 -2
  30. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +3 -2
  31. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +3 -2
  32. package/dist/builtin-memory/05-kinds/review/00-base.md +3 -2
  33. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +3 -2
  34. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +3 -2
  35. package/dist/builtin-memory/05-kinds/spec/00-base.md +3 -2
  36. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +3 -2
  37. package/dist/builtin-memory/05-kinds/spec/requirements.md +3 -2
  38. package/dist/builtin-memory/advisor/council.md +0 -2
  39. package/dist/builtin-memory/design.md +3 -2
  40. package/dist/builtin-memory/development.md +3 -2
  41. package/dist/builtin-memory/insights/capture.md +1 -3
  42. package/dist/builtin-memory/insights/init.md +1 -3
  43. package/dist/builtin-memory/insights/listen.md +3 -2
  44. package/dist/builtin-memory/internal/INDEX.md +5 -4
  45. package/dist/builtin-memory/internal/agent-shaping.md +5 -4
  46. package/dist/builtin-memory/internal/examples/INDEX.md +3 -2
  47. package/dist/builtin-memory/internal/examples/imessage-assistant.md +3 -2
  48. package/dist/builtin-memory/internal/marketplaces.md +3 -2
  49. package/dist/builtin-memory/internal/memory-loading.md +31 -20
  50. package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -2
  51. package/dist/builtin-memory/internal/plugins.md +7 -6
  52. package/dist/builtin-memory/internal/storage-tiers.md +3 -2
  53. package/dist/builtin-memory/plan/roadmap.md +3 -2
  54. package/dist/builtin-memory/spec/guide.md +0 -2
  55. package/dist/builtin-memory/spec/requirements.md +0 -2
  56. package/dist/builtin-memory/spec/roadmap.md +3 -2
  57. package/dist/builtin-memory/testing.md +1 -3
  58. package/dist/builtin-memory/wedged-child-on-runaway-bash.md +3 -2
  59. package/dist/builtin-pi-packages/pi-crtr-extensions/README.md +1 -1
  60. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +3 -3
  61. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +1 -4
  62. package/dist/clients/attach/viewer.js +366 -366
  63. package/dist/commands/memory/delete.js +3 -18
  64. package/dist/commands/memory/edit.js +23 -30
  65. package/dist/commands/memory/history.js +1 -1
  66. package/dist/commands/memory/lint.d.ts +7 -8
  67. package/dist/commands/memory/lint.js +178 -132
  68. package/dist/commands/memory/list.d.ts +0 -1
  69. package/dist/commands/memory/list.js +2 -12
  70. package/dist/commands/memory/move.d.ts +1 -0
  71. package/dist/commands/memory/move.js +195 -0
  72. package/dist/commands/memory/read.js +135 -141
  73. package/dist/commands/memory/shared.d.ts +18 -17
  74. package/dist/commands/memory/shared.js +93 -39
  75. package/dist/commands/memory/write.js +23 -33
  76. package/dist/commands/memory.js +5 -4
  77. package/dist/commands/pkg/browse/catalog.js +2 -4
  78. package/dist/commands/pkg/browse/doc-view.js +17 -11
  79. package/dist/commands/pkg/browse/model.d.ts +7 -9
  80. package/dist/commands/sys/migrate.d.ts +1 -0
  81. package/dist/commands/sys/migrate.js +106 -0
  82. package/dist/commands/sys/sync-deps.js +5 -10
  83. package/dist/commands/sys/sync-project-guidance.js +36 -16
  84. package/dist/commands/sys/sync-skills.js +8 -4
  85. package/dist/commands/sys.js +3 -2
  86. package/dist/core/__tests__/inline-memory-refs.test.js +8 -5
  87. package/dist/core/__tests__/memory-resolver-precedence.test.js +5 -4
  88. package/dist/core/__tests__/nested-store-discovery.test.js +1 -3
  89. package/dist/core/__tests__/on-read-crouter-home-fence.test.js +3 -3
  90. package/dist/core/__tests__/on-read-dedup-resume.test.js +12 -12
  91. package/dist/core/__tests__/on-read-nested-store.test.js +23 -20
  92. package/dist/core/canvas/db.d.ts +3 -1
  93. package/dist/core/canvas/db.js +12 -2
  94. package/dist/core/memory/history.d.ts +4 -1
  95. package/dist/core/memory/history.js +1 -0
  96. package/dist/core/memory/inline-ref-inventory.d.ts +5 -4
  97. package/dist/core/memory/inline-ref-inventory.js +23 -19
  98. package/dist/core/memory-resolver.d.ts +4 -4
  99. package/dist/core/memory-resolver.js +16 -43
  100. package/dist/core/runtime/bearings.d.ts +4 -3
  101. package/dist/core/runtime/bearings.js +4 -4
  102. package/dist/core/runtime/broker-extension-render.d.ts +3 -2
  103. package/dist/core/runtime/broker-extension-render.js +3 -3
  104. package/dist/core/runtime/memory.js +2 -3
  105. package/dist/core/substrate/index.d.ts +7 -4
  106. package/dist/core/substrate/index.js +6 -4
  107. package/dist/core/substrate/injected-store.d.ts +24 -12
  108. package/dist/core/substrate/injected-store.js +80 -33
  109. package/dist/core/substrate/listings.d.ts +21 -0
  110. package/dist/core/substrate/listings.js +88 -0
  111. package/dist/core/substrate/on-read-node.d.ts +5 -5
  112. package/dist/core/substrate/on-read-node.js +4 -5
  113. package/dist/core/substrate/on-read.d.ts +25 -4
  114. package/dist/core/substrate/on-read.js +81 -102
  115. package/dist/core/substrate/render-node.d.ts +5 -2
  116. package/dist/core/substrate/render-node.js +5 -3
  117. package/dist/core/substrate/render.d.ts +9 -8
  118. package/dist/core/substrate/render.js +104 -96
  119. package/dist/core/substrate/schema.d.ts +34 -18
  120. package/dist/core/substrate/schema.js +75 -32
  121. package/dist/core/substrate/surface-match.d.ts +32 -0
  122. package/dist/core/substrate/surface-match.js +179 -0
  123. package/dist/daemon/api/handlers/nodes.js +9 -0
  124. package/dist/migrations/001-surfaces-frontmatter.d.ts +2 -0
  125. package/dist/migrations/001-surfaces-frontmatter.js +276 -0
  126. package/dist/migrations/convergent.d.ts +31 -0
  127. package/dist/migrations/convergent.js +71 -0
  128. package/dist/migrations/registry.d.ts +2 -0
  129. package/dist/migrations/registry.js +19 -0
  130. package/dist/migrations/types.d.ts +40 -0
  131. package/dist/migrations/types.js +11 -0
  132. package/dist/pi-extensions/canvas-context-intro.d.ts +2 -1
  133. package/dist/pi-extensions/canvas-doc-substrate.d.ts +2 -1
  134. package/dist/pi-extensions/canvas-doc-substrate.js +57 -34
  135. package/package.json +1 -1
  136. package/runtime.lock.json +2 -2
  137. package/dist/core/substrate/ceiling.d.ts +0 -17
  138. package/dist/core/substrate/ceiling.js +0 -67
@@ -305,8 +305,7 @@ function matchNormalizedChild(dir, segment, want, ext = '.md') {
305
305
  /** Resolve a NORMALIZED (numeric-prefix-stripped) segment path against a
306
306
  * physical memory dir: walk intermediate segments matching each as a
307
307
  * directory by normalized name, then resolve the final segment as either a
308
- * `.md` file or a directory (the bare-dir -> INDEX.md fallback needs the dir
309
- * match even when a same-named file also exists). Either half is null when
308
+ * `.md` file or a directory. Either half is null when
310
309
  * the segment path does not resolve that way. This is what makes
311
310
  * `00-runtime-base.md` findable as `runtime-base` and `01-spine/00-has-manager`
312
311
  * findable as `spine/has-manager` — the physical path keeps its pins, only
@@ -329,28 +328,16 @@ function resolveNormalizedPath(baseDir, segments, ext = '.md') {
329
328
  * tree segment for segment with `.jsonl` in place of `.md`. Reuses the doc
330
329
  * resolution rules so a log outlives its doc under the same name the doc had:
331
330
  * 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. */
331
+ * `.history/00-topic.jsonl` still answers to `topic`). Returns null when
332
+ * nothing resolves. */
334
333
  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;
334
+ return resolveNormalizedPath(historyDir, segments, '.jsonl').filePath;
344
335
  }
345
336
  /** Direct full-path lookup of memory/<name>.md within ONE source. Returns that
346
337
  * source's single hit, or undefined — a source can produce at most one direct
347
338
  * match (native wins over plugin within the source, and at most one plugin's
348
- * name can match).
349
- *
350
- * A directory INDEX is the cleaner contract: when `<name>.md` is absent but
351
- * `<name>/INDEX.md` exists, the bare dir name (`taste`) resolves to the dir's
352
- * INDEX doc — carrying the dir name as its identity. (`taste/INDEX` still
353
- * resolves directly as the file path.)
339
+ * name can match). A directory name matches NO doc here: the read leaf
340
+ * answers a directory with its listing, never a stand-in document.
354
341
  *
355
342
  * `name`'s segments are already normalized (a caller-supplied identifier is
356
343
  * never authored with a numeric prefix); resolution against the physical tree
@@ -362,40 +349,26 @@ function findMemoryMatchInSource(name, segments, source) {
362
349
  // precedence), then its enabled plugins.
363
350
  const dir = source.memoryDir;
364
351
  if (dir) {
365
- const { filePath, dirPath } = resolveNormalizedPath(dir, segments);
352
+ const { filePath } = resolveNormalizedPath(dir, segments);
366
353
  if (!isLegacySkillDoc && filePath !== null)
367
354
  return loadMemoryDoc(source.scope, dir, filePath, name);
368
- if (dirPath !== null) {
369
- const indexPath = join(dirPath, 'INDEX.md');
370
- if (pathExists(indexPath))
371
- return loadMemoryDoc(source.scope, dir, indexPath, name);
372
- }
373
355
  }
374
356
  // Plugin memory dir: a `<plugin>/<rest>` name resolves against that enabled
375
357
  // plugin's memory/ tree (the `<pluginName>/` mount that listAllMemoryDocs
376
- // enumerates — `read` must resolve what `list` shows). A BARE plugin name
377
- // (no slash) resolves the plugin-root INDEX.md, mirroring the native
378
- // bare-dir-name -> INDEX.md contract for the plugin mount root.
358
+ // enumerates — `read` must resolve what `list` shows).
379
359
  const slash = name.indexOf('/');
380
- const pluginName = slash > 0 ? name.slice(0, slash) : name;
381
- const rest = slash > 0 ? name.slice(slash + 1) : '';
360
+ if (slash <= 0)
361
+ return undefined;
362
+ const pluginName = name.slice(0, slash);
363
+ const rest = name.slice(slash + 1);
382
364
  for (const p of source.plugins) {
383
365
  if (p.name !== pluginName)
384
366
  continue;
385
367
  const pdir = pluginMemoryDir(p);
386
- const restSegments = rest ? rest.split('/') : [];
387
- if (rest) {
388
- const { filePath } = resolveNormalizedPath(pdir, restSegments);
389
- if (restSegments.at(-1) !== 'SKILL' && filePath !== null)
390
- return loadMemoryDoc(source.scope, pdir, filePath, name);
391
- }
392
- // Bare name -> <plugin>/memory/INDEX.md; slashed name -> dir INDEX.
393
- const pIndexDir = rest ? resolveNormalizedPath(pdir, restSegments).dirPath : pdir;
394
- if (pIndexDir !== null) {
395
- const pindex = join(pIndexDir, 'INDEX.md');
396
- if (pathExists(pindex))
397
- return loadMemoryDoc(source.scope, pdir, pindex, name);
398
- }
368
+ const restSegments = rest.split('/');
369
+ const { filePath } = resolveNormalizedPath(pdir, restSegments);
370
+ if (restSegments.at(-1) !== 'SKILL' && filePath !== null)
371
+ return loadMemoryDoc(source.scope, pdir, filePath, name);
399
372
  }
400
373
  return undefined;
401
374
  }
@@ -1,4 +1,5 @@
1
1
  import { type Cron } from '../canvas/index.js';
2
+ import type { InjectedDocs } from '../substrate/injected-store.js';
2
3
  /** The bearings custom type is shared with every attached renderer. */
3
4
  export { CONTEXT_INTRO_CUSTOM_TYPE } from '../../shared/generated-context.js';
4
5
  /** The `<project_context>` block — the `<environment>` snapshot only: a
@@ -71,10 +72,10 @@ export declare function buildWakeBearings(origin: WakeOrigin): string;
71
72
  * from.
72
73
  * 2. `<memory kind="knowledge">` — the consultable boot catalog, a SIBLING of
73
74
  * the bearings (never nested under the context dir). Dropped when empty.
74
- * 3. `<auto-loaded-context>` — memories whose `applies-to: "."` fires because
75
- * cwd/profile mounted their owning project store. Dropped when empty.
75
+ * 3. `<auto-loaded-context>` — memories whose `on: workspace-open` entry fires
76
+ * because cwd/profile mounted their owning project store. Dropped when empty.
76
77
  * 4. `<project_context>` — the cwd/git environment snapshot. pi's own
77
78
  * AGENTS.md/CLAUDE.md injection stays suppressed (`noContextFiles` in
78
79
  * broker.ts); `crtr sys sync project-guidance` imports that guidance into
79
80
  * the directory's `.crouter/memory/INDEX.md` front door. */
80
- export declare function buildContextBearings(nodeId: string, seen?: Set<string>): string;
81
+ export declare function buildContextBearings(nodeId: string, seen?: InjectedDocs): string;
@@ -340,8 +340,8 @@ function managedWorktreeNote(node) {
340
340
  * from.
341
341
  * 2. `<memory kind="knowledge">` — the consultable boot catalog, a SIBLING of
342
342
  * the bearings (never nested under the context dir). Dropped when empty.
343
- * 3. `<auto-loaded-context>` — memories whose `applies-to: "."` fires because
344
- * cwd/profile mounted their owning project store. Dropped when empty.
343
+ * 3. `<auto-loaded-context>` — memories whose `on: workspace-open` entry fires
344
+ * because cwd/profile mounted their owning project store. Dropped when empty.
345
345
  * 4. `<project_context>` — the cwd/git environment snapshot. pi's own
346
346
  * AGENTS.md/CLAUDE.md injection stays suppressed (`noContextFiles` in
347
347
  * broker.ts); `crtr sys sync project-guidance` imports that guidance into
@@ -365,8 +365,8 @@ export function buildContextBearings(nodeId, seen) {
365
365
  bearings.push('', orchestratorContextNote(nodeId));
366
366
  bearings.push('</crtr-bearings>');
367
367
  // The consultable catalog rides BESIDE the bearings, not inside them.
368
- const knowledge = renderKnowledgeBlock(nodeId);
369
- // A workspace mount is the initial read of its root (`applies-to: "."`).
368
+ const knowledge = renderKnowledgeBlock(nodeId, seen);
369
+ // A workspace mount is the initial read of its root (`on: workspace-open`).
370
370
  const workspaceContext = renderWorkspaceOpenDocs(nodeId, seen);
371
371
  // Hidden applet/situation-origin ambient context (see situational-context.ts)
372
372
  // — the stored `situational` card, placed in the bearings BODY, so it is a
@@ -1,3 +1,4 @@
1
+ import type { InjectedDocs } from '../substrate/injected-store.js';
1
2
  import type { BrokerExtensionNodeDTO, BrokerExtensionStateDTO } from '../../api/dto/broker-ops.js';
2
3
  export declare function editorLabelForBrokerNode(node: BrokerExtensionNodeDTO): string;
3
4
  /** Same environment-only project context rendered at normal boot, without a
@@ -6,9 +7,9 @@ export declare function buildProjectContextBlockForBroker(cwd: string): string;
6
7
  /** `situational` is the node's stored situational card, placed in the bearings
7
8
  * BODY — the one place in the system where a runtime card nests inside
8
9
  * another (see situational-context.ts). '' when none is set. */
9
- export declare function buildContextBearingsFromState(state: BrokerExtensionStateDTO, projectContext: string, situational?: string, seen?: Set<string>): string;
10
+ export declare function buildContextBearingsFromState(state: BrokerExtensionStateDTO, projectContext: string, situational?: string, seen?: InjectedDocs): string;
10
11
  export declare function buildForkBearingsFromState(state: BrokerExtensionStateDTO, situational?: string): string;
11
- export declare function renderPreferencesFromState(state: BrokerExtensionStateDTO): string;
12
+ export declare function renderPreferencesFromState(state: BrokerExtensionStateDTO, seen?: InjectedDocs): string;
12
13
  /** A canonical report ref has already been validated by crtrd in state. The
13
14
  * broker reads only its local filesystem content; no canvas accessor is needed. */
14
15
  export declare function reportPathFromRef(ref: string, reportNodes: ReadonlyMap<string, unknown>): string | null;
@@ -158,7 +158,7 @@ export function buildContextBearingsFromState(state, projectContext, situational
158
158
  if (node.mode === 'orchestrator')
159
159
  bearings.push('', `Because you persist across refresh cycles, your context directory (${contextDir(node.node_id)}) is also where a future cycle of you resumes the work — keep the working notes and decisions a refreshed you would need there, alongside the docs you share with the nodes you spawn.`);
160
160
  bearings.push('</crtr-bearings>');
161
- const knowledge = renderKnowledgeForSubject(state.subject, node.node_id);
161
+ const knowledge = renderKnowledgeForSubject(state.subject, node.node_id, seen);
162
162
  const workspace = renderWorkspaceOpenDocsForSubject(state.subject, node.cwd, node.profile_id, seen);
163
163
  return [bearings.join('\n'), knowledge, workspace, situational, projectContext].filter((part) => part !== '').join('\n');
164
164
  }
@@ -186,8 +186,8 @@ export function buildForkBearingsFromState(state, situational = '') {
186
186
  bearings.push('', 'The first real user message after these bearings is your mandate.', '</crtr-bearings>');
187
187
  return [bearings.join('\n'), situational].filter((part) => part !== '').join('\n');
188
188
  }
189
- export function renderPreferencesFromState(state) {
190
- return interpolateNodePaths(renderPreferencesForSubject(state.subject).block, state.node.node_id);
189
+ export function renderPreferencesFromState(state, seen) {
190
+ return interpolateNodePaths(renderPreferencesForSubject(state.subject, seen).block, state.node.node_id);
191
191
  }
192
192
  /** A canonical report ref has already been validated by crtrd in state. The
193
193
  * broker reads only its local filesystem content; no canvas accessor is needed. */
@@ -1,9 +1,8 @@
1
1
  // memory.ts — the node-local substrate document store.
2
2
  //
3
3
  // One scope lives here: node-local memory, a `memory/` directory of substrate
4
- // documents (.md files with typed frontmatter — kind, when, why,
5
- // system-prompt-visibility, gate) inside a node's context dir; it dies with the
6
- // node. User-global and project memory are scope-resolved stores — see
4
+ // documents (.md files with typed frontmatter — kind, when-and-why-to-read,
5
+ // surfaces, gate) inside a node's context dir; it dies with the node. User-global and project memory are scope-resolved stores — see
7
6
  // `scopeMemoryDir` in core/scope.ts.
8
7
  import { join } from 'node:path';
9
8
  // Import the pure path helper from paths.ts directly, NOT the canvas/index.js
@@ -1,11 +1,14 @@
1
- export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, FALLBACK_RUNG, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, normalizeNameSegment, normalizeDocName, resolveDocName, } from './schema.js';
2
- export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc } from './schema.js';
1
+ export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, SURFACE_EVENTS, SURFACE_RUNGS, bootRung, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, normalizeNameSegment, normalizeDocName, resolveDocName, } from './schema.js';
2
+ export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc, SurfaceEntry, SurfaceEvent, SurfaceRung } from './schema.js';
3
+ export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, owningRootOf, underOwningRoot } from './surface-match.js';
4
+ export { dirDedupKey, docsByName, isDirName, ancestorDirsOf, renderDirListing } from './listings.js';
5
+ export { deliveredAtOrAbove, recordDelivery, mergeInjectedDocs } from './injected-store.js';
6
+ export type { InjectedDocs } from './injected-store.js';
3
7
  export { scopeForCwd, profileNameFor } from './subject-fields.js';
4
8
  export type { NodeConfigSubject } from './subject-fields.js';
5
9
  export { spineDepth, assembleNodeSubject } from './subject.js';
6
10
  export { gatePasses } from './gate.js';
7
- export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveSystemPromptRung, } from './ceiling.js';
8
11
  export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
9
12
  export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
10
- export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject } from './on-read.js';
13
+ export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject, renderCandidateBlocks, memoryReadDocBlocks } from './on-read.js';
11
14
  export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read-node.js';
@@ -8,19 +8,21 @@ export {
8
8
  KINDS, isDocKind,
9
9
  // ladder
10
10
  RUNGS, rungRank, rungAtLeast,
11
- // fallback floor
12
- FALLBACK_RUNG,
11
+ // surfaces (event routing)
12
+ SURFACE_EVENTS, SURFACE_RUNGS, bootRung,
13
13
  // parse + render-shared helpers
14
14
  parseSubstrateFrontmatter, parseSubstrateDoc, previewLine,
15
15
  // display-name normalization
16
16
  normalizeNameSegment, normalizeDocName,
17
17
  // substrate identity (explicit-name-then-path-fallback)
18
18
  resolveDocName, } from './schema.js';
19
+ export { matchesReadEntry, matchesMemoryReadEntry, matchesCommandEntry, readDeliveryRung, workspaceOpenRung, memoryReadDeliveryRung, commandDeliveryRung, owningRootOf, underOwningRoot } from './surface-match.js';
20
+ export { dirDedupKey, docsByName, isDirName, ancestorDirsOf, renderDirListing } from './listings.js';
21
+ export { deliveredAtOrAbove, recordDelivery, mergeInjectedDocs } from './injected-store.js';
19
22
  export { scopeForCwd, profileNameFor } from './subject-fields.js';
20
23
  export { spineDepth, assembleNodeSubject } from './subject.js';
21
24
  export { gatePasses } from './gate.js';
22
- export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveSystemPromptRung, } from './ceiling.js';
23
25
  export { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
24
26
  export { renderPreferencesSection, renderKnowledgeBlock } from './render-node.js';
25
- export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject } from './on-read.js';
27
+ export { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject, renderOnCommandDocsForSubject, renderCandidateBlocks, memoryReadDocBlocks } from './on-read.js';
26
28
  export { renderOnReadDocs, renderWorkspaceOpenDocs } from './on-read-node.js';
@@ -1,16 +1,28 @@
1
- /** The process-shared dedup set for a node: loaded from disk once, then the
2
- * SAME Set instance on every call, so all hooks in this pi process grow and
3
- * consult one set. Daemon-side deliveries (a warm-spare claim) write the disk
1
+ import { type Rung } from './schema.js';
2
+ /** realpath → highest rung rank delivered this transcript (1=name, 2=preview,
3
+ * 3=content — `rungRank` of the delivered rung). */
4
+ export type InjectedDocs = Map<string, number>;
5
+ /** Already delivered at `rung` or above? — the skip test every channel runs
6
+ * before rendering a doc (or a listing line, at preview) again. */
7
+ export declare function deliveredAtOrAbove(seen: InjectedDocs, realpath: string, rung: Rung): boolean;
8
+ /** Record a delivery at `rung`, keeping the highest rank seen. */
9
+ export declare function recordDelivery(seen: InjectedDocs, realpath: string, rung: Rung): void;
10
+ /** Max-merge `source` into `target` — used to fold an out-of-process delivery
11
+ * (the daemon's warm-spare bearings push) into a live process's shared map. */
12
+ export declare function mergeInjectedDocs(target: InjectedDocs, source: InjectedDocs): void;
13
+ /** The process-shared dedup map for a node: loaded from disk once, then the
14
+ * SAME Map instance on every call, so all hooks in this pi process grow and
15
+ * consult one map. Daemon-side deliveries (a warm-spare claim) write the disk
4
16
  * file from another process; consumers that must see those merge the plain
5
17
  * `loadInjectedDocs` result in before rendering. */
6
- export declare function sharedInjectedDocs(nodeId: string): Set<string>;
7
- /** Rehydrate a node's on-read dedup set from disk. Returns an empty set when the
8
- * file is absent, unreadable, or malformed (a fresh transcript, or a node that
9
- * has not yet surfaced any on-read doc). */
10
- export declare function loadInjectedDocs(nodeId: string): Set<string>;
11
- /** Persist a node's on-read dedup set. Called after each read that surfaced a
12
- * new doc, so the grown set survives a later dormancy. */
13
- export declare function saveInjectedDocs(nodeId: string, seen: Set<string>): void;
14
- /** Drop a node's persisted dedup set. Called by the launch paths that start a
18
+ export declare function sharedInjectedDocs(nodeId: string): InjectedDocs;
19
+ /** Rehydrate a node's dedup map from disk. Returns an empty map when the file
20
+ * is absent, unreadable, or malformed (a fresh transcript, or a node that has
21
+ * not yet surfaced any doc). */
22
+ export declare function loadInjectedDocs(nodeId: string): InjectedDocs;
23
+ /** Persist a node's dedup map. Called after each delivery that grew it, so
24
+ * the grown map survives a later dormancy. */
25
+ export declare function saveInjectedDocs(nodeId: string, seen: InjectedDocs): void;
26
+ /** Drop a node's persisted dedup map. Called by the launch paths that start a
15
27
  * FRESH transcript, so the new conversation surfaces docs from scratch. */
16
28
  export declare function clearInjectedDocs(nodeId: string): void;
@@ -1,32 +1,63 @@
1
- // injected-store.ts — durable per-node dedup set for on-read doc injection.
1
+ // injected-store.ts — durable per-node dedup map for substrate doc delivery.
2
2
  //
3
- // The on-read substrate hook (canvas-doc-substrate.ts → renderOnReadDocs) dedups
4
- // so a given doc surfaces at most once per conversation. That set USED to live
5
- // only in the pi process heap, cleared on session_start. But a node's logical
6
- // session — the .jsonl transcript — spans MULTIPLE pi processes: a dormancy →
7
- // revive(resume) cycle exits the old process and launches a fresh `pi --session`
8
- // that REUSES the same transcript. The fresh process started with an empty set,
9
- // so any doc already injected before dormancy got injected AGAIN on the next
10
- // read — the "fires a second time per session" bug.
3
+ // Delivery channels (boot render seeding, workspace-open, on-read, memory-read
4
+ // listings and events, command events) dedup so a given doc surfaces at most
5
+ // once per conversation AT A GIVEN DISCLOSURE. The map keys on realpath and
6
+ // carries the highest rung rank delivered so far; higher rungs pierce lower —
7
+ // a listing line (preview rank) must not block a later explicit content
8
+ // delivery, while a content delivery silences everything after it. Directory
9
+ // listings participate under the directory's own realpath (a directory never
10
+ // collides with a file's realpath).
11
11
  //
12
- // This module persists the set to `nodes/<id>/injected-docs.json` so the resumed
13
- // process rehydrates it and skips docs already present in the transcript. The
14
- // one launch path that begins a FRESH transcript (reviveNode with resume=false,
15
- // in runtime/revive.ts) calls clearInjectedDocs(), so a new conversation starts
16
- // with an empty set.
12
+ // The map USED to live only in the pi process heap, cleared on session_start.
13
+ // But a node's logical session — the .jsonl transcript — spans MULTIPLE pi
14
+ // processes: a dormancy → revive(resume) cycle exits the old process and
15
+ // launches a fresh `pi --session` that REUSES the same transcript. This module
16
+ // persists the map to `nodes/<id>/injected-docs.json` so the resumed process
17
+ // rehydrates it and skips docs already present in the transcript. The one
18
+ // launch path that begins a FRESH transcript (reviveNode with resume=false, in
19
+ // runtime/revive.ts) calls clearInjectedDocs(), so a new conversation starts
20
+ // empty. CLI processes (the `memory read` leaf) load/save the same file.
17
21
  //
18
- // All ops are best-effort: a failed read/write degrades to a possible re-inject,
19
- // never a crash — a dedup miss must never break a read or a revive.
22
+ // Disk format: `{"v":2,"docs":{"<realpath>":<rank 1|2|3>}}` (rank =
23
+ // rungRank of the delivered rung). A legacy v1 string-array rehydrates at
24
+ // content rank — its entries were recorded under never-re-deliver semantics.
25
+ //
26
+ // All ops are best-effort: a failed read/write degrades to a possible
27
+ // re-inject, never a crash — a dedup miss must never break a read or a revive.
20
28
  import { readFileSync, writeFileSync, rmSync } from 'node:fs';
21
29
  import { injectedDocsPath } from '../canvas/paths.js';
22
- /** Per-process cache behind `sharedInjectedDocs`: one Set instance per node,
30
+ import { rungRank } from './schema.js';
31
+ /** Already delivered at `rung` or above? — the skip test every channel runs
32
+ * before rendering a doc (or a listing line, at preview) again. */
33
+ export function deliveredAtOrAbove(seen, realpath, rung) {
34
+ const have = seen.get(realpath);
35
+ return have !== undefined && have >= rungRank(rung);
36
+ }
37
+ /** Record a delivery at `rung`, keeping the highest rank seen. */
38
+ export function recordDelivery(seen, realpath, rung) {
39
+ const rank = rungRank(rung);
40
+ const have = seen.get(realpath);
41
+ if (have === undefined || have < rank)
42
+ seen.set(realpath, rank);
43
+ }
44
+ /** Max-merge `source` into `target` — used to fold an out-of-process delivery
45
+ * (the daemon's warm-spare bearings push) into a live process's shared map. */
46
+ export function mergeInjectedDocs(target, source) {
47
+ for (const [real, rank] of source) {
48
+ const have = target.get(real);
49
+ if (have === undefined || have < rank)
50
+ target.set(real, rank);
51
+ }
52
+ }
53
+ /** Per-process cache behind `sharedInjectedDocs`: one Map instance per node,
23
54
  * shared by every extension in the pi process (context-intro's workspace-open
24
55
  * render and doc-substrate's on-read hook), so a doc delivered by one hook is
25
- * already in the set the other dedups against. */
56
+ * already in the map the other dedups against. */
26
57
  const cache = new Map();
27
- /** The process-shared dedup set for a node: loaded from disk once, then the
28
- * SAME Set instance on every call, so all hooks in this pi process grow and
29
- * consult one set. Daemon-side deliveries (a warm-spare claim) write the disk
58
+ /** The process-shared dedup map for a node: loaded from disk once, then the
59
+ * SAME Map instance on every call, so all hooks in this pi process grow and
60
+ * consult one map. Daemon-side deliveries (a warm-spare claim) write the disk
30
61
  * file from another process; consumers that must see those merge the plain
31
62
  * `loadInjectedDocs` result in before rendering. */
32
63
  export function sharedInjectedDocs(nodeId) {
@@ -37,31 +68,47 @@ export function sharedInjectedDocs(nodeId) {
37
68
  }
38
69
  return seen;
39
70
  }
40
- /** Rehydrate a node's on-read dedup set from disk. Returns an empty set when the
41
- * file is absent, unreadable, or malformed (a fresh transcript, or a node that
42
- * has not yet surfaced any on-read doc). */
71
+ const CONTENT_RANK = rungRank('content');
72
+ /** Rehydrate a node's dedup map from disk. Returns an empty map when the file
73
+ * is absent, unreadable, or malformed (a fresh transcript, or a node that has
74
+ * not yet surfaced any doc). */
43
75
  export function loadInjectedDocs(nodeId) {
44
76
  try {
45
- const arr = JSON.parse(readFileSync(injectedDocsPath(nodeId), 'utf8'));
46
- if (!Array.isArray(arr))
47
- return new Set();
48
- return new Set(arr.filter((x) => typeof x === 'string'));
77
+ const raw = JSON.parse(readFileSync(injectedDocsPath(nodeId), 'utf8'));
78
+ const out = new Map();
79
+ if (Array.isArray(raw)) {
80
+ // legacy v1: bare realpath array, never-re-deliver semantics
81
+ for (const x of raw)
82
+ if (typeof x === 'string')
83
+ out.set(x, CONTENT_RANK);
84
+ return out;
85
+ }
86
+ if (raw !== null && typeof raw === 'object') {
87
+ const docs = raw.docs;
88
+ if (docs !== null && typeof docs === 'object' && !Array.isArray(docs)) {
89
+ for (const [real, rank] of Object.entries(docs)) {
90
+ if (typeof rank === 'number' && rank >= 1 && rank <= CONTENT_RANK)
91
+ out.set(real, rank);
92
+ }
93
+ }
94
+ }
95
+ return out;
49
96
  }
50
97
  catch {
51
- return new Set();
98
+ return new Map();
52
99
  }
53
100
  }
54
- /** Persist a node's on-read dedup set. Called after each read that surfaced a
55
- * new doc, so the grown set survives a later dormancy. */
101
+ /** Persist a node's dedup map. Called after each delivery that grew it, so
102
+ * the grown map survives a later dormancy. */
56
103
  export function saveInjectedDocs(nodeId, seen) {
57
104
  try {
58
- writeFileSync(injectedDocsPath(nodeId), JSON.stringify([...seen]));
105
+ writeFileSync(injectedDocsPath(nodeId), JSON.stringify({ v: 2, docs: Object.fromEntries(seen) }));
59
106
  }
60
107
  catch {
61
108
  // best-effort — a failed persist only risks a re-inject, never a crash
62
109
  }
63
110
  }
64
- /** Drop a node's persisted dedup set. Called by the launch paths that start a
111
+ /** Drop a node's persisted dedup map. Called by the launch paths that start a
65
112
  * FRESH transcript, so the new conversation surfaces docs from scratch. */
66
113
  export function clearInjectedDocs(nodeId) {
67
114
  cache.delete(nodeId);
@@ -0,0 +1,21 @@
1
+ import type { MemoryDoc } from '../memory-resolver.js';
2
+ import { type InjectedDocs } from './injected-store.js';
3
+ /** Dedup key for a whole directory's listing. Realpath keys start with `/`,
4
+ * so the scheme prefix never collides with a doc entry. */
5
+ export declare function dirDedupKey(dir: string): string;
6
+ /** First-occurrence-wins name map over a precedence-ordered corpus: the doc
7
+ * that renders a name's line is the doc a read of that name would return. */
8
+ export declare function docsByName(corpus: readonly MemoryDoc[]): Map<string, MemoryDoc>;
9
+ /** Is `name` a directory in the merged name tree — i.e. does any doc live
10
+ * beneath it? The corpus root ('') is never a directory here: store roots
11
+ * merge into the whole catalog, which `memory list` owns. */
12
+ export declare function isDirName(byName: ReadonlyMap<string, MemoryDoc>, name: string): boolean;
13
+ /** The dirs a doc read discloses: the doc's own directory, then each ancestor
14
+ * walking up, the root excluded. `a/b/c` → `['a/b', 'a']`. */
15
+ export declare function ancestorDirsOf(name: string): string[];
16
+ /** Render one directory's listing lines: `[[name]]: <when-and-why>` per
17
+ * member doc (bare `[[name]]` when the doc carries no routing line), a bare
18
+ * `[[name]]` per subdirectory. `unlisted` members are omitted always;
19
+ * `filterDelivered` additionally drops members already delivered at preview
20
+ * rank or higher. Rendered members record at preview rank into `seen`. */
21
+ export declare function renderDirListing(byName: ReadonlyMap<string, MemoryDoc>, dir: string, seen: InjectedDocs | null, filterDelivered: boolean): string[];
@@ -0,0 +1,88 @@
1
+ // listings.ts — system-owned directory listings over the merged name tree.
2
+ //
3
+ // Reading a doc discloses where it sits: `ls` semantics over the corpus
4
+ // namespace (directories merge across stores), consulting no frontmatter
5
+ // except `unlisted`. A listing renders one when-and-why line per member doc
6
+ // and one bare name per subdirectory — never recursive contents, never gates.
7
+ //
8
+ // Two render modes share one function. An EXPLICIT directory read (`crtr
9
+ // memory read <dir>`) is a deliberate act, so every line renders (unlisted
10
+ // excluded) — mirroring how an explicit doc read always returns content. The
11
+ // AUTOMATIC neighborhood listings a doc read prepends are unsolicited, so
12
+ // each member line dedup-filters against the transcript set and whole
13
+ // directories dedup under a synthetic `dir://<name>` key (a name-tree
14
+ // directory spans stores, so no single physical realpath identifies it).
15
+ // Either mode records rendered member docs at preview rank — a listing line
16
+ // IS a preview-rank render — when a dedup map is supplied.
17
+ import { realpathOrSelf } from '../fs-utils.js';
18
+ import { parseSubstrateDoc, previewLine } from './schema.js';
19
+ import { deliveredAtOrAbove, recordDelivery } from './injected-store.js';
20
+ /** Dedup key for a whole directory's listing. Realpath keys start with `/`,
21
+ * so the scheme prefix never collides with a doc entry. */
22
+ export function dirDedupKey(dir) {
23
+ return `dir://${dir}`;
24
+ }
25
+ /** First-occurrence-wins name map over a precedence-ordered corpus: the doc
26
+ * that renders a name's line is the doc a read of that name would return. */
27
+ export function docsByName(corpus) {
28
+ const byName = new Map();
29
+ for (const d of corpus)
30
+ if (!byName.has(d.name))
31
+ byName.set(d.name, d);
32
+ return byName;
33
+ }
34
+ /** Is `name` a directory in the merged name tree — i.e. does any doc live
35
+ * beneath it? The corpus root ('') is never a directory here: store roots
36
+ * merge into the whole catalog, which `memory list` owns. */
37
+ export function isDirName(byName, name) {
38
+ if (name === '')
39
+ return false;
40
+ const prefix = name + '/';
41
+ for (const n of byName.keys())
42
+ if (n.startsWith(prefix))
43
+ return true;
44
+ return false;
45
+ }
46
+ /** The dirs a doc read discloses: the doc's own directory, then each ancestor
47
+ * walking up, the root excluded. `a/b/c` → `['a/b', 'a']`. */
48
+ export function ancestorDirsOf(name) {
49
+ const dirs = [];
50
+ const segs = name.split('/');
51
+ for (let i = segs.length - 1; i >= 1; i--)
52
+ dirs.push(segs.slice(0, i).join('/'));
53
+ return dirs;
54
+ }
55
+ /** Render one directory's listing lines: `[[name]]: <when-and-why>` per
56
+ * member doc (bare `[[name]]` when the doc carries no routing line), a bare
57
+ * `[[name]]` per subdirectory. `unlisted` members are omitted always;
58
+ * `filterDelivered` additionally drops members already delivered at preview
59
+ * rank or higher. Rendered members record at preview rank into `seen`. */
60
+ export function renderDirListing(byName, dir, seen, filterDelivered) {
61
+ const prefix = dir + '/';
62
+ const memberLines = new Map();
63
+ const subdirs = new Set();
64
+ for (const [name, doc] of byName) {
65
+ if (!name.startsWith(prefix))
66
+ continue;
67
+ const rest = name.slice(prefix.length);
68
+ const slash = rest.indexOf('/');
69
+ if (slash >= 0) {
70
+ subdirs.add(rest.slice(0, slash));
71
+ continue;
72
+ }
73
+ const sub = parseSubstrateDoc(doc);
74
+ if (sub?.unlisted)
75
+ continue;
76
+ const real = realpathOrSelf(doc.path);
77
+ if (filterDelivered && seen !== null && deliveredAtOrAbove(seen, real, 'preview'))
78
+ continue;
79
+ const line = sub === null ? '' : previewLine(sub);
80
+ memberLines.set(rest, line === '' ? `[[${name}]]` : `[[${name}]]: ${line}`);
81
+ if (seen !== null)
82
+ recordDelivery(seen, real, 'preview');
83
+ }
84
+ return [
85
+ ...[...memberLines.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, l]) => l),
86
+ ...[...subdirs].sort((a, b) => a.localeCompare(b)).map((s) => `[[${prefix}${s}]]`),
87
+ ];
88
+ }
@@ -1,9 +1,9 @@
1
+ import type { InjectedDocs } from './injected-store.js';
1
2
  /** Surface docs matched by a successful read tool call. An unknown node id or
2
3
  * an unreadable canvas renders nothing. */
3
- export declare function renderOnReadDocs(nodeId: string, readFilePath: string, seen?: Set<string>): string;
4
+ export declare function renderOnReadDocs(nodeId: string, readFilePath: string, seen?: InjectedDocs): string;
4
5
  /** Surface workspace-wide docs during first-message assembly, using the node's
5
- * recorded cwd and selected profile. `.` is a reserved applies-to target
6
- * meaning “when this project store is mounted by cwd/profile” (and, on the
7
- * read event, “a file under the store's owning dir was read”). `seen` is the
6
+ * recorded cwd and selected profile: docs in mounted project stores carrying
7
+ * a `workspace-open` surfaces entry deliver here. `seen` is the
8
8
  * transcript-scoped dedup set shared with the on-read hook. */
9
- export declare function renderWorkspaceOpenDocs(nodeId: string, seen?: Set<string>): string;
9
+ export declare function renderWorkspaceOpenDocs(nodeId: string, seen?: InjectedDocs): string;
@@ -8,7 +8,7 @@ import { assembleNodeSubject } from './subject.js';
8
8
  import { renderOnReadDocsForSubject, renderWorkspaceOpenDocsForSubject } from './on-read.js';
9
9
  /** Surface docs matched by a successful read tool call. An unknown node id or
10
10
  * an unreadable canvas renders nothing. */
11
- export function renderOnReadDocs(nodeId, readFilePath, seen = new Set()) {
11
+ export function renderOnReadDocs(nodeId, readFilePath, seen = new Map()) {
12
12
  let subject;
13
13
  try {
14
14
  subject = assembleNodeSubject(nodeId);
@@ -21,11 +21,10 @@ export function renderOnReadDocs(nodeId, readFilePath, seen = new Set()) {
21
21
  return renderOnReadDocsForSubject(subject, readFilePath, seen);
22
22
  }
23
23
  /** Surface workspace-wide docs during first-message assembly, using the node's
24
- * recorded cwd and selected profile. `.` is a reserved applies-to target
25
- * meaning “when this project store is mounted by cwd/profile” (and, on the
26
- * read event, “a file under the store's owning dir was read”). `seen` is the
24
+ * recorded cwd and selected profile: docs in mounted project stores carrying
25
+ * a `workspace-open` surfaces entry deliver here. `seen` is the
27
26
  * transcript-scoped dedup set shared with the on-read hook. */
28
- export function renderWorkspaceOpenDocs(nodeId, seen = new Set()) {
27
+ export function renderWorkspaceOpenDocs(nodeId, seen = new Map()) {
29
28
  try {
30
29
  const subject = assembleNodeSubject(nodeId);
31
30
  const node = getNode(nodeId);
@@ -1,9 +1,30 @@
1
+ import { type InjectedDocs } from './injected-store.js';
2
+ import { type Rung, type SubstrateDoc } from './schema.js';
1
3
  import type { NodeConfigSubject } from './subject-fields.js';
2
- /** Surface docs matched by a successful read tool call using the caller-supplied
3
- * daemon snapshot subject. */
4
- export declare function renderOnReadDocsForSubject(subject: NodeConfigSubject, readFilePath: string, seen?: Set<string>): string;
4
+ /** Render candidates at their matched rungs, gate-checked and deduped against
5
+ * the transcript set (higher rungs pierce lower; a delivery records at its
6
+ * rendered rung). A `null` subject means no node identity is available for
7
+ * gate evaluation: gated docs are skipped, ungated docs still deliver. */
8
+ export declare function renderCandidateBlocks(subject: NodeConfigSubject | null, candidates: Array<{
9
+ doc: SubstrateDoc;
10
+ rung: Rung;
11
+ }>, seen: InjectedDocs): string[];
12
+ /** Surface docs whose `read` entries match a successfully read file, using
13
+ * the caller-supplied daemon snapshot subject. */
14
+ export declare function renderOnReadDocsForSubject(subject: NodeConfigSubject, readFilePath: string, seen?: InjectedDocs): string;
5
15
  /** Workspace boot docs using the snapshot's cwd/profile instead of a canvas
6
16
  * lookup. `seen` is the transcript-scoped dedup set shared with the on-read
7
17
  * hook, so a front door delivered here never re-delivers on a later read
8
18
  * beneath its store. */
9
- export declare function renderWorkspaceOpenDocsForSubject(subject: NodeConfigSubject, cwd: string, profileId: string | null, seen?: Set<string>): string;
19
+ export declare function renderWorkspaceOpenDocsForSubject(subject: NodeConfigSubject, cwd: string, profileId: string | null, seen?: InjectedDocs): string;
20
+ /** Inner `<memory>` blocks for the memory-read event: docs whose
21
+ * `memory-read` entries match the doc a `crtr memory read` just resolved
22
+ * (known by `targetNames`; `excludeRealpath` keeps the resolved doc from
23
+ * delivering itself). Returns unwrapped blocks — the read leaf composes one
24
+ * `<auto-loaded-context>` envelope from these plus the directory listings. */
25
+ export declare function memoryReadDocBlocks(subject: NodeConfigSubject | null, excludeRealpath: string, targetNames: string[], seen: InjectedDocs): string[];
26
+ /** Surface docs whose `command` entries match an executed bash command
27
+ * string. The corpus is the resolved cwd/profile set only — a command has no
28
+ * file to walk enclosing stores from. `seen` is the same transcript-scoped
29
+ * dedup set as the other channels. */
30
+ export declare function renderOnCommandDocsForSubject(subject: NodeConfigSubject, command: string, seen?: InjectedDocs): string;