@north-light/crouter 0.3.221 → 0.3.223

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 (169) hide show
  1. package/dist/api/client.d.ts +21 -1
  2. package/dist/api/client.js +34 -0
  3. package/dist/api/dto/chat-inventory.d.ts +13 -0
  4. package/dist/api/dto/human-requests.d.ts +88 -0
  5. package/dist/api/dto/human-requests.js +4 -0
  6. package/dist/api/dto/human.d.ts +3 -0
  7. package/dist/api/dto/reviews.d.ts +2 -0
  8. package/dist/api/index.d.ts +1 -0
  9. package/dist/api/index.js +1 -0
  10. package/dist/api/routes.d.ts +7 -0
  11. package/dist/api/routes.js +10 -0
  12. package/dist/builtin-memory/00-runtime-base/00-authoring.md +31 -0
  13. package/dist/builtin-memory/00-runtime-base/01-escalation.md +14 -0
  14. package/dist/builtin-memory/{insights/listen.md → 00-runtime-base/02-insight-capture.md} +1 -0
  15. package/dist/builtin-memory/02-turn-lifecycle/00-ending-a-turn.md +27 -0
  16. package/dist/builtin-memory/{02-lifecycle/01-resident.md → 02-turn-lifecycle/02-resident.md} +5 -0
  17. package/dist/builtin-memory/04-base-worker.md +4 -8
  18. package/dist/builtin-memory/04-orchestration-kernel.md +1 -1
  19. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +1 -0
  20. package/dist/builtin-memory/05-kinds/advisor/advice-contract.md +1 -0
  21. package/dist/builtin-memory/05-kinds/design/00-base.md +2 -1
  22. package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +2 -1
  23. package/dist/builtin-memory/05-kinds/design/design-contract.md +19 -0
  24. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -0
  25. package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +1 -0
  26. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -0
  27. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -0
  28. package/dist/builtin-memory/05-kinds/general/00-base.md +1 -0
  29. package/dist/builtin-memory/05-kinds/plan/00-base.md +2 -1
  30. package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +2 -1
  31. package/dist/builtin-memory/05-kinds/plan/plan-contract.md +28 -0
  32. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -0
  33. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -0
  34. package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +1 -0
  35. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -0
  36. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -0
  37. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -0
  38. package/dist/builtin-memory/05-kinds/review/00-base.md +1 -0
  39. package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -0
  40. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +1 -0
  41. package/dist/builtin-memory/05-kinds/review/security-findings.md +1 -0
  42. package/dist/builtin-memory/05-kinds/spec/00-base.md +4 -3
  43. package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -0
  44. package/dist/builtin-memory/05-kinds/spec/requirements.md +1 -0
  45. package/dist/builtin-memory/design/guide.md +35 -0
  46. package/dist/builtin-memory/design/roadmap.md +21 -0
  47. package/dist/builtin-memory/insights/capture.md +1 -1
  48. package/dist/builtin-memory/internal/memory-loading.md +4 -4
  49. package/dist/builtin-memory/internal/plugins.md +10 -1
  50. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  51. package/dist/builtin-memory/plan/roadmap.md +6 -28
  52. package/dist/builtin-memory/spec/guide.md +19 -8
  53. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +28 -15
  54. package/dist/clients/attach/render/markdown-source.js +106 -1
  55. package/dist/clients/attach/session/file-links.d.ts +13 -4
  56. package/dist/clients/attach/session/file-links.js +54 -58
  57. package/dist/clients/attach/viewer.js +545 -543
  58. package/dist/clients/inbox/controller.js +1 -1
  59. package/dist/clients/inbox/page-adapter.js +2 -1
  60. package/dist/clients/inbox/resolve.d.ts +1 -0
  61. package/dist/clients/inbox/review/review-client.js +3 -1
  62. package/dist/clients/inbox/tui/panel.js +23 -4
  63. package/dist/clients/inbox/tui/render.js +6 -0
  64. package/dist/clients/inbox/tui/types.d.ts +8 -0
  65. package/dist/commands/__tests__/human.test.js +2 -2
  66. package/dist/commands/human/request.d.ts +2 -0
  67. package/dist/commands/human/request.js +281 -0
  68. package/dist/commands/human.js +5 -2
  69. package/dist/commands/memory/shared.d.ts +2 -2
  70. package/dist/commands/memory/shared.js +11 -6
  71. package/dist/commands/pkg/browse/doc-view.js +2 -0
  72. package/dist/commands/sys/config.js +2 -2
  73. package/dist/commands/sys/doctor.js +54 -2
  74. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +7 -4
  75. package/dist/core/__tests__/fixtures/memory-slash-live-probe.d.ts +1 -0
  76. package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +71 -0
  77. package/dist/core/__tests__/human-action-delivery.test.d.ts +1 -0
  78. package/dist/core/__tests__/human-action-delivery.test.js +140 -0
  79. package/dist/core/__tests__/human-actions.test.d.ts +1 -0
  80. package/dist/core/__tests__/human-actions.test.js +116 -0
  81. package/dist/core/__tests__/inline-memory-refs.test.js +1 -1
  82. package/dist/core/__tests__/profile-project-memory-delivery.test.js +70 -3
  83. package/dist/core/__tests__/prospective-inventory-capability-parity.test.d.ts +1 -0
  84. package/dist/core/__tests__/prospective-inventory-capability-parity.test.js +91 -0
  85. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.d.ts +1 -0
  86. package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +127 -0
  87. package/dist/core/__tests__/seam/prospective-inventory-stdout.test.d.ts +1 -0
  88. package/dist/core/__tests__/seam/prospective-inventory-stdout.test.js +31 -0
  89. package/dist/core/canvas/db.js +23 -0
  90. package/dist/core/canvas/human-deliveries.d.ts +53 -0
  91. package/dist/core/canvas/human-deliveries.js +75 -0
  92. package/dist/core/config.d.ts +13 -1
  93. package/dist/core/config.js +51 -1
  94. package/dist/core/feed/inbox.d.ts +6 -0
  95. package/dist/core/feed/inbox.js +9 -1
  96. package/dist/core/human/action-binding.d.ts +21 -0
  97. package/dist/core/human/action-binding.js +40 -0
  98. package/dist/core/human/completion.d.ts +38 -0
  99. package/dist/core/human/completion.js +27 -0
  100. package/dist/core/human/convention.d.ts +2 -0
  101. package/dist/core/human/convention.js +2 -0
  102. package/dist/core/human/tickets.d.ts +25 -6
  103. package/dist/core/human/tickets.js +19 -13
  104. package/dist/core/human/types.d.ts +5 -0
  105. package/dist/core/human-actions.d.ts +25 -0
  106. package/dist/core/human-actions.js +101 -0
  107. package/dist/core/memory-resolver.js +1 -1
  108. package/dist/core/profiles/select.d.ts +2 -0
  109. package/dist/core/profiles/select.js +21 -4
  110. package/dist/core/runtime/broker/frame-dispatch.js +2 -5
  111. package/dist/core/runtime/broker-inventory.d.ts +1 -2
  112. package/dist/core/runtime/broker-inventory.js +2 -77
  113. package/dist/core/runtime/broker-persona-guidance.js +1 -1
  114. package/dist/core/runtime/broker.js +4 -4
  115. package/dist/core/runtime/chat-inventory-rows.d.ts +8 -0
  116. package/dist/core/runtime/chat-inventory-rows.js +105 -0
  117. package/dist/core/runtime/command-surface.d.ts +8 -3
  118. package/dist/core/runtime/command-surface.js +42 -6
  119. package/dist/core/runtime/launch-target.d.ts +25 -0
  120. package/dist/core/runtime/launch-target.js +54 -0
  121. package/dist/core/runtime/persona.js +3 -3
  122. package/dist/core/runtime/prospective-inventory-cli.d.ts +1 -0
  123. package/dist/core/runtime/prospective-inventory-cli.js +61 -0
  124. package/dist/core/runtime/prospective-inventory.d.ts +10 -0
  125. package/dist/core/runtime/prospective-inventory.js +88 -0
  126. package/dist/core/runtime/spawn.d.ts +3 -1
  127. package/dist/core/runtime/spawn.js +5 -3
  128. package/dist/core/substrate/frontmatter-validation.d.ts +2 -5
  129. package/dist/core/substrate/frontmatter-validation.js +8 -4
  130. package/dist/core/substrate/gate.d.ts +4 -1
  131. package/dist/core/substrate/gate.js +5 -0
  132. package/dist/core/substrate/on-read.js +21 -32
  133. package/dist/core/substrate/render-node.d.ts +3 -2
  134. package/dist/core/substrate/render-node.js +3 -2
  135. package/dist/core/substrate/render.js +64 -37
  136. package/dist/core/substrate/schema.d.ts +17 -8
  137. package/dist/core/substrate/schema.js +14 -10
  138. package/dist/core/substrate/surface-match.d.ts +8 -7
  139. package/dist/core/substrate/surface-match.js +15 -14
  140. package/dist/core/user-settings.d.ts +4 -0
  141. package/dist/core/user-settings.js +1 -0
  142. package/dist/daemon/api/__tests__/profile-launch-gates.test.js +52 -3
  143. package/dist/daemon/api/handlers/human-requests.d.ts +2 -0
  144. package/dist/daemon/api/handlers/human-requests.js +409 -0
  145. package/dist/daemon/api/handlers/human.js +3 -0
  146. package/dist/daemon/api/handlers/inbox.js +3 -0
  147. package/dist/daemon/api/handlers/nodes.d.ts +1 -3
  148. package/dist/daemon/api/handlers/nodes.js +11 -46
  149. package/dist/daemon/api/handlers/prospective-chat-inventory.d.ts +2 -0
  150. package/dist/daemon/api/handlers/prospective-chat-inventory.js +59 -0
  151. package/dist/daemon/api/handlers/reviews.js +10 -2
  152. package/dist/daemon/api/server.js +4 -0
  153. package/dist/daemon/crtrd.js +6 -0
  154. package/dist/daemon/human/deliver-action.d.ts +16 -0
  155. package/dist/daemon/human/deliver-action.js +168 -0
  156. package/dist/daemon/human/finish.d.ts +8 -5
  157. package/dist/daemon/human/finish.js +45 -6
  158. package/dist/daemon/human/sweep.js +4 -1
  159. package/dist/daemon/reconcilers/human-delivery-lane.d.ts +10 -0
  160. package/dist/daemon/reconcilers/human-delivery-lane.js +41 -0
  161. package/dist/daemon/review/finish.d.ts +8 -3
  162. package/dist/daemon/review/finish.js +19 -1
  163. package/dist/types.d.ts +8 -0
  164. package/dist/types.js +1 -0
  165. package/package.json +1 -1
  166. package/runtime.lock.json +2 -2
  167. package/dist/builtin-memory/00-runtime-base.md +0 -55
  168. package/dist/builtin-memory/design.md +0 -55
  169. /package/dist/builtin-memory/{02-lifecycle/00-terminal.md → 02-turn-lifecycle/01-terminal.md} +0 -0
@@ -48,27 +48,6 @@ function attr(s) {
48
48
  .replace(/</g, '&lt;')
49
49
  .replace(/>/g, '&gt;');
50
50
  }
51
- function gitRootOf(p) {
52
- let d = p;
53
- const root = parse(d).root;
54
- while (true) {
55
- if (pathExists(`${d}${sep}.git`))
56
- return d;
57
- if (d === root)
58
- return null;
59
- const parent = dirname(d);
60
- if (parent === d)
61
- return null;
62
- d = parent;
63
- }
64
- }
65
- function disp(p) {
66
- const root = gitRootOf(p);
67
- if (!root)
68
- return p;
69
- const rel = relative(root, p);
70
- return rel === '' ? '.' : rel;
71
- }
72
51
  function isJunkAncestor(dir) {
73
52
  return dir.split(sep).some((segment) => JUNK_DIRS.has(segment));
74
53
  }
@@ -184,16 +163,26 @@ function docContentKey(doc) {
184
163
  return null;
185
164
  return `content-sha256:${createHash('sha256').update(body).digest('hex')}`;
186
165
  }
166
+ // Two shapes: element-with-body means the content is here; self-closing means
167
+ // it is not — a preview carries its routing line as `readWhen` metadata so the
168
+ // one-liner can never be mistaken for the document's content. No disk path:
169
+ // agents address memory by name through the command surface, and the explicit
170
+ // read leaf already returns `path`.
187
171
  function renderDocEnvelope(doc, rung) {
188
172
  if (rung === 'none')
189
173
  return null;
190
- let body = '';
191
- if (rung === 'content')
192
- body = doc.body.trim();
193
- else if (rung === 'preview')
194
- body = previewLine(doc);
195
- const attrs = `kind="${attr(doc.kind)}" name="${attr(doc.name)}" src="${attr(disp(doc.path))}"`;
196
- return body === '' ? `<memory ${attrs} />` : `<memory ${attrs}>\n${body}\n</memory>`;
174
+ let attrs = `kind="${attr(doc.kind)}" name="${attr(doc.name)}"`;
175
+ if (rung === 'content') {
176
+ const body = doc.body.trim();
177
+ if (body !== '')
178
+ return `<memory ${attrs}>\n${body}\n</memory>`;
179
+ }
180
+ else if (rung === 'preview') {
181
+ const pl = previewLine(doc);
182
+ if (pl !== '')
183
+ attrs += ` readWhen="${attr(pl)}"`;
184
+ }
185
+ return `<memory ${attrs} />`;
197
186
  }
198
187
  /** Render candidates at their matched rungs, gate-checked and deduped against
199
188
  * the transcript set (higher rungs pierce lower; a delivery records at its
@@ -240,7 +229,7 @@ export function renderOnReadDocsForSubject(subject, readFilePath, seen = new Map
240
229
  const readFrontmatter = readFileFrontmatter(absReadFile);
241
230
  const candidates = dedupeByPhysicalPath([...enclosingProjectDocs(absReadFile), ...resolvedDocs()])
242
231
  .filter((doc) => realpathOrSelf(doc.path) !== absReadFile)
243
- .map((doc) => ({ doc, rung: readDeliveryRung(doc, absReadFile, readFrontmatter) }))
232
+ .map((doc) => ({ doc, rung: readDeliveryRung(doc, subject, absReadFile, readFrontmatter) }))
244
233
  .filter(({ rung }) => rung !== 'none');
245
234
  return renderCandidates(subject, candidates, seen);
246
235
  }
@@ -265,7 +254,7 @@ export function renderWorkspaceOpenDocsForSubject(subject, cwd, profileId, seen
265
254
  return '';
266
255
  }
267
256
  const candidates = dedupeByPhysicalPath(docs)
268
- .map((doc) => ({ doc, rung: minRung(workspaceOpenRung(doc), doc.projectMemory) }))
257
+ .map((doc) => ({ doc, rung: minRung(workspaceOpenRung(doc, subject), doc.projectMemory) }))
269
258
  .filter(({ rung }) => rung !== 'none')
270
259
  .sort((a, b) => {
271
260
  const aRoot = owningRootOf(a.doc) ?? '';
@@ -283,7 +272,7 @@ export function renderWorkspaceOpenDocsForSubject(subject, cwd, profileId, seen
283
272
  export function memoryReadDocBlocks(subject, excludeRealpath, targetNames, seen) {
284
273
  const candidates = dedupeByPhysicalPath(resolvedDocs())
285
274
  .filter((doc) => realpathOrSelf(doc.path) !== excludeRealpath)
286
- .map((doc) => ({ doc, rung: memoryReadDeliveryRung(doc, targetNames) }))
275
+ .map((doc) => ({ doc, rung: memoryReadDeliveryRung(doc, subject, targetNames) }))
287
276
  .filter(({ rung }) => rung !== 'none');
288
277
  return renderCandidateBlocks(subject, candidates, seen);
289
278
  }
@@ -293,7 +282,7 @@ export function memoryReadDocBlocks(subject, excludeRealpath, targetNames, seen)
293
282
  * dedup set as the other channels. */
294
283
  export function renderOnCommandDocsForSubject(subject, command, seen = new Map()) {
295
284
  const candidates = dedupeByPhysicalPath(resolvedDocs())
296
- .map((doc) => ({ doc, rung: commandDeliveryRung(doc, command) }))
285
+ .map((doc) => ({ doc, rung: commandDeliveryRung(doc, subject, command) }))
297
286
  .filter(({ rung }) => rung !== 'none');
298
287
  return renderCandidates(subject, candidates, seen);
299
288
  }
@@ -1,6 +1,7 @@
1
1
  import type { InjectedDocs } from './injected-store.js';
2
- /** The system-prompt `<memory kind="preference">` block for `nodeId`: assemble
3
- * the node's subject from the canvas-db, render the pure block, then swap the
2
+ /** The system-prompt substrate splice for `nodeId` — shipped-layer protocol
3
+ * prose followed by the `<memory kind="preference">` block: assemble
4
+ * the node's subject from the canvas-db, render the pure splice, then swap the
4
5
  * literal `$CRTR_CONTEXT_DIR` env-var token for the node's real absolute path
5
6
  * so agents don't paste it into the non-expanding Write tool (see
6
7
  * interpolateNodePaths). A null subject (unknown id) still renders the
@@ -8,8 +8,9 @@
8
8
  import { interpolateNodePaths } from '../canvas/paths.js';
9
9
  import { assembleNodeSubject } from './subject.js';
10
10
  import { renderPreferencesForSubject, renderKnowledgeForSubject } from './render.js';
11
- /** The system-prompt `<memory kind="preference">` block for `nodeId`: assemble
12
- * the node's subject from the canvas-db, render the pure block, then swap the
11
+ /** The system-prompt substrate splice for `nodeId` — shipped-layer protocol
12
+ * prose followed by the `<memory kind="preference">` block: assemble
13
+ * the node's subject from the canvas-db, render the pure splice, then swap the
13
14
  * literal `$CRTR_CONTEXT_DIR` env-var token for the node's real absolute path
14
15
  * so agents don't paste it into the non-expanding Write tool (see
15
16
  * interpolateNodePaths). A null subject (unknown id) still renders the
@@ -8,11 +8,10 @@
8
8
  // then the `preview`+`name` docs render as ONE catalog tree, each at its own
9
9
  // rung (preview → a `# read when:` routing line; name → a bare entry); a doc
10
10
  // with NO boot entry leaks only into its dir's `[+N more]` count, never a
11
- // name — EXCEPT a node-local doc, whose contract is "node-local rides into
12
- // knowledge" unconditionally: renderKnowledgeForSubject floors a bootless
13
- // node-local doc up to `name` so it still shows its bare name rather than
14
- // collapsing into a hidden count. Only gate evaluation removes a node-local
15
- // doc outright.
11
+ // name — EXCEPT a node-local doc with no boot entry: renderKnowledgeForSubject
12
+ // floors it up to `name` so it still shows its bare name rather than
13
+ // collapsing into a hidden count. A node-local doc with boot entries remains
14
+ // absent when none of those entries participates.
16
15
  //
17
16
  // • the SYSTEM-PROMPT half — `<memory kind="preference">`
18
17
  // (renderPreferencesSection) — the always-present memory-usage guidance plus
@@ -27,11 +26,11 @@
27
26
  //
28
27
  // The pipeline is explicitly TWO-STEP and must never run in the other order:
29
28
  // 1. SELECT WINNERS — MemoryDoc → parseSubstrateDoc → (null-filter
30
- // non-substrate) → per-doc boot rung (`bootRung`, the max `at` over the
31
- // doc's `on: boot` surfaces entries) → gatePasses → first-wins DEDUP by
29
+ // non-substrate) → per-doc boot rung (`bootRung`, the max `at` over
30
+ // participating `on: boot` surfaces entries) → gatePasses → first-wins DEDUP by
32
31
  // name over the resolver's precedence order (nearest project > user >
33
32
  // builtin). Node-local docs follow the same gate rule, but the
34
- // knowledge-block render floors a bootless node-local doc's rung to
33
+ // knowledge-block render floors only a node-local doc with no boot entry to
35
34
  // `name` — the one rung-floor exemption, scoped to the node-local store
36
35
  // only.
37
36
  // 2. RENDER IN DISPLAY ORDER — split winners into content vs. the rest;
@@ -108,7 +107,7 @@ function selectWinners(subject, kind) {
108
107
  .filter((d) => d.kind === kind)
109
108
  .filter((d) => d.projectMemory !== 'none')
110
109
  .filter((d) => gatePasses(d, subject))
111
- .map((d) => ({ ...d, bootRung: minRung(bootRung(d), d.projectMemory) }));
110
+ .map((d) => ({ ...d, bootRung: minRung(bootRung(d, subject), d.projectMemory) }));
112
111
  return dedupeFirstWins(eligible);
113
112
  }
114
113
  // ---------------------------------------------------------------------------
@@ -183,9 +182,16 @@ function compareTreePosition(a, b) {
183
182
  }
184
183
  return as.length - bs.length;
185
184
  }
185
+ /** A doc the running agent can revise through `crtr memory edit`: any native
186
+ * scope-store doc (user/profile/project/node). Shipped docs — builtin and
187
+ * plugin-mounted — are not editable from a node; the preference render treats
188
+ * them as operating protocol, not memory (see buildPreferencesBlock). */
189
+ function isAgentEditable(doc) {
190
+ return doc.scope !== 'builtin' && doc.plugin === undefined;
191
+ }
186
192
  /** STEP 2 — split deduped `winners` by rung and render each group in its
187
193
  * display order: `content` docs first, as clean prose (general→specific,
188
- * then tree position, then filename — no tree chrome, no name label); every
194
+ * then tree position, then filename — no tree chrome); every
189
195
  * other winner (preview/name/hidden-bootless) folds into ONE catalog tree
190
196
  * exactly as before. `nodeLocalSet` marks which winners came from the
191
197
  * node-local store (ranked most-specific in content ordering); omit it for a
@@ -216,13 +222,9 @@ function renderGrouped(winners, rootLabel, nodeLocalSet = new Set()) {
216
222
  * Returned across ALL kinds — node-local is the catch-all this-node store and
217
223
  * rides into the knowledge block.
218
224
  *
219
- * Node-local docs follow the same GATE rule as every other store — gate
220
- * evaluation is the only thing that removes a doc here. Rung is different:
221
- * node-local's contract is "node-local rides into knowledge" unconditionally,
222
- * so a doc with no boot entry is retained by this function — it is the caller
223
- * (renderKnowledgeForSubject) that floors a bootless node-local doc up to
224
- * `name` so it still shows its bare name instead of collapsing into a
225
- * `[+N more]` count. */
225
+ * Node-local docs follow the same GATE rule as every other store. A doc with
226
+ * no boot entry is retained here; renderKnowledgeForSubject floors it to
227
+ * `name` so it shows as a bare name instead of collapsing into `[+N more]`. */
226
228
  function nodeLocalDocs(nodeId, subject) {
227
229
  const dir = memoryDir(nodeId);
228
230
  if (!pathExists(dir))
@@ -489,37 +491,62 @@ function buildSubPersonaMenu(kind) {
489
491
  lines.join('\n'));
490
492
  }
491
493
  // ---------------------------------------------------------------------------
492
- // 1. Preferences — `<memory kind="preference">` (system prompt).
494
+ // 1. Preferences — protocol prose + `<memory kind="preference">` (system prompt).
493
495
  // ---------------------------------------------------------------------------
494
- /** The system-prompt memory block: the always-present memory-usage guidance,
495
- * then (when any are eligible) the `kind: preference` docs GROUPED by rung —
496
- * content-rung docs first as clean prose (general→specific), then the
497
- * preview/name catalog tree. ALWAYS returns a non-empty `<memory
498
- * kind="preference">` block — the guidance frame is unconditional, so the
499
- * system-prompt splice is never empty even when no preference is eligible. */
496
+ /** The system-prompt substrate splice, TWO parts in one string.
497
+ *
498
+ * 1. The operating protocol: shipped (builtin/plugin) content-rung bodies as
499
+ * bare prose, plus the sub-persona menu. No memory envelope — the protocol
500
+ * is the rules of the node's world, and wrapping it as recalled "memory:
501
+ * preference" content gave it softer epistemic status than the
502
+ * instructions it is; the intro/outro sentences ("how the user wants you
503
+ * to behave", "update the preference") were also false of it.
504
+ * 2. The `<memory kind="preference">` block: the always-present memory-usage
505
+ * guidance, then (when any are eligible) each agent-editable doc wrapped
506
+ * `<preference name="…">…</preference>` — the name is the edit handle the
507
+ * outro's update directive needs — then the preview/name catalog tree.
508
+ * Every frame sentence is true of everything inside it.
509
+ *
510
+ * Ordering across the two parts is unchanged from the single-block render
511
+ * (shipped→editable was already the scope sort), so the shared cache prefix
512
+ * across nodes is preserved. ALWAYS returns a non-empty splice — the guidance
513
+ * frame is unconditional, so it is never empty even when no preference is
514
+ * eligible. */
500
515
  function buildPreferencesBlock(subject, seen) {
501
- let body = MEMORY_USAGE_GUIDANCE;
502
516
  let contentDocs = [];
503
517
  let tree = '';
518
+ let protocol = '';
519
+ let memBody = MEMORY_USAGE_GUIDANCE;
504
520
  if (subject !== null) {
505
521
  const winners = selectWinners(subject, 'preference');
506
522
  seedBootDelivery(seen, winners);
507
523
  const grouped = renderGrouped(winners, 'preferences');
508
524
  contentDocs = grouped.contentDocs;
509
525
  tree = grouped.tree;
526
+ const shipped = contentDocs
527
+ .filter((d) => !isAgentEditable(d))
528
+ .map((d) => d.body.trim())
529
+ .filter((b) => b !== '');
530
+ const editable = contentDocs
531
+ .filter((d) => isAgentEditable(d))
532
+ .map((d) => {
533
+ const b = d.body.trim();
534
+ return b === '' ? '' : `<preference name="${d.name}">\n${b}\n</preference>`;
535
+ })
536
+ .filter((b) => b !== '');
510
537
  const subPersonaMenu = buildSubPersonaMenu(subject.kind);
511
- if (grouped.contentProse !== '' || grouped.tree !== '' || subPersonaMenu !== '') {
512
- body += `\n\n${PREFERENCES_INTRO}`;
513
- if (grouped.contentProse !== '')
514
- body += `\n\n${grouped.contentProse}`;
515
- if (subPersonaMenu !== '')
516
- body += `\n\n${subPersonaMenu}`;
517
- if (grouped.tree !== '')
518
- body += `\n\n${grouped.tree}`;
519
- body += `\n\n${PREFERENCES_OUTRO}`;
538
+ protocol = [...shipped, ...(subPersonaMenu === '' ? [] : [subPersonaMenu])].join('\n\n');
539
+ if (editable.length > 0 || tree !== '') {
540
+ memBody += `\n\n${PREFERENCES_INTRO}`;
541
+ if (editable.length > 0)
542
+ memBody += `\n\n${editable.join('\n\n')}`;
543
+ if (tree !== '')
544
+ memBody += `\n\n${tree}`;
545
+ memBody += `\n\n${PREFERENCES_OUTRO}`;
520
546
  }
521
547
  }
522
- return { block: `<memory kind="preference">\n${body}\n</memory>`, contentDocs, tree };
548
+ const memoryBlock = `<memory kind="preference">\n${memBody}\n</memory>`;
549
+ return { block: protocol === '' ? memoryBlock : `${protocol}\n\n${memoryBlock}`, contentDocs, tree };
523
550
  }
524
551
  export function renderPreferencesForSubject(subject, seen) {
525
552
  return buildPreferencesBlock(subject, seen);
@@ -542,8 +569,8 @@ export function renderPreferencesForSubject(subject, seen) {
542
569
  * eligible. */
543
570
  export function renderKnowledgeForSubject(subject, nodeId, seen) {
544
571
  const nodeLocal = nodeLocalDocs(nodeId, subject).map((d) => {
545
- const r = bootRung(d);
546
- return { ...d, bootRung: isVisible(r) ? r : 'name' };
572
+ const r = bootRung(d, subject);
573
+ return { ...d, bootRung: isVisible(r) || d.surfaces.some((e) => e.on === 'boot') ? r : 'name' };
547
574
  });
548
575
  const nodeLocalSet = new Set(nodeLocal);
549
576
  // Resolver winners first: first-wins dedup keeps the resolver's copy over a
@@ -1,5 +1,6 @@
1
1
  import { type ProfileProjectMemory } from '../../api/dto/profiles.js';
2
2
  import type { MemoryDoc, MemoryScope } from '../memory-resolver.js';
3
+ import type { NodeConfigSubject } from './subject-fields.js';
3
4
  export declare const KINDS: readonly ["knowledge", "preference"];
4
5
  export type DocKind = (typeof KINDS)[number];
5
6
  /** Is `v` one of the two valid document kinds? */
@@ -14,7 +15,7 @@ export declare function rungAtLeast(r: Rung, min: Rung): boolean;
14
15
  * authored rung against its project relationship's cap. */
15
16
  export declare function minRung(a: Rung, b: Rung): Rung;
16
17
  /** Strip an optional `NN-` ordering prefix from ONE path segment (file or
17
- * directory display name). `00-runtime-base` -> `runtime-base`; `spine` (no
18
+ * directory display name). `00-runtime-base/00-authoring` -> `runtime-base/authoring`; `spine` (no
18
19
  * prefix) is unchanged. */
19
20
  export declare function normalizeNameSegment(segment: string): string;
20
21
  /** Normalize a full slash-separated path-derived doc name by stripping the
@@ -34,10 +35,10 @@ export type SurfaceEvent = (typeof SURFACE_EVENTS)[number];
34
35
  export declare const SURFACE_RUNGS: readonly ["name", "preview", "content"];
35
36
  export type SurfaceRung = (typeof SURFACE_RUNGS)[number];
36
37
  /** One routing entry: when event `on` exposes a subject that fits `match`
37
- * (and `matchFrontmatter`, `read` only), the doc delivers at rung `at`.
38
- * Constraints within one entry AND together (each present constraint must
39
- * fit; a `match` list is satisfied by any one glob); multiple entries per
40
- * event OR together. */
38
+ * (and `matchFrontmatter`, `read` only) and its optional node-config `gate`,
39
+ * the doc delivers at rung `at`. Constraints within one entry AND together
40
+ * (each present constraint must fit; a `match` list is satisfied by any one
41
+ * glob); multiple entries per event OR together. */
41
42
  export interface SurfaceEntry {
42
43
  on: SurfaceEvent;
43
44
  /** Globs vs the event's subject namespace. Required on read/memory-read/
@@ -47,11 +48,15 @@ export interface SurfaceEntry {
47
48
  /** Predicate over the read file's own YAML frontmatter — `read` event only.
48
49
  * Frontmatter key `match-frontmatter`. */
49
50
  matchFrontmatter?: GatePredicate;
51
+ /** Optional predicate over the current node config. Frontmatter key `gate`.
52
+ * The document-level gate remains the hard eligibility check. */
53
+ gate?: GatePredicate;
50
54
  at: SurfaceRung;
51
55
  }
52
- /** A doc's boot rung: the highest `at` over its `boot` entries, `none` when
53
- * it carries no boot entry. The boot render's per-doc selection input. */
54
- export declare function bootRung(doc: Pick<SubstrateSchema, 'surfaces'>): Rung;
56
+ /** A doc's boot rung for `subject`: the highest `at` over participating
57
+ * `boot` entries, `none` when it carries none. The boot render's per-doc
58
+ * selection input. */
59
+ export declare function bootRung(doc: Pick<SubstrateSchema, 'surfaces'>, subject: NodeConfigSubject): Rung;
55
60
  /** A gate predicate tree, evaluated by predicate.ts (`evalCondition`) against
56
61
  * the node-config subject. Typed loosely on purpose — the matcher engine owns
57
62
  * validation; structurally it is a field→matcher map with optional
@@ -111,6 +116,10 @@ export interface SubstrateDoc extends SubstrateSchema {
111
116
  name: string;
112
117
  /** The scope this doc resolved from. */
113
118
  scope: MemoryScope;
119
+ /** Owning plugin name when the doc is mounted from an installed plugin;
120
+ * undefined for a native scope doc. Shipped (builtin/plugin) docs and
121
+ * agent-editable scope docs render differently at boot (see renderGrouped). */
122
+ plugin?: string;
114
123
  /** Absolute path to the source .md. */
115
124
  path: string;
116
125
  /** Document body, frontmatter stripped. */
@@ -8,6 +8,7 @@
8
8
  // never throw): invalid `surfaces` entries are dropped rather than crashing a
9
9
  // render, and `crtr memory lint` owns strict authoring-time enforcement.
10
10
  import { PROFILE_PROJECT_MEMORY_VALUES } from '../../api/dto/profiles.js';
11
+ import { surfaceEntryGatePasses } from './gate.js';
11
12
  // ---------------------------------------------------------------------------
12
13
  // Kinds — the two semantic kinds (design §3): `knowledge` (consult — procedural
13
14
  // playbooks + factual references merged) vs `preference` (behave — standing
@@ -43,18 +44,18 @@ export function minRung(a, b) {
43
44
  // ---------------------------------------------------------------------------
44
45
  // Display-name normalization — the optional `NN-` ordering pin. A doc's
45
46
  // physical path may carry a two-digit numeric prefix on a file or directory
46
- // segment (`00-runtime-base.md`, `01-spine/`) purely to pin structural order
47
+ // segment (`00-runtime-base/`, `01-spine/`) purely to pin structural order
47
48
  // against natural sort (design: builtin prompt corpus, used sparingly). The
48
49
  // prefix is NEVER part of the doc's identity: every identity-facing surface —
49
50
  // MemoryDoc.name, direct lookup, leaf fallback, `crtr memory read`/`list`,
50
51
  // prompt render, on-read display — derives from the NORMALIZED segments, so
51
- // `00-runtime-base` displays/dedups/resolves as `runtime-base`. When a doc
52
+ // `00-runtime-base/00-authoring` displays/dedups/resolves as `runtime-base/authoring`. When a doc
52
53
  // sets an explicit frontmatter `name`, that normalized name wins instead of
53
54
  // the path-derived fallback. Only the physical path keeps the prefix.
54
55
  // ---------------------------------------------------------------------------
55
56
  const NUMERIC_PREFIX_RE = /^\d{2}-/;
56
57
  /** Strip an optional `NN-` ordering prefix from ONE path segment (file or
57
- * directory display name). `00-runtime-base` -> `runtime-base`; `spine` (no
58
+ * directory display name). `00-runtime-base/00-authoring` -> `runtime-base/authoring`; `spine` (no
58
59
  * prefix) is unchanged. */
59
60
  export function normalizeNameSegment(segment) {
60
61
  return segment.replace(NUMERIC_PREFIX_RE, '');
@@ -95,12 +96,13 @@ export const SURFACE_EVENTS = ['boot', 'workspace-open', 'read', 'memory-read',
95
96
  * absence of an entry. (`Rung`'s `none` survives only as the internal
96
97
  * ranking floor, e.g. `bootRung` of a doc with no boot entry.) */
97
98
  export const SURFACE_RUNGS = ['name', 'preview', 'content'];
98
- /** A doc's boot rung: the highest `at` over its `boot` entries, `none` when
99
- * it carries no boot entry. The boot render's per-doc selection input. */
100
- export function bootRung(doc) {
99
+ /** A doc's boot rung for `subject`: the highest `at` over participating
100
+ * `boot` entries, `none` when it carries none. The boot render's per-doc
101
+ * selection input. */
102
+ export function bootRung(doc, subject) {
101
103
  let r = 'none';
102
104
  for (const e of doc.surfaces) {
103
- if (e.on !== 'boot')
105
+ if (e.on !== 'boot' || !surfaceEntryGatePasses(e, subject))
104
106
  continue;
105
107
  if (rungRank(e.at) > rungRank(r))
106
108
  r = e.at;
@@ -142,7 +144,7 @@ export function parseSubstrateDoc(doc) {
142
144
  const schema = parseSubstrateFrontmatter(doc.frontmatter);
143
145
  if (schema === null)
144
146
  return null;
145
- return { ...schema, name: doc.name, scope: doc.scope, path: doc.path, body: doc.body, projectMemory: doc.projectMemory };
147
+ return { ...schema, name: doc.name, scope: doc.scope, plugin: doc.plugin, path: doc.path, body: doc.body, projectMemory: doc.projectMemory };
146
148
  }
147
149
  /** The kind a doc resolves to for `--kind` filtering/matching: the substrate
148
150
  * kind when the frontmatter parses as a valid substrate doc, else the raw
@@ -194,8 +196,8 @@ function parseGate(v) {
194
196
  * dropped (bad `on`, bad `at`, a read/memory-read/command entry left with
195
197
  * neither `match` nor — read only — `match-frontmatter`). `match-frontmatter`
196
198
  * on a non-read event is dropped from the entry, which survives iff it still
197
- * carries a `match`. Lint owns strict enforcement; the runtime parser maps
198
- * over many docs and must never throw. */
199
+ * carries a `match`; an invalid entry `gate` is dropped. Lint owns strict
200
+ * enforcement; the runtime parser maps over many docs and must never throw. */
199
201
  function parseSurfaces(v) {
200
202
  if (!Array.isArray(v))
201
203
  return [];
@@ -212,6 +214,7 @@ function parseSurfaces(v) {
212
214
  continue;
213
215
  const match = parseMatchGlobs(rec['match']);
214
216
  const matchFrontmatter = on === 'read' ? parseGate(rec['match-frontmatter']) : undefined;
217
+ const gate = parseGate(rec['gate']);
215
218
  if ((on === 'read' || on === 'memory-read' || on === 'command') &&
216
219
  match === undefined &&
217
220
  matchFrontmatter === undefined) {
@@ -222,6 +225,7 @@ function parseSurfaces(v) {
222
225
  at: at,
223
226
  ...(match !== undefined ? { match } : {}),
224
227
  ...(matchFrontmatter !== undefined ? { matchFrontmatter } : {}),
228
+ ...(gate !== undefined ? { gate } : {}),
225
229
  });
226
230
  }
227
231
  return out;
@@ -1,4 +1,5 @@
1
1
  import { type Rung, type SubstrateDoc, type SurfaceEntry } from './schema.js';
2
+ import type { NodeConfigSubject } from './subject-fields.js';
2
3
  /** The project directory owning a doc's store: the path segment stack above
3
4
  * the doc's `.crouter` dir. `null` when the doc's path carries no `.crouter`
4
5
  * segment (builtin docs). */
@@ -13,20 +14,20 @@ export declare function underOwningRoot(doc: Pick<SubstrateDoc, 'path'>, absFile
13
14
  * realpathed. `readFrontmatter` is the read file's own parsed YAML (empty
14
15
  * for non-markdown files); an empty record never satisfies
15
16
  * `matchFrontmatter`. */
16
- export declare function matchesReadEntry(entry: SurfaceEntry, doc: Pick<SubstrateDoc, 'path' | 'scope'>, absReadFile: string, readFrontmatter: Record<string, unknown>): boolean;
17
+ export declare function matchesReadEntry(entry: SurfaceEntry, doc: Pick<SubstrateDoc, 'path' | 'scope'>, subject: NodeConfigSubject, absReadFile: string, readFrontmatter: Record<string, unknown>): boolean;
17
18
  /** Does a `memory-read` entry on the doc named `carrierName` fit a resolved
18
19
  * read of a doc known by `targetNames`? */
19
- export declare function matchesMemoryReadEntry(entry: SurfaceEntry, carrierName: string, targetNames: string[]): boolean;
20
+ export declare function matchesMemoryReadEntry(entry: SurfaceEntry, carrierName: string, subject: NodeConfigSubject | null, targetNames: string[]): boolean;
20
21
  /** Does a `command` entry fit the executed command string? */
21
- export declare function matchesCommandEntry(entry: SurfaceEntry, command: string): boolean;
22
+ export declare function matchesCommandEntry(entry: SurfaceEntry, subject: NodeConfigSubject, command: string): boolean;
22
23
  /** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
23
- export declare function readDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'path' | 'scope'>, absReadFile: string, readFrontmatter: Record<string, unknown>): Rung;
24
+ export declare function readDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'path' | 'scope'>, subject: NodeConfigSubject, absReadFile: string, readFrontmatter: Record<string, unknown>): Rung;
24
25
  /** The doc's workspace-open rung. Presence of an entry is the match; callers
25
26
  * scope the corpus to project stores mounted by the subject's cwd/profile. */
26
- export declare function workspaceOpenRung(doc: Pick<SubstrateDoc, 'surfaces'>): Rung;
27
+ export declare function workspaceOpenRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject): Rung;
27
28
  /** The doc's delivery rung for a `crtr memory read` that resolved a doc
28
29
  * known by `targetNames` (canonical name, plus the mount-stripped name for
29
30
  * plugin-mounted docs). The doc's own `name` anchors `./` globs. */
30
- export declare function memoryReadDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'name'>, targetNames: string[]): Rung;
31
+ export declare function memoryReadDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces' | 'name'>, subject: NodeConfigSubject | null, targetNames: string[]): Rung;
31
32
  /** The doc's delivery rung for an executed command string. */
32
- export declare function commandDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, command: string): Rung;
33
+ export declare function commandDeliveryRung(doc: Pick<SubstrateDoc, 'surfaces'>, subject: NodeConfigSubject, command: string): Rung;
@@ -13,6 +13,7 @@ import { basename, isAbsolute, matchesGlob, relative, sep } from 'node:path';
13
13
  import { CRTR_DIR_NAME } from '../../types.js';
14
14
  import { realpathOrSelf } from '../fs-utils.js';
15
15
  import { evalCondition } from '../predicate.js';
16
+ import { surfaceEntryGatePasses } from './gate.js';
16
17
  import { rungRank } from './schema.js';
17
18
  /** The project directory owning a doc's store: the path segment stack above
18
19
  * the doc's `.crouter` dir. `null` when the doc's path carries no `.crouter`
@@ -62,8 +63,8 @@ function readGlobMatches(glob, doc, absReadFile) {
62
63
  * realpathed. `readFrontmatter` is the read file's own parsed YAML (empty
63
64
  * for non-markdown files); an empty record never satisfies
64
65
  * `matchFrontmatter`. */
65
- export function matchesReadEntry(entry, doc, absReadFile, readFrontmatter) {
66
- if (entry.on !== 'read')
66
+ export function matchesReadEntry(entry, doc, subject, absReadFile, readFrontmatter) {
67
+ if (entry.on !== 'read' || !surfaceEntryGatePasses(entry, subject))
67
68
  return false;
68
69
  if (entry.match !== undefined && !entry.match.some((g) => readGlobMatches(g, doc, absReadFile))) {
69
70
  return false;
@@ -97,8 +98,8 @@ function memoryReadGlobMatches(glob, carrierName, targetNames) {
97
98
  }
98
99
  /** Does a `memory-read` entry on the doc named `carrierName` fit a resolved
99
100
  * read of a doc known by `targetNames`? */
100
- export function matchesMemoryReadEntry(entry, carrierName, targetNames) {
101
- if (entry.on !== 'memory-read' || entry.match === undefined)
101
+ export function matchesMemoryReadEntry(entry, carrierName, subject, targetNames) {
102
+ if (entry.on !== 'memory-read' || entry.match === undefined || !surfaceEntryGatePasses(entry, subject))
102
103
  return false;
103
104
  return entry.match.some((g) => memoryReadGlobMatches(g, carrierName, targetNames));
104
105
  }
@@ -119,8 +120,8 @@ function commandGlobMatches(glob, command) {
119
120
  }
120
121
  }
121
122
  /** Does a `command` entry fit the executed command string? */
122
- export function matchesCommandEntry(entry, command) {
123
- if (entry.on !== 'command' || entry.match === undefined)
123
+ export function matchesCommandEntry(entry, subject, command) {
124
+ if (entry.on !== 'command' || entry.match === undefined || !surfaceEntryGatePasses(entry, subject))
124
125
  return false;
125
126
  return entry.match.some((g) => commandGlobMatches(g, command));
126
127
  }
@@ -131,10 +132,10 @@ export function matchesCommandEntry(entry, command) {
131
132
  // floor, never an authored rung).
132
133
  // ---------------------------------------------------------------------------
133
134
  /** The doc's delivery rung for a read of `absReadFile` (already realpathed). */
134
- export function readDeliveryRung(doc, absReadFile, readFrontmatter) {
135
+ export function readDeliveryRung(doc, subject, absReadFile, readFrontmatter) {
135
136
  let r = 'none';
136
137
  for (const e of doc.surfaces) {
137
- if (!matchesReadEntry(e, doc, absReadFile, readFrontmatter))
138
+ if (!matchesReadEntry(e, doc, subject, absReadFile, readFrontmatter))
138
139
  continue;
139
140
  if (rungRank(e.at) > rungRank(r))
140
141
  r = e.at;
@@ -143,10 +144,10 @@ export function readDeliveryRung(doc, absReadFile, readFrontmatter) {
143
144
  }
144
145
  /** The doc's workspace-open rung. Presence of an entry is the match; callers
145
146
  * scope the corpus to project stores mounted by the subject's cwd/profile. */
146
- export function workspaceOpenRung(doc) {
147
+ export function workspaceOpenRung(doc, subject) {
147
148
  let r = 'none';
148
149
  for (const e of doc.surfaces) {
149
- if (e.on !== 'workspace-open')
150
+ if (e.on !== 'workspace-open' || !surfaceEntryGatePasses(e, subject))
150
151
  continue;
151
152
  if (rungRank(e.at) > rungRank(r))
152
153
  r = e.at;
@@ -156,10 +157,10 @@ export function workspaceOpenRung(doc) {
156
157
  /** The doc's delivery rung for a `crtr memory read` that resolved a doc
157
158
  * known by `targetNames` (canonical name, plus the mount-stripped name for
158
159
  * plugin-mounted docs). The doc's own `name` anchors `./` globs. */
159
- export function memoryReadDeliveryRung(doc, targetNames) {
160
+ export function memoryReadDeliveryRung(doc, subject, targetNames) {
160
161
  let r = 'none';
161
162
  for (const e of doc.surfaces) {
162
- if (!matchesMemoryReadEntry(e, doc.name, targetNames))
163
+ if (!matchesMemoryReadEntry(e, doc.name, subject, targetNames))
163
164
  continue;
164
165
  if (rungRank(e.at) > rungRank(r))
165
166
  r = e.at;
@@ -167,10 +168,10 @@ export function memoryReadDeliveryRung(doc, targetNames) {
167
168
  return r;
168
169
  }
169
170
  /** The doc's delivery rung for an executed command string. */
170
- export function commandDeliveryRung(doc, command) {
171
+ export function commandDeliveryRung(doc, subject, command) {
171
172
  let r = 'none';
172
173
  for (const e of doc.surfaces) {
173
- if (!matchesCommandEntry(e, command))
174
+ if (!matchesCommandEntry(e, subject, command))
174
175
  continue;
175
176
  if (rungRank(e.at) > rungRank(r))
176
177
  r = e.at;
@@ -326,6 +326,10 @@ export declare const CONFIG_SETTINGS_COVERAGE: {
326
326
  surface: "specialized";
327
327
  owner: string;
328
328
  };
329
+ humanActions: {
330
+ surface: "specialized";
331
+ owner: string;
332
+ };
329
333
  };
330
334
  /** The exhaustive top-level key list used by config persistence and help. */
331
335
  export declare const SCOPE_CONFIG_KEYS: (keyof ScopeConfig)[];
@@ -389,6 +389,7 @@ export const CONFIG_SETTINGS_COVERAGE = {
389
389
  remoteCanvas: { surface: 'specialized', owner: 'canvas config commands' },
390
390
  spawnEnv: { surface: 'specialized', owner: 'spawn environment allow-list' },
391
391
  bin: { surface: 'specialized', owner: 'bare-binary contributions on every node PATH' },
392
+ humanActions: { surface: 'specialized', owner: 'scope-configured human completion actions' },
392
393
  };
393
394
  /** The exhaustive top-level key list used by config persistence and help. */
394
395
  export const SCOPE_CONFIG_KEYS = Object.keys(CONFIG_SETTINGS_COVERAGE);