@north-light/crouter 0.3.234 → 0.3.235
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/api/dto/nodes.d.ts +11 -0
- package/dist/clients/attach/viewer.js +200 -200
- package/dist/commands/attention.js +1 -1
- package/dist/commands/cron.js +5 -5
- package/dist/commands/human.js +1 -1
- package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.d.ts +1 -0
- package/dist/commands/memory/__tests__/memory-list-and-lint-ignore.test.js +96 -0
- package/dist/commands/memory/delete.js +1 -1
- package/dist/commands/memory/edit.js +6 -3
- package/dist/commands/memory/list.d.ts +4 -0
- package/dist/commands/memory/list.js +20 -14
- package/dist/commands/memory/move.js +1 -1
- package/dist/commands/memory/shared.d.ts +1 -0
- package/dist/commands/memory/shared.js +13 -1
- package/dist/commands/memory/write.js +4 -1
- package/dist/commands/node/inspect.js +11 -4
- package/dist/commands/node/lifecycle.js +1 -0
- package/dist/commands/node/message.js +8 -4
- package/dist/commands/node/subscription.js +5 -1
- package/dist/commands/node/wait.js +5 -1
- package/dist/commands/pkg/plugin-manage.js +1 -1
- package/dist/commands/pkg.js +1 -0
- package/dist/commands/profile/rename.js +1 -1
- package/dist/commands/sys/config.js +1 -0
- package/dist/commands/sys.js +1 -0
- package/dist/core/__tests__/cli-help-contract.test.d.ts +1 -0
- package/dist/core/__tests__/cli-help-contract.test.js +150 -0
- package/dist/core/__tests__/integration/command-plugins.test.js +21 -0
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +11 -6
- package/dist/core/canvas/canvas.js +11 -1
- package/dist/core/canvas/meta-file.js +18 -27
- package/dist/core/canvas/remote-canvas-source.d.ts +8 -0
- package/dist/core/canvas/remote-canvas-source.js +9 -1
- package/dist/core/command-manifests/registry.d.ts +2 -0
- package/dist/core/command-plugins/compose.js +4 -2
- package/dist/core/command-plugins/discovery.d.ts +1 -0
- package/dist/core/command-plugins/discovery.js +1 -1
- package/dist/core/command.js +2 -2
- package/dist/core/fault-classifier.js +8 -0
- package/dist/core/pagination.d.ts +2 -0
- package/dist/core/pagination.js +2 -1
- package/dist/core/runtime/broker/fault-retry.js +6 -0
- package/dist/core/runtime/broker/rebind.js +50 -0
- package/dist/core/runtime/launch.d.ts +2 -2
- package/dist/core/runtime/launch.js +17 -4
- package/dist/core/runtime/stop-guard.js +28 -16
- package/dist/core/substrate/frontmatter-validation.d.ts +1 -0
- package/dist/core/substrate/frontmatter-validation.js +1 -1
- package/dist/daemon/api/__tests__/error-seam.test.js +27 -0
- package/dist/daemon/api/handlers/messages.js +12 -12
- package/dist/daemon/api/handlers/nodes.js +13 -1
- package/dist/daemon/api/handlers/worktree.js +6 -22
- package/dist/daemon/api/map.d.ts +4 -2
- package/dist/daemon/api/map.js +22 -2
- package/dist/daemon/reconcilers/broker-supervision.js +1 -1
- package/dist/pi-extensions/canvas-bash-valve.js +4 -3
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -209,7 +209,7 @@ export const attentionBranch = defineBranch({
|
|
|
209
209
|
help: {
|
|
210
210
|
name: 'canvas attention',
|
|
211
211
|
summary: 'aggregate pending human tickets across the canvas',
|
|
212
|
-
model: '`count` returns a single integer (stdout.count is machine-parseable); `list` returns itemised asking-node entries
|
|
212
|
+
model: '`count` returns a single integer (stdout.count is machine-parseable) and accepts --node or --view; `list` returns itemised asking-node entries and accepts --view only. Both default to canvas-wide. A ticket is any humanloop interaction awaiting the user: an ask, a review, or a notification.',
|
|
213
213
|
},
|
|
214
214
|
children: [attentionCount, attentionList, attentionMap],
|
|
215
215
|
});
|
package/dist/commands/cron.js
CHANGED
|
@@ -19,7 +19,7 @@ import { resolve as resolvePath } from 'node:path';
|
|
|
19
19
|
import { defineBranch, defineLeaf } from '../core/command.js';
|
|
20
20
|
import { InputError, readStdinRaw } from '../core/io.js';
|
|
21
21
|
import { parseWhen, parseCadence, cadenceDisplay } from '../core/wake.js';
|
|
22
|
-
import { resolveProfileOperand } from '../core/profiles/manifest.js';
|
|
22
|
+
import { loadProfileManifest, resolveProfileOperand } from '../core/profiles/manifest.js';
|
|
23
23
|
import { profilesStateBlock } from '../core/profiles/state-block.js';
|
|
24
24
|
import { cliClient, rethrowAsCliError } from './api-client.js';
|
|
25
25
|
/** Canonicalize a profile operand (id, manifest name, or nearest match) to its profile id so
|
|
@@ -69,7 +69,7 @@ function parseRunTimeout(raw) {
|
|
|
69
69
|
* caller sees the whole canvas home. */
|
|
70
70
|
function scopeQuery() {
|
|
71
71
|
const p = envProfileId();
|
|
72
|
-
return p !== undefined && p.trim() !== '' ? { profile:
|
|
72
|
+
return p !== undefined && p.trim() !== '' ? { profile: p } : undefined;
|
|
73
73
|
}
|
|
74
74
|
/** Resolve the chosen-else-inherited cwd, and refuse one that is not a
|
|
75
75
|
* directory NOW — the row is a snapshot, so a bad cwd would fail at every
|
|
@@ -235,7 +235,7 @@ const addLeaf = defineLeaf({
|
|
|
235
235
|
const profile = profileFlag !== undefined
|
|
236
236
|
? canonicalProfileId(profileFlag)
|
|
237
237
|
: inherited !== undefined && inherited.trim() !== ''
|
|
238
|
-
?
|
|
238
|
+
? loadProfileManifest(inherited).profileId
|
|
239
239
|
: undefined;
|
|
240
240
|
const scopeFlag = input['scope'];
|
|
241
241
|
const scope = scopeFlag ?? (profile !== undefined ? 'profile' : 'global');
|
|
@@ -286,7 +286,7 @@ const listLeaf = defineLeaf({
|
|
|
286
286
|
{ name: 'crons', type: 'object[]', required: true, constraint: 'One row per cron: cron_id, name, fire_at (next fire, UTC), recur (cadence display), state (active|paused), held (true while parked by an exit-75 gate — fires on daemon poke, else at fire_at), on_output, sink, expires_at, last_run (the most recent settled run: finished, exit_code, delivered — null if it never ran).' },
|
|
287
287
|
],
|
|
288
288
|
outputKind: 'object',
|
|
289
|
-
effects: [],
|
|
289
|
+
effects: ['None. Read-only.'],
|
|
290
290
|
},
|
|
291
291
|
run: async () => {
|
|
292
292
|
const crons = await cliClient().listCrons(scopeQuery()).catch(rethrowAsCliError);
|
|
@@ -349,7 +349,7 @@ const showLeaf = defineLeaf({
|
|
|
349
349
|
{ name: 'runs', type: 'object[]', required: true, constraint: 'The bounded run log, most recent first: started, exit_code (-1 = timeout kill), duration_ms, stdout/stderr heads, delivered (what the sink did, or why it did not).' },
|
|
350
350
|
],
|
|
351
351
|
outputKind: 'object',
|
|
352
|
-
effects: [],
|
|
352
|
+
effects: ['None. Read-only.'],
|
|
353
353
|
},
|
|
354
354
|
run: async (input) => {
|
|
355
355
|
const id = requireCronId(input);
|
package/dist/commands/human.js
CHANGED
|
@@ -41,7 +41,7 @@ export function registerHuman() {
|
|
|
41
41
|
pageHelp.brief,
|
|
42
42
|
pageHelp.authoring,
|
|
43
43
|
pageHelp.componentSelection.join('\n'),
|
|
44
|
-
'Props, response contracts, and the full component catalog: `crtr human components
|
|
44
|
+
'Props, response contracts, and the full component catalog: `crtr human components`; select with `crtr human components list`, then read one with `crtr human components show <component>`.',
|
|
45
45
|
].join('\n\n'),
|
|
46
46
|
},
|
|
47
47
|
children: [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { test, after } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { parseFrontmatterGeneric } from '../../../core/frontmatter.js';
|
|
7
|
+
import { resetScopeCache } from '../../../core/scope.js';
|
|
8
|
+
import { CrtrError } from '../../../core/errors.js';
|
|
9
|
+
import { editLeaf } from '../edit.js';
|
|
10
|
+
import { listLeaf } from '../list.js';
|
|
11
|
+
import { writeLeaf } from '../write.js';
|
|
12
|
+
const previousHome = process.env['HOME'];
|
|
13
|
+
const previousCrtrHome = process.env['CRTR_HOME'];
|
|
14
|
+
const home = mkdtempSync(join(tmpdir(), 'crtr-memory-list-'));
|
|
15
|
+
const canvasHome = mkdtempSync(join(tmpdir(), 'crtr-memory-list-canvas-'));
|
|
16
|
+
mkdirSync(join(home, '.crouter', 'memory'), { recursive: true });
|
|
17
|
+
process.env['HOME'] = home;
|
|
18
|
+
process.env['CRTR_HOME'] = canvasHome;
|
|
19
|
+
function writeMemory(name, frontmatter = 'kind: knowledge') {
|
|
20
|
+
writeFileSync(join(home, '.crouter', 'memory', name), `---\n${frontmatter}\n---\n\nBODY\n`, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
after(() => {
|
|
23
|
+
resetScopeCache();
|
|
24
|
+
if (previousHome === undefined)
|
|
25
|
+
delete process.env['HOME'];
|
|
26
|
+
else
|
|
27
|
+
process.env['HOME'] = previousHome;
|
|
28
|
+
if (previousCrtrHome === undefined)
|
|
29
|
+
delete process.env['CRTR_HOME'];
|
|
30
|
+
else
|
|
31
|
+
process.env['CRTR_HOME'] = previousCrtrHome;
|
|
32
|
+
rmSync(home, { recursive: true, force: true });
|
|
33
|
+
rmSync(canvasHome, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
test('memory list resumes locale-aware pages across case boundaries', async () => {
|
|
36
|
+
writeMemory('upper.md', 'kind: knowledge\nname: A');
|
|
37
|
+
writeMemory('lower.md', 'kind: knowledge\nname: a');
|
|
38
|
+
writeMemory('INDEXED.md');
|
|
39
|
+
const first = (await listLeaf.run({ scope: 'user', limit: 2, cursor: undefined, kind: undefined, dir: undefined, paths: false }));
|
|
40
|
+
assert.deepEqual(first.items.map((item) => item.name), ['a', 'A']);
|
|
41
|
+
assert.ok(first.next_cursor);
|
|
42
|
+
const second = (await listLeaf.run({ scope: 'user', limit: 1, cursor: first.next_cursor, kind: undefined, dir: undefined, paths: false }));
|
|
43
|
+
assert.deepEqual(second.items.map((item) => item.name), ['INDEXED']);
|
|
44
|
+
const edge = (await listLeaf.run({ scope: 'user', limit: 3, cursor: undefined, kind: undefined, dir: undefined, paths: false }));
|
|
45
|
+
assert.deepEqual(edge.items.map((item) => item.name), ['a', 'A', 'INDEXED']);
|
|
46
|
+
assert.equal(edge.next_cursor, null);
|
|
47
|
+
});
|
|
48
|
+
test('memory write and edit set, validate, and unset lint-ignore', async () => {
|
|
49
|
+
const scalar = (await writeLeaf.run({
|
|
50
|
+
name: 'lint-scalar',
|
|
51
|
+
scope: 'user',
|
|
52
|
+
kind: 'knowledge',
|
|
53
|
+
whenAndWhyToRead: 'When lint length is intentional, this knowledge should be read because the document remains available.',
|
|
54
|
+
lintIgnore: 'length',
|
|
55
|
+
body: 'SCALAR',
|
|
56
|
+
}));
|
|
57
|
+
assert.equal(parseFrontmatterGeneric(readFileSync(scalar.path, 'utf8')).data?.['lint-ignore'], 'length');
|
|
58
|
+
const list = (await writeLeaf.run({
|
|
59
|
+
name: 'lint-list',
|
|
60
|
+
scope: 'user',
|
|
61
|
+
kind: 'knowledge',
|
|
62
|
+
whenAndWhyToRead: 'When lint breadth is intentional, this knowledge should be read because the document remains available.',
|
|
63
|
+
lintIgnore: ['length', 'broad-memory-read'],
|
|
64
|
+
body: 'LIST',
|
|
65
|
+
}));
|
|
66
|
+
assert.deepEqual(parseFrontmatterGeneric(readFileSync(list.path, 'utf8')).data?.['lint-ignore'], ['length', 'broad-memory-read']);
|
|
67
|
+
await editLeaf.run({
|
|
68
|
+
name: 'lint-scalar',
|
|
69
|
+
scope: 'user',
|
|
70
|
+
rationale: 'set the second suppressible rule',
|
|
71
|
+
lintIgnore: ['length', 'broad-memory-read'],
|
|
72
|
+
});
|
|
73
|
+
await editLeaf.run({
|
|
74
|
+
name: 'lint-scalar',
|
|
75
|
+
scope: 'user',
|
|
76
|
+
rationale: 'remove lint suppression',
|
|
77
|
+
unset: ['lint-ignore'],
|
|
78
|
+
});
|
|
79
|
+
assert.equal(parseFrontmatterGeneric(readFileSync(scalar.path, 'utf8')).data?.['lint-ignore'], undefined);
|
|
80
|
+
let error;
|
|
81
|
+
try {
|
|
82
|
+
await writeLeaf.run({
|
|
83
|
+
name: 'lint-invalid',
|
|
84
|
+
scope: 'user',
|
|
85
|
+
kind: 'knowledge',
|
|
86
|
+
whenAndWhyToRead: 'When invalid lint metadata is supplied, this knowledge should be read because it must be rejected.',
|
|
87
|
+
lintIgnore: ['length', 'not-a-rule'],
|
|
88
|
+
body: 'INVALID',
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (caught) {
|
|
92
|
+
error = caught;
|
|
93
|
+
}
|
|
94
|
+
assert.ok(error instanceof CrtrError);
|
|
95
|
+
assert.equal(error.code, 'usage');
|
|
96
|
+
});
|
|
@@ -33,7 +33,7 @@ export const deleteLeaf = defineLeaf({
|
|
|
33
33
|
summary: 'resolve an exact canonical name and remove that document from its store',
|
|
34
34
|
params: [
|
|
35
35
|
{ kind: 'positional', name: 'name', required: true, constraint: 'Full canonical name of the document — a project document is `<namespace>/<local name>`. Resolution is exact, as `read` resolves it: a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail. A canonical directory’s own document is deleted at the directory name; its members are untouched and stay reachable. If two physical files in the selected store own the name, delete selects the first path in stable lexical order and reports it. Builtin and installed-plugin documents are read-only and refuse to delete, as does the repository-root document, which declares the immutable namespace for every document in the repository; a nested store’s root guide is ordinary and may be deleted.' },
|
|
36
|
-
selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, '
|
|
36
|
+
selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, 'Package-shipped documents cannot be deleted through this writable-scope selector.'),
|
|
37
37
|
selectorParam('dir', {}, 'Deletes from that one store, including a non-winning duplicate no target view resolves to.'),
|
|
38
38
|
],
|
|
39
39
|
output: [
|
|
@@ -4,11 +4,11 @@ import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
|
|
|
4
4
|
import { readText, writeText } from '../../core/fs-utils.js';
|
|
5
5
|
import { loadMemoryStoreView, resolveMemoryCandidates, } from '../../core/memory-resolver.js';
|
|
6
6
|
import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor, readHistoryRecords, } from '../../core/memory/history.js';
|
|
7
|
-
import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, parseRequestedExtensionChanges, applyExtensionChanges, literalBodySegment, serializeMemoryDocLiteral, selectorParam, requireLocalNameInStore, requireUnambiguousDoc, resolveReadSelector, } from './shared.js';
|
|
7
|
+
import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, coerceGate, coerceLintIgnore, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, parseRequestedExtensionChanges, applyExtensionChanges, literalBodySegment, serializeMemoryDocLiteral, selectorParam, requireLocalNameInStore, requireUnambiguousDoc, resolveReadSelector, } from './shared.js';
|
|
8
8
|
/** Frontmatter fields an overlay flag cannot express removing. `kind` and the
|
|
9
9
|
* routing line are the document's contract (a doc without them is malformed);
|
|
10
10
|
* `origin` and `last-updated` are runtime provenance. */
|
|
11
|
-
const CLEARABLE_FIELDS = ['short-form', 'gate', 'surfaces', 'unlisted', 'slash', 'rationale'];
|
|
11
|
+
const CLEARABLE_FIELDS = ['short-form', 'gate', 'surfaces', 'unlisted', 'slash', 'rationale', 'lint-ignore'];
|
|
12
12
|
export const editLeaf = defineLeaf({
|
|
13
13
|
name: 'edit',
|
|
14
14
|
description: 'revise an existing memory document, recording why',
|
|
@@ -19,7 +19,7 @@ export const editLeaf = defineLeaf({
|
|
|
19
19
|
guide: 'The body you pipe is saved byte for byte. Nothing between stdin and disk parses, normalizes, summarizes, or rewrites it \u2014 the frontmatter fence is prepended and the text is otherwise untouched. Relaying a person\u2019s own words is therefore safe: pass them through unchanged and mark them with --verbatim.\n\n' +
|
|
20
20
|
'Two different rationales exist and they never share a flag. --rationale is why THIS REVISION is happening; it is required, it lands in the revision history, and it never touches the document text. --doc-rationale replaces the document\u2019s standing `rationale` frontmatter field \u2014 the observed gap the doc exists to close.\n\n' +
|
|
21
21
|
'Omitting stdin leaves the body exactly as it stands, which is how a frontmatter-only revision is made. Piping a body REPLACES the whole body; there is no partial or append mode. Emptying a document is not an edit \u2014 that intent is `crtr memory delete`.\n\n' +
|
|
22
|
-
'Omitted frontmatter flags preserve their fields. --unset is the only way to REMOVE one, and it is limited to fields a doc can legitimately live without (short-form, gate, surfaces, unlisted, slash, rationale). --surface replaces the whole surfaces list rather than adding to it.\n\n' +
|
|
22
|
+
'Omitted frontmatter flags preserve their fields. --unset is the only way to REMOVE one, and it is limited to fields a doc can legitimately live without (short-form, gate, surfaces, unlisted, slash, rationale, lint-ignore). --surface replaces the whole surfaces list rather than adding to it.\n\n' +
|
|
23
23
|
'An edit that changes nothing fails. A rationale attached to zero change is noise in the trail the history exists to build \u2014 if the doc already says what you meant, there is nothing to record.\n\n' +
|
|
24
24
|
'`last-updated` is stamped on every successful edit; `origin` is preserved, so it keeps pointing at the conversation that created the doc rather than the last one to touch it.\n\n' +
|
|
25
25
|
GUIDE_ROUTING_LINE + '\n\n' +
|
|
@@ -38,6 +38,7 @@ export const editLeaf = defineLeaf({
|
|
|
38
38
|
overlayParam('unlisted', {}, 'Presence SETS the field; remove it with `--unset unlisted`.'),
|
|
39
39
|
overlayParam('surface'),
|
|
40
40
|
overlayParam('gate'),
|
|
41
|
+
overlayParam('lint-ignore'),
|
|
41
42
|
overlayParam('slash', {}, 'Presence SETS the field; remove it with `--unset slash`.'),
|
|
42
43
|
{ kind: 'flag', name: 'doc-rationale', type: 'string', required: false, constraint: DOC_RATIONALE_CONSTRAINT },
|
|
43
44
|
{ kind: 'flag', name: 'extension', type: 'string', required: false, repeatable: true, constraint: 'Set one declared plugin field as `extensions.<plugin>.<field>=VALUE`. The path is required in full; booleans accept only true or false, numbers require finite numeric syntax, and strings/enums preserve the literal text after the first =. Every requested field validates before the document is revised.' },
|
|
@@ -105,6 +106,7 @@ export const editLeaf = defineLeaf({
|
|
|
105
106
|
if (doc.scope === 'builtin') {
|
|
106
107
|
throw usage(`${doc.name} is a builtin document shipped with the package (read-only) \u2014 it cannot be edited. Override it with a same-named doc at a writable scope instead (\`crtr memory write ${doc.name} ...\`).`, { memory: doc.name, scope: 'builtin' });
|
|
107
108
|
}
|
|
109
|
+
const lintIgnore = coerceLintIgnore(input['lintIgnore']);
|
|
108
110
|
const extensionChanges = parseRequestedExtensionChanges(input['extension'], input['unsetExtension'], memoryExtensionCatalogForDoc(doc));
|
|
109
111
|
const before = readText(doc.path);
|
|
110
112
|
const parsed = parseFrontmatterGeneric(before);
|
|
@@ -146,6 +148,7 @@ export const editLeaf = defineLeaf({
|
|
|
146
148
|
}
|
|
147
149
|
if (input['gate'] !== undefined)
|
|
148
150
|
frontmatter['gate'] = coerceGate(input['gate']);
|
|
151
|
+
setIf('lint-ignore', lintIgnore);
|
|
149
152
|
if (input['slash'] === true)
|
|
150
153
|
frontmatter['slash'] = true;
|
|
151
154
|
setIf('rationale', input['docRationale']);
|
|
@@ -20,4 +20,8 @@ export interface ListedMemoryDoc {
|
|
|
20
20
|
* point of naming a store is to see what it physically holds, including a pair
|
|
21
21
|
* that shares one identity. */
|
|
22
22
|
export declare function listMemoryDocs(kindFilter: string | undefined, docs: readonly MemoryDoc[], dedup: boolean): ListedMemoryDoc[];
|
|
23
|
+
/** The stable key used by both inventory ordering and cursor resumption. */
|
|
24
|
+
export declare function memoryListKey(doc: Pick<ListedMemoryDoc, 'scope' | 'kind' | 'name' | 'physicalRelativePath'>): string;
|
|
25
|
+
/** Locale-aware total order for memory inventory keys. */
|
|
26
|
+
export declare function compareMemoryListKeys(left: string, right: string): number;
|
|
23
27
|
export declare const listLeaf: import("../../core/command.js").LeafDef;
|
|
@@ -46,20 +46,26 @@ export function listMemoryDocs(kindFilter, docs, dedup) {
|
|
|
46
46
|
candidates: byName.get(doc.name) ?? [doc],
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
// stable order — the cursor key ends on the same component.
|
|
51
|
-
items.sort((a, b) => {
|
|
52
|
-
const sr = scopeRank(a.scope) - scopeRank(b.scope);
|
|
53
|
-
if (sr !== 0)
|
|
54
|
-
return sr;
|
|
55
|
-
const kc = a.kind.localeCompare(b.kind);
|
|
56
|
-
if (kc !== 0)
|
|
57
|
-
return kc;
|
|
58
|
-
const nc = a.name.localeCompare(b.name);
|
|
59
|
-
return nc !== 0 ? nc : a.physicalRelativePath.localeCompare(b.physicalRelativePath);
|
|
60
|
-
});
|
|
49
|
+
items.sort((a, b) => compareMemoryListKeys(memoryListKey(a), memoryListKey(b)));
|
|
61
50
|
return items;
|
|
62
51
|
}
|
|
52
|
+
/** The stable key used by both inventory ordering and cursor resumption. */
|
|
53
|
+
export function memoryListKey(doc) {
|
|
54
|
+
return `${String(scopeRank(doc.scope) + 10).padStart(2, '0')}:${doc.kind}:${doc.name}:${doc.physicalRelativePath}`;
|
|
55
|
+
}
|
|
56
|
+
/** Locale-aware total order for memory inventory keys. */
|
|
57
|
+
export function compareMemoryListKeys(left, right) {
|
|
58
|
+
const [leftScope, leftKind, leftName, ...leftPath] = left.split(':');
|
|
59
|
+
const [rightScope, rightKind, rightName, ...rightPath] = right.split(':');
|
|
60
|
+
const scope = Number(leftScope) - Number(rightScope);
|
|
61
|
+
if (scope !== 0)
|
|
62
|
+
return scope;
|
|
63
|
+
const kind = leftKind.localeCompare(rightKind);
|
|
64
|
+
if (kind !== 0)
|
|
65
|
+
return kind;
|
|
66
|
+
const name = leftName.localeCompare(rightName);
|
|
67
|
+
return name !== 0 ? name : leftPath.join(':').localeCompare(rightPath.join(':'));
|
|
68
|
+
}
|
|
63
69
|
export const listLeaf = defineLeaf({
|
|
64
70
|
name: 'list',
|
|
65
71
|
description: 'inventory of stored memory documents',
|
|
@@ -95,8 +101,8 @@ export const listLeaf = defineLeaf({
|
|
|
95
101
|
const items = selector.store !== null
|
|
96
102
|
? listMemoryDocs(kindFilter, loadMemoryStoreView(requireMountedStore(selector.store), false).docs, false)
|
|
97
103
|
: listMemoryDocs(kindFilter, listAllMemoryDocs(selector.scope, false, true), true);
|
|
98
|
-
const keyOf =
|
|
99
|
-
const result = paginate(items, { limit, cursor }, { defaultLimit: 50, maxLimit: 200, keyOf, total: 'count' });
|
|
104
|
+
const keyOf = memoryListKey;
|
|
105
|
+
const result = paginate(items, { limit, cursor }, { defaultLimit: 50, maxLimit: 200, keyOf, compareKeys: compareMemoryListKeys, total: 'count' });
|
|
100
106
|
const output = {
|
|
101
107
|
items: result.items.map((d) => ({
|
|
102
108
|
name: d.name,
|
|
@@ -53,7 +53,7 @@ export const moveLeaf = defineLeaf({
|
|
|
53
53
|
params: [
|
|
54
54
|
{ kind: 'positional', name: 'name', required: true, constraint: 'Full canonical name of the document to move — a project document is `<namespace>/<local name>`. Resolution is exact, as `read` resolves it: a file path, a bare leaf, a `<scope>/<name>` spelling, and a trailing `/INDEX` all fail. If two physical files in the selected store own the name, move selects the first path in stable lexical order and recovers the collision. Builtin and installed-plugin documents are read-only and refuse to move, as does the repository-root document, which declares the immutable namespace for every document in the repository; a nested store’s root guide is ordinary and may move.' },
|
|
55
55
|
{ kind: 'flag', name: 'to', type: 'string', required: true, constraint: 'The new full canonical name, in the SAME store — the repository namespace and store path included, since a move never crosses stores. `/`-joined segments of [A-Za-z0-9_-], no trailing `INDEX` (a directory’s document answers at the directory name). A nested store’s effective prefix is a legal destination for its optional root guide. Rejected when a document in that store already answers to it, or when either `<local name>.md` or `<local name>/INDEX.md` already exists there.' },
|
|
56
|
-
selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, '
|
|
56
|
+
selectorParam('scope-read', { choices: [...MEMORY_WRITE_SCOPES] }, 'Package-shipped documents cannot move through this writable-scope selector.'),
|
|
57
57
|
selectorParam('dir', {}, 'Source, destination, and inbound-link rewriting all stay inside that one store, so moving a non-winning duplicate leaves every other store untouched — and rewrites no link at all, because the name still resolves to the candidate that outranks it here.'),
|
|
58
58
|
],
|
|
59
59
|
output: [
|
|
@@ -185,6 +185,7 @@ export declare const SELECTOR_PARAMS: Record<string, FlagParam>;
|
|
|
185
185
|
/** One selector flag, with leaf-specific prose appended to the shared
|
|
186
186
|
* constraint and any leaf-specific schema override applied. */
|
|
187
187
|
export declare function selectorParam(name: string, overrides?: Partial<FlagParam>, extraConstraint?: string): FlagParam;
|
|
188
|
+
export declare function coerceLintIgnore(raw: unknown): string | string[] | undefined;
|
|
188
189
|
export declare const FRONTMATTER_OVERLAY_PARAMS: Record<string, FlagParam>;
|
|
189
190
|
/** One overlay flag, with leaf-specific prose appended to the shared
|
|
190
191
|
* constraint and any leaf-specific schema override applied. */
|
|
@@ -18,6 +18,7 @@ import { NEUTRAL_PROJECT_MEMORY, scopeMemoryDir, projectScopeRoot, ensureProject
|
|
|
18
18
|
import { loadProfileManifest, resolveProfileOperand, profileMemoryDir } from '../../core/profiles/manifest.js';
|
|
19
19
|
import { memoryDir as nodeMemoryDir } from '../../core/runtime/memory.js';
|
|
20
20
|
import { SURFACE_EVENTS, SURFACE_RUNGS } from '../../core/substrate/schema.js';
|
|
21
|
+
import { SUPPRESSIBLE_RULES } from '../../core/substrate/frontmatter-validation.js';
|
|
21
22
|
import { exposureTarget, loadContextExposureState, registerExposure, saveContextExposureState, } from '../../core/substrate/injected-store.js';
|
|
22
23
|
// The two memory kinds — knowledge (consult: procedural playbooks + factual
|
|
23
24
|
// references merged) vs preference (behave: standing directives). Used as the
|
|
@@ -463,6 +464,7 @@ const FRONTMATTER_ORDER = [
|
|
|
463
464
|
'gate',
|
|
464
465
|
'slash',
|
|
465
466
|
'rationale',
|
|
467
|
+
'lint-ignore',
|
|
466
468
|
'last-updated',
|
|
467
469
|
'origin',
|
|
468
470
|
];
|
|
@@ -734,7 +736,7 @@ export const SELECTOR_PARAMS = {
|
|
|
734
736
|
type: 'enum',
|
|
735
737
|
choices: [...MEMORY_WRITE_SCOPES],
|
|
736
738
|
required: false,
|
|
737
|
-
constraint: 'Store to write to (
|
|
739
|
+
constraint: 'Store to write to (package-shipped documents and installed-plugin documents are managed outside this command, so neither is writable). Default: project when cwd sits in a project, else user. `project` resolves to the NEAREST ancestor `.crouter/` walking up from cwd — in a nested workspace that can be a parent’s store, not the dir you are standing in; pass --dir to pin the exact project. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile. `node` writes the this-node store (`nodes/<CRTR_NODE_ID>/context/memory/`), the nearest scope, seen only by this running node. Conflicts with --dir.',
|
|
738
740
|
},
|
|
739
741
|
'dir': {
|
|
740
742
|
kind: 'flag',
|
|
@@ -760,6 +762,15 @@ export function selectorParam(name, overrides = {}, extraConstraint) {
|
|
|
760
762
|
const constraint = extraConstraint === undefined ? base.constraint : `${base.constraint} ${extraConstraint}`;
|
|
761
763
|
return { ...base, ...overrides, constraint };
|
|
762
764
|
}
|
|
765
|
+
export function coerceLintIgnore(raw) {
|
|
766
|
+
if (raw === undefined)
|
|
767
|
+
return undefined;
|
|
768
|
+
const rules = Array.isArray(raw) ? raw : [raw];
|
|
769
|
+
if (rules.length === 0 || rules.some((rule) => typeof rule !== 'string' || !SUPPRESSIBLE_RULES.includes(rule))) {
|
|
770
|
+
throw usage(`invalid lint-ignore: ${JSON.stringify(raw)} (suppressible rules: ${SUPPRESSIBLE_RULES.map((rule) => `\`${rule}\``).join(', ')})`);
|
|
771
|
+
}
|
|
772
|
+
return rules.length === 1 ? rules[0] : rules;
|
|
773
|
+
}
|
|
763
774
|
export const FRONTMATTER_OVERLAY_PARAMS = {
|
|
764
775
|
'kind': { kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: false, constraint: 'Document kind.' },
|
|
765
776
|
'when-and-why-to-read': { kind: 'flag', name: 'when-and-why-to-read', type: 'string', required: false, constraint: 'ONE routing sentence: "When <circumstance>, this <kind> should be read because <broader downstream payoff>." WHY is the reader\u2019s payoff \u2014 the consequence they secure for their task by reading \u2014 NEVER the doc summary, its rule, or that rule reworded as an outcome (a benefit-shaped restatement still fails). Rendered verbatim as the preview.' },
|
|
@@ -767,6 +778,7 @@ export const FRONTMATTER_OVERLAY_PARAMS = {
|
|
|
767
778
|
'unlisted': { kind: 'flag', name: 'unlisted', type: 'bool', required: false, default: false, constraint: 'Suppress this doc from directory listings. Suppression only — explicit reads, [[links]], and surfaces entries still work.' },
|
|
768
779
|
'surface': { kind: 'flag', name: 'surface', type: 'string', required: false, repeatable: true, constraint: 'One routing entry per occurrence, as a YAML/JSON object `{on, at, match?, match-frontmatter?, gate?}`; the flag set replaces the document’s whole surfaces list. `on` is boot|workspace-open|read|memory-read|command|pre-command; `at` is name|preview|content. `match` holds the event’s globs — required on read/memory-read/command/pre-command (a read entry may carry `match-frontmatter`, a predicate over the read file’s own frontmatter, instead), meaningless on boot/workspace-open. Optional entry `gate` is a node-config predicate using the same vocabulary as the document gate; its event constraints and gate both match before it participates. A `./`-anchored glob is relative: for `read` to the store’s owning repo dir, for `memory-read` to this doc’s routing anchor — its own canonical name when the doc is its directory’s document (`<dir>/INDEX.md`), otherwise the canonical directory it sits in. Participating entries fold to their highest `at`; there is no cross-entry deny precedence.' },
|
|
769
780
|
'gate': { kind: 'flag', name: 'gate', type: 'string', required: false, constraint: 'Frontmatter gate \u2014 YAML/JSON object predicate over node config using the same field/matcher vocabulary described in the guide.' },
|
|
781
|
+
'lint-ignore': { kind: 'flag', name: 'lint-ignore', type: 'enum', choices: [...SUPPRESSIBLE_RULES], required: false, repeatable: true, constraint: 'Suppress one authoring lint rule for this document. Repeat for a list; valid rules: length, broad-memory-read.' },
|
|
770
782
|
'slash': { kind: 'flag', name: 'slash', type: 'bool', required: false, default: false, constraint: 'Presence flags this doc invocable as a pi slash command (`/<name>`, `/` in a nested name rendered as `:`) \u2014 the doc body becomes the command\u2019s injected prompt. Default false: most docs are consulted, not invoked.' },
|
|
771
783
|
};
|
|
772
784
|
/** One overlay flag, with leaf-specific prose appended to the shared
|
|
@@ -11,7 +11,7 @@ import { parsedSubstrateSurfaces } from '../../core/substrate/frontmatter-valida
|
|
|
11
11
|
import { ensureDir, realpathOrSelf, walkFiles, writeText } from '../../core/fs-utils.js';
|
|
12
12
|
import { associateRepository } from '../../core/memory/repository-association.js';
|
|
13
13
|
import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor, } from '../../core/memory/history.js';
|
|
14
|
-
import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, planNewDocumentPlacement, resolveWriteSelector, requireMountedStore, buildOrigin, coerceGate, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, requireCanonicalName, selectorParam, parseRequestedExtensionChanges, applyExtensionChanges, serializeMemoryDoc, } from './shared.js';
|
|
14
|
+
import { DOC_RATIONALE_CONSTRAINT, GUIDE_CANONICAL_NAMES, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_SURFACES, planNewDocumentPlacement, resolveWriteSelector, requireMountedStore, buildOrigin, coerceGate, coerceLintIgnore, coerceSurface, memoryExtensionCatalogForDoc, memoryExtensionFieldCatalogHelp, overlayParam, requireCanonicalName, selectorParam, parseRequestedExtensionChanges, applyExtensionChanges, serializeMemoryDoc, } from './shared.js';
|
|
15
15
|
function emptyMemoryStore(memoryDir) {
|
|
16
16
|
return walkFiles(memoryDir, (name) => name.endsWith('.md') && name !== 'SKILL.md', (name) => name === '.crouter').length === 0;
|
|
17
17
|
}
|
|
@@ -60,6 +60,7 @@ export const writeLeaf = defineLeaf({
|
|
|
60
60
|
overlayParam('unlisted'),
|
|
61
61
|
overlayParam('surface'),
|
|
62
62
|
overlayParam('gate'),
|
|
63
|
+
overlayParam('lint-ignore'),
|
|
63
64
|
overlayParam('slash'),
|
|
64
65
|
{ kind: 'flag', name: 'rationale', type: 'string', required: false, constraint: DOC_RATIONALE_CONSTRAINT },
|
|
65
66
|
{ kind: 'flag', name: 'extension', type: 'string', required: false, repeatable: true, constraint: 'Set one declared plugin field as `extensions.<plugin>.<field>=VALUE`. The path is required in full; booleans accept only true or false, numbers require finite numeric syntax, and strings/enums preserve the literal text after the first =. Every requested field validates before the document is written.' },
|
|
@@ -117,6 +118,7 @@ export const writeLeaf = defineLeaf({
|
|
|
117
118
|
canonicalName = placement.canonicalName;
|
|
118
119
|
path = placement.path;
|
|
119
120
|
}
|
|
121
|
+
const lintIgnore = coerceLintIgnore(input['lintIgnore']);
|
|
120
122
|
const extensionChanges = parseRequestedExtensionChanges(input['extension'], undefined, memoryExtensionCatalogForDoc({ scope, path }));
|
|
121
123
|
const frontmatter = {};
|
|
122
124
|
frontmatter['kind'] = kind;
|
|
@@ -141,6 +143,7 @@ export const writeLeaf = defineLeaf({
|
|
|
141
143
|
}
|
|
142
144
|
if (input['gate'] !== undefined)
|
|
143
145
|
frontmatter['gate'] = coerceGate(input['gate']);
|
|
146
|
+
setIf('lint-ignore', lintIgnore);
|
|
144
147
|
if (input['slash'] === true)
|
|
145
148
|
frontmatter['slash'] = true;
|
|
146
149
|
setIf('rationale', input['rationale']);
|
|
@@ -82,10 +82,13 @@ const nodeList = defineLeaf({
|
|
|
82
82
|
return false;
|
|
83
83
|
return true;
|
|
84
84
|
});
|
|
85
|
-
const nodes =
|
|
85
|
+
const nodes = [];
|
|
86
|
+
for (const row of rows) {
|
|
86
87
|
const detail = await canvasSource.getNode(row.node_id).catch(rethrowAsCliError);
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
const named = { ...row, name: fullName(detail ?? row) };
|
|
89
|
+
if (name === undefined || name === '' || fuzzyMatch(name, named.name))
|
|
90
|
+
nodes.push(named);
|
|
91
|
+
}
|
|
89
92
|
return { nodes };
|
|
90
93
|
},
|
|
91
94
|
});
|
|
@@ -119,6 +122,7 @@ const nodeShow = defineLeaf({
|
|
|
119
122
|
],
|
|
120
123
|
output: [
|
|
121
124
|
{ name: 'node', type: 'object', required: true, constraint: 'The full node meta (system prompt + every telemetry field). The render surfaces a glanceable slice; `--json` carries the complete dump.' },
|
|
125
|
+
{ name: 'fault', type: 'object', required: true, constraint: 'The active runtime fault projection, or null: {link, kind, retry: {disposition}, since}.' },
|
|
122
126
|
{ name: 'reports', type: 'object[]', required: true, constraint: 'Who this node subscribes to (its reports/down), each enriched with {node_id, name, kind, status, active} — these are typically its spawned children.' },
|
|
123
127
|
{ name: 'managers', type: 'object[]', required: true, constraint: 'Who subscribes to this node (its managers/up), same enriched shape.' },
|
|
124
128
|
{ name: 'artifacts', type: 'object', required: true, constraint: 'Counts of what the node left on disk by corpus: {report, doc, roadmap}. Enumerate them with `node inspect artifacts <id>`.' },
|
|
@@ -186,7 +190,7 @@ const nodeShow = defineLeaf({
|
|
|
186
190
|
if (reports.length > 0)
|
|
187
191
|
hints.push(`Drill into a child/report: \`crtr node inspect show <node_id>\` (${reports.map((r) => r['node_id']).join(', ')}).`);
|
|
188
192
|
hints.push(`Full machine dump (system prompt + all telemetry): \`crtr --json node inspect show ${id}\`.`);
|
|
189
|
-
return { node, reports, managers, artifacts, pending_crons, paths, follow_up: hints.join('\n') };
|
|
193
|
+
return { node, fault: node.fault ?? null, reports, managers, artifacts, pending_crons, paths, follow_up: hints.join('\n') };
|
|
190
194
|
},
|
|
191
195
|
render: (r) => {
|
|
192
196
|
const node = r['node'];
|
|
@@ -195,12 +199,14 @@ const nodeShow = defineLeaf({
|
|
|
195
199
|
const isRemoteShow = remoteShowResults.has(r);
|
|
196
200
|
const alive = isRemoteShow ? 'remote (liveness not checked locally)' : (isPidAlive(pid) ? 'alive' : 'dead');
|
|
197
201
|
const intent = node['intent'] ? `/${String(node['intent'])}` : '';
|
|
202
|
+
const fault = r['fault'];
|
|
198
203
|
const parts = [];
|
|
199
204
|
// 1) Glanceable meta slice — the debug-critical fields, not the full dump.
|
|
200
205
|
parts.push(`# ${fullName(node)} (${id})`);
|
|
201
206
|
const meta = [
|
|
202
207
|
`- kind/mode/lifecycle: ${node['kind']} / ${node['mode']} / ${node['lifecycle']}`,
|
|
203
208
|
`- status: ${node['status']}${intent}`,
|
|
209
|
+
...(fault === null || fault === undefined ? [] : [`- fault: ${String(fault['kind'])} / ${String(fault['retry']?.disposition)} since ${String(fault['since'])}`]),
|
|
204
210
|
`- broker pid: ${pid ?? '(none)'} — ${alive}`,
|
|
205
211
|
`- cwd: ${node['cwd']}`,
|
|
206
212
|
`- host: ${node['host_kind']}` + (node['profile_id'] ? ` · profile ${node['profile_id']}` : ''),
|
|
@@ -291,6 +297,7 @@ export const nodeInspect = defineBranch({
|
|
|
291
297
|
help: {
|
|
292
298
|
name: 'node inspect',
|
|
293
299
|
summary: 'read one node (or the roster) — topology, context dir, and artifacts',
|
|
300
|
+
model: 'Inspection is read-only and never revives a node. The roster is the selection surface; one-node metadata points to progressively heavier views: a parsed broker snapshot, rendered conversation, context browser, or artifact references.',
|
|
294
301
|
},
|
|
295
302
|
children: [nodeList, nodeShow, nodeSnapshotLeaf, nodeTranscriptLeaf, nodeContextLeaf, nodeArtifactsLeaf],
|
|
296
303
|
});
|
|
@@ -355,6 +355,7 @@ export const nodeLifecycle = defineBranch({
|
|
|
355
355
|
help: {
|
|
356
356
|
name: 'node lifecycle',
|
|
357
357
|
summary: 'end, transition, or revive a node — recycle the pane, close + cascade-cancel a subtree, demote to terminal in place, or revive a dormant node',
|
|
358
|
+
model: 'Lifecycle actions preserve or replace an existing node identity. Finishing is permanent, closing is revivable, demotion changes the completion obligation in place, and revival relaunches a dormant broker. Teardown cascades only through descendants exclusively managed by the affected subtree.',
|
|
358
359
|
},
|
|
359
360
|
children: [nodeRecycle, nodeClose, nodeDemote, nodeReviveLeaf],
|
|
360
361
|
});
|
|
@@ -56,7 +56,7 @@ function messageRequestParams() {
|
|
|
56
56
|
{ kind: 'stdin', name: 'body', required: false, constraint: 'Visible message body. The schema alone is a complete request.' },
|
|
57
57
|
{ kind: 'flag', name: 'to', type: 'string', required: false, constraint: 'Target an existing node by id. Exactly one of --to or --self is required.' },
|
|
58
58
|
{ kind: 'flag', name: 'self', type: 'bool', required: false, constraint: 'Target the calling node. Exactly one of --to or --self is required.' },
|
|
59
|
-
{ kind: 'flag', name: 'tier', type: 'enum', choices: ['critical', 'urgent', 'normal'], required: false, default: 'normal', constraint: 'Delivery urgency.
|
|
59
|
+
{ kind: 'flag', name: 'tier', type: 'enum', choices: ['critical', 'urgent', 'normal'], required: false, default: 'normal', constraint: 'Delivery urgency. Every accepted tier may revive a dormant target, because the request must be answered; deferred is rejected for that reason.' },
|
|
60
60
|
{ kind: 'flag', name: 'output-schema', type: 'string', required: true, constraint: `JSON Schema the result must satisfy. ${OUTPUT_SCHEMA_TRANSPORT} The target answers through a constrained submit tool, then keeps working. ${OUTPUT_SCHEMA_CONSTRAINTS}` },
|
|
61
61
|
{ kind: 'flag', name: 'situational-context', type: 'string', required: false, constraint: 'Non-empty hidden ambient context delivered beside the message, or alone.' },
|
|
62
62
|
{ kind: 'flag', name: 'reopen', type: 'bool', required: false, hidden: true, constraint: 'Clear a finalized target latch before delivery or fresh revive.' },
|
|
@@ -105,7 +105,7 @@ async function runMessageEngine(input) {
|
|
|
105
105
|
const nodeMessageSend = defineLeaf({
|
|
106
106
|
name: 'send',
|
|
107
107
|
description: 'deliver an inbox message to an existing node immediately',
|
|
108
|
-
whenToUse: '
|
|
108
|
+
whenToUse: 'a node that already exists should have information or new direction now — steering a worker mid-task, handing over a finding it needs, correcting a wrong turn, or waking a dormant node with fresh work. The target reads it and decides what to do with it. Use `node message request` instead when you need an answer back in a shape you specify, and `node subscription add` when the target should keep receiving this node’s pushes rather than this one delivery',
|
|
109
109
|
help: {
|
|
110
110
|
name: 'node message send',
|
|
111
111
|
summary: 'deliver an inbox message to an existing node immediately',
|
|
@@ -120,7 +120,7 @@ const nodeMessageSend = defineLeaf({
|
|
|
120
120
|
const nodeMessageRequest = defineLeaf({
|
|
121
121
|
name: 'request',
|
|
122
122
|
description: 'deliver an immediate typed-output request to an existing node',
|
|
123
|
-
whenToUse: '
|
|
123
|
+
whenToUse: 'a node that already exists must hand back one answer in a shape you fix — a choice among options, a structured extraction, a verdict your own logic will branch on — or must be held to answering at all before it goes quiet. Use `node message send` instead when prose in the target’s own judgment is enough, and `node new --output-schema` when no node holds the context yet and the work needs a fresh agent',
|
|
124
124
|
help: {
|
|
125
125
|
name: 'node message request',
|
|
126
126
|
summary: 'deliver an immediate typed-output request to an existing node',
|
|
@@ -139,6 +139,10 @@ export const nodeMessage = defineBranch({
|
|
|
139
139
|
name: 'message',
|
|
140
140
|
description: 'deliver immediate inbox work to an existing node',
|
|
141
141
|
whenToUse: 'you need one immediate delivery: send ordinary information, or request one schema-constrained result',
|
|
142
|
-
help: {
|
|
142
|
+
help: {
|
|
143
|
+
name: 'node message',
|
|
144
|
+
summary: 'deliver one immediate inbox entry to an existing node',
|
|
145
|
+
model: 'Both children append one inbox entry to a node that already exists, targeted by id or --self, and may revive it if it is dormant. They differ in what the delivery obligates. An ordinary message obligates nothing: the target reads it and acts however it judges best. A request installs a one-off submit schema and holds the target awake and owing until it answers; the answer is validated against the schema, written to the target’s result file, and pushed as an update report to the TARGET’S subscribers, so subscribe to it first if the answer has to reach you. Neither child creates that standing relationship — a durable delivery edge is `node subscription`, and work that needs a fresh agent is `node new`.',
|
|
146
|
+
},
|
|
143
147
|
children: [nodeMessageSend, nodeMessageRequest],
|
|
144
148
|
});
|
|
@@ -69,6 +69,10 @@ export const nodeSubscription = defineBranch({
|
|
|
69
69
|
name: 'subscription',
|
|
70
70
|
description: 'manage push-delivery edges between nodes',
|
|
71
71
|
whenToUse: 'you need an ongoing active or passive subscriber-to-publisher delivery edge that spawn did not create, or need to remove one',
|
|
72
|
-
help: {
|
|
72
|
+
help: {
|
|
73
|
+
name: 'node subscription',
|
|
74
|
+
summary: 'add or remove subscriber-to-publisher push-delivery edges',
|
|
75
|
+
model: 'Subscriptions point from a subscriber to a publisher and control future push delivery. Active edges wake on each push; passive edges accumulate pushes for the subscriber’s next turn. Removing either edge stops subsequent delivery without changing messages already received.',
|
|
76
|
+
},
|
|
73
77
|
children: [nodeSubscribe, nodeUnsubscribe],
|
|
74
78
|
});
|
|
@@ -154,6 +154,10 @@ export const nodeWaitBranch = defineBranch({
|
|
|
154
154
|
name: 'wait',
|
|
155
155
|
description: 'declare a durable controller or deadline wait',
|
|
156
156
|
whenToUse: 'a terminal node must dorm for one named controller, or race its inbox against a self-scheduled deadline',
|
|
157
|
-
help: {
|
|
157
|
+
help: {
|
|
158
|
+
name: 'node wait',
|
|
159
|
+
summary: 'declare durable wait state; choose the event that settles it',
|
|
160
|
+
model: 'Both waits belong to the calling terminal node and settle on one future event. A controller wait records an existing node as the wake source; a deadline wait arms an urgent one-shot cron that races any earlier inbox delivery and is canceled when that delivery wins.',
|
|
161
|
+
},
|
|
158
162
|
children: [nodeWait, nodeWaitDeadline],
|
|
159
163
|
});
|
|
@@ -791,7 +791,7 @@ export const pluginRemove = defineLeaf({
|
|
|
791
791
|
summary: 'remove a plugin and its directory from the given scope',
|
|
792
792
|
params: [
|
|
793
793
|
{ kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Plugin name to remove.' },
|
|
794
|
-
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: searches
|
|
794
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Default: searches every scope.' },
|
|
795
795
|
],
|
|
796
796
|
output: [
|
|
797
797
|
{ name: 'scopes', type: 'string[]', required: true, constraint: 'Scopes the plugin was removed from.' },
|
package/dist/commands/pkg.js
CHANGED
|
@@ -14,6 +14,7 @@ export function registerPkg() {
|
|
|
14
14
|
help: {
|
|
15
15
|
name: 'pkg',
|
|
16
16
|
summary: 'manage plugins and marketplace sources',
|
|
17
|
+
model: 'Marketplaces are configured source catalogs; plugins are installed extensions resolved from those catalogs or direct HTTP endpoints. Browsing searches the configured catalogs without changing source or install state.',
|
|
17
18
|
},
|
|
18
19
|
children: [pkgBrowseLeaf, pluginBranch, marketBranch],
|
|
19
20
|
});
|
|
@@ -3,7 +3,7 @@ import { resolveProfileOperand, renameProfile } from '../../core/profiles/manife
|
|
|
3
3
|
export const renameLeaf = defineLeaf({
|
|
4
4
|
name: 'rename',
|
|
5
5
|
description: "change a profile's display name",
|
|
6
|
-
whenToUse: "you want to
|
|
6
|
+
whenToUse: "you want the profile to appear under a new name in bearings, listings, and `profile` subject gates. The profile id (its directory) never changes.",
|
|
7
7
|
help: {
|
|
8
8
|
name: 'profile rename',
|
|
9
9
|
summary: "change a profile's manifest `name` only — the profile_id is stable and never changes",
|
|
@@ -257,6 +257,7 @@ export const configBranch = defineBranch({
|
|
|
257
257
|
help: {
|
|
258
258
|
name: 'sys config',
|
|
259
259
|
summary: 'read and write crtr configuration, including model ladders; edit humanActions maps directly in ~/.crouter/config.json or <repo>/.crouter/config.json',
|
|
260
|
+
model: 'Configuration resolves through ordered scopes. Reads can resolve the effective value, writes target one writable scope, and path inspection reveals the backing files without reading values.',
|
|
260
261
|
},
|
|
261
262
|
children: [configGet, configSet, configPath],
|
|
262
263
|
});
|
package/dist/commands/sys.js
CHANGED
|
@@ -24,6 +24,7 @@ export function registerSys() {
|
|
|
24
24
|
help: {
|
|
25
25
|
name: 'sys',
|
|
26
26
|
summary: 'crtr system configuration, settings, diagnostics, and self-management',
|
|
27
|
+
model: 'Configuration and settings own durable behavior; diagnostics and support observe it. Setup, migration, sync, and update change installed or generated state, while daemon control owns the supervisor lifecycle.',
|
|
27
28
|
},
|
|
28
29
|
children: [sysSettingsLeaf, configBranch, sysSetupLeaf, sysDoctorLeaf, sysSyspromptLeaf, promptReviewLeaf, sysFeedbackLeaf, sysLogsLeaf, supportBranch, sysUpdateLeaf, sysVersionLeaf, sysMigrateLeaf, sysSyncBranch, daemonBranch],
|
|
29
30
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|