@crouton-kit/crouter 0.3.27 → 0.3.29

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 (343) hide show
  1. package/dist/build-root.js +4 -2
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  6. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  7. package/dist/builtin-memory/design.md +1 -1
  8. package/dist/builtin-memory/development.md +1 -1
  9. package/dist/builtin-memory/internal/INDEX.md +1 -1
  10. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  12. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  13. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  14. package/dist/builtin-memory/planning.md +1 -1
  15. package/dist/builtin-memory/spec.md +6 -4
  16. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  17. package/dist/builtin-personas/runtime-base.md +8 -4
  18. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  19. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  20. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  21. package/dist/builtin-views/canvas/core.mjs +586 -0
  22. package/dist/builtin-views/canvas/text.mjs +58 -0
  23. package/dist/builtin-views/canvas/tui.mjs +165 -0
  24. package/dist/builtin-views/canvas/web.jsx +120 -0
  25. package/dist/builtin-views/git-pr/core.mjs +673 -0
  26. package/dist/builtin-views/git-pr/text.mjs +84 -0
  27. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  28. package/dist/builtin-views/git-pr/web.jsx +216 -0
  29. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  30. package/dist/builtin-views/inbox/core.mjs +1273 -0
  31. package/dist/builtin-views/inbox/text.mjs +73 -0
  32. package/dist/builtin-views/inbox/tui.mjs +312 -0
  33. package/dist/builtin-views/inbox/web.jsx +188 -0
  34. package/dist/builtin-views/linkedin/core.mjs +906 -0
  35. package/dist/builtin-views/linkedin/text.mjs +69 -0
  36. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  37. package/dist/builtin-views/linkedin/web.jsx +206 -0
  38. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  39. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  40. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  41. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  42. package/dist/cli.js +3 -3
  43. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  44. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  45. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  46. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  47. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  48. package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
  51. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  52. package/dist/clients/attach/attach-cmd.js +445 -41
  53. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  54. package/dist/clients/attach/auth-pickers.js +194 -0
  55. package/dist/clients/attach/canvas-panels.d.ts +12 -0
  56. package/dist/clients/attach/canvas-panels.js +78 -0
  57. package/dist/clients/attach/chat-view.d.ts +27 -2
  58. package/dist/clients/attach/chat-view.js +98 -9
  59. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  60. package/dist/clients/attach/clipboard-image.js +150 -26
  61. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  62. package/dist/clients/attach/clipboard-text.js +39 -0
  63. package/dist/clients/attach/config-load.d.ts +54 -7
  64. package/dist/clients/attach/config-load.js +99 -3
  65. package/dist/clients/attach/context-message.d.ts +12 -0
  66. package/dist/clients/attach/context-message.js +72 -0
  67. package/dist/clients/attach/git-info.d.ts +20 -0
  68. package/dist/clients/attach/git-info.js +44 -0
  69. package/dist/clients/attach/graph-overlay.d.ts +43 -0
  70. package/dist/clients/attach/graph-overlay.js +306 -0
  71. package/dist/clients/attach/input-controller.d.ts +81 -13
  72. package/dist/clients/attach/input-controller.js +307 -67
  73. package/dist/clients/attach/pickers.d.ts +58 -0
  74. package/dist/clients/attach/pickers.js +171 -0
  75. package/dist/clients/attach/slash-commands.d.ts +44 -1
  76. package/dist/clients/attach/slash-commands.js +250 -27
  77. package/dist/clients/attach/titled-editor.d.ts +33 -0
  78. package/dist/clients/attach/titled-editor.js +84 -0
  79. package/dist/clients/attach/view-socket.d.ts +37 -2
  80. package/dist/clients/attach/view-socket.js +131 -7
  81. package/dist/clients/web/dev-server.d.ts +7 -0
  82. package/dist/clients/web/dev-server.js +59 -0
  83. package/dist/clients/web/events.d.ts +14 -0
  84. package/dist/clients/web/events.js +151 -0
  85. package/dist/clients/web/server.d.ts +18 -0
  86. package/dist/clients/web/server.js +450 -0
  87. package/dist/clients/web/web-cmd.d.ts +2 -0
  88. package/dist/clients/web/web-cmd.js +120 -0
  89. package/dist/commands/canvas.js +1 -2
  90. package/dist/commands/chord.js +1 -1
  91. package/dist/commands/dashboard.js +5 -1
  92. package/dist/commands/human/prompts.js +3 -3
  93. package/dist/commands/human/queue.d.ts +17 -0
  94. package/dist/commands/human/queue.js +111 -4
  95. package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
  96. package/dist/commands/memory/lint.d.ts +5 -4
  97. package/dist/commands/memory/lint.js +23 -7
  98. package/dist/commands/memory/read.js +2 -2
  99. package/dist/commands/memory/shared.d.ts +7 -1
  100. package/dist/commands/memory/shared.js +18 -3
  101. package/dist/commands/memory/write.js +20 -7
  102. package/dist/commands/memory.js +4 -4
  103. package/dist/commands/node.d.ts +1 -1
  104. package/dist/commands/node.js +64 -65
  105. package/dist/commands/pkg.js +1 -2
  106. package/dist/commands/revive.js +11 -3
  107. package/dist/commands/sys/feedback.d.ts +1 -0
  108. package/dist/commands/sys/feedback.js +163 -0
  109. package/dist/commands/sys/sync.d.ts +1 -0
  110. package/dist/commands/sys/sync.js +187 -0
  111. package/dist/commands/sys.js +4 -2
  112. package/dist/commands/view-cycle.js +2 -2
  113. package/dist/commands/view-list.js +8 -8
  114. package/dist/commands/view-new.js +21 -17
  115. package/dist/commands/view-pick.js +1 -1
  116. package/dist/commands/view-run.js +35 -14
  117. package/dist/commands/view.js +8 -6
  118. package/dist/commands/workspace.d.ts +2 -0
  119. package/dist/commands/workspace.js +161 -0
  120. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  121. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  122. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  123. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  124. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  125. package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
  126. package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
  127. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  128. package/dist/core/__tests__/close.test.js +14 -20
  129. package/dist/core/__tests__/context-intro.test.js +19 -19
  130. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  131. package/dist/core/__tests__/draw-style.test.js +1 -1
  132. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  133. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  134. package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
  135. package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
  136. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  137. package/dist/core/__tests__/focuses.test.js +25 -13
  138. package/dist/core/__tests__/fork.test.js +22 -5
  139. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  140. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  141. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  142. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  143. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  144. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  145. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  146. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  147. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  148. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  149. package/dist/core/__tests__/helpers/harness.js +19 -36
  150. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  151. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  152. package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
  153. package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
  154. package/dist/core/__tests__/lifecycle.test.js +1 -19
  155. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  156. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  157. package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
  158. package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
  159. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  160. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  161. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  162. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  163. package/dist/core/__tests__/reset.test.js +19 -79
  164. package/dist/core/__tests__/steer-note.test.js +9 -7
  165. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  166. package/dist/core/bootstrap.d.ts +0 -3
  167. package/dist/core/bootstrap.js +1 -143
  168. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  169. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  170. package/dist/core/canvas/browse/app.js +131 -8
  171. package/dist/core/canvas/browse/model.d.ts +23 -5
  172. package/dist/core/canvas/browse/model.js +68 -13
  173. package/dist/core/canvas/browse/render.d.ts +7 -1
  174. package/dist/core/canvas/browse/render.js +178 -47
  175. package/dist/core/canvas/canvas.js +1 -1
  176. package/dist/core/canvas/focuses.d.ts +4 -4
  177. package/dist/core/canvas/focuses.js +7 -7
  178. package/dist/core/canvas/nav-model.d.ts +162 -0
  179. package/dist/core/canvas/nav-model.js +487 -0
  180. package/dist/core/canvas/paths.d.ts +7 -0
  181. package/dist/core/canvas/paths.js +9 -0
  182. package/dist/core/canvas/render.d.ts +68 -9
  183. package/dist/core/canvas/render.js +195 -61
  184. package/dist/core/canvas/types.d.ts +16 -23
  185. package/dist/core/config.js +1 -4
  186. package/dist/core/memory-resolver.js +43 -5
  187. package/dist/core/runtime/bearings.d.ts +1 -1
  188. package/dist/core/runtime/bearings.js +8 -8
  189. package/dist/core/runtime/branded-host.d.ts +16 -0
  190. package/dist/core/runtime/branded-host.js +127 -0
  191. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  192. package/dist/core/runtime/broker-sdk.d.ts +0 -12
  193. package/dist/core/runtime/broker-sdk.js +77 -6
  194. package/dist/core/runtime/broker.d.ts +5 -1
  195. package/dist/core/runtime/broker.js +520 -59
  196. package/dist/core/runtime/close.d.ts +12 -2
  197. package/dist/core/runtime/close.js +37 -16
  198. package/dist/core/runtime/front-door.js +14 -7
  199. package/dist/core/runtime/host.d.ts +14 -34
  200. package/dist/core/runtime/host.js +14 -50
  201. package/dist/core/runtime/launch.d.ts +12 -9
  202. package/dist/core/runtime/launch.js +27 -15
  203. package/dist/core/runtime/lifecycle.d.ts +1 -1
  204. package/dist/core/runtime/lifecycle.js +15 -19
  205. package/dist/core/runtime/nodes.d.ts +0 -55
  206. package/dist/core/runtime/nodes.js +6 -74
  207. package/dist/core/runtime/placement.d.ts +91 -334
  208. package/dist/core/runtime/placement.js +262 -828
  209. package/dist/core/runtime/promote.d.ts +2 -0
  210. package/dist/core/runtime/promote.js +24 -6
  211. package/dist/core/runtime/recap.d.ts +8 -0
  212. package/dist/core/runtime/recap.js +107 -0
  213. package/dist/core/runtime/recycle.js +25 -61
  214. package/dist/core/runtime/reset.d.ts +43 -37
  215. package/dist/core/runtime/reset.js +131 -218
  216. package/dist/core/runtime/revive.d.ts +9 -29
  217. package/dist/core/runtime/revive.js +45 -135
  218. package/dist/core/runtime/spawn.d.ts +15 -11
  219. package/dist/core/runtime/spawn.js +168 -136
  220. package/dist/core/runtime/surface-bg.d.ts +11 -0
  221. package/dist/core/runtime/surface-bg.js +68 -0
  222. package/dist/core/runtime/tmux.d.ts +15 -43
  223. package/dist/core/runtime/tmux.js +38 -80
  224. package/dist/core/scope.d.ts +1 -2
  225. package/dist/core/scope.js +1 -5
  226. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  227. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  228. package/dist/core/skill-sync/builtins.d.ts +42 -0
  229. package/dist/core/skill-sync/builtins.js +112 -0
  230. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  231. package/dist/core/skill-sync/claude-plugins.js +71 -0
  232. package/dist/core/skill-sync/engine.d.ts +42 -0
  233. package/dist/core/skill-sync/engine.js +633 -0
  234. package/dist/core/skill-sync/export.d.ts +23 -0
  235. package/dist/core/skill-sync/export.js +86 -0
  236. package/dist/core/skill-sync/manifest.d.ts +64 -0
  237. package/dist/core/skill-sync/manifest.js +181 -0
  238. package/dist/core/skill-sync/profile.d.ts +76 -0
  239. package/dist/core/skill-sync/profile.js +173 -0
  240. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  241. package/dist/core/skill-sync/snapshot.js +120 -0
  242. package/dist/core/spawn.d.ts +14 -0
  243. package/dist/core/spawn.js +29 -9
  244. package/dist/core/substrate/index.d.ts +2 -2
  245. package/dist/core/substrate/index.js +7 -7
  246. package/dist/core/substrate/injected-store.d.ts +10 -0
  247. package/dist/core/substrate/injected-store.js +55 -0
  248. package/dist/core/substrate/on-read.js +68 -6
  249. package/dist/core/substrate/render.d.ts +8 -11
  250. package/dist/core/substrate/render.js +29 -43
  251. package/dist/core/substrate/schema.d.ts +16 -11
  252. package/dist/core/substrate/schema.js +32 -31
  253. package/dist/core/tui/host.d.ts +12 -4
  254. package/dist/core/tui/host.js +280 -149
  255. package/dist/core/view/bridge.d.ts +10 -0
  256. package/dist/core/view/bridge.js +31 -0
  257. package/dist/core/view/chrome.d.ts +9 -0
  258. package/dist/core/view/chrome.js +22 -0
  259. package/dist/core/view/contract.d.ts +171 -0
  260. package/dist/core/view/contract.js +23 -0
  261. package/dist/core/view/loader.d.ts +31 -0
  262. package/dist/core/view/loader.js +188 -0
  263. package/dist/core/view/transport-local.d.ts +7 -0
  264. package/dist/core/view/transport-local.js +70 -0
  265. package/dist/core/view/transport.d.ts +4 -0
  266. package/dist/core/view/transport.js +15 -0
  267. package/dist/daemon/crtrd.d.ts +38 -27
  268. package/dist/daemon/crtrd.js +303 -376
  269. package/dist/daemon/manage.js +6 -1
  270. package/dist/index.js +1 -1
  271. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  272. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  273. package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
  274. package/dist/pi-extensions/canvas-nav.js +35 -385
  275. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  276. package/dist/pi-extensions/canvas-recap.js +236 -0
  277. package/dist/pi-extensions/canvas-resume.js +2 -1
  278. package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
  279. package/dist/pi-extensions/canvas-stophook.js +119 -103
  280. package/dist/pi-extensions/canvas-view.js +2 -1
  281. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  282. package/dist/pi-extensions/widget-order-bus.js +34 -0
  283. package/dist/prompts/view.d.ts +2 -2
  284. package/dist/prompts/view.js +145 -73
  285. package/dist/types.d.ts +1 -6
  286. package/dist/types.js +1 -3
  287. package/dist/web/ViewChrome.d.ts +7 -0
  288. package/dist/web/ViewChrome.js +28 -0
  289. package/dist/web/ViewPane.d.ts +39 -0
  290. package/dist/web/ViewPane.js +48 -0
  291. package/dist/web/index.d.ts +6 -0
  292. package/dist/web/index.js +16 -0
  293. package/dist/web/runtime.d.ts +39 -0
  294. package/dist/web/runtime.js +133 -0
  295. package/dist/web/states.d.ts +24 -0
  296. package/dist/web/states.js +24 -0
  297. package/dist/web/transport-http.d.ts +5 -0
  298. package/dist/web/transport-http.js +28 -0
  299. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  300. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  301. package/dist/web-client/index.html +13 -0
  302. package/package.json +20 -6
  303. package/dist/builtin-views/canvas/client.mjs +0 -303
  304. package/dist/builtin-views/canvas/view.mjs +0 -576
  305. package/dist/builtin-views/git-pr/client.mjs +0 -440
  306. package/dist/builtin-views/git-pr/view.mjs +0 -675
  307. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  308. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  309. package/dist/builtin-views/inbox/view.mjs +0 -889
  310. package/dist/builtin-views/linkedin/client.mjs +0 -610
  311. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  312. package/dist/commands/pkg/bridge.d.ts +0 -1
  313. package/dist/commands/pkg/bridge.js +0 -137
  314. package/dist/commands/skill/author.d.ts +0 -3
  315. package/dist/commands/skill/author.js +0 -140
  316. package/dist/commands/skill/shared.d.ts +0 -3
  317. package/dist/commands/skill/shared.js +0 -19
  318. package/dist/commands/skill.d.ts +0 -2
  319. package/dist/commands/skill.js +0 -21
  320. package/dist/commands/tmux-spread.d.ts +0 -2
  321. package/dist/commands/tmux-spread.js +0 -144
  322. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  323. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  324. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  325. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  326. package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
  327. package/dist/core/__tests__/home-session.test.js +0 -186
  328. package/dist/core/__tests__/relaunch.test.js +0 -335
  329. package/dist/core/bridge-map.d.ts +0 -19
  330. package/dist/core/bridge-map.js +0 -73
  331. package/dist/core/tui/contract.d.ts +0 -83
  332. package/dist/core/tui/contract.js +0 -8
  333. package/dist/core/tui/loader.d.ts +0 -16
  334. package/dist/core/tui/loader.js +0 -94
  335. package/dist/prompts/skill.d.ts +0 -2
  336. package/dist/prompts/skill.js +0 -650
  337. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  338. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  339. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  340. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  341. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  342. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
  343. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Per-pair merge-base snapshot store + conflict-report helpers.
3
+ *
4
+ * All machine-local skill-sync I/O lives here. Nothing in this module is
5
+ * committed: everything is rooted under `~/.crouter/skill-sync/` (R-U9), which
6
+ * is the machine-local, uncommitted tier — never a scope's committed dirs.
7
+ *
8
+ * snapshots/<pair-id>/body.md merge base, frontmatter stripped (R-I3)
9
+ * snapshots/<pair-id>/assets/… byte-for-byte base of every non-SKILL.md
10
+ * file, mirroring subdir structure (R-I3)
11
+ * snapshots/<pair-id>/meta.json { syncedAt, crtrFrontmatter, claudeFrontmatter }
12
+ * conflicts/<pair-id>.md current conflict report, if any (R-E6/OD-4)
13
+ *
14
+ * The snapshot is the SOLE change-detector (R-U4): the engine diffs body/assets
15
+ * against `body.md`/`assets/`, and each frontmatter value against the recorded
16
+ * `meta.json` value — no mtime, no fingerprint, no stamp file.
17
+ */
18
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from 'node:fs';
19
+ import { dirname, join, relative, sep } from 'node:path';
20
+ import { userScopeRoot } from '../scope.js';
21
+ // ── Path roots ───────────────────────────────────────────────────────────────
22
+ /** `~/.crouter/skill-sync/` — the machine-local, uncommitted root (R-U9). */
23
+ export function skillSyncRoot() {
24
+ return join(userScopeRoot(), 'skill-sync');
25
+ }
26
+ function snapshotsRoot() {
27
+ return join(skillSyncRoot(), 'snapshots');
28
+ }
29
+ function conflictsRoot() {
30
+ return join(skillSyncRoot(), 'conflicts');
31
+ }
32
+ /** `~/.crouter/skill-sync/snapshots/<id>/`. */
33
+ export function snapshotDir(id) {
34
+ return join(snapshotsRoot(), id);
35
+ }
36
+ /** `~/.crouter/skill-sync/conflicts/<id>.md`. */
37
+ export function conflictReportPath(id) {
38
+ return join(conflictsRoot(), `${id}.md`);
39
+ }
40
+ // ── Asset helpers ────────────────────────────────────────────────────────────
41
+ /** Recursively read every file under `dir`, keyed by POSIX relpath. */
42
+ function readAssetTree(dir) {
43
+ const out = new Map();
44
+ if (!existsSync(dir))
45
+ return out;
46
+ const walk = (cur) => {
47
+ for (const entry of readdirSync(cur, { withFileTypes: true })) {
48
+ const full = join(cur, entry.name);
49
+ if (entry.isDirectory()) {
50
+ walk(full);
51
+ }
52
+ else if (entry.isFile()) {
53
+ const rel = relative(dir, full).split(sep).join('/');
54
+ out.set(rel, readFileSync(full));
55
+ }
56
+ }
57
+ };
58
+ walk(dir);
59
+ return out;
60
+ }
61
+ // ── Snapshot read / write ────────────────────────────────────────────────────
62
+ /**
63
+ * Load the merge base for `id`, or `null` when the snapshot dir is absent
64
+ * (no prior sync — the engine seeds on first reconcile, R-S1/R-S3).
65
+ */
66
+ export function readSnapshot(id) {
67
+ const dir = snapshotDir(id);
68
+ if (!existsSync(dir))
69
+ return null;
70
+ const bodyPath = join(dir, 'body.md');
71
+ const body = existsSync(bodyPath) ? readFileSync(bodyPath, 'utf8') : '';
72
+ const assets = readAssetTree(join(dir, 'assets'));
73
+ const metaPath = join(dir, 'meta.json');
74
+ const meta = existsSync(metaPath)
75
+ ? JSON.parse(readFileSync(metaPath, 'utf8'))
76
+ : { syncedAt: '', crtrFrontmatter: {}, claudeFrontmatter: {} };
77
+ return { body, assets, meta };
78
+ }
79
+ /**
80
+ * Write the merge base for `id` byte-for-byte. The snapshot dir is rebuilt
81
+ * from scratch so stale assets from a prior base never linger — the on-disk
82
+ * `assets/` always mirrors exactly the passed `assets` map (R-U5/R-I3).
83
+ */
84
+ export function writeSnapshot(id, snapshot) {
85
+ const dir = snapshotDir(id);
86
+ rmSync(dir, { recursive: true, force: true });
87
+ mkdirSync(dir, { recursive: true });
88
+ writeFileSync(join(dir, 'body.md'), snapshot.body, 'utf8');
89
+ const assetsDir = join(dir, 'assets');
90
+ mkdirSync(assetsDir, { recursive: true });
91
+ for (const [rel, buf] of snapshot.assets) {
92
+ const full = join(assetsDir, ...rel.split('/'));
93
+ mkdirSync(dirname(full), { recursive: true });
94
+ writeFileSync(full, buf);
95
+ }
96
+ writeFileSync(join(dir, 'meta.json'), `${JSON.stringify(snapshot.meta, null, 2)}\n`, 'utf8');
97
+ }
98
+ // ── Conflict-report helpers (R-E6 / OD-4) ────────────────────────────────────
99
+ /**
100
+ * Write the current conflict report for `id` to `conflicts/<id>.md`. The engine
101
+ * calls this on any true conflict (overlapping body/asset merge or both-sides
102
+ * non-equivalent frontmatter) after deciding to write nothing for the pair.
103
+ */
104
+ export function writeConflictReport(id, text) {
105
+ const path = conflictReportPath(id);
106
+ mkdirSync(dirname(path), { recursive: true });
107
+ writeFileSync(path, text, 'utf8');
108
+ }
109
+ /**
110
+ * Delete a stale `conflicts/<id>.md` once the pair reconciles cleanly or
111
+ * becomes a no-op (OD-4). No-op if absent — never throws on a missing report.
112
+ */
113
+ export function clearConflictReport(id) {
114
+ rmSync(conflictReportPath(id), { force: true });
115
+ }
116
+ /** True iff a conflict report currently exists for `id`. */
117
+ export function hasConflictReport(id) {
118
+ const path = conflictReportPath(id);
119
+ return existsSync(path) && statSync(path).isFile();
120
+ }
@@ -1,4 +1,18 @@
1
+ /** Does THIS process sit inside a tmux client (its own $TMUX is set)? NOT the
2
+ * gate for the human surface — see tmuxServerReachable. A headless-broker child
3
+ * strips TMUX/TMUX_PANE (broker.ts, anti-stophook-hijack) yet can still drive
4
+ * the canvas tmux server, so gating the surface on this wrongly starves broker
5
+ * nodes of human-in-the-loop. */
1
6
  export declare function isInTmux(): boolean;
7
+ /** Is the canvas tmux SERVER reachable? The canvas runs on the DEFAULT tmux
8
+ * socket (no -L/-S in core/runtime/tmux.ts), so any `crtr` child — including a
9
+ * headless-broker child that has deleted its own $TMUX — can split panes into
10
+ * it. The human surface gates on THIS (can I reach the server to open a pane
11
+ * the user is watching?), not on the caller's $TMUX. `list-clients` exits 0
12
+ * whenever a server is up (even with zero attached clients) and non-zero when
13
+ * no server is running — the true "no tmux to surface into" case that should
14
+ * degrade to the inbox-drain follow-up. */
15
+ export declare function tmuxServerReachable(): boolean;
2
16
  export declare function shellQuote(s: string): string;
3
17
  /** Count panes in a tmux window (0 outside tmux / on error). With `targetPane`,
4
18
  * counts the window THAT pane lives in (the placement decision must reflect the
@@ -3,13 +3,30 @@
3
3
  // A small set of tmux primitives used by the `human` command tree to put the
4
4
  // humanloop TUI in a detached pane: spawnAndDetach (open a pane running a given
5
5
  // command), countPanesInCurrentWindow (placement decision), plus shellQuote and
6
- // isInTmux. The canvas runtime has its own one-window-per-node machinery in
7
- // core/runtime/tmux.ts; this module is only the pane-split path the human TUI
8
- // needs.
6
+ // tmuxServerReachable. The canvas runtime has its own one-window-per-node
7
+ // machinery in core/runtime/tmux.ts; this module is only the pane-split path the
8
+ // human TUI needs.
9
9
  import { spawnSync } from 'node:child_process';
10
+ /** Does THIS process sit inside a tmux client (its own $TMUX is set)? NOT the
11
+ * gate for the human surface — see tmuxServerReachable. A headless-broker child
12
+ * strips TMUX/TMUX_PANE (broker.ts, anti-stophook-hijack) yet can still drive
13
+ * the canvas tmux server, so gating the surface on this wrongly starves broker
14
+ * nodes of human-in-the-loop. */
10
15
  export function isInTmux() {
11
16
  return Boolean(process.env.TMUX);
12
17
  }
18
+ /** Is the canvas tmux SERVER reachable? The canvas runs on the DEFAULT tmux
19
+ * socket (no -L/-S in core/runtime/tmux.ts), so any `crtr` child — including a
20
+ * headless-broker child that has deleted its own $TMUX — can split panes into
21
+ * it. The human surface gates on THIS (can I reach the server to open a pane
22
+ * the user is watching?), not on the caller's $TMUX. `list-clients` exits 0
23
+ * whenever a server is up (even with zero attached clients) and non-zero when
24
+ * no server is running — the true "no tmux to surface into" case that should
25
+ * degrade to the inbox-drain follow-up. */
26
+ export function tmuxServerReachable() {
27
+ const r = spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], { encoding: 'utf8' });
28
+ return r.status === 0;
29
+ }
13
30
  export function shellQuote(s) {
14
31
  return `'${s.replace(/'/g, "'\\''")}'`;
15
32
  }
@@ -34,7 +51,7 @@ export function countPanesInCurrentWindow() {
34
51
  * output on an unresolvable pane, so test for non-empty stdout, not just `.ok`.
35
52
  * False outside tmux / on error. */
36
53
  export function paneAlive(pane) {
37
- if (!isInTmux() || !/^%\d+$/.test(pane))
54
+ if (!/^%\d+$/.test(pane))
38
55
  return false;
39
56
  const r = spawnSync('tmux', ['display-message', '-p', '-t', pane, '#{pane_id}'], {
40
57
  encoding: 'utf8',
@@ -46,7 +63,7 @@ export function paneAlive(pane) {
46
63
  * specify pane here"); only split-window -t takes a pane. null outside tmux /
47
64
  * on a bad pane id / on error / empty. */
48
65
  export function paneWindowTarget(pane) {
49
- if (!isInTmux() || !/^%\d+$/.test(pane))
66
+ if (!/^%\d+$/.test(pane))
50
67
  return null;
51
68
  const r = spawnSync('tmux', ['display-message', '-p', '-t', pane, '#{session_name}:#{window_index}'], { encoding: 'utf8' });
52
69
  if (r.status !== 0)
@@ -59,8 +76,6 @@ export function paneWindowTarget(pane) {
59
76
  * to surface a human prompt in the user's view when nothing in the asking
60
77
  * node's graph is focused. null outside tmux / no client / on error. */
61
78
  export function attachedClientPane() {
62
- if (!isInTmux())
63
- return null;
64
79
  const clients = spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], {
65
80
  encoding: 'utf8',
66
81
  });
@@ -102,10 +117,15 @@ export function scheduleKillCurrentPane(delaySeconds) {
102
117
  * as soon as the new pane is up; does NOT wait for the command to finish.
103
118
  */
104
119
  export function spawnAndDetach(opts) {
105
- if (!isInTmux()) {
120
+ // Gate on the CANVAS tmux server's reachability, NOT the caller's $TMUX. A
121
+ // headless-broker child strips its own $TMUX (broker.ts) but the canvas server
122
+ // is on the default socket, so `split-window -t <pane>` against a resolved
123
+ // target pane (resolveHumanTarget) works fine from it. Only a genuine
124
+ // no-server case degrades to the inbox-drain follow-up.
125
+ if (!tmuxServerReachable()) {
106
126
  return {
107
127
  status: 'not-in-tmux',
108
- message: 'handoff requires tmux (TMUX env var not set)',
128
+ message: 'handoff requires a reachable tmux server',
109
129
  };
110
130
  }
111
131
  const splitArgs = [];
@@ -1,9 +1,9 @@
1
- export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, KIND_DEFAULT_RUNGS, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, } from './schema.js';
1
+ export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, FALLBACK_RUNG, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, } from './schema.js';
2
2
  export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc } from './schema.js';
3
3
  export { scopeForCwd, spineDepth, assembleNodeSubject } from './subject.js';
4
4
  export type { NodeConfigSubject } from './subject.js';
5
5
  export { gatePasses } from './gate.js';
6
6
  export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveRung, } from './ceiling.js';
7
7
  export type { Surface } from './ceiling.js';
8
- export { renderSkillsSection, renderPreferencesSection, renderReferencesBlock, renderMemoryGuidance, } from './render.js';
8
+ export { renderPreferencesSection, renderKnowledgeBlock, renderMemoryGuidance, } from './render.js';
9
9
  export { renderOnReadDocs } from './on-read.js';
@@ -1,19 +1,19 @@
1
1
  // substrate/ — the unified document-substrate keystone: the frontmatter schema,
2
- // the 4-rung visibility ladder, the kind→default-rungs table, the node-config
3
- // subject assembly, and gate evaluation. Pure + well-typed; the shared base the
4
- // CLI verbs, boot render, on-read render, and migrator all build on. See
5
- // design-substrate.md §4/§9 + plan-substrate.md §2.
2
+ // the 4-rung visibility ladder, the node-config subject assembly, and gate
3
+ // evaluation. Pure + well-typed; the shared base the CLI verbs, boot render,
4
+ // on-read render, and migrator all build on. See design-substrate.md §4 +
5
+ // plan-substrate.md §2.
6
6
  export {
7
7
  // kinds
8
8
  KINDS, isDocKind,
9
9
  // ladder
10
10
  RUNGS, rungRank, rungAtLeast,
11
- // defaults
12
- KIND_DEFAULT_RUNGS,
11
+ // fallback floor
12
+ FALLBACK_RUNG,
13
13
  // parse + render-shared helpers
14
14
  parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, } from './schema.js';
15
15
  export { scopeForCwd, spineDepth, assembleNodeSubject } from './subject.js';
16
16
  export { gatePasses } from './gate.js';
17
17
  export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveRung, } from './ceiling.js';
18
- export { renderSkillsSection, renderPreferencesSection, renderReferencesBlock, renderMemoryGuidance, } from './render.js';
18
+ export { renderPreferencesSection, renderKnowledgeBlock, renderMemoryGuidance, } from './render.js';
19
19
  export { renderOnReadDocs } from './on-read.js';
@@ -0,0 +1,10 @@
1
+ /** Rehydrate a node's on-read dedup set from disk. Returns an empty set when the
2
+ * file is absent, unreadable, or malformed (a fresh transcript, or a node that
3
+ * has not yet surfaced any on-read doc). */
4
+ export declare function loadInjectedDocs(nodeId: string): Set<string>;
5
+ /** Persist a node's on-read dedup set. Called after each read that surfaced a
6
+ * new doc, so the grown set survives a later dormancy. */
7
+ export declare function saveInjectedDocs(nodeId: string, seen: Set<string>): void;
8
+ /** Drop a node's persisted dedup set. Called by the launch paths that start a
9
+ * FRESH transcript, so the new conversation surfaces docs from scratch. */
10
+ export declare function clearInjectedDocs(nodeId: string): void;
@@ -0,0 +1,55 @@
1
+ // injected-store.ts — durable per-node dedup set for on-read doc injection.
2
+ //
3
+ // The on-read substrate hook (canvas-doc-substrate.ts → renderOnReadDocs) dedups
4
+ // so a given doc surfaces at most once per conversation. That set USED to live
5
+ // only in the pi process heap, cleared on session_start. But a node's logical
6
+ // session — the .jsonl transcript — spans MULTIPLE pi processes: a dormancy →
7
+ // revive(resume) cycle exits the old process and launches a fresh `pi --session`
8
+ // that REUSES the same transcript. The fresh process started with an empty set,
9
+ // so any doc already injected before dormancy got injected AGAIN on the next
10
+ // read — the "fires a second time per session" bug.
11
+ //
12
+ // This module persists the set to `nodes/<id>/injected-docs.json` so the resumed
13
+ // process rehydrates it and skips docs already present in the transcript. The
14
+ // one launch path that begins a FRESH transcript (reviveNode with resume=false,
15
+ // in runtime/revive.ts) calls clearInjectedDocs(), so a new conversation starts
16
+ // with an empty set.
17
+ //
18
+ // All ops are best-effort: a failed read/write degrades to a possible re-inject,
19
+ // never a crash — a dedup miss must never break a read or a revive.
20
+ import { readFileSync, writeFileSync, rmSync } from 'node:fs';
21
+ import { injectedDocsPath } from '../canvas/paths.js';
22
+ /** Rehydrate a node's on-read dedup set from disk. Returns an empty set when the
23
+ * file is absent, unreadable, or malformed (a fresh transcript, or a node that
24
+ * has not yet surfaced any on-read doc). */
25
+ export function loadInjectedDocs(nodeId) {
26
+ try {
27
+ const arr = JSON.parse(readFileSync(injectedDocsPath(nodeId), 'utf8'));
28
+ if (!Array.isArray(arr))
29
+ return new Set();
30
+ return new Set(arr.filter((x) => typeof x === 'string'));
31
+ }
32
+ catch {
33
+ return new Set();
34
+ }
35
+ }
36
+ /** Persist a node's on-read dedup set. Called after each read that surfaced a
37
+ * new doc, so the grown set survives a later dormancy. */
38
+ export function saveInjectedDocs(nodeId, seen) {
39
+ try {
40
+ writeFileSync(injectedDocsPath(nodeId), JSON.stringify([...seen]));
41
+ }
42
+ catch {
43
+ // best-effort — a failed persist only risks a re-inject, never a crash
44
+ }
45
+ }
46
+ /** Drop a node's persisted dedup set. Called by the launch paths that start a
47
+ * FRESH transcript, so the new conversation surfaces docs from scratch. */
48
+ export function clearInjectedDocs(nodeId) {
49
+ try {
50
+ rmSync(injectedDocsPath(nodeId), { force: true });
51
+ }
52
+ catch {
53
+ // ignore — absence is the desired state anyway
54
+ }
55
+ }
@@ -3,16 +3,25 @@
3
3
  // When a `read` tool call returns, the canvas-doc-substrate pi extension calls
4
4
  // renderOnReadDocs() to surface the substrate docs that should appear ALONGSIDE
5
5
  // the file just read — each at its FILE-READ-VISIBILITY rung (NOT the
6
- // system-prompt rung the boot render uses). Two independent triggers decide
7
- // which docs surface (design §4/§6; plan-substrate.md track D1):
6
+ // system-prompt rung the boot render uses). Three triggers decide which docs
7
+ // surface (design §4/§6; Stream A native rules):
8
8
  //
9
9
  // • POSITIONAL — walk the read file's ancestor dirs; any doc living in an
10
10
  // ancestor's `.crouter/memory/` surfaces (a doc surfaces when a file
11
- // beside/under its own scope dir is read). This mirrors nested-context's
12
- // `.claude/rules` ancestor walk, but keyed on `.crouter/memory/`.
11
+ // beside/under its own scope dir is read), UNLESS it carries an explicit
12
+ // read-trigger (see D5 below). This mirrors nested-context's `.claude/rules`
13
+ // ancestor walk, but keyed on `.crouter/memory/`.
13
14
  // • applies-to GLOB — any RESOLVED substrate doc (user/project/builtin scope)
14
15
  // whose `appliesTo` glob matches the read file path surfaces, regardless of
15
16
  // where the read file sits relative to the doc.
17
+ // • read-when FRONTMATTER (Stream A) — any RESOLVED doc whose `readWhen`
18
+ // condition matches the READ FILE's own parsed frontmatter (markdown only),
19
+ // via `evalCondition` against that YAML rather than the node subject.
20
+ //
21
+ // D5 — an EXPLICIT read-trigger (applies-to OR read-when) SUPPRESSES the
22
+ // positional default for that doc: an author who declared a trigger meant it, so
23
+ // the doc fires only on its trigger, never positionally on every read under its
24
+ // scope. The two explicit triggers compose by OR.
16
25
  //
17
26
  // Each candidate runs the substrate pipeline at its fileReadVisibility rung:
18
27
  // parse → gatePasses(doc, assembleNodeSubject(nodeId)) → render
@@ -35,6 +44,7 @@ import { basename, dirname, join, matchesGlob, parse, relative, sep } from 'node
35
44
  import { CRTR_DIR_NAME } from '../../types.js';
36
45
  import { pathExists, readText, walkFiles } from '../fs-utils.js';
37
46
  import { parseFrontmatterGeneric } from '../frontmatter.js';
47
+ import { evalCondition } from '../predicate.js';
38
48
  import { listAllMemoryDocs } from '../memory-resolver.js';
39
49
  import { assembleNodeSubject, buildCeilingIndex, displayName, effectiveRung, gatePasses, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, } from './index.js';
40
50
  import { cachedSubstrateDocs } from './session-cache.js';
@@ -117,6 +127,12 @@ function safeWalkMd(dir) {
117
127
  return [];
118
128
  }
119
129
  }
130
+ /** Does the doc declare an EXPLICIT read-trigger? (applies-to glob OR read-when
131
+ * frontmatter condition). D5: an explicit trigger SUPPRESSES the positional
132
+ * default — such a doc fires only on its declared trigger, never positionally. */
133
+ function hasExplicitTrigger(doc) {
134
+ return (doc.appliesTo !== undefined && doc.appliesTo.length > 0) || doc.readWhen !== undefined;
135
+ }
120
136
  /** POSITIONAL trigger: every substrate doc in an ancestor dir's
121
137
  * `.crouter/memory/`, walking from the read file up to $HOME (or the
122
138
  * filesystem root for a read outside $HOME), skipping junk ancestors. */
@@ -138,7 +154,9 @@ function positionalCandidates(absReadFile) {
138
154
  continue;
139
155
  seenDocPaths.add(real);
140
156
  const doc = loadPositionalDoc(file, memDir, scope);
141
- if (doc)
157
+ // D5: a doc with an explicit read-trigger (applies-to/read-when) does
158
+ // NOT fire positionally — it surfaces only via that trigger.
159
+ if (doc && !hasExplicitTrigger(doc))
142
160
  out.push({ doc, realpath: real, order: depth });
143
161
  }
144
162
  }
@@ -204,6 +222,49 @@ function appliesToCandidates(absReadFile, taken) {
204
222
  }
205
223
  return out;
206
224
  }
225
+ /** Parse the READ FILE's own YAML frontmatter once (markdown only), the subject
226
+ * a `read-when` condition is evaluated against. Non-markdown reads (and any
227
+ * unreadable / frontmatter-less file) yield `{}` — no `read-when` rule can then
228
+ * match (mirrors pi's frontmatter-rules, which only consider .md/.mdx/.markdown). */
229
+ function readFileFrontmatter(absReadFile) {
230
+ if (!/\.(md|mdx|markdown)$/i.test(absReadFile))
231
+ return {};
232
+ try {
233
+ return parseFrontmatterGeneric(readText(absReadFile)).data ?? {};
234
+ }
235
+ catch {
236
+ return {};
237
+ }
238
+ }
239
+ /** read-when FRONTMATTER trigger (Stream A): every RESOLVED substrate doc whose
240
+ * `readWhen` condition matches the read file's own frontmatter. `taken` carries
241
+ * the realpaths already claimed by earlier passes so a doc is not double-counted.
242
+ * Uses the per-session cache (same corpus the glob pass walks). Returns [] when
243
+ * the read file has no frontmatter — no condition can match an empty subject. */
244
+ function readWhenCandidates(readFileFm, taken) {
245
+ if (Object.keys(readFileFm).length === 0)
246
+ return [];
247
+ let docs;
248
+ try {
249
+ docs = cachedSubstrateDocs(listAllMemoryDocs, parseSubstrateDoc);
250
+ }
251
+ catch {
252
+ return [];
253
+ }
254
+ const out = [];
255
+ for (const doc of docs) {
256
+ if (doc.readWhen === undefined)
257
+ continue;
258
+ const real = realpathOrSelf(doc.path);
259
+ if (taken.has(real))
260
+ continue;
261
+ if (!evalCondition(doc.readWhen, readFileFm))
262
+ continue;
263
+ taken.add(real);
264
+ out.push({ doc, realpath: real, order: -1 });
265
+ }
266
+ return out;
267
+ }
207
268
  // ---------------------------------------------------------------------------
208
269
  // Per-doc envelope render.
209
270
  // ---------------------------------------------------------------------------
@@ -256,7 +317,8 @@ export function renderOnReadDocs(nodeId, readFilePath, seen = new Set()) {
256
317
  const positional = positionalCandidates(absReadFile);
257
318
  const taken = new Set(positional.map((c) => c.realpath));
258
319
  const byGlob = appliesToCandidates(absReadFile, taken);
259
- candidates = [...positional, ...byGlob];
320
+ const byReadWhen = readWhenCandidates(readFileFrontmatter(absReadFile), taken);
321
+ candidates = [...positional, ...byGlob, ...byReadWhen];
260
322
  }
261
323
  catch {
262
324
  return '';
@@ -1,20 +1,17 @@
1
- /** The `<skills>` system-prompt block: every eligible `kind: skill` doc placed
2
- * in one tree at its effective rung. Plugin skills (`<plugin>/…` names) fall out
3
- * naturally as dirs. Returns '' when nothing is eligible. */
4
- export declare function renderSkillsSection(nodeId: string): string;
5
1
  /** The `<preferences>` system-prompt block: every eligible `kind: preference`
6
2
  * doc in one tree at its effective rung (the preference default rung is
7
3
  * `preview` → the routing line). Returns '' when nothing is eligible. */
8
4
  export declare function renderPreferencesSection(nodeId: string): string;
9
- /** The `<references>` block embedded INSIDE the `<crtr-context>` session_start
5
+ /** The `<knowledge>` block embedded INSIDE the `<crtr-context>` session_start
10
6
  * message (bearings.ts pushes the returned string into the block, or drops it
11
- * when ''). Holds every eligible `kind: reference` resolver doc at its effective
12
- * rung (reference boot default is `none`, so resolver references usually surface
13
- * only as `[+N more]` counts unless author-promoted) PLUS the node-local memory
14
- * docs (any kind), the latter floored to `name` so a `none`-rung node-local doc
15
- * still shows its name rather than collapsing into a count. Returns '' when
7
+ * when ''). The consultable catalog: every eligible `kind: knowledge` resolver
8
+ * doc at its effective rung (most surface only as `[+N more]` counts unless
9
+ * author-promoted) PLUS the node-local memory docs (any kind), the latter
10
+ * floored to `name` so a `none`-rung node-local doc still shows its name rather
11
+ * than collapsing into a count. Procedural skills and factual references both
12
+ * live here now — they merged into the one `knowledge` kind. Returns '' when
16
13
  * nothing is eligible. */
17
- export declare function renderReferencesBlock(nodeId: string): string;
14
+ export declare function renderKnowledgeBlock(nodeId: string): string;
18
15
  /** The memory-hygiene directive spliced into the system prompt after the
19
16
  * preferences block. ALWAYS present for a canvas node (the memory system always
20
17
  * exists), so the system-prompt splice stays non-empty even when both trees are
@@ -6,12 +6,13 @@
6
6
  // line; content → the full body indented beneath the entry); hidden (`none`-rung)
7
7
  // docs leak only a `[+N more]` count under their dir, never a name.
8
8
  //
9
- // • the SYSTEM-PROMPT half — `<skills>` (renderSkillsSection) + `<preferences>`
10
- // (renderPreferencesSection) + `<memory-guidance>` (renderMemoryGuidance) —
11
- // strings the canvas-doc-substrate `before_agent_start` extension splices
12
- // into the system prompt before the `\n\nGuidelines:` anchor;
13
- // • the `<crtr-context>` half — `<references>` (renderReferencesBlock) — the
14
- // string bearings.ts embeds in the session_start message.
9
+ // • the SYSTEM-PROMPT half — `<preferences>` (renderPreferencesSection) +
10
+ // `<memory-guidance>` (renderMemoryGuidance) — strings the
11
+ // canvas-doc-substrate `before_agent_start` extension splices into the
12
+ // system prompt before the `\n\nGuidelines:` anchor;
13
+ // • the `<crtr-context>` half — `<knowledge>` (renderKnowledgeBlock) — the
14
+ // consultable catalog (every `kind: knowledge` doc ∪ node-local) the string
15
+ // bearings.ts embeds in the session_start message.
15
16
  //
16
17
  // Every doc flows through the same pipeline: MemoryDoc → parseSubstrateDoc →
17
18
  // (null-filter non-substrate) → ceiling-capped effective rung → gatePasses →
@@ -73,11 +74,11 @@ function scopeRank(scope) {
73
74
  * parseSubstrateFrontmatter (mirroring the resolver pipeline), then gate-passed.
74
75
  * Non-substrate files (those with no `kind`) parse to null and drop out.
75
76
  * Returned across ALL kinds — node-local is the catch-all this-node store and
76
- * rides into the references block.
77
+ * rides into the knowledge block.
77
78
  *
78
79
  * IMPORTANT: node-local docs are NOT filtered or capped by rung. Their contract
79
- * is "node-local rides into references" without qualification, so a `none`-rung
80
- * node-local reference must still surface — the caller floors it to `name` so it
80
+ * is "node-local rides into knowledge" without qualification, so a `none`-rung
81
+ * node-local doc must still surface — the caller floors it to `name` so it
81
82
  * renders its name (never collapsing into a hidden count). Only gate evaluation
82
83
  * removes a node-local doc from the block. */
83
84
  function nodeLocalDocs(nodeId, subject) {
@@ -276,20 +277,17 @@ function buildTree(docs, rootLabel) {
276
277
  // ---------------------------------------------------------------------------
277
278
  const READ_LEGEND = 'Each %s below shows either its full content (indented beneath its name), a ' +
278
279
  '`# read when:` line telling you when to read the full document, or simply its name.';
279
- const SKILLS_INTRO = 'Skills contain procedural knowledge — playbooks and techniques for how to do things. ' +
280
- 'To read a skill, run `crtr memory read <name>`. Reach for a matching skill before ' +
281
- 'improvising. ' +
282
- READ_LEGEND.replace('%s', 'skill');
283
- const SKILLS_OUTRO = 'If you learn a better way to do something a skill covers, update the skill.';
284
280
  const PREFERENCES_INTRO = 'Preferences are how the user wants you to behave — standing directives and corrections. ' +
285
281
  'To read a preference, run `crtr memory read <name>`. ' +
286
282
  READ_LEGEND.replace('%s', 'preference');
287
283
  const PREFERENCES_OUTRO = 'If the user corrects you in a way that contradicts a preference, update the preference.';
288
- const REFERENCES_INTRO = 'References contain documentation and knowledge relating to the user, projects, and this node. ' +
289
- 'To read a reference, run `crtr memory read <name>`. Read them when they seem relevant to the ' +
284
+ const KNOWLEDGE_INTRO = 'Knowledge documents are what you consult — playbooks and techniques for how to do things, and ' +
285
+ 'references on the user, projects, and this node. To read one, run `crtr memory read <name>`. ' +
286
+ 'Reach for a matching document before improvising, and read one when it seems relevant to the ' +
290
287
  'task at hand. ' +
291
- READ_LEGEND.replace('%s', 'reference');
292
- const REFERENCES_OUTRO = 'If you gain information that directly contradicts a reference, update the reference.';
288
+ READ_LEGEND.replace('%s', 'document');
289
+ const KNOWLEDGE_OUTRO = 'If you learn a better way to do something a document covers, or gain information that ' +
290
+ 'contradicts one, update that document.';
293
291
  /** Wrap an intro + tree + outro in a kind-named block, or '' when the tree is
294
292
  * empty (the whole block is dropped). */
295
293
  function wrapBlock(tag, intro, tree, outro) {
@@ -298,20 +296,7 @@ function wrapBlock(tag, intro, tree, outro) {
298
296
  return `<${tag}>\n${intro}\n\n${tree}\n\n${outro}\n</${tag}>`;
299
297
  }
300
298
  // ---------------------------------------------------------------------------
301
- // 1. Skills — `<skills>` (system prompt).
302
- // ---------------------------------------------------------------------------
303
- /** The `<skills>` system-prompt block: every eligible `kind: skill` doc placed
304
- * in one tree at its effective rung. Plugin skills (`<plugin>/…` names) fall out
305
- * naturally as dirs. Returns '' when nothing is eligible. */
306
- export function renderSkillsSection(nodeId) {
307
- const subject = cachedNodeSubject(nodeId, assembleNodeSubject);
308
- if (subject === null)
309
- return '';
310
- const tree = buildTree(effectiveDocs(subject, 'skill'), 'skills');
311
- return wrapBlock('skills', SKILLS_INTRO, tree, SKILLS_OUTRO);
312
- }
313
- // ---------------------------------------------------------------------------
314
- // 2. Preferences — `<preferences>` (system prompt).
299
+ // 1. Preferences — `<preferences>` (system prompt).
315
300
  // ---------------------------------------------------------------------------
316
301
  /** The `<preferences>` system-prompt block: every eligible `kind: preference`
317
302
  * doc in one tree at its effective rung (the preference default rung is
@@ -324,28 +309,29 @@ export function renderPreferencesSection(nodeId) {
324
309
  return wrapBlock('preferences', PREFERENCES_INTRO, tree, PREFERENCES_OUTRO);
325
310
  }
326
311
  // ---------------------------------------------------------------------------
327
- // 3. References — `<references>` (inside the <crtr-context> message).
312
+ // 2. Knowledge — `<knowledge>` (inside the <crtr-context> message).
328
313
  // ---------------------------------------------------------------------------
329
- /** The `<references>` block embedded INSIDE the `<crtr-context>` session_start
314
+ /** The `<knowledge>` block embedded INSIDE the `<crtr-context>` session_start
330
315
  * message (bearings.ts pushes the returned string into the block, or drops it
331
- * when ''). Holds every eligible `kind: reference` resolver doc at its effective
332
- * rung (reference boot default is `none`, so resolver references usually surface
333
- * only as `[+N more]` counts unless author-promoted) PLUS the node-local memory
334
- * docs (any kind), the latter floored to `name` so a `none`-rung node-local doc
335
- * still shows its name rather than collapsing into a count. Returns '' when
316
+ * when ''). The consultable catalog: every eligible `kind: knowledge` resolver
317
+ * doc at its effective rung (most surface only as `[+N more]` counts unless
318
+ * author-promoted) PLUS the node-local memory docs (any kind), the latter
319
+ * floored to `name` so a `none`-rung node-local doc still shows its name rather
320
+ * than collapsing into a count. Procedural skills and factual references both
321
+ * live here now — they merged into the one `knowledge` kind. Returns '' when
336
322
  * nothing is eligible. */
337
- export function renderReferencesBlock(nodeId) {
323
+ export function renderKnowledgeBlock(nodeId) {
338
324
  const subject = cachedNodeSubject(nodeId, assembleNodeSubject);
339
325
  if (subject === null)
340
326
  return '';
341
327
  const nodeLocal = nodeLocalDocs(nodeId, subject).map((d) => rungRank(d.systemPromptVisibility) >= rungRank('name')
342
328
  ? d
343
329
  : { ...d, systemPromptVisibility: 'name' });
344
- const tree = buildTree([...effectiveDocs(subject, 'reference'), ...nodeLocal], 'references');
345
- return wrapBlock('references', REFERENCES_INTRO, tree, REFERENCES_OUTRO);
330
+ const tree = buildTree([...effectiveDocs(subject, 'knowledge'), ...nodeLocal], 'knowledge');
331
+ return wrapBlock('knowledge', KNOWLEDGE_INTRO, tree, KNOWLEDGE_OUTRO);
346
332
  }
347
333
  // ---------------------------------------------------------------------------
348
- // 4. Memory-saving hygiene guidance — `<memory-guidance>` (system prompt).
334
+ // 3. Memory-saving hygiene guidance — `<memory-guidance>` (system prompt).
349
335
  // ---------------------------------------------------------------------------
350
336
  /** The memory-hygiene directive spliced into the system prompt after the
351
337
  * preferences block. ALWAYS present for a canvas node (the memory system always