@crouton-kit/crouter 0.3.28 → 0.3.30

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 (324) hide show
  1. package/dist/build-root.d.ts +15 -7
  2. package/dist/build-root.js +48 -34
  3. package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
  4. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
  5. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
  6. package/dist/builtin-memory/crouter-development/personas.md +1 -1
  7. package/dist/builtin-memory/crouter-development/plugins.md +20 -18
  8. package/dist/builtin-memory/design.md +1 -1
  9. package/dist/builtin-memory/development.md +1 -1
  10. package/dist/builtin-memory/internal/INDEX.md +1 -1
  11. package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
  12. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  13. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  14. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  15. package/dist/builtin-memory/planning.md +1 -1
  16. package/dist/builtin-memory/spec.md +6 -4
  17. package/dist/builtin-personas/orchestration-kernel.md +4 -5
  18. package/dist/builtin-personas/runtime-base.md +5 -4
  19. package/dist/builtin-personas/spec/PERSONA.md +8 -4
  20. package/dist/builtin-personas/spec/orchestrator.md +5 -3
  21. package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
  22. package/dist/builtin-views/canvas/core.mjs +586 -0
  23. package/dist/builtin-views/canvas/text.mjs +58 -0
  24. package/dist/builtin-views/canvas/tui.mjs +165 -0
  25. package/dist/builtin-views/canvas/web.jsx +120 -0
  26. package/dist/builtin-views/git-pr/core.mjs +673 -0
  27. package/dist/builtin-views/git-pr/text.mjs +84 -0
  28. package/dist/builtin-views/git-pr/tui.mjs +302 -0
  29. package/dist/builtin-views/git-pr/web.jsx +216 -0
  30. package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
  31. package/dist/builtin-views/inbox/core.mjs +1273 -0
  32. package/dist/builtin-views/inbox/text.mjs +73 -0
  33. package/dist/builtin-views/inbox/tui.mjs +312 -0
  34. package/dist/builtin-views/inbox/web.jsx +188 -0
  35. package/dist/builtin-views/linkedin/core.mjs +906 -0
  36. package/dist/builtin-views/linkedin/text.mjs +69 -0
  37. package/dist/builtin-views/linkedin/tui.mjs +427 -0
  38. package/dist/builtin-views/linkedin/web.jsx +206 -0
  39. package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
  40. package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
  41. package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
  42. package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
  43. package/dist/cli.js +22 -17
  44. package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
  45. package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
  46. package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
  47. package/dist/clients/attach/__tests__/git-info.test.js +31 -0
  48. package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
  49. package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
  50. package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
  51. package/dist/clients/attach/attach-cmd.js +282 -46
  52. package/dist/clients/attach/auth-pickers.d.ts +9 -0
  53. package/dist/clients/attach/auth-pickers.js +194 -0
  54. package/dist/clients/attach/canvas-panels.d.ts +4 -2
  55. package/dist/clients/attach/canvas-panels.js +45 -17
  56. package/dist/clients/attach/chat-view.d.ts +27 -2
  57. package/dist/clients/attach/chat-view.js +98 -9
  58. package/dist/clients/attach/clipboard-image.d.ts +47 -8
  59. package/dist/clients/attach/clipboard-image.js +150 -26
  60. package/dist/clients/attach/clipboard-text.d.ts +3 -0
  61. package/dist/clients/attach/clipboard-text.js +39 -0
  62. package/dist/clients/attach/config-load.d.ts +54 -7
  63. package/dist/clients/attach/config-load.js +99 -3
  64. package/dist/clients/attach/context-message.d.ts +12 -0
  65. package/dist/clients/attach/context-message.js +72 -0
  66. package/dist/clients/attach/git-info.d.ts +20 -0
  67. package/dist/clients/attach/git-info.js +44 -0
  68. package/dist/clients/attach/graph-overlay.d.ts +13 -4
  69. package/dist/clients/attach/graph-overlay.js +59 -19
  70. package/dist/clients/attach/input-controller.d.ts +75 -13
  71. package/dist/clients/attach/input-controller.js +305 -67
  72. package/dist/clients/attach/pickers.d.ts +58 -0
  73. package/dist/clients/attach/pickers.js +171 -0
  74. package/dist/clients/attach/slash-commands.d.ts +22 -0
  75. package/dist/clients/attach/slash-commands.js +91 -25
  76. package/dist/clients/attach/titled-editor.d.ts +33 -0
  77. package/dist/clients/attach/titled-editor.js +84 -0
  78. package/dist/clients/attach/view-socket.d.ts +18 -1
  79. package/dist/clients/attach/view-socket.js +70 -1
  80. package/dist/clients/web/dev-server.d.ts +7 -0
  81. package/dist/clients/web/dev-server.js +59 -0
  82. package/dist/clients/web/events.d.ts +14 -0
  83. package/dist/clients/web/events.js +151 -0
  84. package/dist/clients/web/server.d.ts +18 -0
  85. package/dist/clients/web/server.js +450 -0
  86. package/dist/clients/web/web-cmd.d.ts +2 -0
  87. package/dist/clients/web/web-cmd.js +120 -0
  88. package/dist/commands/canvas.js +1 -2
  89. package/dist/commands/chord.js +4 -25
  90. package/dist/commands/dashboard.js +5 -1
  91. package/dist/commands/memory/__tests__/lint-schema.test.js +6 -6
  92. package/dist/commands/memory/lint.js +14 -2
  93. package/dist/commands/memory/read.js +2 -2
  94. package/dist/commands/memory/shared.d.ts +7 -1
  95. package/dist/commands/memory/shared.js +18 -3
  96. package/dist/commands/memory/write.js +8 -4
  97. package/dist/commands/memory.js +4 -4
  98. package/dist/commands/node.d.ts +1 -1
  99. package/dist/commands/node.js +64 -65
  100. package/dist/commands/pkg.js +1 -2
  101. package/dist/commands/revive.js +11 -3
  102. package/dist/commands/sys/sync.d.ts +1 -0
  103. package/dist/commands/sys/sync.js +187 -0
  104. package/dist/commands/sys.js +3 -2
  105. package/dist/commands/view-cycle.js +2 -2
  106. package/dist/commands/view-list.js +8 -8
  107. package/dist/commands/view-new.js +21 -17
  108. package/dist/commands/view-pick.js +1 -1
  109. package/dist/commands/view-run.js +35 -14
  110. package/dist/commands/view.js +8 -6
  111. package/dist/commands/workspace.d.ts +2 -0
  112. package/dist/commands/workspace.js +161 -0
  113. package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
  114. package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
  115. package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
  116. package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
  117. package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
  118. package/dist/core/__tests__/child-death-wake.test.js +56 -111
  119. package/dist/core/__tests__/close.test.js +14 -20
  120. package/dist/core/__tests__/context-intro.test.js +19 -19
  121. package/dist/core/__tests__/daemon-boot.test.js +30 -23
  122. package/dist/core/__tests__/draw-style.test.js +1 -1
  123. package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
  124. package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
  125. package/dist/core/__tests__/fixtures/fake-engine.d.ts +17 -1
  126. package/dist/core/__tests__/fixtures/fake-engine.js +22 -2
  127. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  128. package/dist/core/__tests__/focuses.test.js +25 -13
  129. package/dist/core/__tests__/fork.test.js +22 -5
  130. package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
  131. package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
  132. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
  133. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
  134. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
  135. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
  136. package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
  137. package/dist/core/__tests__/full/cascade-close.test.js +22 -8
  138. package/dist/core/__tests__/full/detach-focus.test.js +66 -169
  139. package/dist/core/__tests__/helpers/harness.d.ts +6 -9
  140. package/dist/core/__tests__/helpers/harness.js +19 -36
  141. package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
  142. package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
  143. package/dist/core/__tests__/lifecycle.test.js +1 -19
  144. package/dist/core/__tests__/listing-completeness.test.js +2 -2
  145. package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
  146. package/dist/core/__tests__/memory-resolver.test.js +1 -1
  147. package/dist/core/__tests__/on-read-dedup-resume.test.js +2 -2
  148. package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
  149. package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
  150. package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
  151. package/dist/core/__tests__/relaunch-root.test.js +114 -0
  152. package/dist/core/__tests__/reset.test.js +19 -79
  153. package/dist/core/__tests__/steer-note.test.js +9 -7
  154. package/dist/core/__tests__/tmux-surface.test.js +13 -7
  155. package/dist/core/bootstrap.d.ts +0 -3
  156. package/dist/core/bootstrap.js +1 -143
  157. package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
  158. package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
  159. package/dist/core/canvas/browse/app.js +131 -8
  160. package/dist/core/canvas/browse/model.d.ts +23 -5
  161. package/dist/core/canvas/browse/model.js +68 -13
  162. package/dist/core/canvas/browse/render.d.ts +7 -1
  163. package/dist/core/canvas/browse/render.js +178 -47
  164. package/dist/core/canvas/canvas.js +1 -1
  165. package/dist/core/canvas/focuses.d.ts +4 -4
  166. package/dist/core/canvas/focuses.js +7 -7
  167. package/dist/core/canvas/nav-model.js +8 -7
  168. package/dist/core/canvas/render.d.ts +68 -9
  169. package/dist/core/canvas/render.js +195 -61
  170. package/dist/core/canvas/types.d.ts +16 -23
  171. package/dist/core/config.js +1 -4
  172. package/dist/core/memory-resolver.js +43 -5
  173. package/dist/core/runtime/bearings.d.ts +1 -1
  174. package/dist/core/runtime/bearings.js +8 -8
  175. package/dist/core/runtime/branded-host.d.ts +16 -0
  176. package/dist/core/runtime/branded-host.js +127 -0
  177. package/dist/core/runtime/broker-protocol.d.ts +212 -6
  178. package/dist/core/runtime/broker.d.ts +4 -1
  179. package/dist/core/runtime/broker.js +494 -58
  180. package/dist/core/runtime/close.d.ts +12 -2
  181. package/dist/core/runtime/close.js +37 -16
  182. package/dist/core/runtime/front-door.js +6 -14
  183. package/dist/core/runtime/host.d.ts +14 -34
  184. package/dist/core/runtime/host.js +14 -50
  185. package/dist/core/runtime/launch.d.ts +12 -9
  186. package/dist/core/runtime/launch.js +27 -15
  187. package/dist/core/runtime/lifecycle.d.ts +1 -1
  188. package/dist/core/runtime/lifecycle.js +15 -19
  189. package/dist/core/runtime/nodes.d.ts +0 -55
  190. package/dist/core/runtime/nodes.js +6 -74
  191. package/dist/core/runtime/placement.d.ts +91 -335
  192. package/dist/core/runtime/placement.js +291 -858
  193. package/dist/core/runtime/promote.d.ts +2 -0
  194. package/dist/core/runtime/promote.js +24 -6
  195. package/dist/core/runtime/recap.d.ts +8 -0
  196. package/dist/core/runtime/recap.js +107 -0
  197. package/dist/core/runtime/recycle.js +25 -61
  198. package/dist/core/runtime/reset.d.ts +43 -37
  199. package/dist/core/runtime/reset.js +131 -218
  200. package/dist/core/runtime/revive.d.ts +9 -29
  201. package/dist/core/runtime/revive.js +40 -139
  202. package/dist/core/runtime/spawn.d.ts +15 -16
  203. package/dist/core/runtime/spawn.js +165 -194
  204. package/dist/core/runtime/surface-bg.d.ts +11 -0
  205. package/dist/core/runtime/surface-bg.js +68 -0
  206. package/dist/core/runtime/tmux.d.ts +15 -52
  207. package/dist/core/runtime/tmux.js +35 -99
  208. package/dist/core/scope.d.ts +1 -2
  209. package/dist/core/scope.js +1 -5
  210. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
  211. package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
  212. package/dist/core/skill-sync/builtins.d.ts +42 -0
  213. package/dist/core/skill-sync/builtins.js +112 -0
  214. package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
  215. package/dist/core/skill-sync/claude-plugins.js +71 -0
  216. package/dist/core/skill-sync/engine.d.ts +42 -0
  217. package/dist/core/skill-sync/engine.js +633 -0
  218. package/dist/core/skill-sync/export.d.ts +23 -0
  219. package/dist/core/skill-sync/export.js +86 -0
  220. package/dist/core/skill-sync/manifest.d.ts +64 -0
  221. package/dist/core/skill-sync/manifest.js +181 -0
  222. package/dist/core/skill-sync/profile.d.ts +76 -0
  223. package/dist/core/skill-sync/profile.js +173 -0
  224. package/dist/core/skill-sync/snapshot.d.ts +57 -0
  225. package/dist/core/skill-sync/snapshot.js +120 -0
  226. package/dist/core/substrate/index.d.ts +1 -1
  227. package/dist/core/substrate/index.js +1 -1
  228. package/dist/core/substrate/injected-store.js +3 -3
  229. package/dist/core/substrate/on-read.js +68 -6
  230. package/dist/core/substrate/render.d.ts +8 -11
  231. package/dist/core/substrate/render.js +29 -43
  232. package/dist/core/substrate/schema.d.ts +10 -3
  233. package/dist/core/substrate/schema.js +6 -3
  234. package/dist/core/tui/host.d.ts +12 -4
  235. package/dist/core/tui/host.js +280 -149
  236. package/dist/core/view/bridge.d.ts +10 -0
  237. package/dist/core/view/bridge.js +31 -0
  238. package/dist/core/view/chrome.d.ts +9 -0
  239. package/dist/core/view/chrome.js +22 -0
  240. package/dist/core/view/contract.d.ts +171 -0
  241. package/dist/core/view/contract.js +23 -0
  242. package/dist/core/view/loader.d.ts +31 -0
  243. package/dist/core/view/loader.js +188 -0
  244. package/dist/core/view/transport-local.d.ts +7 -0
  245. package/dist/core/view/transport-local.js +70 -0
  246. package/dist/core/view/transport.d.ts +4 -0
  247. package/dist/core/view/transport.js +15 -0
  248. package/dist/daemon/crtrd.d.ts +38 -27
  249. package/dist/daemon/crtrd.js +303 -376
  250. package/dist/daemon/manage.js +6 -1
  251. package/dist/index.js +1 -1
  252. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
  253. package/dist/pi-extensions/canvas-context-intro.js +4 -4
  254. package/dist/pi-extensions/canvas-doc-substrate.js +16 -16
  255. package/dist/pi-extensions/canvas-nav.js +5 -0
  256. package/dist/pi-extensions/canvas-recap.d.ts +37 -0
  257. package/dist/pi-extensions/canvas-recap.js +236 -0
  258. package/dist/pi-extensions/canvas-resume.js +2 -1
  259. package/dist/pi-extensions/canvas-stophook.d.ts +14 -12
  260. package/dist/pi-extensions/canvas-stophook.js +87 -101
  261. package/dist/pi-extensions/canvas-view.js +2 -1
  262. package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
  263. package/dist/pi-extensions/widget-order-bus.js +34 -0
  264. package/dist/prompts/view.d.ts +2 -2
  265. package/dist/prompts/view.js +145 -73
  266. package/dist/types.d.ts +1 -6
  267. package/dist/types.js +1 -3
  268. package/dist/web/ViewChrome.d.ts +7 -0
  269. package/dist/web/ViewChrome.js +28 -0
  270. package/dist/web/ViewPane.d.ts +39 -0
  271. package/dist/web/ViewPane.js +48 -0
  272. package/dist/web/index.d.ts +6 -0
  273. package/dist/web/index.js +16 -0
  274. package/dist/web/runtime.d.ts +39 -0
  275. package/dist/web/runtime.js +133 -0
  276. package/dist/web/states.d.ts +24 -0
  277. package/dist/web/states.js +24 -0
  278. package/dist/web/transport-http.d.ts +5 -0
  279. package/dist/web/transport-http.js +28 -0
  280. package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
  281. package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
  282. package/dist/web-client/index.html +13 -0
  283. package/package.json +20 -6
  284. package/dist/builtin-views/canvas/client.mjs +0 -303
  285. package/dist/builtin-views/canvas/view.mjs +0 -576
  286. package/dist/builtin-views/git-pr/client.mjs +0 -440
  287. package/dist/builtin-views/git-pr/view.mjs +0 -675
  288. package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
  289. package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
  290. package/dist/builtin-views/inbox/view.mjs +0 -889
  291. package/dist/builtin-views/linkedin/client.mjs +0 -610
  292. package/dist/builtin-views/linkedin/view.mjs +0 -1171
  293. package/dist/commands/pkg/bridge.d.ts +0 -1
  294. package/dist/commands/pkg/bridge.js +0 -137
  295. package/dist/commands/skill/author.d.ts +0 -3
  296. package/dist/commands/skill/author.js +0 -140
  297. package/dist/commands/skill/shared.d.ts +0 -3
  298. package/dist/commands/skill/shared.js +0 -19
  299. package/dist/commands/skill.d.ts +0 -2
  300. package/dist/commands/skill.js +0 -21
  301. package/dist/commands/tmux-spread.d.ts +0 -2
  302. package/dist/commands/tmux-spread.js +0 -144
  303. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
  304. package/dist/core/__tests__/full/placement-focus.test.js +0 -309
  305. package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
  306. package/dist/core/__tests__/full/placement-revive.test.js +0 -238
  307. package/dist/core/__tests__/full/placement-teardown.test.js +0 -297
  308. package/dist/core/__tests__/home-session.test.js +0 -186
  309. package/dist/core/__tests__/relaunch.test.js +0 -335
  310. package/dist/core/bridge-map.d.ts +0 -19
  311. package/dist/core/bridge-map.js +0 -73
  312. package/dist/core/tui/contract.d.ts +0 -83
  313. package/dist/core/tui/contract.js +0 -8
  314. package/dist/core/tui/loader.d.ts +0 -16
  315. package/dist/core/tui/loader.js +0 -94
  316. package/dist/prompts/skill.d.ts +0 -2
  317. package/dist/prompts/skill.js +0 -650
  318. /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
  319. /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
  320. /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
  321. /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
  322. /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
  323. /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
  324. /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/titled-editor.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
+ }
@@ -5,5 +5,5 @@ 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';
@@ -15,5 +15,5 @@ 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';
@@ -11,9 +11,9 @@
11
11
  //
12
12
  // This module persists the set to `nodes/<id>/injected-docs.json` so the resumed
13
13
  // process rehydrates it and skips docs already present in the transcript. The
14
- // launch paths that begin a FRESH transcript (reviveNode resume=false,
15
- // reviveInPlace, relaunchRootInPane — all in runtime/revive.ts) call
16
- // clearInjectedDocs(), so a new conversation starts with an empty set.
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
17
  //
18
18
  // All ops are best-effort: a failed read/write degrades to a possible re-inject,
19
19
  // never a crash — a dedup miss must never break a read or a revive.
@@ -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
@@ -1,8 +1,8 @@
1
1
  import type { Scope } from '../../types.js';
2
2
  import type { MemoryDoc } from '../memory-resolver.js';
3
- export declare const KINDS: readonly ["skill", "reference", "preference"];
3
+ export declare const KINDS: readonly ["knowledge", "preference"];
4
4
  export type DocKind = (typeof KINDS)[number];
5
- /** Is `v` one of the three valid document kinds? */
5
+ /** Is `v` one of the two valid document kinds? */
6
6
  export declare function isDocKind(v: unknown): v is DocKind;
7
7
  export declare const RUNGS: readonly ["none", "name", "preview", "content"];
8
8
  export type Rung = (typeof RUNGS)[number];
@@ -21,7 +21,7 @@ export type GatePredicate = Record<string, unknown>;
21
21
  * defaults applied. Required fields (`kind`/`when-and-why-to-read`) and
22
22
  * optionals all resolved to concrete typed values. */
23
23
  export interface SubstrateSchema {
24
- /** Which of the three semantic kinds. */
24
+ /** Which of the two semantic kinds (knowledge | preference). */
25
25
  kind: DocKind;
26
26
  /** The read-routing line — a single sentence answering WHEN to read this doc
27
27
  * and WHY it is worth the read: "When <circumstance>, this <kind> should be
@@ -43,6 +43,13 @@ export interface SubstrateSchema {
43
43
  /** Optional glob list narrowing the on-read trigger to matching read files.
44
44
  * Absent ⇒ positional trigger only. A single glob is normalized to a 1-list. */
45
45
  appliesTo?: string[];
46
+ /** Optional condition over the READ FILE's own frontmatter — the on-read
47
+ * frontmatter trigger (Stream A native rules). Same coercion + predicate
48
+ * vocabulary as `gate` (non-null non-array object carried; empty `{}` inert),
49
+ * but evaluated by `evalCondition` against the read file's parsed YAML rather
50
+ * than the node subject. Absent ⇒ no frontmatter trigger. Frontmatter key
51
+ * `read-when`. */
52
+ readWhen?: GatePredicate;
46
53
  }
47
54
  /** A fully-resolved substrate document: the parsed schema PLUS the resolver's
48
55
  * path-derived identity and body. This single object flows through the whole
@@ -12,10 +12,12 @@
12
12
  // floor `none` — a malformed doc renders invisible rather than crashing, and
13
13
  // lint flags it. Valid docs never hit the fallback.
14
14
  // ---------------------------------------------------------------------------
15
- // Kinds — the three semantic kinds (design §3). `kind` is data, not a fork.
15
+ // Kinds — the two semantic kinds (design §3): `knowledge` (consult procedural
16
+ // playbooks + factual references merged) vs `preference` (behave — standing
17
+ // directives). `kind` is a load-bearing binary, not a content taxonomy.
16
18
  // ---------------------------------------------------------------------------
17
- export const KINDS = ['skill', 'reference', 'preference'];
18
- /** Is `v` one of the three valid document kinds? */
19
+ export const KINDS = ['knowledge', 'preference'];
20
+ /** Is `v` one of the two valid document kinds? */
19
21
  export function isDocKind(v) {
20
22
  return typeof v === 'string' && KINDS.includes(v);
21
23
  }
@@ -66,6 +68,7 @@ export function parseSubstrateFrontmatter(fm) {
66
68
  fileReadVisibility: parseRung(fm['file-read-visibility'], FALLBACK_RUNG),
67
69
  gate: parseGate(fm.gate),
68
70
  appliesTo: parseAppliesTo(fm['applies-to']),
71
+ readWhen: parseGate(fm['read-when']),
69
72
  };
70
73
  }
71
74
  /** Parse a resolved MemoryDoc into a fully-typed SubstrateDoc (schema + the
@@ -1,5 +1,5 @@
1
1
  import { type Draw, type Rect, type Size } from './draw.js';
2
- import type { ViewModule, ViewManifest, BannerLevel } from './contract.js';
2
+ import type { ViewCore, TuiPresenter, TextPresenter, KeyHint, BannerLevel } from '../view/contract.js';
3
3
  export interface RunViewOptions {
4
4
  /** CLI flags forwarded verbatim to the view via host.options. */
5
5
  options?: Record<string, string>;
@@ -19,11 +19,19 @@ export interface Chrome {
19
19
  subtitle: string | null;
20
20
  mode: string | null;
21
21
  }
22
+ /** The slice of a view's manifest that drawChrome reads (title + subtitle), plus
23
+ * the footer hints lifted from the TuiPresenter's keymap bindings. */
24
+ export interface ChromeManifest {
25
+ title: string;
26
+ subtitle?: string;
27
+ keymap?: KeyHint[];
28
+ }
22
29
  /** Draw the host chrome into `draw` and return the content Rect for the view.
23
30
  * Three zones: title row (state rail + title/subtitle + state chip + liveness),
24
31
  * a hairline, and a footer (status left, keymap right) with a severity banner
25
32
  * on the row above it when set. */
26
- declare function drawChrome(draw: Draw, size: Size, manifest: ViewManifest, c: Chrome, now?: number): Rect;
33
+ declare function drawChrome(draw: Draw, size: Size, manifest: ChromeManifest, c: Chrome, now?: number): Rect;
27
34
  export { drawChrome };
28
- /** Host a view in the alt screen until it quits (or Ctrl-C). */
29
- export declare function runView<S>(view: ViewModule<S>, opts?: RunViewOptions): Promise<void>;
35
+ /** Host a dual-target ViewCore + TuiPresenter in the alt screen until it quits
36
+ * (or Ctrl-C). `text` is the optional text presenter for the piped path. */
37
+ export declare function runCoreView<S>(core: ViewCore<S>, tui: TuiPresenter<S>, text: TextPresenter<S> | null, opts?: RunViewOptions): Promise<void>;