@crouton-kit/crouter 0.3.32 → 0.3.33
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/README.md +8 -0
- package/dist/build-root.js +1 -0
- package/dist/builtin-memory/crouter-development/marketplaces.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +2 -2
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +5 -5
- package/dist/builtin-memory/crouter-development/personas.md +3 -4
- package/dist/builtin-memory/crouter-development/plugins.md +18 -18
- package/dist/builtin-memory/design.md +4 -1
- package/dist/builtin-memory/development.md +4 -1
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +6 -6
- package/dist/builtin-memory/internal/storage-tiers.md +5 -5
- package/dist/builtin-memory/planning.md +4 -1
- package/dist/builtin-memory/product.md +80 -0
- package/dist/builtin-memory/spec.md +4 -1
- package/dist/builtin-personas/advisor/PERSONA.md +10 -0
- package/dist/builtin-personas/design/PERSONA.md +2 -2
- package/dist/builtin-personas/design/orchestrator.md +3 -3
- package/dist/builtin-personas/developer/PERSONA.md +1 -1
- package/dist/builtin-personas/developer/orchestrator.md +2 -2
- package/dist/builtin-personas/explore/PERSONA.md +3 -3
- package/dist/builtin-personas/explore/orchestrator.md +4 -2
- package/dist/builtin-personas/general/PERSONA.md +1 -1
- package/dist/builtin-personas/general/orchestrator.md +1 -1
- package/dist/builtin-personas/orchestration-kernel.md +7 -14
- package/dist/builtin-personas/plan/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/orchestrator.md +2 -2
- package/dist/builtin-personas/plan/reviewers/architecture-fit/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/code-smells/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md +1 -1
- package/dist/builtin-personas/plan/reviewers/security/PERSONA.md +1 -1
- package/dist/builtin-personas/product/PERSONA.md +18 -0
- package/dist/builtin-personas/product/orchestrator.md +14 -0
- package/dist/builtin-personas/product/teardown/PERSONA.md +13 -0
- package/dist/builtin-personas/review/PERSONA.md +1 -1
- package/dist/builtin-personas/review/orchestrator.md +1 -1
- package/dist/builtin-personas/runtime-base.md +5 -0
- package/dist/builtin-personas/spec/PERSONA.md +1 -1
- package/dist/builtin-personas/spec/orchestrator.md +2 -2
- package/dist/builtin-personas/spec/requirements/PERSONA.md +1 -1
- package/dist/builtin-views/canvas/core.mjs +82 -1
- package/dist/builtin-views/canvas/tui.mjs +9 -5
- package/dist/builtin-views/canvas/web.jsx +3 -2
- package/dist/builtin-views/chat/core.mjs +725 -0
- package/dist/builtin-views/chat/text.mjs +101 -0
- package/dist/builtin-views/chat/tui.mjs +368 -0
- package/dist/builtin-views/chat/web.jsx +367 -0
- package/dist/builtin-views/prompt-review/core.mjs +863 -0
- package/dist/builtin-views/prompt-review/text.mjs +15 -0
- package/dist/builtin-views/prompt-review/tui.mjs +196 -0
- package/dist/builtin-views/prompt-review/web.jsx +484 -0
- package/dist/builtin-views/settings/core.mjs +397 -0
- package/dist/builtin-views/settings/text.mjs +40 -0
- package/dist/builtin-views/settings/tui.mjs +95 -0
- package/dist/builtin-views/settings/web.jsx +167 -0
- package/dist/cli.js +16 -3
- package/dist/clients/attach/__tests__/autocomplete-and-bash-mode.test.js +96 -0
- package/dist/clients/attach/__tests__/bash-bang-routing.test.js +58 -0
- package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.d.ts +1 -0
- package/dist/clients/attach/__tests__/bundle-pi-tui-dedup.test.js +56 -0
- package/dist/clients/attach/__tests__/titled-editor.test.js +22 -1
- package/dist/clients/attach/attach-cmd.d.ts +18 -0
- package/dist/clients/attach/attach-cmd.js +1723 -795
- package/dist/clients/attach/canvas-panels.js +2 -2
- package/dist/clients/attach/chat-view.d.ts +10 -0
- package/dist/clients/attach/chat-view.js +23 -0
- package/dist/clients/attach/config-load.d.ts +8 -4
- package/dist/clients/attach/config-load.js +2 -0
- package/dist/clients/attach/extension-dialogs.d.ts +2 -3
- package/dist/clients/attach/extension-dialogs.js +7 -9
- package/dist/clients/attach/graph-overlay.js +3 -2
- package/dist/clients/attach/input-controller.d.ts +4 -3
- package/dist/clients/attach/input-controller.js +24 -3
- package/dist/clients/attach/slash-commands.d.ts +7 -7
- package/dist/clients/attach/slash-commands.js +38 -14
- package/dist/clients/attach/titled-editor.js +18 -14
- package/dist/clients/attach/view-socket.d.ts +2 -1
- package/dist/clients/attach/view-socket.js +27 -8
- package/dist/clients/web/__tests__/source-cache.test.d.ts +1 -0
- package/dist/clients/web/__tests__/source-cache.test.js +32 -0
- package/dist/clients/web/dev-server.js +1 -0
- package/dist/clients/web/events.js +9 -11
- package/dist/clients/web/server.d.ts +4 -3
- package/dist/clients/web/server.js +138 -31
- package/dist/clients/web/source-cache.d.ts +10 -0
- package/dist/clients/web/source-cache.js +57 -0
- package/dist/clients/web/web-cmd.js +28 -9
- package/dist/commands/__tests__/human.test.js +30 -28
- package/dist/commands/canvas-history/search.js +1 -1
- package/dist/commands/canvas-history.js +5 -8
- package/dist/commands/canvas-issue.d.ts +2 -0
- package/dist/commands/canvas-issue.js +148 -0
- package/dist/commands/canvas-prune.js +19 -0
- package/dist/commands/canvas-rebuild-index.d.ts +2 -0
- package/dist/commands/canvas-rebuild-index.js +57 -0
- package/dist/commands/canvas-snapshot.d.ts +2 -0
- package/dist/commands/canvas-snapshot.js +48 -0
- package/dist/commands/canvas-tmux-spread.d.ts +2 -0
- package/dist/commands/canvas-tmux-spread.js +188 -0
- package/dist/commands/canvas.d.ts +1 -0
- package/dist/commands/canvas.js +16 -1
- package/dist/commands/chord.js +143 -48
- package/dist/commands/daemon.js +3 -3
- package/dist/commands/human/prompts.d.ts +0 -1
- package/dist/commands/human/prompts.js +39 -54
- package/dist/commands/human/queue.d.ts +12 -1
- package/dist/commands/human/queue.js +468 -73
- package/dist/commands/human/shared.d.ts +3 -4
- package/dist/commands/human/shared.js +3 -3
- package/dist/commands/human.js +11 -10
- package/dist/commands/memory/find.js +3 -2
- package/dist/commands/memory/list.js +4 -3
- package/dist/commands/memory/read.js +3 -3
- package/dist/commands/memory/shared.d.ts +1 -4
- package/dist/commands/memory/shared.js +6 -9
- package/dist/commands/memory/write.js +3 -3
- package/dist/commands/memory.js +4 -7
- package/dist/commands/node-context.d.ts +2 -0
- package/dist/commands/node-context.js +172 -0
- package/dist/commands/node-snapshot.d.ts +2 -0
- package/dist/commands/node-snapshot.js +123 -0
- package/dist/commands/node.js +228 -52
- package/dist/commands/pkg/plugin-inspect.js +6 -6
- package/dist/commands/pkg/plugin-manage.js +1 -1
- package/dist/commands/pkg/plugin.js +2 -2
- package/dist/commands/pkg.js +2 -2
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +79 -11
- package/dist/commands/revive.js +74 -1
- package/dist/commands/search/answer.d.ts +1 -0
- package/dist/commands/search/answer.js +50 -0
- package/dist/commands/search/contents.d.ts +1 -0
- package/dist/commands/search/contents.js +96 -0
- package/dist/commands/search/exa.d.ts +53 -0
- package/dist/commands/search/exa.js +155 -0
- package/dist/commands/search/puremd.d.ts +11 -0
- package/dist/commands/search/puremd.js +53 -0
- package/dist/commands/search/web.d.ts +1 -0
- package/dist/commands/search/web.js +65 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +24 -0
- package/dist/commands/sys/__tests__/sync-import.test.d.ts +1 -0
- package/dist/commands/sys/__tests__/sync-import.test.js +72 -0
- package/dist/commands/sys/config.js +59 -10
- package/dist/commands/sys/doctor.js +56 -4
- package/dist/commands/sys/prompt-review.d.ts +1 -0
- package/dist/commands/sys/prompt-review.js +178 -0
- package/dist/commands/sys/promptstudio.d.ts +2 -0
- package/dist/commands/sys/promptstudio.js +65 -0
- package/dist/commands/sys/settings.d.ts +2 -0
- package/dist/commands/sys/settings.js +16 -0
- package/dist/commands/sys/sync.js +311 -159
- package/dist/commands/sys/sysprompt.d.ts +1 -0
- package/dist/commands/sys/sysprompt.js +86 -0
- package/dist/commands/sys.js +9 -5
- package/dist/commands/view-new.js +2 -2
- package/dist/core/__tests__/artifact-paths.test.d.ts +1 -0
- package/dist/core/__tests__/artifact-paths.test.js +44 -0
- package/dist/core/__tests__/broker-preflight.test.d.ts +1 -0
- package/dist/core/__tests__/broker-preflight.test.js +85 -0
- package/dist/core/__tests__/broker-sdk-wiring.test.js +15 -0
- package/dist/core/__tests__/canvas.test.js +37 -1
- package/dist/core/__tests__/chat-view-reconnect.test.d.ts +1 -0
- package/dist/core/__tests__/chat-view-reconnect.test.js +70 -0
- package/dist/core/__tests__/child-death-wake.test.js +11 -2
- package/dist/core/__tests__/close.test.js +39 -1
- package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +1 -0
- package/dist/core/__tests__/connection-reconnect-resume.test.js +37 -0
- package/dist/core/__tests__/context-intro.test.js +48 -14
- package/dist/core/__tests__/daemon-boot.test.js +182 -9
- package/dist/core/__tests__/editor-label.test.d.ts +1 -0
- package/dist/core/__tests__/editor-label.test.js +26 -0
- package/dist/core/__tests__/fault-marker.test.d.ts +1 -0
- package/dist/core/__tests__/fault-marker.test.js +112 -0
- package/dist/core/__tests__/fault-retry-rewind.test.d.ts +1 -0
- package/dist/core/__tests__/fault-retry-rewind.test.js +123 -0
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +30 -4
- package/dist/core/__tests__/fixtures/fake-engine.js +156 -17
- package/dist/core/__tests__/fixtures/fake-pi-host.js +10 -1
- package/dist/core/__tests__/full/human-new-window-regression.test.js +1 -1
- package/dist/core/__tests__/helpers/harness.d.ts +1 -1
- package/dist/core/__tests__/helpers/harness.js +2 -2
- package/dist/core/__tests__/human-stranded-deliver.test.js +38 -1
- package/dist/core/__tests__/human-surface-target.test.js +1 -1
- package/dist/core/__tests__/kickoff.test.js +18 -19
- package/dist/core/__tests__/memory-resolver.test.js +12 -1
- package/dist/core/__tests__/migration.test.js +49 -14
- package/dist/core/__tests__/model-ladders.test.d.ts +1 -0
- package/dist/core/__tests__/model-ladders.test.js +160 -0
- package/dist/core/__tests__/node-env.test.d.ts +1 -0
- package/dist/core/__tests__/node-env.test.js +26 -0
- package/dist/core/__tests__/push-final-guard.test.js +7 -1
- package/dist/core/__tests__/relaunch-root.test.js +9 -9
- package/dist/core/__tests__/revive.test.js +36 -6
- package/dist/core/__tests__/spawn-root.test.js +24 -1
- package/dist/core/__tests__/stop-guard.test.js +10 -0
- package/dist/core/__tests__/stranded-relaunch.test.d.ts +1 -0
- package/dist/core/__tests__/stranded-relaunch.test.js +72 -0
- package/dist/core/__tests__/tmux-surface.test.js +5 -1
- package/dist/core/__tests__/unknown-path.test.js +19 -19
- package/dist/core/__tests__/yield-ensures-daemon.test.d.ts +1 -0
- package/dist/core/__tests__/yield-ensures-daemon.test.js +54 -0
- package/dist/core/artifact.d.ts +2 -33
- package/dist/core/artifact.js +27 -87
- package/dist/core/bootstrap.js +2 -0
- package/dist/core/canvas/browse/__tests__/model.test.js +35 -1
- package/dist/core/canvas/browse/app.js +87 -17
- package/dist/core/canvas/browse/model.d.ts +10 -1
- package/dist/core/canvas/browse/model.js +37 -1
- package/dist/core/canvas/browse/render.js +35 -33
- package/dist/core/canvas/canvas.d.ts +7 -0
- package/dist/core/canvas/canvas.js +54 -5
- package/dist/core/canvas/db.js +35 -8
- package/dist/core/canvas/labels.d.ts +6 -8
- package/dist/core/canvas/labels.js +8 -11
- package/dist/core/canvas/nav-model.d.ts +5 -4
- package/dist/core/canvas/nav-model.js +31 -17
- package/dist/core/canvas/paths.d.ts +18 -1
- package/dist/core/canvas/paths.js +31 -2
- package/dist/core/canvas/render.d.ts +25 -0
- package/dist/core/canvas/render.js +96 -13
- package/dist/core/canvas/status-glyph.d.ts +35 -0
- package/dist/core/canvas/status-glyph.js +104 -0
- package/dist/core/canvas/types.d.ts +2 -2
- package/dist/core/config.js +114 -16
- package/dist/core/fault-classifier.d.ts +41 -0
- package/dist/core/fault-classifier.js +110 -0
- package/dist/core/feed/inbox.d.ts +15 -0
- package/dist/core/feed/inbox.js +40 -14
- package/dist/core/frontmatter.d.ts +1 -11
- package/dist/core/frontmatter.js +1 -55
- package/dist/core/hearth/config.d.ts +2 -0
- package/dist/core/hearth/config.js +75 -0
- package/dist/core/hearth/guest-env.d.ts +2 -0
- package/dist/core/hearth/guest-env.js +11 -0
- package/dist/core/hearth/index.d.ts +5 -0
- package/dist/core/hearth/index.js +5 -0
- package/dist/core/hearth/provider.d.ts +21 -0
- package/dist/core/hearth/provider.js +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.d.ts +10 -0
- package/dist/core/hearth/providers/blaxel-bootstrap.js +39 -0
- package/dist/core/hearth/providers/blaxel-home.d.ts +11 -0
- package/dist/core/hearth/providers/blaxel-home.js +126 -0
- package/dist/core/hearth/providers/blaxel.d.ts +26 -0
- package/dist/core/hearth/providers/blaxel.js +208 -0
- package/dist/core/hearth/providers/types.d.ts +72 -0
- package/dist/core/hearth/providers/types.js +1 -0
- package/dist/core/hearth/registry.d.ts +15 -0
- package/dist/core/hearth/registry.js +179 -0
- package/dist/core/hearth/types.d.ts +121 -0
- package/dist/core/hearth/types.js +1 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +1 -0
- package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +79 -0
- package/dist/core/{skill-sync → host-exports}/builtins.d.ts +21 -13
- package/dist/core/host-exports/builtins.js +68 -0
- package/dist/core/{skill-sync → host-exports}/export.d.ts +9 -6
- package/dist/core/{skill-sync → host-exports}/export.js +37 -14
- package/dist/core/io.js +1 -1
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +113 -0
- package/dist/core/memory-resolver.d.ts +8 -8
- package/dist/core/memory-resolver.js +15 -28
- package/dist/core/personas/index.d.ts +4 -4
- package/dist/core/personas/index.js +2 -2
- package/dist/core/personas/loader.d.ts +51 -0
- package/dist/core/personas/loader.js +54 -50
- package/dist/core/personas/resolve.d.ts +43 -27
- package/dist/core/personas/resolve.js +336 -94
- package/dist/core/runtime/auth-reload.d.ts +7 -0
- package/dist/core/runtime/auth-reload.js +69 -0
- package/dist/core/runtime/bearings.d.ts +35 -15
- package/dist/core/runtime/bearings.js +305 -44
- package/dist/core/runtime/branded-host.d.ts +4 -2
- package/dist/core/runtime/branded-host.js +66 -4
- package/dist/core/runtime/broker-protocol.d.ts +38 -3
- package/dist/core/runtime/broker.js +343 -18
- package/dist/core/runtime/close.js +37 -29
- package/dist/core/runtime/connectivity.d.ts +12 -0
- package/dist/core/runtime/connectivity.js +73 -0
- package/dist/core/runtime/fault-recovery-nudge.d.ts +2 -0
- package/dist/core/runtime/fault-recovery-nudge.js +2 -0
- package/dist/core/runtime/fault-recovery.d.ts +23 -0
- package/dist/core/runtime/fault-recovery.js +74 -0
- package/dist/core/runtime/fault.d.ts +25 -0
- package/dist/core/runtime/fault.js +176 -0
- package/dist/core/runtime/front-door.d.ts +1 -1
- package/dist/core/runtime/front-door.js +2 -2
- package/dist/core/runtime/host.d.ts +10 -0
- package/dist/core/runtime/host.js +71 -9
- package/dist/core/runtime/kickoff.js +31 -45
- package/dist/core/runtime/launch.d.ts +32 -18
- package/dist/core/runtime/launch.js +148 -43
- package/dist/core/runtime/model-swap.d.ts +18 -0
- package/dist/core/runtime/model-swap.js +95 -0
- package/dist/core/runtime/naming.d.ts +7 -0
- package/dist/core/runtime/naming.js +61 -9
- package/dist/core/runtime/nodes.js +6 -1
- package/dist/core/runtime/persona.js +16 -17
- package/dist/core/runtime/placement.d.ts +36 -31
- package/dist/core/runtime/placement.js +149 -64
- package/dist/core/runtime/promote.d.ts +2 -0
- package/dist/core/runtime/promote.js +23 -3
- package/dist/core/runtime/recycle.js +6 -4
- package/dist/core/runtime/reset.d.ts +5 -2
- package/dist/core/runtime/reset.js +2 -2
- package/dist/core/runtime/revive.d.ts +4 -0
- package/dist/core/runtime/revive.js +27 -9
- package/dist/core/runtime/roadmap.d.ts +1 -1
- package/dist/core/runtime/roadmap.js +1 -1
- package/dist/core/runtime/spawn.d.ts +2 -2
- package/dist/core/runtime/spawn.js +30 -6
- 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 +29 -6
- package/dist/core/runtime/tmux.js +134 -80
- package/dist/core/runtime/view-socket-client.d.ts +46 -0
- package/dist/core/runtime/view-socket-client.js +203 -0
- package/dist/core/substrate/index.d.ts +1 -1
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/on-read.js +16 -4
- package/dist/core/substrate/render.d.ts +10 -13
- package/dist/core/substrate/render.js +56 -51
- package/dist/core/tui/host.js +83 -18
- package/dist/core/view/__tests__/transport-cache.test.d.ts +1 -0
- package/dist/core/view/__tests__/transport-cache.test.js +62 -0
- package/dist/core/view/contract.d.ts +20 -0
- package/dist/core/view/stream-local.d.ts +3 -0
- package/dist/core/view/stream-local.js +184 -0
- package/dist/core/view/transport-cache.d.ts +8 -0
- package/dist/core/view/transport-cache.js +38 -0
- package/dist/core/view/transport-local.js +4 -0
- package/dist/core/view/transport.d.ts +7 -1
- package/dist/daemon/crtrd.d.ts +3 -19
- package/dist/daemon/crtrd.js +139 -178
- package/dist/daemon/manage.d.ts +18 -1
- package/dist/daemon/manage.js +54 -9
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/anthropic-oauth.test.js +289 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/config-timeout.test.js +34 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/guest-source.test.js +203 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hardening.test.js +59 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/hearth-status-route.test.js +372 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/http-running-route-fast-path.test.js +258 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/model-auth-routes.test.js +437 -0
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/source-bridge-gate.test.js +455 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/static-asset-allowlist.test.js +15 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/warm-path-concurrency.test.js +141 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.d.ts +1 -0
- package/dist/hearth/wake-proxy/__tests__/ws-teardown-accounting.test.js +143 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.d.ts +58 -0
- package/dist/hearth/wake-proxy/anthropic-oauth.js +189 -0
- package/dist/hearth/wake-proxy/auth.d.ts +22 -0
- package/dist/hearth/wake-proxy/auth.js +128 -0
- package/dist/hearth/wake-proxy/config.d.ts +2 -0
- package/dist/hearth/wake-proxy/config.js +151 -0
- package/dist/hearth/wake-proxy/guest-source.d.ts +14 -0
- package/dist/hearth/wake-proxy/guest-source.js +130 -0
- package/dist/hearth/wake-proxy/hearth-status.d.ts +44 -0
- package/dist/hearth/wake-proxy/hearth-status.js +267 -0
- package/dist/hearth/wake-proxy/home.d.ts +67 -0
- package/dist/hearth/wake-proxy/home.js +297 -0
- package/dist/hearth/wake-proxy/main.d.ts +1 -0
- package/dist/hearth/wake-proxy/main.js +134 -0
- package/dist/hearth/wake-proxy/model-auth.d.ts +13 -0
- package/dist/hearth/wake-proxy/model-auth.js +345 -0
- package/dist/hearth/wake-proxy/proxy.d.ts +35 -0
- package/dist/hearth/wake-proxy/proxy.js +716 -0
- package/dist/hearth/wake-proxy/public-source-gate.d.ts +9 -0
- package/dist/hearth/wake-proxy/public-source-gate.js +409 -0
- package/dist/hearth/wake-proxy/redact.d.ts +1 -0
- package/dist/hearth/wake-proxy/redact.js +17 -0
- package/dist/hearth/wake-proxy/server.d.ts +11 -0
- package/dist/hearth/wake-proxy/server.js +142 -0
- package/dist/hearth/wake-proxy/state.d.ts +18 -0
- package/dist/hearth/wake-proxy/state.js +342 -0
- package/dist/hearth/wake-proxy/types.d.ts +76 -0
- package/dist/hearth/wake-proxy/types.js +1 -0
- package/dist/index.js +3 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +6 -5
- package/dist/pi-extensions/canvas-context-intro.js +7 -7
- package/dist/pi-extensions/canvas-doc-substrate.js +19 -22
- package/dist/pi-extensions/canvas-nav.js +15 -8
- package/dist/pi-extensions/canvas-stophook.d.ts +1 -1
- package/dist/pi-extensions/canvas-stophook.js +78 -11
- package/dist/types.d.ts +39 -20
- package/dist/types.js +38 -13
- package/dist/web/runtime.js +141 -42
- package/dist/web/transport-http.js +7 -5
- package/dist/web/transport-stream.d.ts +3 -0
- package/dist/web/transport-stream.js +204 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
- package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
- package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
- package/dist/web-client/assets/fraunces-latin-ext-wght-normal-Ca2vKHc0.woff2 +0 -0
- package/dist/web-client/assets/fraunces-latin-wght-normal-ukD16Tqj.woff2 +0 -0
- package/dist/web-client/assets/fraunces-vietnamese-wght-normal-CnvboYUG.woff2 +0 -0
- package/dist/web-client/assets/index-BZUxTkv5.css +2 -0
- package/dist/web-client/assets/index-D36PNBj4.js +80 -0
- package/dist/web-client/assets/instrument-sans-latin-ext-wght-normal-B5bTHO_g.woff2 +0 -0
- package/dist/web-client/assets/instrument-sans-latin-wght-normal-BbzFLZTg.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
- package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
- package/dist/web-client/index.html +3 -2
- package/package.json +32 -10
- package/dist/core/__tests__/error-stall-recycle.test.js +0 -141
- package/dist/core/skill-sync/__tests__/dry-run-wrote-count.test.js +0 -57
- package/dist/core/skill-sync/builtins.js +0 -112
- package/dist/core/skill-sync/claude-plugins.d.ts +0 -23
- package/dist/core/skill-sync/claude-plugins.js +0 -71
- package/dist/core/skill-sync/engine.d.ts +0 -42
- package/dist/core/skill-sync/engine.js +0 -633
- package/dist/core/skill-sync/manifest.d.ts +0 -64
- package/dist/core/skill-sync/manifest.js +0 -181
- package/dist/core/skill-sync/profile.d.ts +0 -76
- package/dist/core/skill-sync/profile.js +0 -173
- package/dist/core/skill-sync/snapshot.d.ts +0 -57
- package/dist/core/skill-sync/snapshot.js +0 -120
- package/dist/pi-extensions/canvas-commands.d.ts +0 -37
- package/dist/pi-extensions/canvas-commands.js +0 -113
- package/dist/pi-extensions/canvas-resume.d.ts +0 -21
- package/dist/pi-extensions/canvas-resume.js +0 -83
- package/dist/pi-extensions/canvas-view.d.ts +0 -21
- package/dist/pi-extensions/canvas-view.js +0 -76
- package/dist/web-client/assets/index-BUvQb4hR.css +0 -2
- package/dist/web-client/assets/index-ClLQXYAE.js +0 -10
- /package/dist/{core/__tests__/error-stall-recycle.test.d.ts → clients/attach/__tests__/autocomplete-and-bash-mode.test.d.ts} +0 -0
- /package/dist/{core/skill-sync/__tests__/dry-run-wrote-count.test.d.ts → clients/attach/__tests__/bash-bang-routing.test.d.ts} +0 -0
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layered manifest for bidirectional skill sync (Phase P2).
|
|
3
|
-
*
|
|
4
|
-
* Reads the user-scope (`~/.crouter/skill-sync.json`) and project-scope
|
|
5
|
-
* (`<proj>/.crouter/skill-sync.json`) manifests, validates each against the
|
|
6
|
-
* R-I1 schema with a hand-rolled strict validator (no new dependency), and
|
|
7
|
-
* merges them into a single additive, project-wins pair list.
|
|
8
|
-
*
|
|
9
|
-
* Contract (R-U7/R-U8/R-U9/R-I1/R-X4):
|
|
10
|
-
* - Missing file → empty layer (absence ≠ malformation).
|
|
11
|
-
* - Invalid JSON → HARD error naming the file (no lenient fallback).
|
|
12
|
-
* - Schema-invalid pair → HARD error naming the file (no skip-and-proceed).
|
|
13
|
-
* - Merge → additive union keyed on `id`; a project pair with an
|
|
14
|
-
* `id` present in the user layer REPLACES it entirely.
|
|
15
|
-
*
|
|
16
|
-
* The shared data shapes (`Pair`, `Endpoint`, `TranslationOverride`) are
|
|
17
|
-
* exported from here; P5/P6/P7 import them.
|
|
18
|
-
*/
|
|
19
|
-
/** The scope a sync endpoint resolves within. `plugin` is crtr-side only (a
|
|
20
|
-
* skill bundle inside a crtr-registry plugin); `claude-plugin` is Claude-side
|
|
21
|
-
* only (a skill IN PLACE inside its Claude plugin's install path). */
|
|
22
|
-
export type EndpointScope = 'user' | 'project' | 'plugin' | 'claude-plugin';
|
|
23
|
-
/** One side of a sync pair — a single SKILL.md bundle on either the crtr or the
|
|
24
|
-
* Claude side. Resolves to `<dir>/<name>/SKILL.md` (R-I2, resolved by P6).
|
|
25
|
-
* `plugin` is required iff `scope` is `plugin` (crtr) or `claude-plugin`
|
|
26
|
-
* (Claude) — it names the owning plugin (a `claude-plugin` value is the
|
|
27
|
-
* marketplace-qualified key `<plugin>@<marketplace>`). */
|
|
28
|
-
export interface Endpoint {
|
|
29
|
-
scope: EndpointScope;
|
|
30
|
-
name: string;
|
|
31
|
-
plugin?: string;
|
|
32
|
-
}
|
|
33
|
-
/** Per-pair override of the default translation profile — shallow-merged over
|
|
34
|
-
* it by P5's `resolveProfile` (R-I5). The JSON-overridable surface is the
|
|
35
|
-
* owned-field lists; P5 performs the profile-shape validation and rejects
|
|
36
|
-
* unknown keys, so this manifest validator only checks it is an object. */
|
|
37
|
-
export interface TranslationOverride {
|
|
38
|
-
crtrOwned?: string[];
|
|
39
|
-
claudeOwned?: string[];
|
|
40
|
-
}
|
|
41
|
-
/** A single bidirectional sync pair (R-I1). `kind` is optional and, if present,
|
|
42
|
-
* MUST be `"skill"` — the other endpoint kinds (`command`/`agent`/`plugin`) are
|
|
43
|
-
* built-in export artifacts (skill-sync/builtins.ts), not manifest-enrollable. */
|
|
44
|
-
export interface Pair {
|
|
45
|
-
id: string;
|
|
46
|
-
kind?: 'skill';
|
|
47
|
-
crtr: Endpoint;
|
|
48
|
-
claude: Endpoint;
|
|
49
|
-
frontmatter?: TranslationOverride;
|
|
50
|
-
}
|
|
51
|
-
/** The merged, validated manifest. */
|
|
52
|
-
export interface LayeredManifest {
|
|
53
|
-
pairs: Pair[];
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Read, validate, and merge the user- and project-layer manifests.
|
|
57
|
-
*
|
|
58
|
-
* @param cwd starting directory for project-scope resolution (defaults to the
|
|
59
|
-
* process cwd via `findProjectScopeRoot`).
|
|
60
|
-
* @returns `{ pairs }` — the additive, project-wins union (R-U8).
|
|
61
|
-
* @throws `usage()` error naming the offending file on malformed JSON or a
|
|
62
|
-
* schema-invalid layer (R-U7/R-X4). NEVER swallows a bad layer.
|
|
63
|
-
*/
|
|
64
|
-
export declare function readLayeredManifest(cwd?: string): LayeredManifest;
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layered manifest for bidirectional skill sync (Phase P2).
|
|
3
|
-
*
|
|
4
|
-
* Reads the user-scope (`~/.crouter/skill-sync.json`) and project-scope
|
|
5
|
-
* (`<proj>/.crouter/skill-sync.json`) manifests, validates each against the
|
|
6
|
-
* R-I1 schema with a hand-rolled strict validator (no new dependency), and
|
|
7
|
-
* merges them into a single additive, project-wins pair list.
|
|
8
|
-
*
|
|
9
|
-
* Contract (R-U7/R-U8/R-U9/R-I1/R-X4):
|
|
10
|
-
* - Missing file → empty layer (absence ≠ malformation).
|
|
11
|
-
* - Invalid JSON → HARD error naming the file (no lenient fallback).
|
|
12
|
-
* - Schema-invalid pair → HARD error naming the file (no skip-and-proceed).
|
|
13
|
-
* - Merge → additive union keyed on `id`; a project pair with an
|
|
14
|
-
* `id` present in the user layer REPLACES it entirely.
|
|
15
|
-
*
|
|
16
|
-
* The shared data shapes (`Pair`, `Endpoint`, `TranslationOverride`) are
|
|
17
|
-
* exported from here; P5/P6/P7 import them.
|
|
18
|
-
*/
|
|
19
|
-
import { readFileSync } from 'node:fs';
|
|
20
|
-
import { join } from 'node:path';
|
|
21
|
-
import { userScopeRoot, findProjectScopeRoot } from '../scope.js';
|
|
22
|
-
import { usage } from '../errors.js';
|
|
23
|
-
// ── Public API ───────────────────────────────────────────────────────────────
|
|
24
|
-
const MANIFEST_FILE = 'skill-sync.json';
|
|
25
|
-
const VALID_SCOPES = ['user', 'project', 'plugin', 'claude-plugin'];
|
|
26
|
-
/**
|
|
27
|
-
* Read, validate, and merge the user- and project-layer manifests.
|
|
28
|
-
*
|
|
29
|
-
* @param cwd starting directory for project-scope resolution (defaults to the
|
|
30
|
-
* process cwd via `findProjectScopeRoot`).
|
|
31
|
-
* @returns `{ pairs }` — the additive, project-wins union (R-U8).
|
|
32
|
-
* @throws `usage()` error naming the offending file on malformed JSON or a
|
|
33
|
-
* schema-invalid layer (R-U7/R-X4). NEVER swallows a bad layer.
|
|
34
|
-
*/
|
|
35
|
-
export function readLayeredManifest(cwd) {
|
|
36
|
-
const userPath = join(userScopeRoot(), MANIFEST_FILE);
|
|
37
|
-
const userPairs = readLayer(userPath);
|
|
38
|
-
const projectRoot = findProjectScopeRoot(cwd);
|
|
39
|
-
const projectPairs = projectRoot
|
|
40
|
-
? readLayer(join(projectRoot, MANIFEST_FILE))
|
|
41
|
-
: [];
|
|
42
|
-
// Additive union keyed on `id`; project replaces user on collision (R-U8).
|
|
43
|
-
// A Map preserves first-insertion order, so a project pair overwriting a user
|
|
44
|
-
// pair keeps the user pair's position while replacing its value.
|
|
45
|
-
const merged = new Map();
|
|
46
|
-
for (const p of userPairs)
|
|
47
|
-
merged.set(p.id, p);
|
|
48
|
-
for (const p of projectPairs)
|
|
49
|
-
merged.set(p.id, p);
|
|
50
|
-
return { pairs: [...merged.values()] };
|
|
51
|
-
}
|
|
52
|
-
// ── Layer read + strict validation ──────────────────────────────────────────
|
|
53
|
-
/** Read one layer file. Missing → empty (R-U7). Present-but-malformed → throw
|
|
54
|
-
* naming the file (R-U7/R-X4). Returns the validated pairs of that one layer. */
|
|
55
|
-
function readLayer(path) {
|
|
56
|
-
let raw;
|
|
57
|
-
try {
|
|
58
|
-
raw = readFileSync(path, 'utf8');
|
|
59
|
-
}
|
|
60
|
-
catch (err) {
|
|
61
|
-
if (err.code === 'ENOENT')
|
|
62
|
-
return []; // empty layer
|
|
63
|
-
throw usage(`failed to read ${path}: ${err.message}`);
|
|
64
|
-
}
|
|
65
|
-
let parsed;
|
|
66
|
-
try {
|
|
67
|
-
parsed = JSON.parse(raw);
|
|
68
|
-
}
|
|
69
|
-
catch (err) {
|
|
70
|
-
throw usage(`invalid JSON in ${path}: ${err.message}`);
|
|
71
|
-
}
|
|
72
|
-
return validateLayer(parsed, path);
|
|
73
|
-
}
|
|
74
|
-
/** Validate one parsed layer against the R-I1 schema. Throws naming `path` on
|
|
75
|
-
* any violation. Duplicate-`id` detection is scoped to THIS layer only —
|
|
76
|
-
* cross-layer `id` collisions are the legal project-wins merge, not an error. */
|
|
77
|
-
function validateLayer(doc, path) {
|
|
78
|
-
if (!isObject(doc)) {
|
|
79
|
-
throw usage(`malformed manifest ${path}: top level must be a JSON object`);
|
|
80
|
-
}
|
|
81
|
-
// Strict-require `version: 2` — any existing manifest MUST declare it (no
|
|
82
|
-
// advisory hedge). v1 has zero on-disk instances, so this is a clean forward
|
|
83
|
-
// marker, not a migration.
|
|
84
|
-
const version = doc.version;
|
|
85
|
-
if (version !== 2) {
|
|
86
|
-
throw usage(`malformed manifest ${path}: "version" must be 2 (got ${JSON.stringify(version)})`);
|
|
87
|
-
}
|
|
88
|
-
// `pairs` absent → empty manifest; present → must be an array.
|
|
89
|
-
const rawPairs = doc.pairs;
|
|
90
|
-
if (rawPairs === undefined)
|
|
91
|
-
return [];
|
|
92
|
-
if (!Array.isArray(rawPairs)) {
|
|
93
|
-
throw usage(`malformed manifest ${path}: "pairs" must be an array`);
|
|
94
|
-
}
|
|
95
|
-
const seen = new Set();
|
|
96
|
-
const pairs = [];
|
|
97
|
-
rawPairs.forEach((entry, i) => {
|
|
98
|
-
const pair = validatePair(entry, i, path);
|
|
99
|
-
if (seen.has(pair.id)) {
|
|
100
|
-
throw usage(`malformed manifest ${path}: duplicate pair id "${pair.id}" within one layer`);
|
|
101
|
-
}
|
|
102
|
-
seen.add(pair.id);
|
|
103
|
-
pairs.push(pair);
|
|
104
|
-
});
|
|
105
|
-
return pairs;
|
|
106
|
-
}
|
|
107
|
-
function validatePair(entry, i, path) {
|
|
108
|
-
const where = `pairs[${i}]`;
|
|
109
|
-
if (!isObject(entry)) {
|
|
110
|
-
throw usage(`malformed manifest ${path}: ${where} must be an object`);
|
|
111
|
-
}
|
|
112
|
-
const e = entry;
|
|
113
|
-
if (typeof e.id !== 'string' || e.id.length === 0) {
|
|
114
|
-
throw usage(`malformed manifest ${path}: ${where} is missing a non-empty string "id"`);
|
|
115
|
-
}
|
|
116
|
-
const id = e.id;
|
|
117
|
-
// `kind` is optional; absent → "skill". Any value other than "skill" is a hard
|
|
118
|
-
// error — export kinds (command/agent/plugin) are built-in, not enrollable.
|
|
119
|
-
let kind;
|
|
120
|
-
if (e.kind !== undefined) {
|
|
121
|
-
if (e.kind !== 'skill') {
|
|
122
|
-
throw usage(`malformed manifest ${path}: ${where}.kind must be "skill" or absent — ` +
|
|
123
|
-
`kinds like "command"/"agent"/"plugin" are built-in export artifacts, ` +
|
|
124
|
-
`not manifest-enrollable`);
|
|
125
|
-
}
|
|
126
|
-
kind = e.kind;
|
|
127
|
-
}
|
|
128
|
-
const crtr = validateEndpoint(e.crtr, `${where}.crtr`, path, 'crtr');
|
|
129
|
-
const claude = validateEndpoint(e.claude, `${where}.claude`, path, 'claude');
|
|
130
|
-
let frontmatter;
|
|
131
|
-
if (e.frontmatter !== undefined) {
|
|
132
|
-
if (!isObject(e.frontmatter)) {
|
|
133
|
-
throw usage(`malformed manifest ${path}: ${where}.frontmatter must be an object`);
|
|
134
|
-
}
|
|
135
|
-
frontmatter = e.frontmatter;
|
|
136
|
-
}
|
|
137
|
-
return { id, kind, crtr, claude, frontmatter };
|
|
138
|
-
}
|
|
139
|
-
function validateEndpoint(value, where, path, side) {
|
|
140
|
-
if (!isObject(value)) {
|
|
141
|
-
throw usage(`malformed manifest ${path}: ${where} is missing or not an object`);
|
|
142
|
-
}
|
|
143
|
-
const ep = value;
|
|
144
|
-
if (!isScope(ep.scope)) {
|
|
145
|
-
throw usage(`malformed manifest ${path}: ${where}.scope must be one of ${VALID_SCOPES.join('|')}`);
|
|
146
|
-
}
|
|
147
|
-
const scope = ep.scope;
|
|
148
|
-
// Scopes are side-specific: `plugin` resolves a crtr-registry plugin, so it is
|
|
149
|
-
// only legal on the crtr side; `claude-plugin` resolves a Claude install path,
|
|
150
|
-
// so it is only legal on the Claude side (R-I1).
|
|
151
|
-
if (scope === 'plugin' && side !== 'crtr') {
|
|
152
|
-
throw usage(`malformed manifest ${path}: ${where}.scope "plugin" is only valid for a crtr ` +
|
|
153
|
-
`endpoint — for a Claude plugin skill use "claude-plugin"`);
|
|
154
|
-
}
|
|
155
|
-
if (scope === 'claude-plugin' && side !== 'claude') {
|
|
156
|
-
throw usage(`malformed manifest ${path}: ${where}.scope "claude-plugin" is only valid for a ` +
|
|
157
|
-
`Claude endpoint`);
|
|
158
|
-
}
|
|
159
|
-
if (typeof ep.name !== 'string' || ep.name.length === 0) {
|
|
160
|
-
throw usage(`malformed manifest ${path}: ${where}.name must be a non-empty string`);
|
|
161
|
-
}
|
|
162
|
-
const name = ep.name;
|
|
163
|
-
// `plugin` required iff scope names a plugin (`plugin` or `claude-plugin`).
|
|
164
|
-
if (scope === 'plugin' || scope === 'claude-plugin') {
|
|
165
|
-
if (typeof ep.plugin !== 'string' || ep.plugin.length === 0) {
|
|
166
|
-
throw usage(`malformed manifest ${path}: ${where} has scope "${scope}" but no non-empty "plugin" name`);
|
|
167
|
-
}
|
|
168
|
-
return { scope, name, plugin: ep.plugin };
|
|
169
|
-
}
|
|
170
|
-
if (ep.plugin !== undefined) {
|
|
171
|
-
throw usage(`malformed manifest ${path}: ${where}.plugin is only valid when scope is "plugin" or "claude-plugin"`);
|
|
172
|
-
}
|
|
173
|
-
return { scope, name };
|
|
174
|
-
}
|
|
175
|
-
// ── Type guards ──────────────────────────────────────────────────────────────
|
|
176
|
-
function isObject(v) {
|
|
177
|
-
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
178
|
-
}
|
|
179
|
-
function isScope(v) {
|
|
180
|
-
return typeof v === 'string' && VALID_SCOPES.includes(v);
|
|
181
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Translation profile (Phase P5) — the single home of the crtr ↔ Claude
|
|
3
|
-
* frontmatter asymmetry.
|
|
4
|
-
*
|
|
5
|
-
* Body and asset reconciliation is symmetric (a plain 3-way merge); frontmatter
|
|
6
|
-
* is the ONLY asymmetric axis, and all of that asymmetry lives here (R-U3). The
|
|
7
|
-
* forward translator (`claudeToCrtr.description`) reshapes a Claude `description`
|
|
8
|
-
* into a crtr read-routing `when-and-why-to-read` sentence + `short-form`; the
|
|
9
|
-
* reverse translator (`crtrToClaude.description`) inverts that back into the
|
|
10
|
-
* canonical Claude form `"<gist>. Use when <situation>."` so a round-trip is
|
|
11
|
-
* byte-stable (R-S4 / OD-1).
|
|
12
|
-
*
|
|
13
|
-
* The forward string functions (`reshapeWhenAndWhy`, `cleanClause`,
|
|
14
|
-
* `toShortForm`, `yamlQuote`, `USE_WHEN_RE`) preserve the legacy reshape
|
|
15
|
-
* behavior exactly — `reshapeWhenAndWhy` output is byte-identical to that
|
|
16
|
-
* legacy for the same input (R-I4). Frontmatter writing and generator stamping
|
|
17
|
-
* are intentionally NOT part of this module — the reconcile engine owns all
|
|
18
|
-
* writes.
|
|
19
|
-
*/
|
|
20
|
-
import type { TranslationOverride } from './manifest.js';
|
|
21
|
-
/** Reshape a Claude `description` into a read-routing sentence per
|
|
22
|
-
* taste/why-field-means-why-to-read: "When <situation>, this skill should be
|
|
23
|
-
* read [because <gist>]." Claude descriptions are typically
|
|
24
|
-
* "<gist>. Use when <situation>." — we lift the use-when clause into the
|
|
25
|
-
* situation and the preceding text into the payoff. With no explicit clause,
|
|
26
|
-
* the whole description becomes the situation. */
|
|
27
|
-
export declare function reshapeWhenAndWhy(description: string): string;
|
|
28
|
-
/** One-line short-form for the human inventory (never loaded into agent
|
|
29
|
-
* context): the source description collapsed to a single line. */
|
|
30
|
-
export declare function toShortForm(description: string): string;
|
|
31
|
-
/** Always-double-quote a YAML scalar, escaping backslashes and quotes. The two
|
|
32
|
-
* generated fields are single-line, so this is safe regardless of colons,
|
|
33
|
-
* commas, or other YAML-significant characters. */
|
|
34
|
-
export declare function yamlQuote(s: string): string;
|
|
35
|
-
/**
|
|
36
|
-
* Reverse translator: a crtr `when-and-why-to-read` (plus optional `short-form`
|
|
37
|
-
* for the gist when the sentence carries none) → the canonical Claude
|
|
38
|
-
* `description` form `"<gist>. Use when <situation>."`.
|
|
39
|
-
*
|
|
40
|
-
* This is the exact inverse of `reshapeWhenAndWhy`, normalized so that
|
|
41
|
-
* `crtrToClaudeDescription(reshapeWhenAndWhy(d), toShortForm(d))` is a fixed
|
|
42
|
-
* point under re-application — a round-trip is byte-stable (R-S4 / OD-1). The
|
|
43
|
-
* `gist` is recovered from the sentence's `because` clause; only when that is
|
|
44
|
-
* absent do we fall back to the (use-when-stripped) `short-form`, so a
|
|
45
|
-
* `short-form` that is itself a "Use when …" line contributes nothing and
|
|
46
|
-
* cannot ping-pong.
|
|
47
|
-
*/
|
|
48
|
-
export declare function crtrToClaudeDescription(whenAndWhy: string, shortForm?: string): string;
|
|
49
|
-
/** The crtr ↔ Claude frontmatter contract: the two translatable `description`
|
|
50
|
-
* axes plus the owned-field lists that are NEVER crossed to the other side
|
|
51
|
-
* (R-X2). Shape per R-I4. */
|
|
52
|
-
export interface TranslationProfile {
|
|
53
|
-
claudeToCrtr: {
|
|
54
|
-
description: (d: string) => {
|
|
55
|
-
whenAndWhy: string;
|
|
56
|
-
shortForm: string;
|
|
57
|
-
};
|
|
58
|
-
};
|
|
59
|
-
crtrToClaude: {
|
|
60
|
-
description: (whenAndWhy: string, shortForm?: string) => string;
|
|
61
|
-
};
|
|
62
|
-
crtrOwned: readonly string[];
|
|
63
|
-
claudeOwned: readonly string[];
|
|
64
|
-
}
|
|
65
|
-
/** The default profile (R-I4). The forward translator reuses
|
|
66
|
-
* `reshapeWhenAndWhy`/`toShortForm` verbatim; the reverse uses
|
|
67
|
-
* `crtrToClaudeDescription`. Owned-field lists match the field taxonomy. */
|
|
68
|
-
export declare const DEFAULT_PROFILE: TranslationProfile;
|
|
69
|
-
/**
|
|
70
|
-
* Resolve a pair's effective profile: a SHALLOW merge of its `frontmatter`
|
|
71
|
-
* override over `DEFAULT_PROFILE`, validated against the profile shape (R-I5).
|
|
72
|
-
* An unknown key or a non-string-array value is a malformed-manifest error
|
|
73
|
-
* (R-U7). NEVER mutates `DEFAULT_PROFILE` — the override path returns a fresh
|
|
74
|
-
* object; the no-override path returns the (unmodified) constant.
|
|
75
|
-
*/
|
|
76
|
-
export declare function resolveProfile(override?: TranslationOverride): TranslationProfile;
|
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Translation profile (Phase P5) — the single home of the crtr ↔ Claude
|
|
3
|
-
* frontmatter asymmetry.
|
|
4
|
-
*
|
|
5
|
-
* Body and asset reconciliation is symmetric (a plain 3-way merge); frontmatter
|
|
6
|
-
* is the ONLY asymmetric axis, and all of that asymmetry lives here (R-U3). The
|
|
7
|
-
* forward translator (`claudeToCrtr.description`) reshapes a Claude `description`
|
|
8
|
-
* into a crtr read-routing `when-and-why-to-read` sentence + `short-form`; the
|
|
9
|
-
* reverse translator (`crtrToClaude.description`) inverts that back into the
|
|
10
|
-
* canonical Claude form `"<gist>. Use when <situation>."` so a round-trip is
|
|
11
|
-
* byte-stable (R-S4 / OD-1).
|
|
12
|
-
*
|
|
13
|
-
* The forward string functions (`reshapeWhenAndWhy`, `cleanClause`,
|
|
14
|
-
* `toShortForm`, `yamlQuote`, `USE_WHEN_RE`) preserve the legacy reshape
|
|
15
|
-
* behavior exactly — `reshapeWhenAndWhy` output is byte-identical to that
|
|
16
|
-
* legacy for the same input (R-I4). Frontmatter writing and generator stamping
|
|
17
|
-
* are intentionally NOT part of this module — the reconcile engine owns all
|
|
18
|
-
* writes.
|
|
19
|
-
*/
|
|
20
|
-
import { usage } from '../errors.js';
|
|
21
|
-
// ── Forward translation (Claude `description` → crtr `when-and-why-to-read`) ──
|
|
22
|
-
// "Use when …" / "Use this when …" / "This skill should be used when …" / "Used when …"
|
|
23
|
-
const USE_WHEN_RE = /\b(?:use\s+this\s+skill\s+when|use\s+this\s+when|this\s+skill\s+should\s+be\s+used\s+when|used\s+when|use\s+when)\b\s*/i;
|
|
24
|
-
/** Collapse whitespace, strip surrounding connective punctuation, and lowercase
|
|
25
|
-
* a leading Capitalized word (but never an ACRONYM) so the clause reads inline
|
|
26
|
-
* after "When " / "because ". */
|
|
27
|
-
function cleanClause(s) {
|
|
28
|
-
let c = s.replace(/\s+/g, ' ').trim();
|
|
29
|
-
c = c.replace(/^[,;:.\s]+/, '').replace(/[,;:.\s]+$/, '');
|
|
30
|
-
if (/^[A-Z][a-z]/.test(c))
|
|
31
|
-
c = c.charAt(0).toLowerCase() + c.slice(1);
|
|
32
|
-
return c;
|
|
33
|
-
}
|
|
34
|
-
/** Reshape a Claude `description` into a read-routing sentence per
|
|
35
|
-
* taste/why-field-means-why-to-read: "When <situation>, this skill should be
|
|
36
|
-
* read [because <gist>]." Claude descriptions are typically
|
|
37
|
-
* "<gist>. Use when <situation>." — we lift the use-when clause into the
|
|
38
|
-
* situation and the preceding text into the payoff. With no explicit clause,
|
|
39
|
-
* the whole description becomes the situation. */
|
|
40
|
-
export function reshapeWhenAndWhy(description) {
|
|
41
|
-
const desc = description.replace(/\s+/g, ' ').trim();
|
|
42
|
-
if (desc === '')
|
|
43
|
-
return 'When this skill applies, this skill should be read.';
|
|
44
|
-
const m = desc.match(USE_WHEN_RE);
|
|
45
|
-
if (m && m.index !== undefined) {
|
|
46
|
-
const gist = cleanClause(desc.slice(0, m.index));
|
|
47
|
-
const situation = cleanClause(desc.slice(m.index + m[0].length));
|
|
48
|
-
if (situation !== '') {
|
|
49
|
-
let out = `When ${situation}, this skill should be read`;
|
|
50
|
-
if (gist !== '')
|
|
51
|
-
out += ` because ${gist}`;
|
|
52
|
-
return out + '.';
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return `When ${cleanClause(desc)}, this skill should be read.`;
|
|
56
|
-
}
|
|
57
|
-
/** One-line short-form for the human inventory (never loaded into agent
|
|
58
|
-
* context): the source description collapsed to a single line. */
|
|
59
|
-
export function toShortForm(description) {
|
|
60
|
-
return description.replace(/\s+/g, ' ').trim();
|
|
61
|
-
}
|
|
62
|
-
/** Always-double-quote a YAML scalar, escaping backslashes and quotes. The two
|
|
63
|
-
* generated fields are single-line, so this is safe regardless of colons,
|
|
64
|
-
* commas, or other YAML-significant characters. */
|
|
65
|
-
export function yamlQuote(s) {
|
|
66
|
-
return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
67
|
-
}
|
|
68
|
-
// ── Reverse translation (crtr `when-and-why-to-read` → Claude `description`) ──
|
|
69
|
-
/** The forward `reshapeWhenAndWhy` form, parsed back into its parts:
|
|
70
|
-
* "When <situation>, this skill should be read[ because <gist>]." */
|
|
71
|
-
const RESHAPE_RE = /^When (.+?), this skill should be read(?: because (.+?))?\.?$/s;
|
|
72
|
-
/** Recover `{ situation, gist }` from a `when-and-why-to-read` sentence. A
|
|
73
|
-
* value emitted by `reshapeWhenAndWhy` matches `RESHAPE_RE`; anything else is
|
|
74
|
-
* treated as a bare situation with no gist. The clauses are normalized through
|
|
75
|
-
* `cleanClause` so the reverse is the exact inverse of the forward split. */
|
|
76
|
-
function parseWhenAndWhy(whenAndWhy) {
|
|
77
|
-
const w = whenAndWhy.replace(/\s+/g, ' ').trim();
|
|
78
|
-
const m = w.match(RESHAPE_RE);
|
|
79
|
-
if (m) {
|
|
80
|
-
return { situation: cleanClause(m[1]), gist: cleanClause(m[2] ?? '') };
|
|
81
|
-
}
|
|
82
|
-
return { situation: cleanClause(w), gist: '' };
|
|
83
|
-
}
|
|
84
|
-
/** Pull a gist out of a `short-form` line: the text before any "Use when …"
|
|
85
|
-
* clause (mirroring the forward split), or the whole line if there is none. */
|
|
86
|
-
function gistFromShortForm(shortForm) {
|
|
87
|
-
const s = shortForm.replace(/\s+/g, ' ').trim();
|
|
88
|
-
const m = s.match(USE_WHEN_RE);
|
|
89
|
-
if (m && m.index !== undefined)
|
|
90
|
-
return cleanClause(s.slice(0, m.index));
|
|
91
|
-
return cleanClause(s);
|
|
92
|
-
}
|
|
93
|
-
/** Capitalize the first character of a clause (no-op for an empty string or a
|
|
94
|
-
* leading acronym, which is already upper-case). */
|
|
95
|
-
function capitalizeFirst(s) {
|
|
96
|
-
return s === '' ? s : s.charAt(0).toUpperCase() + s.slice(1);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Reverse translator: a crtr `when-and-why-to-read` (plus optional `short-form`
|
|
100
|
-
* for the gist when the sentence carries none) → the canonical Claude
|
|
101
|
-
* `description` form `"<gist>. Use when <situation>."`.
|
|
102
|
-
*
|
|
103
|
-
* This is the exact inverse of `reshapeWhenAndWhy`, normalized so that
|
|
104
|
-
* `crtrToClaudeDescription(reshapeWhenAndWhy(d), toShortForm(d))` is a fixed
|
|
105
|
-
* point under re-application — a round-trip is byte-stable (R-S4 / OD-1). The
|
|
106
|
-
* `gist` is recovered from the sentence's `because` clause; only when that is
|
|
107
|
-
* absent do we fall back to the (use-when-stripped) `short-form`, so a
|
|
108
|
-
* `short-form` that is itself a "Use when …" line contributes nothing and
|
|
109
|
-
* cannot ping-pong.
|
|
110
|
-
*/
|
|
111
|
-
export function crtrToClaudeDescription(whenAndWhy, shortForm) {
|
|
112
|
-
const { situation, gist: parsedGist } = parseWhenAndWhy(whenAndWhy);
|
|
113
|
-
let gist = parsedGist;
|
|
114
|
-
if (gist === '' && shortForm !== undefined)
|
|
115
|
-
gist = gistFromShortForm(shortForm);
|
|
116
|
-
const useWhen = situation === '' ? '' : `Use when ${situation}.`;
|
|
117
|
-
if (gist === '')
|
|
118
|
-
return useWhen;
|
|
119
|
-
const cap = capitalizeFirst(gist);
|
|
120
|
-
return useWhen === '' ? `${cap}.` : `${cap}. ${useWhen}`;
|
|
121
|
-
}
|
|
122
|
-
/** The default profile (R-I4). The forward translator reuses
|
|
123
|
-
* `reshapeWhenAndWhy`/`toShortForm` verbatim; the reverse uses
|
|
124
|
-
* `crtrToClaudeDescription`. Owned-field lists match the field taxonomy. */
|
|
125
|
-
export const DEFAULT_PROFILE = {
|
|
126
|
-
claudeToCrtr: {
|
|
127
|
-
description: (d) => ({ whenAndWhy: reshapeWhenAndWhy(d), shortForm: toShortForm(d) }),
|
|
128
|
-
},
|
|
129
|
-
crtrToClaude: {
|
|
130
|
-
description: (whenAndWhy, shortForm) => crtrToClaudeDescription(whenAndWhy, shortForm),
|
|
131
|
-
},
|
|
132
|
-
crtrOwned: ['kind', 'system-prompt-visibility', 'file-read-visibility', 'gate', 'applies-to'],
|
|
133
|
-
claudeOwned: ['argument-hint', 'user-invocable', 'paths', 'context', 'model', 'agent', 'keywords', 'type'],
|
|
134
|
-
};
|
|
135
|
-
/** The only fields a per-pair `frontmatter` override may set (R-I5). The
|
|
136
|
-
* translators are functions and cannot be expressed in JSON, so the
|
|
137
|
-
* overridable surface is exactly the owned-field lists. */
|
|
138
|
-
const OVERRIDABLE_KEYS = ['crtrOwned', 'claudeOwned'];
|
|
139
|
-
/**
|
|
140
|
-
* Resolve a pair's effective profile: a SHALLOW merge of its `frontmatter`
|
|
141
|
-
* override over `DEFAULT_PROFILE`, validated against the profile shape (R-I5).
|
|
142
|
-
* An unknown key or a non-string-array value is a malformed-manifest error
|
|
143
|
-
* (R-U7). NEVER mutates `DEFAULT_PROFILE` — the override path returns a fresh
|
|
144
|
-
* object; the no-override path returns the (unmodified) constant.
|
|
145
|
-
*/
|
|
146
|
-
export function resolveProfile(override) {
|
|
147
|
-
if (override === undefined)
|
|
148
|
-
return DEFAULT_PROFILE;
|
|
149
|
-
if (typeof override !== 'object' || override === null || Array.isArray(override)) {
|
|
150
|
-
throw usage('malformed manifest: pair frontmatter override must be an object');
|
|
151
|
-
}
|
|
152
|
-
for (const key of Object.keys(override)) {
|
|
153
|
-
if (!OVERRIDABLE_KEYS.includes(key)) {
|
|
154
|
-
throw usage(`malformed manifest: unknown frontmatter override key "${key}" (allowed: ${OVERRIDABLE_KEYS.join(', ')})`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return {
|
|
158
|
-
claudeToCrtr: DEFAULT_PROFILE.claudeToCrtr,
|
|
159
|
-
crtrToClaude: DEFAULT_PROFILE.crtrToClaude,
|
|
160
|
-
crtrOwned: validateOwned(override.crtrOwned, 'crtrOwned') ?? DEFAULT_PROFILE.crtrOwned,
|
|
161
|
-
claudeOwned: validateOwned(override.claudeOwned, 'claudeOwned') ?? DEFAULT_PROFILE.claudeOwned,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/** Validate one owned-field override list: undefined → inherit the default;
|
|
165
|
-
* anything other than an array of strings → malformed-manifest error. */
|
|
166
|
-
function validateOwned(value, key) {
|
|
167
|
-
if (value === undefined)
|
|
168
|
-
return undefined;
|
|
169
|
-
if (!Array.isArray(value) || !value.every((x) => typeof x === 'string')) {
|
|
170
|
-
throw usage(`malformed manifest: frontmatter override "${key}" must be an array of strings`);
|
|
171
|
-
}
|
|
172
|
-
return value;
|
|
173
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The translatable values recorded for each side at last sync. These are the
|
|
3
|
-
* ONLY values stored (§Resolved-here-4): owned fields are never recorded —
|
|
4
|
-
* each side reads its own owned fields through, so they need no merge base.
|
|
5
|
-
*/
|
|
6
|
-
export interface SnapshotMeta {
|
|
7
|
-
/** ISO 8601 timestamp of the sync that produced this base. */
|
|
8
|
-
syncedAt: string;
|
|
9
|
-
/** crtr-side translatable frontmatter at last sync. */
|
|
10
|
-
crtrFrontmatter: {
|
|
11
|
-
whenAndWhy?: string;
|
|
12
|
-
shortForm?: string;
|
|
13
|
-
};
|
|
14
|
-
/** Claude-side translatable frontmatter at last sync. */
|
|
15
|
-
claudeFrontmatter: {
|
|
16
|
-
description?: string;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/** A fully-loaded merge base for one pair. */
|
|
20
|
-
export interface Snapshot {
|
|
21
|
-
/** Shared body, frontmatter stripped. */
|
|
22
|
-
body: string;
|
|
23
|
-
/** Non-SKILL.md base assets, keyed by POSIX-style relpath under `assets/`. */
|
|
24
|
-
assets: Map<string, Buffer>;
|
|
25
|
-
/** Recorded translatable values + sync timestamp. */
|
|
26
|
-
meta: SnapshotMeta;
|
|
27
|
-
}
|
|
28
|
-
/** `~/.crouter/skill-sync/` — the machine-local, uncommitted root (R-U9). */
|
|
29
|
-
export declare function skillSyncRoot(): string;
|
|
30
|
-
/** `~/.crouter/skill-sync/snapshots/<id>/`. */
|
|
31
|
-
export declare function snapshotDir(id: string): string;
|
|
32
|
-
/** `~/.crouter/skill-sync/conflicts/<id>.md`. */
|
|
33
|
-
export declare function conflictReportPath(id: string): string;
|
|
34
|
-
/**
|
|
35
|
-
* Load the merge base for `id`, or `null` when the snapshot dir is absent
|
|
36
|
-
* (no prior sync — the engine seeds on first reconcile, R-S1/R-S3).
|
|
37
|
-
*/
|
|
38
|
-
export declare function readSnapshot(id: string): Snapshot | null;
|
|
39
|
-
/**
|
|
40
|
-
* Write the merge base for `id` byte-for-byte. The snapshot dir is rebuilt
|
|
41
|
-
* from scratch so stale assets from a prior base never linger — the on-disk
|
|
42
|
-
* `assets/` always mirrors exactly the passed `assets` map (R-U5/R-I3).
|
|
43
|
-
*/
|
|
44
|
-
export declare function writeSnapshot(id: string, snapshot: Snapshot): void;
|
|
45
|
-
/**
|
|
46
|
-
* Write the current conflict report for `id` to `conflicts/<id>.md`. The engine
|
|
47
|
-
* calls this on any true conflict (overlapping body/asset merge or both-sides
|
|
48
|
-
* non-equivalent frontmatter) after deciding to write nothing for the pair.
|
|
49
|
-
*/
|
|
50
|
-
export declare function writeConflictReport(id: string, text: string): void;
|
|
51
|
-
/**
|
|
52
|
-
* Delete a stale `conflicts/<id>.md` once the pair reconciles cleanly or
|
|
53
|
-
* becomes a no-op (OD-4). No-op if absent — never throws on a missing report.
|
|
54
|
-
*/
|
|
55
|
-
export declare function clearConflictReport(id: string): void;
|
|
56
|
-
/** True iff a conflict report currently exists for `id`. */
|
|
57
|
-
export declare function hasConflictReport(id: string): boolean;
|
|
@@ -1,120 +0,0 @@
|
|
|
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
|
-
}
|