@crouton-kit/crouter 0.3.37 → 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 +1 -0
- package/dist/{builtin-personas/runtime-base.md → builtin-memory/00-runtime-base.md} +7 -0
- 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-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/attach-cmd.js +416 -416
- 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.js +40 -11
- 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/sys/config.js +6 -21
- package/dist/commands/sys/prompt-review.js +90 -17
- package/dist/core/canvas/canvas.js +10 -9
- package/dist/core/canvas/db.js +11 -0
- package/dist/core/canvas/history.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/memory-resolver.d.ts +26 -15
- package/dist/core/memory-resolver.js +158 -40
- 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/runtime/bearings.d.ts +9 -0
- package/dist/core/runtime/bearings.js +31 -0
- package/dist/core/runtime/front-door.js +9 -2
- 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/spawn.d.ts +13 -0
- package/dist/core/runtime/spawn.js +25 -0
- 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/pi-extensions/canvas-doc-substrate.js +2 -3
- package/dist/types.d.ts +57 -1
- package/dist/types.js +83 -1
- package/dist/web-client/assets/{index-MSKSWwV0.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/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/core/config.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { CONFIG_FILE, STATE_FILE, defaultScopeConfig, defaultScopeState, defaultCanvasNavConfig, defaultModelLaddersConfig } from '../types.js';
|
|
2
|
+
import { CONFIG_FILE, STATE_FILE, defaultScopeConfig, defaultScopeState, defaultCanvasNavConfig, defaultModelLaddersConfig, defaultKindsConfig } from '../types.js';
|
|
3
3
|
import { readJsonIfExists, writeJson, ensureDir } from './fs-utils.js';
|
|
4
|
-
import { scopeRoot, requireScopeRoot } from './scope.js';
|
|
4
|
+
import { scopeRoot, requireScopeRoot, findProjectScopeRoots } from './scope.js';
|
|
5
|
+
import { profileRoot } from './profiles/manifest.js';
|
|
5
6
|
function configPathFor(root) {
|
|
6
7
|
return join(root, CONFIG_FILE);
|
|
7
8
|
}
|
|
@@ -150,14 +151,15 @@ function mergeCanvasNav(raw) {
|
|
|
150
151
|
graphBinds: mergeBinds(defaults.graphBinds, r.graphBinds, normalizeGraphBind),
|
|
151
152
|
};
|
|
152
153
|
}
|
|
153
|
-
function mergeModelLadders(raw) {
|
|
154
|
-
const defaults =
|
|
154
|
+
function mergeModelLadders(raw, base = defaultModelLaddersConfig()) {
|
|
155
|
+
const defaults = base;
|
|
155
156
|
if (raw === null || typeof raw !== 'object')
|
|
156
157
|
return defaults;
|
|
157
158
|
const r = raw;
|
|
158
159
|
const out = {
|
|
159
160
|
anthropic: { ...defaults.anthropic },
|
|
160
161
|
openai: { ...defaults.openai },
|
|
162
|
+
...(defaults.defaultProvider !== undefined ? { defaultProvider: defaults.defaultProvider } : {}),
|
|
161
163
|
};
|
|
162
164
|
if (r.defaultProvider === 'anthropic' || r.defaultProvider === 'openai') {
|
|
163
165
|
out.defaultProvider = r.defaultProvider;
|
|
@@ -174,13 +176,42 @@ function mergeModelLadders(raw) {
|
|
|
174
176
|
}
|
|
175
177
|
return out;
|
|
176
178
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
+
/** Validate one raw `kinds.<name>` entry into a `KindConfig`, or drop it
|
|
180
|
+
* (return null) rather than throwing — an invalid kind entry in config.json
|
|
181
|
+
* must not break config parsing for every other kind. `whenToUse` is the
|
|
182
|
+
* only required field. */
|
|
183
|
+
function normalizeKindEntry(raw) {
|
|
179
184
|
if (raw === null || typeof raw !== 'object')
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
185
|
+
return null;
|
|
186
|
+
const r = raw;
|
|
187
|
+
if (typeof r.whenToUse !== 'string' || r.whenToUse.trim() === '')
|
|
188
|
+
return null;
|
|
189
|
+
const out = { whenToUse: r.whenToUse };
|
|
190
|
+
if (typeof r.model === 'string' && r.model.trim() !== '')
|
|
191
|
+
out.model = r.model;
|
|
192
|
+
if (typeof r.orchestratorModel === 'string' && r.orchestratorModel.trim() !== '')
|
|
193
|
+
out.orchestratorModel = r.orchestratorModel;
|
|
194
|
+
const isStringArray = (v) => Array.isArray(v) && v.every((item) => typeof item === 'string');
|
|
195
|
+
if (isStringArray(r.tools))
|
|
196
|
+
out.tools = r.tools;
|
|
197
|
+
if (isStringArray(r.extensions))
|
|
198
|
+
out.extensions = r.extensions;
|
|
199
|
+
if (isStringArray(r.availableTo))
|
|
200
|
+
out.availableTo = r.availableTo;
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
/** Merge a raw `kinds` block over `base` (defaulting to the builtin registry):
|
|
204
|
+
* each valid entry adds or shadows a kind by name; invalid entries are
|
|
205
|
+
* dropped, never thrown. Mirrors `mergeModelLadders`'s layer-over-defaults
|
|
206
|
+
* shape so a user/project config.json can add or override a single kind
|
|
207
|
+
* without restating the whole registry. */
|
|
208
|
+
function mergeKinds(raw, base = defaultKindsConfig()) {
|
|
209
|
+
const out = { ...base };
|
|
210
|
+
if (raw !== null && typeof raw === 'object') {
|
|
211
|
+
for (const [kind, value] of Object.entries(raw)) {
|
|
212
|
+
const normalized = normalizeKindEntry(value);
|
|
213
|
+
if (normalized !== null)
|
|
214
|
+
out[kind] = normalized;
|
|
184
215
|
}
|
|
185
216
|
}
|
|
186
217
|
return out;
|
|
@@ -215,8 +246,120 @@ function mergeConfig(partial) {
|
|
|
215
246
|
: defaults.max_panes_per_window;
|
|
216
247
|
const canvasNav = mergeCanvasNav(partial.canvasNav);
|
|
217
248
|
const modelLadders = mergeModelLadders(partial.modelLadders);
|
|
218
|
-
const
|
|
219
|
-
return { schema_version, marketplaces, plugins, auto_update, max_panes_per_window, canvasNav, modelLadders,
|
|
249
|
+
const kinds = mergeKinds(partial.kinds);
|
|
250
|
+
return { schema_version, marketplaces, plugins, auto_update, max_panes_per_window, canvasNav, modelLadders, kinds };
|
|
251
|
+
}
|
|
252
|
+
/** Raw (un-defaulted) partial config for one scope, or null if the scope has
|
|
253
|
+
* no root or no config.json. Used by `readMergedLaunchConfig` to layer
|
|
254
|
+
* scopes onto each other WITHOUT each scope's own default-fill masking a
|
|
255
|
+
* lower-precedence scope's real customization (see that function's comment
|
|
256
|
+
* for why `readConfig(scope)`, which already defaults-fills, is unusable
|
|
257
|
+
* for cross-scope layering). */
|
|
258
|
+
function readRawScopeConfig(scope) {
|
|
259
|
+
const root = scopeRoot(scope);
|
|
260
|
+
if (!root)
|
|
261
|
+
return null;
|
|
262
|
+
return readJsonIfExists(configPathFor(root));
|
|
263
|
+
}
|
|
264
|
+
/** Raw partial config at `<profileRoot(profileId)>/config.json`, or null when
|
|
265
|
+
* no profile is selected, the profile id no longer resolves (deleted/
|
|
266
|
+
* invalid), or it has no config.json. `profileRoot` centralizes the id-shape
|
|
267
|
+
* + containment checks (never a raw path join), and any resolution failure
|
|
268
|
+
* is swallowed here — launch config resolution is a hot path that must never
|
|
269
|
+
* throw on a stale `CRTR_PROFILE_ID`. */
|
|
270
|
+
function readRawProfileConfig(profileId) {
|
|
271
|
+
if (profileId === null || profileId === '')
|
|
272
|
+
return null;
|
|
273
|
+
try {
|
|
274
|
+
return readJsonIfExists(configPathFor(profileRoot(profileId)));
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/** Raw partial configs for the whole project-scope STACK (`findProjectScopeRoots`
|
|
281
|
+
* — every ancestor `.crouter/`, widened by a selected profile's `projects`),
|
|
282
|
+
* ordered FARTHEST-first so `readMergedLaunchConfig` can layer them on last
|
|
283
|
+
* and have the NEAREST root win — mirroring the resolver's nearest-project-
|
|
284
|
+
* strongest replacement precedence. */
|
|
285
|
+
function readRawProjectScopeConfigs() {
|
|
286
|
+
const nearestFirst = findProjectScopeRoots();
|
|
287
|
+
const farthestFirst = [...nearestFirst].reverse();
|
|
288
|
+
const out = [];
|
|
289
|
+
for (const root of farthestFirst) {
|
|
290
|
+
const raw = readJsonIfExists(configPathFor(root));
|
|
291
|
+
if (raw !== null)
|
|
292
|
+
out.push(raw);
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
}
|
|
296
|
+
/** Merge launch knobs (`kinds`, `modelLadders`) across scopes in
|
|
297
|
+
* project stack > profile > user > builtin precedence — the same precedence
|
|
298
|
+
* order used for memory resolution. A kind or ladder cell declared at a
|
|
299
|
+
* more-specific scope shadows the same key from a less-specific scope; the
|
|
300
|
+
* project STACK (`findProjectScopeRoots` — every ancestor `.crouter/`,
|
|
301
|
+
* widened by a selected profile's `projects`) layers nearest-root-strongest;
|
|
302
|
+
* a key no scope declares falls through to the builtin default registry
|
|
303
|
+
* (`defaultScopeConfig().kinds` / `.modelLadders`).
|
|
304
|
+
*
|
|
305
|
+
* Layers RAW (un-defaulted) partial config per scope, not `readConfig(scope)`
|
|
306
|
+
* — `readConfig` already fills in every default kind for a scope that
|
|
307
|
+
* customizes even one, so naively overlaying two already-defaulted
|
|
308
|
+
* `ScopeConfig.kinds` objects would let an untouched project-scope kind
|
|
309
|
+
* (silently defaulted) clobber a real user-scope customization of that same
|
|
310
|
+
* kind. Layering the raw partials avoids that.
|
|
311
|
+
*
|
|
312
|
+
* Callers (launch, kind registry) go through this function rather than
|
|
313
|
+
* `readConfig` directly, so `ScopeConfig.kinds`/`modelLadders`/tools/
|
|
314
|
+
* extensions/`availableTo` all honor the same profile + multi-project
|
|
315
|
+
* precedence. */
|
|
316
|
+
export function readMergedLaunchConfig() {
|
|
317
|
+
const defaults = defaultScopeConfig();
|
|
318
|
+
const userRaw = readRawScopeConfig('user');
|
|
319
|
+
const profileRaw = readRawProfileConfig(process.env['CRTR_PROFILE_ID'] || null);
|
|
320
|
+
const projectRawsFarthestFirst = readRawProjectScopeConfigs();
|
|
321
|
+
let kinds = mergeKinds(userRaw?.kinds, defaults.kinds);
|
|
322
|
+
let modelLadders = mergeModelLadders(userRaw?.modelLadders, defaults.modelLadders);
|
|
323
|
+
if (profileRaw !== null) {
|
|
324
|
+
kinds = mergeKinds(profileRaw.kinds, kinds);
|
|
325
|
+
modelLadders = mergeModelLadders(profileRaw.modelLadders, modelLadders);
|
|
326
|
+
}
|
|
327
|
+
for (const raw of projectRawsFarthestFirst) {
|
|
328
|
+
kinds = mergeKinds(raw.kinds, kinds);
|
|
329
|
+
modelLadders = mergeModelLadders(raw.modelLadders, modelLadders);
|
|
330
|
+
}
|
|
331
|
+
return { kinds, modelLadders };
|
|
332
|
+
}
|
|
333
|
+
/** The effective `KindConfig` for one full kind string (top-level, e.g.
|
|
334
|
+
* `developer`, or sub-kind, e.g. `plan/reviewers/security`), across
|
|
335
|
+
* project > user > builtin precedence. Returns `undefined` for a kind no
|
|
336
|
+
* scope registers — existence is deliberately NOT validated here (kind
|
|
337
|
+
* existence/launch-menu enumeration is a Phase 3 caller concern); this only
|
|
338
|
+
* resolves the config for a kind the caller already knows about. */
|
|
339
|
+
export function resolveKindConfig(kind) {
|
|
340
|
+
return readMergedLaunchConfig().kinds[kind];
|
|
341
|
+
}
|
|
342
|
+
/** The sub-kinds available to spawn FROM a given top-level kind — every
|
|
343
|
+
* registered sub-kind (full path contains `/`) whose `availableTo` (default:
|
|
344
|
+
* its own top-level ancestor, e.g. `plan/reviewers/security` defaults to
|
|
345
|
+
* `['plan']`) includes `kind` or `'*'`. The single source both `sys
|
|
346
|
+
* prompt-review --list`'s `subPersonas` metadata and the live sub-persona
|
|
347
|
+
* spawn-menu splice (`core/substrate/render.ts`) read, so the two can never
|
|
348
|
+
* drift apart. Sorted by full kind name for stable rendering. */
|
|
349
|
+
export function subKindsAvailableTo(kind) {
|
|
350
|
+
const registry = readMergedLaunchConfig().kinds;
|
|
351
|
+
const out = [];
|
|
352
|
+
for (const [full, cfg] of Object.entries(registry)) {
|
|
353
|
+
const slash = full.indexOf('/');
|
|
354
|
+
if (slash === -1)
|
|
355
|
+
continue;
|
|
356
|
+
const topAncestor = full.slice(0, slash);
|
|
357
|
+
const availableTo = cfg.availableTo ?? [topAncestor];
|
|
358
|
+
if (!(availableTo.includes('*') || availableTo.includes(kind)))
|
|
359
|
+
continue;
|
|
360
|
+
out.push({ kind: full, whenToUse: cfg.whenToUse });
|
|
361
|
+
}
|
|
362
|
+
return out.sort((a, b) => a.kind.localeCompare(b.kind));
|
|
220
363
|
}
|
|
221
364
|
export function updateConfig(scope, mutate) {
|
|
222
365
|
const cfg = readConfig(scope);
|
|
@@ -2,18 +2,20 @@ import type { InstalledPlugin, Scope } from '../types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Thin memory-document resolver for the document substrate. Resolution is scope
|
|
4
4
|
* + leaf/path only: qualifier parse → scope precedence → direct path lookup →
|
|
5
|
-
* leaf-name fallback. The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* leaf-name fallback. The memory scopes resolve in precedence order project
|
|
6
|
+
* stack > profile > user > builtin. It returns the raw parsed frontmatter +
|
|
7
|
+
* body; it does NOT interpret the schema, kind, gate, or rungs — that is the
|
|
8
|
+
* schema/gate layer's job (callers filter by `frontmatter.kind`). Project
|
|
9
|
+
* resolution is a nearest-first stack of every ancestor `.crouter/` (widened by
|
|
10
|
+
* a selected profile's `projects` manifest entries — see `findProjectScopeRoots`
|
|
11
|
+
* in scope.ts), profile is the selected profile's own singleton `memory/` store
|
|
12
|
+
* (from `CRTR_PROFILE_ID`), and user/builtin remain singleton scopes.
|
|
11
13
|
*/
|
|
12
14
|
export interface MemoryDoc {
|
|
13
15
|
/** Path-derived identity: the doc's path under the scope's memory/ root, no
|
|
14
16
|
* extension, slash-separated — e.g. memory/taste/foo.md → "taste/foo". */
|
|
15
17
|
name: string;
|
|
16
|
-
scope:
|
|
18
|
+
scope: MemoryScope;
|
|
17
19
|
/** Absolute path to the resolved .md file. */
|
|
18
20
|
path: string;
|
|
19
21
|
/** Raw, uncoerced frontmatter record (null when the doc has no frontmatter). */
|
|
@@ -21,26 +23,35 @@ export interface MemoryDoc {
|
|
|
21
23
|
/** Document body, with the frontmatter block stripped. */
|
|
22
24
|
body: string;
|
|
23
25
|
}
|
|
26
|
+
/** The memory-only scope union: the global `Scope` (`user|project|builtin`)
|
|
27
|
+
* plus `profile`. Confined to the memory resolver/list/read/write/render
|
|
28
|
+
* paths — deliberately NOT folded into the global `Scope` union, because that
|
|
29
|
+
* would leak `profile` into config, plugin, marketplace, view, and doctor/
|
|
30
|
+
* sys-config surfaces the spec does not extend. */
|
|
31
|
+
export type MemoryScope = Scope | 'profile';
|
|
24
32
|
export interface MemoryResolutionOpts {
|
|
25
33
|
/** Restrict resolution to a single scope. Conflicts with a scope prefix on
|
|
26
34
|
* the identifier (e.g. `user/foo` with scope=project) throw. */
|
|
27
|
-
scope?:
|
|
35
|
+
scope?: MemoryScope;
|
|
28
36
|
}
|
|
29
37
|
/** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
|
|
30
38
|
export declare function memoryDocId(doc: MemoryDoc): string;
|
|
31
39
|
/** All native memory docs for a scope. Project scope is a nearest-first stack of
|
|
32
|
-
* every ancestor `.crouter/memory
|
|
33
|
-
|
|
40
|
+
* every ancestor `.crouter/memory/` (widened by a selected profile's project
|
|
41
|
+
* stack); profile is the selected profile's own singleton store, resolved
|
|
42
|
+
* through `loadProfileManifest`; user and builtin are singleton stores. */
|
|
43
|
+
export declare function listMemoryDocs(scope: MemoryScope, quiet?: boolean): MemoryDoc[];
|
|
34
44
|
/** All of one plugin's substrate docs, mounted under the virtual `<pluginName>/`
|
|
35
45
|
* namespace. Walks `pluginMemoryDir(plugin)` recursively for *.md, deriving each
|
|
36
46
|
* doc's name exactly as `listMemoryDocs` does (path-relative, no extension,
|
|
37
47
|
* slash-separated) then prefixing the plugin name. Builtin has no plugins. */
|
|
38
|
-
export declare function listPluginMemoryDocs(plugin: InstalledPlugin, scope:
|
|
48
|
+
export declare function listPluginMemoryDocs(plugin: InstalledPlugin, scope: MemoryScope, quiet?: boolean): MemoryDoc[];
|
|
39
49
|
/** All memory docs across the resolved sources, in precedence order: each
|
|
40
|
-
* ancestor project `.crouter/` from nearest to farthest, then
|
|
41
|
-
*
|
|
42
|
-
* docs, so native wins on the
|
|
43
|
-
|
|
50
|
+
* ancestor project `.crouter/` from nearest to farthest, then the selected
|
|
51
|
+
* profile's memory (if any), then user, then builtin. Within each source,
|
|
52
|
+
* native docs are emitted before enabled-plugin docs, so native wins on the
|
|
53
|
+
* caller's first-wins dedup. */
|
|
54
|
+
export declare function listAllMemoryDocs(scope?: MemoryScope, quiet?: boolean): MemoryDoc[];
|
|
44
55
|
/**
|
|
45
56
|
* Resolve a path-derived name to a single memory document.
|
|
46
57
|
*
|
|
@@ -6,26 +6,41 @@ import { listInstalledPlugins, listInstalledPluginsInRoot, parseSkillQualifier }
|
|
|
6
6
|
import { ambiguous, notFound, usage } from './errors.js';
|
|
7
7
|
import { warn } from './output.js';
|
|
8
8
|
import { pluginMemoryDir, projectScopeRoot, projectScopeRoots, scopeMemoryDir } from './scope.js';
|
|
9
|
+
import { normalizeDocName, normalizeNameSegment } from './substrate/schema.js';
|
|
10
|
+
import { loadProfileManifest, profileMemoryDir } from './profiles/manifest.js';
|
|
9
11
|
/** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
|
|
10
12
|
export function memoryDocId(doc) {
|
|
11
13
|
return `${doc.scope}/${doc.name}`;
|
|
12
14
|
}
|
|
13
|
-
/** The
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
/** The selected profile id from the process env, or '' when none. The single
|
|
16
|
+
* seam every profile-aware memory/config path reads through — see the plan's
|
|
17
|
+
* synthesis decision on why this is process env, not a threaded parameter. */
|
|
18
|
+
function selectedProfileId() {
|
|
19
|
+
return process.env['CRTR_PROFILE_ID'] || '';
|
|
20
|
+
}
|
|
21
|
+
/** The memory scopes in resolution precedence: project stack > profile > user >
|
|
22
|
+
* builtin. Project is included only when a project scope exists for the cwd
|
|
23
|
+
* (or the selected profile's project stack); profile is included only when a
|
|
24
|
+
* profile is selected. A single `scope` narrows to just that scope. */
|
|
16
25
|
function scopesInPrecedence(scope) {
|
|
17
26
|
if (scope)
|
|
18
27
|
return [scope];
|
|
19
28
|
const out = [];
|
|
20
29
|
if (projectScopeRoot())
|
|
21
30
|
out.push('project');
|
|
31
|
+
if (selectedProfileId() !== '')
|
|
32
|
+
out.push('profile');
|
|
22
33
|
out.push('user');
|
|
23
34
|
out.push('builtin');
|
|
24
35
|
return out;
|
|
25
36
|
}
|
|
26
37
|
/** Memory loads project scopes as a stack, not a single nearest root: every
|
|
27
|
-
* ancestor `.crouter/` from cwd upward
|
|
28
|
-
*
|
|
38
|
+
* ancestor `.crouter/` from cwd upward (widened by a selected profile's
|
|
39
|
+
* `projects`) contributes, nearest first. Profile is a singleton store
|
|
40
|
+
* resolved through the centralized `loadProfileManifest` — never a raw path
|
|
41
|
+
* join — and drops out silently (never throws the resolver) when the
|
|
42
|
+
* selected profile id no longer resolves to a manifest. User and builtin
|
|
43
|
+
* remain singleton scopes after that. */
|
|
29
44
|
function memorySourcesInPrecedence(scope) {
|
|
30
45
|
const out = [];
|
|
31
46
|
for (const s of scopesInPrecedence(scope)) {
|
|
@@ -38,6 +53,18 @@ function memorySourcesInPrecedence(scope) {
|
|
|
38
53
|
});
|
|
39
54
|
}
|
|
40
55
|
}
|
|
56
|
+
else if (s === 'profile') {
|
|
57
|
+
const profileId = selectedProfileId();
|
|
58
|
+
if (profileId !== '') {
|
|
59
|
+
try {
|
|
60
|
+
const { profileId: resolvedId } = loadProfileManifest(profileId);
|
|
61
|
+
out.push({ scope: 'profile', memoryDir: profileMemoryDir(resolvedId), plugins: [] });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Missing/deleted/invalid selected profile: no profile memory source.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
41
68
|
else {
|
|
42
69
|
out.push({
|
|
43
70
|
scope: s,
|
|
@@ -56,7 +83,7 @@ function loadMemoryDoc(name, scope, path) {
|
|
|
56
83
|
* subdirs supported), sorted by path-derived name. SKILL.md bundles are legacy
|
|
57
84
|
* Agent Skills and are ignored; crouter memory docs are plain .md files under
|
|
58
85
|
* memory/. */
|
|
59
|
-
function listMemoryDocsInDir(scope, dir) {
|
|
86
|
+
function listMemoryDocsInDir(scope, dir, quiet = false) {
|
|
60
87
|
if (!dir || !pathExists(dir))
|
|
61
88
|
return [];
|
|
62
89
|
const docs = [];
|
|
@@ -76,7 +103,8 @@ function listMemoryDocsInDir(scope, dir) {
|
|
|
76
103
|
stack.push(join(d, e.name));
|
|
77
104
|
else if (e.isFile() && e.name.endsWith('.md') && e.name !== 'SKILL.md') {
|
|
78
105
|
const file = join(d, e.name);
|
|
79
|
-
const
|
|
106
|
+
const raw = relative(dir, file).replace(/\.md$/i, '').split(sep).join('/');
|
|
107
|
+
const name = normalizeDocName(raw);
|
|
80
108
|
if (name)
|
|
81
109
|
found.push({ path: file, name });
|
|
82
110
|
}
|
|
@@ -86,35 +114,52 @@ function listMemoryDocsInDir(scope, dir) {
|
|
|
86
114
|
// COLLECTION layer: the strict frontmatter parser throws on invalid YAML.
|
|
87
115
|
// Isolate one malformed doc with a clear scoped notice + skip, so a single
|
|
88
116
|
// bad file can't brick `memory list`/`find` or the substrate boot render.
|
|
117
|
+
// `quiet` suppresses the notice for a targeted resolve (a leaf-name read),
|
|
118
|
+
// where another doc's health is irrelevant noise before the result.
|
|
89
119
|
try {
|
|
90
120
|
docs.push(loadMemoryDoc(name, scope, path));
|
|
91
121
|
}
|
|
92
122
|
catch (e) {
|
|
93
123
|
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
94
|
-
|
|
124
|
+
if (!quiet)
|
|
125
|
+
warn(`invalid frontmatter in ${path}: ${msg}`);
|
|
95
126
|
}
|
|
96
127
|
}
|
|
97
128
|
return docs.sort((a, b) => a.name.localeCompare(b.name));
|
|
98
129
|
}
|
|
99
130
|
/** All native memory docs for a scope. Project scope is a nearest-first stack of
|
|
100
|
-
* every ancestor `.crouter/memory
|
|
101
|
-
|
|
131
|
+
* every ancestor `.crouter/memory/` (widened by a selected profile's project
|
|
132
|
+
* stack); profile is the selected profile's own singleton store, resolved
|
|
133
|
+
* through `loadProfileManifest`; user and builtin are singleton stores. */
|
|
134
|
+
export function listMemoryDocs(scope, quiet = false) {
|
|
102
135
|
if (scope === 'project') {
|
|
103
|
-
return projectScopeRoots().flatMap((root) => listMemoryDocsInDir('project', join(root, 'memory')));
|
|
136
|
+
return projectScopeRoots().flatMap((root) => listMemoryDocsInDir('project', join(root, 'memory'), quiet));
|
|
104
137
|
}
|
|
105
|
-
|
|
138
|
+
if (scope === 'profile') {
|
|
139
|
+
const profileId = selectedProfileId();
|
|
140
|
+
if (profileId === '')
|
|
141
|
+
return [];
|
|
142
|
+
try {
|
|
143
|
+
const { profileId: resolvedId } = loadProfileManifest(profileId);
|
|
144
|
+
return listMemoryDocsInDir('profile', profileMemoryDir(resolvedId), quiet);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return listMemoryDocsInDir(scope, scopeMemoryDir(scope), quiet);
|
|
106
151
|
}
|
|
107
152
|
/** All of one plugin's substrate docs, mounted under the virtual `<pluginName>/`
|
|
108
153
|
* namespace. Walks `pluginMemoryDir(plugin)` recursively for *.md, deriving each
|
|
109
154
|
* doc's name exactly as `listMemoryDocs` does (path-relative, no extension,
|
|
110
155
|
* slash-separated) then prefixing the plugin name. Builtin has no plugins. */
|
|
111
|
-
export function listPluginMemoryDocs(plugin, scope) {
|
|
156
|
+
export function listPluginMemoryDocs(plugin, scope, quiet = false) {
|
|
112
157
|
const dir = pluginMemoryDir(plugin);
|
|
113
158
|
if (!pathExists(dir))
|
|
114
159
|
return [];
|
|
115
160
|
const docs = [];
|
|
116
161
|
for (const file of walkFiles(dir, (n) => n.endsWith('.md') && n !== 'SKILL.md')) {
|
|
117
|
-
const derived = relative(dir, file).replace(/\.md$/i, '').split(sep).join('/');
|
|
162
|
+
const derived = normalizeDocName(relative(dir, file).replace(/\.md$/i, '').split(sep).join('/'));
|
|
118
163
|
if (!derived)
|
|
119
164
|
continue;
|
|
120
165
|
const name = `${plugin.name}/${derived}`;
|
|
@@ -123,21 +168,84 @@ export function listPluginMemoryDocs(plugin, scope) {
|
|
|
123
168
|
}
|
|
124
169
|
catch (e) {
|
|
125
170
|
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
126
|
-
|
|
171
|
+
if (!quiet)
|
|
172
|
+
warn(`invalid frontmatter in ${file}: ${msg}`);
|
|
127
173
|
}
|
|
128
174
|
}
|
|
129
175
|
return docs.sort((a, b) => a.name.localeCompare(b.name));
|
|
130
176
|
}
|
|
131
177
|
/** All memory docs across the resolved sources, in precedence order: each
|
|
132
|
-
* ancestor project `.crouter/` from nearest to farthest, then
|
|
133
|
-
*
|
|
134
|
-
* docs, so native wins on the
|
|
135
|
-
|
|
178
|
+
* ancestor project `.crouter/` from nearest to farthest, then the selected
|
|
179
|
+
* profile's memory (if any), then user, then builtin. Within each source,
|
|
180
|
+
* native docs are emitted before enabled-plugin docs, so native wins on the
|
|
181
|
+
* caller's first-wins dedup. */
|
|
182
|
+
export function listAllMemoryDocs(scope, quiet = false) {
|
|
136
183
|
return memorySourcesInPrecedence(scope).flatMap((source) => [
|
|
137
|
-
...listMemoryDocsInDir(source.scope, source.memoryDir),
|
|
138
|
-
...source.plugins.flatMap((p) => listPluginMemoryDocs(p, source.scope)),
|
|
184
|
+
...listMemoryDocsInDir(source.scope, source.memoryDir, quiet),
|
|
185
|
+
...source.plugins.flatMap((p) => listPluginMemoryDocs(p, source.scope, quiet)),
|
|
139
186
|
]);
|
|
140
187
|
}
|
|
188
|
+
/** Find the direct child of `dir` — a `.md` file (matched on name minus
|
|
189
|
+
* extension) or a directory — whose NORMALIZED display name equals
|
|
190
|
+
* `segment`. An exact literal match (no prefix to strip) wins over a
|
|
191
|
+
* normalized match, so a `spine` dir sitting beside a `01-spine` dir (a
|
|
192
|
+
* malformed corpus) never masks the literal one; a well-formed corpus has at
|
|
193
|
+
* most one candidate either way. Returns the absolute child path, or null. */
|
|
194
|
+
function matchNormalizedChild(dir, segment, want) {
|
|
195
|
+
let entries;
|
|
196
|
+
try {
|
|
197
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
let normalizedHit = null;
|
|
203
|
+
for (const e of entries) {
|
|
204
|
+
if (want === 'dir') {
|
|
205
|
+
if (!e.isDirectory())
|
|
206
|
+
continue;
|
|
207
|
+
if (e.name === segment)
|
|
208
|
+
return join(dir, e.name);
|
|
209
|
+
if (normalizedHit === null && normalizeNameSegment(e.name) === segment) {
|
|
210
|
+
normalizedHit = join(dir, e.name);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
if (!(e.isFile() && e.name.endsWith('.md')))
|
|
215
|
+
continue;
|
|
216
|
+
const base = e.name.slice(0, -3);
|
|
217
|
+
if (base === segment)
|
|
218
|
+
return join(dir, e.name);
|
|
219
|
+
if (normalizedHit === null && normalizeNameSegment(base) === segment) {
|
|
220
|
+
normalizedHit = join(dir, e.name);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return normalizedHit;
|
|
225
|
+
}
|
|
226
|
+
/** Resolve a NORMALIZED (numeric-prefix-stripped) segment path against a
|
|
227
|
+
* physical memory dir: walk intermediate segments matching each as a
|
|
228
|
+
* directory by normalized name, then resolve the final segment as either a
|
|
229
|
+
* `.md` file or a directory (the bare-dir -> INDEX.md fallback needs the dir
|
|
230
|
+
* match even when a same-named file also exists). Either half is null when
|
|
231
|
+
* the segment path does not resolve that way. This is what makes
|
|
232
|
+
* `00-runtime-base.md` findable as `runtime-base` and `01-spine/00-has-manager`
|
|
233
|
+
* findable as `spine/has-manager` — the physical path keeps its pins, only
|
|
234
|
+
* lookup is prefix-blind. */
|
|
235
|
+
function resolveNormalizedPath(baseDir, segments) {
|
|
236
|
+
let curDir = baseDir;
|
|
237
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
238
|
+
const next = matchNormalizedChild(curDir, segments[i], 'dir');
|
|
239
|
+
if (!next)
|
|
240
|
+
return { filePath: null, dirPath: null };
|
|
241
|
+
curDir = next;
|
|
242
|
+
}
|
|
243
|
+
const last = segments[segments.length - 1];
|
|
244
|
+
return {
|
|
245
|
+
filePath: matchNormalizedChild(curDir, last, 'file'),
|
|
246
|
+
dirPath: matchNormalizedChild(curDir, last, 'dir'),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
141
249
|
/** Direct full-path lookup of memory/<name>.md across scopes, precedence-first.
|
|
142
250
|
* Returns every scope's hit in precedence order; the resolver takes the first
|
|
143
251
|
* (highest-precedence) one — a fully-specified name is never ambiguous.
|
|
@@ -145,7 +253,12 @@ export function listAllMemoryDocs(scope) {
|
|
|
145
253
|
* A directory INDEX is the cleaner contract: when `<name>.md` is absent but
|
|
146
254
|
* `<name>/INDEX.md` exists, the bare dir name (`taste`) resolves to the dir's
|
|
147
255
|
* INDEX doc — carrying the dir name as its identity. (`taste/INDEX` still
|
|
148
|
-
* resolves directly as the file path.)
|
|
256
|
+
* resolves directly as the file path.)
|
|
257
|
+
*
|
|
258
|
+
* `name`'s segments are already normalized (a caller-supplied identifier is
|
|
259
|
+
* never authored with a numeric prefix); resolution against the physical tree
|
|
260
|
+
* is prefix-blind via `resolveNormalizedPath` so a normalized name finds an
|
|
261
|
+
* `NN-`-pinned physical file/dir. */
|
|
149
262
|
function findMemoryMatches(name, scope) {
|
|
150
263
|
const matches = [];
|
|
151
264
|
const segments = name.split('/');
|
|
@@ -156,15 +269,17 @@ function findMemoryMatches(name, scope) {
|
|
|
156
269
|
// ancestor `.crouter/`, then user, then builtin.
|
|
157
270
|
const dir = source.memoryDir;
|
|
158
271
|
if (dir) {
|
|
159
|
-
const
|
|
160
|
-
if (!isLegacySkillDoc &&
|
|
161
|
-
matches.push(loadMemoryDoc(name, source.scope,
|
|
272
|
+
const { filePath, dirPath } = resolveNormalizedPath(dir, segments);
|
|
273
|
+
if (!isLegacySkillDoc && filePath !== null) {
|
|
274
|
+
matches.push(loadMemoryDoc(name, source.scope, filePath));
|
|
162
275
|
continue;
|
|
163
276
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
277
|
+
if (dirPath !== null) {
|
|
278
|
+
const indexPath = join(dirPath, 'INDEX.md');
|
|
279
|
+
if (pathExists(indexPath)) {
|
|
280
|
+
matches.push(loadMemoryDoc(name, source.scope, indexPath));
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
168
283
|
}
|
|
169
284
|
}
|
|
170
285
|
// Plugin memory dir: a `<plugin>/<rest>` name resolves against that enabled
|
|
@@ -179,21 +294,22 @@ function findMemoryMatches(name, scope) {
|
|
|
179
294
|
if (p.name !== pluginName)
|
|
180
295
|
continue;
|
|
181
296
|
const pdir = pluginMemoryDir(p);
|
|
297
|
+
const restSegments = rest ? rest.split('/') : [];
|
|
182
298
|
if (rest) {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
matches.push(loadMemoryDoc(name, source.scope, ppath));
|
|
299
|
+
const { filePath } = resolveNormalizedPath(pdir, restSegments);
|
|
300
|
+
if (restSegments.at(-1) !== 'SKILL' && filePath !== null) {
|
|
301
|
+
matches.push(loadMemoryDoc(name, source.scope, filePath));
|
|
187
302
|
break;
|
|
188
303
|
}
|
|
189
304
|
}
|
|
190
305
|
// Bare name -> <plugin>/memory/INDEX.md; slashed name -> dir INDEX.
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
306
|
+
const pIndexDir = rest ? resolveNormalizedPath(pdir, restSegments).dirPath : pdir;
|
|
307
|
+
if (pIndexDir !== null) {
|
|
308
|
+
const pindex = join(pIndexDir, 'INDEX.md');
|
|
309
|
+
if (pathExists(pindex)) {
|
|
310
|
+
matches.push(loadMemoryDoc(name, source.scope, pindex));
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
197
313
|
}
|
|
198
314
|
}
|
|
199
315
|
}
|
|
@@ -207,7 +323,9 @@ function findMemoryByLeaf(leaf, scope) {
|
|
|
207
323
|
return [];
|
|
208
324
|
let all;
|
|
209
325
|
try {
|
|
210
|
-
|
|
326
|
+
// Quiet: a targeted read must not spew other docs' frontmatter warnings
|
|
327
|
+
// before its own result (esp. a not_found) — corpus health is `lint`'s job.
|
|
328
|
+
all = listAllMemoryDocs(scope, true);
|
|
211
329
|
}
|
|
212
330
|
catch {
|
|
213
331
|
return [];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ProfileManifest } from '../../types.js';
|
|
2
|
+
export declare function profilesRoot(): string;
|
|
3
|
+
export declare function profileRoot(profileId: string): string;
|
|
4
|
+
export declare function profileManifestPath(profileId: string): string;
|
|
5
|
+
export declare function profileMemoryDir(profileId: string): string;
|
|
6
|
+
export interface ProfileEntry {
|
|
7
|
+
profileId: string;
|
|
8
|
+
manifest: ProfileManifest;
|
|
9
|
+
}
|
|
10
|
+
/** Every profile with a readable manifest, in directory-listing order.
|
|
11
|
+
* Enumerates ONLY immediate children of `profilesRoot()` — never recurses,
|
|
12
|
+
* never trusts a caller-supplied id. A dir whose name doesn't match the
|
|
13
|
+
* generated id shape, or whose `profile.json` is missing/corrupt, is skipped
|
|
14
|
+
* rather than crashing the whole listing. */
|
|
15
|
+
export declare function listProfiles(): ProfileEntry[];
|
|
16
|
+
/** Resolve a CLI operand to its profile — exact id first (checked against the
|
|
17
|
+
* actually-enumerated dirs, never a blind join), then a unique manifest
|
|
18
|
+
* `name` match. Ambiguous names fail listing every matching id; no match
|
|
19
|
+
* fails naming `profile list`/`profile new` as recovery. This is the ONLY
|
|
20
|
+
* function every command leaf and future runtime consumer (Phase 5/6) should
|
|
21
|
+
* call to turn a `<profile>` operand or `CRTR_PROFILE_ID` into a concrete,
|
|
22
|
+
* safe profile id. */
|
|
23
|
+
export declare function loadProfileManifest(profileIdOrName: string): ProfileEntry;
|
|
24
|
+
/** Hold the per-profile manifest lock for the duration of `fn`. ALL manifest
|
|
25
|
+
* mutations (create/rename/add-project/remove-project/delete/last-used)
|
|
26
|
+
* below run inside this. */
|
|
27
|
+
export declare function withProfileManifestLock<T>(profileId: string, fn: () => T): T;
|
|
28
|
+
export declare function createProfile(name: string, projects?: string[]): ProfileEntry;
|
|
29
|
+
export declare function updateProfileLastUsed(profileId: string): ProfileEntry;
|
|
30
|
+
export declare function renameProfile(profileId: string, name: string): ProfileEntry;
|
|
31
|
+
export declare function addProfileProject(profileId: string, dir: string): ProfileEntry;
|
|
32
|
+
export declare function removeProfileProject(profileId: string, dir: string): ProfileEntry;
|
|
33
|
+
export declare function deleteProfile(profileId: string): void;
|