@crouton-kit/crouter 0.3.27 → 0.3.29
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 +4 -2
- package/dist/builtin-memory/crouter-development/marketplaces.md +7 -6
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +1 -1
- package/dist/builtin-memory/crouter-development/personas.md +1 -1
- package/dist/builtin-memory/crouter-development/plugins.md +20 -18
- package/dist/builtin-memory/design.md +1 -1
- package/dist/builtin-memory/development.md +1 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
- package/dist/builtin-memory/internal/storage-tiers.md +1 -1
- package/dist/builtin-memory/planning.md +1 -1
- package/dist/builtin-memory/spec.md +6 -4
- package/dist/builtin-personas/orchestration-kernel.md +4 -5
- package/dist/builtin-personas/runtime-base.md +8 -4
- package/dist/builtin-personas/spec/PERSONA.md +8 -4
- package/dist/builtin-personas/spec/orchestrator.md +5 -3
- package/dist/builtin-personas/spec/requirements/PERSONA.md +10 -0
- package/dist/builtin-views/canvas/core.mjs +586 -0
- package/dist/builtin-views/canvas/text.mjs +58 -0
- package/dist/builtin-views/canvas/tui.mjs +165 -0
- package/dist/builtin-views/canvas/web.jsx +120 -0
- package/dist/builtin-views/git-pr/core.mjs +673 -0
- package/dist/builtin-views/git-pr/text.mjs +84 -0
- package/dist/builtin-views/git-pr/tui.mjs +302 -0
- package/dist/builtin-views/git-pr/web.jsx +216 -0
- package/dist/builtin-views/inbox/_lib/render.mjs +1 -3
- package/dist/builtin-views/inbox/core.mjs +1273 -0
- package/dist/builtin-views/inbox/text.mjs +73 -0
- package/dist/builtin-views/inbox/tui.mjs +312 -0
- package/dist/builtin-views/inbox/web.jsx +188 -0
- package/dist/builtin-views/linkedin/core.mjs +906 -0
- package/dist/builtin-views/linkedin/text.mjs +69 -0
- package/dist/builtin-views/linkedin/tui.mjs +427 -0
- package/dist/builtin-views/linkedin/web.jsx +206 -0
- package/dist/builtin-views/workspace-sidebar/core.mjs +621 -0
- package/dist/builtin-views/workspace-sidebar/text.mjs +53 -0
- package/dist/builtin-views/workspace-sidebar/tui.mjs +142 -0
- package/dist/builtin-views/workspace-sidebar/web.jsx +109 -0
- package/dist/cli.js +3 -3
- package/dist/clients/attach/__tests__/action-parity.test.js +48 -0
- package/dist/clients/attach/__tests__/clipboard-image.test.js +76 -0
- package/dist/clients/attach/__tests__/editor-newline.test.js +57 -0
- package/dist/clients/attach/__tests__/git-info.test.js +31 -0
- package/dist/clients/attach/__tests__/onrequest-wired.test.js +20 -0
- package/dist/clients/attach/__tests__/reconnect-giveup.test.js +30 -0
- package/dist/clients/attach/__tests__/slash-quit-copy.test.js +42 -0
- package/dist/clients/attach/__tests__/titled-editor.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +39 -0
- package/dist/clients/attach/attach-cmd.js +445 -41
- package/dist/clients/attach/auth-pickers.d.ts +9 -0
- package/dist/clients/attach/auth-pickers.js +194 -0
- package/dist/clients/attach/canvas-panels.d.ts +12 -0
- package/dist/clients/attach/canvas-panels.js +78 -0
- package/dist/clients/attach/chat-view.d.ts +27 -2
- package/dist/clients/attach/chat-view.js +98 -9
- package/dist/clients/attach/clipboard-image.d.ts +47 -8
- package/dist/clients/attach/clipboard-image.js +150 -26
- package/dist/clients/attach/clipboard-text.d.ts +3 -0
- package/dist/clients/attach/clipboard-text.js +39 -0
- package/dist/clients/attach/config-load.d.ts +54 -7
- package/dist/clients/attach/config-load.js +99 -3
- package/dist/clients/attach/context-message.d.ts +12 -0
- package/dist/clients/attach/context-message.js +72 -0
- package/dist/clients/attach/git-info.d.ts +20 -0
- package/dist/clients/attach/git-info.js +44 -0
- package/dist/clients/attach/graph-overlay.d.ts +43 -0
- package/dist/clients/attach/graph-overlay.js +306 -0
- package/dist/clients/attach/input-controller.d.ts +81 -13
- package/dist/clients/attach/input-controller.js +307 -67
- package/dist/clients/attach/pickers.d.ts +58 -0
- package/dist/clients/attach/pickers.js +171 -0
- package/dist/clients/attach/slash-commands.d.ts +44 -1
- package/dist/clients/attach/slash-commands.js +250 -27
- package/dist/clients/attach/titled-editor.d.ts +33 -0
- package/dist/clients/attach/titled-editor.js +84 -0
- package/dist/clients/attach/view-socket.d.ts +37 -2
- package/dist/clients/attach/view-socket.js +131 -7
- package/dist/clients/web/dev-server.d.ts +7 -0
- package/dist/clients/web/dev-server.js +59 -0
- package/dist/clients/web/events.d.ts +14 -0
- package/dist/clients/web/events.js +151 -0
- package/dist/clients/web/server.d.ts +18 -0
- package/dist/clients/web/server.js +450 -0
- package/dist/clients/web/web-cmd.d.ts +2 -0
- package/dist/clients/web/web-cmd.js +120 -0
- package/dist/commands/canvas.js +1 -2
- package/dist/commands/chord.js +1 -1
- package/dist/commands/dashboard.js +5 -1
- package/dist/commands/human/prompts.js +3 -3
- package/dist/commands/human/queue.d.ts +17 -0
- package/dist/commands/human/queue.js +111 -4
- package/dist/commands/memory/__tests__/lint-schema.test.js +27 -4
- package/dist/commands/memory/lint.d.ts +5 -4
- package/dist/commands/memory/lint.js +23 -7
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +7 -1
- package/dist/commands/memory/shared.js +18 -3
- package/dist/commands/memory/write.js +20 -7
- package/dist/commands/memory.js +4 -4
- package/dist/commands/node.d.ts +1 -1
- package/dist/commands/node.js +64 -65
- package/dist/commands/pkg.js +1 -2
- package/dist/commands/revive.js +11 -3
- package/dist/commands/sys/feedback.d.ts +1 -0
- package/dist/commands/sys/feedback.js +163 -0
- package/dist/commands/sys/sync.d.ts +1 -0
- package/dist/commands/sys/sync.js +187 -0
- package/dist/commands/sys.js +4 -2
- package/dist/commands/view-cycle.js +2 -2
- package/dist/commands/view-list.js +8 -8
- package/dist/commands/view-new.js +21 -17
- package/dist/commands/view-pick.js +1 -1
- package/dist/commands/view-run.js +35 -14
- package/dist/commands/view.js +8 -6
- package/dist/commands/workspace.d.ts +2 -0
- package/dist/commands/workspace.js +161 -0
- package/dist/core/__tests__/broker-double-spawn.test.d.ts +1 -0
- package/dist/core/__tests__/broker-double-spawn.test.js +143 -0
- package/dist/core/__tests__/broker-fork-seam.test.d.ts +1 -0
- package/dist/core/__tests__/broker-fork-seam.test.js +102 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +2 -0
- package/dist/core/__tests__/broker-snapshot-history.test.d.ts +1 -0
- package/dist/core/__tests__/broker-snapshot-history.test.js +105 -0
- package/dist/core/__tests__/child-death-wake.test.js +56 -111
- package/dist/core/__tests__/close.test.js +14 -20
- package/dist/core/__tests__/context-intro.test.js +19 -19
- package/dist/core/__tests__/daemon-boot.test.js +30 -23
- package/dist/core/__tests__/draw-style.test.js +1 -1
- package/dist/core/__tests__/error-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/error-stall-recycle.test.js +141 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +24 -1
- package/dist/core/__tests__/fixtures/fake-engine.js +32 -2
- package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
- package/dist/core/__tests__/focuses.test.js +25 -13
- package/dist/core/__tests__/fork.test.js +22 -5
- package/dist/core/__tests__/full/broker-control-preempt.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-control-preempt.test.js +61 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +57 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +1 -0
- package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +80 -0
- package/dist/core/__tests__/full/broker-pane-resolution.test.js +90 -0
- package/dist/core/__tests__/full/cascade-close.test.js +22 -8
- package/dist/core/__tests__/full/detach-focus.test.js +66 -169
- package/dist/core/__tests__/helpers/harness.d.ts +6 -9
- package/dist/core/__tests__/helpers/harness.js +19 -36
- package/dist/core/__tests__/human-node-not-supervised.test.d.ts +1 -0
- package/dist/core/__tests__/human-node-not-supervised.test.js +83 -0
- package/dist/core/__tests__/human-stranded-deliver.test.d.ts +1 -0
- package/dist/core/__tests__/human-stranded-deliver.test.js +108 -0
- package/dist/core/__tests__/lifecycle.test.js +1 -19
- package/dist/core/__tests__/live-mutation-verbs.test.js +5 -5
- package/dist/core/__tests__/memory-resolver.test.js +1 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.d.ts +1 -0
- package/dist/core/__tests__/on-read-dedup-resume.test.js +81 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/refresh-stall-recycle.test.js +88 -0
- package/dist/core/__tests__/relaunch-root.test.d.ts +1 -0
- package/dist/core/__tests__/relaunch-root.test.js +114 -0
- package/dist/core/__tests__/reset.test.js +19 -79
- package/dist/core/__tests__/steer-note.test.js +9 -7
- package/dist/core/__tests__/tmux-surface.test.js +13 -7
- package/dist/core/bootstrap.d.ts +0 -3
- package/dist/core/bootstrap.js +1 -143
- package/dist/core/canvas/browse/__tests__/model.test.js +86 -1
- package/dist/core/canvas/browse/__tests__/render.test.js +2 -1
- package/dist/core/canvas/browse/app.js +131 -8
- package/dist/core/canvas/browse/model.d.ts +23 -5
- package/dist/core/canvas/browse/model.js +68 -13
- package/dist/core/canvas/browse/render.d.ts +7 -1
- package/dist/core/canvas/browse/render.js +178 -47
- package/dist/core/canvas/canvas.js +1 -1
- package/dist/core/canvas/focuses.d.ts +4 -4
- package/dist/core/canvas/focuses.js +7 -7
- package/dist/core/canvas/nav-model.d.ts +162 -0
- package/dist/core/canvas/nav-model.js +487 -0
- package/dist/core/canvas/paths.d.ts +7 -0
- package/dist/core/canvas/paths.js +9 -0
- package/dist/core/canvas/render.d.ts +68 -9
- package/dist/core/canvas/render.js +195 -61
- package/dist/core/canvas/types.d.ts +16 -23
- package/dist/core/config.js +1 -4
- package/dist/core/memory-resolver.js +43 -5
- package/dist/core/runtime/bearings.d.ts +1 -1
- package/dist/core/runtime/bearings.js +8 -8
- package/dist/core/runtime/branded-host.d.ts +16 -0
- package/dist/core/runtime/branded-host.js +127 -0
- package/dist/core/runtime/broker-protocol.d.ts +212 -6
- package/dist/core/runtime/broker-sdk.d.ts +0 -12
- package/dist/core/runtime/broker-sdk.js +77 -6
- package/dist/core/runtime/broker.d.ts +5 -1
- package/dist/core/runtime/broker.js +520 -59
- package/dist/core/runtime/close.d.ts +12 -2
- package/dist/core/runtime/close.js +37 -16
- package/dist/core/runtime/front-door.js +14 -7
- package/dist/core/runtime/host.d.ts +14 -34
- package/dist/core/runtime/host.js +14 -50
- package/dist/core/runtime/launch.d.ts +12 -9
- package/dist/core/runtime/launch.js +27 -15
- package/dist/core/runtime/lifecycle.d.ts +1 -1
- package/dist/core/runtime/lifecycle.js +15 -19
- package/dist/core/runtime/nodes.d.ts +0 -55
- package/dist/core/runtime/nodes.js +6 -74
- package/dist/core/runtime/placement.d.ts +91 -334
- package/dist/core/runtime/placement.js +262 -828
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +24 -6
- package/dist/core/runtime/recap.d.ts +8 -0
- package/dist/core/runtime/recap.js +107 -0
- package/dist/core/runtime/recycle.js +25 -61
- package/dist/core/runtime/reset.d.ts +43 -37
- package/dist/core/runtime/reset.js +131 -218
- package/dist/core/runtime/revive.d.ts +9 -29
- package/dist/core/runtime/revive.js +45 -135
- package/dist/core/runtime/spawn.d.ts +15 -11
- package/dist/core/runtime/spawn.js +168 -136
- package/dist/core/runtime/surface-bg.d.ts +11 -0
- package/dist/core/runtime/surface-bg.js +68 -0
- package/dist/core/runtime/tmux.d.ts +15 -43
- package/dist/core/runtime/tmux.js +38 -80
- package/dist/core/scope.d.ts +1 -2
- package/dist/core/scope.js +1 -5
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts +1 -0
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +57 -0
- package/dist/core/skill-sync/builtins.d.ts +42 -0
- package/dist/core/skill-sync/builtins.js +112 -0
- package/dist/core/skill-sync/claude-plugins.d.ts +23 -0
- package/dist/core/skill-sync/claude-plugins.js +71 -0
- package/dist/core/skill-sync/engine.d.ts +42 -0
- package/dist/core/skill-sync/engine.js +633 -0
- package/dist/core/skill-sync/export.d.ts +23 -0
- package/dist/core/skill-sync/export.js +86 -0
- package/dist/core/skill-sync/manifest.d.ts +64 -0
- package/dist/core/skill-sync/manifest.js +181 -0
- package/dist/core/skill-sync/profile.d.ts +76 -0
- package/dist/core/skill-sync/profile.js +173 -0
- package/dist/core/skill-sync/snapshot.d.ts +57 -0
- package/dist/core/skill-sync/snapshot.js +120 -0
- package/dist/core/spawn.d.ts +14 -0
- package/dist/core/spawn.js +29 -9
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +7 -7
- package/dist/core/substrate/injected-store.d.ts +10 -0
- package/dist/core/substrate/injected-store.js +55 -0
- package/dist/core/substrate/on-read.js +68 -6
- package/dist/core/substrate/render.d.ts +8 -11
- package/dist/core/substrate/render.js +29 -43
- package/dist/core/substrate/schema.d.ts +16 -11
- package/dist/core/substrate/schema.js +32 -31
- package/dist/core/tui/host.d.ts +12 -4
- package/dist/core/tui/host.js +280 -149
- package/dist/core/view/bridge.d.ts +10 -0
- package/dist/core/view/bridge.js +31 -0
- package/dist/core/view/chrome.d.ts +9 -0
- package/dist/core/view/chrome.js +22 -0
- package/dist/core/view/contract.d.ts +171 -0
- package/dist/core/view/contract.js +23 -0
- package/dist/core/view/loader.d.ts +31 -0
- package/dist/core/view/loader.js +188 -0
- package/dist/core/view/transport-local.d.ts +7 -0
- package/dist/core/view/transport-local.js +70 -0
- package/dist/core/view/transport.d.ts +4 -0
- package/dist/core/view/transport.js +15 -0
- package/dist/daemon/crtrd.d.ts +38 -27
- package/dist/daemon/crtrd.js +303 -376
- package/dist/daemon/manage.js +6 -1
- package/dist/index.js +1 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +28 -20
- package/dist/pi-extensions/canvas-context-intro.js +4 -4
- package/dist/pi-extensions/canvas-doc-substrate.js +29 -20
- package/dist/pi-extensions/canvas-nav.js +35 -385
- package/dist/pi-extensions/canvas-recap.d.ts +37 -0
- package/dist/pi-extensions/canvas-recap.js +236 -0
- package/dist/pi-extensions/canvas-resume.js +2 -1
- package/dist/pi-extensions/canvas-stophook.d.ts +15 -13
- package/dist/pi-extensions/canvas-stophook.js +119 -103
- package/dist/pi-extensions/canvas-view.js +2 -1
- package/dist/pi-extensions/widget-order-bus.d.ts +6 -0
- package/dist/pi-extensions/widget-order-bus.js +34 -0
- package/dist/prompts/view.d.ts +2 -2
- package/dist/prompts/view.js +145 -73
- package/dist/types.d.ts +1 -6
- package/dist/types.js +1 -3
- package/dist/web/ViewChrome.d.ts +7 -0
- package/dist/web/ViewChrome.js +28 -0
- package/dist/web/ViewPane.d.ts +39 -0
- package/dist/web/ViewPane.js +48 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +16 -0
- package/dist/web/runtime.d.ts +39 -0
- package/dist/web/runtime.js +133 -0
- package/dist/web/states.d.ts +24 -0
- package/dist/web/states.js +24 -0
- package/dist/web/transport-http.d.ts +5 -0
- package/dist/web/transport-http.js +28 -0
- package/dist/web-client/assets/index-BUvQb4hR.css +2 -0
- package/dist/web-client/assets/index-ClLQXYAE.js +10 -0
- package/dist/web-client/index.html +13 -0
- package/package.json +20 -6
- package/dist/builtin-views/canvas/client.mjs +0 -303
- package/dist/builtin-views/canvas/view.mjs +0 -576
- package/dist/builtin-views/git-pr/client.mjs +0 -440
- package/dist/builtin-views/git-pr/view.mjs +0 -675
- package/dist/builtin-views/inbox/sources/gmail.mjs +0 -965
- package/dist/builtin-views/inbox/sources/linkedin.mjs +0 -427
- package/dist/builtin-views/inbox/view.mjs +0 -889
- package/dist/builtin-views/linkedin/client.mjs +0 -610
- package/dist/builtin-views/linkedin/view.mjs +0 -1171
- package/dist/commands/pkg/bridge.d.ts +0 -1
- package/dist/commands/pkg/bridge.js +0 -137
- package/dist/commands/skill/author.d.ts +0 -3
- package/dist/commands/skill/author.js +0 -140
- package/dist/commands/skill/shared.d.ts +0 -3
- package/dist/commands/skill/shared.js +0 -19
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -21
- package/dist/commands/tmux-spread.d.ts +0 -2
- package/dist/commands/tmux-spread.js +0 -144
- package/dist/core/__tests__/full/daemon-liveness-pane.full.test.js +0 -292
- package/dist/core/__tests__/full/placement-focus.test.js +0 -309
- package/dist/core/__tests__/full/placement-reconcile.test.js +0 -212
- package/dist/core/__tests__/full/placement-revive.test.js +0 -238
- package/dist/core/__tests__/full/placement-teardown.test.js +0 -221
- package/dist/core/__tests__/home-session.test.js +0 -186
- package/dist/core/__tests__/relaunch.test.js +0 -335
- package/dist/core/bridge-map.d.ts +0 -19
- package/dist/core/bridge-map.js +0 -73
- package/dist/core/tui/contract.d.ts +0 -83
- package/dist/core/tui/contract.js +0 -8
- package/dist/core/tui/loader.d.ts +0 -16
- package/dist/core/tui/loader.js +0 -94
- package/dist/prompts/skill.d.ts +0 -2
- package/dist/prompts/skill.js +0 -650
- /package/dist/{core/__tests__/full/daemon-liveness-pane.full.test.d.ts → clients/attach/__tests__/action-parity.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-focus.test.d.ts → clients/attach/__tests__/clipboard-image.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-reconcile.test.d.ts → clients/attach/__tests__/editor-newline.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-revive.test.d.ts → clients/attach/__tests__/git-info.test.d.ts} +0 -0
- /package/dist/{core/__tests__/full/placement-teardown.test.d.ts → clients/attach/__tests__/onrequest-wired.test.d.ts} +0 -0
- /package/dist/{core/__tests__/home-session.test.d.ts → clients/attach/__tests__/reconnect-giveup.test.d.ts} +0 -0
- /package/dist/{core/__tests__/relaunch.test.d.ts → clients/attach/__tests__/slash-quit-copy.test.d.ts} +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-pair merge-base snapshot store + conflict-report helpers.
|
|
3
|
+
*
|
|
4
|
+
* All machine-local skill-sync I/O lives here. Nothing in this module is
|
|
5
|
+
* committed: everything is rooted under `~/.crouter/skill-sync/` (R-U9), which
|
|
6
|
+
* is the machine-local, uncommitted tier — never a scope's committed dirs.
|
|
7
|
+
*
|
|
8
|
+
* snapshots/<pair-id>/body.md merge base, frontmatter stripped (R-I3)
|
|
9
|
+
* snapshots/<pair-id>/assets/… byte-for-byte base of every non-SKILL.md
|
|
10
|
+
* file, mirroring subdir structure (R-I3)
|
|
11
|
+
* snapshots/<pair-id>/meta.json { syncedAt, crtrFrontmatter, claudeFrontmatter }
|
|
12
|
+
* conflicts/<pair-id>.md current conflict report, if any (R-E6/OD-4)
|
|
13
|
+
*
|
|
14
|
+
* The snapshot is the SOLE change-detector (R-U4): the engine diffs body/assets
|
|
15
|
+
* against `body.md`/`assets/`, and each frontmatter value against the recorded
|
|
16
|
+
* `meta.json` value — no mtime, no fingerprint, no stamp file.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
19
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
20
|
+
import { userScopeRoot } from '../scope.js';
|
|
21
|
+
// ── Path roots ───────────────────────────────────────────────────────────────
|
|
22
|
+
/** `~/.crouter/skill-sync/` — the machine-local, uncommitted root (R-U9). */
|
|
23
|
+
export function skillSyncRoot() {
|
|
24
|
+
return join(userScopeRoot(), 'skill-sync');
|
|
25
|
+
}
|
|
26
|
+
function snapshotsRoot() {
|
|
27
|
+
return join(skillSyncRoot(), 'snapshots');
|
|
28
|
+
}
|
|
29
|
+
function conflictsRoot() {
|
|
30
|
+
return join(skillSyncRoot(), 'conflicts');
|
|
31
|
+
}
|
|
32
|
+
/** `~/.crouter/skill-sync/snapshots/<id>/`. */
|
|
33
|
+
export function snapshotDir(id) {
|
|
34
|
+
return join(snapshotsRoot(), id);
|
|
35
|
+
}
|
|
36
|
+
/** `~/.crouter/skill-sync/conflicts/<id>.md`. */
|
|
37
|
+
export function conflictReportPath(id) {
|
|
38
|
+
return join(conflictsRoot(), `${id}.md`);
|
|
39
|
+
}
|
|
40
|
+
// ── Asset helpers ────────────────────────────────────────────────────────────
|
|
41
|
+
/** Recursively read every file under `dir`, keyed by POSIX relpath. */
|
|
42
|
+
function readAssetTree(dir) {
|
|
43
|
+
const out = new Map();
|
|
44
|
+
if (!existsSync(dir))
|
|
45
|
+
return out;
|
|
46
|
+
const walk = (cur) => {
|
|
47
|
+
for (const entry of readdirSync(cur, { withFileTypes: true })) {
|
|
48
|
+
const full = join(cur, entry.name);
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
walk(full);
|
|
51
|
+
}
|
|
52
|
+
else if (entry.isFile()) {
|
|
53
|
+
const rel = relative(dir, full).split(sep).join('/');
|
|
54
|
+
out.set(rel, readFileSync(full));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
walk(dir);
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
// ── Snapshot read / write ────────────────────────────────────────────────────
|
|
62
|
+
/**
|
|
63
|
+
* Load the merge base for `id`, or `null` when the snapshot dir is absent
|
|
64
|
+
* (no prior sync — the engine seeds on first reconcile, R-S1/R-S3).
|
|
65
|
+
*/
|
|
66
|
+
export function readSnapshot(id) {
|
|
67
|
+
const dir = snapshotDir(id);
|
|
68
|
+
if (!existsSync(dir))
|
|
69
|
+
return null;
|
|
70
|
+
const bodyPath = join(dir, 'body.md');
|
|
71
|
+
const body = existsSync(bodyPath) ? readFileSync(bodyPath, 'utf8') : '';
|
|
72
|
+
const assets = readAssetTree(join(dir, 'assets'));
|
|
73
|
+
const metaPath = join(dir, 'meta.json');
|
|
74
|
+
const meta = existsSync(metaPath)
|
|
75
|
+
? JSON.parse(readFileSync(metaPath, 'utf8'))
|
|
76
|
+
: { syncedAt: '', crtrFrontmatter: {}, claudeFrontmatter: {} };
|
|
77
|
+
return { body, assets, meta };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Write the merge base for `id` byte-for-byte. The snapshot dir is rebuilt
|
|
81
|
+
* from scratch so stale assets from a prior base never linger — the on-disk
|
|
82
|
+
* `assets/` always mirrors exactly the passed `assets` map (R-U5/R-I3).
|
|
83
|
+
*/
|
|
84
|
+
export function writeSnapshot(id, snapshot) {
|
|
85
|
+
const dir = snapshotDir(id);
|
|
86
|
+
rmSync(dir, { recursive: true, force: true });
|
|
87
|
+
mkdirSync(dir, { recursive: true });
|
|
88
|
+
writeFileSync(join(dir, 'body.md'), snapshot.body, 'utf8');
|
|
89
|
+
const assetsDir = join(dir, 'assets');
|
|
90
|
+
mkdirSync(assetsDir, { recursive: true });
|
|
91
|
+
for (const [rel, buf] of snapshot.assets) {
|
|
92
|
+
const full = join(assetsDir, ...rel.split('/'));
|
|
93
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
94
|
+
writeFileSync(full, buf);
|
|
95
|
+
}
|
|
96
|
+
writeFileSync(join(dir, 'meta.json'), `${JSON.stringify(snapshot.meta, null, 2)}\n`, 'utf8');
|
|
97
|
+
}
|
|
98
|
+
// ── Conflict-report helpers (R-E6 / OD-4) ────────────────────────────────────
|
|
99
|
+
/**
|
|
100
|
+
* Write the current conflict report for `id` to `conflicts/<id>.md`. The engine
|
|
101
|
+
* calls this on any true conflict (overlapping body/asset merge or both-sides
|
|
102
|
+
* non-equivalent frontmatter) after deciding to write nothing for the pair.
|
|
103
|
+
*/
|
|
104
|
+
export function writeConflictReport(id, text) {
|
|
105
|
+
const path = conflictReportPath(id);
|
|
106
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
107
|
+
writeFileSync(path, text, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Delete a stale `conflicts/<id>.md` once the pair reconciles cleanly or
|
|
111
|
+
* becomes a no-op (OD-4). No-op if absent — never throws on a missing report.
|
|
112
|
+
*/
|
|
113
|
+
export function clearConflictReport(id) {
|
|
114
|
+
rmSync(conflictReportPath(id), { force: true });
|
|
115
|
+
}
|
|
116
|
+
/** True iff a conflict report currently exists for `id`. */
|
|
117
|
+
export function hasConflictReport(id) {
|
|
118
|
+
const path = conflictReportPath(id);
|
|
119
|
+
return existsSync(path) && statSync(path).isFile();
|
|
120
|
+
}
|
package/dist/core/spawn.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
/** Does THIS process sit inside a tmux client (its own $TMUX is set)? NOT the
|
|
2
|
+
* gate for the human surface — see tmuxServerReachable. A headless-broker child
|
|
3
|
+
* strips TMUX/TMUX_PANE (broker.ts, anti-stophook-hijack) yet can still drive
|
|
4
|
+
* the canvas tmux server, so gating the surface on this wrongly starves broker
|
|
5
|
+
* nodes of human-in-the-loop. */
|
|
1
6
|
export declare function isInTmux(): boolean;
|
|
7
|
+
/** Is the canvas tmux SERVER reachable? The canvas runs on the DEFAULT tmux
|
|
8
|
+
* socket (no -L/-S in core/runtime/tmux.ts), so any `crtr` child — including a
|
|
9
|
+
* headless-broker child that has deleted its own $TMUX — can split panes into
|
|
10
|
+
* it. The human surface gates on THIS (can I reach the server to open a pane
|
|
11
|
+
* the user is watching?), not on the caller's $TMUX. `list-clients` exits 0
|
|
12
|
+
* whenever a server is up (even with zero attached clients) and non-zero when
|
|
13
|
+
* no server is running — the true "no tmux to surface into" case that should
|
|
14
|
+
* degrade to the inbox-drain follow-up. */
|
|
15
|
+
export declare function tmuxServerReachable(): boolean;
|
|
2
16
|
export declare function shellQuote(s: string): string;
|
|
3
17
|
/** Count panes in a tmux window (0 outside tmux / on error). With `targetPane`,
|
|
4
18
|
* counts the window THAT pane lives in (the placement decision must reflect the
|
package/dist/core/spawn.js
CHANGED
|
@@ -3,13 +3,30 @@
|
|
|
3
3
|
// A small set of tmux primitives used by the `human` command tree to put the
|
|
4
4
|
// humanloop TUI in a detached pane: spawnAndDetach (open a pane running a given
|
|
5
5
|
// command), countPanesInCurrentWindow (placement decision), plus shellQuote and
|
|
6
|
-
//
|
|
7
|
-
// core/runtime/tmux.ts; this module is only the pane-split path the
|
|
8
|
-
// needs.
|
|
6
|
+
// tmuxServerReachable. The canvas runtime has its own one-window-per-node
|
|
7
|
+
// machinery in core/runtime/tmux.ts; this module is only the pane-split path the
|
|
8
|
+
// human TUI needs.
|
|
9
9
|
import { spawnSync } from 'node:child_process';
|
|
10
|
+
/** Does THIS process sit inside a tmux client (its own $TMUX is set)? NOT the
|
|
11
|
+
* gate for the human surface — see tmuxServerReachable. A headless-broker child
|
|
12
|
+
* strips TMUX/TMUX_PANE (broker.ts, anti-stophook-hijack) yet can still drive
|
|
13
|
+
* the canvas tmux server, so gating the surface on this wrongly starves broker
|
|
14
|
+
* nodes of human-in-the-loop. */
|
|
10
15
|
export function isInTmux() {
|
|
11
16
|
return Boolean(process.env.TMUX);
|
|
12
17
|
}
|
|
18
|
+
/** Is the canvas tmux SERVER reachable? The canvas runs on the DEFAULT tmux
|
|
19
|
+
* socket (no -L/-S in core/runtime/tmux.ts), so any `crtr` child — including a
|
|
20
|
+
* headless-broker child that has deleted its own $TMUX — can split panes into
|
|
21
|
+
* it. The human surface gates on THIS (can I reach the server to open a pane
|
|
22
|
+
* the user is watching?), not on the caller's $TMUX. `list-clients` exits 0
|
|
23
|
+
* whenever a server is up (even with zero attached clients) and non-zero when
|
|
24
|
+
* no server is running — the true "no tmux to surface into" case that should
|
|
25
|
+
* degrade to the inbox-drain follow-up. */
|
|
26
|
+
export function tmuxServerReachable() {
|
|
27
|
+
const r = spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], { encoding: 'utf8' });
|
|
28
|
+
return r.status === 0;
|
|
29
|
+
}
|
|
13
30
|
export function shellQuote(s) {
|
|
14
31
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
15
32
|
}
|
|
@@ -34,7 +51,7 @@ export function countPanesInCurrentWindow() {
|
|
|
34
51
|
* output on an unresolvable pane, so test for non-empty stdout, not just `.ok`.
|
|
35
52
|
* False outside tmux / on error. */
|
|
36
53
|
export function paneAlive(pane) {
|
|
37
|
-
if (
|
|
54
|
+
if (!/^%\d+$/.test(pane))
|
|
38
55
|
return false;
|
|
39
56
|
const r = spawnSync('tmux', ['display-message', '-p', '-t', pane, '#{pane_id}'], {
|
|
40
57
|
encoding: 'utf8',
|
|
@@ -46,7 +63,7 @@ export function paneAlive(pane) {
|
|
|
46
63
|
* specify pane here"); only split-window -t takes a pane. null outside tmux /
|
|
47
64
|
* on a bad pane id / on error / empty. */
|
|
48
65
|
export function paneWindowTarget(pane) {
|
|
49
|
-
if (
|
|
66
|
+
if (!/^%\d+$/.test(pane))
|
|
50
67
|
return null;
|
|
51
68
|
const r = spawnSync('tmux', ['display-message', '-p', '-t', pane, '#{session_name}:#{window_index}'], { encoding: 'utf8' });
|
|
52
69
|
if (r.status !== 0)
|
|
@@ -59,8 +76,6 @@ export function paneWindowTarget(pane) {
|
|
|
59
76
|
* to surface a human prompt in the user's view when nothing in the asking
|
|
60
77
|
* node's graph is focused. null outside tmux / no client / on error. */
|
|
61
78
|
export function attachedClientPane() {
|
|
62
|
-
if (!isInTmux())
|
|
63
|
-
return null;
|
|
64
79
|
const clients = spawnSync('tmux', ['list-clients', '-F', '#{client_name}'], {
|
|
65
80
|
encoding: 'utf8',
|
|
66
81
|
});
|
|
@@ -102,10 +117,15 @@ export function scheduleKillCurrentPane(delaySeconds) {
|
|
|
102
117
|
* as soon as the new pane is up; does NOT wait for the command to finish.
|
|
103
118
|
*/
|
|
104
119
|
export function spawnAndDetach(opts) {
|
|
105
|
-
|
|
120
|
+
// Gate on the CANVAS tmux server's reachability, NOT the caller's $TMUX. A
|
|
121
|
+
// headless-broker child strips its own $TMUX (broker.ts) but the canvas server
|
|
122
|
+
// is on the default socket, so `split-window -t <pane>` against a resolved
|
|
123
|
+
// target pane (resolveHumanTarget) works fine from it. Only a genuine
|
|
124
|
+
// no-server case degrades to the inbox-drain follow-up.
|
|
125
|
+
if (!tmuxServerReachable()) {
|
|
106
126
|
return {
|
|
107
127
|
status: 'not-in-tmux',
|
|
108
|
-
message: 'handoff requires
|
|
128
|
+
message: 'handoff requires a reachable tmux server',
|
|
109
129
|
};
|
|
110
130
|
}
|
|
111
131
|
const splitArgs = [];
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast,
|
|
1
|
+
export { KINDS, isDocKind, RUNGS, rungRank, rungAtLeast, FALLBACK_RUNG, parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, } from './schema.js';
|
|
2
2
|
export type { DocKind, Rung, GatePredicate, SubstrateSchema, SubstrateDoc } from './schema.js';
|
|
3
3
|
export { scopeForCwd, spineDepth, assembleNodeSubject } from './subject.js';
|
|
4
4
|
export type { NodeConfigSubject } from './subject.js';
|
|
5
5
|
export { gatePasses } from './gate.js';
|
|
6
6
|
export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveRung, } from './ceiling.js';
|
|
7
7
|
export type { Surface } from './ceiling.js';
|
|
8
|
-
export {
|
|
8
|
+
export { renderPreferencesSection, renderKnowledgeBlock, renderMemoryGuidance, } from './render.js';
|
|
9
9
|
export { renderOnReadDocs } from './on-read.js';
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// substrate/ — the unified document-substrate keystone: the frontmatter schema,
|
|
2
|
-
// the 4-rung visibility ladder, the
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// the 4-rung visibility ladder, the node-config subject assembly, and gate
|
|
3
|
+
// evaluation. Pure + well-typed; the shared base the CLI verbs, boot render,
|
|
4
|
+
// on-read render, and migrator all build on. See design-substrate.md §4 +
|
|
5
|
+
// plan-substrate.md §2.
|
|
6
6
|
export {
|
|
7
7
|
// kinds
|
|
8
8
|
KINDS, isDocKind,
|
|
9
9
|
// ladder
|
|
10
10
|
RUNGS, rungRank, rungAtLeast,
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// fallback floor
|
|
12
|
+
FALLBACK_RUNG,
|
|
13
13
|
// parse + render-shared helpers
|
|
14
14
|
parseSubstrateFrontmatter, parseSubstrateDoc, previewLine, } from './schema.js';
|
|
15
15
|
export { scopeForCwd, spineDepth, assembleNodeSubject } from './subject.js';
|
|
16
16
|
export { gatePasses } from './gate.js';
|
|
17
17
|
export { INDEX_NAME, isIndexName, indexDirOf, displayName, buildCeilingIndex, effectiveRung, } from './ceiling.js';
|
|
18
|
-
export {
|
|
18
|
+
export { renderPreferencesSection, renderKnowledgeBlock, renderMemoryGuidance, } from './render.js';
|
|
19
19
|
export { renderOnReadDocs } from './on-read.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Rehydrate a node's on-read dedup set from disk. Returns an empty set when the
|
|
2
|
+
* file is absent, unreadable, or malformed (a fresh transcript, or a node that
|
|
3
|
+
* has not yet surfaced any on-read doc). */
|
|
4
|
+
export declare function loadInjectedDocs(nodeId: string): Set<string>;
|
|
5
|
+
/** Persist a node's on-read dedup set. Called after each read that surfaced a
|
|
6
|
+
* new doc, so the grown set survives a later dormancy. */
|
|
7
|
+
export declare function saveInjectedDocs(nodeId: string, seen: Set<string>): void;
|
|
8
|
+
/** Drop a node's persisted dedup set. Called by the launch paths that start a
|
|
9
|
+
* FRESH transcript, so the new conversation surfaces docs from scratch. */
|
|
10
|
+
export declare function clearInjectedDocs(nodeId: string): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// injected-store.ts — durable per-node dedup set for on-read doc injection.
|
|
2
|
+
//
|
|
3
|
+
// The on-read substrate hook (canvas-doc-substrate.ts → renderOnReadDocs) dedups
|
|
4
|
+
// so a given doc surfaces at most once per conversation. That set USED to live
|
|
5
|
+
// only in the pi process heap, cleared on session_start. But a node's logical
|
|
6
|
+
// session — the .jsonl transcript — spans MULTIPLE pi processes: a dormancy →
|
|
7
|
+
// revive(resume) cycle exits the old process and launches a fresh `pi --session`
|
|
8
|
+
// that REUSES the same transcript. The fresh process started with an empty set,
|
|
9
|
+
// so any doc already injected before dormancy got injected AGAIN on the next
|
|
10
|
+
// read — the "fires a second time per session" bug.
|
|
11
|
+
//
|
|
12
|
+
// This module persists the set to `nodes/<id>/injected-docs.json` so the resumed
|
|
13
|
+
// process rehydrates it and skips docs already present in the transcript. The
|
|
14
|
+
// one launch path that begins a FRESH transcript (reviveNode with resume=false,
|
|
15
|
+
// in runtime/revive.ts) calls clearInjectedDocs(), so a new conversation starts
|
|
16
|
+
// with an empty set.
|
|
17
|
+
//
|
|
18
|
+
// All ops are best-effort: a failed read/write degrades to a possible re-inject,
|
|
19
|
+
// never a crash — a dedup miss must never break a read or a revive.
|
|
20
|
+
import { readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
21
|
+
import { injectedDocsPath } from '../canvas/paths.js';
|
|
22
|
+
/** Rehydrate a node's on-read dedup set from disk. Returns an empty set when the
|
|
23
|
+
* file is absent, unreadable, or malformed (a fresh transcript, or a node that
|
|
24
|
+
* has not yet surfaced any on-read doc). */
|
|
25
|
+
export function loadInjectedDocs(nodeId) {
|
|
26
|
+
try {
|
|
27
|
+
const arr = JSON.parse(readFileSync(injectedDocsPath(nodeId), 'utf8'));
|
|
28
|
+
if (!Array.isArray(arr))
|
|
29
|
+
return new Set();
|
|
30
|
+
return new Set(arr.filter((x) => typeof x === 'string'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return new Set();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Persist a node's on-read dedup set. Called after each read that surfaced a
|
|
37
|
+
* new doc, so the grown set survives a later dormancy. */
|
|
38
|
+
export function saveInjectedDocs(nodeId, seen) {
|
|
39
|
+
try {
|
|
40
|
+
writeFileSync(injectedDocsPath(nodeId), JSON.stringify([...seen]));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// best-effort — a failed persist only risks a re-inject, never a crash
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Drop a node's persisted dedup set. Called by the launch paths that start a
|
|
47
|
+
* FRESH transcript, so the new conversation surfaces docs from scratch. */
|
|
48
|
+
export function clearInjectedDocs(nodeId) {
|
|
49
|
+
try {
|
|
50
|
+
rmSync(injectedDocsPath(nodeId), { force: true });
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// ignore — absence is the desired state anyway
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -3,16 +3,25 @@
|
|
|
3
3
|
// When a `read` tool call returns, the canvas-doc-substrate pi extension calls
|
|
4
4
|
// renderOnReadDocs() to surface the substrate docs that should appear ALONGSIDE
|
|
5
5
|
// the file just read — each at its FILE-READ-VISIBILITY rung (NOT the
|
|
6
|
-
// system-prompt rung the boot render uses).
|
|
7
|
-
//
|
|
6
|
+
// system-prompt rung the boot render uses). Three triggers decide which docs
|
|
7
|
+
// surface (design §4/§6; Stream A native rules):
|
|
8
8
|
//
|
|
9
9
|
// • POSITIONAL — walk the read file's ancestor dirs; any doc living in an
|
|
10
10
|
// ancestor's `.crouter/memory/` surfaces (a doc surfaces when a file
|
|
11
|
-
// beside/under its own scope dir is read)
|
|
12
|
-
//
|
|
11
|
+
// beside/under its own scope dir is read), UNLESS it carries an explicit
|
|
12
|
+
// read-trigger (see D5 below). This mirrors nested-context's `.claude/rules`
|
|
13
|
+
// ancestor walk, but keyed on `.crouter/memory/`.
|
|
13
14
|
// • applies-to GLOB — any RESOLVED substrate doc (user/project/builtin scope)
|
|
14
15
|
// whose `appliesTo` glob matches the read file path surfaces, regardless of
|
|
15
16
|
// where the read file sits relative to the doc.
|
|
17
|
+
// • read-when FRONTMATTER (Stream A) — any RESOLVED doc whose `readWhen`
|
|
18
|
+
// condition matches the READ FILE's own parsed frontmatter (markdown only),
|
|
19
|
+
// via `evalCondition` against that YAML rather than the node subject.
|
|
20
|
+
//
|
|
21
|
+
// D5 — an EXPLICIT read-trigger (applies-to OR read-when) SUPPRESSES the
|
|
22
|
+
// positional default for that doc: an author who declared a trigger meant it, so
|
|
23
|
+
// the doc fires only on its trigger, never positionally on every read under its
|
|
24
|
+
// scope. The two explicit triggers compose by OR.
|
|
16
25
|
//
|
|
17
26
|
// Each candidate runs the substrate pipeline at its fileReadVisibility rung:
|
|
18
27
|
// parse → gatePasses(doc, assembleNodeSubject(nodeId)) → render
|
|
@@ -35,6 +44,7 @@ import { basename, dirname, join, matchesGlob, parse, relative, sep } from 'node
|
|
|
35
44
|
import { CRTR_DIR_NAME } from '../../types.js';
|
|
36
45
|
import { pathExists, readText, walkFiles } from '../fs-utils.js';
|
|
37
46
|
import { parseFrontmatterGeneric } from '../frontmatter.js';
|
|
47
|
+
import { evalCondition } from '../predicate.js';
|
|
38
48
|
import { listAllMemoryDocs } from '../memory-resolver.js';
|
|
39
49
|
import { assembleNodeSubject, buildCeilingIndex, displayName, effectiveRung, gatePasses, parseSubstrateDoc, parseSubstrateFrontmatter, previewLine, } from './index.js';
|
|
40
50
|
import { cachedSubstrateDocs } from './session-cache.js';
|
|
@@ -117,6 +127,12 @@ function safeWalkMd(dir) {
|
|
|
117
127
|
return [];
|
|
118
128
|
}
|
|
119
129
|
}
|
|
130
|
+
/** Does the doc declare an EXPLICIT read-trigger? (applies-to glob OR read-when
|
|
131
|
+
* frontmatter condition). D5: an explicit trigger SUPPRESSES the positional
|
|
132
|
+
* default — such a doc fires only on its declared trigger, never positionally. */
|
|
133
|
+
function hasExplicitTrigger(doc) {
|
|
134
|
+
return (doc.appliesTo !== undefined && doc.appliesTo.length > 0) || doc.readWhen !== undefined;
|
|
135
|
+
}
|
|
120
136
|
/** POSITIONAL trigger: every substrate doc in an ancestor dir's
|
|
121
137
|
* `.crouter/memory/`, walking from the read file up to $HOME (or the
|
|
122
138
|
* filesystem root for a read outside $HOME), skipping junk ancestors. */
|
|
@@ -138,7 +154,9 @@ function positionalCandidates(absReadFile) {
|
|
|
138
154
|
continue;
|
|
139
155
|
seenDocPaths.add(real);
|
|
140
156
|
const doc = loadPositionalDoc(file, memDir, scope);
|
|
141
|
-
|
|
157
|
+
// D5: a doc with an explicit read-trigger (applies-to/read-when) does
|
|
158
|
+
// NOT fire positionally — it surfaces only via that trigger.
|
|
159
|
+
if (doc && !hasExplicitTrigger(doc))
|
|
142
160
|
out.push({ doc, realpath: real, order: depth });
|
|
143
161
|
}
|
|
144
162
|
}
|
|
@@ -204,6 +222,49 @@ function appliesToCandidates(absReadFile, taken) {
|
|
|
204
222
|
}
|
|
205
223
|
return out;
|
|
206
224
|
}
|
|
225
|
+
/** Parse the READ FILE's own YAML frontmatter once (markdown only), the subject
|
|
226
|
+
* a `read-when` condition is evaluated against. Non-markdown reads (and any
|
|
227
|
+
* unreadable / frontmatter-less file) yield `{}` — no `read-when` rule can then
|
|
228
|
+
* match (mirrors pi's frontmatter-rules, which only consider .md/.mdx/.markdown). */
|
|
229
|
+
function readFileFrontmatter(absReadFile) {
|
|
230
|
+
if (!/\.(md|mdx|markdown)$/i.test(absReadFile))
|
|
231
|
+
return {};
|
|
232
|
+
try {
|
|
233
|
+
return parseFrontmatterGeneric(readText(absReadFile)).data ?? {};
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** read-when FRONTMATTER trigger (Stream A): every RESOLVED substrate doc whose
|
|
240
|
+
* `readWhen` condition matches the read file's own frontmatter. `taken` carries
|
|
241
|
+
* the realpaths already claimed by earlier passes so a doc is not double-counted.
|
|
242
|
+
* Uses the per-session cache (same corpus the glob pass walks). Returns [] when
|
|
243
|
+
* the read file has no frontmatter — no condition can match an empty subject. */
|
|
244
|
+
function readWhenCandidates(readFileFm, taken) {
|
|
245
|
+
if (Object.keys(readFileFm).length === 0)
|
|
246
|
+
return [];
|
|
247
|
+
let docs;
|
|
248
|
+
try {
|
|
249
|
+
docs = cachedSubstrateDocs(listAllMemoryDocs, parseSubstrateDoc);
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
const out = [];
|
|
255
|
+
for (const doc of docs) {
|
|
256
|
+
if (doc.readWhen === undefined)
|
|
257
|
+
continue;
|
|
258
|
+
const real = realpathOrSelf(doc.path);
|
|
259
|
+
if (taken.has(real))
|
|
260
|
+
continue;
|
|
261
|
+
if (!evalCondition(doc.readWhen, readFileFm))
|
|
262
|
+
continue;
|
|
263
|
+
taken.add(real);
|
|
264
|
+
out.push({ doc, realpath: real, order: -1 });
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
207
268
|
// ---------------------------------------------------------------------------
|
|
208
269
|
// Per-doc envelope render.
|
|
209
270
|
// ---------------------------------------------------------------------------
|
|
@@ -256,7 +317,8 @@ export function renderOnReadDocs(nodeId, readFilePath, seen = new Set()) {
|
|
|
256
317
|
const positional = positionalCandidates(absReadFile);
|
|
257
318
|
const taken = new Set(positional.map((c) => c.realpath));
|
|
258
319
|
const byGlob = appliesToCandidates(absReadFile, taken);
|
|
259
|
-
|
|
320
|
+
const byReadWhen = readWhenCandidates(readFileFrontmatter(absReadFile), taken);
|
|
321
|
+
candidates = [...positional, ...byGlob, ...byReadWhen];
|
|
260
322
|
}
|
|
261
323
|
catch {
|
|
262
324
|
return '';
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
/** The `<skills>` system-prompt block: every eligible `kind: skill` doc placed
|
|
2
|
-
* in one tree at its effective rung. Plugin skills (`<plugin>/…` names) fall out
|
|
3
|
-
* naturally as dirs. Returns '' when nothing is eligible. */
|
|
4
|
-
export declare function renderSkillsSection(nodeId: string): string;
|
|
5
1
|
/** The `<preferences>` system-prompt block: every eligible `kind: preference`
|
|
6
2
|
* doc in one tree at its effective rung (the preference default rung is
|
|
7
3
|
* `preview` → the routing line). Returns '' when nothing is eligible. */
|
|
8
4
|
export declare function renderPreferencesSection(nodeId: string): string;
|
|
9
|
-
/** The `<
|
|
5
|
+
/** The `<knowledge>` block embedded INSIDE the `<crtr-context>` session_start
|
|
10
6
|
* message (bearings.ts pushes the returned string into the block, or drops it
|
|
11
|
-
* when '').
|
|
12
|
-
* rung (
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
7
|
+
* when ''). The consultable catalog: every eligible `kind: knowledge` resolver
|
|
8
|
+
* doc at its effective rung (most surface only as `[+N more]` counts unless
|
|
9
|
+
* author-promoted) PLUS the node-local memory docs (any kind), the latter
|
|
10
|
+
* floored to `name` so a `none`-rung node-local doc still shows its name rather
|
|
11
|
+
* than collapsing into a count. Procedural skills and factual references both
|
|
12
|
+
* live here now — they merged into the one `knowledge` kind. Returns '' when
|
|
16
13
|
* nothing is eligible. */
|
|
17
|
-
export declare function
|
|
14
|
+
export declare function renderKnowledgeBlock(nodeId: string): string;
|
|
18
15
|
/** The memory-hygiene directive spliced into the system prompt after the
|
|
19
16
|
* preferences block. ALWAYS present for a canvas node (the memory system always
|
|
20
17
|
* exists), so the system-prompt splice stays non-empty even when both trees are
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
// line; content → the full body indented beneath the entry); hidden (`none`-rung)
|
|
7
7
|
// docs leak only a `[+N more]` count under their dir, never a name.
|
|
8
8
|
//
|
|
9
|
-
// • the SYSTEM-PROMPT half — `<
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// • the `<crtr-context>` half — `<
|
|
14
|
-
//
|
|
9
|
+
// • the SYSTEM-PROMPT half — `<preferences>` (renderPreferencesSection) +
|
|
10
|
+
// `<memory-guidance>` (renderMemoryGuidance) — strings the
|
|
11
|
+
// canvas-doc-substrate `before_agent_start` extension splices into the
|
|
12
|
+
// system prompt before the `\n\nGuidelines:` anchor;
|
|
13
|
+
// • the `<crtr-context>` half — `<knowledge>` (renderKnowledgeBlock) — the
|
|
14
|
+
// consultable catalog (every `kind: knowledge` doc ∪ node-local) the string
|
|
15
|
+
// bearings.ts embeds in the session_start message.
|
|
15
16
|
//
|
|
16
17
|
// Every doc flows through the same pipeline: MemoryDoc → parseSubstrateDoc →
|
|
17
18
|
// (null-filter non-substrate) → ceiling-capped effective rung → gatePasses →
|
|
@@ -73,11 +74,11 @@ function scopeRank(scope) {
|
|
|
73
74
|
* parseSubstrateFrontmatter (mirroring the resolver pipeline), then gate-passed.
|
|
74
75
|
* Non-substrate files (those with no `kind`) parse to null and drop out.
|
|
75
76
|
* Returned across ALL kinds — node-local is the catch-all this-node store and
|
|
76
|
-
* rides into the
|
|
77
|
+
* rides into the knowledge block.
|
|
77
78
|
*
|
|
78
79
|
* IMPORTANT: node-local docs are NOT filtered or capped by rung. Their contract
|
|
79
|
-
* is "node-local rides into
|
|
80
|
-
* node-local
|
|
80
|
+
* is "node-local rides into knowledge" without qualification, so a `none`-rung
|
|
81
|
+
* node-local doc must still surface — the caller floors it to `name` so it
|
|
81
82
|
* renders its name (never collapsing into a hidden count). Only gate evaluation
|
|
82
83
|
* removes a node-local doc from the block. */
|
|
83
84
|
function nodeLocalDocs(nodeId, subject) {
|
|
@@ -276,20 +277,17 @@ function buildTree(docs, rootLabel) {
|
|
|
276
277
|
// ---------------------------------------------------------------------------
|
|
277
278
|
const READ_LEGEND = 'Each %s below shows either its full content (indented beneath its name), a ' +
|
|
278
279
|
'`# read when:` line telling you when to read the full document, or simply its name.';
|
|
279
|
-
const SKILLS_INTRO = 'Skills contain procedural knowledge — playbooks and techniques for how to do things. ' +
|
|
280
|
-
'To read a skill, run `crtr memory read <name>`. Reach for a matching skill before ' +
|
|
281
|
-
'improvising. ' +
|
|
282
|
-
READ_LEGEND.replace('%s', 'skill');
|
|
283
|
-
const SKILLS_OUTRO = 'If you learn a better way to do something a skill covers, update the skill.';
|
|
284
280
|
const PREFERENCES_INTRO = 'Preferences are how the user wants you to behave — standing directives and corrections. ' +
|
|
285
281
|
'To read a preference, run `crtr memory read <name>`. ' +
|
|
286
282
|
READ_LEGEND.replace('%s', 'preference');
|
|
287
283
|
const PREFERENCES_OUTRO = 'If the user corrects you in a way that contradicts a preference, update the preference.';
|
|
288
|
-
const
|
|
289
|
-
'To read
|
|
284
|
+
const KNOWLEDGE_INTRO = 'Knowledge documents are what you consult — playbooks and techniques for how to do things, and ' +
|
|
285
|
+
'references on the user, projects, and this node. To read one, run `crtr memory read <name>`. ' +
|
|
286
|
+
'Reach for a matching document before improvising, and read one when it seems relevant to the ' +
|
|
290
287
|
'task at hand. ' +
|
|
291
|
-
READ_LEGEND.replace('%s', '
|
|
292
|
-
const
|
|
288
|
+
READ_LEGEND.replace('%s', 'document');
|
|
289
|
+
const KNOWLEDGE_OUTRO = 'If you learn a better way to do something a document covers, or gain information that ' +
|
|
290
|
+
'contradicts one, update that document.';
|
|
293
291
|
/** Wrap an intro + tree + outro in a kind-named block, or '' when the tree is
|
|
294
292
|
* empty (the whole block is dropped). */
|
|
295
293
|
function wrapBlock(tag, intro, tree, outro) {
|
|
@@ -298,20 +296,7 @@ function wrapBlock(tag, intro, tree, outro) {
|
|
|
298
296
|
return `<${tag}>\n${intro}\n\n${tree}\n\n${outro}\n</${tag}>`;
|
|
299
297
|
}
|
|
300
298
|
// ---------------------------------------------------------------------------
|
|
301
|
-
// 1.
|
|
302
|
-
// ---------------------------------------------------------------------------
|
|
303
|
-
/** The `<skills>` system-prompt block: every eligible `kind: skill` doc placed
|
|
304
|
-
* in one tree at its effective rung. Plugin skills (`<plugin>/…` names) fall out
|
|
305
|
-
* naturally as dirs. Returns '' when nothing is eligible. */
|
|
306
|
-
export function renderSkillsSection(nodeId) {
|
|
307
|
-
const subject = cachedNodeSubject(nodeId, assembleNodeSubject);
|
|
308
|
-
if (subject === null)
|
|
309
|
-
return '';
|
|
310
|
-
const tree = buildTree(effectiveDocs(subject, 'skill'), 'skills');
|
|
311
|
-
return wrapBlock('skills', SKILLS_INTRO, tree, SKILLS_OUTRO);
|
|
312
|
-
}
|
|
313
|
-
// ---------------------------------------------------------------------------
|
|
314
|
-
// 2. Preferences — `<preferences>` (system prompt).
|
|
299
|
+
// 1. Preferences — `<preferences>` (system prompt).
|
|
315
300
|
// ---------------------------------------------------------------------------
|
|
316
301
|
/** The `<preferences>` system-prompt block: every eligible `kind: preference`
|
|
317
302
|
* doc in one tree at its effective rung (the preference default rung is
|
|
@@ -324,28 +309,29 @@ export function renderPreferencesSection(nodeId) {
|
|
|
324
309
|
return wrapBlock('preferences', PREFERENCES_INTRO, tree, PREFERENCES_OUTRO);
|
|
325
310
|
}
|
|
326
311
|
// ---------------------------------------------------------------------------
|
|
327
|
-
//
|
|
312
|
+
// 2. Knowledge — `<knowledge>` (inside the <crtr-context> message).
|
|
328
313
|
// ---------------------------------------------------------------------------
|
|
329
|
-
/** The `<
|
|
314
|
+
/** The `<knowledge>` block embedded INSIDE the `<crtr-context>` session_start
|
|
330
315
|
* message (bearings.ts pushes the returned string into the block, or drops it
|
|
331
|
-
* when '').
|
|
332
|
-
* rung (
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
316
|
+
* when ''). The consultable catalog: every eligible `kind: knowledge` resolver
|
|
317
|
+
* doc at its effective rung (most surface only as `[+N more]` counts unless
|
|
318
|
+
* author-promoted) PLUS the node-local memory docs (any kind), the latter
|
|
319
|
+
* floored to `name` so a `none`-rung node-local doc still shows its name rather
|
|
320
|
+
* than collapsing into a count. Procedural skills and factual references both
|
|
321
|
+
* live here now — they merged into the one `knowledge` kind. Returns '' when
|
|
336
322
|
* nothing is eligible. */
|
|
337
|
-
export function
|
|
323
|
+
export function renderKnowledgeBlock(nodeId) {
|
|
338
324
|
const subject = cachedNodeSubject(nodeId, assembleNodeSubject);
|
|
339
325
|
if (subject === null)
|
|
340
326
|
return '';
|
|
341
327
|
const nodeLocal = nodeLocalDocs(nodeId, subject).map((d) => rungRank(d.systemPromptVisibility) >= rungRank('name')
|
|
342
328
|
? d
|
|
343
329
|
: { ...d, systemPromptVisibility: 'name' });
|
|
344
|
-
const tree = buildTree([...effectiveDocs(subject, '
|
|
345
|
-
return wrapBlock('
|
|
330
|
+
const tree = buildTree([...effectiveDocs(subject, 'knowledge'), ...nodeLocal], 'knowledge');
|
|
331
|
+
return wrapBlock('knowledge', KNOWLEDGE_INTRO, tree, KNOWLEDGE_OUTRO);
|
|
346
332
|
}
|
|
347
333
|
// ---------------------------------------------------------------------------
|
|
348
|
-
//
|
|
334
|
+
// 3. Memory-saving hygiene guidance — `<memory-guidance>` (system prompt).
|
|
349
335
|
// ---------------------------------------------------------------------------
|
|
350
336
|
/** The memory-hygiene directive spliced into the system prompt after the
|
|
351
337
|
* preferences block. ALWAYS present for a canvas node (the memory system always
|