@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.
- package/dist/build-root.js +3 -6
- package/dist/{builtin-personas/runtime-base.md → builtin-memory/00-runtime-base.md} +8 -1
- package/dist/{builtin-personas/spine/has-manager.md → builtin-memory/01-spine/00-has-manager.md} +8 -0
- package/dist/builtin-memory/01-spine/01-no-manager.md +10 -0
- package/dist/{builtin-personas/lifecycle/terminal.md → builtin-memory/02-lifecycle/00-terminal.md} +8 -0
- package/dist/{builtin-personas/lifecycle/resident.md → builtin-memory/02-lifecycle/01-resident.md} +8 -0
- package/dist/{builtin-personas/waiting.md → builtin-memory/03-waiting.md} +7 -0
- package/dist/{builtin-personas/orchestration-kernel.md → builtin-memory/04-orchestration-kernel.md} +8 -0
- package/dist/{builtin-personas/advisor/PERSONA.md → builtin-memory/05-kinds/advisor/00-base.md} +5 -2
- package/dist/{builtin-personas/design/PERSONA.md → builtin-memory/05-kinds/design/00-base.md} +5 -2
- package/dist/{builtin-personas/design/orchestrator.md → builtin-memory/05-kinds/design/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/developer/PERSONA.md → builtin-memory/05-kinds/developer/00-base.md} +5 -2
- package/dist/{builtin-personas/developer/orchestrator.md → builtin-memory/05-kinds/developer/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/explore/PERSONA.md → builtin-memory/05-kinds/explore/00-base.md} +5 -2
- package/dist/{builtin-personas/explore/orchestrator.md → builtin-memory/05-kinds/explore/01-orchestrator.md} +5 -3
- package/dist/{builtin-personas/general/PERSONA.md → builtin-memory/05-kinds/general/00-base.md} +5 -2
- package/dist/{builtin-personas/general/orchestrator.md → builtin-memory/05-kinds/general/01-orchestrator.md} +5 -3
- package/dist/{builtin-personas/plan/PERSONA.md → builtin-memory/05-kinds/plan/00-base.md} +5 -2
- package/dist/{builtin-personas/plan/orchestrator.md → builtin-memory/05-kinds/plan/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/plan/reviewers/architecture-fit/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/architecture-fit.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/code-smells/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/code-smells.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/security/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/security.md} +5 -2
- package/dist/{builtin-personas/product/PERSONA.md → builtin-memory/05-kinds/product/00-base.md} +5 -2
- package/dist/{builtin-personas/product/orchestrator.md → builtin-memory/05-kinds/product/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/product/teardown/PERSONA.md → builtin-memory/05-kinds/product/teardown.md} +5 -3
- package/dist/{builtin-personas/review/PERSONA.md → builtin-memory/05-kinds/review/00-base.md} +5 -2
- package/dist/{builtin-personas/review/orchestrator.md → builtin-memory/05-kinds/review/01-orchestrator.md} +5 -3
- package/dist/{builtin-personas/spec/PERSONA.md → builtin-memory/05-kinds/spec/00-base.md} +5 -2
- package/dist/{builtin-personas/spec/orchestrator.md → builtin-memory/05-kinds/spec/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/spec/requirements/PERSONA.md → builtin-memory/05-kinds/spec/requirements.md} +5 -2
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-pi-packages/pi-mode-switch/extensions/index.ts +4 -4
- package/dist/builtin-views/canvas/core.mjs +3 -2
- package/dist/builtin-views/prompt-review/core.mjs +6 -134
- package/dist/builtin-views/settings/core.mjs +26 -94
- package/dist/builtin-views/settings/text.mjs +0 -5
- package/dist/builtin-views/settings/tui.mjs +1 -7
- package/dist/builtin-views/settings/web.jsx +3 -49
- package/dist/clients/attach/__tests__/crtr-output-render.test.js +21 -205
- package/dist/clients/attach/attach-cmd.js +429 -432
- package/dist/clients/web/web-cmd.js +7 -7
- package/dist/commands/__tests__/human.test.js +1 -15
- package/dist/commands/attention.js +9 -6
- package/dist/commands/canvas-history/search.js +2 -2
- package/dist/commands/canvas-history/show.js +3 -3
- package/dist/commands/canvas-history.js +1 -1
- package/dist/commands/canvas-issue.js +1 -0
- package/dist/commands/canvas-snapshot.js +1 -0
- package/dist/commands/canvas.js +10 -11
- package/dist/commands/chord.js +1 -0
- package/dist/commands/human/prompts.js +0 -1
- package/dist/commands/human/queue.js +1 -0
- package/dist/commands/memory/read.js +3 -3
- package/dist/commands/memory/shared.d.ts +13 -8
- package/dist/commands/memory/shared.js +22 -8
- package/dist/commands/memory/write.js +5 -3
- package/dist/commands/node-snapshot.js +1 -0
- package/dist/commands/node.js +52 -22
- package/dist/commands/profile/add-project.d.ts +1 -0
- package/dist/commands/profile/add-project.js +42 -0
- package/dist/commands/profile/delete.d.ts +1 -0
- package/dist/commands/profile/delete.js +39 -0
- package/dist/commands/profile/list.d.ts +1 -0
- package/dist/commands/profile/list.js +35 -0
- package/dist/commands/profile/new.d.ts +1 -0
- package/dist/commands/profile/new.js +50 -0
- package/dist/commands/profile/remove-project.d.ts +1 -0
- package/dist/commands/profile/remove-project.js +42 -0
- package/dist/commands/profile/rename.d.ts +1 -0
- package/dist/commands/profile/rename.js +42 -0
- package/dist/commands/profile/show.d.ts +1 -0
- package/dist/commands/profile/show.js +50 -0
- package/dist/commands/profile.d.ts +2 -0
- package/dist/commands/profile.js +33 -0
- package/dist/commands/push.d.ts +0 -1
- package/dist/commands/push.js +2 -228
- package/dist/commands/revive.js +2 -2
- package/dist/commands/{canvas-tmux-spread.js → surface-tmux-spread.js} +9 -6
- package/dist/commands/surface.js +3 -2
- package/dist/commands/sys/config.js +6 -21
- package/dist/commands/sys/daemon.d.ts +2 -0
- package/dist/commands/{daemon.js → sys/daemon.js} +12 -12
- package/dist/commands/sys/doctor.js +1 -0
- package/dist/commands/sys/prompt-review.js +90 -17
- package/dist/commands/sys.js +4 -3
- package/dist/core/__tests__/daemon-boot.test.js +1 -1
- package/dist/core/__tests__/daemon-wedge.test.d.ts +1 -0
- package/dist/core/__tests__/daemon-wedge.test.js +87 -0
- package/dist/core/__tests__/hearth-bootstrap.test.js +2 -2
- package/dist/core/__tests__/history-inbox.test.d.ts +1 -0
- package/dist/core/__tests__/history-inbox.test.js +105 -0
- package/dist/core/__tests__/kickoff.test.js +3 -3
- package/dist/core/__tests__/revive.test.js +39 -1
- package/dist/core/canvas/canvas.js +10 -9
- package/dist/core/canvas/db.js +11 -0
- package/dist/core/canvas/history.d.ts +1 -1
- package/dist/core/canvas/history.js +91 -3
- package/dist/core/canvas/paths.d.ts +6 -0
- package/dist/core/canvas/paths.js +9 -0
- package/dist/core/canvas/status-glyph.js +1 -0
- package/dist/core/canvas/types.d.ts +10 -0
- package/dist/core/config.d.ts +44 -1
- package/dist/core/config.js +155 -12
- package/dist/core/fault-classifier.d.ts +2 -2
- package/dist/core/feed/inbox.d.ts +7 -12
- package/dist/core/feed/inbox.js +45 -29
- package/dist/core/hearth/providers/blaxel-bootstrap.js +1 -1
- package/dist/core/memory-resolver.d.ts +26 -15
- package/dist/core/memory-resolver.js +158 -40
- package/dist/core/preview-registry.js +14 -112
- package/dist/core/profiles/manifest.d.ts +33 -0
- package/dist/core/profiles/manifest.js +332 -0
- package/dist/core/profiles/select.d.ts +11 -0
- package/dist/core/profiles/select.js +115 -0
- package/dist/core/render.js +1 -1
- package/dist/core/runtime/bearings.d.ts +9 -0
- package/dist/core/runtime/bearings.js +31 -0
- package/dist/core/runtime/busy.d.ts +15 -0
- package/dist/core/runtime/busy.js +32 -1
- package/dist/core/runtime/close.d.ts +10 -0
- package/dist/core/runtime/close.js +18 -13
- package/dist/core/runtime/fault-recovery.js +4 -0
- package/dist/core/runtime/front-door.js +9 -2
- package/dist/core/runtime/kickoff.js +5 -7
- package/dist/core/runtime/launch.d.ts +25 -7
- package/dist/core/runtime/launch.js +42 -55
- package/dist/core/runtime/nodes.d.ts +7 -0
- package/dist/core/runtime/nodes.js +6 -0
- package/dist/core/runtime/persona.js +18 -32
- package/dist/core/runtime/placement.js +1 -1
- package/dist/core/runtime/revive.js +9 -1
- package/dist/core/runtime/spawn.d.ts +13 -0
- package/dist/core/runtime/spawn.js +25 -0
- package/dist/core/runtime/tmux.d.ts +2 -2
- package/dist/core/runtime/tmux.js +2 -2
- package/dist/core/scope.d.ts +14 -7
- package/dist/core/scope.js +76 -20
- package/dist/core/substrate/index.d.ts +3 -3
- package/dist/core/substrate/index.js +5 -3
- package/dist/core/substrate/on-read.js +3 -2
- package/dist/core/substrate/render.d.ts +16 -11
- package/dist/core/substrate/render.js +249 -62
- package/dist/core/substrate/schema.d.ts +11 -3
- package/dist/core/substrate/schema.js +25 -0
- package/dist/core/substrate/subject.d.ts +15 -2
- package/dist/core/substrate/subject.js +24 -2
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/crtrd.d.ts +24 -0
- package/dist/daemon/crtrd.js +213 -5
- package/dist/daemon/manage.js +2 -2
- package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
- package/dist/pi-extensions/canvas-nav.js +2 -2
- package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
- package/dist/pi-extensions/canvas-stophook.js +59 -2
- package/dist/types.d.ts +57 -1
- package/dist/types.js +83 -1
- package/dist/web-client/assets/{index-IAJVtuVe.js → index-CbO8L0mN.js} +4 -4
- package/dist/web-client/assets/index-DwO46Cs5.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/package.json +2 -2
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +0 -56
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +0 -57
- package/dist/builtin-memory/crouter-development/personas.md +0 -112
- package/dist/builtin-personas/spine/no-manager.md +0 -2
- package/dist/commands/daemon.d.ts +0 -2
- package/dist/core/personas/index.d.ts +0 -13
- package/dist/core/personas/index.js +0 -11
- package/dist/core/personas/loader.d.ts +0 -159
- package/dist/core/personas/loader.js +0 -327
- package/dist/core/personas/resolve.d.ts +0 -59
- package/dist/core/personas/resolve.js +0 -376
- package/dist/web-client/assets/index-DUThOUzU.css +0 -2
- /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;
|