@north-light/crouter 0.3.226 → 0.3.228
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/05-kinds/design/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/design/01-orchestrator.md +4 -3
- package/dist/builtin-memory/05-kinds/design/design-contract.md +3 -6
- package/dist/builtin-memory/05-kinds/plan/00-base.md +3 -4
- package/dist/builtin-memory/05-kinds/plan/01-orchestrator.md +4 -5
- package/dist/builtin-memory/05-kinds/plan/plan-contract.md +5 -13
- package/dist/builtin-memory/design/guide.md +38 -16
- package/dist/builtin-memory/design/roadmap.md +4 -4
- package/dist/builtin-memory/insights/init.md +5 -5
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- 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-memory/plan/guide.md +53 -0
- package/dist/builtin-memory/plan/roadmap.md +10 -8
- 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/lifecycle.js +2 -2
- 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__/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/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/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 +37 -9
- package/dist/daemon/api/bridge.d.ts +35 -4
- package/dist/daemon/api/bridge.js +50 -8
- 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/package.json +5 -4
- package/runtime.lock.json +2 -2
- /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,11 +1,14 @@
|
|
|
1
|
+
// Managed worktrees: the reap half — auto-drop of clean checkouts, absent or
|
|
2
|
+
// relocated checkouts, and the atomic branch delete's re-verified predicates
|
|
3
|
+
// against commits landing mid-cleanup. Split from its land half
|
|
4
|
+
// (worktree-land.test.ts) only so the two run as two parallel files.
|
|
1
5
|
import { test, before, after, beforeEach } from 'node:test';
|
|
2
6
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { mkdtempSync,
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
7
|
+
import { mkdtempSync, rmSync, existsSync, writeFileSync, chmodSync } from 'node:fs';
|
|
5
8
|
import { tmpdir } from 'node:os';
|
|
6
9
|
import { join } from 'node:path';
|
|
7
10
|
import { gitSync } from '../../git.js';
|
|
8
|
-
import { createManagedWorktree, closeManagedWorktree
|
|
11
|
+
import { createManagedWorktree, closeManagedWorktree } from '../../worktree.js';
|
|
9
12
|
import { createNode, getNode } from '../../canvas/canvas.js';
|
|
10
13
|
import { closeDb } from '../../canvas/db.js';
|
|
11
14
|
let home;
|
|
@@ -59,341 +62,6 @@ after(() => {
|
|
|
59
62
|
rmSync(sourceDir, { recursive: true, force: true });
|
|
60
63
|
delete process.env['CRTR_HOME'];
|
|
61
64
|
});
|
|
62
|
-
// Regression for #710: close lands from a live caller, so it must preserve the
|
|
63
|
-
// caller checkout rather than starting Git's recursive removal beneath it.
|
|
64
|
-
test('closeManagedWorktree leaves the active caller checkout intact with named deferred cleanup', () => {
|
|
65
|
-
const nodeId = 'wt-close-1';
|
|
66
|
-
const wt = createManagedWorktree(sourceDir, nodeId);
|
|
67
|
-
assert.equal(wt.base_ref, 'main', 'default base must remain backward-compatible with persisted main worktrees');
|
|
68
|
-
assert.equal(wt.path, managedWorktreePath(nodeId));
|
|
69
|
-
assert.ok(existsSync(wt.path), 'worktree checkout should exist after create');
|
|
70
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
71
|
-
const result = closeManagedWorktree(nodeId);
|
|
72
|
-
assert.equal(result.worktree_removed, false);
|
|
73
|
-
assert.match(result.worktree_remove_error ?? '', /cleanup pending/);
|
|
74
|
-
assert.ok(existsSync(join(wt.path, 'README.md')), 'the caller checkout keeps its tracked files');
|
|
75
|
-
assert.ok(statSync(wt.path).isDirectory(), 'the caller cwd remains a directory');
|
|
76
|
-
const list = gitSync(['worktree', 'list', '--porcelain'], sourceDir);
|
|
77
|
-
assert.ok(list.stdout.includes(wt.path), 'Git keeps the intact caller checkout registered');
|
|
78
|
-
const meta = getNode(nodeId);
|
|
79
|
-
assert.equal(meta?.managed_worktree?.state, 'closed');
|
|
80
|
-
assert.equal(meta?.managed_worktree?.cleanup, 'pending');
|
|
81
|
-
assert.equal(meta?.cwd, wt.repo_root, 'a later revive uses the durable base checkout');
|
|
82
|
-
});
|
|
83
|
-
test('two concurrent creates from linked worktrees serialize on their shared repository', async () => {
|
|
84
|
-
const worktreeModule = new URL('../../worktree.ts', import.meta.url).href;
|
|
85
|
-
const createFrom = (cwd, nodeId) => new Promise((resolve, reject) => {
|
|
86
|
-
const script = `import { createManagedWorktree } from ${JSON.stringify(worktreeModule)}; createManagedWorktree(${JSON.stringify(cwd)}, ${JSON.stringify(nodeId)});`;
|
|
87
|
-
const child = spawn(process.execPath, ['--import', 'tsx/esm', '--input-type=module', '--eval', script], { env: { ...process.env, CRTR_HOME: home }, stdio: 'pipe' });
|
|
88
|
-
let stderr = '';
|
|
89
|
-
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
90
|
-
child.on('error', reject);
|
|
91
|
-
child.on('exit', (code) => code === 0 ? resolve() : reject(new Error(`concurrent create exited ${code}: ${stderr}`)));
|
|
92
|
-
});
|
|
93
|
-
const linkedCheckout = mkdtempSync(join(tmpdir(), 'crtr-worktree-linked-'));
|
|
94
|
-
rmSync(linkedCheckout, { recursive: true, force: true });
|
|
95
|
-
run(['worktree', 'add', '-b', 'wt-concurrent-source', linkedCheckout], sourceDir);
|
|
96
|
-
try {
|
|
97
|
-
await Promise.all([createFrom(sourceDir, 'wt-concurrent-1'), createFrom(linkedCheckout, 'wt-concurrent-2')]);
|
|
98
|
-
for (const nodeId of ['wt-concurrent-1', 'wt-concurrent-2']) {
|
|
99
|
-
const path = managedWorktreePath(nodeId);
|
|
100
|
-
assert.ok(existsSync(path), `${nodeId} checkout should exist`);
|
|
101
|
-
assert.equal(gitSync(['rev-parse', '--abbrev-ref', 'HEAD'], path).stdout.trim(), `crtr/${nodeId}`);
|
|
102
|
-
rollbackManagedWorktree({
|
|
103
|
-
state: 'open', path, branch: `crtr/${nodeId}`, repo_root: sourceDir, base_ref: 'main',
|
|
104
|
-
base_sha: gitSync(['rev-parse', 'main'], sourceDir).stdout.trim(), created: new Date().toISOString(),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
finally {
|
|
109
|
-
run(['worktree', 'remove', '--force', linkedCheckout], sourceDir);
|
|
110
|
-
run(['branch', '-D', 'wt-concurrent-source'], sourceDir);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
test('failure after branch creation removes only the failed attempt resources', () => {
|
|
114
|
-
// A file at the managed-worktree root makes `git worktree add` fail only
|
|
115
|
-
// after this attempt created its branch. No checkout can be registered.
|
|
116
|
-
mkdirSync(home, { recursive: true });
|
|
117
|
-
writeFileSync(join(home, 'worktrees'), 'blocked');
|
|
118
|
-
const nodeId = 'wt-failed-checkout';
|
|
119
|
-
assert.throws(() => createManagedWorktree(sourceDir, nodeId));
|
|
120
|
-
assert.equal(gitSync(['show-ref', '--verify', '--quiet', `refs/heads/crtr/${nodeId}`], sourceDir).status, 1, 'failed attempt branch must be removed');
|
|
121
|
-
assert.ok(!gitSync(['worktree', 'list', '--porcelain'], sourceDir).stdout.includes(`crtr/${nodeId}`), 'failed attempt must not leave a worktree registration');
|
|
122
|
-
assert.ok(!existsSync(managedWorktreePath(nodeId)), 'failed attempt must not leave a checkout directory');
|
|
123
|
-
});
|
|
124
|
-
// Landing is purely local: a commit made in the managed worktree must advance
|
|
125
|
-
// the source repo's LOCAL main to that commit, and origin/main must stay put
|
|
126
|
-
// (close never pushes to origin).
|
|
127
|
-
test('closeManagedWorktree fast-forwards local main to the worktree tip without touching origin', () => {
|
|
128
|
-
const nodeId = 'wt-close-2';
|
|
129
|
-
const originMainBefore = gitSync(['rev-parse', 'origin/main'], sourceDir).stdout.trim();
|
|
130
|
-
const wt = createManagedWorktree(sourceDir, nodeId);
|
|
131
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
132
|
-
// Commit real work inside the managed worktree.
|
|
133
|
-
writeFileSync(join(wt.path, 'feature.txt'), 'work\n');
|
|
134
|
-
run(['add', '.'], wt.path);
|
|
135
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'feature'], wt.path);
|
|
136
|
-
const workSha = gitSync(['rev-parse', 'HEAD'], wt.path).stdout.trim();
|
|
137
|
-
const result = closeManagedWorktree(nodeId);
|
|
138
|
-
assert.equal(result.landed_sha, workSha, 'landed_sha should be the committed work');
|
|
139
|
-
// Local main in the source (primary) checkout advanced to the work.
|
|
140
|
-
const localMainAfter = gitSync(['rev-parse', 'main'], sourceDir).stdout.trim();
|
|
141
|
-
assert.equal(localMainAfter, workSha, 'local main must fast-forward to the worktree tip');
|
|
142
|
-
// origin/main is untouched — nothing was pushed.
|
|
143
|
-
const originMainAfter = gitSync(['rev-parse', 'origin/main'], sourceDir).stdout.trim();
|
|
144
|
-
assert.equal(originMainAfter, originMainBefore, 'origin/main must not move — landing is local-only');
|
|
145
|
-
});
|
|
146
|
-
test('custom local base is recorded and fast-forwarded without touching main or origin', () => {
|
|
147
|
-
const nodeId = 'wt-custom-base';
|
|
148
|
-
run(['branch', 'feature/custom-base'], sourceDir);
|
|
149
|
-
const baseSha = gitSync(['rev-parse', 'feature/custom-base'], sourceDir).stdout.trim();
|
|
150
|
-
const mainBefore = gitSync(['rev-parse', 'main'], sourceDir).stdout.trim();
|
|
151
|
-
const originMainBefore = gitSync(['rev-parse', 'origin/main'], sourceDir).stdout.trim();
|
|
152
|
-
const wt = createManagedWorktree(sourceDir, nodeId, 'feature/custom-base');
|
|
153
|
-
assert.equal(wt.base_ref, 'feature/custom-base');
|
|
154
|
-
assert.equal(wt.base_sha, baseSha);
|
|
155
|
-
assert.equal(gitSync(['rev-parse', 'HEAD'], wt.path).stdout.trim(), baseSha, 'dedicated branch starts at the custom base');
|
|
156
|
-
assert.equal(gitSync(['rev-parse', '--abbrev-ref', 'HEAD'], wt.path).stdout.trim(), `crtr/${nodeId}`, 'the worktree checks out its own dedicated crtr/<node-id> branch, not the base branch');
|
|
157
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
158
|
-
writeFileSync(join(wt.path, 'custom-base.txt'), 'work\n');
|
|
159
|
-
run(['add', '.'], wt.path);
|
|
160
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'custom base work'], wt.path);
|
|
161
|
-
// The base can advance while the child works. Close must rebase onto that
|
|
162
|
-
// new tip before atomically advancing the base to the child work.
|
|
163
|
-
run(['checkout', 'feature/custom-base'], sourceDir);
|
|
164
|
-
writeFileSync(join(sourceDir, 'base-advance.txt'), 'base work\n');
|
|
165
|
-
run(['add', '.'], sourceDir);
|
|
166
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'base advance'], sourceDir);
|
|
167
|
-
const advancedBaseSha = gitSync(['rev-parse', 'HEAD'], sourceDir).stdout.trim();
|
|
168
|
-
run(['checkout', 'main'], sourceDir);
|
|
169
|
-
const originalWorkSha = gitSync(['rev-parse', 'HEAD'], wt.path).stdout.trim();
|
|
170
|
-
const result = closeManagedWorktree(nodeId);
|
|
171
|
-
assert.equal(gitSync(['merge-base', '--is-ancestor', originalWorkSha, result.landed_sha], sourceDir).status, 1, 'rebasing onto an advanced base must rewrite the child commit');
|
|
172
|
-
assert.equal(gitSync(['merge-base', '--is-ancestor', advancedBaseSha, result.landed_sha], sourceDir).status, 0, 'landed work must be rebased onto the latest base tip');
|
|
173
|
-
assert.equal(gitSync(['rev-parse', 'feature/custom-base'], sourceDir).stdout.trim(), result.landed_sha, 'custom base must fast-forward to the worktree tip');
|
|
174
|
-
assert.equal(gitSync(['rev-parse', 'main'], sourceDir).stdout.trim(), mainBefore, 'main must remain untouched');
|
|
175
|
-
assert.equal(gitSync(['rev-parse', 'origin/main'], sourceDir).stdout.trim(), originMainBefore, 'origin/main must remain untouched');
|
|
176
|
-
});
|
|
177
|
-
test('refuses a custom-base close when its unchecked base advances after rebase', () => {
|
|
178
|
-
const nodeId = 'wt-custom-base-cas-race';
|
|
179
|
-
const base = 'feature/cas-race-base';
|
|
180
|
-
run(['branch', base], sourceDir);
|
|
181
|
-
const baseSha = gitSync(['rev-parse', base], sourceDir).stdout.trim();
|
|
182
|
-
const wt = createManagedWorktree(sourceDir, nodeId, base);
|
|
183
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
184
|
-
writeFileSync(join(wt.path, 'cas-child.txt'), 'child work\n');
|
|
185
|
-
run(['add', '.'], wt.path);
|
|
186
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'cas child work'], wt.path);
|
|
187
|
-
// Pre-create the other writer's commit, then reset the base so the rebase
|
|
188
|
-
// begins from baseSha. A test-only git wrapper advances it when close calls
|
|
189
|
-
// its final update-ref CAS, immediately before the real git receives that
|
|
190
|
-
// CAS request.
|
|
191
|
-
run(['checkout', base], sourceDir);
|
|
192
|
-
writeFileSync(join(sourceDir, 'cas-base.txt'), 'base work\n');
|
|
193
|
-
run(['add', '.'], sourceDir);
|
|
194
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'cas base work'], sourceDir);
|
|
195
|
-
const advancedBaseSha = gitSync(['rev-parse', 'HEAD'], sourceDir).stdout.trim();
|
|
196
|
-
run(['checkout', 'main'], sourceDir);
|
|
197
|
-
run(['update-ref', `refs/heads/${base}`, baseSha], sourceDir);
|
|
198
|
-
const wrapperDir = mkdtempSync(join(tmpdir(), 'crtr-git-wrapper-'));
|
|
199
|
-
const wrapperPath = join(wrapperDir, 'git');
|
|
200
|
-
const originalPath = process.env['PATH'] ?? '';
|
|
201
|
-
writeFileSync(wrapperPath, `#!/bin/sh
|
|
202
|
-
if [ "$1" = "update-ref" ] && [ "$2" = "refs/heads/${base}" ] && [ "$4" = "${baseSha}" ]; then
|
|
203
|
-
PATH="$CRTR_TEST_REAL_PATH" git -C "${sourceDir}" update-ref "refs/heads/${base}" "${advancedBaseSha}"
|
|
204
|
-
fi
|
|
205
|
-
PATH="$CRTR_TEST_REAL_PATH" exec git "$@"
|
|
206
|
-
`);
|
|
207
|
-
chmodSync(wrapperPath, 0o755);
|
|
208
|
-
process.env['CRTR_TEST_REAL_PATH'] = originalPath;
|
|
209
|
-
process.env['PATH'] = `${wrapperDir}:${originalPath}`;
|
|
210
|
-
try {
|
|
211
|
-
assert.throws(() => closeManagedWorktree(nodeId), (error) => error instanceof Error && 'code' in error && error.code === 'land_base_failed');
|
|
212
|
-
}
|
|
213
|
-
finally {
|
|
214
|
-
process.env['PATH'] = originalPath;
|
|
215
|
-
delete process.env['CRTR_TEST_REAL_PATH'];
|
|
216
|
-
rmSync(wrapperDir, { recursive: true, force: true });
|
|
217
|
-
}
|
|
218
|
-
assert.equal(gitSync(['rev-parse', base], sourceDir).stdout.trim(), advancedBaseSha, 'CAS failure must preserve the concurrent base advance');
|
|
219
|
-
assert.equal(getNode(nodeId)?.managed_worktree?.state, 'open', 'failed landing must leave the worktree open for retry or human resolution');
|
|
220
|
-
});
|
|
221
|
-
test('rebases onto the captured SHA when an unchecked base advances before rebase starts', () => {
|
|
222
|
-
const nodeId = 'wt-custom-base-rebase-race';
|
|
223
|
-
const base = 'feature/rebase-race-base';
|
|
224
|
-
run(['branch', base], sourceDir);
|
|
225
|
-
const baseSha = gitSync(['rev-parse', base], sourceDir).stdout.trim();
|
|
226
|
-
const wt = createManagedWorktree(sourceDir, nodeId, base);
|
|
227
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
228
|
-
writeFileSync(join(wt.path, 'rebase-race-child.txt'), 'child work\n');
|
|
229
|
-
run(['add', '.'], wt.path);
|
|
230
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'rebase race child work'], wt.path);
|
|
231
|
-
// Prepare another writer's commit, then move the unchecked base back to the
|
|
232
|
-
// SHA close will capture. The wrapper advances the base immediately before
|
|
233
|
-
// the real rebase starts. Close must still rebase onto its captured SHA, not
|
|
234
|
-
// resolve the now-advanced symbolic branch, and the stale landing CAS fails.
|
|
235
|
-
run(['checkout', base], sourceDir);
|
|
236
|
-
writeFileSync(join(sourceDir, 'rebase-race-base.txt'), 'base work\n');
|
|
237
|
-
run(['add', '.'], sourceDir);
|
|
238
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'rebase race base work'], sourceDir);
|
|
239
|
-
const advancedBaseSha = gitSync(['rev-parse', 'HEAD'], sourceDir).stdout.trim();
|
|
240
|
-
run(['checkout', 'main'], sourceDir);
|
|
241
|
-
run(['update-ref', `refs/heads/${base}`, baseSha], sourceDir);
|
|
242
|
-
const wrapperDir = mkdtempSync(join(tmpdir(), 'crtr-git-wrapper-'));
|
|
243
|
-
const wrapperPath = join(wrapperDir, 'git');
|
|
244
|
-
const originalPath = process.env['PATH'] ?? '';
|
|
245
|
-
writeFileSync(wrapperPath, `#!/bin/sh
|
|
246
|
-
if [ "$1" = "rebase" ] && [ "$2" = "${baseSha}" ]; then
|
|
247
|
-
PATH="$CRTR_TEST_REAL_PATH" git -C "${sourceDir}" update-ref "refs/heads/${base}" "${advancedBaseSha}"
|
|
248
|
-
fi
|
|
249
|
-
PATH="$CRTR_TEST_REAL_PATH" exec git "$@"
|
|
250
|
-
`);
|
|
251
|
-
chmodSync(wrapperPath, 0o755);
|
|
252
|
-
process.env['CRTR_TEST_REAL_PATH'] = originalPath;
|
|
253
|
-
process.env['PATH'] = `${wrapperDir}:${originalPath}`;
|
|
254
|
-
try {
|
|
255
|
-
assert.throws(() => closeManagedWorktree(nodeId), (error) => error instanceof Error && 'code' in error && error.code === 'land_base_failed');
|
|
256
|
-
}
|
|
257
|
-
finally {
|
|
258
|
-
process.env['PATH'] = originalPath;
|
|
259
|
-
delete process.env['CRTR_TEST_REAL_PATH'];
|
|
260
|
-
rmSync(wrapperDir, { recursive: true, force: true });
|
|
261
|
-
}
|
|
262
|
-
const rebasedChildSha = gitSync(['rev-parse', 'HEAD'], wt.path).stdout.trim();
|
|
263
|
-
assert.equal(gitSync(['rev-parse', base], sourceDir).stdout.trim(), advancedBaseSha, 'CAS failure must preserve the advance that raced with rebase');
|
|
264
|
-
assert.equal(gitSync(['merge-base', '--is-ancestor', advancedBaseSha, rebasedChildSha], sourceDir).status, 1, 'the child must not rebase onto a symbolic base advance after baseSha was captured');
|
|
265
|
-
assert.equal(getNode(nodeId)?.managed_worktree?.state, 'open', 'failed landing must leave the worktree open for retry or human resolution');
|
|
266
|
-
});
|
|
267
|
-
test('refuses to land when a checked-out base is reset after its SHA is captured', () => {
|
|
268
|
-
const nodeId = 'wt-custom-base-checked-out-reset-race';
|
|
269
|
-
const base = 'feature/checked-out-reset-race-base';
|
|
270
|
-
run(['checkout', '-b', base], sourceDir);
|
|
271
|
-
const resetSha = gitSync(['rev-parse', 'HEAD'], sourceDir).stdout.trim();
|
|
272
|
-
writeFileSync(join(sourceDir, 'checked-out-reset-base.txt'), 'base work\n');
|
|
273
|
-
run(['add', '.'], sourceDir);
|
|
274
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'checked out reset base work'], sourceDir);
|
|
275
|
-
const baseSha = gitSync(['rev-parse', 'HEAD'], sourceDir).stdout.trim();
|
|
276
|
-
const wt = createManagedWorktree(sourceDir, nodeId, base);
|
|
277
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
278
|
-
writeFileSync(join(wt.path, 'checked-out-reset-child.txt'), 'child work\n');
|
|
279
|
-
run(['add', '.'], wt.path);
|
|
280
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'checked out reset child work'], wt.path);
|
|
281
|
-
// Reset the checked-out base backward immediately before the child rebases.
|
|
282
|
-
// The real rebase still targets captured baseSha, so only the explicit HEAD
|
|
283
|
-
// equality check can prevent an ff-only merge from restoring discarded work.
|
|
284
|
-
const wrapperDir = mkdtempSync(join(tmpdir(), 'crtr-git-wrapper-'));
|
|
285
|
-
const wrapperPath = join(wrapperDir, 'git');
|
|
286
|
-
const originalPath = process.env['PATH'] ?? '';
|
|
287
|
-
writeFileSync(wrapperPath, `#!/bin/sh
|
|
288
|
-
if [ "$1" = "rebase" ] && [ "$2" = "${baseSha}" ]; then
|
|
289
|
-
PATH="$CRTR_TEST_REAL_PATH" git -C "${sourceDir}" reset --hard "${resetSha}"
|
|
290
|
-
fi
|
|
291
|
-
PATH="$CRTR_TEST_REAL_PATH" exec git "$@"
|
|
292
|
-
`);
|
|
293
|
-
chmodSync(wrapperPath, 0o755);
|
|
294
|
-
process.env['CRTR_TEST_REAL_PATH'] = originalPath;
|
|
295
|
-
process.env['PATH'] = `${wrapperDir}:${originalPath}`;
|
|
296
|
-
try {
|
|
297
|
-
assert.throws(() => closeManagedWorktree(nodeId), (error) => error instanceof Error && 'code' in error && error.code === 'land_base_failed');
|
|
298
|
-
}
|
|
299
|
-
finally {
|
|
300
|
-
process.env['PATH'] = originalPath;
|
|
301
|
-
delete process.env['CRTR_TEST_REAL_PATH'];
|
|
302
|
-
rmSync(wrapperDir, { recursive: true, force: true });
|
|
303
|
-
}
|
|
304
|
-
assert.equal(gitSync(['rev-parse', 'HEAD'], sourceDir).stdout.trim(), resetSha, 'failed landing must leave the checked-out base at the concurrent reset SHA');
|
|
305
|
-
assert.equal(gitSync(['rev-parse', base], sourceDir).stdout.trim(), resetSha, 'failed landing must not restore discarded base commits');
|
|
306
|
-
assert.equal(getNode(nodeId)?.managed_worktree?.state, 'open', 'failed landing must leave the worktree open for retry or human resolution');
|
|
307
|
-
run(['checkout', 'main'], sourceDir);
|
|
308
|
-
});
|
|
309
|
-
test('close fast-forwards a custom base in its checked-out worktree', () => {
|
|
310
|
-
const nodeId = 'wt-custom-base-checked-out';
|
|
311
|
-
run(['checkout', '-b', 'feature/checked-out-base'], sourceDir);
|
|
312
|
-
const wt = createManagedWorktree(sourceDir, nodeId, 'feature/checked-out-base');
|
|
313
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
314
|
-
writeFileSync(join(wt.path, 'checked-out-base.txt'), 'work\n');
|
|
315
|
-
run(['add', '.'], wt.path);
|
|
316
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'checked out base work'], wt.path);
|
|
317
|
-
const workSha = gitSync(['rev-parse', 'HEAD'], wt.path).stdout.trim();
|
|
318
|
-
closeManagedWorktree(nodeId);
|
|
319
|
-
assert.equal(gitSync(['rev-parse', 'feature/checked-out-base'], sourceDir).stdout.trim(), workSha);
|
|
320
|
-
assert.equal(gitSync(['rev-parse', 'HEAD'], sourceDir).stdout.trim(), workSha, 'checked-out base worktree must advance with its ref');
|
|
321
|
-
run(['checkout', 'main'], sourceDir);
|
|
322
|
-
});
|
|
323
|
-
test('rejects remote and nonexistent worktree bases before creating any resources', () => {
|
|
324
|
-
for (const [nodeId, base] of [['wt-remote-base', 'origin/main'], ['wt-bogus-base', 'feature/does-not-exist']]) {
|
|
325
|
-
assert.throws(() => createManagedWorktree(sourceDir, nodeId, base), (error) => error instanceof Error
|
|
326
|
-
&& 'code' in error
|
|
327
|
-
&& error.code === 'missing_base_branch'
|
|
328
|
-
&& error.message.includes(base));
|
|
329
|
-
assert.ok(!existsSync(managedWorktreePath(nodeId)), `${base} must not leave a worktree directory`);
|
|
330
|
-
assert.equal(gitSync(['show-ref', '--verify', '--quiet', `refs/heads/crtr/${nodeId}`], sourceDir).status, 1, `${base} must not leave a dedicated branch`);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
test('refuses to close when the recorded base was deleted and leaves the worktree open', () => {
|
|
334
|
-
const nodeId = 'wt-deleted-base';
|
|
335
|
-
run(['branch', 'feature/deleted-base'], sourceDir);
|
|
336
|
-
const mainBefore = gitSync(['rev-parse', 'main'], sourceDir).stdout.trim();
|
|
337
|
-
const wt = createManagedWorktree(sourceDir, nodeId, 'feature/deleted-base');
|
|
338
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
339
|
-
writeFileSync(join(wt.path, 'deleted-base.txt'), 'work\n');
|
|
340
|
-
run(['add', '.'], wt.path);
|
|
341
|
-
run(['-c', 'user.email=test@example.com', '-c', 'user.name=Test', 'commit', '-m', 'deleted base work'], wt.path);
|
|
342
|
-
run(['branch', '-D', 'feature/deleted-base'], sourceDir);
|
|
343
|
-
assert.throws(() => closeManagedWorktree(nodeId), (error) => error instanceof Error && 'code' in error && error.code === 'missing_base_branch');
|
|
344
|
-
assert.equal(getNode(nodeId)?.managed_worktree?.state, 'open', 'a missing base must not silently close or retarget the worktree');
|
|
345
|
-
assert.ok(existsSync(wt.path), 'the worktree must remain available for a human decision');
|
|
346
|
-
assert.equal(gitSync(['rev-parse', 'main'], sourceDir).stdout.trim(), mainBefore, 'missing base must never auto-land onto main');
|
|
347
|
-
});
|
|
348
|
-
// Regression for #330/#331: crtr's own untracked `.crouter/config.json` /
|
|
349
|
-
// `state.json` bookkeeping (written by ordinary project-scope init) must
|
|
350
|
-
// never block `close`'s dirty check — Fix A seeds `.git/info/exclude` for
|
|
351
|
-
// exactly those two filenames at worktree-creation time.
|
|
352
|
-
test('close ignores crtr\'s own untracked .crouter/config.json and state.json bookkeeping', () => {
|
|
353
|
-
const nodeId = 'wt-crtr-bookkeeping';
|
|
354
|
-
const wt = createManagedWorktree(sourceDir, nodeId);
|
|
355
|
-
createNode(node(nodeId, { cwd: wt.path, managed_worktree: wt }));
|
|
356
|
-
mkdirSync(join(wt.path, '.crouter'), { recursive: true });
|
|
357
|
-
writeFileSync(join(wt.path, '.crouter', 'config.json'), '{}\n');
|
|
358
|
-
writeFileSync(join(wt.path, '.crouter', 'state.json'), '{}\n');
|
|
359
|
-
// The seeded exclude is on the SHARED common git dir — assert git itself
|
|
360
|
-
// reports a clean tree, not just that close happens not to notice.
|
|
361
|
-
assert.equal(gitSync(['status', '--porcelain'], wt.path).stdout.trim(), '', 'git status must already be clean with only crtr bookkeeping present');
|
|
362
|
-
const result = closeManagedWorktree(nodeId);
|
|
363
|
-
assert.equal(result.worktree_removed, false);
|
|
364
|
-
assert.match(result.worktree_remove_error ?? '', /cleanup pending/);
|
|
365
|
-
});
|
|
366
|
-
// The exclude must be narrow: it must NEVER hide real, committable node work
|
|
367
|
-
// that merely happens to live under `.crouter/` — this mirrors crouter's OWN
|
|
368
|
-
// repo, which tracks `.crouter/memory/`. Set up a dedicated repo (not the
|
|
369
|
-
// shared sourceDir, which never tracks `.crouter/`) that already tracks a
|
|
370
|
-
// memory doc, then prove BOTH a modification to it and a brand-new untracked
|
|
371
|
-
// doc beside it still block close exactly as before Fix A.
|
|
372
|
-
test('close still blocks on real .crouter/memory/ work — a tracked-file edit and a new untracked doc', () => {
|
|
373
|
-
const memoryRepo = mkdtempSync(join(tmpdir(), 'crtr-worktree-memoryrepo-'));
|
|
374
|
-
run(['init', '-b', 'main'], memoryRepo);
|
|
375
|
-
run(['config', 'user.email', 'test@example.com'], memoryRepo);
|
|
376
|
-
run(['config', 'user.name', 'Test'], memoryRepo);
|
|
377
|
-
mkdirSync(join(memoryRepo, '.crouter', 'memory'), { recursive: true });
|
|
378
|
-
writeFileSync(join(memoryRepo, '.crouter', 'memory', 'tracked.md'), 'tracked memory doc\n');
|
|
379
|
-
run(['add', '.'], memoryRepo);
|
|
380
|
-
run(['commit', '-m', 'init with tracked memory'], memoryRepo);
|
|
381
|
-
const editNodeId = 'wt-real-crouter-memory-edit';
|
|
382
|
-
const editWt = createManagedWorktree(memoryRepo, editNodeId);
|
|
383
|
-
createNode(node(editNodeId, { cwd: editWt.path, managed_worktree: editWt }));
|
|
384
|
-
writeFileSync(join(editWt.path, '.crouter', 'memory', 'tracked.md'), 'edited tracked memory doc\n');
|
|
385
|
-
const editStatus = gitSync(['status', '--porcelain'], editWt.path).stdout;
|
|
386
|
-
assert.ok(editStatus.includes('M .crouter/memory/tracked.md'), 'a tracked .crouter/memory/ edit must still show as modified — exclude can never hide a tracked-file change');
|
|
387
|
-
assert.throws(() => closeManagedWorktree(editNodeId), (error) => error instanceof Error && 'code' in error && error.code === 'dirty_worktree');
|
|
388
|
-
const newDocNodeId = 'wt-real-crouter-memory-new-doc';
|
|
389
|
-
const newDocWt = createManagedWorktree(memoryRepo, newDocNodeId);
|
|
390
|
-
createNode(node(newDocNodeId, { cwd: newDocWt.path, managed_worktree: newDocWt }));
|
|
391
|
-
writeFileSync(join(newDocWt.path, '.crouter', 'memory', 'new-doc.md'), 'a legitimate new memory doc\n');
|
|
392
|
-
const newDocStatus = gitSync(['status', '--porcelain'], newDocWt.path).stdout;
|
|
393
|
-
assert.ok(newDocStatus.includes('.crouter/memory/new-doc.md'), 'a brand-new untracked doc under an already-tracked .crouter/memory/ must still show as dirty');
|
|
394
|
-
assert.throws(() => closeManagedWorktree(newDocNodeId), (error) => error instanceof Error && 'code' in error && error.code === 'dirty_worktree');
|
|
395
|
-
rmSync(memoryRepo, { recursive: true, force: true });
|
|
396
|
-
});
|
|
397
65
|
// Fix B (#327/#333): a managed worktree with zero commits ahead of its base
|
|
398
66
|
// and nothing uncommitted has nothing to land — push-final's auto-drop path
|
|
399
67
|
// (autoDropCleanManagedWorktree) must close it without requiring an explicit
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
// Run with: node --import tsx/esm --test src/core/__tests__/kickoff.test.ts
|
|
2
2
|
//
|
|
3
3
|
// The fresh-revive kickoff split (runtime/kickoff.ts). drainBearings reads the
|
|
4
|
-
// durable yield note and
|
|
5
|
-
//
|
|
6
|
-
// the note handoff through clearYieldMessage.
|
|
4
|
+
// durable yield note, feed, and pending persona framing; buildReviveKickoff is a
|
|
5
|
+
// pure assembler. session_start commits the note and persona handoffs.
|
|
7
6
|
import { test, before, after, beforeEach } from 'node:test';
|
|
8
7
|
import assert from 'node:assert/strict';
|
|
9
8
|
import { mkdtempSync, rmSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
10
9
|
import { tmpdir } from 'node:os';
|
|
11
10
|
import { join } from 'node:path';
|
|
12
|
-
import { createNode, subscribe } from '../canvas/canvas.js';
|
|
11
|
+
import { createNode, getNode, subscribe } from '../canvas/canvas.js';
|
|
13
12
|
import { reportsDir } from '../canvas/paths.js';
|
|
14
13
|
import { closeDb } from '../canvas/db.js';
|
|
15
14
|
import { buildReviveKickoff, clearYieldMessage, drainBearings, readYieldMessage, writeYieldMessage, yieldMessagePath, } from '../runtime/kickoff.js';
|
|
@@ -17,7 +16,7 @@ import { appendInbox, readCursor } from '../feed/inbox.js';
|
|
|
17
16
|
import { appendSituationalContext } from '../runtime/situational-context.js';
|
|
18
17
|
import { buildContextBearings as buildContextIntro } from '../runtime/bearings.js';
|
|
19
18
|
let home;
|
|
20
|
-
function node(id) {
|
|
19
|
+
function node(id, over = {}) {
|
|
21
20
|
return {
|
|
22
21
|
node_id: id,
|
|
23
22
|
name: id,
|
|
@@ -27,6 +26,7 @@ function node(id) {
|
|
|
27
26
|
mode: 'base',
|
|
28
27
|
lifecycle: 'terminal',
|
|
29
28
|
status: 'active',
|
|
29
|
+
...over,
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
before(() => {
|
|
@@ -62,6 +62,17 @@ test('drainBearings preserves the yield note until explicit boot confirmation',
|
|
|
62
62
|
assert.equal(readYieldMessage(id), null, 'confirmation clear is idempotent');
|
|
63
63
|
assert.equal(existsSync(yieldMessagePath(id)), false, 'confirmed handoff removes the note');
|
|
64
64
|
});
|
|
65
|
+
test('drainBearings leaves persona drift pending until the replacement context exists', () => {
|
|
66
|
+
const oldPersona = { kind: 'developer', mode: 'base', lifecycle: 'terminal' };
|
|
67
|
+
const meta = createNode(node('pending-persona', {
|
|
68
|
+
kind: 'developer',
|
|
69
|
+
mode: 'orchestrator',
|
|
70
|
+
persona_ack: oldPersona,
|
|
71
|
+
}));
|
|
72
|
+
const bearings = drainBearings(meta);
|
|
73
|
+
assert.match(bearings.driftGuidance ?? '', /ORCHESTRATOR/);
|
|
74
|
+
assert.deepEqual(getNode(meta.node_id)?.persona_ack, oldPersona, 'pre-session_start launch work cannot acknowledge the drift');
|
|
75
|
+
});
|
|
65
76
|
test('the feed block frames awaiting workers as alive + auto-waking, so a fresh revive has no reason to poll', () => {
|
|
66
77
|
const parent = createNode(node('p1'));
|
|
67
78
|
const child = createNode(node('c1')); // status 'active' by default
|