@crouton-kit/crouter 0.3.20 → 0.3.21
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/builtin-memory/crouter-development/marketplaces.md +2 -4
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +3 -5
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +3 -6
- package/dist/builtin-memory/crouter-development/personas.md +2 -6
- package/dist/builtin-memory/crouter-development/plugins.md +2 -4
- package/dist/builtin-memory/design.md +2 -4
- package/dist/builtin-memory/development.md +3 -3
- package/dist/builtin-memory/planning.md +3 -4
- package/dist/builtin-memory/spec.md +3 -10
- package/dist/builtin-personas/orchestration-kernel.md +1 -1
- package/dist/commands/human/queue.js +2 -2
- package/dist/commands/memory/__tests__/lint-schema.test.d.ts +1 -0
- package/dist/commands/memory/__tests__/lint-schema.test.js +29 -0
- package/dist/commands/memory/find.js +10 -15
- package/dist/commands/memory/lint.d.ts +7 -0
- package/dist/commands/memory/lint.js +10 -1
- package/dist/commands/memory/shared.js +1 -2
- package/dist/commands/memory/write.js +6 -9
- package/dist/commands/node.js +47 -25
- package/dist/commands/push.js +10 -15
- package/dist/commands/tmux-spread.js +16 -1
- package/dist/commands/view-cycle.js +9 -5
- package/dist/commands/view-run.js +3 -2
- package/dist/core/__tests__/cascade-close.test.js +3 -2
- package/dist/core/__tests__/context-intro.test.js +23 -27
- package/dist/core/__tests__/detach-focus.test.js +2 -2
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory.test.js +4 -4
- package/dist/core/__tests__/subscription-delivery.test.js +3 -3
- package/dist/core/bootstrap.js +18 -14
- package/dist/core/help.d.ts +2 -2
- package/dist/core/help.js +1 -1
- package/dist/core/render.d.ts +4 -3
- package/dist/core/render.js +38 -41
- package/dist/core/resolver.js +1 -1
- package/dist/core/runtime/bearings.d.ts +2 -2
- package/dist/core/runtime/bearings.js +2 -2
- package/dist/core/substrate/render.d.ts +1 -2
- package/dist/core/substrate/render.js +9 -12
- package/dist/core/substrate/schema.d.ts +18 -13
- package/dist/core/substrate/schema.js +12 -11
- package/dist/pi-extensions/canvas-context-intro.js +5 -3
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When a
|
|
4
|
-
|
|
5
|
-
symlink-based install, auto-bump CI, dual-publishing. Use when creating a
|
|
6
|
-
marketplace or contributing plugins to one.
|
|
3
|
+
when-and-why-to-read: When creating a crtr marketplace or contributing plugins
|
|
4
|
+
to one, this skill should be read.
|
|
7
5
|
short-form: How to author a crtr marketplace — marketplace.json index, plugin
|
|
8
6
|
entries, symlink-based install, auto-bump CI, dual-publishing. Use when
|
|
9
7
|
creating a marketplace or contributing plugins to one.
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
what to put in it, the identity/deliverable/boundary/report shape, and the
|
|
7
|
-
voice to use. Use when writing or revising a <kind>/PERSONA.md.
|
|
3
|
+
when-and-why-to-read: When writing or revising a base persona prompt (a
|
|
4
|
+
<kind>/PERSONA.md, the system prompt for a single-window worker node), this
|
|
5
|
+
skill should be read.
|
|
8
6
|
short-form: How to write a base persona prompt (the mode=base PERSONA.md) — the
|
|
9
7
|
system prompt for a single-window worker node. Covers what a base persona is
|
|
10
8
|
for, what to put in it, the identity/deliverable/boundary/report shape, and
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
belongs in the per-kind body, naming the child pipeline, the roadmapSkill
|
|
7
|
-
pointer, and the @include rule. Use when writing or revising a
|
|
8
|
-
<kind>/orchestrator.md.
|
|
3
|
+
when-and-why-to-read: When writing or revising an orchestrator persona prompt (a
|
|
4
|
+
<kind>/orchestrator.md, the system prompt for a resident coordinator node),
|
|
5
|
+
this skill should be read.
|
|
9
6
|
short-form: How to write an orchestrator persona prompt (orchestrator.md) — the
|
|
10
7
|
system prompt for a resident coordinator node. Covers the kernel-vs-kind
|
|
11
8
|
split, what belongs in the per-kind body, naming the child pipeline, the
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When a
|
|
4
|
-
|
|
5
|
-
PERSONA.md/orchestrator files, the frontmatter contract (incl. whenToUse),
|
|
6
|
-
nested sub-personas, scope resolution and overrides, and how to write the
|
|
7
|
-
prose. Use when adding a new `--kind`, overriding a builtin agent, or
|
|
8
|
-
debugging persona resolution.
|
|
3
|
+
when-and-why-to-read: When adding a new `--kind`, overriding a builtin agent, or
|
|
4
|
+
debugging persona resolution, this skill should be read.
|
|
9
5
|
short-form: How to define a custom node kind (persona) for crtr — the
|
|
10
6
|
PERSONA.md/orchestrator files, the frontmatter contract (incl. whenToUse),
|
|
11
7
|
nested sub-personas, scope resolution and overrides, and how to write the
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When a
|
|
4
|
-
|
|
5
|
-
scopes, install mechanics, versioning. Use when creating a new plugin,
|
|
6
|
-
packaging skills for distribution, or debugging install/resolution.
|
|
3
|
+
when-and-why-to-read: When creating a crtr plugin, packaging skills for
|
|
4
|
+
distribution, or debugging install/resolution, this skill should be read.
|
|
7
5
|
short-form: How to author a crtr plugin — plugin.json manifest, directory
|
|
8
6
|
layout, scopes, install mechanics, versioning. Use when creating a new plugin,
|
|
9
7
|
packaging skills for distribution, or debugging install/resolution.
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When shaping a design roadmap or producing an
|
|
4
|
-
|
|
5
|
-
top-down vs bottom-up, and how to decompose a large design into composable
|
|
6
|
-
sub-designs.
|
|
3
|
+
when-and-why-to-read: When shaping a design roadmap or producing an
|
|
4
|
+
architecture/interface design, this skill should be read.
|
|
7
5
|
short-form: Use when shaping a design roadmap or producing an
|
|
8
6
|
architecture/interface design — covers what a design deliverable is, the
|
|
9
7
|
design-artifact shape, when to go top-down vs bottom-up, and how to decompose
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When shaping or reshaping a build roadmap
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
when-and-why-to-read: When shaping or reshaping a build roadmap — choosing a
|
|
4
|
+
development style, selecting a phase skeleton, or setting exit criteria for a
|
|
5
|
+
software goal — this skill should be read.
|
|
6
6
|
short-form: Use when shaping or reshaping a build roadmap — choosing a
|
|
7
7
|
development style, selecting a phase skeleton, or setting exit criteria for a
|
|
8
8
|
software goal.
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When shaping a planning roadmap, deciding plan structure,
|
|
4
|
-
plan-review specialists before declaring a plan ready
|
|
5
|
-
|
|
6
|
-
out plan-review specialists before declaring a plan ready.
|
|
3
|
+
when-and-why-to-read: When shaping a planning roadmap, deciding plan structure,
|
|
4
|
+
or fanning out plan-review specialists before declaring a plan ready, this
|
|
5
|
+
skill should be read.
|
|
7
6
|
short-form: Use when shaping a planning roadmap, deciding plan structure, or
|
|
8
7
|
fanning out plan-review specialists before declaring a plan ready.
|
|
9
8
|
system-prompt-visibility: name
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
kind: skill
|
|
3
|
-
when: When running a specification effort, shaping a spec
|
|
4
|
-
how to stage design and requirements work
|
|
5
|
-
|
|
6
|
-
node, the isolation principle behind the design/requirements split, and what a
|
|
7
|
-
finished spec contains
|
|
8
|
-
why: Use when running a specification effort, shaping a spec roadmap, or
|
|
9
|
-
deciding how to stage design and requirements work. Covers the three-stage
|
|
10
|
-
shape→design→requirements methodology, when to delegate design to a child
|
|
11
|
-
node, the isolation principle behind the design/requirements split, and what a
|
|
12
|
-
finished spec contains.
|
|
3
|
+
when-and-why-to-read: When running a specification effort, shaping a spec
|
|
4
|
+
roadmap, or deciding how to stage design and requirements work, this skill
|
|
5
|
+
should be read.
|
|
13
6
|
short-form: Use when running a specification effort, shaping a spec roadmap, or
|
|
14
7
|
deciding how to stage design and requirements work. Covers the three-stage
|
|
15
8
|
shape→design→requirements methodology, when to delegate design to a child
|
|
@@ -52,7 +52,7 @@ Separate from the roadmap (your live plan and state) you have a persistent docum
|
|
|
52
52
|
|
|
53
53
|
**Reading.** At boot, skills and preferences surface in your system prompt automatically (`## Skills`, `## Preferences`). References surface in your `<crtr-context>` block (`## References`). To browse the full inventory: `crtr memory list`. To search by topic: `crtr memory find <query>`. To load a document by name: `crtr memory read <name>`.
|
|
54
54
|
|
|
55
|
-
**Writing.** Use `crtr memory write` to create or update a document. Every document carries `kind
|
|
55
|
+
**Writing.** Use `crtr memory write` to create or update a document. Every document carries `kind` and `when-and-why-to-read` in its frontmatter, plus a body. `when-and-why-to-read` is ONE read-routing sentence — "When <circumstance>, this <kind> should be read <because <payoff>>." — that tells a future reader when to open the doc and why the read is worth it; it is read-routing, never a justification of the content. It becomes the preview line verbatim. The `kind` governs which section it surfaces in at boot and how it loads:
|
|
56
56
|
|
|
57
57
|
- `skill` — a workflow or methodology to adopt. Surfaces by name in `## Skills`; load with `crtr memory read`.
|
|
58
58
|
- `preference` — how you should work. Surfaces as a `###` routing line in `## Preferences` at boot (default `system-prompt-visibility: preview`).
|
|
@@ -177,8 +177,8 @@ export const humanCancel = defineLeaf({
|
|
|
177
177
|
return { canceled: true, job_id: jobId };
|
|
178
178
|
},
|
|
179
179
|
render: (r) => r['canceled'] === true
|
|
180
|
-
?
|
|
181
|
-
:
|
|
180
|
+
? `Canceled human interaction ${r['job_id']} — its TUI pane is closed and subscribers were notified no answer is coming.`
|
|
181
|
+
: `Nothing to cancel for ${r['job_id']} — ${r['reason'] ?? 'nothing to cancel'}.`,
|
|
182
182
|
});
|
|
183
183
|
// ---------------------------------------------------------------------------
|
|
184
184
|
// _run (hidden worker; not listed in branch help)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Bug-regression: guards the hard cut that merged the substrate's `when`/`why`
|
|
2
|
+
// frontmatter pair into the single `when-and-why-to-read` read-routing field.
|
|
3
|
+
// The CTO ruling (taste/why-field-means-why-to-read) requires the migration be
|
|
4
|
+
// a hard cut: an old-shape doc must FAIL at `crtr memory lint`, never be read at
|
|
5
|
+
// runtime. `lintSubstrateSchema` is that enforcement seam — these lock it.
|
|
6
|
+
//
|
|
7
|
+
// Run: node --import tsx/esm --test src/commands/memory/__tests__/lint-schema.test.ts
|
|
8
|
+
import { test } from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import { lintSubstrateSchema } from '../lint.js';
|
|
11
|
+
const ROUTING = 'When you are X, this reference should be read because Y';
|
|
12
|
+
test('lint rejects a doc still carrying the retired `when` key', () => {
|
|
13
|
+
const err = lintSubstrateSchema({ kind: 'reference', when: 'when X' });
|
|
14
|
+
assert.ok(err !== null, 'old-shape `when` must produce a finding');
|
|
15
|
+
assert.match(err, /when-and-why-to-read/, 'the message points at the new field');
|
|
16
|
+
});
|
|
17
|
+
test('lint rejects a doc still carrying the retired `why` key', () => {
|
|
18
|
+
const err = lintSubstrateSchema({ kind: 'reference', why: 'because Y' });
|
|
19
|
+
assert.ok(err !== null, 'old-shape `why` must produce a finding');
|
|
20
|
+
assert.match(err, /when-and-why-to-read/, 'the message points at the new field');
|
|
21
|
+
});
|
|
22
|
+
test('lint rejects a substrate doc missing the merged routing field', () => {
|
|
23
|
+
const err = lintSubstrateSchema({ kind: 'reference' });
|
|
24
|
+
assert.ok(err !== null, 'a substrate doc must carry when-and-why-to-read');
|
|
25
|
+
assert.match(err, /when-and-why-to-read/);
|
|
26
|
+
});
|
|
27
|
+
test('lint accepts the merged new-shape frontmatter', () => {
|
|
28
|
+
assert.equal(lintSubstrateSchema({ kind: 'reference', 'when-and-why-to-read': ROUTING }), null);
|
|
29
|
+
});
|
|
@@ -12,8 +12,7 @@ function substrateUnit(d) {
|
|
|
12
12
|
kind: d.kind,
|
|
13
13
|
scope: d.scope,
|
|
14
14
|
path: d.path,
|
|
15
|
-
|
|
16
|
-
why: d.why,
|
|
15
|
+
routing: d.whenAndWhyToRead,
|
|
17
16
|
shortForm: d.shortForm,
|
|
18
17
|
loadBody: () => d.body,
|
|
19
18
|
};
|
|
@@ -26,10 +25,9 @@ function skillUnit(s) {
|
|
|
26
25
|
kind: 'skill',
|
|
27
26
|
scope: s.scope,
|
|
28
27
|
path: s.path,
|
|
29
|
-
// A skill's description plays the
|
|
30
|
-
// ranker weighs them too. short_form surfaces the description as the hook.
|
|
31
|
-
|
|
32
|
-
why: '',
|
|
28
|
+
// A skill's description plays the read-routing role; keywords ride along so
|
|
29
|
+
// the ranker weighs them too. short_form surfaces the description as the hook.
|
|
30
|
+
routing: [desc, keywords].filter((x) => x).join(' '),
|
|
33
31
|
shortForm: desc,
|
|
34
32
|
loadBody: () => parseFrontmatter(readText(s.path)).body,
|
|
35
33
|
};
|
|
@@ -80,12 +78,12 @@ function candidates(kindFilter) {
|
|
|
80
78
|
export const findLeaf = defineLeaf({
|
|
81
79
|
name: 'find',
|
|
82
80
|
description: 'relevance search across memory documents',
|
|
83
|
-
whenToUse: 'you do not yet know which document applies and need to discover what is stored — ranks documents by relevance, weighted over name,
|
|
81
|
+
whenToUse: 'you do not yet know which document applies and need to discover what is stored — ranks documents by relevance, weighted over name, the read-routing line, and short-form (add --body to also weigh body text). Searches the full scope set regardless of any visibility gate. Use --grep instead when you need an exact regex or literal-string match across document bodies rather than a ranked topic match.',
|
|
84
82
|
help: {
|
|
85
83
|
name: 'memory find',
|
|
86
|
-
summary: 'relevance search across memory documents, weighted over name/
|
|
84
|
+
summary: 'relevance search across memory documents, weighted over name/routing-line/short-form (and body with --body)',
|
|
87
85
|
params: [
|
|
88
|
-
{ kind: 'positional', name: 'query', required: true, constraint: 'With ranked search (default): whitespace-separated terms, matched case-insensitively and weighted over name,
|
|
86
|
+
{ kind: 'positional', name: 'query', required: true, constraint: 'With ranked search (default): whitespace-separated terms, matched case-insensitively and weighted over name, the read-routing line, and short-form (plus body with --body); documents matching more/stronger fields rank higher. With --grep: an ECMAScript regex applied to each document body line.' },
|
|
89
87
|
{ kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: false, constraint: 'Filter to a single kind. Default: all kinds.' },
|
|
90
88
|
{ kind: 'flag', name: 'grep', type: 'bool', required: false, constraint: 'Treat the query as an ECMAScript regex and match it against document bodies, instead of weighted relevance ranking. Mutually exclusive with --body.' },
|
|
91
89
|
{ kind: 'flag', name: 'body', type: 'bool', required: false, constraint: 'Also weigh document body text in the relevance ranking (in addition to name/when/why/short-form). Ignored under --grep, which always scans bodies.' },
|
|
@@ -134,7 +132,7 @@ export const findLeaf = defineLeaf({
|
|
|
134
132
|
follow_up: 'Read a document in full with `crtr memory read <name>`. Drop --grep for a ranked topic search.',
|
|
135
133
|
};
|
|
136
134
|
}
|
|
137
|
-
// --- ranked mode: weighted relevance over name/
|
|
135
|
+
// --- ranked mode: weighted relevance over name/routing-line/short-form(+body) ---
|
|
138
136
|
const terms = query
|
|
139
137
|
.toLowerCase()
|
|
140
138
|
.split(/\s+/)
|
|
@@ -144,18 +142,15 @@ export const findLeaf = defineLeaf({
|
|
|
144
142
|
const hits = [];
|
|
145
143
|
for (const u of units) {
|
|
146
144
|
const nameLc = u.name.toLowerCase();
|
|
147
|
-
const
|
|
148
|
-
const whyLc = u.why.toLowerCase();
|
|
145
|
+
const routingLc = u.routing.toLowerCase();
|
|
149
146
|
const shortLc = u.shortForm.toLowerCase();
|
|
150
147
|
const bodyLc = weighBody ? u.loadBody().toLowerCase() : null;
|
|
151
148
|
let score = 0;
|
|
152
149
|
for (const term of terms) {
|
|
153
150
|
if (nameLc.includes(term))
|
|
154
151
|
score += 10;
|
|
155
|
-
if (
|
|
152
|
+
if (routingLc.includes(term))
|
|
156
153
|
score += 5;
|
|
157
|
-
if (whyLc.includes(term))
|
|
158
|
-
score += 4;
|
|
159
154
|
if (shortLc.includes(term))
|
|
160
155
|
score += 3;
|
|
161
156
|
if (bodyLc !== null && bodyLc.includes(term))
|
|
@@ -1 +1,8 @@
|
|
|
1
|
+
/** Schema checks for a doc living in a substrate memory dir: a memory store
|
|
2
|
+
* holds ONLY substrate docs, so a missing/invalid `kind` is an authoring
|
|
3
|
+
* error here (elsewhere it just means "not a substrate doc"). Rung and gate
|
|
4
|
+
* values are checked RAW — the runtime parser silently falls back to kind
|
|
5
|
+
* defaults / inert gates, which is exactly the silent tolerance this lint
|
|
6
|
+
* exists to catch at authoring time. */
|
|
7
|
+
export declare function lintSubstrateSchema(fm: Record<string, unknown> | null): string | null;
|
|
1
8
|
export declare const lintLeaf: import("../../core/command.js").LeafDef;
|
|
@@ -24,12 +24,21 @@ const RUNG_FIELDS = ['system-prompt-visibility', 'file-read-visibility'];
|
|
|
24
24
|
* values are checked RAW — the runtime parser silently falls back to kind
|
|
25
25
|
* defaults / inert gates, which is exactly the silent tolerance this lint
|
|
26
26
|
* exists to catch at authoring time. */
|
|
27
|
-
function lintSubstrateSchema(fm) {
|
|
27
|
+
export function lintSubstrateSchema(fm) {
|
|
28
28
|
if (fm === null)
|
|
29
29
|
return 'missing frontmatter: a memory store doc requires `kind: skill|reference|preference`';
|
|
30
30
|
if (!isDocKind(fm.kind)) {
|
|
31
31
|
return `invalid kind: ${JSON.stringify(fm.kind)} (expected skill|reference|preference)`;
|
|
32
32
|
}
|
|
33
|
+
// The retired `when`/`why` pair was merged into one read-routing field. The
|
|
34
|
+
// hard cut is enforced HERE: an old-shape doc must fail, never be silently
|
|
35
|
+
// read at runtime.
|
|
36
|
+
if ('when' in fm || 'why' in fm) {
|
|
37
|
+
return 'retired `when`/`why` keys: merge them into one `when-and-why-to-read` line — "When <circumstance>, this <kind> should be read <because <payoff>>."';
|
|
38
|
+
}
|
|
39
|
+
if (typeof fm['when-and-why-to-read'] !== 'string' || fm['when-and-why-to-read'].trim() === '') {
|
|
40
|
+
return 'missing `when-and-why-to-read`: one read-routing line — "When <circumstance>, this <kind> should be read <because <payoff>>."';
|
|
41
|
+
}
|
|
33
42
|
for (const field of RUNG_FIELDS) {
|
|
34
43
|
const v = fm[field];
|
|
35
44
|
if (v !== undefined && (typeof v !== 'string' || !VALID_RUNGS.includes(v))) {
|
|
@@ -91,8 +91,7 @@ export function coerceAppliesTo(raw) {
|
|
|
91
91
|
// first in this order; any preserved-on-update extras append after.
|
|
92
92
|
const FRONTMATTER_ORDER = [
|
|
93
93
|
'kind',
|
|
94
|
-
'when',
|
|
95
|
-
'why',
|
|
94
|
+
'when-and-why-to-read',
|
|
96
95
|
'short-form',
|
|
97
96
|
'system-prompt-visibility',
|
|
98
97
|
'file-read-visibility',
|
|
@@ -13,8 +13,7 @@ export const writeLeaf = defineLeaf({
|
|
|
13
13
|
params: [
|
|
14
14
|
{ kind: 'positional', name: 'name', required: true, constraint: 'Path-derived identity (e.g. `topic` or `area/topic`) → memory/<name>.md at the resolved scope. Updated in place if it already exists, otherwise created.' },
|
|
15
15
|
{ kind: 'flag', name: 'kind', type: 'enum', choices: [...MEMORY_KINDS], required: true, constraint: 'Document kind.' },
|
|
16
|
-
{ kind: 'flag', name: 'when', type: 'string', required: false, constraint: '
|
|
17
|
-
{ kind: 'flag', name: 'why', type: 'string', required: false, constraint: 'Frontmatter whyText — a short string describing why this document matters.' },
|
|
16
|
+
{ kind: 'flag', name: 'when-and-why-to-read', type: 'string', required: false, constraint: 'The read-routing line, authored as ONE sentence: "When <circumstance>, this <kind> should be read <because <payoff>>." It states WHEN to read this doc and WHY the read is worth it — read-routing, NEVER a justification of why the content should be obeyed. Rendered verbatim as the preview. Required when creating.' },
|
|
18
17
|
{ kind: 'flag', name: 'short-form', type: 'string', required: false, constraint: 'Frontmatter short-form — a very abbreviated version of the content, the hook shown in `crtr memory list`.' },
|
|
19
18
|
{ kind: 'flag', name: 'system-prompt-visibility', type: 'enum', choices: [...VISIBILITY_RUNGS], required: false, constraint: 'Rung controlling how much of this document auto-loads into the system prompt / CLI help.' },
|
|
20
19
|
{ kind: 'flag', name: 'file-read-visibility', type: 'enum', choices: [...VISIBILITY_RUNGS], required: false, constraint: 'Rung controlling how much of this document surfaces when it is read off disk.' },
|
|
@@ -44,11 +43,10 @@ export const writeLeaf = defineLeaf({
|
|
|
44
43
|
const { scope, memoryDir } = resolveWriteTarget(scopeArg);
|
|
45
44
|
const path = memoryFilePath(memoryDir, name);
|
|
46
45
|
const created = !pathExists(path);
|
|
47
|
-
// CREATE requires the
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
throw usage(`creating ${name} requires --when and --why (they compose the preview routing line "{when}, read this ${kind}. {why}.")`);
|
|
46
|
+
// CREATE requires the read-routing line that becomes the preview — without
|
|
47
|
+
// it every preview renders empty. UPDATE inherits it from the existing doc.
|
|
48
|
+
if (created && input['whenAndWhyToRead'] === undefined) {
|
|
49
|
+
throw usage(`creating ${name} requires --when-and-why-to-read: one read-routing sentence "When <circumstance>, this ${kind} should be read <because <payoff>>." (rendered verbatim as the preview).`);
|
|
52
50
|
}
|
|
53
51
|
// In-place update: start from the existing frontmatter (preserving fields
|
|
54
52
|
// not passed this time), then overlay the provided ones. Create: start clean.
|
|
@@ -62,8 +60,7 @@ export const writeLeaf = defineLeaf({
|
|
|
62
60
|
if (value !== undefined)
|
|
63
61
|
frontmatter[key] = value;
|
|
64
62
|
};
|
|
65
|
-
setIf('when', input['
|
|
66
|
-
setIf('why', input['why']);
|
|
63
|
+
setIf('when-and-why-to-read', input['whenAndWhyToRead']);
|
|
67
64
|
setIf('short-form', input['shortForm']);
|
|
68
65
|
setIf('system-prompt-visibility', input['systemPromptVisibility']);
|
|
69
66
|
setIf('file-read-visibility', input['fileReadVisibility']);
|
package/dist/commands/node.js
CHANGED
|
@@ -164,7 +164,14 @@ const nodeNew = defineLeaf({
|
|
|
164
164
|
: childFollowUp(parent ?? process.env['CRTR_NODE_ID']),
|
|
165
165
|
};
|
|
166
166
|
},
|
|
167
|
-
render: (r) =>
|
|
167
|
+
render: (r) => {
|
|
168
|
+
const win = r['window'] ? ` in window ${r['window']}` : '';
|
|
169
|
+
const lines = [`Spawned "${r['name']}" (${r['node_id']}) — ${r['status']}${win}.`];
|
|
170
|
+
if (r['session'])
|
|
171
|
+
lines.push(`- session: ${r['session']}`);
|
|
172
|
+
lines.push('', String(r['follow_up']));
|
|
173
|
+
return lines.join('\n');
|
|
174
|
+
},
|
|
168
175
|
});
|
|
169
176
|
// ---------------------------------------------------------------------------
|
|
170
177
|
// node list — the active canvas (or a status slice)
|
|
@@ -352,9 +359,16 @@ const nodeRecycle = defineLeaf({
|
|
|
352
359
|
const res = await recycleNode(id, pane);
|
|
353
360
|
return { recycled: res.recycled, node_id: id, finalized: res.finalized, delivered: res.delivered.length, new_root: res.newRoot ?? undefined };
|
|
354
361
|
},
|
|
355
|
-
render: (r) =>
|
|
356
|
-
|
|
357
|
-
|
|
362
|
+
render: (r) => {
|
|
363
|
+
if (r['recycled'] !== true)
|
|
364
|
+
return `Recycle failed for ${r['node_id'] ?? '?'} — not in tmux, or no agent in this pane.`;
|
|
365
|
+
const lines = [`Recycled the pane — finished ${r['node_id']}.`, `- finalized: ${r['finalized']}`];
|
|
366
|
+
if (r['finalized'] === true)
|
|
367
|
+
lines.push(`- delivered: ${r['delivered']}`);
|
|
368
|
+
if (r['new_root'])
|
|
369
|
+
lines.push(`- new root: ${r['new_root']}`);
|
|
370
|
+
return lines.join('\n');
|
|
371
|
+
},
|
|
358
372
|
});
|
|
359
373
|
// ---------------------------------------------------------------------------
|
|
360
374
|
// node close — close a node + cascade-cancel its exclusive subtree (Alt+C → x)
|
|
@@ -398,9 +412,12 @@ const nodeClose = defineLeaf({
|
|
|
398
412
|
const res = closeNode(id);
|
|
399
413
|
return { closed: true, node_id: res.root, count: res.closed.length, closed_ids: res.closed, spared: res.spared };
|
|
400
414
|
},
|
|
401
|
-
render: (r) =>
|
|
402
|
-
|
|
403
|
-
|
|
415
|
+
render: (r) => {
|
|
416
|
+
if (r['closed'] !== true)
|
|
417
|
+
return 'Close failed — no node found in this pane to close.';
|
|
418
|
+
const spared = r['spared']?.length ?? 0;
|
|
419
|
+
return `Closed ${r['node_id']} and its exclusive subtree — ${r['count']} node(s) closed${spared > 0 ? `, ${spared} spared (still managed from outside the subtree)` : ''}.`;
|
|
420
|
+
},
|
|
404
421
|
});
|
|
405
422
|
// ---------------------------------------------------------------------------
|
|
406
423
|
// node cycle — DFS-walk the canvas one window at a time (Alt+] / Alt+[)
|
|
@@ -486,8 +503,8 @@ const nodeCycle = defineLeaf({
|
|
|
486
503
|
return { focused: res.focused, node_id: targetId, name: target.name, from: fromId };
|
|
487
504
|
},
|
|
488
505
|
render: (r) => r['focused'] === true
|
|
489
|
-
?
|
|
490
|
-
:
|
|
506
|
+
? `Cycled to "${r['name'] ?? ''}" (${r['node_id']}) — from ${r['from'] ?? '?'}.`
|
|
507
|
+
: 'No other live node to focus.',
|
|
491
508
|
});
|
|
492
509
|
// ---------------------------------------------------------------------------
|
|
493
510
|
// node msg — direct-address any node at a wake tier (wakes a dormant target)
|
|
@@ -582,7 +599,7 @@ const nodeSubscribe = defineLeaf({
|
|
|
582
599
|
subscribe(subscriber, publisher, !passive);
|
|
583
600
|
return { subscribed: true, subscriber, publisher, mode: passive ? 'passive' : 'active' };
|
|
584
601
|
},
|
|
585
|
-
render: (r) =>
|
|
602
|
+
render: (r) => `Subscribed ${r['subscriber']} → ${r['publisher']} (${r['mode']}).`,
|
|
586
603
|
});
|
|
587
604
|
const nodeUnsubscribe = defineLeaf({
|
|
588
605
|
name: 'unsubscribe',
|
|
@@ -609,7 +626,7 @@ const nodeUnsubscribe = defineLeaf({
|
|
|
609
626
|
unsubscribe(subscriber, publisher);
|
|
610
627
|
return { unsubscribed: true, subscriber, publisher };
|
|
611
628
|
},
|
|
612
|
-
render: (r) =>
|
|
629
|
+
render: (r) => `Unsubscribed ${r['subscriber']} from ${r['publisher']}.`,
|
|
613
630
|
});
|
|
614
631
|
// ---------------------------------------------------------------------------
|
|
615
632
|
// node promote — become an orchestrator (the worker→orchestrator polymorph)
|
|
@@ -728,7 +745,7 @@ const nodeDemote = defineLeaf({
|
|
|
728
745
|
const res = setLifecycle(id, 'terminal', { pane, detach: input['detach'] === true });
|
|
729
746
|
return { node_id: res.node_id, lifecycle: res.lifecycle, detached: res.detached };
|
|
730
747
|
},
|
|
731
|
-
render: (r) =>
|
|
748
|
+
render: (r) => `Demoted ${r['node_id']} — lifecycle now ${r['lifecycle']} (in place)${r['detached'] === true ? ', relocated to the background crtr session' : ''}.`,
|
|
732
749
|
});
|
|
733
750
|
// ---------------------------------------------------------------------------
|
|
734
751
|
// node lifecycle — flip the lifecycle axis (terminal ↔ resident), independent
|
|
@@ -765,7 +782,7 @@ const nodeLifecycle = defineLeaf({
|
|
|
765
782
|
const res = setLifecycle(id, value, { pane, detach: input['detach'] === true });
|
|
766
783
|
return { node_id: res.node_id, lifecycle: res.lifecycle, detached: res.detached };
|
|
767
784
|
},
|
|
768
|
-
render: (r) =>
|
|
785
|
+
render: (r) => `Set ${r['node_id']} lifecycle → ${r['lifecycle']}${r['detached'] === true ? ', relocated to the background crtr session' : ''}.`,
|
|
769
786
|
});
|
|
770
787
|
// ---------------------------------------------------------------------------
|
|
771
788
|
// node yield — refresh: discard context, revive fresh against the roadmap
|
|
@@ -908,10 +925,6 @@ function etaHint(fireAt, now) {
|
|
|
908
925
|
return ` (~${hrs}h)`;
|
|
909
926
|
return ` (~${Math.round(ms / 86_400_000)}d)`;
|
|
910
927
|
}
|
|
911
|
-
/** Escape a value for a rendered XML attribute (list rows carry free-text notes). */
|
|
912
|
-
function xmlAttr(s) {
|
|
913
|
-
return s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
914
|
-
}
|
|
915
928
|
// node wake at ---------------------------------------------------------------
|
|
916
929
|
const nodeWakeAt = defineLeaf({
|
|
917
930
|
name: 'at',
|
|
@@ -1011,7 +1024,10 @@ const nodeWakeAt = defineLeaf({
|
|
|
1011
1024
|
: `Bare self-alarm armed. You wake in a fresh window at ${fireAt}${eta}. End your turn now to go dormant; do not push final. The wake re-reads your roadmap.`;
|
|
1012
1025
|
return { id, kind, fires_at: fireAt, recur: cadenceDisplay(recur), target, guidance };
|
|
1013
1026
|
},
|
|
1014
|
-
render: (r) =>
|
|
1027
|
+
render: (r) => {
|
|
1028
|
+
const recur = r['recur'] !== 'none' ? `, recurs ${r['recur']}` : '';
|
|
1029
|
+
return `Armed ${r['kind']} wake ${r['id']} — fires ${r['fires_at']}${recur}, target ${r['target']}.\n\n${r['guidance']}`;
|
|
1030
|
+
},
|
|
1015
1031
|
});
|
|
1016
1032
|
// node wake until ------------------------------------------------------------
|
|
1017
1033
|
const nodeWakeUntil = defineLeaf({
|
|
@@ -1063,7 +1079,7 @@ const nodeWakeUntil = defineLeaf({
|
|
|
1063
1079
|
const guidance = `Deadline armed. You wake on the first inbox message, or at ${w.fireAt}${eta} if none arrives. Now delegate / end your turn to go dormant — whichever fires first cancels the other.`;
|
|
1064
1080
|
return { id, fires_at: w.fireAt, target: 'self', guidance };
|
|
1065
1081
|
},
|
|
1066
|
-
render: (r) =>
|
|
1082
|
+
render: (r) => `Armed deadline ${r['id']} — fires ${r['fires_at']}, target ${r['target']}.\n\n${r['guidance']}`,
|
|
1067
1083
|
});
|
|
1068
1084
|
// node wake spawn -----------------------------------------------------------
|
|
1069
1085
|
const nodeWakeSpawn = defineLeaf({
|
|
@@ -1164,7 +1180,10 @@ const nodeWakeSpawn = defineLeaf({
|
|
|
1164
1180
|
: `Deferred spawn armed: a fresh ${kind} node is born at ${fireAt}${eta}. No node exists yet — inspect/cancel via \`crtr node wake list\` / \`crtr node wake cancel ${id}\`. Pick up other work or end your turn.`;
|
|
1165
1181
|
return { id, kind, fires_at: fireAt, recur: cadenceDisplay(recur), guidance };
|
|
1166
1182
|
},
|
|
1167
|
-
render: (r) =>
|
|
1183
|
+
render: (r) => {
|
|
1184
|
+
const recur = r['recur'] !== 'none' ? `, recurs ${r['recur']}` : '';
|
|
1185
|
+
return `Armed deferred spawn ${r['id']} — a ${r['kind']} node, fires ${r['fires_at']}${recur}.\n\n${r['guidance']}`;
|
|
1186
|
+
},
|
|
1168
1187
|
});
|
|
1169
1188
|
// node wake list -------------------------------------------------------------
|
|
1170
1189
|
const nodeWakeList = defineLeaf({
|
|
@@ -1246,10 +1265,13 @@ const nodeWakeList = defineLeaf({
|
|
|
1246
1265
|
},
|
|
1247
1266
|
render: (r) => {
|
|
1248
1267
|
const wakes = r['wakes'];
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1268
|
+
const follow = 'Cancel one with `crtr node wake cancel <id>`. Fired one-shots are gone; a recurrence shows its NEXT fire, not past slots.';
|
|
1269
|
+
if (wakes.length === 0)
|
|
1270
|
+
return `No pending wakes in scope ${r['scope']}.\n\n${follow}`;
|
|
1271
|
+
const cell = (v) => String(v).replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
1272
|
+
const head = '| id | kind | next | recur | target | owner | note |\n| --- | --- | --- | --- | --- | --- | --- |';
|
|
1273
|
+
const rows = wakes.map((w) => `| ${cell(w['id'])} | ${cell(w['kind'])} | ${cell(w['next'])} | ${cell(w['recur'])} | ${cell(w['target'])} | ${cell(w['owner'])} | ${cell(w['note'])} |`);
|
|
1274
|
+
return `${wakes.length} pending wake(s) in scope ${r['scope']}.\n\n${head}\n${rows.join('\n')}\n\n${follow}`;
|
|
1253
1275
|
},
|
|
1254
1276
|
});
|
|
1255
1277
|
// node wake cancel -----------------------------------------------------------
|
|
@@ -1276,7 +1298,7 @@ const nodeWakeCancel = defineLeaf({
|
|
|
1276
1298
|
cancelWake(id);
|
|
1277
1299
|
return { id, was_pending: wasPending };
|
|
1278
1300
|
},
|
|
1279
|
-
render: (r) =>
|
|
1301
|
+
render: (r) => `Canceled wake ${r['id']}${r['was_pending'] === true ? '' : ' (already gone — no-op)'}.`,
|
|
1280
1302
|
});
|
|
1281
1303
|
// node wake (branch) ---------------------------------------------------------
|
|
1282
1304
|
const nodeWake = defineBranch({
|
package/dist/commands/push.js
CHANGED
|
@@ -112,7 +112,7 @@ function makeTierLeaf(tier) {
|
|
|
112
112
|
: tier === 'urgent'
|
|
113
113
|
? `Urgent report fanned to ${n} subscriber(s) — they are force-woken.`
|
|
114
114
|
: `Progress report fanned to ${n} subscriber(s).`;
|
|
115
|
-
return
|
|
115
|
+
return `${line}\n\n- report: ${r['report_path']}`;
|
|
116
116
|
},
|
|
117
117
|
});
|
|
118
118
|
}
|
|
@@ -161,16 +161,16 @@ const feedReadLeaf = defineLeaf({
|
|
|
161
161
|
const n = Array.isArray(r['entries']) ? r['entries'].length : 0;
|
|
162
162
|
const rawDigest = typeof r['digest'] === 'string' ? r['digest'] : '';
|
|
163
163
|
if (n > 0 && rawDigest.trim() !== '') {
|
|
164
|
-
return
|
|
164
|
+
return `${n} unread pointer${n === 1 ? '' : 's'} drained from ${r['node_id']}'s inbox.\n\n${rawDigest.trim()}`;
|
|
165
165
|
}
|
|
166
166
|
// Empty drain has two distinct causes — say which, honestly. An inbox that
|
|
167
167
|
// already holds entries was drained when the watcher woke you (the wake
|
|
168
168
|
// message WAS that digest); a never-used inbox simply has nothing yet.
|
|
169
169
|
const total = typeof r['inbox_total'] === 'number' ? r['inbox_total'] : 0;
|
|
170
|
-
const
|
|
171
|
-
? '
|
|
172
|
-
: '
|
|
173
|
-
return
|
|
170
|
+
const explanation = total > 0
|
|
171
|
+
? 'Your inbox is not empty, though. The watcher drains your inbox to wake you, so the entries you already saw in your wake message are the same ones you would read here; that is why this is empty. Re-read the whole history (full message bodies included) with `crtr feed read --all`, or dereference the refs from the wake digest you already have.'
|
|
172
|
+
: 'Your inbox is empty — nothing has arrived from your subscriptions yet. Expected while workers run: a worker that has not pushed yet leaves no pointer, and it will wake you the moment it does. The wake is automatic — just continue your own work or end your turn. (Reach for `crtr feed peek` only if you suspect a worker died, not to confirm a live one.)';
|
|
173
|
+
return `No unread pointers for ${r['node_id']} (unread=0).\n\n${explanation}`;
|
|
174
174
|
},
|
|
175
175
|
});
|
|
176
176
|
// ---------------------------------------------------------------------------
|
|
@@ -249,28 +249,23 @@ const feedPeekLeaf = defineLeaf({
|
|
|
249
249
|
const tail = unread > 0
|
|
250
250
|
? `${unread} unread report${unread === 1 ? '' : 's'} sit in your inbox — run \`crtr feed read\` to absorb ${unread === 1 ? 'it' : 'them'}.`
|
|
251
251
|
: 'If you spawned workers, they have finished and detached, or you never subscribed. Nothing will wake you.';
|
|
252
|
-
return
|
|
252
|
+
return `No nodes below you (0 subscriptions, ${unread} unread). ${tail}`;
|
|
253
253
|
}
|
|
254
254
|
const working = kids.filter((k) => k.status === 'active' || k.status === 'idle');
|
|
255
255
|
const liveWaking = working.filter((k) => k.active); // active sub to a live node = it will wake me
|
|
256
256
|
const done = kids.filter((k) => k.status === 'done' || k.status === 'canceled');
|
|
257
257
|
const dead = kids.filter((k) => k.status === 'dead');
|
|
258
|
-
let verdict;
|
|
259
258
|
let line;
|
|
260
259
|
if (dead.length > 0) {
|
|
261
|
-
verdict = 'attention';
|
|
262
260
|
line = `\u26a0 ${dead.length} below you ${dead.length === 1 ? 'is' : 'are'} dead and will NOT wake you. Inspect with \`crtr node inspect show <id>\`, then re-delegate or proceed without ${dead.length === 1 ? 'it' : 'them'}.`;
|
|
263
261
|
}
|
|
264
262
|
else if (liveWaking.length > 0) {
|
|
265
|
-
verdict = 'working';
|
|
266
263
|
line = `Safe to yield \u2014 ${liveWaking.length} worker${liveWaking.length === 1 ? '' : 's'} running async will wake you on the next push. Nothing to do now; end your turn and chill.`;
|
|
267
264
|
}
|
|
268
265
|
else if (unread > 0) {
|
|
269
|
-
verdict = 'ready';
|
|
270
266
|
line = `Nothing still running, but ${unread} unread report${unread === 1 ? '' : 's'} \u2014 run \`crtr feed read\` to absorb ${unread === 1 ? 'it' : 'them'}, then continue or finish.`;
|
|
271
267
|
}
|
|
272
268
|
else {
|
|
273
|
-
verdict = 'idle';
|
|
274
269
|
line = 'Everything below you has finished and been drained \u2014 nothing is running. Continue your own work, or `crtr push final` to finish.';
|
|
275
270
|
}
|
|
276
271
|
const rows = kids.map((k) => {
|
|
@@ -279,10 +274,10 @@ const feedPeekLeaf = defineLeaf({
|
|
|
279
274
|
? `pushed ${fmtAge(k.last_push.ts)} [${k.last_push.kind}]${k.last_push.ref !== null ? ` ref:${k.last_push.ref}` : ''}`
|
|
280
275
|
: 'no push yet';
|
|
281
276
|
const glyph = STATUS_GLYPH[k.status] ?? '?';
|
|
282
|
-
return
|
|
277
|
+
return `- ${glyph} ${k.name}${sub} (${k.node_id}) \u00b7 ${k.kind} \u00b7 ${k.status} \u00b7 spawned ${fmtAge(k.spawned)} \u00b7 cyc ${k.cycles} \u00b7 ${push}`;
|
|
283
278
|
}).join('\n');
|
|
284
|
-
const
|
|
285
|
-
return
|
|
279
|
+
const summary = `${kids.length} node${kids.length === 1 ? '' : 's'} below you — ${working.length} working, ${done.length} done, ${dead.length} dead, ${unread} unread:`;
|
|
280
|
+
return `${line}\n\n${summary}\n${rows}`;
|
|
286
281
|
},
|
|
287
282
|
});
|
|
288
283
|
// ---------------------------------------------------------------------------
|