@crouton-kit/crouter 0.3.20 → 0.3.22

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 (144) hide show
  1. package/dist/builtin-memory/crouter-development/marketplaces.md +2 -4
  2. package/dist/builtin-memory/crouter-development/personas/base-prompt.md +3 -5
  3. package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +3 -6
  4. package/dist/builtin-memory/crouter-development/personas.md +2 -6
  5. package/dist/builtin-memory/crouter-development/plugins.md +2 -4
  6. package/dist/builtin-memory/design.md +2 -4
  7. package/dist/builtin-memory/development.md +3 -3
  8. package/dist/builtin-memory/planning.md +3 -4
  9. package/dist/builtin-memory/spec.md +3 -10
  10. package/dist/builtin-personas/orchestration-kernel.md +1 -1
  11. package/dist/commands/canvas-history/read.d.ts +1 -0
  12. package/dist/commands/canvas-history/read.js +61 -0
  13. package/dist/commands/canvas-history/search.d.ts +1 -0
  14. package/dist/commands/canvas-history/search.js +270 -0
  15. package/dist/commands/canvas-history/show.d.ts +1 -0
  16. package/dist/commands/canvas-history/show.js +79 -0
  17. package/dist/commands/canvas-history.d.ts +2 -0
  18. package/dist/commands/canvas-history.js +33 -0
  19. package/dist/commands/canvas.js +3 -2
  20. package/dist/commands/human/queue.js +2 -2
  21. package/dist/commands/memory/__tests__/lint-schema.test.js +29 -0
  22. package/dist/commands/memory/find.js +14 -52
  23. package/dist/commands/memory/lint.d.ts +7 -0
  24. package/dist/commands/memory/lint.js +29 -35
  25. package/dist/commands/memory/list.js +8 -18
  26. package/dist/commands/memory/read.js +4 -37
  27. package/dist/commands/memory/shared.js +1 -2
  28. package/dist/commands/memory/write.js +6 -9
  29. package/dist/commands/node.js +47 -25
  30. package/dist/commands/pkg/bridge.d.ts +1 -0
  31. package/dist/commands/pkg/bridge.js +137 -0
  32. package/dist/commands/pkg/plugin-inspect.js +7 -6
  33. package/dist/commands/pkg.js +2 -1
  34. package/dist/commands/push.js +10 -15
  35. package/dist/commands/skill/author.js +35 -44
  36. package/dist/commands/skill/shared.d.ts +1 -5
  37. package/dist/commands/skill/shared.js +9 -63
  38. package/dist/commands/skill.js +5 -6
  39. package/dist/commands/sys/doctor.js +6 -144
  40. package/dist/commands/tmux-spread.js +16 -1
  41. package/dist/commands/view-cycle.js +9 -5
  42. package/dist/commands/view-run.js +3 -2
  43. package/dist/core/__tests__/broker-dormant-wake.test.js +71 -34
  44. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +21 -5
  45. package/dist/core/__tests__/canvas-inbox-watcher.test.js +21 -5
  46. package/dist/core/__tests__/child-death-wake.test.js +133 -185
  47. package/dist/core/__tests__/context-intro.test.js +23 -27
  48. package/dist/core/__tests__/daemon-liveness.test.js +28 -258
  49. package/dist/core/__tests__/flagship-lifecycle.test.js +156 -135
  50. package/dist/core/__tests__/frame-decoder-perf.test.js +51 -25
  51. package/dist/core/__tests__/{broker-attach-limits.test.js → full/broker-attach-limits.test.js} +62 -35
  52. package/dist/core/__tests__/{broker-attach-stream.test.js → full/broker-attach-stream.test.js} +54 -31
  53. package/dist/core/__tests__/full/broker-crash-teardown.test.js +143 -0
  54. package/dist/core/__tests__/full/broker-dialogs.test.js +153 -0
  55. package/dist/core/__tests__/full/broker-lifecycle.test.js +116 -0
  56. package/dist/core/__tests__/{cascade-close.test.js → full/cascade-close.test.js} +4 -3
  57. package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +292 -0
  58. package/dist/core/__tests__/{dead-pane-regression.test.js → full/dead-pane-regression.test.js} +2 -2
  59. package/dist/core/__tests__/{detach-focus.test.js → full/detach-focus.test.js} +7 -7
  60. package/dist/core/__tests__/{human-new-window-regression.test.js → full/human-new-window-regression.test.js} +2 -2
  61. package/dist/core/__tests__/{placement-focus.test.js → full/placement-focus.test.js} +5 -5
  62. package/dist/core/__tests__/{placement-reconcile.test.js → full/placement-reconcile.test.js} +3 -3
  63. package/dist/core/__tests__/{placement-revive.test.js → full/placement-revive.test.js} +5 -5
  64. package/dist/core/__tests__/{placement-teardown.test.js → full/placement-teardown.test.js} +4 -4
  65. package/dist/core/__tests__/{review-render-pane-regression.test.js → full/review-render-pane-regression.test.js} +2 -2
  66. package/dist/core/__tests__/full/spike-harness.test.d.ts +1 -0
  67. package/dist/core/__tests__/full/spike-harness.test.js +117 -0
  68. package/dist/core/__tests__/grace-clock.test.js +72 -75
  69. package/dist/core/__tests__/helpers/harness.d.ts +35 -1
  70. package/dist/core/__tests__/helpers/harness.js +70 -12
  71. package/dist/core/__tests__/live-mutation-verbs.test.js +100 -105
  72. package/dist/core/__tests__/live-mutation.test.js +111 -134
  73. package/dist/core/__tests__/memory.test.js +4 -4
  74. package/dist/core/__tests__/revive.test.js +96 -86
  75. package/dist/core/__tests__/subscription-delivery.test.js +116 -138
  76. package/dist/core/__tests__/wake-origin.test.js +54 -50
  77. package/dist/core/bootstrap.js +18 -14
  78. package/dist/core/bridge-map.d.ts +19 -0
  79. package/dist/core/bridge-map.js +73 -0
  80. package/dist/core/canvas/history.d.ts +91 -0
  81. package/dist/core/canvas/history.js +389 -0
  82. package/dist/core/canvas/index.d.ts +1 -0
  83. package/dist/core/canvas/index.js +1 -0
  84. package/dist/core/config.js +3 -22
  85. package/dist/core/help.d.ts +2 -2
  86. package/dist/core/help.js +1 -1
  87. package/dist/core/memory-resolver.d.ts +9 -2
  88. package/dist/core/memory-resolver.js +47 -7
  89. package/dist/core/render.d.ts +4 -3
  90. package/dist/core/render.js +38 -41
  91. package/dist/core/resolver.d.ts +1 -30
  92. package/dist/core/resolver.js +6 -546
  93. package/dist/core/runtime/bearings.d.ts +2 -2
  94. package/dist/core/runtime/bearings.js +2 -2
  95. package/dist/core/runtime/persona.js +5 -7
  96. package/dist/core/scope.d.ts +7 -5
  97. package/dist/core/scope.js +16 -17
  98. package/dist/core/substrate/ceiling.d.ts +28 -0
  99. package/dist/core/substrate/ceiling.js +87 -0
  100. package/dist/core/substrate/index.d.ts +2 -0
  101. package/dist/core/substrate/index.js +1 -0
  102. package/dist/core/substrate/on-read.js +18 -2
  103. package/dist/core/substrate/render.d.ts +3 -5
  104. package/dist/core/substrate/render.js +68 -59
  105. package/dist/core/substrate/schema.d.ts +18 -13
  106. package/dist/core/substrate/schema.js +12 -11
  107. package/dist/pi-extensions/canvas-context-intro.js +5 -3
  108. package/dist/pi-extensions/canvas-inbox-watcher.js +19 -2
  109. package/dist/types.d.ts +3 -18
  110. package/dist/types.js +0 -8
  111. package/package.json +4 -3
  112. package/dist/builtin-skills/.crouter-plugin/plugin.json +0 -5
  113. package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +0 -157
  114. package/dist/builtin-skills/skills/crouter-development/personas/SKILL.md +0 -106
  115. package/dist/builtin-skills/skills/crouter-development/personas/base-prompt/SKILL.md +0 -49
  116. package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +0 -49
  117. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +0 -156
  118. package/dist/builtin-skills/skills/design/SKILL.md +0 -51
  119. package/dist/builtin-skills/skills/development/SKILL.md +0 -109
  120. package/dist/builtin-skills/skills/planning/SKILL.md +0 -59
  121. package/dist/builtin-skills/skills/spec/SKILL.md +0 -83
  122. package/dist/commands/skill/state.d.ts +0 -3
  123. package/dist/commands/skill/state.js +0 -71
  124. package/dist/core/__tests__/broker-crash-teardown.test.js +0 -116
  125. package/dist/core/__tests__/broker-dialogs.test.js +0 -126
  126. package/dist/core/__tests__/broker-lifecycle.test.js +0 -87
  127. package/dist/core/__tests__/resolver.test.js +0 -181
  128. package/dist/core/__tests__/spike-harness.test.js +0 -242
  129. /package/dist/{core/__tests__/broker-attach-limits.test.d.ts → commands/memory/__tests__/lint-schema.test.d.ts} +0 -0
  130. /package/dist/core/__tests__/{broker-attach-stream.test.d.ts → full/broker-attach-limits.test.d.ts} +0 -0
  131. /package/dist/core/__tests__/{broker-crash-teardown.test.d.ts → full/broker-attach-stream.test.d.ts} +0 -0
  132. /package/dist/core/__tests__/{broker-dialogs.test.d.ts → full/broker-crash-teardown.test.d.ts} +0 -0
  133. /package/dist/core/__tests__/{broker-lifecycle.test.d.ts → full/broker-dialogs.test.d.ts} +0 -0
  134. /package/dist/core/__tests__/{cascade-close.test.d.ts → full/broker-lifecycle.test.d.ts} +0 -0
  135. /package/dist/core/__tests__/{dead-pane-regression.test.d.ts → full/cascade-close.test.d.ts} +0 -0
  136. /package/dist/core/__tests__/{detach-focus.test.d.ts → full/daemon-liveness-pane.full.test.d.ts} +0 -0
  137. /package/dist/core/__tests__/{human-new-window-regression.test.d.ts → full/dead-pane-regression.test.d.ts} +0 -0
  138. /package/dist/core/__tests__/{placement-focus.test.d.ts → full/detach-focus.test.d.ts} +0 -0
  139. /package/dist/core/__tests__/{placement-reconcile.test.d.ts → full/human-new-window-regression.test.d.ts} +0 -0
  140. /package/dist/core/__tests__/{placement-revive.test.d.ts → full/placement-focus.test.d.ts} +0 -0
  141. /package/dist/core/__tests__/{placement-teardown.test.d.ts → full/placement-reconcile.test.d.ts} +0 -0
  142. /package/dist/core/__tests__/{resolver.test.d.ts → full/placement-revive.test.d.ts} +0 -0
  143. /package/dist/core/__tests__/{review-render-pane-regression.test.d.ts → full/placement-teardown.test.d.ts} +0 -0
  144. /package/dist/core/__tests__/{spike-harness.test.d.ts → full/review-render-pane-regression.test.d.ts} +0 -0
@@ -1,35 +1,12 @@
1
- import { join, relative, sep, dirname } from 'node:path';
2
- import { SCOPE_SKILL_PLUGIN, SKILL_ENTRY_FILE, SKILLS_DIR, skillConfigKey, } from '../types.js';
1
+ import { join } from 'node:path';
3
2
  import { readConfig } from './config.js';
4
- import { listDirs, pathExists, readText, readTextIfExists, walkFiles, } from './fs-utils.js';
3
+ import { listDirs, pathExists } from './fs-utils.js';
5
4
  import { readMarketplaceManifest, readPluginManifest } from './manifest.js';
6
- import { parseFrontmatter } from './frontmatter.js';
7
- import { ambiguous, notFound, usage } from './errors.js';
8
- import { warn } from './output.js';
9
5
  import { InputError } from './io.js';
10
- import { builtinSkillsRoot, marketplacesDir, pluginsDir, projectScopeRoot, scopeSkillsDir, userScopeRoot, } from './scope.js';
11
- function getBuiltinPlugin() {
12
- const root = builtinSkillsRoot();
13
- if (!pathExists(root))
14
- return null;
15
- const manifest = readPluginManifest(root);
16
- if (!manifest)
17
- return null;
18
- return {
19
- name: manifest.name,
20
- scope: 'builtin',
21
- root,
22
- manifest,
23
- enabled: true,
24
- builtin: true,
25
- version: manifest.version,
26
- };
27
- }
6
+ import { marketplacesDir, pluginsDir, projectScopeRoot, userScopeRoot, } from './scope.js';
28
7
  export function listInstalledPlugins(scope) {
29
- if (scope === 'builtin') {
30
- const builtin = getBuiltinPlugin();
31
- return builtin ? [builtin] : [];
32
- }
8
+ // The builtin scope has no scopeRoot, so pluginsDir('builtin') is null and
9
+ // this returns [] — builtin content is the memory substrate, not plugins.
33
10
  const dir = pluginsDir(scope);
34
11
  if (!dir || !pathExists(dir))
35
12
  return [];
@@ -77,406 +54,12 @@ export function findPluginByName(name, scope) {
77
54
  }
78
55
  return null;
79
56
  }
80
- function loadScopeConfigs() {
81
- const user = readConfig('user');
82
- if (projectScopeRoot())
83
- return { project: readConfig('project'), user };
84
- return { user };
85
- }
86
- export function effectiveSkillEnabled(pluginName, skillName, cfgs) {
87
- const key = skillConfigKey(pluginName, skillName);
88
- if (cfgs.project && cfgs.project.skills[key] !== undefined) {
89
- const e = cfgs.project.skills[key].enabled;
90
- return e ? { enabled: true } : { enabled: false, disabledIn: 'project' };
91
- }
92
- if (cfgs.user.skills[key] !== undefined) {
93
- const e = cfgs.user.skills[key].enabled;
94
- return e ? { enabled: true } : { enabled: false, disabledIn: 'user' };
95
- }
96
- return { enabled: true };
97
- }
98
- /** Parse one skill file's frontmatter at the COLLECTION layer: the strict
99
- * parser THROWS on invalid YAML (the frontmatter contract is "valid YAML"),
100
- * so a single malformed SKILL.md must not brick a whole `skill find`/catalog
101
- * scan across the corpus. On a parse error, emit a clear scoped notice naming
102
- * the file and return null so the iterator SKIPS it and continues. A doc with
103
- * no frontmatter block parses fine (data === null) and is kept. */
104
- function readSkillFrontmatterSafe(file) {
105
- try {
106
- return parseFrontmatter(readText(file));
107
- }
108
- catch (e) {
109
- const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
110
- warn(`invalid frontmatter in ${file}: ${msg}`);
111
- return null;
112
- }
113
- }
114
- export function listSkillsInPlugin(plugin, cfgs) {
115
- const skillsRoot = join(plugin.root, SKILLS_DIR);
116
- if (!pathExists(skillsRoot))
117
- return [];
118
- const configs = cfgs === undefined ? loadScopeConfigs() : cfgs;
119
- const skills = [];
120
- const skillFiles = walkFiles(skillsRoot, (n) => n === SKILL_ENTRY_FILE);
121
- for (const file of skillFiles) {
122
- const rel = relative(skillsRoot, dirname(file));
123
- const name = rel.split(sep).join('/');
124
- if (!name)
125
- continue;
126
- const parsed = readSkillFrontmatterSafe(file);
127
- if (parsed === null)
128
- continue;
129
- const { data } = parsed;
130
- const { enabled, disabledIn } = effectiveSkillEnabled(plugin.name, name, configs);
131
- skills.push({
132
- name,
133
- plugin: plugin.name,
134
- scope: plugin.scope,
135
- path: file,
136
- pluginRoot: plugin.root,
137
- frontmatter: data === null ? { name } : data,
138
- enabled,
139
- disabledIn,
140
- });
141
- }
142
- return skills.sort((a, b) => a.name.localeCompare(b.name));
143
- }
144
- export function listScopeRootSkills(scope, cfgs) {
145
- if (scope === 'builtin')
146
- return [];
147
- const skillsRoot = scopeSkillsDir(scope);
148
- if (!skillsRoot || !pathExists(skillsRoot))
149
- return [];
150
- const configs = cfgs === undefined ? loadScopeConfigs() : cfgs;
151
- const skills = [];
152
- const skillFiles = walkFiles(skillsRoot, (n) => n === SKILL_ENTRY_FILE);
153
- for (const file of skillFiles) {
154
- const rel = relative(skillsRoot, dirname(file));
155
- const name = rel.split(sep).join('/');
156
- if (!name)
157
- continue;
158
- const parsed = readSkillFrontmatterSafe(file);
159
- if (parsed === null)
160
- continue;
161
- const { data } = parsed;
162
- const { enabled, disabledIn } = effectiveSkillEnabled(SCOPE_SKILL_PLUGIN, name, configs);
163
- skills.push({
164
- name,
165
- plugin: SCOPE_SKILL_PLUGIN,
166
- scope,
167
- path: file,
168
- pluginRoot: skillsRoot,
169
- frontmatter: data === null ? { name } : data,
170
- enabled,
171
- disabledIn,
172
- });
173
- }
174
- return skills.sort((a, b) => a.name.localeCompare(b.name));
175
- }
176
- export function listAllSkills(scopeFilter) {
177
- const plugins = scopeFilter ? listInstalledPlugins(scopeFilter) : listAllPlugins();
178
- const cfgs = loadScopeConfigs();
179
- const scopes = scopeFilter
180
- ? [scopeFilter]
181
- : [projectScopeRoot() ? 'project' : null, 'user'].filter(Boolean);
182
- return [
183
- ...scopes.flatMap((s) => listScopeRootSkills(s, cfgs)),
184
- ...plugins.filter((p) => p.enabled).flatMap((p) => listSkillsInPlugin(p, cfgs)),
185
- ];
186
- }
187
- function enumerateNeighborPool(skill) {
188
- if (skill.plugin === SCOPE_SKILL_PLUGIN) {
189
- return listScopeRootSkills(skill.scope);
190
- }
191
- const plugin = listInstalledPlugins(skill.scope).find((p) => p.name === skill.plugin);
192
- if (!plugin)
193
- return [];
194
- return listSkillsInPlugin(plugin);
195
- }
196
- export function listSkillSiblings(skill) {
197
- const pool = enumerateNeighborPool(skill);
198
- const segs = skill.name.split('/');
199
- const depth = segs.length;
200
- const parentPrefix = segs.slice(0, -1).join('/');
201
- return pool.filter((s) => {
202
- if (s.name === skill.name)
203
- return false;
204
- const sSegs = s.name.split('/');
205
- if (sSegs.length !== depth)
206
- return false;
207
- if (parentPrefix === '')
208
- return !s.name.includes('/');
209
- return s.name.startsWith(parentPrefix + '/');
210
- });
211
- }
212
- export function listSkillChildren(skill) {
213
- const pool = enumerateNeighborPool(skill);
214
- const prefix = skill.name + '/';
215
- return pool.filter((s) => {
216
- if (!s.name.startsWith(prefix))
217
- return false;
218
- const rest = s.name.slice(prefix.length);
219
- return rest.length > 0 && !rest.includes('/');
220
- });
221
- }
222
- export function resolveSkill(rawName, opts = {}) {
223
- const parsed = parseSkillQualifier(rawName);
224
- if (parsed.scope && opts.scope && parsed.scope !== opts.scope) {
225
- throw usage(`scope conflict: identifier "${rawName}" uses scope "${parsed.scope}" but --scope is "${opts.scope}"`);
226
- }
227
- const effectiveScope = opts.scope ?? parsed.scope;
228
- // Lookup-based disambiguation: if segments[0] matches an installed plugin, treat it as plugin.
229
- // Otherwise the entire segments array is the skill path under the scope-direct plugin.
230
- let pluginQualifier;
231
- let skillName;
232
- if (parsed.segments.length === 0) {
233
- throw usage(`skill name required`);
234
- }
235
- if (opts.pluginFilter !== undefined) {
236
- // Explicit plugin filter overrides disambiguation.
237
- pluginQualifier = opts.pluginFilter;
238
- skillName = parsed.segments.join('/');
239
- }
240
- else if (parsed.segments.length > 1) {
241
- const maybePlugin = parsed.segments[0];
242
- const pluginMatch = findPluginByName(maybePlugin, effectiveScope) ??
243
- (effectiveScope === undefined ? null : findPluginByName(maybePlugin));
244
- if (pluginMatch !== null) {
245
- pluginQualifier = maybePlugin;
246
- skillName = parsed.segments.slice(1).join('/');
247
- }
248
- else {
249
- pluginQualifier = undefined;
250
- skillName = parsed.segments.join('/');
251
- }
252
- }
253
- else {
254
- pluginQualifier = undefined;
255
- skillName = parsed.segments[0];
256
- }
257
- if (pluginQualifier && opts.pluginFilter && pluginQualifier !== opts.pluginFilter) {
258
- throw usage(`plugin conflict: identifier "${rawName}" uses plugin "${pluginQualifier}" but --plugin is "${opts.pluginFilter}"`);
259
- }
260
- const effectivePluginFilter = opts.pluginFilter ?? pluginQualifier;
261
- const direct = findSkillMatches(skillName, pluginQualifier, effectiveScope, effectivePluginFilter);
262
- if (direct.length > 0)
263
- return pickMatch(direct, skillName, pluginQualifier);
264
- // Leaf-name fallback: the caller supplied only the final path segment
265
- // (e.g. "cli-design" for "ai/interface/cli-design"). A direct path lookup
266
- // missed because the skill lives under a nested path. Match by last segment.
267
- const byLeaf = findSkillsByLeaf(skillName, pluginQualifier, effectiveScope, effectivePluginFilter);
268
- if (byLeaf.length === 1)
269
- return byLeaf[0];
270
- if (byLeaf.length > 1) {
271
- throw ambiguous(formatLeafAmbiguousMessage(skillName, byLeaf), {
272
- skill: skillName,
273
- candidates: byLeaf.map((m) => ({
274
- id: formatSkillId(m),
275
- plugin: m.plugin,
276
- scope: m.scope,
277
- path: m.path,
278
- })),
279
- next: 'Multiple skills share this leaf name. Re-run with one of the full paths in candidates.',
280
- });
281
- }
282
- throw notFound(formatNotFoundMessage(rawName, skillName, pluginQualifier), {
283
- skill: skillName,
284
- plugin: pluginQualifier,
285
- scope: parsed.scope,
286
- });
287
- }
288
- /** Canonical, unambiguous identifier for a skill. Scope-root skills are
289
- * qualified by scope; plugin skills by plugin name. */
290
- function formatSkillId(s) {
291
- return s.plugin === SCOPE_SKILL_PLUGIN ? `${s.scope}/${s.name}` : `${s.plugin}/${s.name}`;
292
- }
293
- /** Match skills whose final path segment equals `leaf`. Only meaningful when
294
- * `leaf` is a bare segment (no slash) — a slashed query can never equal a
295
- * single segment, so this returns empty and the caller falls through. */
296
- function findSkillsByLeaf(leaf, pluginQualifier, scope, pluginFilter) {
297
- if (leaf.includes('/'))
298
- return [];
299
- let all;
300
- try {
301
- all = scope ? listAllSkills(scope) : listAllSkills();
302
- }
303
- catch {
304
- return [];
305
- }
306
- return all.filter((s) => {
307
- if ((s.name.split('/').pop() ?? s.name) !== leaf)
308
- return false;
309
- if (pluginQualifier && s.plugin !== pluginQualifier)
310
- return false;
311
- if (pluginFilter && s.plugin !== pluginFilter)
312
- return false;
313
- return true;
314
- });
315
- }
316
- function formatLeafAmbiguousMessage(leaf, matches) {
317
- const ids = matches.map(formatSkillId).join(', ');
318
- return `ambiguous skill: ${leaf} matches multiple skills: ${ids}`;
319
- }
320
- function findSkillMatches(name, pluginQualifier, scope, pluginFilter) {
321
- const plugins = scope ? listInstalledPlugins(scope) : listAllPlugins();
322
- const enabledPlugins = plugins.filter((p) => p.enabled);
323
- const cfgs = loadScopeConfigs();
324
- const matches = [];
325
- // Scope-root skills first — they're the user's own captured knowledge.
326
- if (!pluginFilter &&
327
- (pluginQualifier === undefined || pluginQualifier === SCOPE_SKILL_PLUGIN)) {
328
- const scopes = scope
329
- ? [scope]
330
- : [projectScopeRoot() ? 'project' : null, 'user'].filter(Boolean);
331
- for (const s of scopes) {
332
- const skillsRoot = scopeSkillsDir(s);
333
- if (!skillsRoot)
334
- continue;
335
- const skillPath = join(skillsRoot, ...name.split('/'), SKILL_ENTRY_FILE);
336
- if (!pathExists(skillPath))
337
- continue;
338
- const source = readText(skillPath);
339
- const { data } = parseFrontmatter(source);
340
- const { enabled, disabledIn } = effectiveSkillEnabled(SCOPE_SKILL_PLUGIN, name, cfgs);
341
- matches.push({
342
- name,
343
- plugin: SCOPE_SKILL_PLUGIN,
344
- scope: s,
345
- path: skillPath,
346
- pluginRoot: skillsRoot,
347
- frontmatter: data === null ? { name } : data,
348
- enabled,
349
- disabledIn,
350
- });
351
- }
352
- }
353
- const ordered = orderPluginsByResolution(enabledPlugins);
354
- for (const plugin of ordered) {
355
- if (pluginQualifier && plugin.name !== pluginQualifier)
356
- continue;
357
- if (pluginFilter && plugin.name !== pluginFilter)
358
- continue;
359
- const skillPath = join(plugin.root, SKILLS_DIR, ...name.split('/'), SKILL_ENTRY_FILE);
360
- if (!pathExists(skillPath))
361
- continue;
362
- const source = readText(skillPath);
363
- const { data } = parseFrontmatter(source);
364
- const { enabled, disabledIn } = effectiveSkillEnabled(plugin.name, name, cfgs);
365
- matches.push({
366
- name,
367
- plugin: plugin.name,
368
- scope: plugin.scope,
369
- path: skillPath,
370
- pluginRoot: plugin.root,
371
- frontmatter: data === null ? { name } : data,
372
- enabled,
373
- disabledIn,
374
- });
375
- }
376
- return matches;
377
- }
378
- function pickMatch(matches, name, pluginQualifier) {
379
- if (matches.length === 1)
380
- return matches[0];
381
- const sameScopeAndPlugin = matches.every((m) => m.plugin === matches[0].plugin && m.scope === matches[0].scope);
382
- if (sameScopeAndPlugin)
383
- return matches[0];
384
- if (!pluginQualifier)
385
- return matches[0];
386
- throw ambiguous(`ambiguous skill: ${name}`, {
387
- skill: name,
388
- candidates: matches.map((m) => ({
389
- plugin: m.plugin,
390
- scope: m.scope,
391
- path: m.path,
392
- })),
393
- });
394
- }
395
- function formatNotFoundMessage(rawName, skillName, pluginQualifier) {
396
- const suggestions = suggestSkills(skillName, pluginQualifier);
397
- const lines = [`skill not found: ${rawName}`];
398
- lines.push(' expected forms: <name>, <plugin>/<name>, <scope>/<name>, <scope>/<plugin>/<name>');
399
- if (suggestions.length > 0) {
400
- const formatted = suggestions
401
- .map((s) => s.plugin === SCOPE_SKILL_PLUGIN ? s.name : `${s.plugin}/${s.name}`)
402
- .slice(0, 3);
403
- lines.push(` did you mean: ${formatted.join(', ')}`);
404
- }
405
- else {
406
- lines.push(' run `crtr skill find list` or `crtr skill find search <query>` to discover skills');
407
- }
408
- return lines.join('\n');
409
- }
410
- function suggestSkills(name, plugin) {
411
- let all;
412
- try {
413
- all = listAllSkills();
414
- }
415
- catch {
416
- return [];
417
- }
418
- const target = name.toLowerCase();
419
- const targetBase = target.split('/').pop() ?? target;
420
- const targetPluginGuess = target.includes('/') ? target.split('/')[0] : undefined;
421
- const exactName = all.filter((s) => s.name.toLowerCase() === target);
422
- if (exactName.length > 0)
423
- return exactName;
424
- const exactBase = all.filter((s) => {
425
- const sBase = s.name.toLowerCase().split('/').pop() ?? s.name.toLowerCase();
426
- return sBase === targetBase;
427
- });
428
- if (exactBase.length > 0)
429
- return exactBase;
430
- const scored = all
431
- .map((s) => {
432
- const sName = s.name.toLowerCase();
433
- const sBase = sName.split('/').pop() ?? sName;
434
- const sPlugin = s.plugin.toLowerCase();
435
- let score = 0;
436
- if (plugin !== undefined && sPlugin === plugin.toLowerCase())
437
- score += 5;
438
- if (targetPluginGuess !== undefined && sPlugin === targetPluginGuess)
439
- score += 5;
440
- if (sName.includes(target) || target.includes(sName))
441
- score += 4;
442
- if (sBase.includes(targetBase) || targetBase.includes(sBase))
443
- score += 3;
444
- if (editDistance(sBase, targetBase) <= 2)
445
- score += 4;
446
- return { skill: s, score };
447
- })
448
- .filter((x) => x.score > 0)
449
- .sort((a, b) => b.score - a.score);
450
- return scored.slice(0, 3).map((x) => x.skill);
451
- }
452
- function editDistance(a, b) {
453
- if (a === b)
454
- return 0;
455
- if (a.length === 0)
456
- return b.length;
457
- if (b.length === 0)
458
- return a.length;
459
- const prev = new Array(b.length + 1);
460
- const curr = new Array(b.length + 1);
461
- for (let j = 0; j <= b.length; j++)
462
- prev[j] = j;
463
- for (let i = 1; i <= a.length; i++) {
464
- curr[0] = i;
465
- for (let j = 1; j <= b.length; j++) {
466
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
467
- curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
468
- }
469
- for (let j = 0; j <= b.length; j++)
470
- prev[j] = curr[j];
471
- }
472
- return prev[b.length];
473
- }
474
57
  const SCOPE_QUALIFIERS = new Set(['user', 'project']);
475
58
  // Accepted identifier forms:
476
59
  // <name> — bare name; scope-root first, then plugins
477
60
  // <plugin>/<name> — explicit plugin (plugin may contain slashes)
478
61
  // <scope>/<name> — scope-root in a specific scope
479
- // <scope>/<plugin>/<name> — fully qualified; plugin-vs-path disambiguation is lookup-based in resolveSkill
62
+ // <scope>/<plugin>/<name> — fully qualified
480
63
  export function parseSkillQualifier(raw) {
481
64
  if (raw.includes(':')) {
482
65
  const suggested = raw.replace(/:/g, '/');
@@ -495,21 +78,6 @@ export function parseSkillQualifier(raw) {
495
78
  }
496
79
  return { segments };
497
80
  }
498
- function orderPluginsByResolution(plugins) {
499
- const score = (p) => {
500
- if (p.scope === 'builtin')
501
- return 4;
502
- const fromMarketplace = Boolean(p.sourceMarketplace);
503
- if (p.scope === 'project' && !fromMarketplace)
504
- return 0;
505
- if (p.scope === 'user' && !fromMarketplace)
506
- return 1;
507
- if (p.scope === 'project' && fromMarketplace)
508
- return 2;
509
- return 3;
510
- };
511
- return [...plugins].sort((a, b) => score(a) - score(b));
512
- }
513
81
  export function listInstalledMarketplaces(scope) {
514
82
  const dir = marketplacesDir(scope);
515
83
  if (!dir || !pathExists(dir))
@@ -555,114 +123,6 @@ export function findMarketplaceByName(name, scope) {
555
123
  }
556
124
  return null;
557
125
  }
558
- export function resolveCategory(name, opts = {}) {
559
- const parsed = parseSkillQualifier(name);
560
- if (parsed.segments.length === 0)
561
- return null;
562
- const effectiveScope = opts.scope ?? parsed.scope;
563
- let pluginQualifier;
564
- let subpath;
565
- if (opts.pluginFilter !== undefined) {
566
- pluginQualifier = opts.pluginFilter;
567
- const sub = parsed.segments.join('/');
568
- subpath = sub || undefined;
569
- }
570
- else if (parsed.segments.length > 1) {
571
- const maybePlugin = parsed.segments[0];
572
- const pluginMatch = findPluginByName(maybePlugin, effectiveScope) ??
573
- (effectiveScope === undefined ? null : findPluginByName(maybePlugin));
574
- if (pluginMatch !== null) {
575
- pluginQualifier = maybePlugin;
576
- subpath = parsed.segments.slice(1).join('/');
577
- }
578
- else {
579
- pluginQualifier = undefined;
580
- subpath = parsed.segments.join('/');
581
- }
582
- }
583
- else {
584
- const maybePlugin = parsed.segments[0];
585
- const pluginMatch = findPluginByName(maybePlugin, effectiveScope) ??
586
- (effectiveScope === undefined ? null : findPluginByName(maybePlugin));
587
- if (pluginMatch !== null) {
588
- pluginQualifier = maybePlugin;
589
- subpath = undefined;
590
- }
591
- else {
592
- pluginQualifier = undefined;
593
- subpath = maybePlugin;
594
- }
595
- }
596
- let skills;
597
- let dir;
598
- let id;
599
- let resolvedScope;
600
- if (pluginQualifier !== undefined) {
601
- const plugin = findPluginByName(pluginQualifier, effectiveScope) ??
602
- (effectiveScope === undefined ? null : findPluginByName(pluginQualifier));
603
- if (!plugin)
604
- return null;
605
- resolvedScope = plugin.scope;
606
- const allPluginSkills = listSkillsInPlugin(plugin);
607
- if (subpath === undefined) {
608
- skills = allPluginSkills;
609
- dir = join(plugin.root, SKILLS_DIR);
610
- id = pluginQualifier;
611
- }
612
- else {
613
- skills = allPluginSkills.filter((s) => s.name.startsWith(subpath + '/'));
614
- dir = join(plugin.root, SKILLS_DIR, ...subpath.split('/'));
615
- id = `${pluginQualifier}/${subpath}`;
616
- }
617
- }
618
- else if (subpath !== undefined) {
619
- const scope = effectiveScope ?? 'user';
620
- resolvedScope = scope;
621
- const skillsRoot = scopeSkillsDir(scope);
622
- if (!skillsRoot)
623
- return null;
624
- const allScopeSkills = listScopeRootSkills(scope);
625
- skills = allScopeSkills.filter((s) => s.name.startsWith(subpath + '/'));
626
- dir = join(skillsRoot, ...subpath.split('/'));
627
- id = `${scope}/${subpath}`;
628
- }
629
- else {
630
- return null;
631
- }
632
- if (skills.length === 0)
633
- return null;
634
- const indexMd = join(dir, 'index.md');
635
- const indexPath = pathExists(indexMd) ? indexMd : undefined;
636
- return { id, plugin: pluginQualifier, scope: resolvedScope, dir, indexPath, skills };
637
- }
638
- export function buildCategoryIndex(cat) {
639
- const lines = [];
640
- lines.push(`# ${cat.id} — ${cat.skills.length} skills`);
641
- lines.push('');
642
- if (cat.indexPath !== undefined) {
643
- const authored = readTextIfExists(cat.indexPath);
644
- if (authored !== null) {
645
- const { body } = parseFrontmatter(authored);
646
- const trimmed = body.trim();
647
- if (trimmed.length > 0) {
648
- lines.push(trimmed);
649
- lines.push('');
650
- }
651
- }
652
- }
653
- lines.push('## Skills');
654
- const sorted = [...cat.skills].sort((a, b) => a.name.localeCompare(b.name));
655
- for (const skill of sorted) {
656
- const fullId = skill.plugin === SCOPE_SKILL_PLUGIN
657
- ? `${skill.scope}/${skill.name}`
658
- : `${skill.plugin}/${skill.name}`;
659
- const desc = skill.frontmatter.description ?? '(no description)';
660
- lines.push(`- \`${fullId}\` — ${desc}`);
661
- }
662
- lines.push('');
663
- lines.push('Read one with `crtr memory read <full-id>`. Search with `crtr memory find "<topic>"`.');
664
- return lines.join('\n');
665
- }
666
126
  export function scopeRootsLabel() {
667
127
  const proj = projectScopeRoot();
668
128
  return proj ? `project=${proj}, user=${userScopeRoot()}` : `user=${userScopeRoot()}`;
@@ -56,6 +56,6 @@ export declare function buildWakeBearings(origin: WakeOrigin): string;
56
56
  * any copied-in persona) followed by the `<crtr-context>` bearings block. Base
57
57
  * framing rides for EVERY node; the across-cycles context-dir note is added
58
58
  * ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
59
- * resumes from. The `## References` block (substrate reference docs +
60
- * node-local docs) replaces the old `<memory>` block. */
59
+ * resumes from. The `## References` block carries the substrate reference docs
60
+ * + node-local docs. */
61
61
  export declare function buildContextBearings(nodeId: string): string;
@@ -145,8 +145,8 @@ export function buildWakeBearings(origin) {
145
145
  * any copied-in persona) followed by the `<crtr-context>` bearings block. Base
146
146
  * framing rides for EVERY node; the across-cycles context-dir note is added
147
147
  * ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
148
- * resumes from. The `## References` block (substrate reference docs +
149
- * node-local docs) replaces the old `<memory>` block. */
148
+ * resumes from. The `## References` block carries the substrate reference docs
149
+ * + node-local docs. */
150
150
  export function buildContextBearings(nodeId) {
151
151
  const identity = buildIdentityAssertion(nodeId);
152
152
  const dir = contextDir(nodeId);
@@ -21,9 +21,7 @@
21
21
  // the caller delivers the guidance, then commits the ack.
22
22
  import { getNode, updateNode } from '../canvas/index.js';
23
23
  import { loadKernel, loadPersona, loadLifecycleFragment } from '../personas/index.js';
24
- import { resolveSkill } from '../resolver.js';
25
- import { readText } from '../fs-utils.js';
26
- import { parseFrontmatter } from '../frontmatter.js';
24
+ import { resolveMemoryDoc } from '../memory-resolver.js';
27
25
  import { readRoadmap, roadmapPath } from './roadmap.js';
28
26
  import { orchestratorContextNote } from './bearings.js';
29
27
  // ---------------------------------------------------------------------------
@@ -42,12 +40,12 @@ function scalarToString(v) {
42
40
  return String(v);
43
41
  return null;
44
42
  }
45
- /** Load a skill's body text by name, or null if it can't be resolved. Used to
46
- * inline a kind's roadmap-shaping skill into the orchestration guidance. */
43
+ /** Load a substrate doc's body text by name, or null if it can't be resolved.
44
+ * Used to inline a kind's roadmap-shaping skill into the orchestration
45
+ * guidance — the skill now lives as a substrate memory doc. */
47
46
  function loadSkillBody(name) {
48
47
  try {
49
- const skill = resolveSkill(name, {});
50
- return parseFrontmatter(readText(skill.path)).body.trim();
48
+ return resolveMemoryDoc(name).body.trim();
51
49
  }
52
50
  catch {
53
51
  return null;
@@ -1,5 +1,4 @@
1
- import type { Scope } from '../types.js';
2
- export declare function builtinSkillsRoot(): string;
1
+ import type { InstalledPlugin, Scope } from '../types.js';
3
2
  export declare function builtinViewsRoot(): string;
4
3
  export declare function builtinMemoryRoot(): string;
5
4
  export declare function userScopeRoot(): string;
@@ -12,10 +11,13 @@ export declare function pluginsDir(scope: Scope): string | null;
12
11
  export declare function marketplacesDir(scope: Scope): string | null;
13
12
  export declare function scopeSkillsDir(scope: Scope): string | null;
14
13
  /** Where substrate memory documents live per scope. Builtin memory is the
15
- * special case — its own package dir (builtinMemoryRoot, a sibling of
16
- * builtin-skills), NOT scopeRoot('builtin')/memory — mirroring viewsDir's
17
- * builtin handling. User and project memory live at scopeRoot(scope)/memory. */
14
+ * special case — its own package dir (builtinMemoryRoot), since the builtin
15
+ * scope has no scopeRoot — mirroring viewsDir's builtin handling. User and
16
+ * project memory live at scopeRoot(scope)/memory. */
18
17
  export declare function scopeMemoryDir(scope: Scope): string | null;
18
+ /** Where a plugin's substrate memory documents live: `<plugin.root>/memory`.
19
+ * The resolver mounts these under the virtual `<pluginName>/` namespace. */
20
+ export declare function pluginMemoryDir(plugin: InstalledPlugin): string;
19
21
  /** Where view definition dirs live per scope. Builtin views sit directly under
20
22
  * builtinViewsRoot() (no `views/` segment — they ARE the builtin views dir),
21
23
  * matching the loader's `<root>/<name>/view.mjs` resolution. */