@crouton-kit/crouter 0.3.18 → 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 (257) 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/orchestrator.md +1 -1
  12. package/dist/builtin-personas/developer/orchestrator.md +1 -1
  13. package/dist/builtin-personas/lifecycle/resident.md +1 -1
  14. package/dist/builtin-personas/lifecycle/terminal.md +5 -2
  15. package/dist/builtin-personas/orchestration-kernel.md +15 -24
  16. package/dist/builtin-personas/plan/orchestrator.md +1 -1
  17. package/dist/builtin-personas/runtime-base.md +6 -3
  18. package/dist/builtin-personas/spec/orchestrator.md +1 -1
  19. package/dist/builtin-personas/waiting.md +8 -0
  20. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
  21. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
  22. package/dist/builtin-views/_lib/states.mjs +161 -0
  23. package/dist/builtin-views/canvas/client.mjs +303 -0
  24. package/dist/builtin-views/canvas/view.mjs +576 -0
  25. package/dist/builtin-views/git-pr/client.mjs +440 -0
  26. package/dist/builtin-views/git-pr/view.mjs +675 -0
  27. package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
  28. package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
  29. package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
  30. package/dist/builtin-views/inbox/view.mjs +889 -0
  31. package/dist/builtin-views/linkedin/client.mjs +610 -0
  32. package/dist/builtin-views/linkedin/view.mjs +1171 -0
  33. package/dist/clients/attach/attach-cmd.d.ts +2 -0
  34. package/dist/clients/attach/attach-cmd.js +354 -0
  35. package/dist/clients/attach/chat-view.d.ts +77 -0
  36. package/dist/clients/attach/chat-view.js +450 -0
  37. package/dist/clients/attach/clipboard-image.d.ts +16 -0
  38. package/dist/clients/attach/clipboard-image.js +113 -0
  39. package/dist/clients/attach/config-load.d.ts +31 -0
  40. package/dist/clients/attach/config-load.js +113 -0
  41. package/dist/clients/attach/extension-dialogs.d.ts +29 -0
  42. package/dist/clients/attach/extension-dialogs.js +101 -0
  43. package/dist/clients/attach/input-controller.d.ts +54 -0
  44. package/dist/clients/attach/input-controller.js +204 -0
  45. package/dist/clients/attach/slash-commands.d.ts +36 -0
  46. package/dist/clients/attach/slash-commands.js +200 -0
  47. package/dist/clients/attach/view-socket.d.ts +48 -0
  48. package/dist/clients/attach/view-socket.js +126 -0
  49. package/dist/commands/attention.js +3 -3
  50. package/dist/commands/canvas-prune.js +1 -1
  51. package/dist/commands/daemon.js +3 -2
  52. package/dist/commands/human/prompts.js +1 -1
  53. package/dist/commands/human/queue.js +43 -8
  54. package/dist/commands/human/shared.d.ts +2 -0
  55. package/dist/commands/memory/find.d.ts +1 -0
  56. package/dist/commands/memory/find.js +180 -0
  57. package/dist/commands/memory/lint.d.ts +1 -0
  58. package/dist/commands/memory/lint.js +140 -0
  59. package/dist/commands/memory/list.d.ts +1 -0
  60. package/dist/commands/memory/list.js +79 -0
  61. package/dist/commands/memory/read.js +103 -0
  62. package/dist/commands/memory/shared.d.ts +30 -0
  63. package/dist/commands/memory/shared.js +122 -0
  64. package/dist/commands/memory/write.d.ts +1 -0
  65. package/dist/commands/memory/write.js +85 -0
  66. package/dist/commands/memory.d.ts +2 -0
  67. package/dist/commands/memory.js +27 -0
  68. package/dist/commands/node.d.ts +3 -2
  69. package/dist/commands/node.js +607 -61
  70. package/dist/commands/pkg/market-manage.js +1 -1
  71. package/dist/commands/push.js +6 -6
  72. package/dist/commands/revive.js +1 -1
  73. package/dist/commands/skill/author.js +1 -1
  74. package/dist/commands/skill/shared.d.ts +1 -8
  75. package/dist/commands/skill/shared.js +2 -55
  76. package/dist/commands/skill.js +9 -14
  77. package/dist/commands/sys/doctor.js +1 -1
  78. package/dist/commands/sys/update.js +1 -1
  79. package/dist/commands/view-cycle.d.ts +2 -0
  80. package/dist/commands/view-cycle.js +125 -0
  81. package/dist/commands/view-list.d.ts +2 -0
  82. package/dist/commands/view-list.js +66 -0
  83. package/dist/commands/view-new.d.ts +2 -0
  84. package/dist/commands/view-new.js +70 -0
  85. package/dist/commands/view-pick.d.ts +2 -0
  86. package/dist/commands/view-pick.js +119 -0
  87. package/dist/commands/view-run.d.ts +2 -0
  88. package/dist/commands/view-run.js +191 -0
  89. package/dist/commands/view.d.ts +2 -0
  90. package/dist/commands/view.js +29 -0
  91. package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
  92. package/dist/core/__tests__/broker-lifecycle.test.js +677 -0
  93. package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
  94. package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
  95. package/dist/core/__tests__/cascade-close.test.js +12 -2
  96. package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
  97. package/dist/core/__tests__/child-death-wake.test.js +245 -0
  98. package/dist/core/__tests__/context-intro.test.js +76 -62
  99. package/dist/core/__tests__/daemon-boot.test.js +14 -5
  100. package/dist/core/__tests__/daemon-liveness.test.js +34 -9
  101. package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
  102. package/dist/core/__tests__/detach-focus.test.js +206 -0
  103. package/dist/core/__tests__/draw-style.test.d.ts +1 -0
  104. package/dist/core/__tests__/draw-style.test.js +258 -0
  105. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
  106. package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
  107. package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
  108. package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
  109. package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
  110. package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
  111. package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
  112. package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
  113. package/dist/core/__tests__/helpers/harness.d.ts +9 -0
  114. package/dist/core/__tests__/helpers/harness.js +111 -1
  115. package/dist/core/__tests__/home-session.test.js +41 -8
  116. package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
  117. package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
  118. package/dist/core/__tests__/kickoff.test.js +37 -3
  119. package/dist/core/__tests__/live-mutation.test.js +50 -33
  120. package/dist/core/__tests__/memory.test.js +23 -115
  121. package/dist/core/__tests__/placement-focus.test.js +5 -0
  122. package/dist/core/__tests__/placement-teardown.test.js +54 -11
  123. package/dist/core/__tests__/relaunch.test.js +4 -3
  124. package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
  125. package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
  126. package/dist/core/__tests__/spawn-root.test.js +10 -0
  127. package/dist/core/__tests__/spike-harness.test.js +1 -0
  128. package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
  129. package/dist/core/__tests__/wake-bearings.test.js +156 -0
  130. package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
  131. package/dist/core/__tests__/wake-origin.test.js +110 -0
  132. package/dist/core/bootstrap.js +1 -1
  133. package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
  134. package/dist/core/canvas/browse/app.js +24 -2
  135. package/dist/core/canvas/browse/model.d.ts +38 -2
  136. package/dist/core/canvas/browse/model.js +134 -10
  137. package/dist/core/canvas/browse/render.d.ts +6 -12
  138. package/dist/core/canvas/browse/render.js +72 -104
  139. package/dist/core/canvas/canvas.js +15 -22
  140. package/dist/core/canvas/db.js +46 -0
  141. package/dist/core/canvas/index.d.ts +1 -0
  142. package/dist/core/canvas/index.js +1 -0
  143. package/dist/core/canvas/pid.d.ts +4 -0
  144. package/dist/core/canvas/pid.js +23 -0
  145. package/dist/core/canvas/render.d.ts +11 -2
  146. package/dist/core/canvas/render.js +69 -0
  147. package/dist/core/canvas/types.d.ts +85 -3
  148. package/dist/core/canvas/wakeups.d.ts +76 -0
  149. package/dist/core/canvas/wakeups.js +185 -0
  150. package/dist/core/config.js +4 -1
  151. package/dist/core/frontmatter.js +37 -124
  152. package/dist/core/memory-resolver.d.ts +49 -0
  153. package/dist/core/memory-resolver.js +141 -0
  154. package/dist/core/personas/loader.d.ts +7 -0
  155. package/dist/core/personas/loader.js +36 -5
  156. package/dist/core/personas/resolve.js +13 -3
  157. package/dist/core/predicate.d.ts +63 -0
  158. package/dist/core/predicate.js +189 -0
  159. package/dist/core/resolver.js +26 -5
  160. package/dist/core/runtime/bearings.d.ts +53 -12
  161. package/dist/core/runtime/bearings.js +132 -59
  162. package/dist/core/runtime/broker-cli.d.ts +1 -0
  163. package/dist/core/runtime/broker-cli.js +46 -0
  164. package/dist/core/runtime/broker-protocol.d.ts +332 -0
  165. package/dist/core/runtime/broker-protocol.js +153 -0
  166. package/dist/core/runtime/broker-sdk.d.ts +48 -0
  167. package/dist/core/runtime/broker-sdk.js +72 -0
  168. package/dist/core/runtime/broker.d.ts +55 -0
  169. package/dist/core/runtime/broker.js +1128 -0
  170. package/dist/core/runtime/close.js +35 -6
  171. package/dist/core/runtime/host.d.ts +53 -0
  172. package/dist/core/runtime/host.js +186 -0
  173. package/dist/core/runtime/kickoff.d.ts +2 -1
  174. package/dist/core/runtime/kickoff.js +91 -5
  175. package/dist/core/runtime/launch.d.ts +45 -2
  176. package/dist/core/runtime/launch.js +65 -2
  177. package/dist/core/runtime/lifecycle.js +23 -6
  178. package/dist/core/runtime/memory.d.ts +2 -42
  179. package/dist/core/runtime/memory.js +11 -162
  180. package/dist/core/runtime/nodes.d.ts +33 -0
  181. package/dist/core/runtime/nodes.js +59 -1
  182. package/dist/core/runtime/persona.js +21 -11
  183. package/dist/core/runtime/pi-vendored.d.ts +18 -0
  184. package/dist/core/runtime/pi-vendored.js +49 -0
  185. package/dist/core/runtime/placement.d.ts +32 -14
  186. package/dist/core/runtime/placement.js +206 -52
  187. package/dist/core/runtime/promote.d.ts +0 -6
  188. package/dist/core/runtime/promote.js +1 -12
  189. package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
  190. package/dist/core/runtime/{demote.js → recycle.js} +27 -11
  191. package/dist/core/runtime/reset.js +8 -6
  192. package/dist/core/runtime/revive.d.ts +2 -0
  193. package/dist/core/runtime/revive.js +34 -34
  194. package/dist/core/runtime/spawn.d.ts +19 -0
  195. package/dist/core/runtime/spawn.js +75 -22
  196. package/dist/core/runtime/stop-guard.d.ts +1 -1
  197. package/dist/core/runtime/stop-guard.js +6 -1
  198. package/dist/core/runtime/tmux-chrome.d.ts +1 -1
  199. package/dist/core/runtime/tmux-chrome.js +1 -1
  200. package/dist/core/runtime/tmux.d.ts +28 -0
  201. package/dist/core/runtime/tmux.js +80 -6
  202. package/dist/core/scope.d.ts +11 -0
  203. package/dist/core/scope.js +39 -0
  204. package/dist/core/spawn.d.ts +5 -0
  205. package/dist/core/spawn.js +20 -2
  206. package/dist/core/substrate/gate.d.ts +13 -0
  207. package/dist/core/substrate/gate.js +21 -0
  208. package/dist/core/substrate/index.d.ts +7 -0
  209. package/dist/core/substrate/index.js +18 -0
  210. package/dist/core/substrate/on-read.d.ts +14 -0
  211. package/dist/core/substrate/on-read.js +292 -0
  212. package/dist/core/substrate/render.d.ts +25 -0
  213. package/dist/core/substrate/render.js +256 -0
  214. package/dist/core/substrate/schema.d.ts +76 -0
  215. package/dist/core/substrate/schema.js +124 -0
  216. package/dist/core/substrate/session-cache.d.ts +30 -0
  217. package/dist/core/substrate/session-cache.js +77 -0
  218. package/dist/core/substrate/subject.d.ts +41 -0
  219. package/dist/core/substrate/subject.js +54 -0
  220. package/dist/core/tui/contract.d.ts +83 -0
  221. package/dist/core/tui/contract.js +8 -0
  222. package/dist/core/tui/draw.d.ts +96 -0
  223. package/dist/core/tui/draw.js +339 -0
  224. package/dist/core/tui/host.d.ts +29 -0
  225. package/dist/core/tui/host.js +379 -0
  226. package/dist/core/tui/loader.d.ts +16 -0
  227. package/dist/core/tui/loader.js +94 -0
  228. package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
  229. package/dist/core/wake.d.ts +86 -0
  230. package/dist/core/wake.js +308 -0
  231. package/dist/daemon/crtrd.d.ts +29 -4
  232. package/dist/daemon/crtrd.js +662 -46
  233. package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
  234. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
  235. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
  236. package/dist/pi-extensions/canvas-commands.d.ts +3 -0
  237. package/dist/pi-extensions/canvas-commands.js +10 -0
  238. package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
  239. package/dist/pi-extensions/canvas-context-intro.js +55 -15
  240. package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
  241. package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
  242. package/dist/pi-extensions/canvas-nav.d.ts +3 -0
  243. package/dist/pi-extensions/canvas-nav.js +87 -23
  244. package/dist/pi-extensions/canvas-stophook.js +17 -8
  245. package/dist/pi-extensions/canvas-view.d.ts +21 -0
  246. package/dist/pi-extensions/canvas-view.js +75 -0
  247. package/dist/prompts/skill.js +19 -26
  248. package/dist/prompts/view.d.ts +7 -0
  249. package/dist/prompts/view.js +101 -0
  250. package/dist/types.d.ts +4 -0
  251. package/dist/types.js +1 -0
  252. package/package.json +9 -4
  253. package/dist/commands/skill/find.d.ts +0 -4
  254. package/dist/commands/skill/find.js +0 -257
  255. package/dist/commands/skill/read.js +0 -91
  256. /package/dist/commands/{skill → memory}/read.d.ts +0 -0
  257. /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
@@ -1,3 +1,4 @@
1
+ import { parse as parseYaml } from 'yaml';
1
2
  import { isSkillType } from '../types.js';
2
3
  const FRONTMATTER_RE = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?/;
3
4
  export function parseFrontmatter(source) {
@@ -7,7 +8,7 @@ export function parseFrontmatter(source) {
7
8
  }
8
9
  const raw = match[1];
9
10
  const body = source.slice(match[0].length);
10
- return { data: toSkillFrontmatter(parseYamlRecord(raw)), body, raw };
11
+ return { data: toSkillFrontmatter(parseYamlBlock(raw)), body, raw };
11
12
  }
12
13
  /** Like parseFrontmatter but returns the raw key/value record instead of
13
14
  * coercing to SkillFrontmatter. Used by consumers (e.g. subagents) that read
@@ -19,138 +20,50 @@ export function parseFrontmatterGeneric(source) {
19
20
  }
20
21
  const raw = match[1];
21
22
  const body = source.slice(match[0].length);
22
- return { data: parseYamlRecord(raw), body, raw };
23
+ return { data: parseYamlBlock(raw), body, raw };
23
24
  }
24
25
  function toSkillFrontmatter(out) {
25
26
  const fm = {
26
- name: typeof out.name === 'string' ? out.name : '',
27
- description: typeof out.description === 'string' ? out.description : undefined,
28
- keywords: Array.isArray(out.keywords) ? out.keywords : undefined,
27
+ name: scalarToString(out.name) ?? '',
28
+ description: scalarToString(out.description),
29
+ keywords: Array.isArray(out.keywords) ? out.keywords.map((k) => String(k)) : undefined,
29
30
  type: isSkillType(out.type) ? out.type : undefined,
30
31
  };
31
32
  return fm;
32
33
  }
33
- function parseYamlRecord(yaml) {
34
- const lines = yaml.split(/\r?\n/);
35
- const out = {};
36
- let i = 0;
37
- while (i < lines.length) {
38
- const raw = lines[i];
39
- if (!raw.trim()) {
40
- i++;
41
- continue;
42
- }
43
- const idx = raw.indexOf(':');
44
- if (idx === -1) {
45
- i++;
46
- continue;
47
- }
48
- const key = raw.slice(0, idx).trim();
49
- const rest = raw.slice(idx + 1).trim();
50
- // Block scalar: `key: |` or `key: >` with optional chomp indicator (-/+)
51
- const blockMatch = rest.match(/^([|>])([-+]?)\s*$/);
52
- if (blockMatch) {
53
- const style = blockMatch[1];
54
- const chomp = blockMatch[2];
55
- const collected = [];
56
- let blockIndent = null;
57
- let j = i + 1;
58
- while (j < lines.length) {
59
- const r = lines[j];
60
- if (r.trim() === '') {
61
- collected.push('');
62
- j++;
63
- continue;
64
- }
65
- const ind = r.match(/^(\s*)/)?.[1].length ?? 0;
66
- if (blockIndent === null) {
67
- if (ind === 0)
68
- break;
69
- blockIndent = ind;
70
- }
71
- if (ind < blockIndent)
72
- break;
73
- collected.push(r.slice(blockIndent));
74
- j++;
75
- }
76
- while (collected.length > 0 && collected[collected.length - 1] === '')
77
- collected.pop();
78
- let value;
79
- if (style === '|') {
80
- value = collected.join('\n');
81
- }
82
- else {
83
- const parts = [];
84
- let para = [];
85
- for (const ln of collected) {
86
- if (ln === '') {
87
- if (para.length > 0) {
88
- parts.push(para.join(' '));
89
- para = [];
90
- }
91
- parts.push('');
92
- }
93
- else {
94
- para.push(ln);
95
- }
96
- }
97
- if (para.length > 0)
98
- parts.push(para.join(' '));
99
- const folded = [];
100
- for (let k = 0; k < parts.length; k++) {
101
- if (parts[k] === '' && (k === 0 || parts[k - 1] === ''))
102
- continue;
103
- folded.push(parts[k]);
104
- }
105
- value = folded.join('\n').replace(/\n+$/, '');
106
- }
107
- if (chomp !== '+')
108
- value = value.replace(/\n+$/, '');
109
- out[key] = value;
110
- i = j;
111
- continue;
112
- }
113
- // Empty value: could be a list on subsequent lines
114
- if (rest === '') {
115
- const buf = [];
116
- let j = i + 1;
117
- while (j < lines.length) {
118
- const r = lines[j];
119
- if (r.trim() === '') {
120
- j++;
121
- continue;
122
- }
123
- if (/^\s*-\s+/.test(r)) {
124
- buf.push(stripQuotes(r.replace(/^\s*-\s+/, '').trim()));
125
- j++;
126
- continue;
127
- }
128
- break;
129
- }
130
- if (buf.length > 0)
131
- out[key] = buf;
132
- i = j;
133
- continue;
134
- }
135
- if (rest.startsWith('[') && rest.endsWith(']')) {
136
- out[key] = rest
137
- .slice(1, -1)
138
- .split(',')
139
- .map((s) => stripQuotes(s.trim()))
140
- .filter(Boolean);
141
- i++;
142
- continue;
143
- }
144
- out[key] = stripQuotes(rest);
145
- i++;
146
- }
147
- return out;
34
+ /** Coerce a YAML scalar (string/number/boolean) to its string form. A skill's
35
+ * `name`/`description` are always strings to consumers; native non-string
36
+ * scalars (a numeric/boolean value) render as their string form, and
37
+ * non-scalars (null/undefined/array/object) yield undefined. */
38
+ function scalarToString(v) {
39
+ if (typeof v === 'string')
40
+ return v;
41
+ if (typeof v === 'number' || typeof v === 'boolean')
42
+ return String(v);
43
+ return undefined;
44
+ }
45
+ /** Parse the inner YAML of a frontmatter block into a key/value record.
46
+ *
47
+ * ONE strict parser, no fallback. The frontmatter contract IS "valid YAML":
48
+ * the single `yaml`-package path handles the substrate's nested `gate:` maps,
49
+ * inline `{…}` objects, and native scalar typing — and a doc whose frontmatter
50
+ * is NOT valid YAML THROWS, by design. There is deliberately no second lenient
51
+ * parser; a dual parsing strategy is the hedge we reject. Callers that iterate
52
+ * MANY docs isolate a per-doc throw at the COLLECTION layer (a clear scoped
53
+ * notice naming the bad file, then continue); single-doc callers let the error
54
+ * surface so the user learns their one requested doc is malformed.
55
+ *
56
+ * A VALID non-object document (a bare scalar, a top-level list, or an empty
57
+ * block) normalizes to an empty record, preserving the contract that callers
58
+ * always receive a Record for well-formed-but-non-mapping frontmatter. */
59
+ function parseYamlBlock(raw) {
60
+ return normalizeToRecord(parseYaml(raw));
148
61
  }
149
- function stripQuotes(s) {
150
- if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
151
- return s.slice(1, -1);
62
+ function normalizeToRecord(parsed) {
63
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
64
+ return parsed;
152
65
  }
153
- return s;
66
+ return {};
154
67
  }
155
68
  export function serializeFrontmatter(data) {
156
69
  const lines = ['---'];
@@ -0,0 +1,49 @@
1
+ import type { Scope } from '../types.js';
2
+ /**
3
+ * Thin memory-document resolver for the document substrate. Mirrors the SHAPE
4
+ * of the skill resolver (qualifier parse → scope precedence → direct path
5
+ * lookup → leaf-name fallback) but drops ALL plugin machinery: memory
6
+ * resolution is scope + leaf/path ONLY. The three memory scopes resolve in
7
+ * precedence order project > user > builtin (the same precedence concept as
8
+ * orderPluginsByResolution, minus plugins). It returns the raw parsed
9
+ * frontmatter + body; it does NOT interpret the schema, kind, gate, or rungs —
10
+ * that is the schema/gate layer's job (callers filter by `frontmatter.kind`).
11
+ */
12
+ export interface MemoryDoc {
13
+ /** Path-derived identity: the doc's path under the scope's memory/ root, no
14
+ * extension, slash-separated — e.g. memory/taste/foo.md → "taste/foo". */
15
+ name: string;
16
+ scope: Scope;
17
+ /** Absolute path to the resolved .md file. */
18
+ path: string;
19
+ /** Raw, uncoerced frontmatter record (null when the doc has no frontmatter). */
20
+ frontmatter: Record<string, unknown> | null;
21
+ /** Document body, with the frontmatter block stripped. */
22
+ body: string;
23
+ }
24
+ export interface MemoryResolutionOpts {
25
+ /** Restrict resolution to a single scope. Conflicts with a scope prefix on
26
+ * the identifier (e.g. `user/foo` with scope=project) throw. */
27
+ scope?: Scope;
28
+ }
29
+ /** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
30
+ export declare function memoryDocId(doc: MemoryDoc): string;
31
+ /** All memory docs in one scope's memory/ dir, scanned recursively for *.md
32
+ * (topical subdirs supported), sorted by path-derived name. */
33
+ export declare function listMemoryDocs(scope: Scope): MemoryDoc[];
34
+ /** All memory docs across the resolved scopes, in precedence order
35
+ * (project, then user, then builtin), each scope's docs name-sorted within. */
36
+ export declare function listAllMemoryDocs(scope?: Scope): MemoryDoc[];
37
+ /**
38
+ * Resolve a path-derived name to a single memory document.
39
+ *
40
+ * Accepted identifier forms (mirroring parseSkillQualifier, no plugins):
41
+ * <name> — bare name; resolved by scope precedence project>user>builtin
42
+ * <scope>/<name> — pinned to one scope (user|project)
43
+ * `<name>` may carry topical subdirs (`taste/foo`); a bare leaf (`foo`) falls
44
+ * back to a last-segment match across the resolved scopes.
45
+ *
46
+ * Resolution order: parse qualifier → direct memory/<name>.md lookup
47
+ * (precedence-first) → leaf-name fallback (ambiguity error listing candidates).
48
+ */
49
+ export declare function resolveMemoryDoc(rawName: string, opts?: MemoryResolutionOpts): MemoryDoc;
@@ -0,0 +1,141 @@
1
+ import { join, relative, sep } from 'node:path';
2
+ import { pathExists, readText, walkFiles } from './fs-utils.js';
3
+ import { parseFrontmatterGeneric } from './frontmatter.js';
4
+ import { parseSkillQualifier } from './resolver.js';
5
+ import { ambiguous, notFound, usage } from './errors.js';
6
+ import { warn } from './output.js';
7
+ import { projectScopeRoot, scopeMemoryDir } from './scope.js';
8
+ /** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
9
+ export function memoryDocId(doc) {
10
+ return `${doc.scope}/${doc.name}`;
11
+ }
12
+ /** The memory scopes in resolution precedence: project > user > builtin.
13
+ * Project is included only when a project scope exists for the cwd. A single
14
+ * `scope` narrows to just that scope. */
15
+ function scopesInPrecedence(scope) {
16
+ if (scope)
17
+ return [scope];
18
+ const out = [];
19
+ if (projectScopeRoot())
20
+ out.push('project');
21
+ out.push('user');
22
+ out.push('builtin');
23
+ return out;
24
+ }
25
+ function loadMemoryDoc(name, scope, path) {
26
+ const { data, body } = parseFrontmatterGeneric(readText(path));
27
+ return { name, scope, path, frontmatter: data, body };
28
+ }
29
+ /** All memory docs in one scope's memory/ dir, scanned recursively for *.md
30
+ * (topical subdirs supported), sorted by path-derived name. */
31
+ export function listMemoryDocs(scope) {
32
+ const dir = scopeMemoryDir(scope);
33
+ if (!dir || !pathExists(dir))
34
+ return [];
35
+ const docs = [];
36
+ for (const file of walkFiles(dir, (n) => n.endsWith('.md'))) {
37
+ const name = relative(dir, file).replace(/\.md$/i, '').split(sep).join('/');
38
+ if (!name)
39
+ continue;
40
+ // COLLECTION layer: the strict frontmatter parser throws on invalid YAML.
41
+ // Isolate one malformed doc with a clear scoped notice + skip, so a single
42
+ // bad file can't brick `memory list`/`find` or the substrate boot render.
43
+ try {
44
+ docs.push(loadMemoryDoc(name, scope, file));
45
+ }
46
+ catch (e) {
47
+ const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
48
+ warn(`invalid frontmatter in ${file}: ${msg}`);
49
+ }
50
+ }
51
+ return docs.sort((a, b) => a.name.localeCompare(b.name));
52
+ }
53
+ /** All memory docs across the resolved scopes, in precedence order
54
+ * (project, then user, then builtin), each scope's docs name-sorted within. */
55
+ export function listAllMemoryDocs(scope) {
56
+ return scopesInPrecedence(scope).flatMap(listMemoryDocs);
57
+ }
58
+ /** Direct full-path lookup of memory/<name>.md across scopes, precedence-first.
59
+ * Returns every scope's hit in precedence order; the resolver takes the first
60
+ * (highest-precedence) one — a fully-specified name is never ambiguous. */
61
+ function findMemoryMatches(name, scope) {
62
+ const matches = [];
63
+ for (const s of scopesInPrecedence(scope)) {
64
+ const dir = scopeMemoryDir(s);
65
+ if (!dir)
66
+ continue;
67
+ const path = join(dir, ...name.split('/')) + '.md';
68
+ if (!pathExists(path))
69
+ continue;
70
+ matches.push(loadMemoryDoc(name, s, path));
71
+ }
72
+ return matches;
73
+ }
74
+ /** Leaf-name fallback: match docs whose final path segment equals `leaf`.
75
+ * Only meaningful for a bare segment (a slashed query can never equal a single
76
+ * segment), mirroring findSkillsByLeaf. Returns matches precedence-ordered. */
77
+ function findMemoryByLeaf(leaf, scope) {
78
+ if (leaf.includes('/'))
79
+ return [];
80
+ let all;
81
+ try {
82
+ all = listAllMemoryDocs(scope);
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ return all.filter((d) => (d.name.split('/').pop() ?? d.name) === leaf);
88
+ }
89
+ function formatLeafAmbiguous(leaf, matches) {
90
+ const ids = matches.map(memoryDocId).join(', ');
91
+ return `ambiguous memory document: ${leaf} matches multiple documents: ${ids}`;
92
+ }
93
+ /**
94
+ * Resolve a path-derived name to a single memory document.
95
+ *
96
+ * Accepted identifier forms (mirroring parseSkillQualifier, no plugins):
97
+ * <name> — bare name; resolved by scope precedence project>user>builtin
98
+ * <scope>/<name> — pinned to one scope (user|project)
99
+ * `<name>` may carry topical subdirs (`taste/foo`); a bare leaf (`foo`) falls
100
+ * back to a last-segment match across the resolved scopes.
101
+ *
102
+ * Resolution order: parse qualifier → direct memory/<name>.md lookup
103
+ * (precedence-first) → leaf-name fallback (ambiguity error listing candidates).
104
+ */
105
+ export function resolveMemoryDoc(rawName, opts = {}) {
106
+ const parsed = parseSkillQualifier(rawName);
107
+ if (parsed.scope && opts.scope && parsed.scope !== opts.scope) {
108
+ throw usage(`scope conflict: identifier "${rawName}" uses scope "${parsed.scope}" but --scope is "${opts.scope}"`);
109
+ }
110
+ const effectiveScope = opts.scope ?? parsed.scope;
111
+ const name = parsed.segments.join('/');
112
+ if (name === '')
113
+ throw usage(`memory document name required`);
114
+ // Direct full-path lookup: a fully-specified name resolves by scope precedence.
115
+ const direct = findMemoryMatches(name, effectiveScope);
116
+ if (direct.length > 0)
117
+ return direct[0];
118
+ // Leaf-name fallback: the caller gave only the final segment (e.g. "foo" for
119
+ // "taste/foo"). Match by last segment across the resolved scope dirs.
120
+ const byLeaf = findMemoryByLeaf(name, effectiveScope);
121
+ if (byLeaf.length > 0) {
122
+ // Same path-derived name across scopes → precedence wins (return first);
123
+ // genuinely different docs sharing a leaf → ambiguous.
124
+ const distinctNames = new Set(byLeaf.map((d) => d.name));
125
+ if (distinctNames.size === 1)
126
+ return byLeaf[0];
127
+ throw ambiguous(formatLeafAmbiguous(name, byLeaf), {
128
+ memory: name,
129
+ candidates: byLeaf.map((d) => ({
130
+ id: memoryDocId(d),
131
+ scope: d.scope,
132
+ path: d.path,
133
+ })),
134
+ next: 'Multiple documents share this leaf name. Re-run with one of the full names in candidates.',
135
+ });
136
+ }
137
+ throw notFound(`memory document not found: ${rawName}`, {
138
+ memory: name,
139
+ scope: parsed.scope,
140
+ });
141
+ }
@@ -39,6 +39,13 @@ export declare function loadKernel(): string;
39
39
  * live in their own fragments, loaded below.
40
40
  */
41
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;
42
49
  /**
43
50
  * Load the lifecycle fragment — the "how you end" contract, keyed on the node's
44
51
  * lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
@@ -56,6 +56,23 @@ function personaSearchRoots() {
56
56
  return roots;
57
57
  }
58
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
+ // ---------------------------------------------------------------------------
59
76
  // File resolution helpers
60
77
  // ---------------------------------------------------------------------------
61
78
  /**
@@ -143,6 +160,19 @@ export function loadRuntimeBase() {
143
160
  const { body } = parseFrontmatterGeneric(src);
144
161
  return body.trim();
145
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
+ }
146
176
  /**
147
177
  * Load the lifecycle fragment — the "how you end" contract, keyed on the node's
148
178
  * lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
@@ -205,7 +235,7 @@ export function kindWhenToUse(kind) {
205
235
  if (!filePath)
206
236
  return '';
207
237
  const { data } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
208
- return data && typeof data['whenToUse'] === 'string' ? data['whenToUse'] : '';
238
+ return scalarToString(data?.['whenToUse']) ?? '';
209
239
  }
210
240
  /** Recursively yield every dir under `dir` (inclusive) that holds a PERSONA.md,
211
241
  * with `relKind` = the dir's path relative to the scope root (slash-joined).
@@ -233,10 +263,11 @@ function parseAvailableTo(data, topKind) {
233
263
  const v = data ? data['availableTo'] : undefined;
234
264
  if (v === undefined)
235
265
  return [topKind];
236
- if (typeof v === 'string')
237
- return isWild(v) ? '*' : [v];
266
+ const scalar = scalarToString(v);
267
+ if (scalar !== null)
268
+ return isWild(scalar) ? '*' : [scalar];
238
269
  if (Array.isArray(v)) {
239
- const arr = v.filter((x) => typeof x === 'string');
270
+ const arr = v.map(scalarToString).filter((x) => x !== null);
240
271
  if (arr.some(isWild))
241
272
  return '*';
242
273
  return arr.length > 0 ? arr : [topKind];
@@ -283,7 +314,7 @@ export function subPersonasFor(kind) {
283
314
  const availableTo = parseAvailableTo(data, topKind);
284
315
  if (availableTo !== '*' && !availableTo.includes(kind))
285
316
  continue;
286
- const whenToUse = data && typeof data['whenToUse'] === 'string' ? data['whenToUse'] : '';
317
+ const whenToUse = scalarToString(data?.['whenToUse']) ?? '';
287
318
  out.push({ kind: relKind, name: relKind.split('/').pop(), whenToUse });
288
319
  }
289
320
  }
@@ -19,17 +19,26 @@
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, subPersonasFor } 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) {
@@ -65,6 +74,7 @@ function composeProtocol(personaPrompt, kind, lifecycle, hasManager) {
65
74
  loadRuntimeBase(),
66
75
  loadSpineFragment(hasManager),
67
76
  loadLifecycleFragment(lifecycle),
77
+ loadWaitingFragment(),
68
78
  ]
69
79
  .filter((s) => s.length > 0)
70
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;