@north-light/crouter 0.3.208 → 0.3.210

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
@@ -1,4 +1,5 @@
1
1
  import { CONTEXT_INTRO_CUSTOM_TYPE } from '../shared/generated-context.js';
2
+ import { type InjectedDocs } from '../core/substrate/injected-store.js';
2
3
  /** The `customType` stamped on the injected session message. Used both to write
3
4
  * the entry and to detect it on resume (the idempotency guard). Defined beside
4
5
  * the block builder in `core/runtime/bearings.ts` because the warm-pool claim's
@@ -68,7 +69,7 @@ interface PiLike {
68
69
  }
69
70
  /** Build the full bearings baseline for a conversation without inherited
70
71
  * crouter context. Exported for testing. */
71
- export declare function buildContextIntro(nodeId: string, seen?: Set<string>): Promise<string>;
72
+ export declare function buildContextIntro(nodeId: string, seen?: InjectedDocs): Promise<string>;
72
73
  /** Build the compact node-scoped update for a fork whose copied branch already
73
74
  * carries a full crouter baseline. Exported for testing. */
74
75
  export declare function buildForkContextIntro(nodeId: string): Promise<string>;
@@ -17,7 +17,8 @@ interface ToolResultEventLike {
17
17
  toolName: string;
18
18
  isError?: boolean;
19
19
  /** The tool's resolved input. For `read` it carries the file path under
20
- * `path` (or the legacy `file_path`). Typed loosely; narrowed at use. */
20
+ * `path` (or the legacy `file_path`); for `bash`, the command string under
21
+ * `command`. Typed loosely; narrowed at use. */
21
22
  input?: unknown;
22
23
  content: ContentBlockLike[];
23
24
  }
@@ -15,12 +15,14 @@
15
15
  // a capability. Falls back to appending when the anchor is absent. (The `<memory kind="knowledge">`
16
16
  // catalog rides the bearings message instead — see core/substrate/render.ts.)
17
17
  //
18
- // 2. tool_result (gated on `read`) — the ON-READ half. For each non-error
19
- // `read`, evaluates explicit `applies-to` globs (plus `read-when` metadata)
20
- // across the resolved corpus and any project stores enclosing the read
21
- // file. Matches render at their file-read rung in `<auto-loaded-context>`
22
- // before the file contents. A transcript-scoped realpath set deduplicates
23
- // repeated reads and resumed broker processes.
18
+ // 2. tool_result (gated on `read` and `bash`) — the EVENT half. For each
19
+ // non-error `read`, evaluates the read file against every doc's `read`
20
+ // surfaces entries across the resolved corpus and any project stores
21
+ // enclosing the read file; for each non-error `bash`, evaluates the
22
+ // executed command string against every doc's `command` entries. Matches
23
+ // render at their matched rung in `<auto-loaded-context>` before the
24
+ // tool output. A transcript-scoped (realpath → rung) map deduplicates
25
+ // repeated deliveries and resumed broker processes.
24
26
  //
25
27
  // Both handler bodies are wrapped in try/catch and degrade to INERT on error —
26
28
  // a substrate bug must never brick node boot or break a `read`, only quietly
@@ -34,9 +36,9 @@ import { homedir } from 'node:os';
34
36
  import { join, resolve } from 'node:path';
35
37
  import { brokerExtensionState } from '../core/runtime/broker/daemon-ops.js';
36
38
  import { renderPreferencesFromState } from '../core/runtime/broker-extension-render.js';
37
- import { renderOnReadDocsForSubject } from '../core/substrate/on-read.js';
39
+ import { renderOnCommandDocsForSubject, renderOnReadDocsForSubject } from '../core/substrate/on-read.js';
38
40
  import { clearSessionCache } from '../core/substrate/session-cache.js';
39
- import { loadInjectedDocs, saveInjectedDocs, sharedInjectedDocs } from '../core/substrate/injected-store.js';
41
+ import { loadInjectedDocs, mergeInjectedDocs, saveInjectedDocs, sharedInjectedDocs } from '../core/substrate/injected-store.js';
40
42
  import { autoLoadedContextInner, mergeAutoLoadedContext } from './envelope-merge.js';
41
43
  // ---------------------------------------------------------------------------
42
44
  // Extension
@@ -86,7 +88,13 @@ export function registerCanvasDocSubstrate(pi) {
86
88
  pi.on('before_agent_start', async (event) => {
87
89
  try {
88
90
  const state = await brokerExtensionState(nodeId);
89
- const block = renderPreferencesFromState(state);
91
+ // Merge the on-disk set first so this save never clobbers an
92
+ // out-of-process delivery (the daemon's warm-spare bearings push), then
93
+ // seed each rendered preference at its boot rung. Boot renders every
94
+ // agent run and never skips; re-seeding at the same rung is idempotent.
95
+ mergeInjectedDocs(injectedDocs, loadInjectedDocs(nodeId));
96
+ const block = renderPreferencesFromState(state, injectedDocs);
97
+ saveInjectedDocs(nodeId, injectedDocs);
90
98
  if (block === '')
91
99
  return; // nothing eligible — leave the prompt untouched
92
100
  const idx = event.systemPrompt.indexOf(TOOLS_ANCHOR);
@@ -101,43 +109,58 @@ export function registerCanvasDocSubstrate(pi) {
101
109
  return; // inert on error — never brick node boot
102
110
  }
103
111
  });
104
- // 2. ON-READ half — augment a non-error `read` result with surfacing docs.
112
+ // 2. EVENT half — augment a non-error `read` with docs matched by the read
113
+ // file, and a non-error `bash` with docs matched by the command string.
105
114
  pi.on('tool_result', async (event, ctx) => {
106
115
  try {
107
- if (event.toolName !== 'read' || event.isError === true)
116
+ if (event.isError === true)
108
117
  return;
109
- const input = event.input;
110
- const rawPath = input?.path ?? input?.file_path;
111
- if (rawPath === undefined || rawPath === '')
118
+ let injected;
119
+ if (event.toolName === 'read') {
120
+ const input = event.input;
121
+ const rawPath = input?.path ?? input?.file_path;
122
+ if (rawPath === undefined || rawPath === '')
123
+ return;
124
+ // Resolve to an absolute path (renderOnReadDocsForSubject realpaths it).
125
+ // pi's read tool expands a leading `~`, but event.input keeps the raw
126
+ // form, so expand it ourselves before resolving against the session cwd.
127
+ const cwd = typeof ctx?.cwd === 'string' && ctx.cwd !== '' ? ctx.cwd : process.cwd();
128
+ const expanded = rawPath.startsWith('~') ? join(homedir(), rawPath.slice(1)) : rawPath;
129
+ const absFile = resolve(cwd, expanded);
130
+ const state = await brokerExtensionState(nodeId);
131
+ // Merge the on-disk set: an out-of-process delivery (the daemon's
132
+ // warm-spare claim pushes bearings — including workspace-open docs —
133
+ // into the live session and persists what it delivered) must dedup here
134
+ // too. Cheap: one tiny JSON read per tool result.
135
+ mergeInjectedDocs(injectedDocs, loadInjectedDocs(nodeId));
136
+ injected = renderOnReadDocsForSubject(state.subject, absFile, injectedDocs);
137
+ }
138
+ else if (event.toolName === 'bash') {
139
+ const input = event.input;
140
+ const command = input?.command;
141
+ if (typeof command !== 'string' || command.trim() === '')
142
+ return;
143
+ const state = await brokerExtensionState(nodeId);
144
+ mergeInjectedDocs(injectedDocs, loadInjectedDocs(nodeId));
145
+ injected = renderOnCommandDocsForSubject(state.subject, command, injectedDocs);
146
+ }
147
+ else {
112
148
  return;
113
- // Resolve to an absolute path (renderOnReadDocsForSubject realpaths it).
114
- // pi's read tool expands a leading `~`, but event.input keeps the raw
115
- // form, so expand it ourselves before resolving against the session cwd.
116
- const cwd = typeof ctx?.cwd === 'string' && ctx.cwd !== '' ? ctx.cwd : process.cwd();
117
- const expanded = rawPath.startsWith('~') ? join(homedir(), rawPath.slice(1)) : rawPath;
118
- const absFile = resolve(cwd, expanded);
119
- const state = await brokerExtensionState(nodeId);
120
- // Merge the on-disk set: an out-of-process delivery (the daemon's
121
- // warm-spare claim pushes bearings — including workspace-open docs —
122
- // into the live session and persists what it delivered) must dedup here
123
- // too. Cheap: one tiny JSON read per `read`.
124
- for (const real of loadInjectedDocs(nodeId))
125
- injectedDocs.add(real);
126
- const injected = renderOnReadDocsForSubject(state.subject, absFile, injectedDocs);
149
+ }
127
150
  if (injected === '')
128
- return; // nothing surfaced — pass the read through unchanged
129
- // A doc surfaced → renderOnReadDocs grew injectedDocs; persist the set so
130
- // the dedup survives a later dormancy → revive(resume).
151
+ return; // nothing surfaced — pass the result through unchanged
152
+ // A doc surfaced → the render grew injectedDocs; persist the set so the
153
+ // dedup survives a later dormancy → revive(resume).
131
154
  saveInjectedDocs(nodeId, injectedDocs);
132
- // Fold into any existing auto-loaded-context block from sibling read hooks;
133
- // otherwise prepend one quiet block ahead of the file contents.
155
+ // Fold into any existing auto-loaded-context block from sibling hooks;
156
+ // otherwise prepend one quiet block ahead of the tool output.
134
157
  const inner = autoLoadedContextInner(injected);
135
158
  if (inner === null)
136
159
  return;
137
160
  return { content: mergeAutoLoadedContext([inner], event.content) };
138
161
  }
139
162
  catch {
140
- return; // inert on error — a bug must degrade a read, never break it
163
+ return; // inert on error — a bug must degrade a tool result, never break it
141
164
  }
142
165
  });
143
166
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/crouter",
3
- "version": "0.3.208",
3
+ "version": "0.3.210",
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.208",
3
+ "version": "0.3.210",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/crouter",
9
- "version": "0.3.208",
9
+ "version": "0.3.210",
10
10
  "hasInstallScript": true,
11
11
  "license": "MIT",
12
12
  "dependencies": {
@@ -1,17 +0,0 @@
1
- import { type Rung, type SubstrateDoc } from './schema.js';
2
- /** The reserved basename (no extension) that marks a directory's index doc. */
3
- export declare const INDEX_NAME = "INDEX";
4
- /** Does this path-derived name denote a directory INDEX (`taste/INDEX`, or a
5
- * top-level `INDEX`)? */
6
- export declare function isIndexName(name: string): boolean;
7
- /** The directory an INDEX doc represents: `taste/INDEX` → `taste`, `INDEX` → ''. */
8
- export declare function indexDirOf(name: string): string;
9
- /** Returns the display path corresponding to a canonical logical identifier. */
10
- export declare function displayName(name: string): string;
11
- /** Build the dir → governing-INDEX map. Precedence is first-occurrence-wins, so
12
- * callers MUST pass docs precedence-ordered (project > user > builtin) — the
13
- * highest-precedence INDEX governs a dir, mirroring the memory resolver. */
14
- export declare function buildCeilingIndex(docs: SubstrateDoc[]): Map<string, SubstrateDoc>;
15
- /** A doc's effective system-prompt rung after applying every ancestor INDEX
16
- * ceiling. A `none` ancestor hides the whole subtree from the boot catalog. */
17
- export declare function effectiveSystemPromptRung(doc: SubstrateDoc, ceil: Map<string, SubstrateDoc>): Rung;
@@ -1,67 +0,0 @@
1
- // ceiling.ts — canonical INDEX identifiers and the boot-catalog subtree ceiling.
2
- //
3
- // A directory INDEX has the canonical logical identifier `<directory>/INDEX`
4
- // (`INDEX` at the root). Its system-prompt rung caps every document in that
5
- // subtree while the boot catalog renders:
6
- //
7
- // effective rung of a descendant = min(own rung, every ancestor INDEX's rung)
8
- //
9
- // A `preview` INDEX therefore lets its descendants surface at most at preview;
10
- // a `none` INDEX hides the subtree. File context does not use this ceiling:
11
- // every workspace/file match is an explicit `applies-to` route and renders at
12
- // the matched document's own file-read rung.
13
- import { rungRank } from './schema.js';
14
- /** The reserved basename (no extension) that marks a directory's index doc. */
15
- export const INDEX_NAME = 'INDEX';
16
- /** Does this path-derived name denote a directory INDEX (`taste/INDEX`, or a
17
- * top-level `INDEX`)? */
18
- export function isIndexName(name) {
19
- return name === INDEX_NAME || name.endsWith('/' + INDEX_NAME);
20
- }
21
- /** The directory an INDEX doc represents: `taste/INDEX` → `taste`, `INDEX` → ''. */
22
- export function indexDirOf(name) {
23
- return name === INDEX_NAME ? '' : name.slice(0, name.length - INDEX_NAME.length - 1);
24
- }
25
- /** Returns the display path corresponding to a canonical logical identifier. */
26
- export function displayName(name) {
27
- return isIndexName(name) ? indexDirOf(name) : name;
28
- }
29
- /** Build the dir → governing-INDEX map. Precedence is first-occurrence-wins, so
30
- * callers MUST pass docs precedence-ordered (project > user > builtin) — the
31
- * highest-precedence INDEX governs a dir, mirroring the memory resolver. */
32
- export function buildCeilingIndex(docs) {
33
- const m = new Map();
34
- for (const d of docs) {
35
- if (!isIndexName(d.name))
36
- continue;
37
- const dir = indexDirOf(d.name);
38
- if (!m.has(dir))
39
- m.set(dir, d);
40
- }
41
- return m;
42
- }
43
- function minRung(a, b) {
44
- return rungRank(a) <= rungRank(b) ? a : b;
45
- }
46
- /** Ancestor directory names of a doc, nearest-last: `a/b/c` → [`a/b`, `a`];
47
- * `a` → []. An INDEX doc's OWN dir is included (a self-cap is a no-op), so a
48
- * nested INDEX still inherits its parents' ceilings. */
49
- function ancestorDirs(name) {
50
- const parts = name.split('/');
51
- parts.pop(); // drop the leaf
52
- const out = [];
53
- for (let i = parts.length; i > 0; i--)
54
- out.push(parts.slice(0, i).join('/'));
55
- return out;
56
- }
57
- /** A doc's effective system-prompt rung after applying every ancestor INDEX
58
- * ceiling. A `none` ancestor hides the whole subtree from the boot catalog. */
59
- export function effectiveSystemPromptRung(doc, ceil) {
60
- let rung = doc.systemPromptVisibility;
61
- for (const dir of ancestorDirs(doc.name)) {
62
- const idx = ceil.get(dir);
63
- if (idx)
64
- rung = minRung(rung, idx.systemPromptVisibility);
65
- }
66
- return rung;
67
- }