@crouton-kit/crouter 0.3.37 → 0.3.39

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 (143) hide show
  1. package/dist/build-root.js +1 -0
  2. package/dist/{builtin-personas/runtime-base.md → builtin-memory/00-runtime-base.md} +7 -0
  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-views/prompt-review/core.mjs +6 -134
  36. package/dist/builtin-views/settings/core.mjs +26 -94
  37. package/dist/builtin-views/settings/text.mjs +0 -5
  38. package/dist/builtin-views/settings/tui.mjs +1 -7
  39. package/dist/builtin-views/settings/web.jsx +3 -49
  40. package/dist/clients/attach/attach-cmd.js +416 -416
  41. package/dist/commands/memory/read.js +3 -3
  42. package/dist/commands/memory/shared.d.ts +13 -8
  43. package/dist/commands/memory/shared.js +22 -8
  44. package/dist/commands/memory/write.js +5 -3
  45. package/dist/commands/node.js +40 -11
  46. package/dist/commands/profile/add-project.d.ts +1 -0
  47. package/dist/commands/profile/add-project.js +42 -0
  48. package/dist/commands/profile/delete.d.ts +1 -0
  49. package/dist/commands/profile/delete.js +39 -0
  50. package/dist/commands/profile/list.d.ts +1 -0
  51. package/dist/commands/profile/list.js +35 -0
  52. package/dist/commands/profile/new.d.ts +1 -0
  53. package/dist/commands/profile/new.js +50 -0
  54. package/dist/commands/profile/remove-project.d.ts +1 -0
  55. package/dist/commands/profile/remove-project.js +42 -0
  56. package/dist/commands/profile/rename.d.ts +1 -0
  57. package/dist/commands/profile/rename.js +42 -0
  58. package/dist/commands/profile/show.d.ts +1 -0
  59. package/dist/commands/profile/show.js +50 -0
  60. package/dist/commands/profile.d.ts +2 -0
  61. package/dist/commands/profile.js +33 -0
  62. package/dist/commands/sys/config.js +6 -21
  63. package/dist/commands/sys/prompt-review.js +90 -17
  64. package/dist/core/canvas/canvas.js +10 -9
  65. package/dist/core/canvas/db.js +11 -0
  66. package/dist/core/canvas/history.js +1 -0
  67. package/dist/core/canvas/types.d.ts +10 -0
  68. package/dist/core/config.d.ts +44 -1
  69. package/dist/core/config.js +155 -12
  70. package/dist/core/hearth/provider.d.ts +8 -0
  71. package/dist/core/hearth/providers/__tests__/sweep-and-release.test.d.ts +1 -0
  72. package/dist/core/hearth/providers/__tests__/sweep-and-release.test.js +254 -0
  73. package/dist/core/hearth/providers/blaxel-home.d.ts +39 -0
  74. package/dist/core/hearth/providers/blaxel-home.js +271 -21
  75. package/dist/core/hearth/providers/blaxel.d.ts +5 -0
  76. package/dist/core/hearth/providers/blaxel.js +86 -4
  77. package/dist/core/hearth/providers/types.d.ts +16 -0
  78. package/dist/core/memory-resolver.d.ts +26 -15
  79. package/dist/core/memory-resolver.js +158 -40
  80. package/dist/core/profiles/manifest.d.ts +33 -0
  81. package/dist/core/profiles/manifest.js +332 -0
  82. package/dist/core/profiles/select.d.ts +11 -0
  83. package/dist/core/profiles/select.js +115 -0
  84. package/dist/core/runtime/bearings.d.ts +9 -0
  85. package/dist/core/runtime/bearings.js +31 -0
  86. package/dist/core/runtime/front-door.js +9 -2
  87. package/dist/core/runtime/launch.d.ts +25 -7
  88. package/dist/core/runtime/launch.js +42 -55
  89. package/dist/core/runtime/nodes.d.ts +7 -0
  90. package/dist/core/runtime/nodes.js +6 -0
  91. package/dist/core/runtime/persona.js +18 -32
  92. package/dist/core/runtime/spawn.d.ts +13 -0
  93. package/dist/core/runtime/spawn.js +25 -0
  94. package/dist/core/scope.d.ts +14 -7
  95. package/dist/core/scope.js +76 -20
  96. package/dist/core/substrate/index.d.ts +3 -3
  97. package/dist/core/substrate/index.js +5 -3
  98. package/dist/core/substrate/on-read.js +3 -2
  99. package/dist/core/substrate/render.d.ts +16 -11
  100. package/dist/core/substrate/render.js +249 -62
  101. package/dist/core/substrate/schema.d.ts +11 -3
  102. package/dist/core/substrate/schema.js +25 -0
  103. package/dist/core/substrate/subject.d.ts +15 -2
  104. package/dist/core/substrate/subject.js +24 -2
  105. package/dist/hearth/control-plane/__tests__/error-serialization.test.d.ts +1 -0
  106. package/dist/hearth/control-plane/__tests__/error-serialization.test.js +29 -0
  107. package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.d.ts +1 -0
  108. package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.js +44 -0
  109. package/dist/hearth/control-plane/__tests__/wake-roll.test.d.ts +1 -0
  110. package/dist/hearth/control-plane/__tests__/wake-roll.test.js +230 -0
  111. package/dist/hearth/control-plane/db.js +27 -0
  112. package/dist/hearth/control-plane/hearth-target.d.ts +23 -0
  113. package/dist/hearth/control-plane/hearth-target.js +68 -0
  114. package/dist/hearth/control-plane/registry.d.ts +6 -1
  115. package/dist/hearth/control-plane/registry.js +7 -0
  116. package/dist/hearth/control-plane/relay.d.ts +4 -0
  117. package/dist/hearth/control-plane/relay.js +72 -3
  118. package/dist/hearth/control-plane/secrets.d.ts +14 -0
  119. package/dist/hearth/control-plane/secrets.js +21 -0
  120. package/dist/hearth/control-plane/server.js +140 -4
  121. package/dist/hearth/control-plane/serving.d.ts +15 -0
  122. package/dist/hearth/control-plane/serving.js +106 -0
  123. package/dist/hearth/control-plane/types.d.ts +20 -0
  124. package/dist/hearth/control-plane/wake.d.ts +8 -2
  125. package/dist/hearth/control-plane/wake.js +241 -3
  126. package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
  127. package/dist/types.d.ts +57 -1
  128. package/dist/types.js +83 -1
  129. package/dist/web-client/assets/{index-MSKSWwV0.js → index-CbO8L0mN.js} +4 -4
  130. package/dist/web-client/assets/index-DwO46Cs5.css +2 -0
  131. package/dist/web-client/index.html +2 -2
  132. package/package.json +2 -2
  133. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +0 -56
  134. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +0 -57
  135. package/dist/builtin-memory/crouter-development/personas.md +0 -112
  136. package/dist/builtin-personas/spine/no-manager.md +0 -2
  137. package/dist/core/personas/index.d.ts +0 -13
  138. package/dist/core/personas/index.js +0 -11
  139. package/dist/core/personas/loader.d.ts +0 -159
  140. package/dist/core/personas/loader.js +0 -327
  141. package/dist/core/personas/resolve.d.ts +0 -59
  142. package/dist/core/personas/resolve.js +0 -376
  143. package/dist/web-client/assets/index-DUThOUzU.css +0 -2
@@ -0,0 +1,50 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { defineLeaf } from '../../core/command.js';
4
+ import { loadProfileManifest, profileRoot } from '../../core/profiles/manifest.js';
5
+ import { CONFIG_FILE } from '../../types.js';
6
+ export const showLeaf = defineLeaf({
7
+ name: 'show',
8
+ description: "one profile's manifest in full",
9
+ whenToUse: "you need a profile's full project list, id, and timestamps — after `profile list` narrows to a name, or to inspect what a node's bearings named as its profile.",
10
+ help: {
11
+ name: 'profile show',
12
+ summary: "print one profile's manifest plus its on-disk path and config.json location",
13
+ params: [
14
+ {
15
+ kind: 'positional',
16
+ name: 'profile',
17
+ required: true,
18
+ constraint: 'Exact profile id, or a unique manifest name. An ambiguous name fails listing every matching id.',
19
+ },
20
+ ],
21
+ output: [
22
+ { name: 'profile_id', type: 'string', required: true, constraint: 'Stable directory id.' },
23
+ { name: 'name', type: 'string', required: true, constraint: 'Manifest name.' },
24
+ { name: 'projects', type: 'string[]', required: true, constraint: 'Absolute project directories, in manifest order.' },
25
+ { name: 'created_at', type: 'string', required: true, constraint: 'ISO 8601 creation timestamp.' },
26
+ { name: 'last_used_at', type: 'string | null', required: true, constraint: 'ISO 8601 timestamp of the most recent selection, or null if never selected.' },
27
+ { name: 'path', type: 'string', required: true, constraint: 'Absolute path to the profile directory.' },
28
+ { name: 'config_path', type: 'string', required: true, constraint: 'Absolute path to the profile\'s config.json (hand-edited the same way as user/project config.json — no profile-specific editor verb exists).' },
29
+ { name: 'config_exists', type: 'boolean', required: true, constraint: 'Whether config.json exists at that path yet.' },
30
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands.' },
31
+ ],
32
+ outputKind: 'object',
33
+ effects: ['None. Read-only.'],
34
+ },
35
+ run: async (input) => {
36
+ const { profileId, manifest } = loadProfileManifest(input['profile']);
37
+ const configPath = join(profileRoot(profileId), CONFIG_FILE);
38
+ return {
39
+ profile_id: profileId,
40
+ name: manifest.name,
41
+ projects: manifest.projects,
42
+ created_at: manifest.created_at,
43
+ last_used_at: manifest.last_used_at,
44
+ path: profileRoot(profileId),
45
+ config_path: configPath,
46
+ config_exists: existsSync(configPath),
47
+ follow_up: `Add a project with \`crtr profile add-project ${profileId} --dir <path>\`, or rename with \`crtr profile rename ${profileId} --name <name>\`.`,
48
+ };
49
+ },
50
+ });
@@ -0,0 +1,2 @@
1
+ import type { BranchDef } from '../core/command.js';
2
+ export declare function registerProfile(): BranchDef;
@@ -0,0 +1,33 @@
1
+ // `crtr profile` subtree — the profile store (spec §2.1/§2.5): agent
2
+ // identities defined at the user root, each owning a manifest naming project
3
+ // directories plus its own memory store. Flat leaves; no sub-branches — 7
4
+ // verbs fits the 5-7 hard cap.
5
+ //
6
+ // Every verb but `new`/`list` takes <profile> as an exact id OR a unique
7
+ // manifest name (never two positionals: `add-project`/`remove-project` pair
8
+ // <profile> with `--dir`, `rename` pairs it with `--name`, matching the
9
+ // one-positional-per-leaf argv contract used across the whole tree).
10
+ import { defineBranch } from '../core/command.js';
11
+ import { newLeaf } from './profile/new.js';
12
+ import { listLeaf } from './profile/list.js';
13
+ import { showLeaf } from './profile/show.js';
14
+ import { addProjectLeaf } from './profile/add-project.js';
15
+ import { removeProjectLeaf } from './profile/remove-project.js';
16
+ import { renameLeaf } from './profile/rename.js';
17
+ import { deleteLeaf } from './profile/delete.js';
18
+ export function registerProfile() {
19
+ return defineBranch({
20
+ name: 'profile',
21
+ rootEntry: {
22
+ concept: 'an agent identity defined at the user root — a manifest naming project directories plus its own memory store',
23
+ desc: 'create, inspect, and manage profiles',
24
+ useWhen: 'creating a new agent identity, inspecting or listing existing ones, or changing a profile\u2019s project purview or display name. Selecting a profile for a running node (`--profile`, startup selection) lives elsewhere; this family only manages the manifest.',
25
+ },
26
+ help: {
27
+ name: 'profile',
28
+ summary: 'create, inspect, and manage profiles \u2014 agent identities with their own project purview and memory store',
29
+ model: '`new` creates one from a name (+ at most one initial `--project`). `list` inventories every profile as JSONL. `show` prints one manifest in full. `add-project` / `remove-project` mutate the ordered project list after creation, including mid-session on an already-running profile. `rename` changes only the manifest name \u2014 the profile_id is stable across a rename. `delete` removes a profile\u2019s directory (manifest + its own memory store) entirely and does not touch the project directories it pointed at. Every verb but `new`/`list` takes <profile> as an exact id or a unique manifest name; an ambiguous name fails listing every matching id.',
30
+ },
31
+ children: [newLeaf, listLeaf, showLeaf, addProjectLeaf, removeProjectLeaf, renameLeaf, deleteLeaf],
32
+ });
33
+ }
@@ -13,7 +13,6 @@ const TOP_LEVEL_KEYS = new Set([
13
13
  'max_panes_per_window',
14
14
  'canvasNav',
15
15
  'modelLadders',
16
- 'personaStrengths',
17
16
  ]);
18
17
  const MODEL_PROVIDERS = new Set(['anthropic', 'openai']);
19
18
  const MODEL_STRENGTHS = new Set(['ultra', 'strong', 'medium', 'light']);
@@ -105,20 +104,6 @@ function setNestedValue(cfg, key, rawValue, value) {
105
104
  }
106
105
  throw usage(`unsupported key path for set: ${key}`);
107
106
  }
108
- if (topKey === 'personaStrengths') {
109
- if (parts.length !== 2) {
110
- throw usage(`unsupported key path for set: ${key}`);
111
- }
112
- const persona = parts[1];
113
- if (persona.trim() === '' || persona !== persona.trim()) {
114
- throw usage(`personaStrengths.<persona-kind> must be a non-empty persona kind without leading or trailing whitespace`);
115
- }
116
- if (!isModelStrength(rawValue)) {
117
- throw usage(`personaStrengths.<persona-kind> must be ultra, strong, medium, or light`);
118
- }
119
- cfg.personaStrengths[persona] = rawValue;
120
- return;
121
- }
122
107
  if (parts.length === 1) {
123
108
  cfg[topKey] = value;
124
109
  return;
@@ -136,12 +121,12 @@ function setNestedValue(cfg, key, rawValue, value) {
136
121
  const configGet = defineLeaf({
137
122
  name: 'get',
138
123
  description: 'read a config value by key',
139
- whenToUse: 'you want to read a single config value by its dotted key — the current auto_update policy, max_panes_per_window, the canvasNav prefix key, model ladders, or persona strengths — optionally from a specific scope. Use sys config set instead to change a value, sys config path to locate the file for hand-editing.',
124
+ whenToUse: 'you want to read a single config value by its dotted key — the current auto_update policy, max_panes_per_window, the canvasNav prefix key, or model ladders — optionally from a specific scope. Use sys config set instead to change a value, sys config path to locate the file for hand-editing.',
140
125
  help: {
141
126
  name: 'sys config get',
142
127
  summary: 'read a config value by dotted key',
143
128
  params: [
144
- { kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Top-level keys: auto_update, marketplaces, plugins, max_panes_per_window, canvasNav, modelLadders, personaStrengths (read whole; edit canvasNav.prefixBinds/graphBinds in config.json directly).' },
129
+ { kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Top-level keys: auto_update, marketplaces, plugins, max_panes_per_window, canvasNav, modelLadders (read whole; edit canvasNav.prefixBinds/graphBinds in config.json directly).' },
145
130
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Scope to read from. Default: user.' },
146
131
  ],
147
132
  output: [
@@ -166,12 +151,12 @@ const configGet = defineLeaf({
166
151
  const configSet = defineLeaf({
167
152
  name: 'set',
168
153
  description: 'write a config value by key',
169
- whenToUse: 'you want to change a crtr setting — flip auto_update.crtr or auto_update.content to notify, apply, or off, raise max_panes_per_window, rebind canvasNav.prefixKey, set model ladders, or map persona strengths — written to the user or project scope. Use sys config get instead to read a value; the canvasNav record tables are not settable here, so edit config.json directly (sys config path) for those.',
154
+ whenToUse: 'you want to change a crtr setting — flip auto_update.crtr or auto_update.content to notify, apply, or off, raise max_panes_per_window, rebind canvasNav.prefixKey, or set model ladders — written to the user or project scope. Use sys config get instead to read a value; the canvasNav record tables are not settable here, so edit config.json directly (sys config path) for those.',
170
155
  help: {
171
156
  name: 'sys config set',
172
157
  summary: 'write a config value by dotted key',
173
158
  params: [
174
- { kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Supported: auto_update.crtr, auto_update.content, auto_update.interval_hours, max_panes_per_window, canvasNav.prefixKey, modelLadders.defaultProvider, modelLadders.<anthropic|openai>.<ultra|strong|medium|light>, personaStrengths.<persona-kind>. The canvasNav.prefixBinds/graphBinds record tables are not settable here — edit config.json directly (`crtr sys config path`).' },
159
+ { kind: 'positional', name: 'key', type: 'string', required: true, constraint: 'Dotted key path. Supported: auto_update.crtr, auto_update.content, auto_update.interval_hours, max_panes_per_window, canvasNav.prefixKey, modelLadders.defaultProvider, modelLadders.<anthropic|openai>.<ultra|strong|medium|light>. The canvasNav.prefixBinds/graphBinds record tables are not settable here — edit config.json directly (`crtr sys config path`).' },
175
160
  { kind: 'flag', name: 'value', type: 'string', required: true, constraint: 'value VALUE — string, required. Stored as-is if quoted; coerced to number or boolean when unambiguous.' },
176
161
  { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Scope to write to. Default: user.' },
177
162
  ],
@@ -240,10 +225,10 @@ const configPath = defineLeaf({
240
225
  export const configBranch = defineBranch({
241
226
  name: 'config',
242
227
  description: 'read and write configuration',
243
- whenToUse: 'inspecting or changing crtr settings — read a value with sys config get, change one with sys config set (auto_update policy, max_panes_per_window, canvasNav.prefixKey, model ladders, persona strengths), or locate config.json with sys config path to hand-edit the record tables set cannot reach.',
228
+ whenToUse: 'inspecting or changing crtr settings — read a value with sys config get, change one with sys config set (auto_update policy, max_panes_per_window, canvasNav.prefixKey, model ladders), or locate config.json with sys config path to hand-edit the record tables set cannot reach.',
244
229
  help: {
245
230
  name: 'sys config',
246
- summary: 'read and write crtr configuration, including model ladders and persona strengths',
231
+ summary: 'read and write crtr configuration, including model ladders',
247
232
  },
248
233
  children: [configGet, configSet, configPath],
249
234
  });
@@ -3,8 +3,10 @@ import { join } from 'node:path';
3
3
  import { defineLeaf } from '../../core/command.js';
4
4
  import { InputError, readStdinRaw } from '../../core/io.js';
5
5
  import { workspaceRoot } from '../../core/artifact.js';
6
- import { availableKinds, kindWhenToUse, subPersonasFor } from '../../core/personas/index.js';
7
- import { resolvePromptReview } from '../../core/personas/resolve.js';
6
+ import { getNode } from '../../core/canvas/index.js';
7
+ import { readMergedLaunchConfig, subKindsAvailableTo } from '../../core/config.js';
8
+ import { LAUNCH_SYSTEM_PROMPT } from '../../core/runtime/launch.js';
9
+ import { renderPreferencesForSubject, renderKnowledgeBlock, scopeForCwd, spineDepth, profileNameFor, } from '../../core/substrate/index.js';
8
10
  function resolveLifecycle(mode, lifecycle) {
9
11
  if (lifecycle !== undefined)
10
12
  return lifecycle;
@@ -41,24 +43,47 @@ function parseBool(value, field, defaultValue) {
41
43
  function optionalString(value) {
42
44
  return typeof value === 'string' && value.trim() !== '' ? value : null;
43
45
  }
44
- function normaliseKindList() {
45
- return availableKinds().map((kind) => ({ kind, whenToUse: kindWhenToUse(kind) }));
46
+ function approxTokens(text) {
47
+ return Math.ceil(text.length / 4);
46
48
  }
47
- function normaliseSubPersonaList(kind) {
48
- return subPersonasFor(kind).map((sub) => ({ kind: sub.kind, whenToUse: sub.whenToUse }));
49
+ function topLevelKinds() {
50
+ const registry = readMergedLaunchConfig().kinds;
51
+ return Object.entries(registry)
52
+ .filter(([kind]) => !kind.includes('/'))
53
+ .map(([kind, cfg]) => ({ kind, whenToUse: cfg.whenToUse }))
54
+ .sort((a, b) => a.kind.localeCompare(b.kind));
55
+ }
56
+ function subKindsFor(kind) {
57
+ return subKindsAvailableTo(kind);
49
58
  }
50
59
  function reviewList() {
51
- const kinds = normaliseKindList();
60
+ const kinds = topLevelKinds();
52
61
  const subPersonas = {};
53
62
  for (const { kind } of kinds)
54
- subPersonas[kind] = normaliseSubPersonaList(kind);
63
+ subPersonas[kind] = subKindsFor(kind);
64
+ return { kinds, modes: ['base', 'orchestrator'], lifecycles: ['terminal', 'resident'], subPersonas };
65
+ }
66
+ function buildReviewSubject(kind, mode, lifecycle, hasManager, nodeId) {
67
+ const node = nodeId !== null ? getNode(nodeId) : null;
68
+ const cwd = node?.cwd ?? process.cwd();
55
69
  return {
56
- kinds,
57
- modes: ['base', 'orchestrator'],
58
- lifecycles: ['terminal', 'resident'],
59
- subPersonas,
70
+ kind,
71
+ mode,
72
+ lifecycle,
73
+ hasManager,
74
+ cwd,
75
+ scope: scopeForCwd(cwd),
76
+ orchestration: { depth: node !== null ? spineDepth(node.node_id) : 0 },
77
+ profile: profileNameFor(node?.profile_id),
60
78
  };
61
79
  }
80
+ function spliceIntoScaffold(scaffold, block) {
81
+ const anchor = '\n\nGuidelines:';
82
+ const idx = scaffold.indexOf(anchor);
83
+ if (idx === -1)
84
+ return `${scaffold}\n\n${block}`;
85
+ return `${scaffold.slice(0, idx)}\n\n${block}${scaffold.slice(idx)}`;
86
+ }
62
87
  function promptReviewPath() {
63
88
  const dir = workspaceRoot();
64
89
  const reviewsDir = join(dir, 'prompt-reviews');
@@ -118,14 +143,62 @@ function resolveDeck(input) {
118
143
  const mode = input['mode'] ?? 'base';
119
144
  const lifecycle = resolveLifecycle(mode, input['lifecycle']);
120
145
  const hasManager = parseBool(input['hasManager'], 'hasManager', true);
121
- const node = optionalString(input['node']);
146
+ const nodeId = optionalString(input['node']);
122
147
  if (kind === undefined || kind.trim() === '') {
123
148
  throw new InputError({ error: 'missing_parameter', message: 'kind is required', field: 'kind', next: 'Pass --kind <kind> or use --list.' });
124
149
  }
125
- return resolvePromptReview(kind, mode, {
126
- lifecycle,
127
- hasManager,
128
- }, node ?? undefined);
150
+ const node = nodeId !== null ? getNode(nodeId) : null;
151
+ const subject = buildReviewSubject(kind, mode, lifecycle, hasManager, node?.node_id ?? null);
152
+ const { block, contentDocs, tree } = renderPreferencesForSubject(subject);
153
+ const layers = [
154
+ {
155
+ id: 'launch-scaffold', label: 'Launch scaffold', group: 'generated',
156
+ source: 'generated: launch scaffold', sourcePath: null, scope: 'generated',
157
+ included: true, condition: null, text: LAUNCH_SYSTEM_PROMPT,
158
+ tokens: approxTokens(LAUNCH_SYSTEM_PROMPT), chars: LAUNCH_SYSTEM_PROMPT.length,
159
+ },
160
+ ...contentDocs.map((d) => ({
161
+ id: d.name, label: d.name, group: 'substrate',
162
+ source: `memory: ${d.name}`, sourcePath: d.path, scope: d.scope,
163
+ included: true, condition: null, text: d.body,
164
+ tokens: approxTokens(d.body), chars: d.body.length,
165
+ })),
166
+ ];
167
+ if (tree !== '') {
168
+ layers.push({
169
+ id: 'preference-catalog', label: 'Preference catalog (routing entries)', group: 'substrate',
170
+ source: 'generated: preference catalog', sourcePath: null, scope: 'generated',
171
+ included: true, condition: null, text: tree, tokens: approxTokens(tree), chars: tree.length,
172
+ });
173
+ }
174
+ if (node !== null) {
175
+ const knowledge = renderKnowledgeBlock(node.node_id);
176
+ if (knowledge !== '') {
177
+ layers.push({
178
+ id: 'memory-knowledge', label: 'Memory (knowledge) — context message, not system prompt',
179
+ group: 'substrate', source: 'runtime-spliced', sourcePath: null, scope: 'runtime',
180
+ included: false, condition: 'session_start context message, not the system prompt',
181
+ text: knowledge, tokens: approxTokens(knowledge), chars: knowledge.length,
182
+ });
183
+ }
184
+ }
185
+ const assembled = spliceIntoScaffold(LAUNCH_SYSTEM_PROMPT, block);
186
+ const sources = [];
187
+ const seen = new Set();
188
+ for (const d of contentDocs) {
189
+ const key = `${d.scope}:${d.path}`;
190
+ if (seen.has(key))
191
+ continue;
192
+ seen.add(key);
193
+ sources.push({ id: d.name, label: d.name, path: d.path, scope: d.scope });
194
+ }
195
+ return {
196
+ config: { kind, mode, lifecycle, hasManager, node: node?.node_id ?? null },
197
+ layers,
198
+ assembled,
199
+ total: { tokens: approxTokens(assembled), chars: assembled.length },
200
+ sources,
201
+ };
129
202
  }
130
203
  export const promptReviewLeaf = defineLeaf({
131
204
  name: 'prompt-review',
@@ -21,7 +21,7 @@ import { ensureHome, ensureNodeDirs, nodeMetaPath, nodeDir, nodesRoot, jobDir, }
21
21
  /** The identity keys meta.json persists. Listed explicitly so no runtime field
22
22
  * can ever leak onto disk even when a fully-hydrated NodeMeta is handed in. */
23
23
  const IDENTITY_KEYS = [
24
- 'node_id', 'name', 'description', 'cycles', 'created', 'cwd', 'host_kind', 'kind', 'mode',
24
+ 'node_id', 'name', 'description', 'cycles', 'created', 'cwd', 'host_kind', 'profile_id', 'kind', 'mode',
25
25
  'lifecycle', 'persona_ack', 'parent', 'spawned_by', 'fork_from', 'passive_default',
26
26
  'pi_session_id', 'pi_session_file', 'model_override', 'launch',
27
27
  ];
@@ -101,13 +101,13 @@ function writeMeta(meta) {
101
101
  * state. A fresh insert takes the schema defaults for runtime. */
102
102
  function upsertRow(meta) {
103
103
  openDb()
104
- .prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, host_kind, parent, created)
105
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
104
+ .prepare(`INSERT INTO nodes (node_id, name, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created)
105
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
106
106
  ON CONFLICT(node_id) DO UPDATE SET
107
107
  name=excluded.name, kind=excluded.kind, mode=excluded.mode,
108
108
  lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
109
- parent=excluded.parent`)
110
- .run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.parent ?? null, meta.created);
109
+ profile_id=excluded.profile_id, parent=excluded.parent`)
110
+ .run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.created);
111
111
  }
112
112
  /** Seed a node's row at BIRTH: identity columns + runtime columns taken from the
113
113
  * incoming meta (defaults: status='active', the rest null). The only writer
@@ -116,17 +116,17 @@ function upsertRow(meta) {
116
116
  function seedRow(meta) {
117
117
  openDb()
118
118
  .prepare(`INSERT INTO nodes
119
- (node_id, name, kind, mode, lifecycle, cwd, host_kind, parent, created,
119
+ (node_id, name, kind, mode, lifecycle, cwd, host_kind, profile_id, parent, created,
120
120
  status, intent, pi_pid, "window", tmux_session, pane)
121
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
121
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
122
122
  ON CONFLICT(node_id) DO UPDATE SET
123
123
  name=excluded.name, kind=excluded.kind, mode=excluded.mode,
124
124
  lifecycle=excluded.lifecycle, cwd=excluded.cwd, host_kind=excluded.host_kind,
125
- parent=excluded.parent,
125
+ profile_id=excluded.profile_id, parent=excluded.parent,
126
126
  status=excluded.status, intent=excluded.intent, pi_pid=excluded.pi_pid,
127
127
  "window"=excluded."window", tmux_session=excluded.tmux_session,
128
128
  pane=excluded.pane`)
129
- .run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.parent ?? null, meta.created, meta.status ?? 'active', meta.intent ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
129
+ .run(meta.node_id, meta.name, meta.kind, meta.mode, meta.lifecycle, meta.cwd, meta.host_kind ?? null, meta.profile_id ?? null, meta.parent ?? null, meta.created, meta.status ?? 'active', meta.intent ?? null, meta.pi_pid ?? null, meta.window ?? null, meta.tmux_session ?? null, meta.pane ?? null);
130
130
  }
131
131
  function rowFrom(r) {
132
132
  return {
@@ -138,6 +138,7 @@ function rowFrom(r) {
138
138
  status: r['status'],
139
139
  cwd: r['cwd'],
140
140
  host_kind: r['host_kind'] ?? null,
141
+ profile_id: r['profile_id'] ?? null,
141
142
  parent: r['parent'] ?? null,
142
143
  created: r['created'],
143
144
  intent: r['intent'] ?? null,
@@ -349,6 +349,16 @@ CREATE TABLE IF NOT EXISTS canvas_meta (
349
349
  );
350
350
  `);
351
351
  }
352
+ /** v11 — additive identity column `profile_id`: the profile-directory id
353
+ * (`<slug>-<id>`) a node runs under, chosen once at spawn (inherited from the
354
+ * spawner, or `--profile`). Durable IDENTITY mirrored from meta.json like
355
+ * `cwd`/`host_kind` (NOT runtime like `pane`), so it survives an index rebuild
356
+ * via the `IDENTITY_KEYS`/`upsertRow` path. Defaults NULL (root/no-profile);
357
+ * no data backfill. Additive, forward-only. */
358
+ function addProfileIdColumn(db) {
359
+ const columns = nodeColumns(db);
360
+ addNodeColumnIfMissing(db, columns, 'profile_id', `ALTER TABLE nodes ADD COLUMN profile_id TEXT;`);
361
+ }
352
362
  /** The ordered migration list. Index `i` is migration version `i + 1`; the db's
353
363
  * `user_version` tracks how many have been applied. Append only. */
354
364
  export const MIGRATIONS = [
@@ -362,6 +372,7 @@ export const MIGRATIONS = [
362
372
  /* v8 */ addHostKindColumn,
363
373
  /* v9 */ migrateWakeupsToTriggers,
364
374
  /* v10 */ addCanvasMetaTable,
375
+ /* v11 */ addProfileIdColumn,
365
376
  ];
366
377
  /** Bring `db` up to the latest schema version. Reads `user_version`, runs each
367
378
  * pending migration in order, and bumps `user_version` after each so the work
@@ -404,6 +404,7 @@ export function nodeArtifacts(nodeId, types) {
404
404
  status: node.status,
405
405
  cwd: node.cwd,
406
406
  host_kind: node.host_kind ?? null,
407
+ profile_id: node.profile_id ?? null,
407
408
  parent: node.parent ?? null,
408
409
  created: node.created,
409
410
  intent: node.intent ?? null,
@@ -58,6 +58,13 @@ export interface NodeIdentity {
58
58
  * `cwd`, re-derived on index rebuild), NOT live runtime like `pane`.
59
59
  * NULL ⇒ 'tmux' (every pre-broker node). */
60
60
  host_kind?: 'tmux' | 'broker' | null;
61
+ /** The profile this node runs under, a stable profile-directory id
62
+ * (`<slug>-<id>`, see `core/profiles/manifest.ts`). Durable IDENTITY mirrored
63
+ * to the row (like `cwd`/`host_kind`), NOT re-derived by `rebuildIndex()` —
64
+ * it is chosen once at spawn (inherited from the spawner, or `--profile`) and
65
+ * is the runtime→resolver seam (`CRTR_PROFILE_ID`) the memory/gate substrate
66
+ * reads. NULL for a root/no-profile node — never invent a literal root id. */
67
+ profile_id?: string | null;
61
68
  /** Role the node was born as: explore | developer | plan | review | general… */
62
69
  kind: string;
63
70
  mode: Mode;
@@ -161,6 +168,9 @@ export interface NodeRow {
161
168
  cwd: string;
162
169
  /** Which HOST launches + supervises this node; see NodeIdentity.host_kind. */
163
170
  host_kind: 'tmux' | 'broker' | null;
171
+ /** The profile this node runs under; see NodeIdentity.profile_id. NULL for a
172
+ * root/no-profile node. */
173
+ profile_id: string | null;
164
174
  parent: string | null;
165
175
  created: string;
166
176
  /** Authoritative runtime columns (see NodeRuntime). */
@@ -1,4 +1,4 @@
1
- import type { Scope, ScopeConfig, ScopeState } from '../types.js';
1
+ import type { Scope, ScopeConfig, ScopeState, KindConfig, ModelLaddersConfig } from '../types.js';
2
2
  export declare function configPath(scope: Scope): string | null;
3
3
  export declare function statePath(scope: Scope): string | null;
4
4
  export declare function readConfig(scope: Scope): ScopeConfig;
@@ -6,5 +6,48 @@ export declare function readState(scope: Scope): ScopeState;
6
6
  export declare function writeConfig(scope: Scope, config: ScopeConfig): void;
7
7
  export declare function writeState(scope: Scope, state: ScopeState): void;
8
8
  export declare function ensureScopeInitialized(scope: Scope, root: string): void;
9
+ export interface MergedLaunchConfig {
10
+ kinds: Record<string, KindConfig>;
11
+ modelLadders: ModelLaddersConfig;
12
+ }
13
+ /** Merge launch knobs (`kinds`, `modelLadders`) across scopes in
14
+ * project stack > profile > user > builtin precedence — the same precedence
15
+ * order used for memory resolution. A kind or ladder cell declared at a
16
+ * more-specific scope shadows the same key from a less-specific scope; the
17
+ * project STACK (`findProjectScopeRoots` — every ancestor `.crouter/`,
18
+ * widened by a selected profile's `projects`) layers nearest-root-strongest;
19
+ * a key no scope declares falls through to the builtin default registry
20
+ * (`defaultScopeConfig().kinds` / `.modelLadders`).
21
+ *
22
+ * Layers RAW (un-defaulted) partial config per scope, not `readConfig(scope)`
23
+ * — `readConfig` already fills in every default kind for a scope that
24
+ * customizes even one, so naively overlaying two already-defaulted
25
+ * `ScopeConfig.kinds` objects would let an untouched project-scope kind
26
+ * (silently defaulted) clobber a real user-scope customization of that same
27
+ * kind. Layering the raw partials avoids that.
28
+ *
29
+ * Callers (launch, kind registry) go through this function rather than
30
+ * `readConfig` directly, so `ScopeConfig.kinds`/`modelLadders`/tools/
31
+ * extensions/`availableTo` all honor the same profile + multi-project
32
+ * precedence. */
33
+ export declare function readMergedLaunchConfig(): MergedLaunchConfig;
34
+ /** The effective `KindConfig` for one full kind string (top-level, e.g.
35
+ * `developer`, or sub-kind, e.g. `plan/reviewers/security`), across
36
+ * project > user > builtin precedence. Returns `undefined` for a kind no
37
+ * scope registers — existence is deliberately NOT validated here (kind
38
+ * existence/launch-menu enumeration is a Phase 3 caller concern); this only
39
+ * resolves the config for a kind the caller already knows about. */
40
+ export declare function resolveKindConfig(kind: string): KindConfig | undefined;
41
+ /** The sub-kinds available to spawn FROM a given top-level kind — every
42
+ * registered sub-kind (full path contains `/`) whose `availableTo` (default:
43
+ * its own top-level ancestor, e.g. `plan/reviewers/security` defaults to
44
+ * `['plan']`) includes `kind` or `'*'`. The single source both `sys
45
+ * prompt-review --list`'s `subPersonas` metadata and the live sub-persona
46
+ * spawn-menu splice (`core/substrate/render.ts`) read, so the two can never
47
+ * drift apart. Sorted by full kind name for stable rendering. */
48
+ export declare function subKindsAvailableTo(kind: string): {
49
+ kind: string;
50
+ whenToUse: string;
51
+ }[];
9
52
  export declare function updateConfig(scope: Scope, mutate: (cfg: ScopeConfig) => void): ScopeConfig;
10
53
  export declare function updateState(scope: Scope, mutate: (s: ScopeState) => void): ScopeState;