@crouton-kit/crouter 0.3.36 → 0.3.38

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 (177) hide show
  1. package/dist/build-root.js +3 -6
  2. package/dist/{builtin-personas/runtime-base.md → builtin-memory/00-runtime-base.md} +8 -1
  3. package/dist/{builtin-personas/spine/has-manager.md → builtin-memory/01-spine/00-has-manager.md} +8 -0
  4. package/dist/builtin-memory/01-spine/01-no-manager.md +10 -0
  5. package/dist/{builtin-personas/lifecycle/terminal.md → builtin-memory/02-lifecycle/00-terminal.md} +8 -0
  6. package/dist/{builtin-personas/lifecycle/resident.md → builtin-memory/02-lifecycle/01-resident.md} +8 -0
  7. package/dist/{builtin-personas/waiting.md → builtin-memory/03-waiting.md} +7 -0
  8. package/dist/{builtin-personas/orchestration-kernel.md → builtin-memory/04-orchestration-kernel.md} +8 -0
  9. package/dist/{builtin-personas/advisor/PERSONA.md → builtin-memory/05-kinds/advisor/00-base.md} +5 -2
  10. package/dist/{builtin-personas/design/PERSONA.md → builtin-memory/05-kinds/design/00-base.md} +5 -2
  11. package/dist/{builtin-personas/design/orchestrator.md → builtin-memory/05-kinds/design/01-orchestrator.md} +5 -4
  12. package/dist/{builtin-personas/developer/PERSONA.md → builtin-memory/05-kinds/developer/00-base.md} +5 -2
  13. package/dist/{builtin-personas/developer/orchestrator.md → builtin-memory/05-kinds/developer/01-orchestrator.md} +5 -4
  14. package/dist/{builtin-personas/explore/PERSONA.md → builtin-memory/05-kinds/explore/00-base.md} +5 -2
  15. package/dist/{builtin-personas/explore/orchestrator.md → builtin-memory/05-kinds/explore/01-orchestrator.md} +5 -3
  16. package/dist/{builtin-personas/general/PERSONA.md → builtin-memory/05-kinds/general/00-base.md} +5 -2
  17. package/dist/{builtin-personas/general/orchestrator.md → builtin-memory/05-kinds/general/01-orchestrator.md} +5 -3
  18. package/dist/{builtin-personas/plan/PERSONA.md → builtin-memory/05-kinds/plan/00-base.md} +5 -2
  19. package/dist/{builtin-personas/plan/orchestrator.md → builtin-memory/05-kinds/plan/01-orchestrator.md} +5 -4
  20. package/dist/{builtin-personas/plan/reviewers/architecture-fit/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/architecture-fit.md} +5 -2
  21. package/dist/{builtin-personas/plan/reviewers/code-smells/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/code-smells.md} +5 -2
  22. package/dist/{builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md} +5 -2
  23. package/dist/{builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md} +5 -2
  24. package/dist/{builtin-personas/plan/reviewers/security/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/security.md} +5 -2
  25. package/dist/{builtin-personas/product/PERSONA.md → builtin-memory/05-kinds/product/00-base.md} +5 -2
  26. package/dist/{builtin-personas/product/orchestrator.md → builtin-memory/05-kinds/product/01-orchestrator.md} +5 -4
  27. package/dist/{builtin-personas/product/teardown/PERSONA.md → builtin-memory/05-kinds/product/teardown.md} +5 -3
  28. package/dist/{builtin-personas/review/PERSONA.md → builtin-memory/05-kinds/review/00-base.md} +5 -2
  29. package/dist/{builtin-personas/review/orchestrator.md → builtin-memory/05-kinds/review/01-orchestrator.md} +5 -3
  30. package/dist/{builtin-personas/spec/PERSONA.md → builtin-memory/05-kinds/spec/00-base.md} +5 -2
  31. package/dist/{builtin-personas/spec/orchestrator.md → builtin-memory/05-kinds/spec/01-orchestrator.md} +5 -4
  32. package/dist/{builtin-personas/spec/requirements/PERSONA.md → builtin-memory/05-kinds/spec/requirements.md} +5 -2
  33. package/dist/builtin-memory/internal/INDEX.md +1 -1
  34. package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
  35. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  36. package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +4 -4
  37. package/dist/builtin-views/canvas/core.mjs +3 -2
  38. package/dist/builtin-views/prompt-review/core.mjs +6 -134
  39. package/dist/builtin-views/settings/core.mjs +26 -94
  40. package/dist/builtin-views/settings/text.mjs +0 -5
  41. package/dist/builtin-views/settings/tui.mjs +1 -7
  42. package/dist/builtin-views/settings/web.jsx +3 -49
  43. package/dist/clients/attach/__tests__/crtr-output-render.test.js +21 -205
  44. package/dist/clients/attach/attach-cmd.js +429 -432
  45. package/dist/clients/web/web-cmd.js +7 -7
  46. package/dist/commands/__tests__/human.test.js +1 -15
  47. package/dist/commands/attention.js +9 -6
  48. package/dist/commands/canvas-history/search.js +2 -2
  49. package/dist/commands/canvas-history/show.js +3 -3
  50. package/dist/commands/canvas-history.js +1 -1
  51. package/dist/commands/canvas-issue.js +1 -0
  52. package/dist/commands/canvas-snapshot.js +1 -0
  53. package/dist/commands/canvas.js +10 -11
  54. package/dist/commands/chord.js +1 -0
  55. package/dist/commands/human/prompts.js +0 -1
  56. package/dist/commands/human/queue.js +1 -0
  57. package/dist/commands/memory/read.js +3 -3
  58. package/dist/commands/memory/shared.d.ts +13 -8
  59. package/dist/commands/memory/shared.js +22 -8
  60. package/dist/commands/memory/write.js +5 -3
  61. package/dist/commands/node-snapshot.js +1 -0
  62. package/dist/commands/node.js +52 -22
  63. package/dist/commands/profile/add-project.d.ts +1 -0
  64. package/dist/commands/profile/add-project.js +42 -0
  65. package/dist/commands/profile/delete.d.ts +1 -0
  66. package/dist/commands/profile/delete.js +39 -0
  67. package/dist/commands/profile/list.d.ts +1 -0
  68. package/dist/commands/profile/list.js +35 -0
  69. package/dist/commands/profile/new.d.ts +1 -0
  70. package/dist/commands/profile/new.js +50 -0
  71. package/dist/commands/profile/remove-project.d.ts +1 -0
  72. package/dist/commands/profile/remove-project.js +42 -0
  73. package/dist/commands/profile/rename.d.ts +1 -0
  74. package/dist/commands/profile/rename.js +42 -0
  75. package/dist/commands/profile/show.d.ts +1 -0
  76. package/dist/commands/profile/show.js +50 -0
  77. package/dist/commands/profile.d.ts +2 -0
  78. package/dist/commands/profile.js +33 -0
  79. package/dist/commands/push.d.ts +0 -1
  80. package/dist/commands/push.js +2 -228
  81. package/dist/commands/revive.js +2 -2
  82. package/dist/commands/{canvas-tmux-spread.js → surface-tmux-spread.js} +9 -6
  83. package/dist/commands/surface.js +3 -2
  84. package/dist/commands/sys/config.js +6 -21
  85. package/dist/commands/sys/daemon.d.ts +2 -0
  86. package/dist/commands/{daemon.js → sys/daemon.js} +12 -12
  87. package/dist/commands/sys/doctor.js +1 -0
  88. package/dist/commands/sys/prompt-review.js +90 -17
  89. package/dist/commands/sys.js +4 -3
  90. package/dist/core/__tests__/daemon-boot.test.js +1 -1
  91. package/dist/core/__tests__/daemon-wedge.test.d.ts +1 -0
  92. package/dist/core/__tests__/daemon-wedge.test.js +87 -0
  93. package/dist/core/__tests__/hearth-bootstrap.test.js +2 -2
  94. package/dist/core/__tests__/history-inbox.test.d.ts +1 -0
  95. package/dist/core/__tests__/history-inbox.test.js +105 -0
  96. package/dist/core/__tests__/kickoff.test.js +3 -3
  97. package/dist/core/__tests__/revive.test.js +39 -1
  98. package/dist/core/canvas/canvas.js +10 -9
  99. package/dist/core/canvas/db.js +11 -0
  100. package/dist/core/canvas/history.d.ts +1 -1
  101. package/dist/core/canvas/history.js +91 -3
  102. package/dist/core/canvas/paths.d.ts +6 -0
  103. package/dist/core/canvas/paths.js +9 -0
  104. package/dist/core/canvas/status-glyph.js +1 -0
  105. package/dist/core/canvas/types.d.ts +10 -0
  106. package/dist/core/config.d.ts +44 -1
  107. package/dist/core/config.js +155 -12
  108. package/dist/core/fault-classifier.d.ts +2 -2
  109. package/dist/core/feed/inbox.d.ts +7 -12
  110. package/dist/core/feed/inbox.js +45 -29
  111. package/dist/core/hearth/providers/blaxel-bootstrap.js +1 -1
  112. package/dist/core/memory-resolver.d.ts +26 -15
  113. package/dist/core/memory-resolver.js +158 -40
  114. package/dist/core/preview-registry.js +14 -112
  115. package/dist/core/profiles/manifest.d.ts +33 -0
  116. package/dist/core/profiles/manifest.js +332 -0
  117. package/dist/core/profiles/select.d.ts +11 -0
  118. package/dist/core/profiles/select.js +115 -0
  119. package/dist/core/render.js +1 -1
  120. package/dist/core/runtime/bearings.d.ts +9 -0
  121. package/dist/core/runtime/bearings.js +31 -0
  122. package/dist/core/runtime/busy.d.ts +15 -0
  123. package/dist/core/runtime/busy.js +32 -1
  124. package/dist/core/runtime/close.d.ts +10 -0
  125. package/dist/core/runtime/close.js +18 -13
  126. package/dist/core/runtime/fault-recovery.js +4 -0
  127. package/dist/core/runtime/front-door.js +9 -2
  128. package/dist/core/runtime/kickoff.js +5 -7
  129. package/dist/core/runtime/launch.d.ts +25 -7
  130. package/dist/core/runtime/launch.js +42 -55
  131. package/dist/core/runtime/nodes.d.ts +7 -0
  132. package/dist/core/runtime/nodes.js +6 -0
  133. package/dist/core/runtime/persona.js +18 -32
  134. package/dist/core/runtime/placement.js +1 -1
  135. package/dist/core/runtime/revive.js +9 -1
  136. package/dist/core/runtime/spawn.d.ts +13 -0
  137. package/dist/core/runtime/spawn.js +25 -0
  138. package/dist/core/runtime/tmux.d.ts +2 -2
  139. package/dist/core/runtime/tmux.js +2 -2
  140. package/dist/core/scope.d.ts +14 -7
  141. package/dist/core/scope.js +76 -20
  142. package/dist/core/substrate/index.d.ts +3 -3
  143. package/dist/core/substrate/index.js +5 -3
  144. package/dist/core/substrate/on-read.js +3 -2
  145. package/dist/core/substrate/render.d.ts +16 -11
  146. package/dist/core/substrate/render.js +249 -62
  147. package/dist/core/substrate/schema.d.ts +11 -3
  148. package/dist/core/substrate/schema.js +25 -0
  149. package/dist/core/substrate/subject.d.ts +15 -2
  150. package/dist/core/substrate/subject.js +24 -2
  151. package/dist/daemon/crtrd-cli.js +1 -1
  152. package/dist/daemon/crtrd.d.ts +24 -0
  153. package/dist/daemon/crtrd.js +213 -5
  154. package/dist/daemon/manage.js +2 -2
  155. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  156. package/dist/pi-extensions/canvas-nav.js +2 -2
  157. package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
  158. package/dist/pi-extensions/canvas-stophook.js +59 -2
  159. package/dist/types.d.ts +57 -1
  160. package/dist/types.js +83 -1
  161. package/dist/web-client/assets/{index-IAJVtuVe.js → index-CbO8L0mN.js} +4 -4
  162. package/dist/web-client/assets/index-DwO46Cs5.css +2 -0
  163. package/dist/web-client/index.html +2 -2
  164. package/package.json +2 -2
  165. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +0 -56
  166. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +0 -57
  167. package/dist/builtin-memory/crouter-development/personas.md +0 -112
  168. package/dist/builtin-personas/spine/no-manager.md +0 -2
  169. package/dist/commands/daemon.d.ts +0 -2
  170. package/dist/core/personas/index.d.ts +0 -13
  171. package/dist/core/personas/index.js +0 -11
  172. package/dist/core/personas/loader.d.ts +0 -159
  173. package/dist/core/personas/loader.js +0 -327
  174. package/dist/core/personas/resolve.d.ts +0 -59
  175. package/dist/core/personas/resolve.js +0 -376
  176. package/dist/web-client/assets/index-DUThOUzU.css +0 -2
  177. /package/dist/commands/{canvas-tmux-spread.d.ts → surface-tmux-spread.d.ts} +0 -0
@@ -1,159 +0,0 @@
1
- /**
2
- * Persona file loader.
3
- *
4
- * Discovers and parses persona markdown files with YAML frontmatter.
5
- * Resolution order (highest → lowest precedence): project > user > builtin.
6
- *
7
- * Layout on disk:
8
- * <root>/personas/<kind>/PERSONA.md
9
- * <root>/personas/<kind>/orchestrator.md
10
- * <root>/personas/orchestration-kernel.md
11
- * <root>/personas/<kind>/<...>/PERSONA.md (nested sub-personas)
12
- *
13
- * The builtin root is src/builtin-personas (or dist/builtin-personas in the
14
- * compiled build), resolved relative to this module — mirrors the pattern used
15
- * for STOPHOOK_PATH in spawn.ts.
16
- */
17
- import type { Scope } from '../../types.js';
18
- /**
19
- * Find the first existing file across the scope roots.
20
- * `relativePath` is relative to each root (e.g. 'general/PERSONA.md').
21
- */
22
- export interface ResolvedPersonaSource {
23
- path: string;
24
- scope: Scope;
25
- }
26
- export declare function loadScopedText(relativePath: string): {
27
- text: string;
28
- sourcePath: string;
29
- scope: Scope;
30
- } | null;
31
- export interface LoadedPersona {
32
- /** Raw, uncoerced frontmatter key/value record (null when no frontmatter). */
33
- frontmatter: Record<string, unknown> | null;
34
- /** Body text with any @include directives inlined. */
35
- body: string;
36
- }
37
- export interface LoadedPersonaSource extends LoadedPersona {
38
- /** Raw body text before @include expansion. */
39
- rawBody: string;
40
- /** Absolute source path. */
41
- sourcePath: string;
42
- /** Scope the file was resolved from. */
43
- scope: Scope;
44
- /** Relative source path within the scope root, e.g. `personas/developer/PERSONA.md`. */
45
- source: string;
46
- }
47
- /**
48
- * Load and parse a persona file for the given `kind` and `mode`.
49
- *
50
- * Returns `null` when no file is found in any scope (project/user/builtin).
51
- * On success, `@include` directives in the body are resolved and inlined.
52
- */
53
- export declare function loadPersona(kind: string, mode: 'base' | 'orchestrator'): LoadedPersona | null;
54
- /** Load a persona file together with its provenance. */
55
- export declare function loadPersonaSource(kind: string, mode: 'base' | 'orchestrator'): LoadedPersonaSource | null;
56
- /**
57
- * Load the raw text of the orchestration kernel (no frontmatter, body only).
58
- * Returns an empty string if the kernel file cannot be found.
59
- */
60
- export declare function loadKernel(): string;
61
- export declare function loadKernelSource(): {
62
- text: string;
63
- sourcePath: string;
64
- scope: Scope;
65
- } | null;
66
- /**
67
- * Load the base runtime prompt — the node operating protocol prepended to
68
- * EVERY persona (delegate/ask/promote). Returns '' if not found. The
69
- * lifecycle/spine-specific sections (finish vs. dormant, report-up vs. silent)
70
- * live in their own fragments, loaded below.
71
- */
72
- export declare function loadRuntimeBase(): string;
73
- export declare function loadRuntimeBaseSource(): {
74
- text: string;
75
- sourcePath: string;
76
- scope: Scope;
77
- } | null;
78
- /**
79
- * Load the waiting fragment — the cross-kind "waiting is a way to end a turn"
80
- * operating posture (arm a wake + go dormant instead of busy-looping or
81
- * finishing-to-stop). Spliced into every node's baked prompt immediately after
82
- * the lifecycle fragment (resolve.ts). Returns '' if the fragment is missing.
83
- */
84
- export declare function loadWaitingFragment(): string;
85
- export declare function loadWaitingFragmentSource(): {
86
- text: string;
87
- sourcePath: string;
88
- scope: Scope;
89
- } | null;
90
- /**
91
- * Load the lifecycle fragment — the "how you end" contract, keyed on the node's
92
- * lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
93
- * (dormant/wake, never forced to submit). Single source for both the baked-in
94
- * system prompt (resolve) and the transition guidance (runtime/persona.ts).
95
- * Returns '' if the fragment file cannot be found.
96
- */
97
- export declare function loadLifecycleFragment(lifecycle: 'terminal' | 'resident'): string;
98
- export declare function loadLifecycleFragmentSource(lifecycle: 'terminal' | 'resident'): {
99
- text: string;
100
- sourcePath: string;
101
- scope: Scope;
102
- } | null;
103
- /**
104
- * Load the spine fragment — the "who you report to" contract, keyed on whether
105
- * the node has a manager (anyone it reports up to). `has-manager` teaches the
106
- * `push update`/`push urgent`/escalate verbs; `no-manager` (a top-of-spine root)
107
- * omits the push family entirely — it answers to the human directly.
108
- * Returns '' if the fragment file cannot be found.
109
- */
110
- export declare function loadSpineFragment(hasManager: boolean): string;
111
- export declare function loadSpineFragmentSource(hasManager: boolean): {
112
- text: string;
113
- sourcePath: string;
114
- scope: Scope;
115
- } | null;
116
- /**
117
- * Enumerate the kinds with at least one persona file (PERSONA.md or
118
- * orchestrator.md) across all scope roots (project/user/builtin). Used to
119
- * validate a requested `--kind` and to list the valid choices. Only the
120
- * IMMEDIATE children of each root count — nested sub-personas never pollute
121
- * the global kind list (see subPersonasFor).
122
- */
123
- export declare function availableKinds(): string[];
124
- /**
125
- * The one-line "when to use this node type" gloss for `kind`, read from its
126
- * `<kind>/PERSONA.md` `whenToUse` frontmatter (resolved project > user >
127
- * builtin). Returns '' when the kind has no PERSONA.md or no `whenToUse`.
128
- * Drives the dynamic kind list in `node new -h` / `node promote -h`.
129
- */
130
- export declare function kindWhenToUse(kind: string): string;
131
- export interface SubPersona {
132
- /** Full kind string to spawn, e.g. 'plan/reviewers/security'. */
133
- kind: string;
134
- /** Leaf name, e.g. 'security'. */
135
- name: string;
136
- /** One-line "when to use", from the sub-persona PERSONA.md `whenToUse` frontmatter (or ''). */
137
- whenToUse: string;
138
- }
139
- /**
140
- * Enumerate the sub-personas AVAILABLE TO `kind` — the nested specialist
141
- * personas (e.g. `plan/reviewers/security`) a `kind` node may spawn, surfaced
142
- * in its composed prompt (resolve.ts) and nowhere else.
143
- *
144
- * A sub-persona is any descendant dir (ANY depth) under a top-level kind dir
145
- * that holds a PERSONA.md, EXCLUDING the top-level PERSONA.md itself. Its
146
- * availability is its `availableTo` frontmatter: an explicit list of kind
147
- * strings, or the wildcard `"*"`/`"all"` (visible to every kind); absent, it
148
- * defaults to its own top-level ancestor kind. So the five `plan/reviewers/*`
149
- * (no `availableTo`) are visible only to `plan`, while a sub-persona under
150
- * `developer/` can declare `availableTo: [plan]` to surface in plan's menu —
151
- * which is why ALL top-level kinds' descendants are scanned, not just `<kind>/`.
152
- *
153
- * Sub-personas are intentionally NOT global kinds: `availableKinds()` scans only
154
- * the immediate children of each root, so a nested sub-persona never leaks into
155
- * the global list; it is reachable only by its full kind string. Precedence is
156
- * project > user > builtin keyed on the FULL kind string — the highest root that
157
- * defines a given kind string wins (and owns its `availableTo`).
158
- */
159
- export declare function subPersonasFor(kind: string): SubPersona[];
@@ -1,327 +0,0 @@
1
- /**
2
- * Persona file loader.
3
- *
4
- * Discovers and parses persona markdown files with YAML frontmatter.
5
- * Resolution order (highest → lowest precedence): project > user > builtin.
6
- *
7
- * Layout on disk:
8
- * <root>/personas/<kind>/PERSONA.md
9
- * <root>/personas/<kind>/orchestrator.md
10
- * <root>/personas/orchestration-kernel.md
11
- * <root>/personas/<kind>/<...>/PERSONA.md (nested sub-personas)
12
- *
13
- * The builtin root is src/builtin-personas (or dist/builtin-personas in the
14
- * compiled build), resolved relative to this module — mirrors the pattern used
15
- * for STOPHOOK_PATH in spawn.ts.
16
- */
17
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
18
- import { dirname, join } from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
- import { parseFrontmatterGeneric } from '../frontmatter.js';
21
- import { userScopeRoot, findProjectScopeRoot } from '../scope.js';
22
- // ---------------------------------------------------------------------------
23
- // Builtin root resolution
24
- // ---------------------------------------------------------------------------
25
- /**
26
- * Resolve the builtin-personas directory from the location of this compiled
27
- * or source module. Works from both `dist/core/personas/loader.js` and
28
- * `src/core/personas/loader.ts` (tsx dev runs).
29
- */
30
- function builtinPersonasRoot() {
31
- const here = dirname(fileURLToPath(import.meta.url)); // dist/core/personas or src/core/personas
32
- const coreDir = dirname(here); // dist/core or src/core
33
- const pkgDir = dirname(coreDir); // dist/ or src/
34
- const distPath = join(pkgDir, 'builtin-personas');
35
- const srcPath = join(dirname(pkgDir), 'src', 'builtin-personas');
36
- // Prefer the path that exists; fall back to the dist-sibling (which may not
37
- // exist yet if the package is being run pre-build, but that is the caller's
38
- // problem).
39
- if (existsSync(distPath))
40
- return distPath;
41
- if (existsSync(srcPath))
42
- return srcPath;
43
- return distPath;
44
- }
45
- function personaSearchRoots() {
46
- const roots = [];
47
- const projectRoot = findProjectScopeRoot();
48
- if (projectRoot)
49
- roots.push({ scope: 'project', root: join(projectRoot, 'personas') });
50
- roots.push({ scope: 'user', root: join(userScopeRoot(), 'personas') });
51
- roots.push({ scope: 'builtin', root: builtinPersonasRoot() });
52
- return roots;
53
- }
54
- // ---------------------------------------------------------------------------
55
- // Frontmatter scalar coercion
56
- // ---------------------------------------------------------------------------
57
- /** Coerce a frontmatter scalar to its string form, matching the legacy
58
- * hand-rolled parser (which stringified every scalar). Strings pass through;
59
- * number/boolean coerce via String(); null/undefined and non-scalars
60
- * (objects/arrays) are dropped (→ null). The `yaml` package returns native
61
- * scalar types, so without this a `typeof === 'string'` guard would silently
62
- * DROP a numeric/boolean frontmatter value (e.g. blanking a menu line) where
63
- * the old parser kept its stringified form. */
64
- function scalarToString(v) {
65
- if (typeof v === 'string')
66
- return v;
67
- if (typeof v === 'number' || typeof v === 'boolean')
68
- return String(v);
69
- return null;
70
- }
71
- function resolveFileMeta(relativePath) {
72
- for (const { scope, root } of personaSearchRoots()) {
73
- const candidate = join(root, relativePath);
74
- if (existsSync(candidate))
75
- return { path: candidate, scope };
76
- }
77
- return null;
78
- }
79
- function resolveFile(relativePath) {
80
- return resolveFileMeta(relativePath)?.path ?? null;
81
- }
82
- // ---------------------------------------------------------------------------
83
- // @include inlining
84
- // ---------------------------------------------------------------------------
85
- const INCLUDE_RE = /^@include\s+(\S+)\s*$/m;
86
- /**
87
- * Inline any `@include <filename>` directive found in `body`.
88
- * The included file is looked up via the same scope-resolution chain; if it
89
- * cannot be found, the directive line is replaced with an empty string rather
90
- * than throwing — callers that need the kernel should use `loadKernel()`
91
- * directly and can assert it themselves.
92
- */
93
- function inlineIncludes(body) {
94
- return body.replace(INCLUDE_RE, (_match, filename) => {
95
- const path = resolveFile(filename);
96
- if (!path)
97
- return '';
98
- const src = readFileSync(path, 'utf8');
99
- // The kernel file has no frontmatter, but run through the parser just in
100
- // case someone added one in a user/project override.
101
- const { body: kernelBody } = parseFrontmatterGeneric(src);
102
- return kernelBody.trim();
103
- });
104
- }
105
- export function loadScopedText(relativePath) {
106
- const meta = resolveFileMeta(relativePath);
107
- if (!meta)
108
- return null;
109
- const src = readFileSync(meta.path, 'utf8');
110
- const { body } = parseFrontmatterGeneric(src);
111
- return { text: body.trim(), sourcePath: meta.path, scope: meta.scope };
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';
118
- /**
119
- * Load and parse a persona file for the given `kind` and `mode`.
120
- *
121
- * Returns `null` when no file is found in any scope (project/user/builtin).
122
- * On success, `@include` directives in the body are resolved and inlined.
123
- */
124
- export function loadPersona(kind, mode) {
125
- const source = loadPersonaSource(kind, mode);
126
- if (!source)
127
- return null;
128
- return { frontmatter: source.frontmatter, body: source.body };
129
- }
130
- /** Load a persona file together with its provenance. */
131
- export function loadPersonaSource(kind, mode) {
132
- const relativePath = mode === 'orchestrator' ? `${kind}/orchestrator.md` : `${kind}/PERSONA.md`;
133
- const file = resolveFileMeta(relativePath);
134
- if (!file)
135
- return null;
136
- const src = readFileSync(file.path, 'utf8');
137
- const { data, body } = parseFrontmatterGeneric(src);
138
- return {
139
- frontmatter: data,
140
- rawBody: body.trim(),
141
- body: inlineIncludes(body).trim(),
142
- sourcePath: file.path,
143
- scope: file.scope,
144
- source: `personas/${relativePath}`,
145
- };
146
- }
147
- /**
148
- * Load the raw text of the orchestration kernel (no frontmatter, body only).
149
- * Returns an empty string if the kernel file cannot be found.
150
- */
151
- export function loadKernel() {
152
- const file = loadKernelSource();
153
- return file?.text ?? '';
154
- }
155
- export function loadKernelSource() {
156
- return loadScopedText('orchestration-kernel.md');
157
- }
158
- /**
159
- * Load the base runtime prompt — the node operating protocol prepended to
160
- * EVERY persona (delegate/ask/promote). Returns '' if not found. The
161
- * lifecycle/spine-specific sections (finish vs. dormant, report-up vs. silent)
162
- * live in their own fragments, loaded below.
163
- */
164
- export function loadRuntimeBase() {
165
- return loadRuntimeBaseSource()?.text ?? '';
166
- }
167
- export function loadRuntimeBaseSource() {
168
- return loadScopedText('runtime-base.md');
169
- }
170
- /**
171
- * Load the waiting fragment — the cross-kind "waiting is a way to end a turn"
172
- * operating posture (arm a wake + go dormant instead of busy-looping or
173
- * finishing-to-stop). Spliced into every node's baked prompt immediately after
174
- * the lifecycle fragment (resolve.ts). Returns '' if the fragment is missing.
175
- */
176
- export function loadWaitingFragment() {
177
- return loadWaitingFragmentSource()?.text ?? '';
178
- }
179
- export function loadWaitingFragmentSource() {
180
- return loadScopedText('waiting.md');
181
- }
182
- /**
183
- * Load the lifecycle fragment — the "how you end" contract, keyed on the node's
184
- * lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
185
- * (dormant/wake, never forced to submit). Single source for both the baked-in
186
- * system prompt (resolve) and the transition guidance (runtime/persona.ts).
187
- * Returns '' if the fragment file cannot be found.
188
- */
189
- export function loadLifecycleFragment(lifecycle) {
190
- return loadLifecycleFragmentSource(lifecycle)?.text ?? '';
191
- }
192
- export function loadLifecycleFragmentSource(lifecycle) {
193
- return loadScopedText(`lifecycle/${lifecycle}.md`);
194
- }
195
- /**
196
- * Load the spine fragment — the "who you report to" contract, keyed on whether
197
- * the node has a manager (anyone it reports up to). `has-manager` teaches the
198
- * `push update`/`push urgent`/escalate verbs; `no-manager` (a top-of-spine root)
199
- * omits the push family entirely — it answers to the human directly.
200
- * Returns '' if the fragment file cannot be found.
201
- */
202
- export function loadSpineFragment(hasManager) {
203
- return loadSpineFragmentSource(hasManager)?.text ?? '';
204
- }
205
- export function loadSpineFragmentSource(hasManager) {
206
- return loadScopedText(`spine/${hasManager ? 'has-manager' : 'no-manager'}.md`);
207
- }
208
- /**
209
- * Enumerate the kinds with at least one persona file (PERSONA.md or
210
- * orchestrator.md) across all scope roots (project/user/builtin). Used to
211
- * validate a requested `--kind` and to list the valid choices. Only the
212
- * IMMEDIATE children of each root count — nested sub-personas never pollute
213
- * the global kind list (see subPersonasFor).
214
- */
215
- export function availableKinds() {
216
- const kinds = new Set();
217
- for (const { root } of personaSearchRoots()) {
218
- if (!existsSync(root))
219
- continue;
220
- for (const entry of readdirSync(root, { withFileTypes: true })) {
221
- if (!entry.isDirectory())
222
- continue;
223
- const dir = join(root, entry.name);
224
- if (existsSync(join(dir, PERSONA_FILE)) || existsSync(join(dir, 'orchestrator.md'))) {
225
- kinds.add(entry.name);
226
- }
227
- }
228
- }
229
- return [...kinds].sort();
230
- }
231
- /**
232
- * The one-line "when to use this node type" gloss for `kind`, read from its
233
- * `<kind>/PERSONA.md` `whenToUse` frontmatter (resolved project > user >
234
- * builtin). Returns '' when the kind has no PERSONA.md or no `whenToUse`.
235
- * Drives the dynamic kind list in `node new -h` / `node promote -h`.
236
- */
237
- export function kindWhenToUse(kind) {
238
- const filePath = resolveFile(`${kind}/${PERSONA_FILE}`);
239
- if (!filePath)
240
- return '';
241
- const { data } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
242
- return scalarToString(data?.['whenToUse']) ?? '';
243
- }
244
- /** Recursively yield every dir under `dir` (inclusive) that holds a PERSONA.md,
245
- * with `relKind` = the dir's path relative to the scope root (slash-joined).
246
- * Dirs WITHOUT a PERSONA.md (e.g. a `reviewers/` grouping namespace) are
247
- * transparent — they yield nothing themselves but are still descended into,
248
- * so `plan/reviewers/security` keeps that exact kind string. */
249
- function* walkPersonaDirs(dir, relParts) {
250
- const file = join(dir, PERSONA_FILE);
251
- if (existsSync(file))
252
- yield { relKind: relParts.join('/'), file };
253
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
254
- if (entry.isDirectory())
255
- yield* walkPersonaDirs(join(dir, entry.name), [...relParts, entry.name]);
256
- }
257
- }
258
- /** Parse a sub-persona's `availableTo` frontmatter into its availability set.
259
- * Returns the wildcard sentinel `'*'` for `"*"`/`"all"` (scalar or in an
260
- * array), an explicit list of kind strings when present, else the default
261
- * `[topKind]` (the top-level ancestor kind). */
262
- function parseAvailableTo(data, topKind) {
263
- const isWild = (s) => {
264
- const t = s.trim().toLowerCase();
265
- return t === '*' || t === 'all';
266
- };
267
- const v = data ? data['availableTo'] : undefined;
268
- if (v === undefined)
269
- return [topKind];
270
- const scalar = scalarToString(v);
271
- if (scalar !== null)
272
- return isWild(scalar) ? '*' : [scalar];
273
- if (Array.isArray(v)) {
274
- const arr = v.map(scalarToString).filter((x) => x !== null);
275
- if (arr.some(isWild))
276
- return '*';
277
- return arr.length > 0 ? arr : [topKind];
278
- }
279
- return [topKind];
280
- }
281
- /**
282
- * Enumerate the sub-personas AVAILABLE TO `kind` — the nested specialist
283
- * personas (e.g. `plan/reviewers/security`) a `kind` node may spawn, surfaced
284
- * in its composed prompt (resolve.ts) and nowhere else.
285
- *
286
- * A sub-persona is any descendant dir (ANY depth) under a top-level kind dir
287
- * that holds a PERSONA.md, EXCLUDING the top-level PERSONA.md itself. Its
288
- * availability is its `availableTo` frontmatter: an explicit list of kind
289
- * strings, or the wildcard `"*"`/`"all"` (visible to every kind); absent, it
290
- * defaults to its own top-level ancestor kind. So the five `plan/reviewers/*`
291
- * (no `availableTo`) are visible only to `plan`, while a sub-persona under
292
- * `developer/` can declare `availableTo: [plan]` to surface in plan's menu —
293
- * which is why ALL top-level kinds' descendants are scanned, not just `<kind>/`.
294
- *
295
- * Sub-personas are intentionally NOT global kinds: `availableKinds()` scans only
296
- * the immediate children of each root, so a nested sub-persona never leaks into
297
- * the global list; it is reachable only by its full kind string. Precedence is
298
- * project > user > builtin keyed on the FULL kind string — the highest root that
299
- * defines a given kind string wins (and owns its `availableTo`).
300
- */
301
- export function subPersonasFor(kind) {
302
- const seen = new Set(); // full kind strings already resolved (higher root won)
303
- const out = [];
304
- for (const { root } of personaSearchRoots()) {
305
- if (!existsSync(root))
306
- continue;
307
- for (const top of readdirSync(root, { withFileTypes: true })) {
308
- if (!top.isDirectory())
309
- continue;
310
- const topKind = top.name;
311
- for (const { relKind, file } of walkPersonaDirs(join(root, topKind), [topKind])) {
312
- if (relKind === topKind)
313
- continue; // the top-level PERSONA.md is the kind itself, not a sub-persona
314
- if (seen.has(relKind))
315
- continue; // a higher root already resolved this kind string
316
- seen.add(relKind);
317
- const { data } = parseFrontmatterGeneric(readFileSync(file, 'utf8'));
318
- const availableTo = parseAvailableTo(data, topKind);
319
- if (availableTo !== '*' && !availableTo.includes(kind))
320
- continue;
321
- const whenToUse = scalarToString(data?.['whenToUse']) ?? '';
322
- out.push({ kind: relKind, name: relKind.split('/').pop(), whenToUse });
323
- }
324
- }
325
- }
326
- return out.sort((a, b) => a.kind.localeCompare(b.kind));
327
- }
@@ -1,59 +0,0 @@
1
- /**
2
- * Persona resolver — composes base + orchestrator personas into a
3
- * ResolvedPersona ready for use when spawning a canvas node.
4
- */
5
- export interface ResolvedPersona {
6
- systemPrompt: string;
7
- extensions: string[];
8
- model?: string;
9
- lifecycle: 'terminal' | 'resident';
10
- tools?: string[];
11
- }
12
- export type PromptLayerGroup = 'protocol' | 'persona' | 'generated' | 'substrate';
13
- export type PromptLayerScope = 'project' | 'user' | 'builtin' | 'generated' | 'runtime';
14
- export interface PromptLayer {
15
- id: string;
16
- label: string;
17
- group: PromptLayerGroup;
18
- source: string;
19
- sourcePath: string | null;
20
- scope: PromptLayerScope;
21
- included: boolean;
22
- condition: string | null;
23
- text: string;
24
- tokens: number;
25
- chars: number;
26
- }
27
- export interface PromptSource {
28
- id: string;
29
- label: string;
30
- path: string;
31
- scope: Exclude<PromptLayerScope, 'generated' | 'runtime'>;
32
- }
33
- export interface PromptReviewConfig {
34
- kind: string;
35
- mode: 'base' | 'orchestrator';
36
- lifecycle: 'terminal' | 'resident';
37
- hasManager: boolean;
38
- node: string | null;
39
- }
40
- export interface PromptReviewData {
41
- config: PromptReviewConfig;
42
- layers: PromptLayer[];
43
- assembled: string;
44
- total: {
45
- tokens: number;
46
- chars: number;
47
- };
48
- sources: PromptSource[];
49
- }
50
- export interface ResolveOpts {
51
- lifecycle: 'terminal' | 'resident';
52
- hasManager: boolean;
53
- }
54
- /**
55
- * Resolve a fully composed persona for the given `kind` and `mode`.
56
- */
57
- export declare function resolve(kind: string, mode: 'base' | 'orchestrator', opts: ResolveOpts): ResolvedPersona;
58
- export declare function resolveLayers(kind: string, mode: 'base' | 'orchestrator', opts: ResolveOpts): PromptLayer[];
59
- export declare function resolvePromptReview(kind: string, mode: 'base' | 'orchestrator', opts: ResolveOpts, nodeId?: string): PromptReviewData;