@crouton-kit/crouter 0.3.18 → 0.3.20
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/PERSONA.md +1 -0
- package/dist/builtin-personas/design/orchestrator.md +2 -1
- package/dist/builtin-personas/developer/PERSONA.md +1 -0
- package/dist/builtin-personas/developer/orchestrator.md +2 -1
- package/dist/builtin-personas/explore/PERSONA.md +1 -0
- package/dist/builtin-personas/explore/orchestrator.md +4 -0
- package/dist/builtin-personas/general/PERSONA.md +1 -0
- package/dist/builtin-personas/general/orchestrator.md +4 -0
- 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/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/orchestrator.md +2 -1
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -0
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -0
- package/dist/builtin-personas/review/orchestrator.md +4 -0
- package/dist/builtin-personas/runtime-base.md +6 -3
- package/dist/builtin-personas/spec/PERSONA.md +1 -0
- package/dist/builtin-personas/spec/orchestrator.md +2 -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 +54 -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 +610 -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-attach-limits.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-limits.test.js +157 -0
- package/dist/core/__tests__/broker-attach-stream.test.d.ts +1 -0
- package/dist/core/__tests__/broker-attach-stream.test.js +125 -0
- package/dist/core/__tests__/broker-crash-teardown.test.d.ts +1 -0
- package/dist/core/__tests__/broker-crash-teardown.test.js +116 -0
- package/dist/core/__tests__/broker-dialogs.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dialogs.test.js +126 -0
- package/dist/core/__tests__/broker-dormant-wake.test.d.ts +1 -0
- package/dist/core/__tests__/broker-dormant-wake.test.js +51 -0
- package/dist/core/__tests__/broker-lifecycle.test.d.ts +1 -0
- package/dist/core/__tests__/broker-lifecycle.test.js +87 -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__/canvas-inbox-watcher-hold.test.d.ts +1 -0
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +102 -0
- package/dist/core/__tests__/canvas-inbox-watcher.test.js +2 -36
- 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/broker-clients.d.ts +43 -0
- package/dist/core/__tests__/helpers/broker-clients.js +178 -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-verbs.test.d.ts +1 -0
- package/dist/core/__tests__/live-mutation-verbs.test.js +175 -0
- package/dist/core/__tests__/live-mutation.test.js +6 -132
- 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 +16 -23
- 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 +91 -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 +54 -4
- package/dist/core/runtime/launch.js +85 -5
- 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 +37 -0
- package/dist/core/runtime/nodes.js +60 -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 +4 -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 +11 -8
- package/dist/core/runtime/revive.d.ts +2 -0
- package/dist/core/runtime/revive.js +34 -34
- package/dist/core/runtime/spawn.d.ts +23 -0
- package/dist/core/runtime/spawn.js +77 -23
- 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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { join, relative, sep } from 'node:path';
|
|
2
|
+
import { pathExists, readText, walkFiles } from './fs-utils.js';
|
|
3
|
+
import { parseFrontmatterGeneric } from './frontmatter.js';
|
|
4
|
+
import { parseSkillQualifier } from './resolver.js';
|
|
5
|
+
import { ambiguous, notFound, usage } from './errors.js';
|
|
6
|
+
import { warn } from './output.js';
|
|
7
|
+
import { projectScopeRoot, scopeMemoryDir } from './scope.js';
|
|
8
|
+
/** Canonical, unambiguous identifier for a memory document: `<scope>/<name>`. */
|
|
9
|
+
export function memoryDocId(doc) {
|
|
10
|
+
return `${doc.scope}/${doc.name}`;
|
|
11
|
+
}
|
|
12
|
+
/** The memory scopes in resolution precedence: project > user > builtin.
|
|
13
|
+
* Project is included only when a project scope exists for the cwd. A single
|
|
14
|
+
* `scope` narrows to just that scope. */
|
|
15
|
+
function scopesInPrecedence(scope) {
|
|
16
|
+
if (scope)
|
|
17
|
+
return [scope];
|
|
18
|
+
const out = [];
|
|
19
|
+
if (projectScopeRoot())
|
|
20
|
+
out.push('project');
|
|
21
|
+
out.push('user');
|
|
22
|
+
out.push('builtin');
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function loadMemoryDoc(name, scope, path) {
|
|
26
|
+
const { data, body } = parseFrontmatterGeneric(readText(path));
|
|
27
|
+
return { name, scope, path, frontmatter: data, body };
|
|
28
|
+
}
|
|
29
|
+
/** All memory docs in one scope's memory/ dir, scanned recursively for *.md
|
|
30
|
+
* (topical subdirs supported), sorted by path-derived name. */
|
|
31
|
+
export function listMemoryDocs(scope) {
|
|
32
|
+
const dir = scopeMemoryDir(scope);
|
|
33
|
+
if (!dir || !pathExists(dir))
|
|
34
|
+
return [];
|
|
35
|
+
const docs = [];
|
|
36
|
+
for (const file of walkFiles(dir, (n) => n.endsWith('.md'))) {
|
|
37
|
+
const name = relative(dir, file).replace(/\.md$/i, '').split(sep).join('/');
|
|
38
|
+
if (!name)
|
|
39
|
+
continue;
|
|
40
|
+
// COLLECTION layer: the strict frontmatter parser throws on invalid YAML.
|
|
41
|
+
// Isolate one malformed doc with a clear scoped notice + skip, so a single
|
|
42
|
+
// bad file can't brick `memory list`/`find` or the substrate boot render.
|
|
43
|
+
try {
|
|
44
|
+
docs.push(loadMemoryDoc(name, scope, file));
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
48
|
+
warn(`invalid frontmatter in ${file}: ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return docs.sort((a, b) => a.name.localeCompare(b.name));
|
|
52
|
+
}
|
|
53
|
+
/** All memory docs across the resolved scopes, in precedence order
|
|
54
|
+
* (project, then user, then builtin), each scope's docs name-sorted within. */
|
|
55
|
+
export function listAllMemoryDocs(scope) {
|
|
56
|
+
return scopesInPrecedence(scope).flatMap(listMemoryDocs);
|
|
57
|
+
}
|
|
58
|
+
/** Direct full-path lookup of memory/<name>.md across scopes, precedence-first.
|
|
59
|
+
* Returns every scope's hit in precedence order; the resolver takes the first
|
|
60
|
+
* (highest-precedence) one — a fully-specified name is never ambiguous. */
|
|
61
|
+
function findMemoryMatches(name, scope) {
|
|
62
|
+
const matches = [];
|
|
63
|
+
for (const s of scopesInPrecedence(scope)) {
|
|
64
|
+
const dir = scopeMemoryDir(s);
|
|
65
|
+
if (!dir)
|
|
66
|
+
continue;
|
|
67
|
+
const path = join(dir, ...name.split('/')) + '.md';
|
|
68
|
+
if (!pathExists(path))
|
|
69
|
+
continue;
|
|
70
|
+
matches.push(loadMemoryDoc(name, s, path));
|
|
71
|
+
}
|
|
72
|
+
return matches;
|
|
73
|
+
}
|
|
74
|
+
/** Leaf-name fallback: match docs whose final path segment equals `leaf`.
|
|
75
|
+
* Only meaningful for a bare segment (a slashed query can never equal a single
|
|
76
|
+
* segment), mirroring findSkillsByLeaf. Returns matches precedence-ordered. */
|
|
77
|
+
function findMemoryByLeaf(leaf, scope) {
|
|
78
|
+
if (leaf.includes('/'))
|
|
79
|
+
return [];
|
|
80
|
+
let all;
|
|
81
|
+
try {
|
|
82
|
+
all = listAllMemoryDocs(scope);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
return all.filter((d) => (d.name.split('/').pop() ?? d.name) === leaf);
|
|
88
|
+
}
|
|
89
|
+
function formatLeafAmbiguous(leaf, matches) {
|
|
90
|
+
const ids = matches.map(memoryDocId).join(', ');
|
|
91
|
+
return `ambiguous memory document: ${leaf} matches multiple documents: ${ids}`;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a path-derived name to a single memory document.
|
|
95
|
+
*
|
|
96
|
+
* Accepted identifier forms (mirroring parseSkillQualifier, no plugins):
|
|
97
|
+
* <name> — bare name; resolved by scope precedence project>user>builtin
|
|
98
|
+
* <scope>/<name> — pinned to one scope (user|project)
|
|
99
|
+
* `<name>` may carry topical subdirs (`taste/foo`); a bare leaf (`foo`) falls
|
|
100
|
+
* back to a last-segment match across the resolved scopes.
|
|
101
|
+
*
|
|
102
|
+
* Resolution order: parse qualifier → direct memory/<name>.md lookup
|
|
103
|
+
* (precedence-first) → leaf-name fallback (ambiguity error listing candidates).
|
|
104
|
+
*/
|
|
105
|
+
export function resolveMemoryDoc(rawName, opts = {}) {
|
|
106
|
+
const parsed = parseSkillQualifier(rawName);
|
|
107
|
+
if (parsed.scope && opts.scope && parsed.scope !== opts.scope) {
|
|
108
|
+
throw usage(`scope conflict: identifier "${rawName}" uses scope "${parsed.scope}" but --scope is "${opts.scope}"`);
|
|
109
|
+
}
|
|
110
|
+
const effectiveScope = opts.scope ?? parsed.scope;
|
|
111
|
+
const name = parsed.segments.join('/');
|
|
112
|
+
if (name === '')
|
|
113
|
+
throw usage(`memory document name required`);
|
|
114
|
+
// Direct full-path lookup: a fully-specified name resolves by scope precedence.
|
|
115
|
+
const direct = findMemoryMatches(name, effectiveScope);
|
|
116
|
+
if (direct.length > 0)
|
|
117
|
+
return direct[0];
|
|
118
|
+
// Leaf-name fallback: the caller gave only the final segment (e.g. "foo" for
|
|
119
|
+
// "taste/foo"). Match by last segment across the resolved scope dirs.
|
|
120
|
+
const byLeaf = findMemoryByLeaf(name, effectiveScope);
|
|
121
|
+
if (byLeaf.length > 0) {
|
|
122
|
+
// Same path-derived name across scopes → precedence wins (return first);
|
|
123
|
+
// genuinely different docs sharing a leaf → ambiguous.
|
|
124
|
+
const distinctNames = new Set(byLeaf.map((d) => d.name));
|
|
125
|
+
if (distinctNames.size === 1)
|
|
126
|
+
return byLeaf[0];
|
|
127
|
+
throw ambiguous(formatLeafAmbiguous(name, byLeaf), {
|
|
128
|
+
memory: name,
|
|
129
|
+
candidates: byLeaf.map((d) => ({
|
|
130
|
+
id: memoryDocId(d),
|
|
131
|
+
scope: d.scope,
|
|
132
|
+
path: d.path,
|
|
133
|
+
})),
|
|
134
|
+
next: 'Multiple documents share this leaf name. Re-run with one of the full names in candidates.',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
throw notFound(`memory document not found: ${rawName}`, {
|
|
138
|
+
memory: name,
|
|
139
|
+
scope: parsed.scope,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -39,6 +39,13 @@ export declare function loadKernel(): string;
|
|
|
39
39
|
* live in their own fragments, loaded below.
|
|
40
40
|
*/
|
|
41
41
|
export declare function loadRuntimeBase(): string;
|
|
42
|
+
/**
|
|
43
|
+
* Load the waiting fragment — the cross-kind "waiting is a way to end a turn"
|
|
44
|
+
* operating posture (arm a wake + go dormant instead of busy-looping or
|
|
45
|
+
* finishing-to-stop). Spliced into every node's baked prompt immediately after
|
|
46
|
+
* the lifecycle fragment (resolve.ts). Returns '' if the fragment is missing.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadWaitingFragment(): string;
|
|
42
49
|
/**
|
|
43
50
|
* Load the lifecycle fragment — the "how you end" contract, keyed on the node's
|
|
44
51
|
* lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
|
|
@@ -56,6 +56,23 @@ function personaSearchRoots() {
|
|
|
56
56
|
return roots;
|
|
57
57
|
}
|
|
58
58
|
// ---------------------------------------------------------------------------
|
|
59
|
+
// Frontmatter scalar coercion
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
/** Coerce a frontmatter scalar to its string form, matching the legacy
|
|
62
|
+
* hand-rolled parser (which stringified every scalar). Strings pass through;
|
|
63
|
+
* number/boolean coerce via String(); null/undefined and non-scalars
|
|
64
|
+
* (objects/arrays) are dropped (→ null). The `yaml` package returns native
|
|
65
|
+
* scalar types, so without this a `typeof === 'string'` guard would silently
|
|
66
|
+
* DROP a numeric/boolean frontmatter value (e.g. blanking a menu line) where
|
|
67
|
+
* the old parser kept its stringified form. */
|
|
68
|
+
function scalarToString(v) {
|
|
69
|
+
if (typeof v === 'string')
|
|
70
|
+
return v;
|
|
71
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
72
|
+
return String(v);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
59
76
|
// File resolution helpers
|
|
60
77
|
// ---------------------------------------------------------------------------
|
|
61
78
|
/**
|
|
@@ -143,6 +160,19 @@ export function loadRuntimeBase() {
|
|
|
143
160
|
const { body } = parseFrontmatterGeneric(src);
|
|
144
161
|
return body.trim();
|
|
145
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Load the waiting fragment — the cross-kind "waiting is a way to end a turn"
|
|
165
|
+
* operating posture (arm a wake + go dormant instead of busy-looping or
|
|
166
|
+
* finishing-to-stop). Spliced into every node's baked prompt immediately after
|
|
167
|
+
* the lifecycle fragment (resolve.ts). Returns '' if the fragment is missing.
|
|
168
|
+
*/
|
|
169
|
+
export function loadWaitingFragment() {
|
|
170
|
+
const filePath = resolveFile('waiting.md');
|
|
171
|
+
if (!filePath)
|
|
172
|
+
return '';
|
|
173
|
+
const { body } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
|
|
174
|
+
return body.trim();
|
|
175
|
+
}
|
|
146
176
|
/**
|
|
147
177
|
* Load the lifecycle fragment — the "how you end" contract, keyed on the node's
|
|
148
178
|
* lifecycle axis: `terminal` (drive to done + `push final`) or `resident`
|
|
@@ -205,7 +235,7 @@ export function kindWhenToUse(kind) {
|
|
|
205
235
|
if (!filePath)
|
|
206
236
|
return '';
|
|
207
237
|
const { data } = parseFrontmatterGeneric(readFileSync(filePath, 'utf8'));
|
|
208
|
-
return data
|
|
238
|
+
return scalarToString(data?.['whenToUse']) ?? '';
|
|
209
239
|
}
|
|
210
240
|
/** Recursively yield every dir under `dir` (inclusive) that holds a PERSONA.md,
|
|
211
241
|
* with `relKind` = the dir's path relative to the scope root (slash-joined).
|
|
@@ -233,10 +263,11 @@ function parseAvailableTo(data, topKind) {
|
|
|
233
263
|
const v = data ? data['availableTo'] : undefined;
|
|
234
264
|
if (v === undefined)
|
|
235
265
|
return [topKind];
|
|
236
|
-
|
|
237
|
-
|
|
266
|
+
const scalar = scalarToString(v);
|
|
267
|
+
if (scalar !== null)
|
|
268
|
+
return isWild(scalar) ? '*' : [scalar];
|
|
238
269
|
if (Array.isArray(v)) {
|
|
239
|
-
const arr = v.filter((x) =>
|
|
270
|
+
const arr = v.map(scalarToString).filter((x) => x !== null);
|
|
240
271
|
if (arr.some(isWild))
|
|
241
272
|
return '*';
|
|
242
273
|
return arr.length > 0 ? arr : [topKind];
|
|
@@ -283,7 +314,7 @@ export function subPersonasFor(kind) {
|
|
|
283
314
|
const availableTo = parseAvailableTo(data, topKind);
|
|
284
315
|
if (availableTo !== '*' && !availableTo.includes(kind))
|
|
285
316
|
continue;
|
|
286
|
-
const whenToUse = data
|
|
317
|
+
const whenToUse = scalarToString(data?.['whenToUse']) ?? '';
|
|
287
318
|
out.push({ kind: relKind, name: relKind.split('/').pop(), whenToUse });
|
|
288
319
|
}
|
|
289
320
|
}
|
|
@@ -19,17 +19,26 @@
|
|
|
19
19
|
* derived here; they select the lifecycle/spine protocol fragments spliced
|
|
20
20
|
* ahead of the persona body.
|
|
21
21
|
*/
|
|
22
|
-
import { loadPersona, loadKernel, loadRuntimeBase, loadSpineFragment, loadLifecycleFragment, subPersonasFor } from './loader.js';
|
|
22
|
+
import { loadPersona, loadKernel, loadRuntimeBase, loadSpineFragment, loadLifecycleFragment, loadWaitingFragment, subPersonasFor } from './loader.js';
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
// Helpers
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
function toStringArray(v) {
|
|
27
27
|
if (!Array.isArray(v))
|
|
28
28
|
return [];
|
|
29
|
-
|
|
29
|
+
// yaml.parse coerces scalars to native types; the persona prompt needs strings,
|
|
30
|
+
// so coerce each scalar element (dropping nested objects/arrays/null) — this
|
|
31
|
+
// mirrors the prior hand-rolled parser, which returned every scalar as a string.
|
|
32
|
+
return v.filter((x) => x != null && typeof x !== 'object').map((x) => String(x));
|
|
30
33
|
}
|
|
31
34
|
function toOptionalString(v) {
|
|
32
|
-
|
|
35
|
+
if (typeof v === 'string')
|
|
36
|
+
return v;
|
|
37
|
+
// Preserve the legacy parser's string-typed contract for native scalars
|
|
38
|
+
// (e.g. `model: 4` must compose as "4", not be dropped as a non-string).
|
|
39
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
40
|
+
return String(v);
|
|
41
|
+
return undefined;
|
|
33
42
|
}
|
|
34
43
|
/** The bare-minimum system prompt used when no persona file is found at all. */
|
|
35
44
|
function fallbackBasePrompt(kind) {
|
|
@@ -65,6 +74,7 @@ function composeProtocol(personaPrompt, kind, lifecycle, hasManager) {
|
|
|
65
74
|
loadRuntimeBase(),
|
|
66
75
|
loadSpineFragment(hasManager),
|
|
67
76
|
loadLifecycleFragment(lifecycle),
|
|
77
|
+
loadWaitingFragment(),
|
|
68
78
|
]
|
|
69
79
|
.filter((s) => s.length > 0)
|
|
70
80
|
.join('\n\n');
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predicate matcher engine — vendored verbatim from pi's frontmatter-rules
|
|
3
|
+
* extension.
|
|
4
|
+
*
|
|
5
|
+
* Source: pi-personal-extensions/extensions/frontmatter-rules/index.ts
|
|
6
|
+
* (the "Condition language" block, functions `asArray` … `evalCondition`).
|
|
7
|
+
*
|
|
8
|
+
* The unified document substrate uses this to evaluate a document's `gate`
|
|
9
|
+
* predicate against a node-config subject (e.g. `orchestration.depth: { gte: 2 }`).
|
|
10
|
+
*
|
|
11
|
+
* This is intentionally a COPY, not an import: the source lives in a separate,
|
|
12
|
+
* private, unpublished repo that crtr cannot depend on. The functions are pure
|
|
13
|
+
* TS over `unknown` / `Record<string, unknown>` with zero external imports, so
|
|
14
|
+
* vendoring them keeps this module standalone and dependency-free. Keep it in
|
|
15
|
+
* sync with the source by hand if the matcher language ever changes.
|
|
16
|
+
*
|
|
17
|
+
* Condition language (evaluated against the subject object):
|
|
18
|
+
* A condition is a map of <field> -> <matcher>, AND-ed across fields.
|
|
19
|
+
* `field` may be dotted to reach nested values (e.g. `orchestration.depth`).
|
|
20
|
+
*
|
|
21
|
+
* Matcher forms:
|
|
22
|
+
* scalar (string/number/bool/null)
|
|
23
|
+
* field === value, OR (if the field is an array) the array includes value
|
|
24
|
+
* array [a, b, c]
|
|
25
|
+
* membership/intersection: field is one of these, OR the field array
|
|
26
|
+
* shares any element with these
|
|
27
|
+
* object { <op>: <arg>, ... } (multiple ops AND together)
|
|
28
|
+
* eq equals (scalar or array-includes)
|
|
29
|
+
* ne not eq
|
|
30
|
+
* in [..] — field (or any of its array elements) is in the list
|
|
31
|
+
* nin not in
|
|
32
|
+
* exists true|false — field is present / absent
|
|
33
|
+
* contains field array includes this value
|
|
34
|
+
* containsAll [..] — field array includes all of these
|
|
35
|
+
* containsAny [..] — field array includes any of these
|
|
36
|
+
* matches regex string, case-sensitive (tests String(field); arrays: any)
|
|
37
|
+
* imatches regex string, case-insensitive
|
|
38
|
+
* gt gte lt lte numeric comparison
|
|
39
|
+
*
|
|
40
|
+
* Combinators (reserved keys at any condition level):
|
|
41
|
+
* all: [ {..}, {..} ] every sub-condition true (AND)
|
|
42
|
+
* any: [ {..}, {..} ] at least one true (OR)
|
|
43
|
+
* not: { .. } sub-condition false
|
|
44
|
+
* Sibling field matchers next to combinators are AND-ed in.
|
|
45
|
+
*
|
|
46
|
+
* Two load-bearing edge cases preserved from the source:
|
|
47
|
+
* - An empty condition (`{}`) is INERT: it returns false, NOT match-all.
|
|
48
|
+
* - An unknown op never matches.
|
|
49
|
+
*/
|
|
50
|
+
export declare function asArray(v: unknown): unknown[];
|
|
51
|
+
export declare function scalarEq(a: unknown, b: unknown): boolean;
|
|
52
|
+
export declare function getField(subject: unknown, key: string): unknown;
|
|
53
|
+
export declare function toRegExp(arg: unknown, flags: string): RegExp | null;
|
|
54
|
+
export declare function applyOp(op: string, value: unknown, arg: unknown): boolean;
|
|
55
|
+
export declare function matchField(value: unknown, matcher: unknown): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Evaluate a `gate` predicate against a subject object.
|
|
58
|
+
*
|
|
59
|
+
* This is the one function consumers call. Returns true iff `condition`
|
|
60
|
+
* matches `subject`. A null/non-object condition and an empty object both
|
|
61
|
+
* return false (inert), never match-all.
|
|
62
|
+
*/
|
|
63
|
+
export declare function evalCondition(condition: unknown, subject: Record<string, unknown>): boolean;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predicate matcher engine — vendored verbatim from pi's frontmatter-rules
|
|
3
|
+
* extension.
|
|
4
|
+
*
|
|
5
|
+
* Source: pi-personal-extensions/extensions/frontmatter-rules/index.ts
|
|
6
|
+
* (the "Condition language" block, functions `asArray` … `evalCondition`).
|
|
7
|
+
*
|
|
8
|
+
* The unified document substrate uses this to evaluate a document's `gate`
|
|
9
|
+
* predicate against a node-config subject (e.g. `orchestration.depth: { gte: 2 }`).
|
|
10
|
+
*
|
|
11
|
+
* This is intentionally a COPY, not an import: the source lives in a separate,
|
|
12
|
+
* private, unpublished repo that crtr cannot depend on. The functions are pure
|
|
13
|
+
* TS over `unknown` / `Record<string, unknown>` with zero external imports, so
|
|
14
|
+
* vendoring them keeps this module standalone and dependency-free. Keep it in
|
|
15
|
+
* sync with the source by hand if the matcher language ever changes.
|
|
16
|
+
*
|
|
17
|
+
* Condition language (evaluated against the subject object):
|
|
18
|
+
* A condition is a map of <field> -> <matcher>, AND-ed across fields.
|
|
19
|
+
* `field` may be dotted to reach nested values (e.g. `orchestration.depth`).
|
|
20
|
+
*
|
|
21
|
+
* Matcher forms:
|
|
22
|
+
* scalar (string/number/bool/null)
|
|
23
|
+
* field === value, OR (if the field is an array) the array includes value
|
|
24
|
+
* array [a, b, c]
|
|
25
|
+
* membership/intersection: field is one of these, OR the field array
|
|
26
|
+
* shares any element with these
|
|
27
|
+
* object { <op>: <arg>, ... } (multiple ops AND together)
|
|
28
|
+
* eq equals (scalar or array-includes)
|
|
29
|
+
* ne not eq
|
|
30
|
+
* in [..] — field (or any of its array elements) is in the list
|
|
31
|
+
* nin not in
|
|
32
|
+
* exists true|false — field is present / absent
|
|
33
|
+
* contains field array includes this value
|
|
34
|
+
* containsAll [..] — field array includes all of these
|
|
35
|
+
* containsAny [..] — field array includes any of these
|
|
36
|
+
* matches regex string, case-sensitive (tests String(field); arrays: any)
|
|
37
|
+
* imatches regex string, case-insensitive
|
|
38
|
+
* gt gte lt lte numeric comparison
|
|
39
|
+
*
|
|
40
|
+
* Combinators (reserved keys at any condition level):
|
|
41
|
+
* all: [ {..}, {..} ] every sub-condition true (AND)
|
|
42
|
+
* any: [ {..}, {..} ] at least one true (OR)
|
|
43
|
+
* not: { .. } sub-condition false
|
|
44
|
+
* Sibling field matchers next to combinators are AND-ed in.
|
|
45
|
+
*
|
|
46
|
+
* Two load-bearing edge cases preserved from the source:
|
|
47
|
+
* - An empty condition (`{}`) is INERT: it returns false, NOT match-all.
|
|
48
|
+
* - An unknown op never matches.
|
|
49
|
+
*/
|
|
50
|
+
export function asArray(v) {
|
|
51
|
+
if (Array.isArray(v))
|
|
52
|
+
return v;
|
|
53
|
+
if (v === undefined)
|
|
54
|
+
return [];
|
|
55
|
+
return [v];
|
|
56
|
+
}
|
|
57
|
+
// Scalar equality with light, predictable coercion: exact match, or
|
|
58
|
+
// string/number cross-compare so `version: 2` matches a subject "2".
|
|
59
|
+
export function scalarEq(a, b) {
|
|
60
|
+
if (a === b)
|
|
61
|
+
return true;
|
|
62
|
+
if ((typeof a === 'number' || typeof a === 'string') &&
|
|
63
|
+
(typeof b === 'number' || typeof b === 'string')) {
|
|
64
|
+
return String(a) === String(b);
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// Resolve a (possibly dotted) field path against the subject object.
|
|
69
|
+
export function getField(subject, key) {
|
|
70
|
+
if (subject && typeof subject === 'object' && key in subject) {
|
|
71
|
+
return subject[key];
|
|
72
|
+
}
|
|
73
|
+
if (!key.includes('.'))
|
|
74
|
+
return undefined;
|
|
75
|
+
let cur = subject;
|
|
76
|
+
for (const part of key.split('.')) {
|
|
77
|
+
if (cur == null || typeof cur !== 'object')
|
|
78
|
+
return undefined;
|
|
79
|
+
cur = cur[part];
|
|
80
|
+
}
|
|
81
|
+
return cur;
|
|
82
|
+
}
|
|
83
|
+
export function toRegExp(arg, flags) {
|
|
84
|
+
if (typeof arg !== 'string')
|
|
85
|
+
return null;
|
|
86
|
+
try {
|
|
87
|
+
return new RegExp(arg, flags);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function applyOp(op, value, arg) {
|
|
94
|
+
const vals = asArray(value);
|
|
95
|
+
switch (op) {
|
|
96
|
+
case 'eq':
|
|
97
|
+
return Array.isArray(value) ? value.some((v) => scalarEq(v, arg)) : scalarEq(value, arg);
|
|
98
|
+
case 'ne':
|
|
99
|
+
return !applyOp('eq', value, arg);
|
|
100
|
+
case 'in':
|
|
101
|
+
return asArray(arg).some((a) => vals.some((v) => scalarEq(v, a)));
|
|
102
|
+
case 'nin':
|
|
103
|
+
return !applyOp('in', value, arg);
|
|
104
|
+
case 'exists':
|
|
105
|
+
return (value !== undefined) === (arg !== false);
|
|
106
|
+
case 'contains':
|
|
107
|
+
return vals.some((v) => scalarEq(v, arg));
|
|
108
|
+
case 'containsAll':
|
|
109
|
+
return asArray(arg).every((a) => vals.some((v) => scalarEq(v, a)));
|
|
110
|
+
case 'containsAny':
|
|
111
|
+
return asArray(arg).some((a) => vals.some((v) => scalarEq(v, a)));
|
|
112
|
+
case 'matches':
|
|
113
|
+
case 'imatches': {
|
|
114
|
+
const re = toRegExp(arg, op === 'imatches' ? 'i' : '');
|
|
115
|
+
if (!re)
|
|
116
|
+
return false;
|
|
117
|
+
return vals.some((v) => (typeof v === 'string' || typeof v === 'number' ? re.test(String(v)) : false));
|
|
118
|
+
}
|
|
119
|
+
case 'gt':
|
|
120
|
+
case 'gte':
|
|
121
|
+
case 'lt':
|
|
122
|
+
case 'lte': {
|
|
123
|
+
const n = Number(value);
|
|
124
|
+
const t = Number(arg);
|
|
125
|
+
if (Number.isNaN(n) || Number.isNaN(t))
|
|
126
|
+
return false;
|
|
127
|
+
if (op === 'gt')
|
|
128
|
+
return n > t;
|
|
129
|
+
if (op === 'gte')
|
|
130
|
+
return n >= t;
|
|
131
|
+
if (op === 'lt')
|
|
132
|
+
return n < t;
|
|
133
|
+
return n <= t;
|
|
134
|
+
}
|
|
135
|
+
default:
|
|
136
|
+
return false; // unknown op never matches
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export function matchField(value, matcher) {
|
|
140
|
+
if (matcher === null || ['string', 'number', 'boolean'].includes(typeof matcher)) {
|
|
141
|
+
if (Array.isArray(value))
|
|
142
|
+
return value.some((v) => scalarEq(v, matcher));
|
|
143
|
+
return scalarEq(value, matcher);
|
|
144
|
+
}
|
|
145
|
+
if (Array.isArray(matcher)) {
|
|
146
|
+
const vals = asArray(value);
|
|
147
|
+
return matcher.some((m) => vals.some((v) => scalarEq(v, m)));
|
|
148
|
+
}
|
|
149
|
+
if (matcher && typeof matcher === 'object') {
|
|
150
|
+
return Object.entries(matcher).every(([op, arg]) => applyOp(op, value, arg));
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Evaluate a `gate` predicate against a subject object.
|
|
156
|
+
*
|
|
157
|
+
* This is the one function consumers call. Returns true iff `condition`
|
|
158
|
+
* matches `subject`. A null/non-object condition and an empty object both
|
|
159
|
+
* return false (inert), never match-all.
|
|
160
|
+
*/
|
|
161
|
+
export function evalCondition(condition, subject) {
|
|
162
|
+
if (condition == null)
|
|
163
|
+
return false;
|
|
164
|
+
if (Array.isArray(condition))
|
|
165
|
+
return condition.every((sub) => evalCondition(sub, subject));
|
|
166
|
+
if (typeof condition !== 'object')
|
|
167
|
+
return false;
|
|
168
|
+
const c = condition;
|
|
169
|
+
const hasCombinator = 'all' in c || 'any' in c || 'not' in c;
|
|
170
|
+
if (hasCombinator) {
|
|
171
|
+
let ok = true;
|
|
172
|
+
if ('all' in c)
|
|
173
|
+
ok = ok && asArray(c.all).every((s) => evalCondition(s, subject));
|
|
174
|
+
if ('any' in c)
|
|
175
|
+
ok = ok && asArray(c.any).some((s) => evalCondition(s, subject));
|
|
176
|
+
if ('not' in c)
|
|
177
|
+
ok = ok && !evalCondition(c.not, subject);
|
|
178
|
+
for (const k of Object.keys(c)) {
|
|
179
|
+
if (k === 'all' || k === 'any' || k === 'not')
|
|
180
|
+
continue;
|
|
181
|
+
ok = ok && matchField(getField(subject, k), c[k]);
|
|
182
|
+
}
|
|
183
|
+
return ok;
|
|
184
|
+
}
|
|
185
|
+
const keys = Object.keys(c);
|
|
186
|
+
if (keys.length === 0)
|
|
187
|
+
return false; // empty condition is inert, not match-all
|
|
188
|
+
return keys.every((k) => matchField(getField(subject, k), c[k]));
|
|
189
|
+
}
|
package/dist/core/resolver.js
CHANGED
|
@@ -5,6 +5,7 @@ import { listDirs, pathExists, readText, readTextIfExists, walkFiles, } from './
|
|
|
5
5
|
import { readMarketplaceManifest, readPluginManifest } from './manifest.js';
|
|
6
6
|
import { parseFrontmatter } from './frontmatter.js';
|
|
7
7
|
import { ambiguous, notFound, usage } from './errors.js';
|
|
8
|
+
import { warn } from './output.js';
|
|
8
9
|
import { InputError } from './io.js';
|
|
9
10
|
import { builtinSkillsRoot, marketplacesDir, pluginsDir, projectScopeRoot, scopeSkillsDir, userScopeRoot, } from './scope.js';
|
|
10
11
|
function getBuiltinPlugin() {
|
|
@@ -94,6 +95,22 @@ export function effectiveSkillEnabled(pluginName, skillName, cfgs) {
|
|
|
94
95
|
}
|
|
95
96
|
return { enabled: true };
|
|
96
97
|
}
|
|
98
|
+
/** Parse one skill file's frontmatter at the COLLECTION layer: the strict
|
|
99
|
+
* parser THROWS on invalid YAML (the frontmatter contract is "valid YAML"),
|
|
100
|
+
* so a single malformed SKILL.md must not brick a whole `skill find`/catalog
|
|
101
|
+
* scan across the corpus. On a parse error, emit a clear scoped notice naming
|
|
102
|
+
* the file and return null so the iterator SKIPS it and continues. A doc with
|
|
103
|
+
* no frontmatter block parses fine (data === null) and is kept. */
|
|
104
|
+
function readSkillFrontmatterSafe(file) {
|
|
105
|
+
try {
|
|
106
|
+
return parseFrontmatter(readText(file));
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
110
|
+
warn(`invalid frontmatter in ${file}: ${msg}`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
97
114
|
export function listSkillsInPlugin(plugin, cfgs) {
|
|
98
115
|
const skillsRoot = join(plugin.root, SKILLS_DIR);
|
|
99
116
|
if (!pathExists(skillsRoot))
|
|
@@ -106,8 +123,10 @@ export function listSkillsInPlugin(plugin, cfgs) {
|
|
|
106
123
|
const name = rel.split(sep).join('/');
|
|
107
124
|
if (!name)
|
|
108
125
|
continue;
|
|
109
|
-
const
|
|
110
|
-
|
|
126
|
+
const parsed = readSkillFrontmatterSafe(file);
|
|
127
|
+
if (parsed === null)
|
|
128
|
+
continue;
|
|
129
|
+
const { data } = parsed;
|
|
111
130
|
const { enabled, disabledIn } = effectiveSkillEnabled(plugin.name, name, configs);
|
|
112
131
|
skills.push({
|
|
113
132
|
name,
|
|
@@ -136,8 +155,10 @@ export function listScopeRootSkills(scope, cfgs) {
|
|
|
136
155
|
const name = rel.split(sep).join('/');
|
|
137
156
|
if (!name)
|
|
138
157
|
continue;
|
|
139
|
-
const
|
|
140
|
-
|
|
158
|
+
const parsed = readSkillFrontmatterSafe(file);
|
|
159
|
+
if (parsed === null)
|
|
160
|
+
continue;
|
|
161
|
+
const { data } = parsed;
|
|
141
162
|
const { enabled, disabledIn } = effectiveSkillEnabled(SCOPE_SKILL_PLUGIN, name, configs);
|
|
142
163
|
skills.push({
|
|
143
164
|
name,
|
|
@@ -639,7 +660,7 @@ export function buildCategoryIndex(cat) {
|
|
|
639
660
|
lines.push(`- \`${fullId}\` — ${desc}`);
|
|
640
661
|
}
|
|
641
662
|
lines.push('');
|
|
642
|
-
lines.push(
|
|
663
|
+
lines.push('Read one with `crtr memory read <full-id>`. Search with `crtr memory find "<topic>"`.');
|
|
643
664
|
return lines.join('\n');
|
|
644
665
|
}
|
|
645
666
|
export function scopeRootsLabel() {
|
|
@@ -1,20 +1,61 @@
|
|
|
1
|
+
import { type WakeKind, type Wakeup } from '../canvas/index.js';
|
|
1
2
|
/** Base framing — present for every node. No path baked in: the caller carries
|
|
2
|
-
* the dir in the
|
|
3
|
+
* the dir in the `<crtr-context dir="…">` attribute. */
|
|
3
4
|
export declare const BASE_CONTEXT_NOTE: string;
|
|
4
5
|
/** Orchestrator-only framing: a resident orchestrator survives refresh cycles,
|
|
5
6
|
* so its context dir is also where a future cycle of itself resumes the work.
|
|
6
7
|
* Used inside the bearings block AND in the promotion guidance dump, so a
|
|
7
8
|
* promoted node gets the same note a born-orchestrator gets. */
|
|
8
9
|
export declare function orchestratorContextNote(nodeId: string): string;
|
|
9
|
-
/** The
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
*
|
|
10
|
+
/** The IDENTITY assertion that opens every boot intro — the load-bearing fix
|
|
11
|
+
* for the `--fork-from` impersonation bug. A fork copies the SOURCE node's
|
|
12
|
+
* entire first-person conversation into its own session, so without an explicit
|
|
13
|
+
* re-assertion the forked agent reads that copied narrative as its own and
|
|
14
|
+
* impersonates the source (it kept "monitoring itself" as a phantom child).
|
|
15
|
+
* This block names the node unambiguously. ONLY when the node IS a fork does it
|
|
16
|
+
* additionally name the source AND disown the copied first-person narrative as
|
|
17
|
+
* inherited reference material — a non-fork node's bearings are its first session
|
|
18
|
+
* entry, so there is no earlier narrative to disown (emitting a disown line there
|
|
19
|
+
* is dead weight that reads as a contradiction with no referent). Always the FIRST
|
|
20
|
+
* thing the node reads. Exported for testing. */
|
|
21
|
+
export declare function buildIdentityAssertion(nodeId: string): string;
|
|
22
|
+
/** Why a node woke or was born, as carried from the fired wakeup row to the
|
|
23
|
+
* injection seam. `ownerName` is the armer's resolved display name when it
|
|
24
|
+
* still exists (a reaped cron's armer renders as a bare id, never crashes). */
|
|
25
|
+
export interface WakeOrigin {
|
|
26
|
+
/** The firing kind. The <crtr-wake> block is rendered for 'spawn' (birth) and
|
|
27
|
+
* 'bare' (revive); 'noted'/'deadline' self-mark at the message seam instead. */
|
|
28
|
+
kind: WakeKind;
|
|
29
|
+
/** The armer node id (wakeups.owner_id), or null if somehow absent. */
|
|
30
|
+
ownerId: string | null;
|
|
31
|
+
/** The armer's display name, resolved if it still exists. */
|
|
32
|
+
ownerName?: string;
|
|
33
|
+
/** wakeups.created — when the wake was armed (ISO). */
|
|
34
|
+
armedAt: string;
|
|
35
|
+
/** wakeups.recur JSON when recurring, else null/undefined for a one-shot. */
|
|
36
|
+
recur?: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** Build a WakeOrigin from a fired wakeup row, resolving the armer's display
|
|
39
|
+
* name if the node still exists. The daemon calls this at fire time for the
|
|
40
|
+
* bare-revive and spawn-birth seams. */
|
|
41
|
+
export declare function wakeOriginFrom(w: Wakeup): WakeOrigin;
|
|
42
|
+
/** The <crtr-wake> provenance block — load-bearing agent-facing prose read by
|
|
43
|
+
* every wake-born or wake-woken node. Decision-first: it leads with the fact a
|
|
44
|
+
* TIMER (not a message/event) caused this turn, names the wake kind, surfaces
|
|
45
|
+
* the cadence for a recurrence (so the agent knows it is one run of a standing
|
|
46
|
+
* job, not a one-off), and ends with the directive. The spawn (birth) variant
|
|
47
|
+
* names the ARMER explicitly ("armed by node X") so the newborn never reads that
|
|
48
|
+
* id as its own; the bare (revive) variant drops armer attribution entirely — a
|
|
49
|
+
* bare wake can be armed for a node by ANOTHER (`--node`), and who armed it is
|
|
50
|
+
* not decision-relevant to a timed re-check. No timestamp is rendered ("to fire
|
|
51
|
+
* now" / the cadence already carry the signal; a raw ISO instant is noise an
|
|
52
|
+
* agent cannot cheaply turn into an elapsed delta). Rendered for 'spawn' and
|
|
53
|
+
* 'bare'; other kinds self-mark elsewhere. */
|
|
54
|
+
export declare function buildWakeBearings(origin: WakeOrigin): string;
|
|
55
|
+
/** The full boot intro: the IDENTITY assertion (always first, so it overrides
|
|
56
|
+
* any copied-in persona) followed by the `<crtr-context>` bearings block. Base
|
|
57
|
+
* framing rides for EVERY node; the across-cycles context-dir note is added
|
|
58
|
+
* ONLY for an orchestrator (by mode) — the one node whose dir a future cycle
|
|
59
|
+
* resumes from. The `## References` block (substrate reference docs +
|
|
60
|
+
* node-local docs) replaces the old `<memory>` block. */
|
|
20
61
|
export declare function buildContextBearings(nodeId: string): string;
|