@north-light/crouter 0.3.227 → 0.3.229
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/api/client.d.ts +5 -5
- package/dist/api/client.js +3 -3
- package/dist/api/dto/broker-ops.d.ts +8 -0
- package/dist/api/dto/memory.d.ts +8 -7
- package/dist/api/dto/memory.js +2 -2
- package/dist/builtin-memory/04-base-worker.md +1 -1
- package/dist/builtin-memory/insights/init.md +5 -5
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/agent-shaping.md +11 -11
- package/dist/builtin-memory/internal/examples/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +6 -6
- package/dist/builtin-memory/internal/plugins.md +1 -1
- package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +50 -37
- package/dist/clients/attach/__tests__/pane-tag-successor.test.js +56 -0
- package/dist/clients/attach/__tests__/profile-files.test.js +21 -0
- package/dist/clients/attach/chrome/inbox-strip.js +11 -7
- package/dist/clients/attach/session/pane-tag.d.ts +28 -2
- package/dist/clients/attach/session/pane-tag.js +48 -22
- package/dist/clients/attach/session/profile-files.d.ts +6 -0
- package/dist/clients/attach/session/profile-files.js +44 -11
- package/dist/clients/attach/slash/dispatch.js +4 -7
- package/dist/clients/attach/viewer.js +588 -596
- package/dist/commands/__tests__/surface-reload-target.test.js +21 -0
- package/dist/commands/api-client.d.ts +3 -7
- package/dist/commands/api-client.js +7 -12
- package/dist/commands/memory/__tests__/command-selector-and-mutation-guards.test.js +292 -0
- package/dist/commands/memory/__tests__/repository-root-lint.test.js +146 -0
- package/dist/commands/memory/delete.js +51 -24
- package/dist/commands/memory/edit.js +50 -10
- package/dist/commands/memory/find.js +105 -56
- package/dist/commands/memory/history.js +51 -31
- package/dist/commands/memory/lint.d.ts +0 -9
- package/dist/commands/memory/lint.js +78 -322
- package/dist/commands/memory/list.d.ts +12 -4
- package/dist/commands/memory/list.js +64 -25
- package/dist/commands/memory/move.js +103 -73
- package/dist/commands/memory/origin.js +35 -5
- package/dist/commands/memory/read.d.ts +4 -0
- package/dist/commands/memory/read.js +132 -80
- package/dist/commands/memory/shared.d.ts +115 -25
- package/dist/commands/memory/shared.js +331 -74
- package/dist/commands/memory/write.js +72 -21
- package/dist/commands/memory.js +2 -2
- package/dist/commands/node/create.d.ts +1 -1
- package/dist/commands/node/create.js +1 -1
- package/dist/commands/node/inspect.js +1 -1
- package/dist/commands/node/lifecycle.js +5 -5
- package/dist/commands/pkg/market-manage.js +25 -24
- package/dist/commands/pkg/plugin-manage.d.ts +22 -6
- package/dist/commands/pkg/plugin-manage.js +118 -25
- package/dist/commands/pkg/shared.d.ts +8 -0
- package/dist/commands/pkg/shared.js +23 -20
- package/dist/commands/surface-reload.d.ts +5 -0
- package/dist/commands/surface-reload.js +9 -1
- package/dist/commands/sys/__tests__/migrate.test.js +1140 -22
- package/dist/commands/sys/__tests__/sync-project-guidance.test.js +218 -0
- package/dist/commands/sys/migrate.js +126 -140
- package/dist/commands/sys/panels/profiles-panel.d.ts +66 -0
- package/dist/commands/sys/panels/profiles-panel.js +599 -0
- package/dist/commands/sys/settings-shell.d.ts +4 -2
- package/dist/commands/sys/settings-shell.js +35 -4
- package/dist/commands/sys/settings.js +21 -5
- package/dist/commands/sys/sync-deps.d.ts +2 -0
- package/dist/commands/sys/sync-deps.js +26 -15
- package/dist/commands/sys/sync-project-guidance.js +228 -145
- package/dist/commands/sys/sync-skills.js +28 -17
- package/dist/commands/sys/update.js +11 -3
- package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +15 -2
- package/dist/core/__tests__/config-change-delta.test.js +106 -0
- package/dist/core/__tests__/context-intro.test.js +13 -9
- package/dist/core/__tests__/daemon-boot.test.js +22 -16
- package/dist/core/__tests__/fixtures/memory-slash-live-probe.js +5 -1
- package/dist/core/__tests__/human-deliver.test.js +19 -0
- package/dist/core/__tests__/inline-memory-refs.test.js +50 -46
- package/dist/core/__tests__/{serial → integration}/broker-fork-seam.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-sdk-wiring.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/broker-snapshot-history.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/command-plugins.test.js +94 -3
- package/dist/core/__tests__/{serial → integration}/deferred-no-wake.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/flagship-lifecycle.test.js +13 -27
- package/dist/core/__tests__/{serial → integration}/host-teardown-process-group.test.js +1 -1
- package/dist/core/__tests__/{serial → integration}/human-deliver-e2e.test.js +1 -1
- package/dist/core/__tests__/{live-mutation-verbs.test.js → integration/live-mutation-verbs.test.js} +22 -33
- package/dist/core/__tests__/{serial → integration}/live-mutation.test.js +21 -41
- package/dist/core/__tests__/integration/refresh-stall-recycle.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/refresh-stall-recycle.test.js +1 -1
- package/dist/core/__tests__/integration/revive.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/revive.test.js +38 -19
- package/dist/core/__tests__/integration/spawn-root.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/spawn-root.test.js +104 -1
- package/dist/core/__tests__/integration/subscription-delivery.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/subscription-delivery.test.js +1 -1
- package/dist/core/__tests__/integration/tmux-surface.test.d.ts +1 -0
- package/dist/core/__tests__/{serial → integration}/tmux-surface.test.js +1 -1
- package/dist/core/__tests__/integration/worktree-land.test.d.ts +1 -0
- package/dist/core/__tests__/integration/worktree-land.test.js +400 -0
- package/dist/core/__tests__/integration/worktree-reap.test.d.ts +1 -0
- package/dist/core/__tests__/{serial/worktree.test.js → integration/worktree-reap.test.js} +6 -338
- package/dist/core/__tests__/kickoff.test.js +16 -5
- package/dist/core/__tests__/memory-resolver-precedence.test.js +122 -91
- package/dist/core/__tests__/nested-store-discovery.test.js +5 -3
- package/dist/core/__tests__/on-read-crouter-home-fence.test.js +2 -1
- package/dist/core/__tests__/on-read-dedup-resume.test.js +39 -27
- package/dist/core/__tests__/on-read-nested-store.test.js +19 -13
- package/dist/core/__tests__/profile-project-memory-delivery.test.js +138 -44
- package/dist/core/__tests__/repository-association.test.d.ts +1 -0
- package/dist/core/__tests__/repository-association.test.js +153 -0
- package/dist/core/__tests__/repository-root-identity.test.d.ts +1 -0
- package/dist/core/__tests__/repository-root-identity.test.js +219 -0
- package/dist/core/__tests__/review-model-floor.test.js +12 -4
- package/dist/core/__tests__/seam/memory-slash-node-relative-inventory.test.js +27 -11
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.d.ts +1 -0
- package/dist/core/__tests__/warm-claim-preference-snapshot.test.js +40 -0
- package/dist/core/canvas/canvas.d.ts +3 -0
- package/dist/core/canvas/canvas.js +30 -10
- package/dist/core/canvas/db.js +28 -1
- package/dist/core/canvas/paths.d.ts +4 -6
- package/dist/core/canvas/paths.js +8 -6
- package/dist/core/canvas/render-source.js +2 -2
- package/dist/core/canvas/types.d.ts +9 -13
- package/dist/core/config.d.ts +3 -3
- package/dist/core/config.js +3 -3
- package/dist/core/exclusive-lock.d.ts +2 -0
- package/dist/core/exclusive-lock.js +21 -0
- package/dist/core/git.d.ts +0 -1
- package/dist/core/git.js +0 -3
- package/dist/core/human/__tests__/integration/inbox-core.test.d.ts +1 -0
- package/dist/core/human/feedback-companion.js +3 -0
- package/dist/core/human/scan.js +8 -1
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +2 -1
- package/dist/core/memory/doc-link-grammar.js +2 -2
- package/dist/core/memory/history.d.ts +24 -0
- package/dist/core/memory/history.js +66 -1
- package/dist/core/memory/identity.d.ts +65 -0
- package/dist/core/memory/identity.js +185 -0
- package/dist/core/memory/inline-ref-guidance.d.ts +1 -1
- package/dist/core/memory/inline-ref-guidance.js +1 -1
- package/dist/core/memory/inline-ref-inventory.d.ts +3 -10
- package/dist/core/memory/inline-ref-inventory.js +30 -62
- package/dist/core/memory/lint.d.ts +129 -0
- package/dist/core/memory/lint.js +517 -0
- package/dist/core/memory/project-namespace.d.ts +31 -0
- package/dist/core/memory/project-namespace.js +85 -0
- package/dist/core/memory/repository-association.d.ts +31 -0
- package/dist/core/memory/repository-association.js +129 -0
- package/dist/core/memory/tree.d.ts +39 -0
- package/dist/core/memory/tree.js +93 -0
- package/dist/core/memory-resolver.d.ts +193 -90
- package/dist/core/memory-resolver.js +461 -375
- package/dist/core/nested-stores.js +6 -13
- package/dist/core/profiles/select.d.ts +4 -1
- package/dist/core/profiles/select.js +108 -48
- package/dist/core/review/__tests__/capture-origin.test.js +2 -2
- package/dist/core/review/__tests__/stage-identity.test.js +2 -2
- package/dist/core/review/companion.js +11 -2
- package/dist/core/runtime/bearings.d.ts +2 -2
- package/dist/core/runtime/bearings.js +3 -3
- package/dist/core/runtime/broker/daemon-ops.d.ts +2 -2
- package/dist/core/runtime/broker/rebind.js +5 -0
- package/dist/core/runtime/broker-extension-render.d.ts +7 -3
- package/dist/core/runtime/broker-extension-render.js +10 -5
- package/dist/core/runtime/broker-persona-guidance.d.ts +19 -5
- package/dist/core/runtime/broker-persona-guidance.js +122 -27
- package/dist/core/runtime/deliver-live.d.ts +16 -4
- package/dist/core/runtime/deliver-live.js +29 -15
- package/dist/core/runtime/kickoff.d.ts +3 -3
- package/dist/core/runtime/kickoff.js +8 -16
- package/dist/core/runtime/launch.js +1 -1
- package/dist/core/runtime/lifecycle.js +2 -3
- package/dist/core/runtime/nodes.d.ts +3 -4
- package/dist/core/runtime/nodes.js +3 -4
- package/dist/core/runtime/persona.d.ts +8 -12
- package/dist/core/runtime/persona.js +24 -96
- package/dist/core/runtime/promote.d.ts +2 -2
- package/dist/core/runtime/promote.js +8 -21
- package/dist/core/runtime/revive.js +20 -17
- package/dist/core/runtime/spawn.js +18 -7
- package/dist/core/runtime/tmux-bindings.js +2 -3
- package/dist/core/runtime/warm-pool.js +3 -4
- package/dist/core/scope.js +2 -0
- package/dist/core/self-update.d.ts +0 -2
- package/dist/core/self-update.js +2 -35
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.d.ts +1 -0
- package/dist/core/substrate/__tests__/surface-match-memory-read.test.js +28 -0
- package/dist/core/substrate/index.d.ts +2 -2
- package/dist/core/substrate/index.js +1 -1
- package/dist/core/substrate/injected-store.d.ts +43 -27
- package/dist/core/substrate/injected-store.js +208 -104
- package/dist/core/substrate/listings.d.ts +19 -12
- package/dist/core/substrate/listings.js +75 -52
- package/dist/core/substrate/on-read-node.d.ts +4 -7
- package/dist/core/substrate/on-read-node.js +6 -8
- package/dist/core/substrate/on-read.d.ts +22 -25
- package/dist/core/substrate/on-read.js +103 -147
- package/dist/core/substrate/render-node.d.ts +4 -7
- package/dist/core/substrate/render-node.js +5 -7
- package/dist/core/substrate/render.d.ts +21 -3
- package/dist/core/substrate/render.js +291 -223
- package/dist/core/substrate/schema.d.ts +1 -13
- package/dist/core/substrate/schema.js +5 -40
- package/dist/core/substrate/session-cache.d.ts +14 -4
- package/dist/core/substrate/session-cache.js +40 -22
- package/dist/core/substrate/surface-match.d.ts +9 -7
- package/dist/core/substrate/surface-match.js +26 -25
- package/dist/daemon/__tests__/helpers/source-daemon.d.ts +30 -0
- package/dist/daemon/__tests__/helpers/source-daemon.js +174 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.d.ts +1 -0
- package/dist/daemon/__tests__/integration/migration-startup.test.js +97 -0
- package/dist/daemon/api/__tests__/bridge-heartbeat.test.js +21 -5
- package/dist/daemon/api/handlers/broker-ops.js +21 -16
- package/dist/daemon/api/handlers/memory.js +2 -0
- package/dist/daemon/crtrd.js +2 -0
- package/dist/daemon/human/finish.js +8 -1
- package/dist/daemon/manage.d.ts +0 -1
- package/dist/daemon/manage.js +7 -16
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/migrations/001-surfaces-frontmatter.js +21 -109
- package/dist/migrations/002-profile-project-memory.js +1 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.d.ts +26 -0
- package/dist/migrations/003-repository-root-memory-identity/front-door.js +231 -0
- package/dist/migrations/003-repository-root-memory-identity/index.d.ts +2 -0
- package/dist/migrations/003-repository-root-memory-identity/index.js +513 -0
- package/dist/migrations/003-repository-root-memory-identity/references.d.ts +95 -0
- package/dist/migrations/003-repository-root-memory-identity/references.js +469 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.d.ts +39 -0
- package/dist/migrations/003-repository-root-memory-identity/repository-facts.js +349 -0
- package/dist/migrations/__tests__/activation-concurrency.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation-concurrency.test.js +145 -0
- package/dist/migrations/__tests__/activation.test.d.ts +1 -0
- package/dist/migrations/__tests__/activation.test.js +149 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.d.ts +1 -0
- package/dist/migrations/__tests__/deletion-and-root-declaration.test.js +149 -0
- package/dist/migrations/activation.d.ts +16 -0
- package/dist/migrations/activation.js +78 -0
- package/dist/migrations/convergent.d.ts +14 -3
- package/dist/migrations/convergent.js +21 -10
- package/dist/migrations/corpus.d.ts +78 -0
- package/dist/migrations/corpus.js +497 -0
- package/dist/migrations/frontmatter-splice.d.ts +15 -0
- package/dist/migrations/frontmatter-splice.js +176 -0
- package/dist/migrations/registry.d.ts +6 -1
- package/dist/migrations/registry.js +7 -2
- package/dist/migrations/runner.d.ts +41 -0
- package/dist/migrations/runner.js +81 -0
- package/dist/migrations/types.d.ts +148 -9
- package/dist/migrations/types.js +9 -2
- package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +225 -17
- package/dist/pi-extensions/__tests__/canvas-goal-capture-envelope.test.js +11 -3
- package/dist/pi-extensions/canvas-context-intro.d.ts +3 -5
- package/dist/pi-extensions/canvas-context-intro.js +50 -46
- package/dist/pi-extensions/canvas-doc-substrate.d.ts +1 -8
- package/dist/pi-extensions/canvas-doc-substrate.js +55 -122
- package/dist/pi-extensions/canvas-stophook.js +6 -13
- package/dist/shared/generated-context.d.ts +0 -3
- package/dist/shared/generated-context.js +0 -57
- package/dist/shared/tool-groups.js +2 -3
- package/dist/types.d.ts +8 -11
- package/dist/types.js +4 -31
- package/package.json +5 -4
- package/runtime.lock.json +2 -2
- package/dist/builtin-memory/05-kinds/design/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/design/design-contract.md +0 -19
- package/dist/builtin-memory/05-kinds/developer/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/developer/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/plan/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +0 -28
- package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +0 -15
- package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +0 -15
- package/dist/builtin-memory/05-kinds/plan/reviewers/lens-contract.md +0 -13
- package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +0 -17
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +0 -17
- package/dist/builtin-memory/05-kinds/spec/00-base.md +0 -17
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +0 -15
- package/dist/builtin-memory/05-kinds/spec/requirements.md +0 -15
- package/dist/builtin-memory/design/guide.md +0 -35
- package/dist/builtin-memory/design/roadmap.md +0 -21
- package/dist/builtin-memory/development.md +0 -113
- package/dist/builtin-memory/plan/roadmap.md +0 -25
- package/dist/builtin-memory/spec/guide.md +0 -53
- package/dist/builtin-memory/spec/requirements.md +0 -29
- package/dist/builtin-memory/spec/roadmap.md +0 -36
- package/dist/builtin-memory/testing.md +0 -39
- /package/dist/api/__tests__/{serial → integration}/client.test.d.ts +0 -0
- /package/dist/api/__tests__/{serial → integration}/client.test.js +0 -0
- /package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{serial → integration}/provider-rotation.test.ts +0 -0
- /package/dist/clients/{inbox/__tests__/serial/inbox-controller.test.d.ts → attach/__tests__/pane-tag-successor.test.d.ts} +0 -0
- /package/dist/clients/{inbox/__tests__/serial/mount-panel.test.d.ts → attach/__tests__/profile-files.test.d.ts} +0 -0
- /package/dist/{core/__tests__/live-mutation-verbs.test.d.ts → clients/inbox/__tests__/integration/inbox-controller.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/inbox-controller.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-fork-seam.test.d.ts → clients/inbox/__tests__/integration/mount-panel.test.d.ts} +0 -0
- /package/dist/clients/inbox/__tests__/{serial → integration}/mount-panel.test.js +0 -0
- /package/dist/{core/__tests__/serial/broker-sdk-wiring.test.d.ts → commands/__tests__/surface-reload-target.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/broker-snapshot-history.test.d.ts → commands/memory/__tests__/command-selector-and-mutation-guards.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/command-plugins.test.d.ts → commands/memory/__tests__/repository-root-lint.test.d.ts} +0 -0
- /package/dist/{core/__tests__/serial/deferred-no-wake.test.d.ts → commands/sys/__tests__/sync-project-guidance.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/flagship-lifecycle.test.d.ts → config-change-delta.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/host-teardown-process-group.test.d.ts → integration/broker-fork-seam.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/human-deliver-e2e.test.d.ts → integration/broker-sdk-wiring.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/live-mutation.test.d.ts → integration/broker-snapshot-history.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/refresh-stall-recycle.test.d.ts → integration/command-plugins.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/revive.test.d.ts → integration/deferred-no-wake.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/spawn-root.test.d.ts → integration/flagship-lifecycle.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/subscription-delivery.test.d.ts → integration/host-teardown-process-group.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/tmux-surface.test.d.ts → integration/human-deliver-e2e.test.d.ts} +0 -0
- /package/dist/core/__tests__/{serial/worktree.test.d.ts → integration/live-mutation-verbs.test.d.ts} +0 -0
- /package/dist/core/{human/__tests__/serial/inbox-core.test.d.ts → __tests__/integration/live-mutation.test.d.ts} +0 -0
- /package/dist/core/human/__tests__/{serial → integration}/inbox-core.test.js +0 -0
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
// `crtr sys sync project-guidance` — one-way migration of a
|
|
2
|
-
// CLAUDE.md/AGENTS.md/.claude/rules into
|
|
3
|
-
// `.crouter/memory
|
|
4
|
-
//
|
|
5
|
-
// as read entries, or take the front-door pair when they are pathless.
|
|
1
|
+
// `crtr sys sync project-guidance` — one-way migration of a repository's
|
|
2
|
+
// CLAUDE.md/AGENTS.md/.claude/rules into repository-root and nested
|
|
3
|
+
// `.crouter/memory/` stores. Only the repository root owns the namespace and
|
|
4
|
+
// workspace front door; nested stores derive their prefix from their path.
|
|
6
5
|
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
7
|
-
import { basename,
|
|
6
|
+
import { basename, join, resolve } from 'node:path';
|
|
8
7
|
import { defineLeaf } from '../../core/command.js';
|
|
9
8
|
import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
|
|
10
|
-
import {
|
|
11
|
-
import { pathExists, readText, walkFiles, writeText } from '../../core/fs-utils.js';
|
|
9
|
+
import { usage } from '../../core/errors.js';
|
|
10
|
+
import { pathExists, readText, realpathOrSelf, walkFiles, writeText } from '../../core/fs-utils.js';
|
|
12
11
|
import { loadProfileManifest } from '../../core/profiles/manifest.js';
|
|
13
12
|
import { CRTR_DIR_NAME } from '../../types.js';
|
|
14
13
|
import { memoryFilePath, serializeMemoryDoc } from '../memory/shared.js';
|
|
15
14
|
import { normalizedName, routeFromDescription, scalarString } from './sync-shared.js';
|
|
16
|
-
import {
|
|
15
|
+
import { deriveProjectNamespace } from '../../core/memory/project-namespace.js';
|
|
16
|
+
import { associateRepository } from '../../core/memory/repository-association.js';
|
|
17
|
+
import { openProjectMemoryStore } from '../../core/memory-resolver.js';
|
|
18
|
+
import { composeCanonicalName, localNameFromPath, resolveLocalName, validateExplicitLocalName, } from '../../core/memory/identity.js';
|
|
17
19
|
// Directories a project-doc walk never descends into — build/dependency trees
|
|
18
20
|
// plus crouter's own state dir.
|
|
19
21
|
const PROJECT_DOC_JUNK_DIRS = new Set([
|
|
@@ -130,93 +132,52 @@ function normalizeRulePaths(v) {
|
|
|
130
132
|
}
|
|
131
133
|
return [];
|
|
132
134
|
}
|
|
133
|
-
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const res = gitSync(['rev-parse', '--path-format=absolute', '--git-common-dir'], dir);
|
|
142
|
-
if (res.status !== 0)
|
|
143
|
-
return undefined;
|
|
144
|
-
const gitCommonDir = res.stdout.trim();
|
|
145
|
-
if (gitCommonDir === '')
|
|
146
|
-
return undefined;
|
|
147
|
-
return basename(dirname(gitCommonDir));
|
|
148
|
-
}
|
|
149
|
-
/** The current git worktree's OWN top-level directory (`git rev-parse
|
|
150
|
-
* --show-toplevel`) — unlike `--git-common-dir` above, this does NOT resolve
|
|
151
|
-
* through a linked worktree to the shared repo storage; it is the actual
|
|
152
|
-
* checked-out tree containing `dir`. This is the stable root to compute a
|
|
153
|
-
* doc's subdir suffix against, even when the discovery walk itself started
|
|
154
|
-
* strictly inside that tree — e.g. `--source` pointed directly at a nested
|
|
155
|
-
* package dir, so the walk root and the source dir are the same directory
|
|
156
|
-
* and a walk-root-relative suffix would be empty. Returns undefined when
|
|
157
|
-
* `dir` isn't inside a git working tree at all (the non-git fallback keeps
|
|
158
|
-
* using the walk root itself). */
|
|
159
|
-
function gitWorktreeRoot(dir) {
|
|
160
|
-
const res = gitSync(['rev-parse', '--path-format=absolute', '--show-toplevel'], dir);
|
|
161
|
-
if (res.status !== 0)
|
|
162
|
-
return undefined;
|
|
163
|
-
const top = res.stdout.trim();
|
|
164
|
-
return top === '' ? undefined : resolve(top);
|
|
165
|
-
}
|
|
166
|
-
/** First Markdown H1 heading in the doc body, heading marker stripped. */
|
|
167
|
-
function h1Title(body) {
|
|
168
|
-
for (const raw of body.split('\n')) {
|
|
169
|
-
const m = raw.match(/^#\s+(.+)$/);
|
|
170
|
-
if (m) {
|
|
171
|
-
const title = m[1].replace(/\s+/g, ' ').trim();
|
|
172
|
-
if (title !== '')
|
|
173
|
-
return title;
|
|
174
|
-
}
|
|
135
|
+
/** Resolve one effective guidance namespace for a repository. The cache is
|
|
136
|
+
* keyed by repository root so cwd/profile widening and nested source guides
|
|
137
|
+
* cannot derive competing identities during one sync. */
|
|
138
|
+
function resolveRepositoryNamespace(sourceDir, walkRoot, rootBodies, memo) {
|
|
139
|
+
const associated = associateRepository(sourceDir);
|
|
140
|
+
let repositoryRoot;
|
|
141
|
+
if (associated.ok) {
|
|
142
|
+
repositoryRoot = associated.association.repositoryRoot;
|
|
175
143
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
* unreliable (a worktree/canvas checkout's basename is a node id, not the
|
|
180
|
-
* project name), so prefer the actual git repo's root dirname, then the root
|
|
181
|
-
* doc's own H1 title if one exists there, then the raw basename. */
|
|
182
|
-
function projectRootLabel(root) {
|
|
183
|
-
const gitLabel = gitRepoRootName(root);
|
|
184
|
-
if (gitLabel !== undefined)
|
|
185
|
-
return normalizedName(gitLabel);
|
|
186
|
-
for (const name of PROJECT_DOC_FILENAMES) {
|
|
187
|
-
const p = join(root, name);
|
|
188
|
-
if (existsSync(p) && statSync(p).isFile()) {
|
|
189
|
-
const title = h1Title(parseFrontmatterGeneric(readText(p)).body);
|
|
190
|
-
if (title !== undefined)
|
|
191
|
-
return normalizedName(title);
|
|
192
|
-
}
|
|
144
|
+
else if (associated.ok === false && associated.code === 'no-project-root') {
|
|
145
|
+
// A guidance walk may initialize a repository that has no memory store yet.
|
|
146
|
+
repositoryRoot = realpathOrSelf(walkRoot);
|
|
193
147
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
148
|
+
else {
|
|
149
|
+
const reason = associated.ok === false ? associated.reason : 'unknown repository association failure';
|
|
150
|
+
throw usage(`cannot associate project guidance at ${sourceDir}: ${reason}`, {
|
|
151
|
+
next: `Run \`crtr sys migrate --dir ${sourceDir}\`.`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
repositoryRoot = realpathOrSelf(repositoryRoot);
|
|
155
|
+
const existing = memo.get(repositoryRoot);
|
|
156
|
+
if (existing !== undefined)
|
|
157
|
+
return existing;
|
|
158
|
+
const store = openProjectMemoryStore(repositoryRoot);
|
|
159
|
+
if (store.mountStatus === 'ready') {
|
|
160
|
+
const namespace = store.repositoryNamespace ?? store.namespace;
|
|
161
|
+
const resolved = { namespace, repositoryRoot, initialize: false };
|
|
162
|
+
memo.set(repositoryRoot, resolved);
|
|
163
|
+
return resolved;
|
|
164
|
+
}
|
|
165
|
+
const memoryDir = join(repositoryRoot, CRTR_DIR_NAME, 'memory');
|
|
166
|
+
const docs = pathExists(memoryDir) ? walkFiles(memoryDir, (name) => name.endsWith('.md')) : [];
|
|
167
|
+
if (docs.length > 0) {
|
|
168
|
+
throw usage(`project memory store at ${memoryDir} is undeclared or invalid`, {
|
|
169
|
+
next: `Run \`crtr sys migrate --dir ${repositoryRoot}\`.`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
const derived = deriveProjectNamespace({ ownerDir: repositoryRoot, rootBody: rootBodies.get(repositoryRoot) });
|
|
173
|
+
if (derived.ok === false) {
|
|
174
|
+
throw usage(`cannot initialize project memory store at ${memoryDir}: ${derived.reason}`, {
|
|
175
|
+
next: `Run \`crtr sys migrate --dir ${repositoryRoot}\`.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const resolved = { namespace: derived.namespace, repositoryRoot, initialize: true };
|
|
179
|
+
memo.set(repositoryRoot, resolved);
|
|
180
|
+
return resolved;
|
|
220
181
|
}
|
|
221
182
|
/** The surfaces pair every workspace front door carries: whole-doc delivery
|
|
222
183
|
* when cwd/profile mounts the store, plus on any file read beneath the
|
|
@@ -239,62 +200,106 @@ function anchorReadGlob(g) {
|
|
|
239
200
|
return t;
|
|
240
201
|
return `./${t}`;
|
|
241
202
|
}
|
|
242
|
-
/** AGENTS.md/CLAUDE.md
|
|
243
|
-
*
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
203
|
+
/** AGENTS.md/CLAUDE.md becomes a repository-root or nested store root
|
|
204
|
+
* document. Only the repository root carries the declaration and front door. */
|
|
205
|
+
function prepareAgentsCandidate(candidate, resolution) {
|
|
206
|
+
const parsed = parseFrontmatterGeneric(readText(candidate.path));
|
|
207
|
+
const root = realpathOrSelf(candidate.sourceDir) === resolution.repositoryRoot;
|
|
208
|
+
return prepareRootCandidate(candidate, resolution.namespace, parsed.body, root, parsed.data ?? {});
|
|
209
|
+
}
|
|
210
|
+
function sourceReadRoute(frontmatter) {
|
|
211
|
+
if (!Array.isArray(frontmatter['surfaces']))
|
|
212
|
+
return [];
|
|
213
|
+
return frontmatter['surfaces'].filter((entry) => {
|
|
214
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry))
|
|
215
|
+
return false;
|
|
216
|
+
const value = entry;
|
|
217
|
+
if (value['on'] !== 'read' || !['name', 'preview', 'content'].includes(String(value['at'])))
|
|
218
|
+
return false;
|
|
219
|
+
const match = value['match'];
|
|
220
|
+
const hasMatch = typeof match === 'string'
|
|
221
|
+
? match.trim() !== ''
|
|
222
|
+
: Array.isArray(match) && match.length > 0 && match.every((glob) => typeof glob === 'string' && glob.trim() !== '');
|
|
223
|
+
const matchFrontmatter = value['match-frontmatter'];
|
|
224
|
+
return hasMatch || (matchFrontmatter !== null && typeof matchFrontmatter === 'object' && !Array.isArray(matchFrontmatter));
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function prepareRootCandidate(candidate, namespace, body, repositoryRoot, sourceFrontmatter = {}) {
|
|
228
|
+
if (!repositoryRoot) {
|
|
229
|
+
const surfaces = sourceReadRoute(sourceFrontmatter);
|
|
230
|
+
const authoredRoute = scalarString(sourceFrontmatter['when-and-why-to-read'])?.trim();
|
|
231
|
+
const frontmatter = {
|
|
232
|
+
kind: 'knowledge',
|
|
233
|
+
'when-and-why-to-read': authoredRoute !== undefined && authoredRoute !== ''
|
|
234
|
+
? authoredRoute
|
|
235
|
+
: `When working under ${namespace}, this knowledge should be read because it carries the nested project guidance.`,
|
|
236
|
+
'short-form': synthesizeShortForm(body),
|
|
237
|
+
...(surfaces.length > 0 ? { surfaces } : {}),
|
|
238
|
+
};
|
|
239
|
+
return {
|
|
240
|
+
candidate,
|
|
241
|
+
name: '',
|
|
242
|
+
target: join(candidate.sourceDir, CRTR_DIR_NAME, 'memory', 'INDEX.md'),
|
|
243
|
+
frontmatter,
|
|
244
|
+
body,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
250
247
|
const frontmatter = {
|
|
251
248
|
kind: 'knowledge',
|
|
252
|
-
|
|
253
|
-
'when-and-why-to-read': `When working in ${
|
|
249
|
+
namespace,
|
|
250
|
+
'when-and-why-to-read': `When working in ${namespace}, this knowledge should be read because it is the project's operating guide.`,
|
|
254
251
|
'short-form': synthesizeShortForm(body),
|
|
255
252
|
surfaces: frontDoorSurfaces(),
|
|
256
253
|
};
|
|
257
254
|
return {
|
|
258
255
|
candidate,
|
|
259
|
-
name:
|
|
260
|
-
target:
|
|
256
|
+
name: namespace,
|
|
257
|
+
target: join(candidate.sourceDir, CRTR_DIR_NAME, 'memory', 'INDEX.md'),
|
|
261
258
|
frontmatter,
|
|
262
259
|
body,
|
|
263
260
|
};
|
|
264
261
|
}
|
|
265
|
-
/**
|
|
266
|
-
*
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
262
|
+
/** A rule's identity is store-local. The physical path supplies the default;
|
|
263
|
+
* an authored name is accepted as an explicit local identity. */
|
|
264
|
+
function prepareRuleCandidate(candidate, resolution) {
|
|
265
|
+
const associated = associateRepository(candidate.sourceDir);
|
|
266
|
+
if (!associated.ok) {
|
|
267
|
+
throw usage(`cannot associate rule guidance at ${candidate.path}: ${associated.reason}`, {
|
|
268
|
+
next: `Run \`crtr sys migrate --dir ${candidate.sourceDir}\`.`,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
// Nested stores inherit the repository declaration plus their normalized
|
|
272
|
+
// owner-relative path, exactly as the runtime's project store descriptor.
|
|
273
|
+
const namespace = composeCanonicalName(resolution.namespace, associated.association.ownerRelativePath);
|
|
275
274
|
const raw = readText(candidate.path);
|
|
276
275
|
const parsed = parseFrontmatterGeneric(raw);
|
|
277
276
|
const fm = parsed.data ?? {};
|
|
278
|
-
const
|
|
277
|
+
const physicalLeaf = normalizedName(basename(candidate.path));
|
|
278
|
+
const pathLocal = localNameFromPath(`${physicalLeaf}.md`);
|
|
279
|
+
const localName = resolveLocalName(fm, pathLocal);
|
|
280
|
+
const valid = validateExplicitLocalName(localName, namespace);
|
|
281
|
+
if (valid.ok === false)
|
|
282
|
+
throw usage(`invalid local rule name in ${candidate.path}: ${valid.reason}`);
|
|
279
283
|
const description = scalarString(fm['description']) ?? '';
|
|
280
|
-
const
|
|
281
|
-
const name = normalizedName(`${dirLabel}/${leaf}`);
|
|
284
|
+
const name = composeCanonicalName(namespace, localName);
|
|
282
285
|
const frontmatter = {
|
|
283
286
|
kind: 'knowledge',
|
|
284
|
-
name,
|
|
287
|
+
name: localName,
|
|
285
288
|
'when-and-why-to-read': description !== ''
|
|
286
289
|
? routeFromDescription(description, 'knowledge')
|
|
287
|
-
: `When working under ${
|
|
290
|
+
: `When working under ${namespace}, this knowledge should be read because it is migrated project rule guidance.`,
|
|
288
291
|
'short-form': description !== '' ? description.replace(/\s+/g, ' ').trim() : synthesizeShortForm(parsed.body),
|
|
289
292
|
};
|
|
290
293
|
const globs = normalizeRulePaths(fm['paths']).map(anchorReadGlob);
|
|
294
|
+
// A pathless rule is still readable, but it must not mint a repository
|
|
295
|
+
// workspace front door; that belongs exclusively to the repository root.
|
|
291
296
|
frontmatter['surfaces'] = globs.length > 0
|
|
292
297
|
? [{ on: 'read', match: globs.length === 1 ? globs[0] : globs, at: 'content' }]
|
|
293
|
-
:
|
|
298
|
+
: [{ on: 'read', match: './**', at: 'content' }];
|
|
294
299
|
return {
|
|
295
300
|
candidate,
|
|
296
301
|
name,
|
|
297
|
-
target: memoryFilePath(join(candidate.sourceDir, CRTR_DIR_NAME, 'memory'),
|
|
302
|
+
target: memoryFilePath(join(candidate.sourceDir, CRTR_DIR_NAME, 'memory'), localName),
|
|
298
303
|
frontmatter,
|
|
299
304
|
body: parsed.body,
|
|
300
305
|
};
|
|
@@ -322,6 +327,29 @@ function convertProjectDoc(prepared, opts) {
|
|
|
322
327
|
status: opts.dryRun ? 'would-import' : 'imported',
|
|
323
328
|
};
|
|
324
329
|
}
|
|
330
|
+
function validateAuthoredNamespace(candidate, resolution) {
|
|
331
|
+
if (candidate.kind !== 'agents')
|
|
332
|
+
return;
|
|
333
|
+
let requested;
|
|
334
|
+
try {
|
|
335
|
+
requested = parseFrontmatterGeneric(readText(candidate.path)).data?.['namespace'];
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (requested === undefined)
|
|
341
|
+
return;
|
|
342
|
+
const root = realpathOrSelf(candidate.sourceDir) === resolution.repositoryRoot;
|
|
343
|
+
const declarationPath = join(resolution.repositoryRoot, CRTR_DIR_NAME, 'memory', 'INDEX.md');
|
|
344
|
+
if (!root) {
|
|
345
|
+
throw usage(`project guidance at ${candidate.path} declares a namespace, but ${declarationPath} is the only declaration for this repository`, { next: `Run \`crtr sys migrate --dir ${resolution.repositoryRoot}\`.` });
|
|
346
|
+
}
|
|
347
|
+
if (requested !== resolution.namespace) {
|
|
348
|
+
throw usage(`project guidance namespace ${JSON.stringify(requested)} conflicts with the declared store namespace ${JSON.stringify(resolution.namespace)}`, {
|
|
349
|
+
next: `Run \`crtr sys migrate --dir ${resolution.repositoryRoot}\` for a repository-wide identity change.`,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
325
353
|
function renderSummary(results) {
|
|
326
354
|
const imported = results.filter((r) => r.status === 'imported').length;
|
|
327
355
|
const wouldImport = results.filter((r) => r.status === 'would-import').length;
|
|
@@ -340,11 +368,11 @@ function renderSummary(results) {
|
|
|
340
368
|
}
|
|
341
369
|
export const sysSyncProjectGuidanceLeaf = defineLeaf({
|
|
342
370
|
name: 'project-guidance',
|
|
343
|
-
description:
|
|
344
|
-
whenToUse: "migrating
|
|
371
|
+
description: 'migrate repository guidance into repository-root and nested crouter memory docs',
|
|
372
|
+
whenToUse: "migrating repository CLAUDE.md/AGENTS.md/.claude/rules into the crouter memory-doc model: one-way copy into the repository root's or nested store's .crouter/memory/. Only the repository-root guide becomes the workspace front door; nested guides and rules keep read routes. Never exports memory docs back to these source files.",
|
|
345
373
|
help: {
|
|
346
374
|
name: 'sys sync project-guidance',
|
|
347
|
-
summary:
|
|
375
|
+
summary: 'one-way migration: CLAUDE.md/AGENTS.md/.claude/rules → repository-root and nested crouter memory docs',
|
|
348
376
|
params: [
|
|
349
377
|
{ kind: 'flag', name: 'source', type: 'string', required: false, constraint: "The root to walk recursively for CLAUDE.md/AGENTS.md/.claude/rules files. Default: cwd plus every project in the current profile's purview (when one is selected)." },
|
|
350
378
|
{ kind: 'flag', name: 'dry-run', type: 'bool', required: false, constraint: 'Show what would be migrated without writing memory docs.' },
|
|
@@ -358,7 +386,7 @@ export const sysSyncProjectGuidanceLeaf = defineLeaf({
|
|
|
358
386
|
],
|
|
359
387
|
outputKind: 'object',
|
|
360
388
|
effects: [
|
|
361
|
-
"Copies
|
|
389
|
+
"Copies the repository-root CLAUDE.md/AGENTS.md into the repository-root .crouter/memory/INDEX.md with the namespace declaration and workspace front-door surfaces. Copies nested guides into nested .crouter/memory/INDEX.md files without namespace, name, or workspace-open metadata. Copies each .claude/rules/*.md into its nested store with a local name and a content read entry over the source paths (‘./’-anchored when repo-relative), or a plain read route when no paths are declared. Originals are left untouched.",
|
|
362
390
|
'Skips existing memory docs unless --overwrite is present.',
|
|
363
391
|
'With --dry-run: read-only; writes nothing.',
|
|
364
392
|
],
|
|
@@ -369,31 +397,86 @@ export const sysSyncProjectGuidanceLeaf = defineLeaf({
|
|
|
369
397
|
const overwrite = input['overwrite'] ?? false;
|
|
370
398
|
const results = [];
|
|
371
399
|
const docSeen = new Set();
|
|
400
|
+
const candidates = [];
|
|
372
401
|
for (const root of projectDocRoots(sourceArg)) {
|
|
373
402
|
for (const c of collectProjectDocCandidates(root)) {
|
|
374
403
|
if (docSeen.has(c.path))
|
|
375
404
|
continue;
|
|
376
405
|
docSeen.add(c.path);
|
|
377
|
-
|
|
378
|
-
// frontmatter) as its own skipped row — it must never abort the whole
|
|
379
|
-
// migration.
|
|
380
|
-
try {
|
|
381
|
-
const prepared = c.kind === 'agents' ? prepareAgentsCandidate(c) : prepareRuleCandidate(c);
|
|
382
|
-
results.push(convertProjectDoc(prepared, { dryRun, overwrite }));
|
|
383
|
-
}
|
|
384
|
-
catch (e) {
|
|
385
|
-
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
386
|
-
results.push({
|
|
387
|
-
source: c.path,
|
|
388
|
-
target: '',
|
|
389
|
-
name: '',
|
|
390
|
-
scope: 'project',
|
|
391
|
-
status: 'skipped',
|
|
392
|
-
reason: `unparseable source frontmatter: ${msg} — fix the source file and re-run`,
|
|
393
|
-
});
|
|
394
|
-
}
|
|
406
|
+
candidates.push(c);
|
|
395
407
|
}
|
|
396
408
|
}
|
|
409
|
+
// Resolve each repository once. Root guide bodies are collected before
|
|
410
|
+
// resolution so derivation does not depend on discovery order.
|
|
411
|
+
const rootBodies = new Map();
|
|
412
|
+
for (const c of candidates) {
|
|
413
|
+
if (c.kind !== 'agents')
|
|
414
|
+
continue;
|
|
415
|
+
try {
|
|
416
|
+
rootBodies.set(realpathOrSelf(c.sourceDir), parseFrontmatterGeneric(readText(c.path)).body);
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
rootBodies.set(realpathOrSelf(c.sourceDir), undefined);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const resolutions = new Map();
|
|
423
|
+
const candidateResolutions = new Map();
|
|
424
|
+
for (const c of candidates) {
|
|
425
|
+
const resolution = resolveRepositoryNamespace(c.sourceDir, c.walkRoot, rootBodies, resolutions);
|
|
426
|
+
candidateResolutions.set(c.path, resolution);
|
|
427
|
+
validateAuthoredNamespace(c, resolution);
|
|
428
|
+
}
|
|
429
|
+
const preparedEntries = [];
|
|
430
|
+
for (const c of candidates) {
|
|
431
|
+
const resolution = candidateResolutions.get(c.path);
|
|
432
|
+
// Isolate one malformed source file (e.g. invalid YAML in a rule's
|
|
433
|
+
// frontmatter) as its own skipped row — it must never abort the whole
|
|
434
|
+
// migration.
|
|
435
|
+
try {
|
|
436
|
+
const prepared = c.kind === 'agents'
|
|
437
|
+
? prepareAgentsCandidate(c, resolution)
|
|
438
|
+
: prepareRuleCandidate(c, resolution);
|
|
439
|
+
preparedEntries.push({ candidate: c, prepared });
|
|
440
|
+
}
|
|
441
|
+
catch (e) {
|
|
442
|
+
const msg = (e instanceof Error ? e.message : String(e)).split('\n')[0];
|
|
443
|
+
results.push({
|
|
444
|
+
source: c.path,
|
|
445
|
+
target: '',
|
|
446
|
+
name: '',
|
|
447
|
+
scope: 'project',
|
|
448
|
+
status: 'skipped',
|
|
449
|
+
reason: `unparseable source frontmatter: ${msg} — fix the source file and re-run`,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
// A rules-only source still needs one repository-root declaration for the
|
|
454
|
+
// store to mount. Nested rule directories never receive synthetic roots.
|
|
455
|
+
for (const resolution of resolutions.values()) {
|
|
456
|
+
const hasRootGuide = candidates.some((c) => c.kind === 'agents' && candidateResolutions.get(c.path)?.repositoryRoot === resolution.repositoryRoot && realpathOrSelf(c.sourceDir) === resolution.repositoryRoot);
|
|
457
|
+
if (hasRootGuide || openProjectMemoryStore(resolution.repositoryRoot).mountStatus === 'ready')
|
|
458
|
+
continue;
|
|
459
|
+
const first = candidates.find((c) => candidateResolutions.get(c.path)?.repositoryRoot === resolution.repositoryRoot);
|
|
460
|
+
if (first === undefined)
|
|
461
|
+
continue;
|
|
462
|
+
const syntheticCandidate = {
|
|
463
|
+
...first,
|
|
464
|
+
kind: 'agents',
|
|
465
|
+
sourceDir: resolution.repositoryRoot,
|
|
466
|
+
};
|
|
467
|
+
const root = prepareRootCandidate(syntheticCandidate, resolution.namespace, 'Migrated project guidance.', true);
|
|
468
|
+
results.push(convertProjectDoc(root, { dryRun, overwrite }));
|
|
469
|
+
}
|
|
470
|
+
// Repository roots are always emitted before nested stores, so a real run
|
|
471
|
+
// mounts the repository before any nested content is written.
|
|
472
|
+
const ordered = [...preparedEntries].sort((a, b) => {
|
|
473
|
+
const ar = realpathOrSelf(a.candidate.sourceDir) === candidateResolutions.get(a.candidate.path).repositoryRoot;
|
|
474
|
+
const br = realpathOrSelf(b.candidate.sourceDir) === candidateResolutions.get(b.candidate.path).repositoryRoot;
|
|
475
|
+
return Number(br) - Number(ar);
|
|
476
|
+
});
|
|
477
|
+
for (const { prepared } of ordered) {
|
|
478
|
+
results.push(convertProjectDoc(prepared, { dryRun, overwrite }));
|
|
479
|
+
}
|
|
397
480
|
const imported = results.filter((r) => r.status === 'imported').length;
|
|
398
481
|
const wouldImport = results.filter((r) => r.status === 'would-import').length;
|
|
399
482
|
const skipped = results.filter((r) => r.status === 'skipped').length;
|
|
@@ -8,9 +8,10 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
8
8
|
import { usage } from '../../core/errors.js';
|
|
9
9
|
import { parseFrontmatterGeneric } from '../../core/frontmatter.js';
|
|
10
10
|
import { pathExists, readJsonIfExists, readText, walkFiles, writeJson, writeText } from '../../core/fs-utils.js';
|
|
11
|
-
import {
|
|
11
|
+
import { composeCanonicalName } from '../../core/memory/identity.js';
|
|
12
|
+
import { findProjectScopeRoot } from '../../core/scope.js';
|
|
12
13
|
import { CRTR_DIR_NAME } from '../../types.js';
|
|
13
|
-
import { memoryFilePath,
|
|
14
|
+
import { memoryFilePath, requireMountedStore, resolveWriteSelector, serializeMemoryDoc } from '../memory/shared.js';
|
|
14
15
|
import { normalizedName, routeFromDescription, scalarString } from './sync-shared.js';
|
|
15
16
|
const LEGACY_BOOT_SKILL_MARKER_PREFIX = '<!-- crtr-boot-skill v';
|
|
16
17
|
const HOST_SKILL_FILE = 'SKILL.md';
|
|
@@ -44,7 +45,7 @@ function sourceRootsFromArg(sourceArg, scopeArg) {
|
|
|
44
45
|
return defaultSourceRoots();
|
|
45
46
|
const root = resolve(sourceArg);
|
|
46
47
|
if (scopeArg !== undefined && scopeArg !== 'user' && scopeArg !== 'project') {
|
|
47
|
-
|
|
48
|
+
throw usage(`invalid --scope: ${scopeArg} (expected user|project)`);
|
|
48
49
|
}
|
|
49
50
|
return [{ label: 'source', root, defaultScope: scopeArg ?? 'user' }];
|
|
50
51
|
}
|
|
@@ -75,11 +76,18 @@ function nameForCandidate(candidate, fm) {
|
|
|
75
76
|
const relDir = relative(candidate.root, dirname(candidate.path)).split(sep).join('/');
|
|
76
77
|
return normalizedName(relDir || basename(dirname(candidate.path)));
|
|
77
78
|
}
|
|
78
|
-
function
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
function storeResolver() {
|
|
80
|
+
const cache = new Map();
|
|
81
|
+
return (scope) => {
|
|
82
|
+
const cached = cache.get(scope);
|
|
83
|
+
if (cached !== undefined)
|
|
84
|
+
return cached;
|
|
85
|
+
const { memoryDir, store } = resolveWriteSelector({ scope });
|
|
86
|
+
requireMountedStore(store);
|
|
87
|
+
const resolved = { memoryDir, namespace: store.namespace };
|
|
88
|
+
cache.set(scope, resolved);
|
|
89
|
+
return resolved;
|
|
90
|
+
};
|
|
83
91
|
}
|
|
84
92
|
function readIgnoreState() {
|
|
85
93
|
let doc;
|
|
@@ -134,7 +142,7 @@ function rememberIgnored(prepared, state) {
|
|
|
134
142
|
function skipResult(candidate, scope, reason) {
|
|
135
143
|
return { source: candidate.path, target: '', name: '', scope, status: 'skipped', reason };
|
|
136
144
|
}
|
|
137
|
-
function prepareCandidate(candidate, scopeOverride) {
|
|
145
|
+
function prepareCandidate(candidate, stores, scopeOverride) {
|
|
138
146
|
const raw = readText(candidate.path);
|
|
139
147
|
const scope = scopeOverride ?? candidate.defaultScope;
|
|
140
148
|
if (raw.includes(LEGACY_BOOT_SKILL_MARKER_PREFIX)) {
|
|
@@ -143,11 +151,12 @@ function prepareCandidate(candidate, scopeOverride) {
|
|
|
143
151
|
const parsed = parseFrontmatterGeneric(raw);
|
|
144
152
|
const sourceFm = parsed.data ?? {};
|
|
145
153
|
const kind = sourceFm.kind === 'preference' ? 'preference' : 'knowledge';
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
154
|
+
const localName = nameForCandidate(candidate, sourceFm);
|
|
155
|
+
if (localName === '')
|
|
148
156
|
return skipResult(candidate, scope, 'could not derive memory document name');
|
|
149
|
-
const
|
|
150
|
-
const target = memoryFilePath(memoryDir,
|
|
157
|
+
const store = stores(scope);
|
|
158
|
+
const target = memoryFilePath(store.memoryDir, localName);
|
|
159
|
+
const name = composeCanonicalName(store.namespace, localName);
|
|
151
160
|
return { candidate, raw, parsed, sourceFm, kind, name, scope, target };
|
|
152
161
|
}
|
|
153
162
|
function convertPrepared(prepared, opts) {
|
|
@@ -234,7 +243,7 @@ export const sysSyncSkillsLeaf = defineLeaf({
|
|
|
234
243
|
summary: 'one-way import: host SKILL.md bundles → crouter memory docs',
|
|
235
244
|
params: [
|
|
236
245
|
{ kind: 'flag', name: 'source', type: 'string', required: false, constraint: 'A SKILL.md file, a single skill bundle dir containing SKILL.md, or a skills root to scan recursively. Default: the user/project Claude and pi skill roots that exist.' },
|
|
237
|
-
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Target memory scope for the selected sources. Default: user for user host roots and project for project host roots; an explicit value applies to every selected source.' },
|
|
246
|
+
{ kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: 'Target memory scope for the selected sources. Default: user for user host roots and project for project host roots; an explicit value applies to every selected source. `project` writes into the nearest project store walking up from cwd, which must already declare its `namespace:` — this lane imports documents into a store, never the declaration itself.' },
|
|
238
247
|
{ kind: 'flag', name: 'dry-run', type: 'bool', required: false, constraint: 'Show what would be imported without writing memory docs.' },
|
|
239
248
|
{ kind: 'flag', name: 'overwrite', type: 'bool', required: false, constraint: 'Replace an existing target memory doc. Default: skip existing docs to avoid clobbering user-authored memory.' },
|
|
240
249
|
{ kind: 'flag', name: 'ignore', type: 'bool', required: false, constraint: `Permanently ignore every selected SKILL.md source by recording it in ~/.crouter/${IGNORE_FILE}. Honors --source/--scope; with no --source, ignores every currently discovered default host skill candidate.` },
|
|
@@ -246,11 +255,12 @@ export const sysSyncSkillsLeaf = defineLeaf({
|
|
|
246
255
|
{ name: 'skipped', type: 'number', required: true, constraint: 'Count of source files skipped (existing target without --overwrite, or an unimportable SKILL.md).' },
|
|
247
256
|
{ name: 'ignored', type: 'number', required: true, constraint: 'Count of selected SKILL.md files that are permanently ignored.' },
|
|
248
257
|
{ name: 'wouldIgnore', type: 'number', required: true, constraint: 'Count of selected SKILL.md files that would be ignored under --dry-run.' },
|
|
249
|
-
{ name: 'results', type: 'object[]', required: true, constraint: 'Each: {source,target,name,scope,status,reason?}; status is one of imported, skipped, would-import, ignored, would-ignore.' },
|
|
258
|
+
{ name: 'results', type: 'object[]', required: true, constraint: 'Each: {source,target,name,scope,status,reason?}; status is one of imported, skipped, would-import, ignored, would-ignore. `name` is the full canonical name the imported doc answers at — a user import’s bundle name, a project import’s `<namespace>/<bundle name>`.' },
|
|
250
259
|
],
|
|
251
260
|
outputKind: 'object',
|
|
252
261
|
effects: [
|
|
253
|
-
'Writes memory/<name>.md in the selected crouter scope with substrate frontmatter and the SKILL.md body.',
|
|
262
|
+
'Writes memory/<bundle name>.md in the selected crouter scope with substrate frontmatter and the SKILL.md body. The file stays store-local; a project import answers at `<namespace>/<bundle name>`.',
|
|
263
|
+
'Refuses a project store that declares no namespace, naming the `crtr sys migrate --dir` (legacy store) or `crtr memory write <namespace> --dir` (empty store) remedy; it never declares one itself.',
|
|
254
264
|
'Skips existing memory docs unless --overwrite is present.',
|
|
255
265
|
'Skips marker-bearing generated crtr boot skills; host exports prune those legacy artifacts instead.',
|
|
256
266
|
`With --ignore: writes ~/.crouter/${IGNORE_FILE} and does not import the selected sources.`,
|
|
@@ -275,11 +285,12 @@ export const sysSyncSkillsLeaf = defineLeaf({
|
|
|
275
285
|
candidates.push(c);
|
|
276
286
|
}
|
|
277
287
|
}
|
|
288
|
+
const stores = storeResolver();
|
|
278
289
|
const ignoreState = readIgnoreState();
|
|
279
290
|
const results = [];
|
|
280
291
|
let changedIgnoreState = false;
|
|
281
292
|
for (const candidate of candidates) {
|
|
282
|
-
const prepared = prepareCandidate(candidate, scopeArg);
|
|
293
|
+
const prepared = prepareCandidate(candidate, stores, scopeArg);
|
|
283
294
|
if ('status' in prepared) {
|
|
284
295
|
results.push(prepared);
|
|
285
296
|
continue;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { defineLeaf } from '../../core/command.js';
|
|
2
2
|
import { updateState } from '../../core/config.js';
|
|
3
3
|
import { projectScopeRoot } from '../../core/scope.js';
|
|
4
|
-
import { selfCheck, selfUpdate, contentCheck
|
|
4
|
+
import { selfCheck, selfUpdate, contentCheck } from '../../core/self-update.js';
|
|
5
|
+
import { marketUpdate } from '../pkg/market-manage.js';
|
|
6
|
+
import { pluginUpdate } from '../pkg/plugin-manage.js';
|
|
5
7
|
import { nowIso } from '../../core/fs-utils.js';
|
|
6
8
|
import { processLocatedGeneration, readPackageVersion, runtimeManifest, selectedGeneration } from './shared.js';
|
|
7
9
|
export const sysUpdateLeaf = defineLeaf({
|
|
@@ -26,6 +28,7 @@ export const sysUpdateLeaf = defineLeaf({
|
|
|
26
28
|
effects: [
|
|
27
29
|
'--check — read-only, bounded network calls.',
|
|
28
30
|
'Default (no --check) — launches a background job; returns job handle immediately.',
|
|
31
|
+
'Content update replaces each marketplace and source-installed plugin checkout with a freshly staged revision, discarding local modifications in those directories.',
|
|
29
32
|
],
|
|
30
33
|
},
|
|
31
34
|
run: async (input) => {
|
|
@@ -74,7 +77,11 @@ export const sysUpdateLeaf = defineLeaf({
|
|
|
74
77
|
const up_to_date = updates.every((u) => u.up_to_date || u.unreachable);
|
|
75
78
|
return { updates: updates, up_to_date };
|
|
76
79
|
}
|
|
77
|
-
// Apply path
|
|
80
|
+
// Apply path. Content update owns no refresh logic of its own: it runs the
|
|
81
|
+
// same replace-in-place leaves as `pkg market update` and `pkg plugin
|
|
82
|
+
// update`, so every surface refreshes content one way. Marketplaces go
|
|
83
|
+
// first — one registered with no installed plugin is invisible to the
|
|
84
|
+
// plugin sweep.
|
|
78
85
|
try {
|
|
79
86
|
if (resolvedTarget === 'self' || resolvedTarget === 'all') {
|
|
80
87
|
selfUpdate();
|
|
@@ -88,7 +95,8 @@ export const sysUpdateLeaf = defineLeaf({
|
|
|
88
95
|
}
|
|
89
96
|
}
|
|
90
97
|
if (resolvedTarget === 'content' || resolvedTarget === 'all') {
|
|
91
|
-
|
|
98
|
+
await marketUpdate.run({});
|
|
99
|
+
await pluginUpdate.run({});
|
|
92
100
|
}
|
|
93
101
|
return { target: resolvedTarget, status: 'done' };
|
|
94
102
|
}
|