@north-light/crouter 0.3.233 → 0.3.235

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 (411) hide show
  1. package/dist/api/client.js +16 -15
  2. package/dist/api/dto/nodes.d.ts +11 -0
  3. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/integration/provider-rotation.test.ts +1 -1
  4. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +14 -17
  5. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/crtr-commands/index.ts +7 -11
  6. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +8 -8
  7. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +10 -11
  8. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/pi-shell-runner.ts +2 -2
  9. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +3 -3
  10. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -7
  11. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +8 -8
  12. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/statusline.ts +6 -6
  13. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/sysprompt-window.ts +3 -3
  14. package/dist/builtin-pi-packages/pi-crtr-extensions/lib/subscription-state.ts +1 -1
  15. package/dist/clients/attach/chrome/ticket-panel.js +15 -2
  16. package/dist/clients/attach/overlays/graph.js +0 -3
  17. package/dist/clients/attach/overlays/mcp.js +1 -2
  18. package/dist/clients/attach/render/chat-view.js +0 -12
  19. package/dist/clients/attach/render/crtr-output.js +0 -2
  20. package/dist/clients/attach/render/tool-calls.js +0 -6
  21. package/dist/clients/attach/slash/dispatch.js +8 -11
  22. package/dist/clients/attach/viewer.js +535 -537
  23. package/dist/clients/conversation/projection.js +0 -2
  24. package/dist/clients/inbox/controller.js +22 -3
  25. package/dist/clients/inbox/review/companion-pane.js +0 -4
  26. package/dist/clients/inbox/review-adapter.js +0 -7
  27. package/dist/clients/inbox/tui/slots.d.ts +3 -3
  28. package/dist/clients/inbox/tui/types.d.ts +2 -2
  29. package/dist/commands/api-client.js +0 -8
  30. package/dist/commands/attention.js +1 -9
  31. package/dist/commands/canvas-browse.js +2 -1
  32. package/dist/commands/canvas-config.js +0 -10
  33. package/dist/commands/canvas-history.js +2 -1
  34. package/dist/commands/cron.js +10 -23
  35. package/dist/commands/dashboard.js +0 -2
  36. package/dist/commands/human/feedback.js +2 -1
  37. package/dist/commands/human/prompts.js +2 -5
  38. package/dist/commands/human/queue.js +2 -7
  39. package/dist/commands/human/request.js +0 -14
  40. package/dist/commands/human/review.js +2 -1
  41. package/dist/commands/human.js +1 -1
  42. package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +4 -4
  43. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.d.ts +1 -0
  44. package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +96 -0
  45. package/dist/commands/memory/delete.js +1 -1
  46. package/dist/commands/memory/edit.js +6 -3
  47. package/dist/commands/memory/find.js +2 -2
  48. package/dist/commands/memory/lint.js +2 -1
  49. package/dist/commands/memory/list.d.ts +4 -0
  50. package/dist/commands/memory/list.js +20 -14
  51. package/dist/commands/memory/move.js +1 -1
  52. package/dist/commands/memory/read.d.ts +2 -7
  53. package/dist/commands/memory/read.js +5 -16
  54. package/dist/commands/memory/shared.d.ts +1 -0
  55. package/dist/commands/memory/shared.js +17 -8
  56. package/dist/commands/memory/write.js +8 -4
  57. package/dist/commands/node/bash.js +2 -1
  58. package/dist/commands/node/create.js +2 -1
  59. package/dist/commands/node/inspect.js +11 -10
  60. package/dist/commands/node/lifecycle.js +5 -13
  61. package/dist/commands/node/message.js +11 -6
  62. package/dist/commands/node/subscription.js +7 -2
  63. package/dist/commands/node/wait.js +9 -4
  64. package/dist/commands/node-context.js +2 -1
  65. package/dist/commands/node-lifecycle-revive.js +0 -2
  66. package/dist/commands/node-worktree.js +2 -1
  67. package/dist/commands/pkg/browse/actions.d.ts +1 -1
  68. package/dist/commands/pkg/browse/actions.js +4 -4
  69. package/dist/commands/pkg/browse/catalog.d.ts +1 -1
  70. package/dist/commands/pkg/browse/catalog.js +15 -15
  71. package/dist/commands/pkg/browse/command-view.d.ts +1 -1
  72. package/dist/commands/pkg/browse/command-view.js +3 -3
  73. package/dist/commands/pkg/browse/detail-panel.d.ts +1 -1
  74. package/dist/commands/pkg/browse/detail-panel.js +8 -8
  75. package/dist/commands/pkg/browse/doc-view.d.ts +1 -1
  76. package/dist/commands/pkg/browse/doc-view.js +3 -3
  77. package/dist/commands/pkg/browse/list-panel.d.ts +1 -1
  78. package/dist/commands/pkg/browse/list-panel.js +3 -3
  79. package/dist/commands/pkg/browse/model.d.ts +3 -3
  80. package/dist/commands/pkg/browse/shell.d.ts +2 -2
  81. package/dist/commands/pkg/browse/shell.js +13 -13
  82. package/dist/commands/pkg/plugin-manage.js +3 -2
  83. package/dist/commands/pkg/shared.js +0 -2
  84. package/dist/commands/pkg.js +1 -0
  85. package/dist/commands/profile/delete.js +2 -1
  86. package/dist/commands/profile/rename.js +1 -1
  87. package/dist/commands/push.js +2 -3
  88. package/dist/commands/revive.js +0 -2
  89. package/dist/commands/surface/node/placement.js +2 -1
  90. package/dist/commands/surface-edit.js +2 -1
  91. package/dist/commands/sys/config.js +1 -4
  92. package/dist/commands/sys/daemon.js +0 -10
  93. package/dist/commands/sys/doctor.js +3 -2
  94. package/dist/commands/sys/panels/broker-limits-panel.d.ts +2 -2
  95. package/dist/commands/sys/panels/broker-limits-panel.js +4 -4
  96. package/dist/commands/sys/panels/dependencies-panel.d.ts +2 -2
  97. package/dist/commands/sys/panels/dependencies-panel.js +2 -2
  98. package/dist/commands/sys/panels/keybindings-panel.d.ts +1 -1
  99. package/dist/commands/sys/panels/keybindings-panel.js +5 -5
  100. package/dist/commands/sys/panels/models-panel.d.ts +4 -4
  101. package/dist/commands/sys/panels/models-panel.js +11 -10
  102. package/dist/commands/sys/panels/plugins-panel.d.ts +2 -2
  103. package/dist/commands/sys/panels/plugins-panel.js +2 -2
  104. package/dist/commands/sys/panels/prefs-panel.d.ts +2 -2
  105. package/dist/commands/sys/panels/prefs-panel.js +4 -4
  106. package/dist/commands/sys/panels/profiles-panel.d.ts +1 -1
  107. package/dist/commands/sys/panels/profiles-panel.js +14 -13
  108. package/dist/commands/sys/panels/provider-panel.d.ts +3 -3
  109. package/dist/commands/sys/panels/provider-panel.js +8 -8
  110. package/dist/commands/sys/settings-shell.d.ts +3 -3
  111. package/dist/commands/sys/settings-shell.js +15 -15
  112. package/dist/commands/sys/setup-settings-runner.d.ts +3 -3
  113. package/dist/commands/sys/setup-settings-runner.js +5 -5
  114. package/dist/commands/sys/setup-wizard.d.ts +4 -4
  115. package/dist/commands/sys/setup-wizard.js +8 -8
  116. package/dist/commands/sys/support/prepare.js +4 -4
  117. package/dist/commands/sys/support/submit.js +2 -2
  118. package/dist/commands/sys/sync-deps.js +0 -2
  119. package/dist/commands/sys/sync-project-guidance.js +2 -1
  120. package/dist/commands/sys/sysprompt.js +2 -1
  121. package/dist/commands/sys.js +1 -0
  122. package/dist/core/__tests__/boot.test.js +0 -8
  123. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +23 -1
  124. package/dist/core/__tests__/canvas-db-boundary.test.d.ts +1 -0
  125. package/dist/core/__tests__/canvas-db-boundary.test.js +46 -0
  126. package/dist/core/__tests__/canvas.test.js +1 -1
  127. package/dist/core/__tests__/child-death-wake.test.js +0 -2
  128. package/dist/core/__tests__/cli-help-contract.test.d.ts +1 -0
  129. package/dist/core/__tests__/cli-help-contract.test.js +150 -0
  130. package/dist/core/__tests__/cron-armed-context-failure.test.d.ts +1 -0
  131. package/dist/core/__tests__/cron-armed-context-failure.test.js +145 -0
  132. package/dist/core/__tests__/daemon-wedge.test.js +0 -4
  133. package/dist/core/__tests__/dead-node-policy-table.test.js +0 -4
  134. package/dist/core/__tests__/fixtures/fake-engine.js +4 -14
  135. package/dist/core/__tests__/fixtures/fake-pi-host.js +5 -5
  136. package/dist/core/__tests__/focuses.test.js +0 -6
  137. package/dist/core/__tests__/fs-utils.test.d.ts +1 -0
  138. package/dist/core/__tests__/fs-utils.test.js +50 -0
  139. package/dist/core/__tests__/helpers/harness.js +1 -1
  140. package/dist/core/__tests__/inline-memory-refs.test.js +0 -2
  141. package/dist/core/__tests__/integration/broker-fork-seam.test.js +0 -2
  142. package/dist/core/__tests__/integration/broker-sdk-wiring.test.js +3 -16
  143. package/dist/core/__tests__/integration/command-plugins.test.js +21 -24
  144. package/dist/core/__tests__/integration/deferred-no-wake.test.js +0 -6
  145. package/dist/core/__tests__/integration/flagship-lifecycle.test.js +0 -26
  146. package/dist/core/__tests__/integration/live-mutation-verbs.test.js +0 -4
  147. package/dist/core/__tests__/integration/live-mutation.test.js +1 -5
  148. package/dist/core/__tests__/integration/refresh-stall-recycle.test.js +1 -5
  149. package/dist/core/__tests__/integration/revive.test.js +0 -12
  150. package/dist/core/__tests__/integration/subscription-delivery.test.js +0 -6
  151. package/dist/core/__tests__/integration/tmux-surface.test.js +24 -8
  152. package/dist/core/__tests__/integration/worktree-reap.test.js +0 -6
  153. package/dist/core/__tests__/lifecycle.test.js +0 -18
  154. package/dist/core/__tests__/memory-resolver-precedence.test.js +1 -1
  155. package/dist/core/__tests__/migration.test.js +2 -2
  156. package/dist/core/__tests__/on-read-dedup-resume.test.js +3 -3
  157. package/dist/core/__tests__/pid-identity-match.test.js +1 -1
  158. package/dist/core/__tests__/plugin-kinds.test.js +1 -1
  159. package/dist/core/__tests__/plugin-page-components.test.js +1 -1
  160. package/dist/core/__tests__/preview-registry-sync.test.js +42 -1
  161. package/dist/core/__tests__/respawn-throttle.test.js +0 -4
  162. package/dist/core/__tests__/revive-all.test.js +5 -5
  163. package/dist/core/__tests__/revive-parked-fresh.test.js +62 -11
  164. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +0 -8
  165. package/dist/core/__tests__/seam/broker-attach-stream.test.js +0 -8
  166. package/dist/core/__tests__/seam/broker-crash-teardown.test.js +0 -2
  167. package/dist/core/__tests__/tmux-driver-boundary.test.d.ts +1 -0
  168. package/dist/core/__tests__/tmux-driver-boundary.test.js +50 -0
  169. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +11 -6
  170. package/dist/core/activity-signal.d.ts +7 -0
  171. package/dist/core/activity-signal.js +38 -0
  172. package/dist/core/auto-update.js +2 -1
  173. package/dist/core/bootstrap.js +2 -1
  174. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +1 -1
  175. package/dist/core/canvas/__tests__/remote-transport.test.js +1 -1
  176. package/dist/core/canvas/__tests__/render-remote.test.js +1 -1
  177. package/dist/core/canvas/browse/app.js +3 -2
  178. package/dist/core/canvas/browse/model.d.ts +1 -1
  179. package/dist/core/canvas/browse/model.js +0 -4
  180. package/dist/core/canvas/canvas.d.ts +22 -0
  181. package/dist/core/canvas/canvas.js +61 -19
  182. package/dist/core/canvas/crons.js +0 -6
  183. package/dist/core/canvas/db.d.ts +14 -20
  184. package/dist/core/canvas/db.js +30 -1332
  185. package/dist/core/canvas/focuses.js +0 -4
  186. package/dist/core/canvas/history.js +2 -1
  187. package/dist/core/canvas/meta-file.js +18 -27
  188. package/dist/core/canvas/migrations.d.ts +20 -0
  189. package/dist/core/canvas/migrations.js +1339 -0
  190. package/dist/core/canvas/nav-model.js +1 -11
  191. package/dist/core/canvas/nav-render.js +0 -8
  192. package/dist/core/canvas/remote-canvas-source.d.ts +8 -0
  193. package/dist/core/canvas/remote-canvas-source.js +9 -3
  194. package/dist/core/canvas/render-source.js +31 -13
  195. package/dist/core/command-manifests/manifest.js +0 -10
  196. package/dist/core/command-manifests/registry.d.ts +2 -0
  197. package/dist/core/command-manifests/schema.js +0 -4
  198. package/dist/core/command-plugins/compose.js +4 -2
  199. package/dist/core/command-plugins/discovery.d.ts +1 -0
  200. package/dist/core/command-plugins/discovery.js +1 -1
  201. package/dist/core/command-plugins/transport/http-invoke.js +0 -14
  202. package/dist/core/command.js +2 -6
  203. package/dist/core/config.js +2 -1
  204. package/dist/core/fault-classifier.js +8 -0
  205. package/dist/core/feed/inbox.d.ts +10 -0
  206. package/dist/core/feed/inbox.js +18 -12
  207. package/dist/core/fs-utils.d.ts +9 -2
  208. package/dist/core/fs-utils.js +7 -15
  209. package/dist/core/help.js +0 -8
  210. package/dist/core/host-exports/export.d.ts +2 -2
  211. package/dist/core/host-exports/export.js +9 -12
  212. package/dist/core/human/answer.d.ts +2 -2
  213. package/dist/core/human/convention.d.ts +2 -5
  214. package/dist/core/human/convention.js +3 -13
  215. package/dist/core/human/page-schema.d.ts +2 -2
  216. package/dist/core/human/page-schema.js +2 -2
  217. package/dist/core/human/root.js +6 -23
  218. package/dist/core/human/scan.js +24 -7
  219. package/dist/core/human/tickets.d.ts +4 -1
  220. package/dist/core/human/tickets.js +20 -6
  221. package/dist/core/io.js +0 -12
  222. package/dist/core/memory/identity.js +0 -4
  223. package/dist/core/memory/inline-ref-inventory.js +3 -2
  224. package/dist/core/memory/lint.js +3 -6
  225. package/dist/core/memory-resolver.d.ts +5 -0
  226. package/dist/core/memory-resolver.js +14 -12
  227. package/dist/core/pagination.d.ts +2 -0
  228. package/dist/core/pagination.js +2 -3
  229. package/dist/core/preview-registry.d.ts +14 -0
  230. package/dist/core/preview-registry.js +2 -4
  231. package/dist/core/preview-result-path.js +2 -1
  232. package/dist/core/profiles/manifest.d.ts +37 -5
  233. package/dist/core/profiles/manifest.js +44 -24
  234. package/dist/core/profiles/select.js +0 -4
  235. package/dist/core/review/signal.js +4 -23
  236. package/dist/core/review/ticket-filter.d.ts +3 -3
  237. package/dist/core/review/ticket-filter.js +4 -9
  238. package/dist/core/runtime/__tests__/engine-route.test.js +1 -1
  239. package/dist/core/runtime/__tests__/spawn-env.test.js +0 -8
  240. package/dist/core/runtime/bearings-render.d.ts +56 -0
  241. package/dist/core/runtime/bearings-render.js +241 -0
  242. package/dist/core/runtime/bearings.d.ts +2 -17
  243. package/dist/core/runtime/bearings.js +12 -198
  244. package/dist/core/runtime/broker/auth-reload.d.ts +39 -0
  245. package/dist/core/runtime/broker/auth-reload.js +149 -0
  246. package/dist/core/runtime/broker/engine-drive.d.ts +26 -0
  247. package/dist/core/runtime/broker/engine-drive.js +257 -0
  248. package/dist/core/runtime/broker/engine-routing.d.ts +143 -0
  249. package/dist/core/runtime/broker/engine-routing.js +211 -0
  250. package/dist/core/runtime/broker/event-projection.js +3 -2
  251. package/dist/core/runtime/broker/fault-retry.js +11 -4
  252. package/dist/core/runtime/broker/frame-client.d.ts +38 -0
  253. package/dist/core/runtime/broker/frame-client.js +129 -0
  254. package/dist/core/runtime/broker/frame-dispatch.d.ts +12 -189
  255. package/dist/core/runtime/broker/frame-dispatch.js +89 -1476
  256. package/dist/core/runtime/broker/frame-engine-control.d.ts +20 -0
  257. package/dist/core/runtime/broker/frame-engine-control.js +231 -0
  258. package/dist/core/runtime/broker/frame-memory-refs.d.ts +19 -0
  259. package/dist/core/runtime/broker/frame-memory-refs.js +41 -0
  260. package/dist/core/runtime/broker/frame-model-query.d.ts +13 -0
  261. package/dist/core/runtime/broker/frame-model-query.js +69 -0
  262. package/dist/core/runtime/broker/frame-read.d.ts +19 -0
  263. package/dist/core/runtime/broker/frame-read.js +116 -0
  264. package/dist/core/runtime/broker/frame-replies.d.ts +24 -0
  265. package/dist/core/runtime/broker/frame-replies.js +64 -0
  266. package/dist/core/runtime/broker/frame-session.d.ts +34 -0
  267. package/dist/core/runtime/broker/frame-session.js +203 -0
  268. package/dist/core/runtime/broker/rebind.js +50 -0
  269. package/dist/core/runtime/broker/turn-ignition.d.ts +42 -0
  270. package/dist/core/runtime/broker/turn-ignition.js +74 -0
  271. package/dist/core/runtime/broker-extension-render.js +9 -85
  272. package/dist/core/runtime/broker-protocol.js +0 -4
  273. package/dist/core/runtime/broker-sdk.js +2 -1
  274. package/dist/core/runtime/broker.d.ts +1 -1
  275. package/dist/core/runtime/broker.js +5 -23
  276. package/dist/core/runtime/fault.js +3 -2
  277. package/dist/core/runtime/headless-pi.js +3 -2
  278. package/dist/core/runtime/host.js +2 -3
  279. package/dist/core/runtime/kickoff.js +0 -4
  280. package/dist/core/runtime/launch-target.js +2 -1
  281. package/dist/core/runtime/launch.d.ts +2 -2
  282. package/dist/core/runtime/launch.js +20 -10
  283. package/dist/core/runtime/lifecycle.js +17 -26
  284. package/dist/core/runtime/managed-provider-cooling.d.ts +1 -1
  285. package/dist/core/runtime/naming.js +0 -2
  286. package/dist/core/runtime/nodes.js +2 -1
  287. package/dist/core/runtime/persona.js +0 -6
  288. package/dist/core/runtime/placement-tmux.js +2 -5
  289. package/dist/core/runtime/placement.js +4 -11
  290. package/dist/core/runtime/recap.js +4 -3
  291. package/dist/core/runtime/reopen.js +4 -7
  292. package/dist/core/runtime/reset.js +0 -4
  293. package/dist/core/runtime/resume-root.js +0 -2
  294. package/dist/core/runtime/revive.js +17 -13
  295. package/dist/core/runtime/session-list-cache.js +2 -2
  296. package/dist/core/runtime/shell-expansion.js +0 -2
  297. package/dist/core/runtime/spawn.js +0 -2
  298. package/dist/core/runtime/stop-guard.js +28 -16
  299. package/dist/core/runtime/stream-watchdog.d.ts +0 -1
  300. package/dist/core/runtime/stream-watchdog.js +13 -18
  301. package/dist/core/runtime/tmux-bindings.js +0 -2
  302. package/dist/core/runtime/tmux-driver.d.ts +12 -0
  303. package/dist/core/runtime/tmux-driver.js +18 -13
  304. package/dist/core/runtime/warm-pool.js +0 -4
  305. package/dist/core/scope.js +4 -3
  306. package/dist/core/spawn.js +2 -3
  307. package/dist/core/subscription-state.js +10 -17
  308. package/dist/core/substrate/__tests__/surface-match-pre-command.test.js +3 -3
  309. package/dist/core/substrate/frontmatter-validation.d.ts +1 -0
  310. package/dist/core/substrate/frontmatter-validation.js +1 -1
  311. package/dist/core/substrate/render.js +0 -12
  312. package/dist/core/substrate/schema.js +0 -12
  313. package/dist/core/substrate/surface-match.js +0 -2
  314. package/dist/core/termrender/display.js +2 -1
  315. package/dist/core/tui/page-host.d.ts +3 -3
  316. package/dist/core/tui/page-host.js +4 -4
  317. package/dist/core/tui/panel.js +3 -3
  318. package/dist/core/wake.js +0 -12
  319. package/dist/core/worktree.js +0 -2
  320. package/dist/daemon/__tests__/reconciler-signature.test.d.ts +1 -0
  321. package/dist/daemon/__tests__/reconciler-signature.test.js +36 -0
  322. package/dist/daemon/api/__tests__/error-seam.test.js +27 -0
  323. package/dist/daemon/api/__tests__/seam/api-server.test.js +2 -16
  324. package/dist/daemon/api/__tests__/seam/b10-attach-modelauth.test.js +3 -11
  325. package/dist/daemon/api/__tests__/seam/leaf-api-parity.test.js +0 -6
  326. package/dist/daemon/api/handlers/attach.js +2 -1
  327. package/dist/daemon/api/handlers/broker-ops.js +3 -6
  328. package/dist/daemon/api/handlers/canvas.js +12 -18
  329. package/dist/daemon/api/handlers/feedback-comments.js +0 -8
  330. package/dist/daemon/api/handlers/health.js +3 -2
  331. package/dist/daemon/api/handlers/human-requests.js +0 -10
  332. package/dist/daemon/api/handlers/human.js +1 -5
  333. package/dist/daemon/api/handlers/inbox.js +0 -18
  334. package/dist/daemon/api/handlers/memory.js +1 -1
  335. package/dist/daemon/api/handlers/messages.js +16 -20
  336. package/dist/daemon/api/handlers/nodes.js +23 -22
  337. package/dist/daemon/api/handlers/reports.js +1 -1
  338. package/dist/daemon/api/handlers/reviews.js +1 -6
  339. package/dist/daemon/api/handlers/worktree.js +6 -22
  340. package/dist/daemon/api/map.d.ts +4 -2
  341. package/dist/daemon/api/map.js +22 -8
  342. package/dist/daemon/api/router.js +0 -4
  343. package/dist/daemon/api/server.js +2 -5
  344. package/dist/daemon/boot-migrations.d.ts +14 -0
  345. package/dist/daemon/boot-migrations.js +31 -0
  346. package/dist/daemon/cron/armed-context.d.ts +21 -0
  347. package/dist/daemon/cron/armed-context.js +100 -0
  348. package/dist/daemon/cron/capture.d.ts +20 -0
  349. package/dist/daemon/cron/capture.js +45 -0
  350. package/dist/daemon/cron/disposition.d.ts +19 -0
  351. package/dist/daemon/cron/disposition.js +52 -0
  352. package/dist/daemon/cron/escalation.d.ts +12 -0
  353. package/dist/daemon/cron/escalation.js +124 -0
  354. package/dist/daemon/cron/held.d.ts +4 -0
  355. package/dist/daemon/cron/held.js +20 -0
  356. package/dist/daemon/cron/lease-recovery.d.ts +3 -0
  357. package/dist/daemon/cron/lease-recovery.js +119 -0
  358. package/dist/daemon/cron/live-runs.d.ts +11 -0
  359. package/dist/daemon/cron/live-runs.js +23 -0
  360. package/dist/daemon/cron/passes.d.ts +11 -0
  361. package/dist/daemon/cron/passes.js +78 -0
  362. package/dist/daemon/cron/settle.d.ts +28 -0
  363. package/dist/daemon/cron/settle.js +37 -0
  364. package/dist/daemon/cron/sinks.d.ts +2 -0
  365. package/dist/daemon/cron/sinks.js +100 -0
  366. package/dist/daemon/cron-run.d.ts +5 -41
  367. package/dist/daemon/cron-run.js +193 -882
  368. package/dist/daemon/crtrd-cli.js +2 -1
  369. package/dist/daemon/crtrd.js +22 -22
  370. package/dist/daemon/fleet.js +0 -4
  371. package/dist/daemon/human/deliver-action.js +2 -1
  372. package/dist/daemon/human/sweep.js +15 -4
  373. package/dist/daemon/manage.d.ts +2 -2
  374. package/dist/daemon/manage.js +4 -13
  375. package/dist/daemon/pidfile.js +2 -2
  376. package/dist/daemon/reconcilers/broker-supervision.js +27 -30
  377. package/dist/daemon/reconcilers/dormant-inbox.d.ts +1 -1
  378. package/dist/daemon/reconcilers/dormant-inbox.js +2 -1
  379. package/dist/daemon/reconcilers/live-obligation.js +2 -2
  380. package/dist/daemon/reconcilers/pending-review-submit.d.ts +1 -1
  381. package/dist/daemon/reconcilers/pending-review-submit.js +2 -1
  382. package/dist/daemon/reconcilers/storage-maintenance.d.ts +2 -1
  383. package/dist/daemon/reconcilers/storage-maintenance.js +2 -1
  384. package/dist/daemon/review/companion.js +2 -2
  385. package/dist/daemon/review/sweep.js +8 -3
  386. package/dist/migrations/001-surfaces-frontmatter.js +0 -6
  387. package/dist/migrations/corpus.js +2 -5
  388. package/dist/migrations/registry.js +15 -2
  389. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +0 -4
  390. package/dist/pi-extensions/broker-local.js +2 -1
  391. package/dist/pi-extensions/canvas-bash-valve.js +7 -20
  392. package/dist/pi-extensions/canvas-context-intro.js +2 -7
  393. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  394. package/dist/pi-extensions/canvas-goal-capture.js +2 -1
  395. package/dist/pi-extensions/canvas-inbox-watcher.js +5 -12
  396. package/dist/pi-extensions/canvas-passive-context.js +2 -5
  397. package/dist/pi-extensions/canvas-preview-result.js +2 -1
  398. package/dist/pi-extensions/canvas-prompt-scrub.js +2 -1
  399. package/dist/pi-extensions/canvas-recap.js +3 -16
  400. package/dist/pi-extensions/canvas-review-boundary.js +2 -5
  401. package/dist/pi-extensions/canvas-stophook.js +2 -26
  402. package/dist/pi-extensions/canvas-structured-output.js +2 -1
  403. package/dist/pi-extensions/canvas-tool-guide.js +2 -1
  404. package/dist/shared/__tests__/env-boundary.test.d.ts +1 -0
  405. package/dist/shared/__tests__/env-boundary.test.js +93 -0
  406. package/dist/shared/env.d.ts +68 -0
  407. package/dist/shared/env.js +171 -0
  408. package/package.json +3 -1
  409. package/runtime.lock.json +2 -2
  410. package/dist/core/canvas/render.d.ts +0 -47
  411. package/dist/core/canvas/render.js +0 -245
@@ -82,7 +82,7 @@ beforeEach(() => {
82
82
  mkdirSync(child, { recursive: true });
83
83
  mkdirSync(nestedWrite, { recursive: true });
84
84
  mkdirSync(nestedEmpty, { recursive: true });
85
- // --- repository root, declaring the namespace for every nested store ---
85
+ // repository root, declaring the namespace for every nested store
86
86
  writeDoc(join(storeDir(root), 'INDEX.md'), 'kind: knowledge\nnamespace: acme\nwhen-and-why-to-read: "When the root acme store is addressed, this knowledge should be read because it names what the store holds."', 'ROOT STORE INDEX');
87
87
  writeDoc(join(storeDir(root), 'foo.md'), 'kind: knowledge', 'ROOT FOO BODY');
88
88
  writeDoc(join(storeDir(root), 'linker.md'), 'kind: knowledge', 'ROOT LINKER BODY linking [[acme/foo]] once.');
@@ -91,17 +91,17 @@ beforeEach(() => {
91
91
  // A `name:` pin on a file whose own path already derives a different identity.
92
92
  writeDoc(join(storeDir(root), 'x.md'), 'kind: knowledge\nname: y', 'ROOT PINNED BODY');
93
93
  appendHistoryRecord(join(storeDir(root), '.history', 'x.jsonl'), buildHistoryRecord({ op: 'create', before: '', after: 'ROOT PINNED BODY' }));
94
- // --- child: a nested store deriving `acme/child`; cwd lives here ---
94
+ // child: a nested store deriving `acme/child`; cwd lives here
95
95
  writeDoc(join(storeDir(child), 'INDEX.md'), 'kind: knowledge\nwhen-and-why-to-read: "When the child guide is addressed, this knowledge should be read because it names what the nested store holds."', 'CHILD STORE INDEX');
96
96
  writeDoc(join(storeDir(child), 'foo.md'), 'kind: knowledge', 'CHILD FOO BODY');
97
97
  // This nested store has content but no guide, so a write at its exact prefix
98
98
  // must create metadata-free INDEX.md rather than a second declaration.
99
99
  writeDoc(join(storeDir(nestedWrite), 'topic.md'), 'kind: knowledge', 'NESTED WRITE TOPIC');
100
- // --- lint fixtures ---
100
+ // lint fixtures
101
101
  writeDoc(join(storeDir(badns), 'INDEX.md'), 'kind: knowledge\nnamespace: 42', 'BADNS STORE INDEX');
102
102
  writeDoc(join(storeDir(dangling), 'INDEX.md'), 'kind: knowledge\nnamespace: dang\nwhen-and-why-to-read: "When the dang store is addressed, this knowledge should be read because it names what the store holds."', 'DANG STORE INDEX');
103
103
  writeDoc(join(storeDir(dangling), 'broken.md'), 'kind: knowledge', 'Links [[dang/nowhere]], which no document answers to.');
104
- // --- two profiles, both holding the SAME canonical name ---
104
+ // two profiles, both holding the SAME canonical name
105
105
  const a = createProfile('memcmd-a');
106
106
  const b = createProfile('memcmd-b');
107
107
  profileB = profileMemoryDir(b.profileId);
@@ -0,0 +1,96 @@
1
+ import { test, after } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import { parseFrontmatterGeneric } from '../../../core/frontmatter.js';
7
+ import { resetScopeCache } from '../../../core/scope.js';
8
+ import { CrtrError } from '../../../core/errors.js';
9
+ import { editLeaf } from '../edit.js';
10
+ import { listLeaf } from '../list.js';
11
+ import { writeLeaf } from '../write.js';
12
+ const previousHome = process.env['HOME'];
13
+ const previousCrtrHome = process.env['CRTR_HOME'];
14
+ const home = mkdtempSync(join(tmpdir(), 'crtr-memory-list-'));
15
+ const canvasHome = mkdtempSync(join(tmpdir(), 'crtr-memory-list-canvas-'));
16
+ mkdirSync(join(home, '.crouter', 'memory'), { recursive: true });
17
+ process.env['HOME'] = home;
18
+ process.env['CRTR_HOME'] = canvasHome;
19
+ function writeMemory(name, frontmatter = 'kind: knowledge') {
20
+ writeFileSync(join(home, '.crouter', 'memory', name), `---\n${frontmatter}\n---\n\nBODY\n`, 'utf8');
21
+ }
22
+ after(() => {
23
+ resetScopeCache();
24
+ if (previousHome === undefined)
25
+ delete process.env['HOME'];
26
+ else
27
+ process.env['HOME'] = previousHome;
28
+ if (previousCrtrHome === undefined)
29
+ delete process.env['CRTR_HOME'];
30
+ else
31
+ process.env['CRTR_HOME'] = previousCrtrHome;
32
+ rmSync(home, { recursive: true, force: true });
33
+ rmSync(canvasHome, { recursive: true, force: true });
34
+ });
35
+ test('memory list resumes locale-aware pages across case boundaries', async () => {
36
+ writeMemory('upper.md', 'kind: knowledge\nname: A');
37
+ writeMemory('lower.md', 'kind: knowledge\nname: a');
38
+ writeMemory('INDEXED.md');
39
+ const first = (await listLeaf.run({ scope: 'user', limit: 2, cursor: undefined, kind: undefined, dir: undefined, paths: false }));
40
+ assert.deepEqual(first.items.map((item) => item.name), ['a', 'A']);
41
+ assert.ok(first.next_cursor);
42
+ const second = (await listLeaf.run({ scope: 'user', limit: 1, cursor: first.next_cursor, kind: undefined, dir: undefined, paths: false }));
43
+ assert.deepEqual(second.items.map((item) => item.name), ['INDEXED']);
44
+ const edge = (await listLeaf.run({ scope: 'user', limit: 3, cursor: undefined, kind: undefined, dir: undefined, paths: false }));
45
+ assert.deepEqual(edge.items.map((item) => item.name), ['a', 'A', 'INDEXED']);
46
+ assert.equal(edge.next_cursor, null);
47
+ });
48
+ test('memory write and edit set, validate, and unset lint-ignore', async () => {
49
+ const scalar = (await writeLeaf.run({
50
+ name: 'lint-scalar',
51
+ scope: 'user',
52
+ kind: 'knowledge',
53
+ whenAndWhyToRead: 'When lint length is intentional, this knowledge should be read because the document remains available.',
54
+ lintIgnore: 'length',
55
+ body: 'SCALAR',
56
+ }));
57
+ assert.equal(parseFrontmatterGeneric(readFileSync(scalar.path, 'utf8')).data?.['lint-ignore'], 'length');
58
+ const list = (await writeLeaf.run({
59
+ name: 'lint-list',
60
+ scope: 'user',
61
+ kind: 'knowledge',
62
+ whenAndWhyToRead: 'When lint breadth is intentional, this knowledge should be read because the document remains available.',
63
+ lintIgnore: ['length', 'broad-memory-read'],
64
+ body: 'LIST',
65
+ }));
66
+ assert.deepEqual(parseFrontmatterGeneric(readFileSync(list.path, 'utf8')).data?.['lint-ignore'], ['length', 'broad-memory-read']);
67
+ await editLeaf.run({
68
+ name: 'lint-scalar',
69
+ scope: 'user',
70
+ rationale: 'set the second suppressible rule',
71
+ lintIgnore: ['length', 'broad-memory-read'],
72
+ });
73
+ await editLeaf.run({
74
+ name: 'lint-scalar',
75
+ scope: 'user',
76
+ rationale: 'remove lint suppression',
77
+ unset: ['lint-ignore'],
78
+ });
79
+ assert.equal(parseFrontmatterGeneric(readFileSync(scalar.path, 'utf8')).data?.['lint-ignore'], undefined);
80
+ let error;
81
+ try {
82
+ await writeLeaf.run({
83
+ name: 'lint-invalid',
84
+ scope: 'user',
85
+ kind: 'knowledge',
86
+ whenAndWhyToRead: 'When invalid lint metadata is supplied, this knowledge should be read because it must be rejected.',
87
+ lintIgnore: ['length', 'not-a-rule'],
88
+ body: 'INVALID',
89
+ });
90
+ }
91
+ catch (caught) {
92
+ error = caught;
93
+ }
94
+ assert.ok(error instanceof CrtrError);
95
+ assert.equal(error.code, 'usage');
96
+ });
@@ -33,7 +33,7 @@ export const deleteLeaf = defineLeaf({
33
33
  summary: 'resolve an exact canonical name and remove that document from its store',
34
34
  params: [
35
35
  { kind: 'positional', name: 'name', required: true, constraint: 'Full canonical name of the document — a project document is `<namespace>/<local name>`. Resolution is exact, as `read` resolves it: a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail. A canonical directory’s own document is deleted at the directory name; its members are untouched and stay reachable. If two physical files in the selected store own the name, delete selects the first path in stable lexical order and reports it. Builtin and installed-plugin documents are read-only and refuse to delete, as does the repository-root document, which declares the immutable namespace for every document in the repository; a nested store’s root guide is ordinary and may be deleted.' },
36
- selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, 'builtin is not a choice — builtin documents ship with the package and cannot be deleted.'),
36
+ selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, 'Package-shipped documents cannot be deleted through this writable-scope selector.'),
37
37
  selectorParam('dir', {}, 'Deletes from that one store, including a non-winning duplicate no target view resolves to.'),
38
38
  ],
39
39
  output: [
@@ -4,11 +4,11 @@ import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
4
4
  import { readText, writeText } from '../../core/fs-utils.js';
5
5
  import { loadMemoryStoreView, resolveMemoryCandidates, } from '../../core/memory-resolver.js';
6
6
  import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor, readHistoryRecords, } from '../../core/memory/history.js';
7
- import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, parseRequestedExtensionChanges, applyExtensionChanges, literalBodySegment, serializeMemoryDocLiteral, selectorParam, requireLocalNameInStore, requireUnambiguousDoc, resolveReadSelector, } from './shared.js';
7
+ import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, coerceGate, coerceLintIgnore, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, parseRequestedExtensionChanges, applyExtensionChanges, literalBodySegment, serializeMemoryDocLiteral, selectorParam, requireLocalNameInStore, requireUnambiguousDoc, resolveReadSelector, } from './shared.js';
8
8
  /** Frontmatter fields an overlay flag cannot express removing. `kind` and the
9
9
  * routing line are the document's contract (a doc without them is malformed);
10
10
  * `origin` and `last-updated` are runtime provenance. */
11
- const CLEARABLE_FIELDS = ['short-form', 'gate', 'surfaces', 'unlisted', 'slash', 'rationale'];
11
+ const CLEARABLE_FIELDS = ['short-form', 'gate', 'surfaces', 'unlisted', 'slash', 'rationale', 'lint-ignore'];
12
12
  export const editLeaf = defineLeaf({
13
13
  name: 'edit',
14
14
  description: 'revise an existing memory document, recording why',
@@ -19,7 +19,7 @@ export const editLeaf = defineLeaf({
19
19
  guide: 'The body you pipe is saved byte for byte. Nothing between stdin and disk parses, normalizes, summarizes, or rewrites it \u2014 the frontmatter fence is prepended and the text is otherwise untouched. Relaying a person\u2019s own words is therefore safe: pass them through unchanged and mark them with --verbatim.\n\n' +
20
20
  'Two different rationales exist and they never share a flag. --rationale is why THIS REVISION is happening; it is required, it lands in the revision history, and it never touches the document text. --doc-rationale replaces the document\u2019s standing `rationale` frontmatter field \u2014 the observed gap the doc exists to close.\n\n' +
21
21
  'Omitting stdin leaves the body exactly as it stands, which is how a frontmatter-only revision is made. Piping a body REPLACES the whole body; there is no partial or append mode. Emptying a document is not an edit \u2014 that intent is `crtr memory delete`.\n\n' +
22
- 'Omitted frontmatter flags preserve their fields. --unset is the only way to REMOVE one, and it is limited to fields a doc can legitimately live without (short-form, gate, surfaces, unlisted, slash, rationale). --surface replaces the whole surfaces list rather than adding to it.\n\n' +
22
+ 'Omitted frontmatter flags preserve their fields. --unset is the only way to REMOVE one, and it is limited to fields a doc can legitimately live without (short-form, gate, surfaces, unlisted, slash, rationale, lint-ignore). --surface replaces the whole surfaces list rather than adding to it.\n\n' +
23
23
  'An edit that changes nothing fails. A rationale attached to zero change is noise in the trail the history exists to build \u2014 if the doc already says what you meant, there is nothing to record.\n\n' +
24
24
  '`last-updated` is stamped on every successful edit; `origin` is preserved, so it keeps pointing at the conversation that created the doc rather than the last one to touch it.\n\n' +
25
25
  GUIDE_ROUTING_LINE + '\n\n' +
@@ -38,6 +38,7 @@ export const editLeaf = defineLeaf({
38
38
  overlayParam('unlisted', {}, 'Presence SETS the field; remove it with `--unset unlisted`.'),
39
39
  overlayParam('surface'),
40
40
  overlayParam('gate'),
41
+ overlayParam('lint-ignore'),
41
42
  overlayParam('slash', {}, 'Presence SETS the field; remove it with `--unset slash`.'),
42
43
  { kind: 'flag', name: 'doc-rationale', type: 'string', required: false, constraint: DOC_RATIONALE_CONSTRAINT },
43
44
  { kind: 'flag', name: 'extension', type: 'string', required: false, repeatable: true, constraint: 'Set one declared plugin field as `extensions.<plugin>.<field>=VALUE`. The path is required in full; booleans accept only true or false, numbers require finite numeric syntax, and strings/enums preserve the literal text after the first =. Every requested field validates before the document is revised.' },
@@ -105,6 +106,7 @@ export const editLeaf = defineLeaf({
105
106
  if (doc.scope === 'builtin') {
106
107
  throw usage(`${doc.name} is a builtin document shipped with the package (read-only) \u2014 it cannot be edited. Override it with a same-named doc at a writable scope instead (\`crtr memory write ${doc.name} ...\`).`, { memory: doc.name, scope: 'builtin' });
107
108
  }
109
+ const lintIgnore = coerceLintIgnore(input['lintIgnore']);
108
110
  const extensionChanges = parseRequestedExtensionChanges(input['extension'], input['unsetExtension'], memoryExtensionCatalogForDoc(doc));
109
111
  const before = readText(doc.path);
110
112
  const parsed = parseFrontmatterGeneric(before);
@@ -146,6 +148,7 @@ export const editLeaf = defineLeaf({
146
148
  }
147
149
  if (input['gate'] !== undefined)
148
150
  frontmatter['gate'] = coerceGate(input['gate']);
151
+ setIf('lint-ignore', lintIgnore);
149
152
  if (input['slash'] === true)
150
153
  frontmatter['slash'] = true;
151
154
  setIf('rationale', input['docRationale']);
@@ -126,7 +126,7 @@ export const findLeaf = defineLeaf({
126
126
  const units = selector.store !== null
127
127
  ? searchUnits(kindFilter, loadMemoryStoreView(requireMountedStore(selector.store), false).docs, false)
128
128
  : searchUnits(kindFilter, listAllMemoryDocs(selector.scope, false, true), true);
129
- // --- grep mode: regex over every unit's body, one row per matching line ---
129
+ // grep mode: regex over every unit's body, one row per matching line
130
130
  if (grep) {
131
131
  let regex;
132
132
  try {
@@ -167,7 +167,7 @@ export const findLeaf = defineLeaf({
167
167
  registerMemoryListingExposure(listingIdentity);
168
168
  return output;
169
169
  }
170
- // --- ranked mode: weighted relevance over name/routing-line/short-form(+body) ---
170
+ // ranked mode: weighted relevance over name/routing-line/short-form(+body)
171
171
  const terms = query
172
172
  .toLowerCase()
173
173
  .split(/\s+/)
@@ -3,6 +3,7 @@
3
3
  // verification, the plugin candidate gate, and the build's builtin gate consume
4
4
  // too; this leaf only picks WHAT to lint (a target view, or one exact store
5
5
  // with --dir) and shapes the findings for the CLI.
6
+ import { envProfileId } from '../../shared/env.js';
6
7
  import { defineLeaf } from '../../core/command.js';
7
8
  import { warn } from '../../core/output.js';
8
9
  import { ExitCode } from '../../types.js';
@@ -26,7 +27,7 @@ function findingRow(finding) {
26
27
  function resolvableNamesForStore(store) {
27
28
  const view = loadMemoryTargetView({
28
29
  cwd: store.ownerDir ?? store.storeRoot,
29
- profileId: process.env['CRTR_PROFILE_ID'] || null,
30
+ profileId: envProfileId() || null,
30
31
  nodeId: null,
31
32
  }, { quiet: true, includeDescendants: true });
32
33
  return resolvableCanonicalNames(view.docs);
@@ -20,4 +20,8 @@ export interface ListedMemoryDoc {
20
20
  * point of naming a store is to see what it physically holds, including a pair
21
21
  * that shares one identity. */
22
22
  export declare function listMemoryDocs(kindFilter: string | undefined, docs: readonly MemoryDoc[], dedup: boolean): ListedMemoryDoc[];
23
+ /** The stable key used by both inventory ordering and cursor resumption. */
24
+ export declare function memoryListKey(doc: Pick<ListedMemoryDoc, 'scope' | 'kind' | 'name' | 'physicalRelativePath'>): string;
25
+ /** Locale-aware total order for memory inventory keys. */
26
+ export declare function compareMemoryListKeys(left: string, right: string): number;
23
27
  export declare const listLeaf: import("../../core/command.js").LeafDef;
@@ -46,20 +46,26 @@ export function listMemoryDocs(kindFilter, docs, dedup) {
46
46
  candidates: byName.get(doc.name) ?? [doc],
47
47
  });
48
48
  }
49
- // Physical path is the final tiebreaker so two rows of one identity keep a
50
- // stable order — the cursor key ends on the same component.
51
- items.sort((a, b) => {
52
- const sr = scopeRank(a.scope) - scopeRank(b.scope);
53
- if (sr !== 0)
54
- return sr;
55
- const kc = a.kind.localeCompare(b.kind);
56
- if (kc !== 0)
57
- return kc;
58
- const nc = a.name.localeCompare(b.name);
59
- return nc !== 0 ? nc : a.physicalRelativePath.localeCompare(b.physicalRelativePath);
60
- });
49
+ items.sort((a, b) => compareMemoryListKeys(memoryListKey(a), memoryListKey(b)));
61
50
  return items;
62
51
  }
52
+ /** The stable key used by both inventory ordering and cursor resumption. */
53
+ export function memoryListKey(doc) {
54
+ return `${String(scopeRank(doc.scope) + 10).padStart(2, '0')}:${doc.kind}:${doc.name}:${doc.physicalRelativePath}`;
55
+ }
56
+ /** Locale-aware total order for memory inventory keys. */
57
+ export function compareMemoryListKeys(left, right) {
58
+ const [leftScope, leftKind, leftName, ...leftPath] = left.split(':');
59
+ const [rightScope, rightKind, rightName, ...rightPath] = right.split(':');
60
+ const scope = Number(leftScope) - Number(rightScope);
61
+ if (scope !== 0)
62
+ return scope;
63
+ const kind = leftKind.localeCompare(rightKind);
64
+ if (kind !== 0)
65
+ return kind;
66
+ const name = leftName.localeCompare(rightName);
67
+ return name !== 0 ? name : leftPath.join(':').localeCompare(rightPath.join(':'));
68
+ }
63
69
  export const listLeaf = defineLeaf({
64
70
  name: 'list',
65
71
  description: 'inventory of stored memory documents',
@@ -95,8 +101,8 @@ export const listLeaf = defineLeaf({
95
101
  const items = selector.store !== null
96
102
  ? listMemoryDocs(kindFilter, loadMemoryStoreView(requireMountedStore(selector.store), false).docs, false)
97
103
  : listMemoryDocs(kindFilter, listAllMemoryDocs(selector.scope, false, true), true);
98
- const keyOf = (d) => `${String(scopeRank(d.scope) + 10).padStart(2, '0')}:${d.kind}:${d.name}:${d.physicalRelativePath}`;
99
- const result = paginate(items, { limit, cursor }, { defaultLimit: 50, maxLimit: 200, keyOf, total: 'count' });
104
+ const keyOf = memoryListKey;
105
+ const result = paginate(items, { limit, cursor }, { defaultLimit: 50, maxLimit: 200, keyOf, compareKeys: compareMemoryListKeys, total: 'count' });
100
106
  const output = {
101
107
  items: result.items.map((d) => ({
102
108
  name: d.name,
@@ -53,7 +53,7 @@ export const moveLeaf = defineLeaf({
53
53
  params: [
54
54
  { kind: 'positional', name: 'name', required: true, constraint: 'Full canonical name of the document to move — a project document is `<namespace>/<local name>`. Resolution is exact, as `read` resolves it: a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail. If two physical files in the selected store own the name, move selects the first path in stable lexical order and recovers the collision. Builtin and installed-plugin documents are read-only and refuse to move, as does the repository-root document, which declares the immutable namespace for every document in the repository; a nested store’s root guide is ordinary and may move.' },
55
55
  { kind: 'flag', name: 'to', type: 'string', required: true, constraint: 'The new full canonical name, in the SAME store — the repository namespace and store path included, since a move never crosses stores. `/`-joined segments of [A-Za-z0-9_-], no trailing `INDEX` (a directory’s document answers at the directory name). A nested store’s effective prefix is a legal destination for its optional root guide. Rejected when a document in that store already answers to it, or when either `<local name>.md` or `<local name>/INDEX.md` already exists there.' },
56
- selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, 'builtin is not a choice — builtin documents ship with the package and cannot move.'),
56
+ selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, 'Package-shipped documents cannot move through this writable-scope selector.'),
57
57
  selectorParam('dir', {}, 'Source, destination, and inbound-link rewriting all stay inside that one store, so moving a non-winning duplicate leaves every other store untouched — and rewrites no link at all, because the name still resolves to the candidate that outranks it here.'),
58
58
  ],
59
59
  output: [
@@ -1,11 +1,6 @@
1
- import { createMemoryDocSnapshot, resolveMemoryDocs } from '../../core/memory-resolver.js';
1
+ import { createMemoryDocSnapshot, readMemoryDocContent, resolveMemoryDocs } from '../../core/memory-resolver.js';
2
2
  import type { MemoryDoc } from '../../core/memory-resolver.js';
3
- export { createMemoryDocSnapshot, resolveMemoryDocs };
4
- /** Load the body at an already-resolved memory path with the same frontmatter
5
- * stripping and in-node path interpolation as `memory read`. Callers that
6
- * already have the resolved path (such as slash-command discovery) avoid
7
- * re-walking every memory store for each document. */
8
- export declare function readMemoryDocContent(path: string, includeFrontmatter?: boolean): string;
3
+ export { createMemoryDocSnapshot, readMemoryDocContent, resolveMemoryDocs };
9
4
  /** One physical document sharing a canonical identity, as the path-aware
10
5
  * surfaces report it. `--dir <its project>` is how a non-winner is reached. */
11
6
  export declare function candidateRow(doc: MemoryDoc, winner: MemoryDoc): Record<string, unknown>;
@@ -1,12 +1,11 @@
1
+ import { envNodeId } from '../../shared/env.js';
1
2
  import { CrtrClient } from '../../api/index.js';
2
- import { interpolateNodePaths } from '../../core/canvas/paths.js';
3
3
  import { defineLeaf } from '../../core/command.js';
4
4
  import { CrtrError } from '../../core/errors.js';
5
5
  import { memoryExtensionEffectiveCatalog, projectEffectiveMemoryExtensions } from '../../core/memory/extensions.js';
6
- import { readText, realpathOrSelf } from '../../core/fs-utils.js';
7
- import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
6
+ import { realpathOrSelf } from '../../core/fs-utils.js';
8
7
  import { docLinkNames } from '../../core/memory/doc-link-grammar.js';
9
- import { createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
8
+ import { createMemoryDocSnapshot, listAllMemoryDocs, loadMemoryStoreView, readMemoryDocContent, resolveMemoryCandidates, resolveMemoryDocs, } from '../../core/memory-resolver.js';
10
9
  import { expandShellBlocks, hasShellBlocks, makeNodeShellRunner } from '../../core/runtime/shell-expansion.js';
11
10
  import { emptyContextExposureState, exposedAtOrAbove, exposureTarget, loadContextExposureState, registerDocumentExposure, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
12
11
  import { dirDedupKey, docListingDirs, docsByName, isDirName, renderDirListing } from '../../core/substrate/listings.js';
@@ -15,17 +14,7 @@ import { renderResult } from '../../core/render.js';
15
14
  import { effectiveDocKind } from '../../core/substrate/schema.js';
16
15
  import { canonicalSegments } from '../../core/memory/identity.js';
17
16
  import { MEMORY_KINDS, requireMountedStore, resolveReadSelector, selectorParam } from './shared.js';
18
- export { createMemoryDocSnapshot, resolveMemoryDocs };
19
- /** Load the body at an already-resolved memory path with the same frontmatter
20
- * stripping and in-node path interpolation as `memory read`. Callers that
21
- * already have the resolved path (such as slash-command discovery) avoid
22
- * re-walking every memory store for each document. */
23
- export function readMemoryDocContent(path, includeFrontmatter = false) {
24
- const raw = readText(path);
25
- const content = includeFrontmatter ? raw : parseFrontmatterGeneric(raw).body;
26
- const nodeId = process.env['CRTR_NODE_ID'];
27
- return nodeId ? interpolateNodePaths(content, nodeId) : content;
28
- }
17
+ export { createMemoryDocSnapshot, readMemoryDocContent, resolveMemoryDocs };
29
18
  /** The node-config subject for memory-read-event gate evaluation. A CLI
30
19
  * process cannot take it from canvas-db (V-1); one `/v1` request supplies it.
31
20
  * With no node identity or no reachable daemon the answer is null — gated
@@ -150,7 +139,7 @@ export const readLeaf = defineLeaf({
150
139
  }
151
140
  fail = () => resolveMemoryCandidates(nameRaw, resolveOpts);
152
141
  }
153
- const nodeId = process.env['CRTR_NODE_ID'] || undefined;
142
+ const nodeId = envNodeId() || undefined;
154
143
  if (set === null) {
155
144
  // No document answers this name — a directory with no document of its own
156
145
  // still does, through its listing. An explicit dir read is a deliberate
@@ -185,6 +185,7 @@ export declare const SELECTOR_PARAMS: Record<string, FlagParam>;
185
185
  /** One selector flag, with leaf-specific prose appended to the shared
186
186
  * constraint and any leaf-specific schema override applied. */
187
187
  export declare function selectorParam(name: string, overrides?: Partial<FlagParam>, extraConstraint?: string): FlagParam;
188
+ export declare function coerceLintIgnore(raw: unknown): string | string[] | undefined;
188
189
  export declare const FRONTMATTER_OVERLAY_PARAMS: Record<string, FlagParam>;
189
190
  /** One overlay flag, with leaf-specific prose appended to the shared
190
191
  * constraint and any leaf-specific schema override applied. */
@@ -3,6 +3,7 @@
3
3
  // scope modules and build their documented output objects on top of the small
4
4
  // helpers here. Nothing in this file forks on kind or re-implements the
5
5
  // schema/gate/resolver — it only composes them.
6
+ import { envNodeId, envProfileId } from '../../shared/env.js';
6
7
  import { existsSync, readdirSync, realpathSync, rmdirSync, statSync } from 'node:fs';
7
8
  import { dirname, join, resolve as resolvePath } from 'node:path';
8
9
  import { stringify as yamlStringify, parse as yamlParse } from 'yaml';
@@ -17,6 +18,7 @@ import { NEUTRAL_PROJECT_MEMORY, scopeMemoryDir, projectScopeRoot, ensureProject
17
18
  import { loadProfileManifest, resolveProfileOperand, profileMemoryDir } from '../../core/profiles/manifest.js';
18
19
  import { memoryDir as nodeMemoryDir } from '../../core/runtime/memory.js';
19
20
  import { SURFACE_EVENTS, SURFACE_RUNGS } from '../../core/substrate/schema.js';
21
+ import { SUPPRESSIBLE_RULES } from '../../core/substrate/frontmatter-validation.js';
20
22
  import { exposureTarget, loadContextExposureState, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
21
23
  // The two memory kinds — knowledge (consult: procedural playbooks + factual
22
24
  // references merged) vs preference (behave: standing directives). Used as the
@@ -40,7 +42,7 @@ export function scopeRank(scope) {
40
42
  return scope === 'node' ? -1 : scope === 'project' ? 0 : scope === 'profile' ? 1 : scope === 'user' ? 2 : 3;
41
43
  }
42
44
  export function registerMemoryListingExposure(identity) {
43
- const nodeId = process.env['CRTR_NODE_ID'];
45
+ const nodeId = envNodeId();
44
46
  if (nodeId === undefined || nodeId === '')
45
47
  return;
46
48
  const state = loadContextExposureState(nodeId);
@@ -151,7 +153,7 @@ export function resolveWriteSelector(input) {
151
153
  if (scope === 'node') {
152
154
  // The this-node store: the same dir render.ts's nodeLocalDocs loads at
153
155
  // boot, so a doc written here rides into this node's knowledge block.
154
- const nodeId = process.env['CRTR_NODE_ID'] || '';
156
+ const nodeId = envNodeId() || '';
155
157
  if (nodeId === '') {
156
158
  throw usage('node scope requires a running node (CRTR_NODE_ID unset); rerun inside a node');
157
159
  }
@@ -163,7 +165,7 @@ export function resolveWriteSelector(input) {
163
165
  // is a durable id this process was launched under, so it resolves strictly
164
166
  // — a stale one must fail rather than write into a neighbouring store.
165
167
  const typed = input.profile !== undefined && input.profile !== '';
166
- const profileIdOrName = typed ? input.profile : process.env['CRTR_PROFILE_ID'] || '';
168
+ const profileIdOrName = typed ? input.profile : envProfileId() || '';
167
169
  if (profileIdOrName === '') {
168
170
  throw usage('profile scope requires a selected profile; rerun inside a profiled node or pass --profile');
169
171
  }
@@ -209,14 +211,12 @@ export function requireMountedStore(store) {
209
211
  next: `Run \`crtr sys migrate --dir ${repositoryRoot}\`.`,
210
212
  });
211
213
  }
212
- // ---------------------------------------------------------------------------
213
214
  // Canonical name → physical placement
214
215
  //
215
216
  // Every surface takes the FULL canonical name. Placement strips the selected
216
217
  // store's namespace off it, checks that neither the identity nor either
217
218
  // physical representation of the local name is already taken, and picks the
218
219
  // form the new file takes.
219
- // ---------------------------------------------------------------------------
220
220
  /** Normalize and validate a full canonical name: ordering prefixes are
221
221
  * physical-path-only, a trailing `INDEX` is never an address, and the segment
222
222
  * charset is fixed. */
@@ -464,6 +464,7 @@ const FRONTMATTER_ORDER = [
464
464
  'gate',
465
465
  'slash',
466
466
  'rationale',
467
+ 'lint-ignore',
467
468
  'last-updated',
468
469
  'origin',
469
470
  ];
@@ -709,13 +710,11 @@ export function applyExtensionChanges(frontmatter, sets, unsets) {
709
710
  else
710
711
  frontmatter['extensions'] = extensions;
711
712
  }
712
- // ---------------------------------------------------------------------------
713
713
  // Help material shared by `write` and `edit`
714
714
  //
715
715
  // Single-sourced: both leaves compose these, so a wording fix lands on both.
716
716
  // Anything exclusive to one leaf (creation rules on `write`, the rationale
717
717
  // contract on `edit`) stays owned by that leaf.
718
- // ---------------------------------------------------------------------------
719
718
  /** The frontmatter-overlay flags, keyed by flag name, carrying the prose true
720
719
  * on BOTH leaves. `write` appends its creation clauses with `withConstraint`;
721
720
  * neither leaf restates the other's rules. */
@@ -737,7 +736,7 @@ export const SELECTOR_PARAMS = {
737
736
  type: 'enum',
738
737
  choices: [...MEMORY_WRITE_SCOPES],
739
738
  required: false,
740
- constraint: 'Store to write to (builtin documents ship with the package and installed-plugin documents are managed by `crtr pkg`, so neither is writable). Default: project when cwd sits in a project, else user. `project` resolves to the NEAREST ancestor `.crouter/` walking up from cwd — in a nested workspace that can be a parent’s store, not the dir you are standing in; pass --dir to pin the exact project. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile. `node` writes the this-node store (`nodes/<CRTR_NODE_ID>/context/memory/`), the nearest scope, seen only by this running node. Conflicts with --dir.',
739
+ constraint: 'Store to write to (package-shipped documents and installed-plugin documents are managed outside this command, so neither is writable). Default: project when cwd sits in a project, else user. `project` resolves to the NEAREST ancestor `.crouter/` walking up from cwd — in a nested workspace that can be a parent’s store, not the dir you are standing in; pass --dir to pin the exact project. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile. `node` writes the this-node store (`nodes/<CRTR_NODE_ID>/context/memory/`), the nearest scope, seen only by this running node. Conflicts with --dir.',
741
740
  },
742
741
  'dir': {
743
742
  kind: 'flag',
@@ -763,6 +762,15 @@ export function selectorParam(name, overrides = {}, extraConstraint) {
763
762
  const constraint = extraConstraint === undefined ? base.constraint : `${base.constraint} ${extraConstraint}`;
764
763
  return { ...base, ...overrides, constraint };
765
764
  }
765
+ export function coerceLintIgnore(raw) {
766
+ if (raw === undefined)
767
+ return undefined;
768
+ const rules = Array.isArray(raw) ? raw : [raw];
769
+ if (rules.length === 0 || rules.some((rule) => typeof rule !== 'string' || !SUPPRESSIBLE_RULES.includes(rule))) {
770
+ throw usage(`invalid lint-ignore: ${JSON.stringify(raw)} (suppressible rules: ${SUPPRESSIBLE_RULES.map((rule) => `\`${rule}\``).join(', ')})`);
771
+ }
772
+ return rules.length === 1 ? rules[0] : rules;
773
+ }
766
774
  export const FRONTMATTER_OVERLAY_PARAMS = {
767
775
  'kind': { kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: false, constraint: 'Document kind.' },
768
776
  'when-and-why-to-read': { kind: 'flag', name: 'when-and-why-to-read', type: 'string', required: false, constraint: 'ONE routing sentence: "When <circumstance>, this <kind> should be read because <broader downstream payoff>." WHY is the reader\u2019s payoff \u2014 the consequence they secure for their task by reading \u2014 NEVER the doc summary, its rule, or that rule reworded as an outcome (a benefit-shaped restatement still fails). Rendered verbatim as the preview.' },
@@ -770,6 +778,7 @@ export const FRONTMATTER_OVERLAY_PARAMS = {
770
778
  'unlisted': { kind: 'flag', name: 'unlisted', type: 'bool', required: false, default: false, constraint: 'Suppress this doc from directory listings. Suppression only — explicit reads, [[links]], and surfaces entries still work.' },
771
779
  'surface': { kind: 'flag', name: 'surface', type: 'string', required: false, repeatable: true, constraint: 'One routing entry per occurrence, as a YAML/JSON object `{on, at, match?, match-frontmatter?, gate?}`; the flag set replaces the document’s whole surfaces list. `on` is boot|workspace-open|read|memory-read|command|pre-command; `at` is name|preview|content. `match` holds the event’s globs — required on read/memory-read/command/pre-command (a read entry may carry `match-frontmatter`, a predicate over the read file’s own frontmatter, instead), meaningless on boot/workspace-open. Optional entry `gate` is a node-config predicate using the same vocabulary as the document gate; its event constraints and gate both match before it participates. A `./`-anchored glob is relative: for `read` to the store’s owning repo dir, for `memory-read` to this doc’s routing anchor — its own canonical name when the doc is its directory’s document (`<dir>/INDEX.md`), otherwise the canonical directory it sits in. Participating entries fold to their highest `at`; there is no cross-entry deny precedence.' },
772
780
  'gate': { kind: 'flag', name: 'gate', type: 'string', required: false, constraint: 'Frontmatter gate \u2014 YAML/JSON object predicate over node config using the same field/matcher vocabulary described in the guide.' },
781
+ 'lint-ignore': { kind: 'flag', name: 'lint-ignore', type: 'enum', choices: [...SUPPRESSIBLE_RULES], required: false, repeatable: true, constraint: 'Suppress one authoring lint rule for this document. Repeat for a list; valid rules: length, broad-memory-read.' },
773
782
  'slash': { kind: 'flag', name: 'slash', type: 'bool', required: false, default: false, constraint: 'Presence flags this doc invocable as a pi slash command (`/<name>`, `/` in a nested name rendered as `:`) \u2014 the doc body becomes the command\u2019s injected prompt. Default false: most docs are consulted, not invoked.' },
774
783
  };
775
784
  /** One overlay flag, with leaf-specific prose appended to the shared
@@ -1,3 +1,4 @@
1
+ import { envNodeId, envProfileId } from '../../shared/env.js';
1
2
  import { join } from 'node:path';
2
3
  import { defineLeaf } from '../../core/command.js';
3
4
  import { usage } from '../../core/errors.js';
@@ -10,7 +11,7 @@ import { parsedSubstrateSurfaces } from '../../core/substrate/frontmatter-valida
10
11
  import { ensureDir, realpathOrSelf, walkFiles, writeText } from '../../core/fs-utils.js';
11
12
  import { associateRepository } from '../../core/memory/repository-association.js';
12
13
  import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor, } from '../../core/memory/history.js';
13
- import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, planNewDocumentPlacement, resolveWriteSelector, requireMountedStore, buildOrigin, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, requireCanonicalName, selectorParam, parseRequestedExtensionChanges, applyExtensionChanges, serializeMemoryDoc, } from './shared.js';
14
+ import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, planNewDocumentPlacement, resolveWriteSelector, requireMountedStore, buildOrigin, coerceGate, coerceLintIgnore, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, requireCanonicalName, selectorParam, parseRequestedExtensionChanges, applyExtensionChanges, serializeMemoryDoc, } from './shared.js';
14
15
  function emptyMemoryStore(memoryDir) {
15
16
  return walkFiles(memoryDir, (name) => name.endsWith('.md') && name !== 'SKILL.md', (name) => name === '.crouter').length === 0;
16
17
  }
@@ -59,6 +60,7 @@ export const writeLeaf = defineLeaf({
59
60
  overlayParam('unlisted'),
60
61
  overlayParam('surface'),
61
62
  overlayParam('gate'),
63
+ overlayParam('lint-ignore'),
62
64
  overlayParam('slash'),
63
65
  { kind: 'flag', name: 'rationale', type: 'string', required: false, constraint: DOC_RATIONALE_CONSTRAINT },
64
66
  { kind: 'flag', name: 'extension', type: 'string', required: false, repeatable: true, constraint: 'Set one declared plugin field as `extensions.<plugin>.<field>=VALUE`. The path is required in full; booleans accept only true or false, numbers require finite numeric syntax, and strings/enums preserve the literal text after the first =. Every requested field validates before the document is written.' },
@@ -116,6 +118,7 @@ export const writeLeaf = defineLeaf({
116
118
  canonicalName = placement.canonicalName;
117
119
  path = placement.path;
118
120
  }
121
+ const lintIgnore = coerceLintIgnore(input['lintIgnore']);
119
122
  const extensionChanges = parseRequestedExtensionChanges(input['extension'], undefined, memoryExtensionCatalogForDoc({ scope, path }));
120
123
  const frontmatter = {};
121
124
  frontmatter['kind'] = kind;
@@ -140,6 +143,7 @@ export const writeLeaf = defineLeaf({
140
143
  }
141
144
  if (input['gate'] !== undefined)
142
145
  frontmatter['gate'] = coerceGate(input['gate']);
146
+ setIf('lint-ignore', lintIgnore);
143
147
  if (input['slash'] === true)
144
148
  frontmatter['slash'] = true;
145
149
  setIf('rationale', input['rationale']);
@@ -188,14 +192,14 @@ export const writeLeaf = defineLeaf({
188
192
  ? {
189
193
  resolvable: resolvableCanonicalNames(loadMemoryTargetView({
190
194
  cwd: store.ownerDir ?? process.cwd(),
191
- profileId: process.env['CRTR_PROFILE_ID'] || null,
192
- nodeId: process.env['CRTR_NODE_ID'] || null,
195
+ profileId: envProfileId() || null,
196
+ nodeId: envNodeId() || null,
193
197
  }, { quiet: true, includeDescendants: true }).docs),
194
198
  }
195
199
  : {}),
196
200
  ...(carriesBootEntry
197
201
  ? {
198
- nestedStore: descendantStoreRoots(projectScopeRoots(process.cwd(), process.env['CRTR_PROFILE_ID'] || null))
202
+ nestedStore: descendantStoreRoots(projectScopeRoots(process.cwd(), envProfileId() || null))
199
203
  .map((root) => realpathOrSelf(join(root, 'memory')))
200
204
  .includes(realpathOrSelf(memoryDir)),
201
205
  }
@@ -1,4 +1,5 @@
1
1
  // `crtr node bash` — file-backed background job controls.
2
+ import { envNodeId } from '../../shared/env.js';
2
3
  import { defineBranch, defineLeaf } from '../../core/command.js';
3
4
  import { InputError } from '../../core/io.js';
4
5
  import { displayMessage } from '../../core/runtime/tmux-chrome.js';
@@ -12,7 +13,7 @@ function resolveBashTarget(input, missingMessage) {
12
13
  if (id === undefined || id === '')
13
14
  id = nodeInPane(pane);
14
15
  if (id === undefined || id === '')
15
- id = process.env['CRTR_NODE_ID'];
16
+ id = envNodeId();
16
17
  if (id === undefined || id === '') {
17
18
  throw new InputError({ error: 'no_node', message: missingMessage, next: 'Run from inside a node, pass --node <id>, or --pane <pane>.' });
18
19
  }
@@ -1,4 +1,5 @@
1
1
  // `crtr node new` — creation commands and persona-kind helpers.
2
+ import { envNodeId } from '../../shared/env.js';
2
3
  import { defineLeaf } from '../../core/command.js';
3
4
  import { InputError } from '../../core/io.js';
4
5
  import { mark } from '../../core/timing.js';
@@ -207,7 +208,7 @@ async function runNodeCreation(input) {
207
208
  if (noKickoff && mode === 'orchestrator') {
208
209
  throw new InputError({ error: 'no_kickoff_orchestrator_unsupported', message: '--no-kickoff cannot be combined with --mode orchestrator', field: 'no-kickoff', next: 'Drop --no-kickoff and provide a prompt, or spawn in base mode.' });
209
210
  }
210
- const parentId = parent ?? process.env['CRTR_NODE_ID'];
211
+ const parentId = parent ?? envNodeId();
211
212
  mark('node_new.create_start', { root });
212
213
  const detail = await cliClient().createNode({
213
214
  ...(kind !== undefined ? { kind } : {}),