@crouton-kit/crouter 0.3.17 → 0.3.19

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 (283) hide show
  1. package/dist/build-root.js +6 -0
  2. package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
  3. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
  4. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
  5. package/dist/builtin-memory/crouter-development/personas.md +117 -0
  6. package/dist/builtin-memory/crouter-development/plugins.md +163 -0
  7. package/dist/builtin-memory/design.md +60 -0
  8. package/dist/builtin-memory/development.md +116 -0
  9. package/dist/builtin-memory/planning.md +66 -0
  10. package/dist/builtin-memory/spec.md +99 -0
  11. package/dist/builtin-personas/design/{base.md → PERSONA.md} +4 -0
  12. package/dist/builtin-personas/design/orchestrator.md +1 -1
  13. package/dist/builtin-personas/developer/{base.md → PERSONA.md} +4 -0
  14. package/dist/builtin-personas/developer/orchestrator.md +1 -1
  15. package/dist/builtin-personas/explore/{base.md → PERSONA.md} +4 -0
  16. package/dist/builtin-personas/general/{base.md → PERSONA.md} +4 -0
  17. package/dist/builtin-personas/lifecycle/resident.md +1 -1
  18. package/dist/builtin-personas/lifecycle/terminal.md +5 -2
  19. package/dist/builtin-personas/orchestration-kernel.md +15 -24
  20. package/dist/builtin-personas/plan/{base.md → PERSONA.md} +4 -0
  21. package/dist/builtin-personas/plan/orchestrator.md +1 -1
  22. package/dist/builtin-personas/plan/reviewers/architecture-fit/{base.md → PERSONA.md} +1 -1
  23. package/dist/builtin-personas/plan/reviewers/code-smells/{base.md → PERSONA.md} +1 -1
  24. package/dist/builtin-personas/plan/reviewers/pattern-consistency/{base.md → PERSONA.md} +1 -1
  25. package/dist/builtin-personas/plan/reviewers/requirements-coverage/{base.md → PERSONA.md} +1 -1
  26. package/dist/builtin-personas/plan/reviewers/security/{base.md → PERSONA.md} +1 -1
  27. package/dist/builtin-personas/review/{base.md → PERSONA.md} +4 -0
  28. package/dist/builtin-personas/runtime-base.md +6 -3
  29. package/dist/builtin-personas/spec/{base.md → PERSONA.md} +4 -0
  30. package/dist/builtin-personas/spec/orchestrator.md +1 -1
  31. package/dist/builtin-personas/waiting.md +8 -0
  32. package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +24 -14
  33. package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +4 -4
  34. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
  35. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
  36. package/dist/builtin-views/_lib/states.mjs +161 -0
  37. package/dist/builtin-views/canvas/client.mjs +303 -0
  38. package/dist/builtin-views/canvas/view.mjs +576 -0
  39. package/dist/builtin-views/git-pr/client.mjs +440 -0
  40. package/dist/builtin-views/git-pr/view.mjs +675 -0
  41. package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
  42. package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
  43. package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
  44. package/dist/builtin-views/inbox/view.mjs +889 -0
  45. package/dist/builtin-views/linkedin/client.mjs +610 -0
  46. package/dist/builtin-views/linkedin/view.mjs +1171 -0
  47. package/dist/clients/attach/attach-cmd.d.ts +2 -0
  48. package/dist/clients/attach/attach-cmd.js +354 -0
  49. package/dist/clients/attach/chat-view.d.ts +77 -0
  50. package/dist/clients/attach/chat-view.js +450 -0
  51. package/dist/clients/attach/clipboard-image.d.ts +16 -0
  52. package/dist/clients/attach/clipboard-image.js +113 -0
  53. package/dist/clients/attach/config-load.d.ts +31 -0
  54. package/dist/clients/attach/config-load.js +113 -0
  55. package/dist/clients/attach/extension-dialogs.d.ts +29 -0
  56. package/dist/clients/attach/extension-dialogs.js +101 -0
  57. package/dist/clients/attach/input-controller.d.ts +54 -0
  58. package/dist/clients/attach/input-controller.js +204 -0
  59. package/dist/clients/attach/slash-commands.d.ts +36 -0
  60. package/dist/clients/attach/slash-commands.js +200 -0
  61. package/dist/clients/attach/view-socket.d.ts +48 -0
  62. package/dist/clients/attach/view-socket.js +126 -0
  63. package/dist/commands/attention.js +3 -3
  64. package/dist/commands/canvas-prune.js +1 -1
  65. package/dist/commands/daemon.js +4 -3
  66. package/dist/commands/human/prompts.js +4 -10
  67. package/dist/commands/human/queue.js +43 -8
  68. package/dist/commands/human/shared.d.ts +28 -1
  69. package/dist/commands/human/shared.js +48 -10
  70. package/dist/commands/memory/find.d.ts +1 -0
  71. package/dist/commands/memory/find.js +180 -0
  72. package/dist/commands/memory/lint.d.ts +1 -0
  73. package/dist/commands/memory/lint.js +140 -0
  74. package/dist/commands/memory/list.d.ts +1 -0
  75. package/dist/commands/memory/list.js +79 -0
  76. package/dist/commands/memory/read.js +103 -0
  77. package/dist/commands/memory/shared.d.ts +30 -0
  78. package/dist/commands/memory/shared.js +122 -0
  79. package/dist/commands/memory/write.d.ts +1 -0
  80. package/dist/commands/memory/write.js +85 -0
  81. package/dist/commands/memory.d.ts +2 -0
  82. package/dist/commands/memory.js +27 -0
  83. package/dist/commands/node.d.ts +3 -2
  84. package/dist/commands/node.js +660 -65
  85. package/dist/commands/pkg/market-manage.js +1 -1
  86. package/dist/commands/push.js +6 -6
  87. package/dist/commands/revive.js +1 -1
  88. package/dist/commands/skill/author.js +1 -1
  89. package/dist/commands/skill/shared.d.ts +1 -8
  90. package/dist/commands/skill/shared.js +2 -55
  91. package/dist/commands/skill.js +9 -14
  92. package/dist/commands/sys/doctor.js +1 -1
  93. package/dist/commands/sys/update.js +1 -1
  94. package/dist/commands/view-cycle.d.ts +2 -0
  95. package/dist/commands/view-cycle.js +125 -0
  96. package/dist/commands/view-list.d.ts +2 -0
  97. package/dist/commands/view-list.js +66 -0
  98. package/dist/commands/view-new.d.ts +2 -0
  99. package/dist/commands/view-new.js +70 -0
  100. package/dist/commands/view-pick.d.ts +2 -0
  101. package/dist/commands/view-pick.js +119 -0
  102. package/dist/commands/view-run.d.ts +2 -0
  103. package/dist/commands/view-run.js +191 -0
  104. package/dist/commands/view.d.ts +2 -0
  105. package/dist/commands/view.js +29 -0
  106. package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
  107. package/dist/core/__tests__/broker-lifecycle.test.js +677 -0
  108. package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
  109. package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
  110. package/dist/core/__tests__/cascade-close.test.js +12 -2
  111. package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
  112. package/dist/core/__tests__/child-death-wake.test.js +245 -0
  113. package/dist/core/__tests__/context-intro.test.js +76 -62
  114. package/dist/core/__tests__/daemon-boot.test.js +14 -5
  115. package/dist/core/__tests__/daemon-liveness.test.js +34 -9
  116. package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
  117. package/dist/core/__tests__/detach-focus.test.js +206 -0
  118. package/dist/core/__tests__/draw-style.test.d.ts +1 -0
  119. package/dist/core/__tests__/draw-style.test.js +258 -0
  120. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
  121. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
  122. package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
  123. package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
  124. package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
  125. package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
  126. package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
  127. package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
  128. package/dist/core/__tests__/helpers/harness.d.ts +9 -0
  129. package/dist/core/__tests__/helpers/harness.js +111 -1
  130. package/dist/core/__tests__/home-session.test.js +41 -8
  131. package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
  132. package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
  133. package/dist/core/__tests__/human-surface-target.test.d.ts +1 -0
  134. package/dist/core/__tests__/human-surface-target.test.js +98 -0
  135. package/dist/core/__tests__/kickoff.test.js +37 -3
  136. package/dist/core/__tests__/live-mutation.test.js +50 -33
  137. package/dist/core/__tests__/memory.test.js +23 -115
  138. package/dist/core/__tests__/persona-subkind.test.js +18 -15
  139. package/dist/core/__tests__/placement-focus.test.js +5 -0
  140. package/dist/core/__tests__/placement-teardown.test.js +54 -11
  141. package/dist/core/__tests__/relaunch.test.js +4 -3
  142. package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
  143. package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
  144. package/dist/core/__tests__/spawn-root.test.js +10 -0
  145. package/dist/core/__tests__/spike-harness.test.js +1 -0
  146. package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
  147. package/dist/core/__tests__/wake-bearings.test.js +156 -0
  148. package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
  149. package/dist/core/__tests__/wake-origin.test.js +110 -0
  150. package/dist/core/bootstrap.js +1 -1
  151. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  152. package/dist/core/canvas/browse/app.js +24 -2
  153. package/dist/core/canvas/browse/model.d.ts +38 -2
  154. package/dist/core/canvas/browse/model.js +134 -10
  155. package/dist/core/canvas/browse/render.d.ts +6 -12
  156. package/dist/core/canvas/browse/render.js +72 -104
  157. package/dist/core/canvas/canvas.js +15 -22
  158. package/dist/core/canvas/db.js +46 -0
  159. package/dist/core/canvas/index.d.ts +1 -0
  160. package/dist/core/canvas/index.js +1 -0
  161. package/dist/core/canvas/paths.d.ts +4 -1
  162. package/dist/core/canvas/paths.js +10 -4
  163. package/dist/core/canvas/pid.d.ts +4 -0
  164. package/dist/core/canvas/pid.js +23 -0
  165. package/dist/core/canvas/render.d.ts +11 -2
  166. package/dist/core/canvas/render.js +69 -0
  167. package/dist/core/canvas/types.d.ts +85 -3
  168. package/dist/core/canvas/types.js +2 -2
  169. package/dist/core/canvas/wakeups.d.ts +76 -0
  170. package/dist/core/canvas/wakeups.js +185 -0
  171. package/dist/core/config.js +4 -1
  172. package/dist/core/frontmatter.js +37 -124
  173. package/dist/core/help.d.ts +6 -0
  174. package/dist/core/help.js +7 -0
  175. package/dist/core/memory-resolver.d.ts +49 -0
  176. package/dist/core/memory-resolver.js +141 -0
  177. package/dist/core/personas/index.d.ts +4 -3
  178. package/dist/core/personas/index.js +3 -2
  179. package/dist/core/personas/loader.d.ts +41 -16
  180. package/dist/core/personas/loader.js +133 -29
  181. package/dist/core/personas/resolve.d.ts +4 -4
  182. package/dist/core/personas/resolve.js +28 -16
  183. package/dist/core/predicate.d.ts +63 -0
  184. package/dist/core/predicate.js +189 -0
  185. package/dist/core/resolver.js +26 -5
  186. package/dist/core/runtime/bearings.d.ts +53 -12
  187. package/dist/core/runtime/bearings.js +132 -59
  188. package/dist/core/runtime/broker-cli.d.ts +1 -0
  189. package/dist/core/runtime/broker-cli.js +46 -0
  190. package/dist/core/runtime/broker-protocol.d.ts +332 -0
  191. package/dist/core/runtime/broker-protocol.js +153 -0
  192. package/dist/core/runtime/broker-sdk.d.ts +48 -0
  193. package/dist/core/runtime/broker-sdk.js +72 -0
  194. package/dist/core/runtime/broker.d.ts +55 -0
  195. package/dist/core/runtime/broker.js +1128 -0
  196. package/dist/core/runtime/close.js +35 -6
  197. package/dist/core/runtime/host.d.ts +53 -0
  198. package/dist/core/runtime/host.js +186 -0
  199. package/dist/core/runtime/kickoff.d.ts +2 -1
  200. package/dist/core/runtime/kickoff.js +91 -5
  201. package/dist/core/runtime/launch.d.ts +45 -2
  202. package/dist/core/runtime/launch.js +65 -2
  203. package/dist/core/runtime/lifecycle.js +23 -6
  204. package/dist/core/runtime/memory.d.ts +2 -42
  205. package/dist/core/runtime/memory.js +11 -162
  206. package/dist/core/runtime/nodes.d.ts +33 -0
  207. package/dist/core/runtime/nodes.js +59 -1
  208. package/dist/core/runtime/persona.js +21 -11
  209. package/dist/core/runtime/pi-vendored.d.ts +18 -0
  210. package/dist/core/runtime/pi-vendored.js +49 -0
  211. package/dist/core/runtime/placement.d.ts +42 -14
  212. package/dist/core/runtime/placement.js +228 -38
  213. package/dist/core/runtime/promote.d.ts +0 -6
  214. package/dist/core/runtime/promote.js +1 -12
  215. package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
  216. package/dist/core/runtime/{demote.js → recycle.js} +27 -11
  217. package/dist/core/runtime/reset.js +8 -6
  218. package/dist/core/runtime/revive.d.ts +2 -0
  219. package/dist/core/runtime/revive.js +34 -34
  220. package/dist/core/runtime/spawn.d.ts +19 -0
  221. package/dist/core/runtime/spawn.js +75 -22
  222. package/dist/core/runtime/stop-guard.d.ts +1 -1
  223. package/dist/core/runtime/stop-guard.js +6 -1
  224. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  225. package/dist/core/runtime/tmux-chrome.js +1 -1
  226. package/dist/core/runtime/tmux.d.ts +28 -0
  227. package/dist/core/runtime/tmux.js +80 -6
  228. package/dist/core/scope.d.ts +11 -0
  229. package/dist/core/scope.js +39 -0
  230. package/dist/core/spawn.d.ts +25 -1
  231. package/dist/core/spawn.js +72 -7
  232. package/dist/core/substrate/gate.d.ts +13 -0
  233. package/dist/core/substrate/gate.js +21 -0
  234. package/dist/core/substrate/index.d.ts +7 -0
  235. package/dist/core/substrate/index.js +18 -0
  236. package/dist/core/substrate/on-read.d.ts +14 -0
  237. package/dist/core/substrate/on-read.js +292 -0
  238. package/dist/core/substrate/render.d.ts +25 -0
  239. package/dist/core/substrate/render.js +256 -0
  240. package/dist/core/substrate/schema.d.ts +76 -0
  241. package/dist/core/substrate/schema.js +124 -0
  242. package/dist/core/substrate/session-cache.d.ts +30 -0
  243. package/dist/core/substrate/session-cache.js +77 -0
  244. package/dist/core/substrate/subject.d.ts +41 -0
  245. package/dist/core/substrate/subject.js +54 -0
  246. package/dist/core/tui/contract.d.ts +83 -0
  247. package/dist/core/tui/contract.js +8 -0
  248. package/dist/core/tui/draw.d.ts +96 -0
  249. package/dist/core/tui/draw.js +339 -0
  250. package/dist/core/tui/host.d.ts +29 -0
  251. package/dist/core/tui/host.js +379 -0
  252. package/dist/core/tui/loader.d.ts +16 -0
  253. package/dist/core/tui/loader.js +94 -0
  254. package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
  255. package/dist/core/wake.d.ts +86 -0
  256. package/dist/core/wake.js +308 -0
  257. package/dist/daemon/crtrd.d.ts +29 -4
  258. package/dist/daemon/crtrd.js +662 -46
  259. package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
  260. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
  261. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
  262. package/dist/pi-extensions/canvas-commands.d.ts +3 -0
  263. package/dist/pi-extensions/canvas-commands.js +10 -0
  264. package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
  265. package/dist/pi-extensions/canvas-context-intro.js +55 -15
  266. package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
  267. package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
  268. package/dist/pi-extensions/canvas-nav.d.ts +3 -0
  269. package/dist/pi-extensions/canvas-nav.js +145 -34
  270. package/dist/pi-extensions/canvas-stophook.js +17 -8
  271. package/dist/pi-extensions/canvas-view.d.ts +21 -0
  272. package/dist/pi-extensions/canvas-view.js +75 -0
  273. package/dist/prompts/skill.js +19 -26
  274. package/dist/prompts/view.d.ts +7 -0
  275. package/dist/prompts/view.js +101 -0
  276. package/dist/types.d.ts +4 -0
  277. package/dist/types.js +1 -0
  278. package/package.json +9 -4
  279. package/dist/commands/skill/find.d.ts +0 -4
  280. package/dist/commands/skill/find.js +0 -257
  281. package/dist/commands/skill/read.js +0 -91
  282. /package/dist/commands/{skill → memory}/read.d.ts +0 -0
  283. /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
@@ -5,9 +5,10 @@
5
5
  * Resolution order (highest → lowest precedence): project > user > builtin.
6
6
  *
7
7
  * Layout on disk:
8
- * <root>/personas/<kind>/base.md
8
+ * <root>/personas/<kind>/PERSONA.md
9
9
  * <root>/personas/<kind>/orchestrator.md
10
10
  * <root>/personas/orchestration-kernel.md
11
+ * <root>/personas/<kind>/<...>/PERSONA.md (nested sub-personas)
11
12
  *
12
13
  * The builtin root is src/builtin-personas (or dist/builtin-personas in the
13
14
  * compiled build), resolved relative to this module — mirrors the pattern used
@@ -38,6 +39,13 @@ export declare function loadKernel(): string;
38
39
  * live in their own fragments, loaded below.
39
40
  */
40
41
  export declare function loadRuntimeBase(): string;
42
+ /**
43
+ * Load the waiting fragment — the cross-kind "waiting is a way to end a turn"
44
+ * operating posture (arm a wake + go dormant instead of busy-looping or
45
+ * finishing-to-stop). Spliced into every node's baked prompt immediately after
46
+ * the lifecycle fragment (resolve.ts). Returns '' if the fragment is missing.
47
+ */
48
+ export declare function loadWaitingFragment(): string;
41
49
  /**
42
50
  * Load the lifecycle fragment — the "how you end" contract, keyed on the node's
43
51
  * lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
@@ -55,29 +63,46 @@ export declare function loadLifecycleFragment(lifecycle: 'terminal' | 'resident'
55
63
  */
56
64
  export declare function loadSpineFragment(hasManager: boolean): string;
57
65
  /**
58
- * Enumerate the kinds with at least one persona file (base.md or
66
+ * Enumerate the kinds with at least one persona file (PERSONA.md or
59
67
  * orchestrator.md) across all scope roots (project/user/builtin). Used to
60
- * validate a requested `--kind` and to list the valid choices.
68
+ * validate a requested `--kind` and to list the valid choices. Only the
69
+ * IMMEDIATE children of each root count — nested sub-personas never pollute
70
+ * the global kind list (see subPersonasFor).
61
71
  */
62
72
  export declare function availableKinds(): string[];
63
- export interface SubKind {
73
+ /**
74
+ * The one-line "when to use this node type" gloss for `kind`, read from its
75
+ * `<kind>/PERSONA.md` `whenToUse` frontmatter (resolved project > user >
76
+ * builtin). Returns '' when the kind has no PERSONA.md or no `whenToUse`.
77
+ * Drives the dynamic kind list in `node new -h` / `node promote -h`.
78
+ */
79
+ export declare function kindWhenToUse(kind: string): string;
80
+ export interface SubPersona {
64
81
  /** Full kind string to spawn, e.g. 'plan/reviewers/security'. */
65
82
  kind: string;
66
83
  /** Leaf name, e.g. 'security'. */
67
84
  name: string;
68
- /** One-line "what it reviews", from the sub-kind base.md `summary` frontmatter (or ''). */
69
- summary: string;
85
+ /** One-line "when to use", from the sub-persona PERSONA.md `whenToUse` frontmatter (or ''). */
86
+ whenToUse: string;
70
87
  }
71
88
  /**
72
- * Enumerate the reviewer sub-kinds owned by `parentKind` — the specialist
73
- * personas at `<root>/<parentKind>/reviewers/<name>/base.md`, scanned across all
74
- * scope roots (project > user > builtin; highest precedence wins per name).
89
+ * Enumerate the sub-personas AVAILABLE TO `kind` — the nested specialist
90
+ * personas (e.g. `plan/reviewers/security`) a `kind` node may spawn, surfaced
91
+ * in its composed prompt (resolve.ts) and nowhere else.
92
+ *
93
+ * A sub-persona is any descendant dir (ANY depth) under a top-level kind dir
94
+ * that holds a PERSONA.md, EXCLUDING the top-level PERSONA.md itself. Its
95
+ * availability is its `availableTo` frontmatter: an explicit list of kind
96
+ * strings, or the wildcard `"*"`/`"all"` (visible to every kind); absent, it
97
+ * defaults to its own top-level ancestor kind. So the five `plan/reviewers/*`
98
+ * (no `availableTo`) are visible only to `plan`, while a sub-persona under
99
+ * `developer/` can declare `availableTo: [plan]` to surface in plan's menu —
100
+ * which is why ALL top-level kinds' descendants are scanned, not just `<kind>/`.
75
101
  *
76
- * Sub-kinds are intentionally NOT global kinds: `availableKinds()` scans only the
77
- * immediate children of each persona root, so `<parentKind>/reviewers/*` never
78
- * leaks into the global list. A sub-kind is reachable only by its full kind
79
- * string and is surfaced only in its parent kind's composed prompt (resolve.ts).
80
- * Kind-parametric: any kind owns a roster simply by adding
81
- * `<kind>/reviewers/<name>/base.md` — no code change.
102
+ * Sub-personas are intentionally NOT global kinds: `availableKinds()` scans only
103
+ * the immediate children of each root, so a nested sub-persona never leaks into
104
+ * the global list; it is reachable only by its full kind string. Precedence is
105
+ * project > user > builtin keyed on the FULL kind string the highest root that
106
+ * defines a given kind string wins (and owns its `availableTo`).
82
107
  */
83
- export declare function subKindsFor(parentKind: string): SubKind[];
108
+ export declare function subPersonasFor(kind: string): SubPersona[];
@@ -5,9 +5,10 @@
5
5
  * Resolution order (highest → lowest precedence): project > user > builtin.
6
6
  *
7
7
  * Layout on disk:
8
- * <root>/personas/<kind>/base.md
8
+ * <root>/personas/<kind>/PERSONA.md
9
9
  * <root>/personas/<kind>/orchestrator.md
10
10
  * <root>/personas/orchestration-kernel.md
11
+ * <root>/personas/<kind>/<...>/PERSONA.md (nested sub-personas)
11
12
  *
12
13
  * The builtin root is src/builtin-personas (or dist/builtin-personas in the
13
14
  * compiled build), resolved relative to this module — mirrors the pattern used
@@ -55,11 +56,28 @@ function personaSearchRoots() {
55
56
  return roots;
56
57
  }
57
58
  // ---------------------------------------------------------------------------
59
+ // Frontmatter scalar coercion
60
+ // ---------------------------------------------------------------------------
61
+ /** Coerce a frontmatter scalar to its string form, matching the legacy
62
+ * hand-rolled parser (which stringified every scalar). Strings pass through;
63
+ * number/boolean coerce via String(); null/undefined and non-scalars
64
+ * (objects/arrays) are dropped (→ null). The `yaml` package returns native
65
+ * scalar types, so without this a `typeof === 'string'` guard would silently
66
+ * DROP a numeric/boolean frontmatter value (e.g. blanking a menu line) where
67
+ * the old parser kept its stringified form. */
68
+ function scalarToString(v) {
69
+ if (typeof v === 'string')
70
+ return v;
71
+ if (typeof v === 'number' || typeof v === 'boolean')
72
+ return String(v);
73
+ return null;
74
+ }
75
+ // ---------------------------------------------------------------------------
58
76
  // File resolution helpers
59
77
  // ---------------------------------------------------------------------------
60
78
  /**
61
79
  * Find the first existing file across the scope roots.
62
- * `relativePath` is relative to each root (e.g. 'general/base.md').
80
+ * `relativePath` is relative to each root (e.g. 'general/PERSONA.md').
63
81
  */
64
82
  function resolveFile(relativePath) {
65
83
  for (const root of personaSearchRoots()) {
@@ -92,6 +110,11 @@ function inlineIncludes(body) {
92
110
  return kernelBody.trim();
93
111
  });
94
112
  }
113
+ // ---------------------------------------------------------------------------
114
+ // Public API
115
+ // ---------------------------------------------------------------------------
116
+ /** The role-body filename for every kind/sub-persona (replaces legacy base.md). */
117
+ const PERSONA_FILE = 'PERSONA.md';
95
118
  /**
96
119
  * Load and parse a persona file for the given `kind` and `mode`.
97
120
  *
@@ -99,7 +122,8 @@ function inlineIncludes(body) {
99
122
  * On success, `@include` directives in the body are resolved and inlined.
100
123
  */
101
124
  export function loadPersona(kind, mode) {
102
- const relativePath = `${kind}/${mode}.md`;
125
+ // base PERSONA.md (the role body); orchestrator → its sibling orchestrator.md.
126
+ const relativePath = mode === 'orchestrator' ? `${kind}/orchestrator.md` : `${kind}/PERSONA.md`;
103
127
  const filePath = resolveFile(relativePath);
104
128
  if (!filePath)
105
129
  return null;
@@ -136,6 +160,19 @@ export function loadRuntimeBase() {
136
160
  const { body } = parseFrontmatterGeneric(src);
137
161
  return body.trim();
138
162
  }
163
+ /**
164
+ * Load the waiting fragment — the cross-kind "waiting is a way to end a turn"
165
+ * operating posture (arm a wake + go dormant instead of busy-looping or
166
+ * finishing-to-stop). Spliced into every node's baked prompt immediately after
167
+ * the lifecycle fragment (resolve.ts). Returns '' if the fragment is missing.
168
+ */
169
+ export function loadWaitingFragment() {
170
+ const filePath = resolveFile('waiting.md');
171
+ if (!filePath)
172
+ return '';
173
+ const { body } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
174
+ return body.trim();
175
+ }
139
176
  /**
140
177
  * Load the lifecycle fragment — the "how you end" contract, keyed on the node's
141
178
  * lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
@@ -165,9 +202,11 @@ export function loadSpineFragment(hasManager) {
165
202
  return body.trim();
166
203
  }
167
204
  /**
168
- * Enumerate the kinds with at least one persona file (base.md or
205
+ * Enumerate the kinds with at least one persona file (PERSONA.md or
169
206
  * orchestrator.md) across all scope roots (project/user/builtin). Used to
170
- * validate a requested `--kind` and to list the valid choices.
207
+ * validate a requested `--kind` and to list the valid choices. Only the
208
+ * IMMEDIATE children of each root count — nested sub-personas never pollute
209
+ * the global kind list (see subPersonasFor).
171
210
  */
172
211
  export function availableKinds() {
173
212
  const kinds = new Set();
@@ -178,42 +217,107 @@ export function availableKinds() {
178
217
  if (!entry.isDirectory())
179
218
  continue;
180
219
  const dir = join(root, entry.name);
181
- if (existsSync(join(dir, 'base.md')) || existsSync(join(dir, 'orchestrator.md'))) {
220
+ if (existsSync(join(dir, PERSONA_FILE)) || existsSync(join(dir, 'orchestrator.md'))) {
182
221
  kinds.add(entry.name);
183
222
  }
184
223
  }
185
224
  }
186
225
  return [...kinds].sort();
187
226
  }
188
- const REVIEWERS_SUBDIR = 'reviewers';
189
227
  /**
190
- * Enumerate the reviewer sub-kinds owned by `parentKind` the specialist
191
- * personas at `<root>/<parentKind>/reviewers/<name>/base.md`, scanned across all
192
- * scope roots (project > user > builtin; highest precedence wins per name).
228
+ * The one-line "when to use this node type" gloss for `kind`, read from its
229
+ * `<kind>/PERSONA.md` `whenToUse` frontmatter (resolved project > user >
230
+ * builtin). Returns '' when the kind has no PERSONA.md or no `whenToUse`.
231
+ * Drives the dynamic kind list in `node new -h` / `node promote -h`.
232
+ */
233
+ export function kindWhenToUse(kind) {
234
+ const filePath = resolveFile(`${kind}/${PERSONA_FILE}`);
235
+ if (!filePath)
236
+ return '';
237
+ const { data } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
238
+ return scalarToString(data?.['whenToUse']) ?? '';
239
+ }
240
+ /** Recursively yield every dir under `dir` (inclusive) that holds a PERSONA.md,
241
+ * with `relKind` = the dir's path relative to the scope root (slash-joined).
242
+ * Dirs WITHOUT a PERSONA.md (e.g. a `reviewers/` grouping namespace) are
243
+ * transparent — they yield nothing themselves but are still descended into,
244
+ * so `plan/reviewers/security` keeps that exact kind string. */
245
+ function* walkPersonaDirs(dir, relParts) {
246
+ const file = join(dir, PERSONA_FILE);
247
+ if (existsSync(file))
248
+ yield { relKind: relParts.join('/'), file };
249
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
250
+ if (entry.isDirectory())
251
+ yield* walkPersonaDirs(join(dir, entry.name), [...relParts, entry.name]);
252
+ }
253
+ }
254
+ /** Parse a sub-persona's `availableTo` frontmatter into its availability set.
255
+ * Returns the wildcard sentinel `'*'` for `"*"`/`"all"` (scalar or in an
256
+ * array), an explicit list of kind strings when present, else the default
257
+ * `[topKind]` (the top-level ancestor kind). */
258
+ function parseAvailableTo(data, topKind) {
259
+ const isWild = (s) => {
260
+ const t = s.trim().toLowerCase();
261
+ return t === '*' || t === 'all';
262
+ };
263
+ const v = data ? data['availableTo'] : undefined;
264
+ if (v === undefined)
265
+ return [topKind];
266
+ const scalar = scalarToString(v);
267
+ if (scalar !== null)
268
+ return isWild(scalar) ? '*' : [scalar];
269
+ if (Array.isArray(v)) {
270
+ const arr = v.map(scalarToString).filter((x) => x !== null);
271
+ if (arr.some(isWild))
272
+ return '*';
273
+ return arr.length > 0 ? arr : [topKind];
274
+ }
275
+ return [topKind];
276
+ }
277
+ /**
278
+ * Enumerate the sub-personas AVAILABLE TO `kind` — the nested specialist
279
+ * personas (e.g. `plan/reviewers/security`) a `kind` node may spawn, surfaced
280
+ * in its composed prompt (resolve.ts) and nowhere else.
193
281
  *
194
- * Sub-kinds are intentionally NOT global kinds: `availableKinds()` scans only the
195
- * immediate children of each persona root, so `<parentKind>/reviewers/*` never
196
- * leaks into the global list. A sub-kind is reachable only by its full kind
197
- * string and is surfaced only in its parent kind's composed prompt (resolve.ts).
198
- * Kind-parametric: any kind owns a roster simply by adding
199
- * `<kind>/reviewers/<name>/base.md` no code change.
282
+ * A sub-persona is any descendant dir (ANY depth) under a top-level kind dir
283
+ * that holds a PERSONA.md, EXCLUDING the top-level PERSONA.md itself. Its
284
+ * availability is its `availableTo` frontmatter: an explicit list of kind
285
+ * strings, or the wildcard `"*"`/`"all"` (visible to every kind); absent, it
286
+ * defaults to its own top-level ancestor kind. So the five `plan/reviewers/*`
287
+ * (no `availableTo`) are visible only to `plan`, while a sub-persona under
288
+ * `developer/` can declare `availableTo: [plan]` to surface in plan's menu —
289
+ * which is why ALL top-level kinds' descendants are scanned, not just `<kind>/`.
290
+ *
291
+ * Sub-personas are intentionally NOT global kinds: `availableKinds()` scans only
292
+ * the immediate children of each root, so a nested sub-persona never leaks into
293
+ * the global list; it is reachable only by its full kind string. Precedence is
294
+ * project > user > builtin keyed on the FULL kind string — the highest root that
295
+ * defines a given kind string wins (and owns its `availableTo`).
200
296
  */
201
- export function subKindsFor(parentKind) {
202
- const byName = new Map();
297
+ export function subPersonasFor(kind) {
298
+ const seen = new Set(); // full kind strings already resolved (higher root won)
299
+ const out = [];
203
300
  for (const root of personaSearchRoots()) {
204
- const dir = join(root, parentKind, REVIEWERS_SUBDIR);
205
- if (!existsSync(dir))
301
+ if (!existsSync(root))
206
302
  continue;
207
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
208
- if (!entry.isDirectory() || byName.has(entry.name))
209
- continue; // higher root already won
210
- const baseFile = join(dir, entry.name, 'base.md');
211
- if (!existsSync(baseFile))
303
+ for (const top of readdirSync(root, { withFileTypes: true })) {
304
+ if (!top.isDirectory())
212
305
  continue;
213
- const { data } = parseFrontmatterGeneric(readFileSync(baseFile, 'utf8'));
214
- const summary = data && typeof data['summary'] === 'string' ? data['summary'] : '';
215
- byName.set(entry.name, { kind: `${parentKind}/${REVIEWERS_SUBDIR}/${entry.name}`, name: entry.name, summary });
306
+ const topKind = top.name;
307
+ for (const { relKind, file } of walkPersonaDirs(join(root, topKind), [topKind])) {
308
+ if (relKind === topKind)
309
+ continue; // the top-level PERSONA.md is the kind itself, not a sub-persona
310
+ if (seen.has(relKind))
311
+ continue; // a higher root already resolved this kind string
312
+ seen.add(relKind);
313
+ const { data } = parseFrontmatterGeneric(readFileSync(file, 'utf8'));
314
+ const availableTo = parseAvailableTo(data, topKind);
315
+ if (availableTo !== '*' && !availableTo.includes(kind))
316
+ continue;
317
+ const whenToUse = scalarToString(data?.['whenToUse']) ?? '';
318
+ out.push({ kind: relKind, name: relKind.split('/').pop(), whenToUse });
319
+ }
216
320
  }
217
321
  }
218
- return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
322
+ return out.sort((a, b) => a.kind.localeCompare(b.kind));
219
323
  }
@@ -4,17 +4,17 @@
4
4
  *
5
5
  * Composition rules:
6
6
  * mode==='base'
7
- * → load <kind>/base.md; if missing fall back to general defaults.
7
+ * → load <kind>/PERSONA.md; if missing fall back to general defaults.
8
8
  *
9
9
  * mode==='orchestrator'
10
10
  * → prefer <kind>/orchestrator.md (which must embed the kernel via
11
11
  * @include orchestration-kernel.md — inlined by the loader).
12
12
  * If no orchestrator.md exists for this kind, compose:
13
- * <kind>/base.md body + '\n\n' + kernel body
14
- * If even the base is missing, fall back to general defaults + kernel.
13
+ * <kind>/PERSONA.md body + '\n\n' + kernel body
14
+ * If even the PERSONA.md is missing, fall back to general defaults + kernel.
15
15
  *
16
16
  * Frontmatter from whichever file is the primary source (orchestrator.md >
17
- * base.md) supplies model/skills/extensions/tools. Lifecycle and spine position
17
+ * PERSONA.md) supplies model/skills/extensions/tools. Lifecycle and spine position
18
18
  * are INPUTS (the caller decides them — root/child, terminal/resident), not
19
19
  * derived here; they select the lifecycle/spine protocol fragments spliced
20
20
  * ahead of the persona body.
@@ -4,32 +4,41 @@
4
4
  *
5
5
  * Composition rules:
6
6
  * mode==='base'
7
- * → load <kind>/base.md; if missing fall back to general defaults.
7
+ * → load <kind>/PERSONA.md; if missing fall back to general defaults.
8
8
  *
9
9
  * mode==='orchestrator'
10
10
  * → prefer <kind>/orchestrator.md (which must embed the kernel via
11
11
  * @include orchestration-kernel.md — inlined by the loader).
12
12
  * If no orchestrator.md exists for this kind, compose:
13
- * <kind>/base.md body + '\n\n' + kernel body
14
- * If even the base is missing, fall back to general defaults + kernel.
13
+ * <kind>/PERSONA.md body + '\n\n' + kernel body
14
+ * If even the PERSONA.md is missing, fall back to general defaults + kernel.
15
15
  *
16
16
  * Frontmatter from whichever file is the primary source (orchestrator.md >
17
- * base.md) supplies model/skills/extensions/tools. Lifecycle and spine position
17
+ * PERSONA.md) supplies model/skills/extensions/tools. Lifecycle and spine position
18
18
  * are INPUTS (the caller decides them — root/child, terminal/resident), not
19
19
  * derived here; they select the lifecycle/spine protocol fragments spliced
20
20
  * ahead of the persona body.
21
21
  */
22
- import { loadPersona, loadKernel, loadRuntimeBase, loadSpineFragment, loadLifecycleFragment, subKindsFor } from './loader.js';
22
+ import { loadPersona, loadKernel, loadRuntimeBase, loadSpineFragment, loadLifecycleFragment, loadWaitingFragment, subPersonasFor } from './loader.js';
23
23
  // ---------------------------------------------------------------------------
24
24
  // Helpers
25
25
  // ---------------------------------------------------------------------------
26
26
  function toStringArray(v) {
27
27
  if (!Array.isArray(v))
28
28
  return [];
29
- return v.filter((x) => typeof x === 'string');
29
+ // yaml.parse coerces scalars to native types; the persona prompt needs strings,
30
+ // so coerce each scalar element (dropping nested objects/arrays/null) — this
31
+ // mirrors the prior hand-rolled parser, which returned every scalar as a string.
32
+ return v.filter((x) => x != null && typeof x !== 'object').map((x) => String(x));
30
33
  }
31
34
  function toOptionalString(v) {
32
- return typeof v === 'string' ? v : undefined;
35
+ if (typeof v === 'string')
36
+ return v;
37
+ // Preserve the legacy parser's string-typed contract for native scalars
38
+ // (e.g. `model: 4` must compose as "4", not be dropped as a non-string).
39
+ if (typeof v === 'number' || typeof v === 'boolean')
40
+ return String(v);
41
+ return undefined;
33
42
  }
34
43
  /** The bare-minimum system prompt used when no persona file is found at all. */
35
44
  function fallbackBasePrompt(kind) {
@@ -40,29 +49,32 @@ function fallbackBasePrompt(kind) {
40
49
  * fragment (report-up vs. silent, keyed on whether the node has a manager),
41
50
  * then the lifecycle fragment (finish-with-`push final` vs. dormant/wake). The
42
51
  * kind×mode persona body follows after a rule. Empty fragments drop out. */
43
- /** Render the "sub-kinds you may spawn" menu for a kind that owns a roster.
44
- * Returns '' when the kind owns none. Data-driven: one line per sub-kind, its
45
- * spawn string + its `summary`. Adding a roster file makes it appear here. */
46
- function renderSubKindMenu(kind) {
47
- const subs = subKindsFor(kind);
52
+ /** Render the "sub-personas you may spawn" menu for a kind that has any
53
+ * available to it. Returns '' when none are. Data-driven: one line per
54
+ * sub-persona, its spawn string + its `whenToUse`. A sub-persona surfaces here
55
+ * for a kind when its `availableTo` includes that kind (default: its own
56
+ * top-level ancestor) or is the wildcard. */
57
+ function renderSubPersonaMenu(kind) {
58
+ const subs = subPersonasFor(kind);
48
59
  if (subs.length === 0)
49
60
  return '';
50
- const lines = subs.map((s) => `- \`${s.kind}\` — ${s.summary}`);
61
+ const lines = subs.map((s) => `- \`${s.kind}\` — ${s.whenToUse}`);
51
62
  return [
52
- '## Reviewer sub-kinds you may spawn',
63
+ '## Sub-personas you may spawn',
53
64
  '',
54
- `These specialist reviewers exist only in the ${kind} kind's world — no other kind sees them. Spawn one with \`crtr node new --kind <sub-kind> "<scope>"\`, giving it only its scope, never your suspicions: a reviewer handed a hint anchors on it instead of finding problems independently.`,
65
+ `These specialist sub-personas are available to the ${kind} kind. Spawn one with \`crtr node new --kind <sub> "<scope>"\`, giving it only its scope, never your suspicions: a reviewer handed a hint anchors on it instead of finding problems independently.`,
55
66
  '',
56
67
  ...lines,
57
68
  ].join('\n');
58
69
  }
59
70
  function composeProtocol(personaPrompt, kind, lifecycle, hasManager) {
60
- const menu = renderSubKindMenu(kind);
71
+ const menu = renderSubPersonaMenu(kind);
61
72
  const body = menu ? `${personaPrompt}\n\n${menu}` : personaPrompt;
62
73
  const protocol = [
63
74
  loadRuntimeBase(),
64
75
  loadSpineFragment(hasManager),
65
76
  loadLifecycleFragment(lifecycle),
77
+ loadWaitingFragment(),
66
78
  ]
67
79
  .filter((s) => s.length > 0)
68
80
  .join('\n\n');
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Predicate matcher engine — vendored verbatim from pi's frontmatter-rules
3
+ * extension.
4
+ *
5
+ * Source: pi-personal-extensions/extensions/frontmatter-rules/index.ts
6
+ * (the "Condition language" block, functions `asArray` … `evalCondition`).
7
+ *
8
+ * The unified document substrate uses this to evaluate a document's `gate`
9
+ * predicate against a node-config subject (e.g. `orchestration.depth: { gte: 2 }`).
10
+ *
11
+ * This is intentionally a COPY, not an import: the source lives in a separate,
12
+ * private, unpublished repo that crtr cannot depend on. The functions are pure
13
+ * TS over `unknown` / `Record<string, unknown>` with zero external imports, so
14
+ * vendoring them keeps this module standalone and dependency-free. Keep it in
15
+ * sync with the source by hand if the matcher language ever changes.
16
+ *
17
+ * Condition language (evaluated against the subject object):
18
+ * A condition is a map of <field> -> <matcher>, AND-ed across fields.
19
+ * `field` may be dotted to reach nested values (e.g. `orchestration.depth`).
20
+ *
21
+ * Matcher forms:
22
+ * scalar (string/number/bool/null)
23
+ * field === value, OR (if the field is an array) the array includes value
24
+ * array [a, b, c]
25
+ * membership/intersection: field is one of these, OR the field array
26
+ * shares any element with these
27
+ * object { <op>: <arg>, ... } (multiple ops AND together)
28
+ * eq equals (scalar or array-includes)
29
+ * ne not eq
30
+ * in [..] — field (or any of its array elements) is in the list
31
+ * nin not in
32
+ * exists true|false — field is present / absent
33
+ * contains field array includes this value
34
+ * containsAll [..] — field array includes all of these
35
+ * containsAny [..] — field array includes any of these
36
+ * matches regex string, case-sensitive (tests String(field); arrays: any)
37
+ * imatches regex string, case-insensitive
38
+ * gt gte lt lte numeric comparison
39
+ *
40
+ * Combinators (reserved keys at any condition level):
41
+ * all: [ {..}, {..} ] every sub-condition true (AND)
42
+ * any: [ {..}, {..} ] at least one true (OR)
43
+ * not: { .. } sub-condition false
44
+ * Sibling field matchers next to combinators are AND-ed in.
45
+ *
46
+ * Two load-bearing edge cases preserved from the source:
47
+ * - An empty condition (`{}`) is INERT: it returns false, NOT match-all.
48
+ * - An unknown op never matches.
49
+ */
50
+ export declare function asArray(v: unknown): unknown[];
51
+ export declare function scalarEq(a: unknown, b: unknown): boolean;
52
+ export declare function getField(subject: unknown, key: string): unknown;
53
+ export declare function toRegExp(arg: unknown, flags: string): RegExp | null;
54
+ export declare function applyOp(op: string, value: unknown, arg: unknown): boolean;
55
+ export declare function matchField(value: unknown, matcher: unknown): boolean;
56
+ /**
57
+ * Evaluate a `gate` predicate against a subject object.
58
+ *
59
+ * This is the one function consumers call. Returns true iff `condition`
60
+ * matches `subject`. A null/non-object condition and an empty object both
61
+ * return false (inert), never match-all.
62
+ */
63
+ export declare function evalCondition(condition: unknown, subject: Record<string, unknown>): boolean;