@crouton-kit/crouter 0.3.18 → 0.3.19
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 +6 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +164 -0
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +58 -0
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +60 -0
- package/dist/builtin-memory/crouter-development/personas.md +117 -0
- package/dist/builtin-memory/crouter-development/plugins.md +163 -0
- package/dist/builtin-memory/design.md +60 -0
- package/dist/builtin-memory/development.md +116 -0
- package/dist/builtin-memory/planning.md +66 -0
- package/dist/builtin-memory/spec.md +99 -0
- package/dist/builtin-personas/design/orchestrator.md +1 -1
- package/dist/builtin-personas/developer/orchestrator.md +1 -1
- package/dist/builtin-personas/lifecycle/resident.md +1 -1
- package/dist/builtin-personas/lifecycle/terminal.md +5 -2
- package/dist/builtin-personas/orchestration-kernel.md +15 -24
- package/dist/builtin-personas/plan/orchestrator.md +1 -1
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/orchestrator.md +1 -1
- package/dist/builtin-personas/waiting.md +8 -0
- package/dist/builtin-skills/skills/crouter-development/personas/orchestrator-prompt/SKILL.md +1 -1
- package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +2 -2
- package/dist/builtin-views/_lib/states.mjs +161 -0
- package/dist/builtin-views/canvas/client.mjs +303 -0
- package/dist/builtin-views/canvas/view.mjs +576 -0
- package/dist/builtin-views/git-pr/client.mjs +440 -0
- package/dist/builtin-views/git-pr/view.mjs +675 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +177 -0
- package/dist/builtin-views/inbox/sources/gmail.mjs +965 -0
- package/dist/builtin-views/inbox/sources/linkedin.mjs +427 -0
- package/dist/builtin-views/inbox/view.mjs +889 -0
- package/dist/builtin-views/linkedin/client.mjs +610 -0
- package/dist/builtin-views/linkedin/view.mjs +1171 -0
- package/dist/clients/attach/attach-cmd.d.ts +2 -0
- package/dist/clients/attach/attach-cmd.js +354 -0
- package/dist/clients/attach/chat-view.d.ts +77 -0
- package/dist/clients/attach/chat-view.js +450 -0
- package/dist/clients/attach/clipboard-image.d.ts +16 -0
- package/dist/clients/attach/clipboard-image.js +113 -0
- package/dist/clients/attach/config-load.d.ts +31 -0
- package/dist/clients/attach/config-load.js +113 -0
- package/dist/clients/attach/extension-dialogs.d.ts +29 -0
- package/dist/clients/attach/extension-dialogs.js +101 -0
- package/dist/clients/attach/input-controller.d.ts +54 -0
- package/dist/clients/attach/input-controller.js +204 -0
- package/dist/clients/attach/slash-commands.d.ts +36 -0
- package/dist/clients/attach/slash-commands.js +200 -0
- package/dist/clients/attach/view-socket.d.ts +48 -0
- package/dist/clients/attach/view-socket.js +126 -0
- package/dist/commands/attention.js +3 -3
- package/dist/commands/canvas-prune.js +1 -1
- package/dist/commands/daemon.js +3 -2
- package/dist/commands/human/prompts.js +1 -1
- package/dist/commands/human/queue.js +43 -8
- package/dist/commands/human/shared.d.ts +2 -0
- package/dist/commands/memory/find.d.ts +1 -0
- package/dist/commands/memory/find.js +180 -0
- package/dist/commands/memory/lint.d.ts +1 -0
- package/dist/commands/memory/lint.js +140 -0
- package/dist/commands/memory/list.d.ts +1 -0
- package/dist/commands/memory/list.js +79 -0
- package/dist/commands/memory/read.js +103 -0
- package/dist/commands/memory/shared.d.ts +30 -0
- package/dist/commands/memory/shared.js +122 -0
- package/dist/commands/memory/write.d.ts +1 -0
- package/dist/commands/memory/write.js +85 -0
- package/dist/commands/memory.d.ts +2 -0
- package/dist/commands/memory.js +27 -0
- package/dist/commands/node.d.ts +3 -2
- package/dist/commands/node.js +607 -61
- package/dist/commands/pkg/market-manage.js +1 -1
- package/dist/commands/push.js +6 -6
- package/dist/commands/revive.js +1 -1
- package/dist/commands/skill/author.js +1 -1
- package/dist/commands/skill/shared.d.ts +1 -8
- package/dist/commands/skill/shared.js +2 -55
- package/dist/commands/skill.js +9 -14
- package/dist/commands/sys/doctor.js +1 -1
- package/dist/commands/sys/update.js +1 -1
- package/dist/commands/view-cycle.d.ts +2 -0
- package/dist/commands/view-cycle.js +125 -0
- package/dist/commands/view-list.d.ts +2 -0
- package/dist/commands/view-list.js +66 -0
- package/dist/commands/view-new.d.ts +2 -0
- package/dist/commands/view-new.js +70 -0
- package/dist/commands/view-pick.d.ts +2 -0
- package/dist/commands/view-pick.js +119 -0
- package/dist/commands/view-run.d.ts +2 -0
- package/dist/commands/view-run.js +191 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +29 -0
- package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +677 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.d.ts +1 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +166 -0
- package/dist/core/__tests__/cascade-close.test.js +12 -2
- package/dist/core/__tests__/child-death-wake.test.d.ts +1 -0
- package/dist/core/__tests__/child-death-wake.test.js +245 -0
- package/dist/core/__tests__/context-intro.test.js +76 -62
- package/dist/core/__tests__/daemon-boot.test.js +14 -5
- package/dist/core/__tests__/daemon-liveness.test.js +34 -9
- package/dist/core/__tests__/detach-focus.test.d.ts +1 -0
- package/dist/core/__tests__/detach-focus.test.js +206 -0
- package/dist/core/__tests__/draw-style.test.d.ts +1 -0
- package/dist/core/__tests__/draw-style.test.js +258 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.d.ts +2 -0
- package/dist/core/__tests__/fixtures/c3-custom-provider-ext.js +18 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +138 -0
- package/dist/core/__tests__/fixtures/fake-engine.js +614 -0
- package/dist/core/__tests__/fixtures/fake-pi-host.js +1 -0
- package/dist/core/__tests__/flagship-lifecycle.test.js +7 -1
- package/dist/core/__tests__/frame-decoder-perf.test.d.ts +1 -0
- package/dist/core/__tests__/frame-decoder-perf.test.js +198 -0
- package/dist/core/__tests__/helpers/harness.d.ts +9 -0
- package/dist/core/__tests__/helpers/harness.js +111 -1
- package/dist/core/__tests__/home-session.test.js +41 -8
- package/dist/core/__tests__/human-new-window-regression.test.d.ts +1 -0
- package/dist/core/__tests__/human-new-window-regression.test.js +101 -0
- package/dist/core/__tests__/kickoff.test.js +37 -3
- package/dist/core/__tests__/live-mutation.test.js +50 -33
- package/dist/core/__tests__/memory.test.js +23 -115
- package/dist/core/__tests__/placement-focus.test.js +5 -0
- package/dist/core/__tests__/placement-teardown.test.js +54 -11
- package/dist/core/__tests__/relaunch.test.js +4 -3
- package/dist/core/__tests__/review-render-pane-regression.test.d.ts +1 -0
- package/dist/core/__tests__/review-render-pane-regression.test.js +133 -0
- package/dist/core/__tests__/spawn-root.test.js +10 -0
- package/dist/core/__tests__/spike-harness.test.js +1 -0
- package/dist/core/__tests__/wake-bearings.test.d.ts +1 -0
- package/dist/core/__tests__/wake-bearings.test.js +156 -0
- package/dist/core/__tests__/wake-origin.test.d.ts +1 -0
- package/dist/core/__tests__/wake-origin.test.js +110 -0
- package/dist/core/bootstrap.js +1 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +1 -0
- package/dist/core/canvas/browse/app.js +24 -2
- package/dist/core/canvas/browse/model.d.ts +38 -2
- package/dist/core/canvas/browse/model.js +134 -10
- package/dist/core/canvas/browse/render.d.ts +6 -12
- package/dist/core/canvas/browse/render.js +72 -104
- package/dist/core/canvas/canvas.js +15 -22
- package/dist/core/canvas/db.js +46 -0
- package/dist/core/canvas/index.d.ts +1 -0
- package/dist/core/canvas/index.js +1 -0
- package/dist/core/canvas/pid.d.ts +4 -0
- package/dist/core/canvas/pid.js +23 -0
- package/dist/core/canvas/render.d.ts +11 -2
- package/dist/core/canvas/render.js +69 -0
- package/dist/core/canvas/types.d.ts +85 -3
- package/dist/core/canvas/wakeups.d.ts +76 -0
- package/dist/core/canvas/wakeups.js +185 -0
- package/dist/core/config.js +4 -1
- package/dist/core/frontmatter.js +37 -124
- package/dist/core/memory-resolver.d.ts +49 -0
- package/dist/core/memory-resolver.js +141 -0
- package/dist/core/personas/loader.d.ts +7 -0
- package/dist/core/personas/loader.js +36 -5
- package/dist/core/personas/resolve.js +13 -3
- package/dist/core/predicate.d.ts +63 -0
- package/dist/core/predicate.js +189 -0
- package/dist/core/resolver.js +26 -5
- package/dist/core/runtime/bearings.d.ts +53 -12
- package/dist/core/runtime/bearings.js +132 -59
- package/dist/core/runtime/broker-cli.d.ts +1 -0
- package/dist/core/runtime/broker-cli.js +46 -0
- package/dist/core/runtime/broker-protocol.d.ts +332 -0
- package/dist/core/runtime/broker-protocol.js +153 -0
- package/dist/core/runtime/broker-sdk.d.ts +48 -0
- package/dist/core/runtime/broker-sdk.js +72 -0
- package/dist/core/runtime/broker.d.ts +55 -0
- package/dist/core/runtime/broker.js +1128 -0
- package/dist/core/runtime/close.js +35 -6
- package/dist/core/runtime/host.d.ts +53 -0
- package/dist/core/runtime/host.js +186 -0
- package/dist/core/runtime/kickoff.d.ts +2 -1
- package/dist/core/runtime/kickoff.js +91 -5
- package/dist/core/runtime/launch.d.ts +45 -2
- package/dist/core/runtime/launch.js +65 -2
- package/dist/core/runtime/lifecycle.js +23 -6
- package/dist/core/runtime/memory.d.ts +2 -42
- package/dist/core/runtime/memory.js +11 -162
- package/dist/core/runtime/nodes.d.ts +33 -0
- package/dist/core/runtime/nodes.js +59 -1
- package/dist/core/runtime/persona.js +21 -11
- package/dist/core/runtime/pi-vendored.d.ts +18 -0
- package/dist/core/runtime/pi-vendored.js +49 -0
- package/dist/core/runtime/placement.d.ts +32 -14
- package/dist/core/runtime/placement.js +206 -52
- package/dist/core/runtime/promote.d.ts +0 -6
- package/dist/core/runtime/promote.js +1 -12
- package/dist/core/runtime/{demote.d.ts → recycle.d.ts} +5 -5
- package/dist/core/runtime/{demote.js → recycle.js} +27 -11
- package/dist/core/runtime/reset.js +8 -6
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +19 -0
- package/dist/core/runtime/spawn.js +75 -22
- package/dist/core/runtime/stop-guard.d.ts +1 -1
- package/dist/core/runtime/stop-guard.js +6 -1
- package/dist/core/runtime/tmux-chrome.d.ts +1 -1
- package/dist/core/runtime/tmux-chrome.js +1 -1
- package/dist/core/runtime/tmux.d.ts +28 -0
- package/dist/core/runtime/tmux.js +80 -6
- package/dist/core/scope.d.ts +11 -0
- package/dist/core/scope.js +39 -0
- package/dist/core/spawn.d.ts +5 -0
- package/dist/core/spawn.js +20 -2
- package/dist/core/substrate/gate.d.ts +13 -0
- package/dist/core/substrate/gate.js +21 -0
- package/dist/core/substrate/index.d.ts +7 -0
- package/dist/core/substrate/index.js +18 -0
- package/dist/core/substrate/on-read.d.ts +14 -0
- package/dist/core/substrate/on-read.js +292 -0
- package/dist/core/substrate/render.d.ts +25 -0
- package/dist/core/substrate/render.js +256 -0
- package/dist/core/substrate/schema.d.ts +76 -0
- package/dist/core/substrate/schema.js +124 -0
- package/dist/core/substrate/session-cache.d.ts +30 -0
- package/dist/core/substrate/session-cache.js +77 -0
- package/dist/core/substrate/subject.d.ts +41 -0
- package/dist/core/substrate/subject.js +54 -0
- package/dist/core/tui/contract.d.ts +83 -0
- package/dist/core/tui/contract.js +8 -0
- package/dist/core/tui/draw.d.ts +96 -0
- package/dist/core/tui/draw.js +339 -0
- package/dist/core/tui/host.d.ts +29 -0
- package/dist/core/tui/host.js +379 -0
- package/dist/core/tui/loader.d.ts +16 -0
- package/dist/core/tui/loader.js +94 -0
- package/dist/core/{canvas/browse → tui}/terminal.js +7 -6
- package/dist/core/wake.d.ts +86 -0
- package/dist/core/wake.js +308 -0
- package/dist/daemon/crtrd.d.ts +29 -4
- package/dist/daemon/crtrd.js +662 -46
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.d.ts +1 -0
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +171 -0
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +19 -12
- package/dist/pi-extensions/canvas-commands.d.ts +3 -0
- package/dist/pi-extensions/canvas-commands.js +10 -0
- package/dist/pi-extensions/canvas-context-intro.d.ts +17 -0
- package/dist/pi-extensions/canvas-context-intro.js +55 -15
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +44 -0
- package/dist/pi-extensions/canvas-doc-substrate.js +112 -0
- package/dist/pi-extensions/canvas-nav.d.ts +3 -0
- package/dist/pi-extensions/canvas-nav.js +87 -23
- package/dist/pi-extensions/canvas-stophook.js +17 -8
- package/dist/pi-extensions/canvas-view.d.ts +21 -0
- package/dist/pi-extensions/canvas-view.js +75 -0
- package/dist/prompts/skill.js +19 -26
- package/dist/prompts/view.d.ts +7 -0
- package/dist/prompts/view.js +101 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +1 -0
- package/package.json +9 -4
- package/dist/commands/skill/find.d.ts +0 -4
- package/dist/commands/skill/find.js +0 -257
- package/dist/commands/skill/read.js +0 -91
- /package/dist/commands/{skill → memory}/read.d.ts +0 -0
- /package/dist/core/{canvas/browse → tui}/terminal.d.ts +0 -0
package/dist/core/scope.js
CHANGED
|
@@ -12,6 +12,26 @@ export function builtinSkillsRoot() {
|
|
|
12
12
|
const pkgDir = dirname(coreDir); // src/ or dist/
|
|
13
13
|
return join(pkgDir, 'builtin-skills');
|
|
14
14
|
}
|
|
15
|
+
export function builtinViewsRoot() {
|
|
16
|
+
// Resolved relative to this file exactly like builtinSkillsRoot — sibling of
|
|
17
|
+
// builtin-skills: dist/core/scope.js → dist/builtin-views/ (src/ at dev time).
|
|
18
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
19
|
+
const coreDir = dirname(thisFile);
|
|
20
|
+
const pkgDir = dirname(coreDir); // src/ or dist/
|
|
21
|
+
return join(pkgDir, 'builtin-views');
|
|
22
|
+
}
|
|
23
|
+
export function builtinMemoryRoot() {
|
|
24
|
+
// The substrate's shipped built-in documents live in their OWN package dir,
|
|
25
|
+
// a sibling of builtin-skills — resolved relative to this file exactly like
|
|
26
|
+
// builtinSkillsRoot/builtinViewsRoot: dist/core/scope.js → dist/builtin-memory/
|
|
27
|
+
// (src/ at dev time). This is a HARD special case (design verdict m2): builtin
|
|
28
|
+
// memory is NOT scopeRoot('builtin')/memory, which would land under
|
|
29
|
+
// builtin-skills/ — builtin's scopeRoot is builtinSkillsRoot().
|
|
30
|
+
const thisFile = fileURLToPath(import.meta.url);
|
|
31
|
+
const coreDir = dirname(thisFile);
|
|
32
|
+
const pkgDir = dirname(coreDir); // src/ or dist/
|
|
33
|
+
return join(pkgDir, 'builtin-memory');
|
|
34
|
+
}
|
|
15
35
|
export function userScopeRoot() {
|
|
16
36
|
return join(homedir(), CRTR_DIR_NAME);
|
|
17
37
|
}
|
|
@@ -77,6 +97,25 @@ export function scopeSkillsDir(scope) {
|
|
|
77
97
|
const root = scopeRoot(scope);
|
|
78
98
|
return root ? join(root, 'skills') : null;
|
|
79
99
|
}
|
|
100
|
+
/** Where substrate memory documents live per scope. Builtin memory is the
|
|
101
|
+
* special case — its own package dir (builtinMemoryRoot, a sibling of
|
|
102
|
+
* builtin-skills), NOT scopeRoot('builtin')/memory — mirroring viewsDir's
|
|
103
|
+
* builtin handling. User and project memory live at scopeRoot(scope)/memory. */
|
|
104
|
+
export function scopeMemoryDir(scope) {
|
|
105
|
+
if (scope === 'builtin')
|
|
106
|
+
return builtinMemoryRoot();
|
|
107
|
+
const root = scope === 'user' ? userScopeRoot() : projectScopeRoot();
|
|
108
|
+
return root ? join(root, 'memory') : null;
|
|
109
|
+
}
|
|
110
|
+
/** Where view definition dirs live per scope. Builtin views sit directly under
|
|
111
|
+
* builtinViewsRoot() (no `views/` segment — they ARE the builtin views dir),
|
|
112
|
+
* matching the loader's `<root>/<name>/view.mjs` resolution. */
|
|
113
|
+
export function viewsDir(scope) {
|
|
114
|
+
if (scope === 'builtin')
|
|
115
|
+
return builtinViewsRoot();
|
|
116
|
+
const root = scope === 'user' ? userScopeRoot() : projectScopeRoot();
|
|
117
|
+
return root ? join(root, 'views') : null;
|
|
118
|
+
}
|
|
80
119
|
export function resolveScopeArg(scopeArg) {
|
|
81
120
|
if (scopeArg === undefined)
|
|
82
121
|
return 'all';
|
package/dist/core/spawn.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export declare function countPanesInCurrentWindow(): number;
|
|
|
11
11
|
* output on an unresolvable pane, so test for non-empty stdout, not just `.ok`.
|
|
12
12
|
* False outside tmux / on error. */
|
|
13
13
|
export declare function paneAlive(pane: string): boolean;
|
|
14
|
+
/** Resolve a tmux pane id to its `session:window_index` — the target form
|
|
15
|
+
* `new-window -t` accepts. tmux REJECTS a pane id for new-window ("can't
|
|
16
|
+
* specify pane here"); only split-window -t takes a pane. null outside tmux /
|
|
17
|
+
* on a bad pane id / on error / empty. */
|
|
18
|
+
export declare function paneWindowTarget(pane: string): string | null;
|
|
14
19
|
/** The active pane of the user's attached tmux client — where they are looking
|
|
15
20
|
* right now. `list-clients` first attached client, then its current pane. Used
|
|
16
21
|
* to surface a human prompt in the user's view when nothing in the asking
|
package/dist/core/spawn.js
CHANGED
|
@@ -41,6 +41,19 @@ export function paneAlive(pane) {
|
|
|
41
41
|
});
|
|
42
42
|
return r.status === 0 && r.stdout.trim() !== '';
|
|
43
43
|
}
|
|
44
|
+
/** Resolve a tmux pane id to its `session:window_index` — the target form
|
|
45
|
+
* `new-window -t` accepts. tmux REJECTS a pane id for new-window ("can't
|
|
46
|
+
* specify pane here"); only split-window -t takes a pane. null outside tmux /
|
|
47
|
+
* on a bad pane id / on error / empty. */
|
|
48
|
+
export function paneWindowTarget(pane) {
|
|
49
|
+
if (!isInTmux() || !/^%\d+$/.test(pane))
|
|
50
|
+
return null;
|
|
51
|
+
const r = spawnSync('tmux', ['display-message', '-p', '-t', pane, '#{session_name}:#{window_index}'], { encoding: 'utf8' });
|
|
52
|
+
if (r.status !== 0)
|
|
53
|
+
return null;
|
|
54
|
+
const t = r.stdout.trim();
|
|
55
|
+
return t !== '' ? t : null;
|
|
56
|
+
}
|
|
44
57
|
/** The active pane of the user's attached tmux client — where they are looking
|
|
45
58
|
* right now. `list-clients` first attached client, then its current pane. Used
|
|
46
59
|
* to surface a human prompt in the user's view when nothing in the asking
|
|
@@ -101,8 +114,13 @@ export function spawnAndDetach(opts) {
|
|
|
101
114
|
if (opts.detached === true)
|
|
102
115
|
splitArgs.push('-d'); // don't switch the client to it
|
|
103
116
|
if (opts.targetPane !== undefined && opts.targetPane !== '') {
|
|
104
|
-
// -
|
|
105
|
-
|
|
117
|
+
// new-window -t REJECTS a pane id (tmux exits 1: "can't specify pane
|
|
118
|
+
// here") — only split-window -t accepts a pane. Resolve the target pane to
|
|
119
|
+
// its session:window first; -a then inserts the new window right after it.
|
|
120
|
+
// If the pane can't be resolved, fall back to no -t (tmux uses current).
|
|
121
|
+
const winTarget = paneWindowTarget(opts.targetPane);
|
|
122
|
+
if (winTarget !== null)
|
|
123
|
+
splitArgs.push('-a', '-t', winTarget);
|
|
106
124
|
}
|
|
107
125
|
}
|
|
108
126
|
else {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SubstrateDoc } from './schema.js';
|
|
2
|
+
import type { NodeConfigSubject } from './subject.js';
|
|
3
|
+
/** Does `doc` pass its gate for `subject`?
|
|
4
|
+
*
|
|
5
|
+
* - No gate (field absent) ⇒ `true`: always eligible (the common case).
|
|
6
|
+
* - A gate present ⇒ the matcher engine's verdict. An empty `gate: {}` is inert
|
|
7
|
+
* and returns `false` (never matches) per design §4 — so an author cannot
|
|
8
|
+
* write an always-eligible-via-empty-predicate doc.
|
|
9
|
+
*
|
|
10
|
+
* Eligibility only. A failing gate excludes the doc from BOTH automatic hooks
|
|
11
|
+
* for this node; it remains findable by `crtr memory find` (search ignores
|
|
12
|
+
* gate + rung). */
|
|
13
|
+
export declare function gatePasses(doc: SubstrateDoc, subject: NodeConfigSubject): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// gate.ts — gate evaluation. A document's optional `gate` predicate decides
|
|
2
|
+
// whether it is ELIGIBLE to surface at all for a given node (orthogonal to the
|
|
3
|
+
// rung, which decides how much). See design-substrate.md §4 + §6. Pure function
|
|
4
|
+
// over (parsed doc, assembled subject); the matcher engine in predicate.ts does
|
|
5
|
+
// the actual matching — we do NOT reimplement it here.
|
|
6
|
+
import { evalCondition } from '../predicate.js';
|
|
7
|
+
/** Does `doc` pass its gate for `subject`?
|
|
8
|
+
*
|
|
9
|
+
* - No gate (field absent) ⇒ `true`: always eligible (the common case).
|
|
10
|
+
* - A gate present ⇒ the matcher engine's verdict. An empty `gate: {}` is inert
|
|
11
|
+
* and returns `false` (never matches) per design §4 — so an author cannot
|
|
12
|
+
* write an always-eligible-via-empty-predicate doc.
|
|
13
|
+
*
|
|
14
|
+
* Eligibility only. A failing gate excludes the doc from BOTH automatic hooks
|
|
15
|
+
* for this node; it remains findable by `crtr memory find` (search ignores
|
|
16
|
+
* gate + rung). */
|
|
17
|
+
export function gatePasses(doc, subject) {
|
|
18
|
+
if (doc.gate === undefined)
|
|
19
|
+
return true;
|
|
20
|
+
return evalCondition(doc.gate, subject);
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, KIND_DEFAULT_RUNGS, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, } from './schema.js';
|
|
2
|
+
export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc } from './schema.js';
|
|
3
|
+
export { scopeForCwd, spineDepth, assembleNodeSubject } from './subject.js';
|
|
4
|
+
export type { NodeConfigSubject } from './subject.js';
|
|
5
|
+
export { gatePasses } from './gate.js';
|
|
6
|
+
export { renderSkillsSection, renderPreferencesSection, renderReferencesBlock } from './render.js';
|
|
7
|
+
export { renderOnReadDocs } from './on-read.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// substrate/ — the unified document-substrate keystone: the frontmatter schema,
|
|
2
|
+
// the 4-rung visibility ladder, the kind→default-rungs table, the node-config
|
|
3
|
+
// subject assembly, and gate evaluation. Pure + well-typed; the shared base the
|
|
4
|
+
// CLI verbs, boot render, on-read render, and migrator all build on. See
|
|
5
|
+
// design-substrate.md §4/§9 + plan-substrate.md §2.
|
|
6
|
+
export {
|
|
7
|
+
// kinds
|
|
8
|
+
KINDS, isDocKind,
|
|
9
|
+
// ladder
|
|
10
|
+
RUNGS, rungRank, rungAtLeast,
|
|
11
|
+
// defaults
|
|
12
|
+
KIND_DEFAULT_RUNGS,
|
|
13
|
+
// parse + render-shared helpers
|
|
14
|
+
parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, } from './schema.js';
|
|
15
|
+
export { scopeForCwd, spineDepth, assembleNodeSubject } from './subject.js';
|
|
16
|
+
export { gatePasses } from './gate.js';
|
|
17
|
+
export { renderSkillsSection, renderPreferencesSection, renderReferencesBlock } from './render.js';
|
|
18
|
+
export { renderOnReadDocs } from './on-read.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the substrate docs that should surface alongside a just-read file.
|
|
3
|
+
*
|
|
4
|
+
* @param nodeId the canvas node whose subject gates the docs.
|
|
5
|
+
* @param readFilePath the path the `read` tool returned (absolute or not — it
|
|
6
|
+
* is resolved to a realpath internally).
|
|
7
|
+
* @param seen the CALLER-owned, per-session set of already-injected
|
|
8
|
+
* doc realpaths. Docs already present are skipped; newly
|
|
9
|
+
* injected docs are added. Pass the same set across reads
|
|
10
|
+
* within a session (clear it on session_start) to get the
|
|
11
|
+
* once-per-session dedup; omit it for a standalone render.
|
|
12
|
+
* @returns the `<auto-loaded-context>` envelope, or '' when nothing surfaces.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderOnReadDocs(nodeId: string, readFilePath: string, seen?: Set<string>): string;
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// on-read.ts — the file-read-visibility render for the document substrate.
|
|
2
|
+
//
|
|
3
|
+
// When a `read` tool call returns, the canvas-doc-substrate pi extension calls
|
|
4
|
+
// renderOnReadDocs() to surface the substrate docs that should appear ALONGSIDE
|
|
5
|
+
// the file just read — each at its FILE-READ-VISIBILITY rung (NOT the
|
|
6
|
+
// system-prompt rung the boot render uses). Two independent triggers decide
|
|
7
|
+
// which docs surface (design §4/§6; plan-substrate.md track D1):
|
|
8
|
+
//
|
|
9
|
+
// • POSITIONAL — walk the read file's ancestor dirs; any doc living in an
|
|
10
|
+
// ancestor's `.crouter/memory/` surfaces (a doc surfaces when a file
|
|
11
|
+
// beside/under its own scope dir is read). This mirrors nested-context's
|
|
12
|
+
// `.claude/rules` ancestor walk, but keyed on `.crouter/memory/`.
|
|
13
|
+
// • applies-to GLOB — any RESOLVED substrate doc (user/project/builtin scope)
|
|
14
|
+
// whose `appliesTo` glob matches the read file path surfaces, regardless of
|
|
15
|
+
// where the read file sits relative to the doc.
|
|
16
|
+
//
|
|
17
|
+
// Each candidate runs the substrate pipeline at its fileReadVisibility rung:
|
|
18
|
+
// parse → gatePasses(doc, assembleNodeSubject(nodeId)) → render
|
|
19
|
+
// (content → body, preview → previewLine, name → bare tag, none → skip).
|
|
20
|
+
// The result is the faithful envelope (verdict n1):
|
|
21
|
+
// <auto-loaded-context file="…">
|
|
22
|
+
// <doc kind="…" name="…" src="…" triggered-by="…">…body/preview…</doc>
|
|
23
|
+
// </auto-loaded-context>
|
|
24
|
+
// Returns '' when nothing surfaces.
|
|
25
|
+
//
|
|
26
|
+
// Pure + defensive: reads disk + the resolver + canvas-db subject assembly; no
|
|
27
|
+
// writes, no side effects. Every disk/parse/glob step is wrapped so one bad doc
|
|
28
|
+
// can never throw the whole render (the extension is additionally inert on
|
|
29
|
+
// error). The CALLER owns the per-session `seen` realpath set (cleared on
|
|
30
|
+
// session_start) and threads it in, so a given doc is injected at most once per
|
|
31
|
+
// session across repeated reads.
|
|
32
|
+
import { realpathSync } from 'node:fs';
|
|
33
|
+
import { homedir } from 'node:os';
|
|
34
|
+
import { basename, dirname, join, matchesGlob, parse, relative, sep } from 'node:path';
|
|
35
|
+
import { CRTR_DIR_NAME } from '../../types.js';
|
|
36
|
+
import { pathExists, readText, walkFiles } from '../fs-utils.js';
|
|
37
|
+
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
38
|
+
import { listAllMemoryDocs } from '../memory-resolver.js';
|
|
39
|
+
import { assembleNodeSubject, gatePasses, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, } from './index.js';
|
|
40
|
+
import { cachedSubstrateDocs } from './session-cache.js';
|
|
41
|
+
// Ancestor dirs we never look inside for a `.crouter/memory/` store (the read
|
|
42
|
+
// file may live under a build/dependency tree; `.crouter` is NOT junk here — it
|
|
43
|
+
// is the segment we explicitly join onto each surviving ancestor).
|
|
44
|
+
const JUNK_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.cache', '.yalc']);
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Small path helpers (mirror the on-read precedent — nested-context /
|
|
47
|
+
// frontmatter-rules — so the injected envelope matches their faithful shape).
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
function realpathOrSelf(p) {
|
|
50
|
+
try {
|
|
51
|
+
return realpathSync(p);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return p;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Escape a value for an XML-ish attribute in the injected envelope. */
|
|
58
|
+
function attr(s) {
|
|
59
|
+
return s
|
|
60
|
+
.replace(/&/g, '&')
|
|
61
|
+
.replace(/"/g, '"')
|
|
62
|
+
.replace(/</g, '<')
|
|
63
|
+
.replace(/>/g, '>');
|
|
64
|
+
}
|
|
65
|
+
/** Nearest enclosing git repo root for a path (walk up looking for `.git`). */
|
|
66
|
+
function gitRootOf(p) {
|
|
67
|
+
let d = p;
|
|
68
|
+
const root = parse(d).root;
|
|
69
|
+
while (true) {
|
|
70
|
+
if (pathExists(join(d, '.git')))
|
|
71
|
+
return d;
|
|
72
|
+
if (d === root)
|
|
73
|
+
return null;
|
|
74
|
+
const parent = dirname(d);
|
|
75
|
+
if (parent === d)
|
|
76
|
+
return null;
|
|
77
|
+
d = parent;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Display a path relative to its nearest git repo root, else absolute. */
|
|
81
|
+
function disp(p) {
|
|
82
|
+
const root = gitRootOf(p);
|
|
83
|
+
if (!root)
|
|
84
|
+
return p;
|
|
85
|
+
const rel = relative(root, p);
|
|
86
|
+
return rel === '' ? '.' : rel;
|
|
87
|
+
}
|
|
88
|
+
function isJunkAncestor(dir) {
|
|
89
|
+
return dir.split(sep).some((seg) => JUNK_DIRS.has(seg));
|
|
90
|
+
}
|
|
91
|
+
/** Load one positionally-discovered `.crouter/memory/` file into a SubstrateDoc,
|
|
92
|
+
* or null when it is not a substrate doc / unreadable. `scope` is cosmetic here
|
|
93
|
+
* (gate eval keys off the NODE subject, render off name/body/rung). */
|
|
94
|
+
function loadPositionalDoc(file, memDir, scope) {
|
|
95
|
+
try {
|
|
96
|
+
const name = relative(memDir, file)
|
|
97
|
+
.replace(/\.md$/i, '')
|
|
98
|
+
.split(sep)
|
|
99
|
+
.join('/');
|
|
100
|
+
if (name === '')
|
|
101
|
+
return null;
|
|
102
|
+
const { data, body } = parseFrontmatterGeneric(readText(file));
|
|
103
|
+
const schema = parseSubstrateFrontmatter(data);
|
|
104
|
+
if (schema === null)
|
|
105
|
+
return null;
|
|
106
|
+
return { ...schema, name, scope, path: file, body };
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function safeWalkMd(dir) {
|
|
113
|
+
try {
|
|
114
|
+
return walkFiles(dir, (n) => n.toLowerCase().endsWith('.md'));
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** POSITIONAL trigger: every substrate doc in an ancestor dir's
|
|
121
|
+
* `.crouter/memory/`, walking from the read file up to $HOME (or the
|
|
122
|
+
* filesystem root for a read outside $HOME), skipping junk ancestors. */
|
|
123
|
+
function positionalCandidates(absReadFile) {
|
|
124
|
+
const out = [];
|
|
125
|
+
const seenDocPaths = new Set();
|
|
126
|
+
const home = homedir();
|
|
127
|
+
const fsRoot = parse(absReadFile).root;
|
|
128
|
+
let dir = dirname(absReadFile);
|
|
129
|
+
let depth = 0;
|
|
130
|
+
while (true) {
|
|
131
|
+
if (!isJunkAncestor(dir)) {
|
|
132
|
+
const memDir = join(dir, CRTR_DIR_NAME, 'memory');
|
|
133
|
+
if (pathExists(memDir)) {
|
|
134
|
+
const scope = dir === home ? 'user' : 'project';
|
|
135
|
+
for (const file of safeWalkMd(memDir)) {
|
|
136
|
+
const real = realpathOrSelf(file);
|
|
137
|
+
if (seenDocPaths.has(real))
|
|
138
|
+
continue;
|
|
139
|
+
seenDocPaths.add(real);
|
|
140
|
+
const doc = loadPositionalDoc(file, memDir, scope);
|
|
141
|
+
if (doc)
|
|
142
|
+
out.push({ doc, realpath: real, order: depth });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (dir === home || dir === fsRoot)
|
|
147
|
+
break;
|
|
148
|
+
const parent = dirname(dir);
|
|
149
|
+
if (parent === dir)
|
|
150
|
+
break;
|
|
151
|
+
dir = parent;
|
|
152
|
+
depth += 1;
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
/** The user/project scope root that owns a resolved doc: `<root>/.crouter/memory/…`
|
|
157
|
+
* → `<root>`. Builtin docs (no `.crouter` segment) return null. Used to test an
|
|
158
|
+
* `appliesTo` glob against a read path RELATIVE to the doc's own project root. */
|
|
159
|
+
function owningRootOf(doc) {
|
|
160
|
+
const parts = doc.path.split(sep);
|
|
161
|
+
const idx = parts.lastIndexOf(CRTR_DIR_NAME);
|
|
162
|
+
if (idx <= 0)
|
|
163
|
+
return null;
|
|
164
|
+
return parts.slice(0, idx).join(sep) || sep;
|
|
165
|
+
}
|
|
166
|
+
function globMatches(glob, absReadFile, owningRoot) {
|
|
167
|
+
const targets = [absReadFile, basename(absReadFile)];
|
|
168
|
+
if (owningRoot)
|
|
169
|
+
targets.push(relative(owningRoot, absReadFile));
|
|
170
|
+
return targets.some((t) => {
|
|
171
|
+
try {
|
|
172
|
+
return matchesGlob(t, glob);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return false; // an invalid glob never matches
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/** applies-to GLOB trigger: every RESOLVED substrate doc whose `appliesTo` glob
|
|
180
|
+
* matches the read path. `taken` carries the realpaths already claimed by the
|
|
181
|
+
* positional pass, so a doc found both ways is not double-counted.
|
|
182
|
+
* Uses the per-session cache so the corpus is not re-walked+re-parsed on every
|
|
183
|
+
* read tool call (O(reads × corpus) without the cache). */
|
|
184
|
+
function appliesToCandidates(absReadFile, taken) {
|
|
185
|
+
let docs;
|
|
186
|
+
try {
|
|
187
|
+
docs = cachedSubstrateDocs(listAllMemoryDocs, parseSubstrateDoc);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const out = [];
|
|
193
|
+
for (const doc of docs) {
|
|
194
|
+
const globs = doc.appliesTo;
|
|
195
|
+
if (!globs || globs.length === 0)
|
|
196
|
+
continue;
|
|
197
|
+
const real = realpathOrSelf(doc.path);
|
|
198
|
+
if (taken.has(real))
|
|
199
|
+
continue;
|
|
200
|
+
if (!globs.some((g) => globMatches(g, absReadFile, owningRootOf(doc))))
|
|
201
|
+
continue;
|
|
202
|
+
taken.add(real);
|
|
203
|
+
out.push({ doc, realpath: real, order: -1 });
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Per-doc envelope render.
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
/** One doc as a `<doc …>` element at its fileReadVisibility rung, or null when
|
|
211
|
+
* that rung is `none`. `content` → full body; `preview` → the routing line;
|
|
212
|
+
* `name` → a bare self-closed tag (the `name=` attribute IS the surface). */
|
|
213
|
+
function renderDocEnvelope(doc, absReadFile) {
|
|
214
|
+
const rung = doc.fileReadVisibility;
|
|
215
|
+
if (rung === 'none')
|
|
216
|
+
return null;
|
|
217
|
+
let body = '';
|
|
218
|
+
if (rung === 'content')
|
|
219
|
+
body = doc.body.trim();
|
|
220
|
+
else if (rung === 'preview')
|
|
221
|
+
body = previewLine(doc);
|
|
222
|
+
// 'name' → body stays '' (the tag's name attribute is the whole surface).
|
|
223
|
+
const attrs = `kind="${attr(doc.kind)}" name="${attr(doc.name)}" ` +
|
|
224
|
+
`src="${attr(disp(doc.path))}" triggered-by="${attr(disp(absReadFile))}"`;
|
|
225
|
+
return body === '' ? `<doc ${attrs} />` : `<doc ${attrs}>\n${body}\n</doc>`;
|
|
226
|
+
}
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Public entry point.
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
/**
|
|
231
|
+
* Render the substrate docs that should surface alongside a just-read file.
|
|
232
|
+
*
|
|
233
|
+
* @param nodeId the canvas node whose subject gates the docs.
|
|
234
|
+
* @param readFilePath the path the `read` tool returned (absolute or not — it
|
|
235
|
+
* is resolved to a realpath internally).
|
|
236
|
+
* @param seen the CALLER-owned, per-session set of already-injected
|
|
237
|
+
* doc realpaths. Docs already present are skipped; newly
|
|
238
|
+
* injected docs are added. Pass the same set across reads
|
|
239
|
+
* within a session (clear it on session_start) to get the
|
|
240
|
+
* once-per-session dedup; omit it for a standalone render.
|
|
241
|
+
* @returns the `<auto-loaded-context>` envelope, or '' when nothing surfaces.
|
|
242
|
+
*/
|
|
243
|
+
export function renderOnReadDocs(nodeId, readFilePath, seen = new Set()) {
|
|
244
|
+
let subject;
|
|
245
|
+
try {
|
|
246
|
+
subject = assembleNodeSubject(nodeId);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return '';
|
|
250
|
+
}
|
|
251
|
+
if (subject === null)
|
|
252
|
+
return '';
|
|
253
|
+
const absReadFile = realpathOrSelf(readFilePath);
|
|
254
|
+
let candidates;
|
|
255
|
+
try {
|
|
256
|
+
const positional = positionalCandidates(absReadFile);
|
|
257
|
+
const taken = new Set(positional.map((c) => c.realpath));
|
|
258
|
+
const byGlob = appliesToCandidates(absReadFile, taken);
|
|
259
|
+
candidates = [...positional, ...byGlob];
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
// Outermost-first: the nearest/most-specific doc reads last — closest to the
|
|
265
|
+
// file content that follows it (the applies-to set, order -1, trails).
|
|
266
|
+
candidates.sort((a, b) => b.order - a.order);
|
|
267
|
+
const rendered = [];
|
|
268
|
+
for (const c of candidates) {
|
|
269
|
+
// Never re-surface the doc the agent is literally reading.
|
|
270
|
+
if (c.realpath === absReadFile)
|
|
271
|
+
continue;
|
|
272
|
+
// Once-per-session dedup (caller-owned set).
|
|
273
|
+
if (seen.has(c.realpath))
|
|
274
|
+
continue;
|
|
275
|
+
let block;
|
|
276
|
+
try {
|
|
277
|
+
if (!gatePasses(c.doc, subject))
|
|
278
|
+
continue; // gated out for this node
|
|
279
|
+
block = renderDocEnvelope(c.doc, absReadFile);
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
continue; // a single bad doc never breaks the read
|
|
283
|
+
}
|
|
284
|
+
if (block === null)
|
|
285
|
+
continue; // fileReadVisibility 'none' — not a read surface
|
|
286
|
+
seen.add(c.realpath); // mark injected only once it actually surfaces
|
|
287
|
+
rendered.push(block);
|
|
288
|
+
}
|
|
289
|
+
if (rendered.length === 0)
|
|
290
|
+
return '';
|
|
291
|
+
return `<auto-loaded-context file="${attr(disp(absReadFile))}">\n${rendered.join('\n')}\n</auto-loaded-context>`;
|
|
292
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The `## Skills` system-prompt section: every eligible `kind: skill` doc,
|
|
2
|
+
* rendered at its `system-prompt-visibility`. `name`-rung skills collapse into
|
|
3
|
+
* one compact catalog (group-collapsed, unioned with plugin/marketplace
|
|
4
|
+
* skills); `preview`/`content`-rung skills each get a `###` sub-section.
|
|
5
|
+
* Returns '' when nothing is eligible. */
|
|
6
|
+
export declare function renderSkillsSection(nodeId: string): string;
|
|
7
|
+
/** The `## Preferences` system-prompt section: every eligible `kind: preference`
|
|
8
|
+
* doc as its own `###` sub-section, at its `system-prompt-visibility` (the
|
|
9
|
+
* preference default rung is `preview` → the routing line). Returns '' when
|
|
10
|
+
* nothing is eligible. */
|
|
11
|
+
export declare function renderPreferencesSection(nodeId: string): string;
|
|
12
|
+
/** The `## References` block embedded INSIDE the `<crtr-context>` session_start
|
|
13
|
+
* message (the bearings caller pushes the returned string into the block, or
|
|
14
|
+
* drops it when ''). Holds every eligible `kind: reference` resolver doc at its
|
|
15
|
+
* `system-prompt-visibility` (reference boot default is `none`, so only
|
|
16
|
+
* author-promoted references show) PLUS the node-local memory docs (any kind),
|
|
17
|
+
* each a `###` sub-section. Replaces the old `<memory>` block. Returns '' when
|
|
18
|
+
* nothing is eligible.
|
|
19
|
+
*
|
|
20
|
+
* DEFENSIVE: each doc is rendered in its own try/catch so a single malformed
|
|
21
|
+
* doc drops only itself (with a loud stderr warning naming the offending path),
|
|
22
|
+
* never silently swallowing the entire block (identity included). Per the CTO
|
|
23
|
+
* ruling, strictness lives at the COLLECTION layer (memory-resolver.ts); this
|
|
24
|
+
* catch is error ISOLATION at the render layer, not a fallback parser. */
|
|
25
|
+
export declare function renderReferencesBlock(nodeId: string): string;
|