@astrosheep/keiyaku 4.5.7 → 4.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +3 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +12 -11
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +33 -12
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +35 -96
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +28 -49
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +21 -15
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +148 -89
- package/build/src/akuma/abort.d.ts +2 -1
- package/build/src/akuma/abort.js +6 -23
- package/build/src/akuma/akuma-handle.js +4 -2
- package/build/src/akuma/akuma-observe.d.ts +2 -0
- package/build/src/akuma/akuma-observe.js +1 -0
- package/build/src/akuma/akuma-product.js +9 -7
- package/build/src/akuma/akuma.d.ts +392 -9
- package/build/src/akuma/akuma.js +31 -1
- package/build/src/akuma/allowed.d.ts +1 -1
- package/build/src/akuma/allowed.js +1 -0
- package/build/src/akuma/archetype.js +1 -1
- package/build/src/akuma/body.d.ts +8 -5
- package/build/src/akuma/body.js +12 -5
- package/build/src/akuma/call-request.d.ts +80 -0
- package/build/src/akuma/call-request.js +211 -0
- package/build/src/akuma/heart/facts.d.ts +16 -78
- package/build/src/akuma/heart/facts.js +11 -0
- package/build/src/akuma/heart/index.d.ts +3 -2
- package/build/src/akuma/heart/index.js +4 -1
- package/build/src/akuma/heart/request-authority.d.ts +5 -4
- package/build/src/akuma/heart/request-authority.js +20 -34
- package/build/src/akuma/heart/request-rows.js +12 -23
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +5 -13
- package/build/src/akuma/index.d.ts +1 -1
- package/build/src/akuma/index.js +1 -1
- package/build/src/akuma/projection-read.d.ts +1 -0
- package/build/src/akuma/projection-read.js +8 -1
- package/build/src/akuma/projection.d.ts +907 -137
- package/build/src/akuma/projection.js +193 -9
- package/build/src/akuma/provider.d.ts +30 -3
- package/build/src/akuma/provider.js +111 -0
- package/build/src/akuma/providers/acp/core.d.ts +2 -2
- package/build/src/akuma/providers/acp/core.js +13 -6
- package/build/src/akuma/providers/acp/index.js +5 -4
- package/build/src/akuma/providers/claude/events.d.ts +2 -2
- package/build/src/akuma/providers/claude/events.js +16 -3
- package/build/src/akuma/providers/claude/index.d.ts +3 -3
- package/build/src/akuma/providers/claude/index.js +16 -5
- package/build/src/akuma/providers/codex-app-server/events.d.ts +2 -2
- package/build/src/akuma/providers/codex-app-server/events.js +25 -4
- package/build/src/akuma/providers/codex-app-server/index.js +18 -6
- package/build/src/akuma/providers/grok-build/index.js +5 -4
- package/build/src/akuma/providers/opencode-sdk/index.js +66 -25
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +3 -2
- package/build/src/akuma/providers/opencode-sdk/session.js +28 -11
- package/build/src/akuma/providers/pi/index.js +72 -58
- package/build/src/akuma/request-lifecycle.d.ts +4 -10
- package/build/src/akuma/request-lifecycle.js +7 -5
- package/build/src/akuma/request-rendezvous.d.ts +26 -0
- package/build/src/akuma/request-rendezvous.js +100 -0
- package/build/src/akuma/request-serve.d.ts +6 -66
- package/build/src/akuma/request-serve.js +83 -210
- package/build/src/akuma/request-wire.d.ts +109 -115
- package/build/src/akuma/request-wire.js +63 -318
- package/build/src/akuma/requests.d.ts +19 -47
- package/build/src/akuma/requests.js +51 -107
- package/build/src/akuma/turn-drive.d.ts +5 -3
- package/build/src/akuma/turn-drive.js +39 -11
- package/build/src/akuma-body.d.ts +7 -2
- package/build/src/akuma-body.js +68 -46
- package/build/src/body/amend.js +18 -19
- package/build/src/cli/accepted.d.ts +1 -1
- package/build/src/cli/accepted.js +2 -2
- package/build/src/cli/commands/akuma-invoke.d.ts +2 -0
- package/build/src/cli/commands/akuma-invoke.js +17 -9
- package/build/src/cli/commands/akuma.js +8 -8
- package/build/src/cli/commands/contract-invoke.d.ts +3 -2
- package/build/src/cli/commands/contract-invoke.js +42 -30
- package/build/src/cli/commands/contract.d.ts +16 -20
- package/build/src/cli/commands/contract.js +58 -25
- package/build/src/cli/commands/install.d.ts +2 -1
- package/build/src/cli/commands/install.js +3 -2
- package/build/src/cli/commands/task-invoke.d.ts +3 -0
- package/build/src/cli/commands/task-invoke.js +23 -140
- package/build/src/cli/commands/task.js +54 -20
- package/build/src/cli/invoke.js +39 -25
- package/build/src/cli/main.js +2 -1
- package/build/src/cli/parse.d.ts +2 -2
- package/build/src/cli/parse.js +56 -27
- package/build/src/cli/render/akuma-activity.d.ts +3 -0
- package/build/src/cli/render/akuma-activity.js +45 -29
- package/build/src/cli/render/akuma-tool.d.ts +5 -1
- package/build/src/cli/render/akuma.js +4 -8
- package/build/src/cli/render/audit.js +1 -1
- package/build/src/cli/render/catalog.js +80 -21
- package/build/src/cli/render/contract-observation.d.ts +2 -0
- package/build/src/cli/render/contract-observation.js +11 -0
- package/build/src/cli/render/contract.js +35 -62
- package/build/src/cli/render/kanshi-akuma.d.ts +8 -0
- package/build/src/cli/render/kanshi-akuma.js +132 -0
- package/build/src/cli/render/kanshi.js +131 -213
- package/build/src/cli/render/receipt.d.ts +1 -2
- package/build/src/cli/render/receipt.js +76 -26
- package/build/src/cli/render/refusal.js +2 -10
- package/build/src/cli/render/region.js +5 -2
- package/build/src/cli/render/settings.d.ts +1 -1
- package/build/src/cli/render/settings.js +27 -8
- package/build/src/cli/render/task.js +22 -5
- package/build/src/cli/render/terminal.d.ts +32 -1
- package/build/src/cli/render/terminal.js +77 -1
- package/build/src/cli/result.d.ts +3 -3
- package/build/src/cli/runtime.js +4 -2
- package/build/src/cli/selectors.d.ts +2 -1
- package/build/src/cli/selectors.js +4 -3
- package/build/src/cli/square-edge.js +120 -36
- package/build/src/cli/usage.js +2 -1
- package/build/src/contract-guidance.d.ts +5 -0
- package/build/src/contract-guidance.js +121 -0
- package/build/src/contract-worktree.d.ts +0 -1
- package/build/src/contract-worktree.js +32 -83
- package/build/src/coordination/sqlite-transaction-lock.d.ts +1 -0
- package/build/src/coordination/sqlite-transaction-lock.js +5 -0
- package/build/src/core/facts/codec.d.ts +2 -0
- package/build/src/core/facts/codec.js +4 -0
- package/build/src/core/verbs/placement.d.ts +6 -1
- package/build/src/core/verbs/placement.js +5 -3
- package/build/src/dispatch/index.d.ts +0 -2
- package/build/src/dispatch/index.js +54 -55
- package/build/src/git/admission.js +2 -2
- package/build/src/git/hooks.d.ts +9 -15
- package/build/src/git/hooks.js +23 -317
- package/build/src/git/nuke.d.ts +2 -1
- package/build/src/git/nuke.js +39 -21
- package/build/src/git/private-state-seat.d.ts +12 -0
- package/build/src/git/private-state-seat.js +39 -0
- package/build/src/git/process.d.ts +6 -0
- package/build/src/git/process.js +26 -11
- package/build/src/git/read-observation.js +29 -20
- package/build/src/git/reconcile.d.ts +13 -1
- package/build/src/git/reconcile.js +70 -11
- package/build/src/git/ref-migration.d.ts +22 -0
- package/build/src/git/ref-migration.js +55 -0
- package/build/src/git/repository.d.ts +10 -6
- package/build/src/git/repository.js +16 -8
- package/build/src/git/target-placement.d.ts +1 -0
- package/build/src/git/target-placement.js +5 -0
- package/build/src/git/tender.d.ts +1 -0
- package/build/src/git/terminal-reconcile.d.ts +1 -1
- package/build/src/git/terminal-reconcile.js +22 -20
- package/build/src/git/workspace.d.ts +14 -0
- package/build/src/git/workspace.js +44 -0
- package/build/src/kanshi/index.d.ts +1 -1
- package/build/src/kanshi/read.js +16 -16
- package/build/src/kanshi/report.d.ts +2 -2
- package/build/src/library/address.d.ts +9 -3
- package/build/src/library/address.js +3 -2
- package/build/src/library/akuma-creation.d.ts +4 -3
- package/build/src/library/akuma-creation.js +19 -13
- package/build/src/library/audit.d.ts +14 -6
- package/build/src/library/audit.js +13 -23
- package/build/src/library/catalog.d.ts +3 -1
- package/build/src/library/catalog.js +20 -6
- package/build/src/library/composition.d.ts +385 -0
- package/build/src/library/composition.js +28 -0
- package/build/src/library/configuration.js +8 -2
- package/build/src/library/continuation.js +66 -30
- package/build/src/library/contract-bind.js +10 -2
- package/build/src/library/contract-forwarding-reconciliation-result.d.ts +102 -0
- package/build/src/library/contract-forwarding-reconciliation-result.js +92 -0
- package/build/src/library/contract-forwarding-result.d.ts +4464 -0
- package/build/src/library/contract-forwarding-result.js +521 -0
- package/build/src/library/contract-forwarding.d.ts +71 -0
- package/build/src/library/contract-forwarding.js +114 -0
- package/build/src/library/contract-handle.d.ts +38 -0
- package/build/src/library/contract-handle.js +314 -0
- package/build/src/library/contract-operations.d.ts +214 -69
- package/build/src/library/contract-operations.js +189 -118
- package/build/src/library/contract-types.d.ts +7 -3
- package/build/src/library/contract.d.ts +21 -34
- package/build/src/library/contract.js +29 -287
- package/build/src/library/fleet-result.d.ts +1580 -0
- package/build/src/library/fleet-result.js +98 -0
- package/build/src/library/fleet.d.ts +112 -54
- package/build/src/library/fleet.js +211 -71
- package/build/src/library/input.d.ts +3 -1
- package/build/src/library/input.js +11 -1
- package/build/src/library/keiyaku.d.ts +388 -20
- package/build/src/library/keiyaku.js +13 -21
- package/build/src/library/mutation.d.ts +3 -3
- package/build/src/library/mutation.js +5 -9
- package/build/src/library/nuke.d.ts +1 -1
- package/build/src/library/nuke.js +4 -3
- package/build/src/library/reconcile.d.ts +4 -0
- package/build/src/library/reconcile.js +2 -0
- package/build/src/library/refusal.d.ts +0 -9
- package/build/src/library/refusal.js +0 -7
- package/build/src/protocol/amend.js +56 -50
- package/build/src/protocol/attempt.d.ts +2 -0
- package/build/src/protocol/attempt.js +8 -1
- package/build/src/protocol/completion.js +7 -0
- package/build/src/protocol/deliver.js +6 -2
- package/build/src/protocol/operations.d.ts +4 -1
- package/build/src/protocol/operations.js +5 -2
- package/build/src/protocol/placement.d.ts +1 -0
- package/build/src/protocol/placement.js +52 -38
- package/build/src/protocol/read/observation.d.ts +1 -4
- package/build/src/protocol/read/observation.js +2 -15
- package/build/src/protocol/reintegrate.js +62 -58
- package/build/src/protocol/review.js +5 -0
- package/build/src/protocol/run.js +17 -13
- package/build/src/runtime/proc/run.d.ts +9 -0
- package/build/src/runtime/proc/run.js +51 -4
- package/build/src/runtime/proc/termination.js +26 -22
- package/build/src/runtime/proc/windows-launch.exe +0 -0
- package/build/src/settlement/holder.d.ts +2 -1
- package/build/src/settlement/holder.js +15 -4
- package/build/src/settlement/settle.js +99 -33
- package/build/src/task/board.d.ts +68 -27
- package/build/src/task/board.js +31 -1
- package/build/src/task/catalog.d.ts +4 -0
- package/build/src/task/catalog.js +23 -0
- package/build/src/task/compose-language.js +33 -7
- package/build/src/task/compose-parser.d.ts +1 -1
- package/build/src/task/compose-parser.js +1 -1
- package/build/src/task/compose.js +2 -0
- package/build/src/task/index.d.ts +12 -4
- package/build/src/task/index.js +97 -16
- package/build/src/task/mutation-result.d.ts +923 -0
- package/build/src/task/mutation-result.js +140 -0
- package/build/src/task/mutation.d.ts +552 -28
- package/build/src/task/mutation.js +304 -97
- package/build/src/task/operations.d.ts +1 -4
- package/build/src/task/operations.js +0 -6
- package/build/src/task/public.d.ts +2 -0
- package/build/src/task/public.js +1 -0
- package/build/src/workspace-place.d.ts +7 -2
- package/build/src/workspace-place.js +34 -24
- package/build/src/world.d.ts +1 -0
- package/build/src/world.js +11 -0
- package/package.json +8 -6
- package/build/src/akuma/request-execution.d.ts +0 -8
- package/build/src/akuma/request-execution.js +0 -98
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
2
|
+
import { spawnCancellableProcess } from "../runtime/proc/run.js";
|
|
3
3
|
import { gitObjectId } from "./identity.js";
|
|
4
4
|
import { parseTreeObject, validPath } from "./tree.js";
|
|
5
5
|
import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readRef } from "./repository.js";
|
|
@@ -36,11 +36,18 @@ function streamCursor(stream) {
|
|
|
36
36
|
};
|
|
37
37
|
return { line, exact };
|
|
38
38
|
}
|
|
39
|
+
async function batchObjects(oids, object) {
|
|
40
|
+
const unique = [...new Set(oids)];
|
|
41
|
+
for (const oid of unique)
|
|
42
|
+
gitObjectId(oid, "Git object");
|
|
43
|
+
const entries = await Promise.all(unique.map(async (oid) => [oid, await object(oid)]));
|
|
44
|
+
return new Map(entries);
|
|
45
|
+
}
|
|
39
46
|
function batchChild(repository) {
|
|
40
|
-
return
|
|
47
|
+
return spawnCancellableProcess({
|
|
48
|
+
argv: [repository.gitPath, "cat-file", "--batch"],
|
|
41
49
|
cwd: repository.effectiveCwd,
|
|
42
|
-
|
|
43
|
-
windowsHide: true,
|
|
50
|
+
...(repository.signal === undefined ? {} : { signal: repository.signal }),
|
|
44
51
|
});
|
|
45
52
|
}
|
|
46
53
|
function batchError(child, stderr, message, status) {
|
|
@@ -51,8 +58,18 @@ function batchError(child, stderr, message, status) {
|
|
|
51
58
|
pid: child.pid ?? null,
|
|
52
59
|
});
|
|
53
60
|
}
|
|
61
|
+
async function closeBatchProcess(process, child, closed, spawnError, stderr) {
|
|
62
|
+
if (!child.stdin.destroyed)
|
|
63
|
+
child.stdin.end();
|
|
64
|
+
const outcome = await Promise.race([closed, process.terminationFailure]);
|
|
65
|
+
await process.waitTermination();
|
|
66
|
+
if (spawnError !== null || outcome.code !== 0) {
|
|
67
|
+
throw batchError(child, stderr, process.cancelled() ? "git process cancelled" : "git cat-file --batch did not close cleanly", outcome.code);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
54
70
|
function batchObjectReader(repository) {
|
|
55
|
-
const
|
|
71
|
+
const process = batchChild(repository);
|
|
72
|
+
const child = process.child;
|
|
56
73
|
const cursor = streamCursor(child.stdout);
|
|
57
74
|
const cache = new Map();
|
|
58
75
|
const stderr = [];
|
|
@@ -101,9 +118,10 @@ function batchObjectReader(repository) {
|
|
|
101
118
|
catch (error) {
|
|
102
119
|
if (failure !== null)
|
|
103
120
|
throw failure;
|
|
104
|
-
|
|
121
|
+
await process.terminate(true);
|
|
105
122
|
await closed;
|
|
106
|
-
|
|
123
|
+
await process.waitTermination();
|
|
124
|
+
failure = batchError(child, stderr, process.cancelled() ? "git process cancelled" : error instanceof Error ? error.message : String(error), process.cancelled() ? null : child.exitCode);
|
|
107
125
|
throw failure;
|
|
108
126
|
}
|
|
109
127
|
};
|
|
@@ -116,23 +134,14 @@ function batchObjectReader(repository) {
|
|
|
116
134
|
cache.set(oid, requested);
|
|
117
135
|
return requested;
|
|
118
136
|
};
|
|
119
|
-
const objects =
|
|
120
|
-
const unique = [...new Set(oids)];
|
|
121
|
-
for (const oid of unique)
|
|
122
|
-
gitObjectId(oid, "Git object");
|
|
123
|
-
const entries = await Promise.all(unique.map(async (oid) => [oid, await object(oid)]));
|
|
124
|
-
return new Map(entries);
|
|
125
|
-
};
|
|
137
|
+
const objects = (oids) => batchObjects(oids, object);
|
|
126
138
|
const close = async () => {
|
|
127
139
|
await tail.catch(() => undefined);
|
|
128
|
-
if (failure !== null)
|
|
140
|
+
if (failure !== null) {
|
|
141
|
+
await process.waitTermination();
|
|
129
142
|
return;
|
|
130
|
-
if (!child.stdin.destroyed)
|
|
131
|
-
child.stdin.end();
|
|
132
|
-
const outcome = await closed;
|
|
133
|
-
if (spawnError !== null || outcome.code !== 0) {
|
|
134
|
-
throw batchError(child, stderr, "git cat-file --batch did not close cleanly", outcome.code);
|
|
135
143
|
}
|
|
144
|
+
await closeBatchProcess(process, child, closed, spawnError, stderr);
|
|
136
145
|
};
|
|
137
146
|
return { objects, close };
|
|
138
147
|
}
|
|
@@ -4,6 +4,7 @@ import type { ContractId, ContractState, SnapshotId } from "../core/facts/types.
|
|
|
4
4
|
import type { GitDecodeChannel } from "./read-observation.js";
|
|
5
5
|
import { type TargetCheckoutEffect, type TargetCheckoutLag } from "./target-placement.js";
|
|
6
6
|
import { type WorktreeHookLag, type WorktreeHooks } from "./hooks.js";
|
|
7
|
+
import { type RefMigrationConflict } from "./ref-migration.js";
|
|
7
8
|
import type { UnsealedBytes } from "./terminal-seal.js";
|
|
8
9
|
export type Effect = Readonly<{
|
|
9
10
|
kind: "worktree";
|
|
@@ -46,16 +47,21 @@ type WorktreeFollowRetained = Readonly<{
|
|
|
46
47
|
tender: SnapshotId;
|
|
47
48
|
head: SnapshotId;
|
|
48
49
|
reason: "head-moved" | "head-attached" | "operation-in-progress" | "unsupported-parent-shape";
|
|
50
|
+
paths?: readonly string[];
|
|
49
51
|
}>;
|
|
50
52
|
export type ReconcileFailure = Readonly<{
|
|
51
53
|
kind: "reconcile-failed";
|
|
52
54
|
stage: "observation" | "effect";
|
|
53
55
|
diagnostic: string;
|
|
54
56
|
}>;
|
|
55
|
-
export type ReconcileLag = WorktreeRetained | WorktreeFollowRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag | ReconcileFailure;
|
|
57
|
+
export type ReconcileLag = WorktreeRetained | WorktreeFollowRetained | UnsealedBytes | TargetCheckoutLag | WorktreeHookLag | RefMigrationConflict | ReconcileFailure;
|
|
56
58
|
export type ReconcileResult = Readonly<{
|
|
57
59
|
effects: readonly Effect[];
|
|
58
60
|
lag: readonly ReconcileLag[];
|
|
61
|
+
hookRuns?: readonly {
|
|
62
|
+
phase: "create" | "destroy";
|
|
63
|
+
name: string;
|
|
64
|
+
}[];
|
|
59
65
|
}>;
|
|
60
66
|
type ReconcileBatchItem = Readonly<{
|
|
61
67
|
contract: ContractId;
|
|
@@ -72,8 +78,14 @@ export type WorktreeTopology = Readonly<{
|
|
|
72
78
|
export type ReconcileAccumulation = Readonly<{
|
|
73
79
|
effects: Effect[];
|
|
74
80
|
lag: ReconcileLag[];
|
|
81
|
+
hookRuns: {
|
|
82
|
+
phase: "create" | "destroy";
|
|
83
|
+
name: string;
|
|
84
|
+
}[];
|
|
75
85
|
}>;
|
|
76
86
|
export declare function reconcile(input: ReconcileInput): Promise<GitReconcileObservation>;
|
|
87
|
+
/** Reconcile one retained dependent worktree under its normal per-Contract lock. */
|
|
88
|
+
export declare function reconcileDependentWorktree(repository: GitRepository, contractId: ContractId, path: string, target: SnapshotId): Promise<ReconcileResult>;
|
|
77
89
|
export declare function reconcileObservationFailure(error: unknown): ReconcileResult;
|
|
78
90
|
export declare function reconcileEffectFailure(error: unknown, prior?: ReconcileResult): ReconcileResult;
|
|
79
91
|
type ReconcileBatchOptions = Readonly<{
|
|
@@ -2,14 +2,15 @@ import { access, mkdir } from "node:fs/promises";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { acquireSqliteTransactionLock, } from "../coordination/sqlite-transaction-lock.js";
|
|
4
4
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
5
|
-
import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, registeredWorktreePaths,
|
|
5
|
+
import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, registeredWorktreePaths, } from "./repository.js";
|
|
6
6
|
import { runGit } from "./process.js";
|
|
7
7
|
import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot } from "./identity.js";
|
|
8
8
|
import { observeContractAt } from "./observe.js";
|
|
9
9
|
import { acquireTargetPlacementFence, recoverTargetPlacement, } from "./target-placement.js";
|
|
10
10
|
import { runCreateHooks } from "./hooks.js";
|
|
11
|
-
import { followManagedWorktree, worktreePath } from "./workspace.js";
|
|
11
|
+
import { followDependentManagedWorktree, followManagedWorktree, worktreePath } from "./workspace.js";
|
|
12
12
|
import { removeCollectableScratchWorktrees } from "./scratch.js";
|
|
13
|
+
import { migrateContractCustodyRefs } from "./ref-migration.js";
|
|
13
14
|
import { reconcileTerminalManagedWorktree, removeRef, updateRef } from "./terminal-reconcile.js";
|
|
14
15
|
const pathExists = (path) => access(path).then(() => true, () => false);
|
|
15
16
|
function deliveryRefFor(contract) {
|
|
@@ -31,8 +32,8 @@ async function acquireWorktreeTopology(repository) {
|
|
|
31
32
|
function diagnostic(error) {
|
|
32
33
|
return error instanceof Error ? error.message : String(error);
|
|
33
34
|
}
|
|
34
|
-
function complete(effects = [], lag = []) {
|
|
35
|
-
return { effects, lag };
|
|
35
|
+
function complete(effects = [], lag = [], hookRuns = []) {
|
|
36
|
+
return hookRuns.length === 0 ? { effects, lag } : { effects, lag, hookRuns };
|
|
36
37
|
}
|
|
37
38
|
function failed(stage, error, effects = [], lag = []) {
|
|
38
39
|
if (error instanceof AuthorityCorruptionError || error instanceof TypeError)
|
|
@@ -103,7 +104,7 @@ async function reconcileTargetCheckouts(repository, state) {
|
|
|
103
104
|
result = failed("effect", releaseFailure, result.effects, result.lag);
|
|
104
105
|
return result;
|
|
105
106
|
}
|
|
106
|
-
async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, place }, state, topology, { effects, lag }) {
|
|
107
|
+
async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, place }, state, topology, { effects, lag, hookRuns }) {
|
|
107
108
|
if (place === undefined)
|
|
108
109
|
return complete(effects, [...lag, missingPlaceLag(repository)]);
|
|
109
110
|
const path = worktreePath(repository, place);
|
|
@@ -131,29 +132,39 @@ async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, p
|
|
|
131
132
|
else {
|
|
132
133
|
effects.push(projection);
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
if (projection.action === "created" || retryHooks) {
|
|
136
|
+
const hookRun = await runCreateHooks(path, hooks);
|
|
137
|
+
hookRuns.push(...hookRun.runs.map((name) => ({ phase: "create", name })));
|
|
138
|
+
if (hookRun.lag !== null)
|
|
139
|
+
lag.push(hookRun.lag);
|
|
140
|
+
}
|
|
137
141
|
effects.push(await (state.delivery
|
|
138
142
|
? await updateRef(repository, candidatePinRefFor(state.id), state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot)
|
|
139
143
|
: await removeRef(repository, candidatePinRefFor(state.id))));
|
|
140
|
-
return complete(effects, lag);
|
|
144
|
+
return complete(effects, lag, hookRuns);
|
|
141
145
|
}
|
|
142
146
|
async function reconcileWithTopology(input, state, topology) {
|
|
143
147
|
const { repository } = input;
|
|
144
148
|
const effects = [];
|
|
145
149
|
const lag = [];
|
|
150
|
+
const hookRuns = [];
|
|
146
151
|
try {
|
|
147
152
|
await removeCollectableScratch(repository, topology, effects, lag);
|
|
148
153
|
if (!state)
|
|
149
154
|
return complete(effects, lag);
|
|
155
|
+
const migration = await migrateContractCustodyRefs(repository, state.id);
|
|
156
|
+
effects.push(...migration.effects);
|
|
157
|
+
lag.push(...migration.lag);
|
|
158
|
+
if (migration.lag.length > 0)
|
|
159
|
+
return complete(effects, lag);
|
|
150
160
|
const targetCheckouts = await reconcileTargetCheckouts(repository, state);
|
|
151
161
|
effects.push(...targetCheckouts.effects);
|
|
152
162
|
lag.push(...targetCheckouts.lag);
|
|
153
163
|
if (state.terminal) {
|
|
154
|
-
return await reconcileTerminalManagedWorktree(input, state, topology, { effects, lag }, { ref: deliveryRefFor(state.id), pin: candidatePinRefFor(state.id) });
|
|
164
|
+
return await reconcileTerminalManagedWorktree(input, state, topology, { effects, lag, hookRuns }, { ref: deliveryRefFor(state.id), pin: candidatePinRefFor(state.id) });
|
|
155
165
|
}
|
|
156
|
-
|
|
166
|
+
const result = await reconcileActiveManagedWorktree(input, state, topology, { effects, lag, hookRuns });
|
|
167
|
+
return hookRuns.length === 0 ? result : { ...result, hookRuns };
|
|
157
168
|
}
|
|
158
169
|
catch (error) {
|
|
159
170
|
return failed("effect", error, effects, lag);
|
|
@@ -206,6 +217,54 @@ export async function reconcile(input) {
|
|
|
206
217
|
throw new Error("reconcile produced no observation");
|
|
207
218
|
return observation;
|
|
208
219
|
}
|
|
220
|
+
/** Reconcile one retained dependent worktree under its normal per-Contract lock. */
|
|
221
|
+
export async function reconcileDependentWorktree(repository, contractId, path, target) {
|
|
222
|
+
let held;
|
|
223
|
+
try {
|
|
224
|
+
held = await acquireSqliteTransactionLock({ path: reconcileLockPath(repository, contractId), mode: "immediate" });
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
return failed("effect", error);
|
|
228
|
+
}
|
|
229
|
+
let result;
|
|
230
|
+
try {
|
|
231
|
+
const follow = await followDependentManagedWorktree(repository, path, target);
|
|
232
|
+
if (follow.kind === "followed") {
|
|
233
|
+
result = {
|
|
234
|
+
effects: [{ kind: "worktree", path, action: "followed", before: follow.before, after: follow.after }],
|
|
235
|
+
lag: [],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
else if (follow.kind === "unchanged") {
|
|
239
|
+
result = { effects: [], lag: [] };
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
result = {
|
|
243
|
+
effects: [{ kind: "worktree", path, action: "unchanged" }],
|
|
244
|
+
lag: [
|
|
245
|
+
{
|
|
246
|
+
kind: "worktree-follow-retained",
|
|
247
|
+
path,
|
|
248
|
+
tender: target,
|
|
249
|
+
head: follow.head,
|
|
250
|
+
reason: follow.reason,
|
|
251
|
+
...(follow.paths.length === 0 ? {} : { paths: follow.paths }),
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
result = failed("effect", error, [{ kind: "worktree", path, action: "unchanged" }]);
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
held.close();
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
result = failed("effect", error, result.effects, result.lag);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
209
268
|
export function reconcileObservationFailure(error) {
|
|
210
269
|
return failed("observation", error);
|
|
211
270
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ContractId } from "../core/facts/types.js";
|
|
2
|
+
import { type GitRepository } from "./process.js";
|
|
3
|
+
import { type GitOid } from "./repository.js";
|
|
4
|
+
export type RefMigrationEffect = Readonly<{
|
|
5
|
+
kind: "ref";
|
|
6
|
+
name: string;
|
|
7
|
+
before: GitOid | null;
|
|
8
|
+
after: GitOid | null;
|
|
9
|
+
action: "created" | "removed";
|
|
10
|
+
}>;
|
|
11
|
+
export type RefMigrationConflict = Readonly<{
|
|
12
|
+
kind: "ref-migration-conflict";
|
|
13
|
+
legacyRef: string;
|
|
14
|
+
legacyOid: GitOid;
|
|
15
|
+
currentRef: string;
|
|
16
|
+
currentOid: GitOid;
|
|
17
|
+
}>;
|
|
18
|
+
export type RefMigrationResult = Readonly<{
|
|
19
|
+
effects: readonly RefMigrationEffect[];
|
|
20
|
+
lag: readonly RefMigrationConflict[];
|
|
21
|
+
}>;
|
|
22
|
+
export declare function migrateContractCustodyRefs(repository: GitRepository, contract: ContractId): Promise<RefMigrationResult>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { contractPhysicalName } from "./identity.js";
|
|
2
|
+
import { runGit } from "./process.js";
|
|
3
|
+
import { CANDIDATE_PIN_REF_NAMESPACE, DELIVERY_REF_NAMESPACE, LEGACY_CANDIDATE_PIN_REF_NAMESPACE, LEGACY_DELIVERY_REF_NAMESPACE, readRef, } from "./repository.js";
|
|
4
|
+
export async function migrateContractCustodyRefs(repository, contract) {
|
|
5
|
+
const leaf = contractPhysicalName(contract);
|
|
6
|
+
const pairs = [
|
|
7
|
+
{
|
|
8
|
+
legacy: `${LEGACY_DELIVERY_REF_NAMESPACE}/${leaf}`,
|
|
9
|
+
current: `${DELIVERY_REF_NAMESPACE}/${leaf}`,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
legacy: `${LEGACY_CANDIDATE_PIN_REF_NAMESPACE}/${leaf}`,
|
|
13
|
+
current: `${CANDIDATE_PIN_REF_NAMESPACE}/${leaf}`,
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
const observed = await Promise.all(pairs.map(async (pair) => {
|
|
17
|
+
const [legacyOid, currentOid] = await Promise.all([
|
|
18
|
+
readRef(repository, pair.legacy),
|
|
19
|
+
readRef(repository, pair.current),
|
|
20
|
+
]);
|
|
21
|
+
return { ...pair, legacyOid, currentOid };
|
|
22
|
+
}));
|
|
23
|
+
const lag = observed.flatMap((pair) => pair.legacyOid !== null && pair.currentOid !== null && pair.currentOid !== pair.legacyOid
|
|
24
|
+
? [
|
|
25
|
+
{
|
|
26
|
+
kind: "ref-migration-conflict",
|
|
27
|
+
legacyRef: pair.legacy,
|
|
28
|
+
legacyOid: pair.legacyOid,
|
|
29
|
+
currentRef: pair.current,
|
|
30
|
+
currentOid: pair.currentOid,
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
: []);
|
|
34
|
+
if (lag.length > 0)
|
|
35
|
+
return { effects: [], lag };
|
|
36
|
+
const migrating = observed.filter((pair) => pair.legacyOid !== null);
|
|
37
|
+
if (migrating.length === 0)
|
|
38
|
+
return { effects: [], lag: [] };
|
|
39
|
+
const lines = ["start"];
|
|
40
|
+
const effects = [];
|
|
41
|
+
for (const pair of migrating) {
|
|
42
|
+
if (pair.currentOid === null) {
|
|
43
|
+
lines.push(`create ${pair.current} ${pair.legacyOid}`);
|
|
44
|
+
effects.push({ kind: "ref", name: pair.current, before: null, after: pair.legacyOid, action: "created" });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
lines.push(`verify ${pair.current} ${pair.currentOid}`);
|
|
48
|
+
}
|
|
49
|
+
lines.push(`delete ${pair.legacy} ${pair.legacyOid}`);
|
|
50
|
+
effects.push({ kind: "ref", name: pair.legacy, before: pair.legacyOid, after: null, action: "removed" });
|
|
51
|
+
}
|
|
52
|
+
lines.push("prepare", "commit", "");
|
|
53
|
+
await runGit(repository, ["update-ref", "--stdin", "--no-deref"], lines.join("\n"));
|
|
54
|
+
return { effects, lag: [] };
|
|
55
|
+
}
|
|
@@ -2,12 +2,13 @@ import { type GitRepository } from "./process.js";
|
|
|
2
2
|
import { type TreeChange, type TreeEntry } from "./tree.js";
|
|
3
3
|
export type { TreeChange } from "./tree.js";
|
|
4
4
|
export declare const GIT_REF = "refs/heads/keiyaku-state";
|
|
5
|
-
export declare const DELIVERY_REF_NAMESPACE = "refs/
|
|
6
|
-
export declare const CANDIDATE_PIN_REF_NAMESPACE = "refs/
|
|
7
|
-
export declare const
|
|
8
|
-
export declare const
|
|
5
|
+
export declare const DELIVERY_REF_NAMESPACE = "refs/keiyaku/delivery";
|
|
6
|
+
export declare const CANDIDATE_PIN_REF_NAMESPACE = "refs/keiyaku/candidate";
|
|
7
|
+
export declare const LEGACY_DELIVERY_REF_NAMESPACE = "refs/heads/keiyaku-delivery";
|
|
8
|
+
export declare const LEGACY_CANDIDATE_PIN_REF_NAMESPACE = "refs/heads/keiyaku-candidate";
|
|
9
9
|
export declare const GIT_FORMAT_PATH = "meta/format.json";
|
|
10
10
|
export declare const GIT_FORMAT_BYTES = "{\"version\":4}\n";
|
|
11
|
+
export declare const STATE_COMMIT_SUBJECT = "keiyaku authority - do not delete or rewrite";
|
|
11
12
|
export declare function isKeiyakuOwnedRef(ref: string): boolean;
|
|
12
13
|
export type GitOid = string;
|
|
13
14
|
export type RefPublication = Readonly<{
|
|
@@ -54,14 +55,17 @@ export declare function readBlob(repository: GitRepository, oid: GitOid): Promis
|
|
|
54
55
|
export declare function updateGitTree(repository: GitRepository, baseTree: GitOid | null, changes: ReadonlyMap<string, TreeChange>): Promise<GitOid>;
|
|
55
56
|
/** Write a tree update from directory entries frozen with the admitted base tree. */
|
|
56
57
|
export declare function updateGitTreeFromFrozenDirectories(repository: GitRepository, baseTree: GitOid | null, directories: ReadonlyMap<string, ReadonlyMap<string, TreeEntry>>, changes: ReadonlyMap<string, TreeChange>): Promise<GitOid>;
|
|
57
|
-
|
|
58
|
+
type WriteCommitInput = Readonly<{
|
|
58
59
|
repository: GitRepository;
|
|
59
60
|
tree: GitOid;
|
|
60
61
|
parent: GitOid | null;
|
|
61
62
|
message?: string;
|
|
62
63
|
actor?: string;
|
|
63
64
|
at?: string;
|
|
64
|
-
}
|
|
65
|
+
}>;
|
|
66
|
+
export declare function writeCommit(input: WriteCommitInput): Promise<GitOid>;
|
|
67
|
+
/** Write a commit that will become the canonical keiyaku-state tip. */
|
|
68
|
+
export declare function writeStateCommit(input: WriteCommitInput): Promise<GitOid>;
|
|
65
69
|
export declare function updateRefsAtomically(repository: GitRepository, updates: readonly ({
|
|
66
70
|
readonly ref: typeof GIT_REF;
|
|
67
71
|
readonly newOid: GitOid;
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
1
2
|
import { isAbsolute, resolve } from "node:path";
|
|
2
3
|
import { AuthorityCorruptionError } from "../core/facts/errors.js";
|
|
3
4
|
import { gitObjectId } from "./identity.js";
|
|
4
5
|
import { GitPlumbingError, runGit, runGitWithEnvironment } from "./process.js";
|
|
5
6
|
import { parseTreeObject, prepareTreeUpdate, treeUpdate, validPath, } from "./tree.js";
|
|
6
7
|
export const GIT_REF = "refs/heads/keiyaku-state";
|
|
7
|
-
export const DELIVERY_REF_NAMESPACE = "refs/
|
|
8
|
-
export const CANDIDATE_PIN_REF_NAMESPACE = "refs/
|
|
9
|
-
export const
|
|
10
|
-
export const
|
|
8
|
+
export const DELIVERY_REF_NAMESPACE = "refs/keiyaku/delivery";
|
|
9
|
+
export const CANDIDATE_PIN_REF_NAMESPACE = "refs/keiyaku/candidate";
|
|
10
|
+
export const LEGACY_DELIVERY_REF_NAMESPACE = "refs/heads/keiyaku-delivery";
|
|
11
|
+
export const LEGACY_CANDIDATE_PIN_REF_NAMESPACE = "refs/heads/keiyaku-candidate";
|
|
11
12
|
export const GIT_FORMAT_PATH = "meta/format.json";
|
|
12
13
|
const CURRENT_FORMAT_VERSION = 4;
|
|
13
14
|
export const GIT_FORMAT_BYTES = `{"version":${CURRENT_FORMAT_VERSION}}\n`;
|
|
15
|
+
export const STATE_COMMIT_SUBJECT = "keiyaku authority - do not delete or rewrite";
|
|
14
16
|
export function isKeiyakuOwnedRef(ref) {
|
|
15
17
|
return (ref === GIT_REF ||
|
|
16
18
|
ref === DELIVERY_REF_NAMESPACE ||
|
|
17
19
|
ref.startsWith(`${DELIVERY_REF_NAMESPACE}/`) ||
|
|
18
20
|
ref === CANDIDATE_PIN_REF_NAMESPACE ||
|
|
19
21
|
ref.startsWith(`${CANDIDATE_PIN_REF_NAMESPACE}/`) ||
|
|
20
|
-
ref ===
|
|
21
|
-
ref.startsWith(`${
|
|
22
|
-
ref ===
|
|
23
|
-
ref.startsWith(`${
|
|
22
|
+
ref === LEGACY_DELIVERY_REF_NAMESPACE ||
|
|
23
|
+
ref.startsWith(`${LEGACY_DELIVERY_REF_NAMESPACE}/`) ||
|
|
24
|
+
ref === LEGACY_CANDIDATE_PIN_REF_NAMESPACE ||
|
|
25
|
+
ref.startsWith(`${LEGACY_CANDIDATE_PIN_REF_NAMESPACE}/`));
|
|
24
26
|
}
|
|
25
27
|
export class NoGitWorldError extends Error {
|
|
26
28
|
path;
|
|
@@ -380,6 +382,12 @@ export async function writeCommit(input) {
|
|
|
380
382
|
...(input.at === undefined ? {} : { at: input.at }),
|
|
381
383
|
});
|
|
382
384
|
}
|
|
385
|
+
/** Write a commit that will become the canonical keiyaku-state tip. */
|
|
386
|
+
export async function writeStateCommit(input) {
|
|
387
|
+
const detail = input.message;
|
|
388
|
+
const message = detail === undefined || detail.length === 0 ? STATE_COMMIT_SUBJECT : `${STATE_COMMIT_SUBJECT}\n\n${detail}`;
|
|
389
|
+
return await writeCommit({ ...input, message });
|
|
390
|
+
}
|
|
383
391
|
function refAssertionLine(assertion) {
|
|
384
392
|
assertAssertionRef(assertion.ref);
|
|
385
393
|
gitObjectId(assertion.oid, `expected oid for ${assertion.ref}`);
|
|
@@ -43,6 +43,7 @@ export type TargetPlacementPreparation = Readonly<{
|
|
|
43
43
|
refusal: TargetPlacementRefusal;
|
|
44
44
|
}>;
|
|
45
45
|
export declare function observeTargetHead(repository: GitRepository, target: string): Promise<SnapshotId | null>;
|
|
46
|
+
export declare function observedTreeEqualsCandidate(repository: GitRepository, observed: SnapshotId | null, candidate: SnapshotId): Promise<boolean>;
|
|
46
47
|
export declare function acquireTargetPlacementFence(repository: GitRepository, target: string): Promise<HeldSqliteTransactionLock>;
|
|
47
48
|
export type TargetedContractCoordinates = ContractState["coordinates"] & Readonly<{
|
|
48
49
|
target: string;
|
|
@@ -27,6 +27,11 @@ function literalPath(path) {
|
|
|
27
27
|
async function commitTree(repository, snapshot) {
|
|
28
28
|
return gitObjectId((await runGit(repository, ["show", "-s", "--format=%T", gitObjectIdForSnapshot(snapshot)])).toString("utf8").trim(), "commit tree");
|
|
29
29
|
}
|
|
30
|
+
export async function observedTreeEqualsCandidate(repository, observed, candidate) {
|
|
31
|
+
if (observed === null)
|
|
32
|
+
return false;
|
|
33
|
+
return (await commitTree(repository, observed)) === (await commitTree(repository, candidate));
|
|
34
|
+
}
|
|
30
35
|
function checkoutRefusal(contractId, target, path, reason, paths) {
|
|
31
36
|
return { kind: "checkout-not-followable", contractId, target: target.target, path, reason, paths };
|
|
32
37
|
}
|
|
@@ -9,6 +9,7 @@ export type TenderCaptureCoordinates = Readonly<{
|
|
|
9
9
|
place?: string;
|
|
10
10
|
workspacePath?: string;
|
|
11
11
|
captureMergeState?: boolean;
|
|
12
|
+
/** Opt-in real-index UU refusal. Omit or false when authorized capture must read current worktree bytes. */
|
|
12
13
|
rejectUnmerged?: boolean;
|
|
13
14
|
}>;
|
|
14
15
|
export type WorktreeMissingRefusal = Readonly<{
|
|
@@ -17,5 +17,5 @@ type TerminalCustodyRefs = Readonly<{
|
|
|
17
17
|
}>;
|
|
18
18
|
export declare function updateRef(repository: GitRepository, ref: string, desired: SnapshotId): Promise<Effect>;
|
|
19
19
|
export declare function removeRef(repository: GitRepository, ref: string): Promise<Effect>;
|
|
20
|
-
export declare function reconcileTerminalManagedWorktree({ repository, channel, hooks,
|
|
20
|
+
export declare function reconcileTerminalManagedWorktree({ repository, channel, hooks, retainTerminalWorktree, place }: TerminalReconcileInput, state: ContractState, topology: WorktreeTopology, acc: ReconcileAccumulation, { ref, pin }: TerminalCustodyRefs): Promise<ReconcileResult>;
|
|
21
21
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
|
-
import { isKeiyakuOwnedRef, readRef, writeCommit
|
|
2
|
+
import { isKeiyakuOwnedRef, readRef, writeCommit } from "./repository.js";
|
|
3
3
|
import { gitObjectIdForSnapshot, mintSnapshotId } from "./identity.js";
|
|
4
4
|
import { runDestroyHooks } from "./hooks.js";
|
|
5
5
|
import { terminalSealExpectations as decodeTerminalSealExpectations, terminalSealSnapshots, observeTerminalWorkspace, } from "./terminal-seal.js";
|
|
@@ -9,10 +9,7 @@ const pathExists = (path) => access(path).then(() => true, () => false);
|
|
|
9
9
|
async function removeTerminalWorktree(repository, topology, path) {
|
|
10
10
|
const registered = topology.paths.has(path);
|
|
11
11
|
if (!registered) {
|
|
12
|
-
return {
|
|
13
|
-
effect: { kind: "worktree", path, action: "unchanged" },
|
|
14
|
-
retained: await pathExists(path),
|
|
15
|
-
};
|
|
12
|
+
return { retained: await pathExists(path) };
|
|
16
13
|
}
|
|
17
14
|
if (!(await pathExists(path))) {
|
|
18
15
|
await runGit(repository, ["worktree", "remove", path]);
|
|
@@ -105,20 +102,20 @@ async function snapshotCustodian(repository, snapshot) {
|
|
|
105
102
|
function fromPrimaryWorktree(repository) {
|
|
106
103
|
return { ...repository, effectiveCwd: repository.primaryWorktree };
|
|
107
104
|
}
|
|
108
|
-
function complete(effects = [], lag = []) {
|
|
109
|
-
return { effects, lag };
|
|
105
|
+
function complete(effects = [], lag = [], hookRuns = []) {
|
|
106
|
+
return hookRuns.length === 0 ? { effects, lag } : { effects, lag, hookRuns };
|
|
110
107
|
}
|
|
111
|
-
function retainTerminalWorktree(path, retainedLag, { effects, lag }) {
|
|
108
|
+
function retainTerminalWorktree(path, retainedLag, { effects, lag, hookRuns }) {
|
|
112
109
|
effects.push({ kind: "worktree", path, action: "unchanged" });
|
|
113
110
|
lag.push(retainedLag);
|
|
114
|
-
return complete(effects, lag);
|
|
111
|
+
return complete(effects, lag, hookRuns);
|
|
115
112
|
}
|
|
116
113
|
async function terminalSealExpectations(channel, state) {
|
|
117
114
|
const snapshots = terminalSealSnapshots(state);
|
|
118
115
|
const objects = await channel.readObjects(snapshots.map(gitObjectIdForSnapshot));
|
|
119
116
|
return decodeTerminalSealExpectations(state, objects);
|
|
120
117
|
}
|
|
121
|
-
async function removeSealedTerminalWorktree({ repository, topology, path, state, expected, hooks,
|
|
118
|
+
async function removeSealedTerminalWorktree({ repository, topology, path, state, expected, hooks, acc, }) {
|
|
122
119
|
if (topology.paths.has(path) && (await pathExists(path))) {
|
|
123
120
|
const terminal = state.terminal;
|
|
124
121
|
if (terminal === null)
|
|
@@ -156,9 +153,10 @@ async function removeSealedTerminalWorktree({ repository, topology, path, state,
|
|
|
156
153
|
}
|
|
157
154
|
recovery = await recordRecovery(beforeWorkspace);
|
|
158
155
|
}
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
156
|
+
const hookRun = await runDestroyHooks(path, hooks);
|
|
157
|
+
acc.hookRuns.push(...hookRun.runs.map((name) => ({ phase: "destroy", name })));
|
|
158
|
+
if (hookRun.lag !== null)
|
|
159
|
+
return retainTerminalWorktree(path, hookRun.lag, acc);
|
|
162
160
|
const after = await observeTerminalWorkspace(repository, path, expected);
|
|
163
161
|
const afterWorkspace = after.workspace;
|
|
164
162
|
const afterHooks = after.unsealed;
|
|
@@ -180,7 +178,8 @@ async function removeSealedTerminalWorktree({ repository, topology, path, state,
|
|
|
180
178
|
const removal = await removeTerminalWorktree(repository, topology, path);
|
|
181
179
|
if (removal.retained)
|
|
182
180
|
return retainTerminalWorktree(path, { kind: "worktree-retained", path }, acc);
|
|
183
|
-
|
|
181
|
+
if (removal.effect !== undefined)
|
|
182
|
+
acc.effects.push(removal.effect);
|
|
184
183
|
return null;
|
|
185
184
|
}
|
|
186
185
|
async function targetCustodyForClaimedIntegration(repository, state, integration) {
|
|
@@ -244,23 +243,27 @@ async function releaseTerminalCustody({ repository, state, resolveSeal, ref, pin
|
|
|
244
243
|
}
|
|
245
244
|
}
|
|
246
245
|
}
|
|
247
|
-
export async function reconcileTerminalManagedWorktree({ repository, channel, hooks,
|
|
246
|
+
export async function reconcileTerminalManagedWorktree({ repository, channel, hooks, retainTerminalWorktree, place }, state, topology, acc, { ref, pin }) {
|
|
248
247
|
const primary = fromPrimaryWorktree(repository);
|
|
249
248
|
const path = place === undefined ? undefined : worktreePath(repository, place);
|
|
250
249
|
const resolveSeal = async () => await terminalSealExpectations(channel, state);
|
|
251
250
|
if (path === undefined) {
|
|
252
251
|
if (retainTerminalWorktree === true)
|
|
253
|
-
return complete(acc.effects, acc.lag);
|
|
252
|
+
return complete(acc.effects, acc.lag, acc.hookRuns);
|
|
254
253
|
await releaseTerminalCustody({ repository: primary, state, resolveSeal, ref, pin, acc });
|
|
255
|
-
return complete(acc.effects, acc.lag);
|
|
254
|
+
return complete(acc.effects, acc.lag, acc.hookRuns);
|
|
256
255
|
}
|
|
257
256
|
const expected = await resolveSeal();
|
|
258
257
|
acc.effects.push(await updateRef(primary, ref, state.delivery?.data.tenderSnapshot ?? state.coordinates.start));
|
|
259
258
|
if (state.delivery !== null) {
|
|
260
259
|
acc.effects.push(await updateRef(primary, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot));
|
|
261
260
|
}
|
|
262
|
-
if (retainTerminalWorktree === true)
|
|
261
|
+
if (retainTerminalWorktree === true) {
|
|
262
|
+
if (topology.paths.has(path) && (await pathExists(path))) {
|
|
263
|
+
acc.effects.push({ kind: "worktree", path, action: "unchanged" });
|
|
264
|
+
}
|
|
263
265
|
return complete(acc.effects, acc.lag);
|
|
266
|
+
}
|
|
264
267
|
const retained = await removeSealedTerminalWorktree({
|
|
265
268
|
repository: primary,
|
|
266
269
|
topology,
|
|
@@ -268,11 +271,10 @@ export async function reconcileTerminalManagedWorktree({ repository, channel, ho
|
|
|
268
271
|
state,
|
|
269
272
|
expected,
|
|
270
273
|
hooks,
|
|
271
|
-
retryHooks,
|
|
272
274
|
acc,
|
|
273
275
|
});
|
|
274
276
|
if (retained !== null)
|
|
275
277
|
return retained;
|
|
276
278
|
await releaseTerminalCustody({ repository: primary, state, resolveSeal: async () => expected, ref, pin, acc });
|
|
277
|
-
return complete(acc.effects, acc.lag);
|
|
279
|
+
return complete(acc.effects, acc.lag, acc.hookRuns);
|
|
278
280
|
}
|
|
@@ -26,8 +26,22 @@ export type ManagedWorktreeFollow = Readonly<{
|
|
|
26
26
|
head: SnapshotId;
|
|
27
27
|
reason: "head-moved" | "head-attached" | "operation-in-progress" | "unsupported-parent-shape";
|
|
28
28
|
}>;
|
|
29
|
+
export type DependentWorktreeFollow = Readonly<{
|
|
30
|
+
kind: "followed";
|
|
31
|
+
before: SnapshotId;
|
|
32
|
+
after: SnapshotId;
|
|
33
|
+
}> | Readonly<{
|
|
34
|
+
kind: "unchanged";
|
|
35
|
+
}> | Readonly<{
|
|
36
|
+
kind: "retained";
|
|
37
|
+
head: SnapshotId;
|
|
38
|
+
reason: "head-moved" | "head-attached" | "operation-in-progress";
|
|
39
|
+
paths: readonly string[];
|
|
40
|
+
}>;
|
|
29
41
|
/** Follow an accepted tender only through the native index-and-HEAD transition. */
|
|
30
42
|
export declare function followManagedWorktree(repository: GitRepository, workspace: string, tender: SnapshotId): Promise<ManagedWorktreeFollow>;
|
|
43
|
+
/** Follow a dependency baseline only through a clean, detached fast-forward. */
|
|
44
|
+
export declare function followDependentManagedWorktree(repository: GitRepository, workspace: string, target: SnapshotId): Promise<DependentWorktreeFollow>;
|
|
31
45
|
export declare function withPrivateGitIndex<Value>(action: (environment: Readonly<{
|
|
32
46
|
GIT_INDEX_FILE: string;
|
|
33
47
|
}>) => Value | PromiseLike<Value>): Promise<Value>;
|