@astrosheep/keiyaku 4.0.0 → 4.0.2
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 +133 -8
- package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +4 -4
- package/build/integrations/marketplace/plugins/keiyaku/opencode.js +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/package.json +2 -2
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +13 -5
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -32
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +134 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/agents/openai.yaml +4 -0
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-task/SKILL.md +41 -6
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +262 -46
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +50 -33
- package/build/src/akuma/akuma.js +175 -127
- package/build/src/akuma/archetype.d.ts +14 -5
- package/build/src/akuma/archetype.js +85 -39
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +490 -183
- package/build/src/akuma/coordinate.d.ts +9 -0
- package/build/src/akuma/coordinate.js +22 -0
- package/build/src/akuma/heart/facts.d.ts +88 -45
- package/build/src/akuma/heart/facts.js +19 -8
- package/build/src/akuma/heart/index.d.ts +64 -55
- package/build/src/akuma/heart/index.js +172 -252
- package/build/src/akuma/heart/rows.d.ts +38 -83
- package/build/src/akuma/heart/rows.js +117 -155
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +61 -22
- package/build/src/akuma/heart/soul.d.ts +8 -0
- package/build/src/akuma/heart/soul.js +136 -0
- package/build/src/akuma/heart/storage.d.ts +47 -0
- package/build/src/akuma/heart/storage.js +224 -0
- package/build/src/akuma/heart/tells.d.ts +12 -0
- package/build/src/akuma/heart/tells.js +79 -0
- package/build/src/akuma/heart/timeline.d.ts +15 -0
- package/build/src/akuma/heart/timeline.js +86 -0
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +10 -3
- package/build/src/akuma/index.js +7 -1
- package/build/src/akuma/projection.d.ts +176 -0
- package/build/src/akuma/projection.js +256 -0
- package/build/src/akuma/provider-recipe.d.ts +24 -0
- package/build/src/akuma/provider-recipe.js +100 -0
- package/build/src/akuma/provider.d.ts +67 -13
- package/build/src/akuma/provider.js +172 -105
- package/build/src/akuma/providers/acp/core.d.ts +19 -0
- package/build/src/akuma/providers/acp/core.js +159 -0
- package/build/src/akuma/providers/acp/events.d.ts +20 -0
- package/build/src/akuma/providers/acp/events.js +73 -0
- package/build/src/akuma/providers/acp/index.d.ts +12 -0
- package/build/src/akuma/providers/acp/index.js +94 -0
- package/build/src/akuma/providers/{claude.d.ts → claude/events.d.ts} +9 -41
- package/build/src/akuma/providers/claude/events.js +238 -0
- package/build/src/akuma/providers/claude/index.d.ts +47 -0
- package/build/src/akuma/providers/claude/index.js +279 -0
- package/build/src/akuma/providers/claude/input.d.ts +13 -0
- package/build/src/akuma/providers/claude/input.js +109 -0
- package/build/src/akuma/providers/codex-app-server/events.d.ts +1 -1
- package/build/src/akuma/providers/codex-app-server/events.js +15 -12
- package/build/src/akuma/providers/codex-app-server/index.d.ts +15 -13
- package/build/src/akuma/providers/codex-app-server/index.js +77 -33
- package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
- package/build/src/akuma/providers/grok-build/index.js +70 -0
- package/build/src/akuma/providers/index.d.ts +5 -3
- package/build/src/akuma/providers/index.js +35 -56
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +54 -0
- package/build/src/akuma/providers/opencode-sdk/events.js +235 -0
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +33 -0
- package/build/src/akuma/providers/opencode-sdk/index.js +308 -0
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +26 -0
- package/build/src/akuma/providers/opencode-sdk/session.js +63 -0
- package/build/src/akuma/providers/pi/events.d.ts +33 -0
- package/build/src/akuma/providers/pi/events.js +166 -0
- package/build/src/akuma/providers/pi/index.d.ts +11 -0
- package/build/src/akuma/providers/pi/index.js +191 -0
- package/build/src/akuma/publication.d.ts +3 -3
- package/build/src/akuma/publication.js +28 -40
- package/build/src/akuma/requests.d.ts +9 -6
- package/build/src/akuma/requests.js +95 -65
- package/build/src/alias/index.d.ts +4 -3
- package/build/src/alias/index.js +12 -13
- package/build/src/body/decode.js +7 -1
- package/build/src/body/region.d.ts +2 -0
- package/build/src/body/region.js +77 -7
- package/build/src/body/render.js +2 -1
- package/build/src/body/shape.js +7 -1
- package/build/src/body/verification.js +19 -4
- package/build/src/cli/accepted.d.ts +18 -12
- package/build/src/cli/accepted.js +73 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +44 -19
- package/build/src/cli/commands/akuma-invoke.js +101 -52
- package/build/src/cli/commands/akuma.d.ts +27 -9
- package/build/src/cli/commands/akuma.js +151 -80
- package/build/src/cli/commands/contract.d.ts +155 -0
- package/build/src/cli/commands/contract.js +20 -0
- package/build/src/cli/commands/install.js +2 -0
- package/build/src/cli/commands/task-invoke.d.ts +19 -4
- package/build/src/cli/commands/task-invoke.js +65 -17
- package/build/src/cli/commands/task-query.d.ts +5 -0
- package/build/src/cli/commands/task-query.js +247 -0
- package/build/src/cli/commands/task.d.ts +3 -1
- package/build/src/cli/commands/task.js +52 -14
- package/build/src/cli/coordinates.d.ts +18 -0
- package/build/src/cli/coordinates.js +120 -0
- package/build/src/cli/draft.d.ts +8 -0
- package/build/src/cli/draft.js +96 -0
- package/build/src/cli/invoke.d.ts +9 -3
- package/build/src/cli/invoke.js +287 -108
- package/build/src/cli/main.js +54 -7
- package/build/src/cli/parse.d.ts +21 -117
- package/build/src/cli/parse.js +79 -84
- package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
- package/build/src/cli/render/akuma-tool-command.js +144 -0
- package/build/src/cli/render/akuma-tool.d.ts +4 -2
- package/build/src/cli/render/akuma-tool.js +35 -5
- package/build/src/cli/render/akuma.d.ts +3 -1
- package/build/src/cli/render/akuma.js +247 -212
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/catalog.d.ts +2 -0
- package/build/src/cli/render/catalog.js +16 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +200 -54
- package/build/src/cli/render/kanshi.js +272 -129
- package/build/src/cli/render/receipt.d.ts +19 -0
- package/build/src/cli/render/receipt.js +106 -0
- package/build/src/cli/render/refusal.d.ts +16 -2
- package/build/src/cli/render/refusal.js +88 -3
- package/build/src/cli/render/region.d.ts +2 -0
- package/build/src/cli/render/region.js +17 -0
- package/build/src/cli/render/task.d.ts +2 -1
- package/build/src/cli/render/task.js +223 -60
- package/build/src/cli/render/terminal.d.ts +20 -0
- package/build/src/cli/render/terminal.js +121 -0
- package/build/src/cli/render/text.js +11 -3
- package/build/src/cli/result.d.ts +126 -19
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +44 -0
- package/build/src/contract-worktree.js +251 -0
- package/build/src/coordination/durable-file.d.ts +5 -1
- package/build/src/coordination/durable-file.js +57 -10
- package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
- package/build/src/coordination/sqlite-transaction-lock.js +22 -5
- package/build/src/core/facts/codec.js +23 -4
- package/build/src/core/facts/eligibility.d.ts +1 -10
- package/build/src/core/facts/eligibility.js +0 -49
- package/build/src/core/facts/fold.js +1 -6
- package/build/src/core/facts/gate.d.ts +1 -0
- package/build/src/core/facts/gate.js +1 -0
- package/build/src/core/facts/observation.d.ts +3 -0
- package/build/src/core/facts/observation.js +12 -0
- package/build/src/core/facts/types.d.ts +10 -3
- package/build/src/core/subject.js +2 -2
- package/build/src/core/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +3 -18
- package/build/src/core/verbs/bind.js +2 -16
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +14 -4
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +7 -9
- package/build/src/dispatch/index.d.ts +5 -3
- package/build/src/dispatch/index.js +30 -21
- package/build/src/duration.d.ts +10 -0
- package/build/src/duration.js +22 -0
- package/build/src/git/admission.d.ts +2 -2
- package/build/src/git/admission.js +33 -23
- package/build/src/git/hooks.d.ts +15 -0
- package/build/src/git/hooks.js +70 -18
- package/build/src/git/identity.d.ts +5 -1
- package/build/src/git/identity.js +23 -4
- package/build/src/git/integration.d.ts +48 -0
- package/build/src/git/integration.js +250 -0
- package/build/src/git/observe.d.ts +29 -15
- package/build/src/git/observe.js +185 -76
- package/build/src/git/read-observation.d.ts +41 -0
- package/build/src/git/read-observation.js +361 -0
- package/build/src/git/reconcile.d.ts +17 -5
- package/build/src/git/reconcile.js +305 -109
- package/build/src/git/repository.d.ts +41 -18
- package/build/src/git/repository.js +220 -114
- package/build/src/git/scratch.d.ts +17 -0
- package/build/src/git/scratch.js +73 -0
- package/build/src/git/target-placement.d.ts +92 -0
- package/build/src/git/target-placement.js +375 -0
- package/build/src/git/tender.d.ts +49 -0
- package/build/src/git/tender.js +63 -0
- package/build/src/git/terminal-seal.d.ts +24 -0
- package/build/src/git/terminal-seal.js +89 -0
- package/build/src/git/tree.d.ts +2 -2
- package/build/src/git/tree.js +4 -0
- package/build/src/git/workspace.d.ts +53 -0
- package/build/src/git/workspace.js +139 -0
- package/build/src/identity/selector.d.ts +6 -0
- package/build/src/identity/selector.js +19 -0
- package/build/src/index.d.ts +4 -2
- package/build/src/index.js +2 -1
- package/build/src/kanshi/index.d.ts +2 -2
- package/build/src/kanshi/index.js +1 -1
- package/build/src/kanshi/read.d.ts +7 -3
- package/build/src/kanshi/read.js +245 -45
- package/build/src/kanshi/report.d.ts +81 -6
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +33 -1
- package/build/src/library/address.d.ts +57 -0
- package/build/src/library/address.js +165 -0
- package/build/src/library/akuma-creation.d.ts +16 -7
- package/build/src/library/akuma-creation.js +96 -22
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.d.ts +21 -0
- package/build/src/library/bind.js +56 -0
- package/build/src/library/catalog.d.ts +58 -0
- package/build/src/library/catalog.js +69 -0
- package/build/src/library/configuration.d.ts +6 -15
- package/build/src/library/configuration.js +14 -17
- package/build/src/library/contract.d.ts +23 -49
- package/build/src/library/contract.js +171 -185
- package/build/src/library/delivery.d.ts +24 -0
- package/build/src/library/delivery.js +21 -0
- package/build/src/library/fleet.d.ts +65 -0
- package/build/src/library/fleet.js +153 -0
- package/build/src/library/input.d.ts +3 -0
- package/build/src/library/input.js +19 -0
- package/build/src/library/keiyaku.d.ts +18 -3
- package/build/src/library/keiyaku.js +12 -1
- package/build/src/library/mutation.d.ts +39 -0
- package/build/src/library/mutation.js +37 -0
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/refusal.d.ts +21 -0
- package/build/src/library/refusal.js +29 -0
- package/build/src/library/region.d.ts +14 -1
- package/build/src/library/region.js +18 -4
- package/build/src/library/repo.d.ts +4 -13
- package/build/src/library/repo.js +14 -24
- package/build/src/protocol/attempt.d.ts +16 -3
- package/build/src/protocol/attempt.js +41 -10
- package/build/src/protocol/bind.d.ts +9 -2
- package/build/src/protocol/bind.js +48 -40
- package/build/src/protocol/intent.d.ts +57 -20
- package/build/src/protocol/intent.js +55 -79
- package/build/src/protocol/operations.d.ts +152 -43
- package/build/src/protocol/operations.js +482 -166
- package/build/src/protocol/outcome.d.ts +14 -3
- package/build/src/protocol/outcome.js +11 -3
- package/build/src/protocol/placement.d.ts +41 -0
- package/build/src/protocol/placement.js +147 -0
- package/build/src/protocol/read/documents.d.ts +2 -2
- package/build/src/protocol/read/documents.js +4 -4
- package/build/src/protocol/read/status.d.ts +18 -5
- package/build/src/protocol/read/status.js +73 -13
- package/build/src/protocol/run.d.ts +49 -7
- package/build/src/protocol/run.js +53 -25
- package/build/src/runtime/proc/line-rpc.d.ts +4 -8
- package/build/src/runtime/proc/line-rpc.js +22 -28
- package/build/src/runtime/proc/run.d.ts +23 -21
- package/build/src/runtime/proc/run.js +123 -89
- package/build/src/runtime/proc/stdio.d.ts +18 -0
- package/build/src/runtime/proc/stdio.js +58 -0
- package/build/src/settings.d.ts +7 -1
- package/build/src/settings.js +32 -17
- package/build/src/settlement/fence.d.ts +5 -0
- package/build/src/settlement/fence.js +14 -0
- package/build/src/settlement/holder.d.ts +35 -4
- package/build/src/settlement/holder.js +127 -28
- package/build/src/settlement/settle.d.ts +9 -1
- package/build/src/settlement/settle.js +74 -37
- package/build/src/task/board.d.ts +23 -5
- package/build/src/task/board.js +17 -10
- package/build/src/task/compose.d.ts +2 -2
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +15 -20
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +27 -15
- package/build/src/task/index.js +57 -35
- package/build/src/task/operations.d.ts +38 -13
- package/build/src/task/operations.js +80 -46
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +5 -7
- package/build/src/task/store.js +43 -28
- package/build/src/verification/declaration.d.ts +1 -0
- package/build/src/verification/execution.d.ts +51 -0
- package/build/src/verification/execution.js +108 -0
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +24 -0
- package/build/src/world.js +136 -0
- package/package.json +8 -4
- package/build/src/akuma/activity.d.ts +0 -66
- package/build/src/akuma/activity.js +0 -139
- package/build/src/akuma/providers/claude.js +0 -305
- package/build/src/context-root.d.ts +0 -4
- package/build/src/context-root.js +0 -15
- package/build/src/git/delivery.d.ts +0 -20
- package/build/src/git/delivery.js +0 -109
- package/build/src/git/verification.d.ts +0 -14
- package/build/src/git/verification.js +0 -56
- package/build/src/protocol/read/audit.d.ts +0 -25
- package/build/src/protocol/read/audit.js +0 -39
- package/build/src/verification/producer.d.ts +0 -23
- package/build/src/verification/producer.js +0 -54
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type HeldSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
2
|
+
import type { ContractId, ContractState, SnapshotId } from "../core/facts/types.js";
|
|
3
|
+
import type { RefOperation } from "../core/facts/offer.js";
|
|
4
|
+
import { type GitRepository } from "./repository.js";
|
|
5
|
+
export type WorkspaceNotOnTargetRefusal = Readonly<{
|
|
6
|
+
kind: "workspace-not-on-target";
|
|
7
|
+
contractId: ContractId;
|
|
8
|
+
target: string;
|
|
9
|
+
branch: string | null;
|
|
10
|
+
}>;
|
|
11
|
+
export type CheckoutNotFollowableRefusal = Readonly<{
|
|
12
|
+
kind: "checkout-not-followable";
|
|
13
|
+
contractId: ContractId;
|
|
14
|
+
target: string;
|
|
15
|
+
path: string;
|
|
16
|
+
reason: "staged" | "conflict" | "untracked";
|
|
17
|
+
paths: readonly string[];
|
|
18
|
+
}>;
|
|
19
|
+
export type TargetPlacementRefusal = CheckoutNotFollowableRefusal | WorkspaceNotOnTargetRefusal;
|
|
20
|
+
export type TargetCheckoutEffect = Readonly<{
|
|
21
|
+
kind: "target-checkout";
|
|
22
|
+
path: string;
|
|
23
|
+
target: string;
|
|
24
|
+
action: "followed" | "recovered";
|
|
25
|
+
}>;
|
|
26
|
+
export type TargetCheckoutLag = Readonly<{
|
|
27
|
+
kind: "target-checkout-retained";
|
|
28
|
+
path: string;
|
|
29
|
+
target: string;
|
|
30
|
+
diagnostic: string;
|
|
31
|
+
}>;
|
|
32
|
+
export type TargetPlacementPhysicalResult = Readonly<{
|
|
33
|
+
effects: readonly TargetCheckoutEffect[];
|
|
34
|
+
lag: readonly TargetCheckoutLag[];
|
|
35
|
+
}>;
|
|
36
|
+
type FollowArm = Readonly<{
|
|
37
|
+
kind: "ordinary" | "here";
|
|
38
|
+
path: string;
|
|
39
|
+
}>;
|
|
40
|
+
export type PreparedTargetPlacement = Readonly<{
|
|
41
|
+
target: RefOperation;
|
|
42
|
+
arms: readonly FollowArm[];
|
|
43
|
+
}>;
|
|
44
|
+
export type TargetPlacementPreparation = Readonly<{
|
|
45
|
+
kind: "prepared";
|
|
46
|
+
placement: PreparedTargetPlacement;
|
|
47
|
+
}> | Readonly<{
|
|
48
|
+
kind: "refused";
|
|
49
|
+
refusal: TargetPlacementRefusal;
|
|
50
|
+
}>;
|
|
51
|
+
export declare function observeTargetHead(repository: GitRepository, target: string): Promise<SnapshotId | null>;
|
|
52
|
+
export declare function acquireTargetPlacementFence(repository: GitRepository, target: string): Promise<HeldSqliteTransactionLock>;
|
|
53
|
+
export type TargetedContractCoordinates = ContractState["coordinates"] & Readonly<{
|
|
54
|
+
target: string;
|
|
55
|
+
}>;
|
|
56
|
+
export type TargetPlacementObservationInput = Readonly<{
|
|
57
|
+
contractId: ContractId;
|
|
58
|
+
coordinates: TargetedContractCoordinates;
|
|
59
|
+
predecessor: SnapshotId;
|
|
60
|
+
candidate: SnapshotId;
|
|
61
|
+
}>;
|
|
62
|
+
export type TargetPlacementObservation = Readonly<{
|
|
63
|
+
kind: "ready";
|
|
64
|
+
arms: readonly FollowArm[];
|
|
65
|
+
}> | Readonly<{
|
|
66
|
+
kind: "refused";
|
|
67
|
+
refusal: TargetPlacementRefusal;
|
|
68
|
+
}>;
|
|
69
|
+
/** Observe whether registered target checkouts can follow a predecessor-to-candidate movement. */
|
|
70
|
+
export declare function observeTargetPlacement(repository: GitRepository, input: TargetPlacementObservationInput): Promise<TargetPlacementObservation>;
|
|
71
|
+
export type AuditTargetAnswer = Readonly<{
|
|
72
|
+
kind: "placeable";
|
|
73
|
+
ref: string;
|
|
74
|
+
head: SnapshotId;
|
|
75
|
+
}> | Readonly<{
|
|
76
|
+
kind: "moved";
|
|
77
|
+
ref: string;
|
|
78
|
+
expected: SnapshotId;
|
|
79
|
+
observed: SnapshotId | null;
|
|
80
|
+
}> | Readonly<{
|
|
81
|
+
kind: "refused";
|
|
82
|
+
refusal: TargetPlacementRefusal;
|
|
83
|
+
}> | Readonly<{
|
|
84
|
+
kind: "failed";
|
|
85
|
+
diagnostic: string;
|
|
86
|
+
}>;
|
|
87
|
+
/** Adjudicate the complete post-Verification audit target answer without placing. */
|
|
88
|
+
export declare function adjudicateAuditTarget(repository: GitRepository, input: TargetPlacementObservationInput): Promise<AuditTargetAnswer>;
|
|
89
|
+
export declare function prepareTargetPlacement(repository: GitRepository, state: ContractState, target: RefOperation): Promise<TargetPlacementPreparation>;
|
|
90
|
+
export declare function followTargetPlacement(repository: GitRepository, prepared: PreparedTargetPlacement): Promise<TargetPlacementPhysicalResult>;
|
|
91
|
+
export declare function recoverTargetPlacement(repository: GitRepository, state: ContractState): Promise<TargetPlacementPhysicalResult>;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { lstat } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { acquireSqliteTransactionLock } from "../coordination/sqlite-transaction-lock.js";
|
|
4
|
+
import { gitObjectId, gitObjectIdForSnapshot, gitRefLocator, mintSnapshotId } from "./identity.js";
|
|
5
|
+
import { currentBranch } from "./observe.js";
|
|
6
|
+
import { commonGitDirectory, consumeGitStdout, GitPlumbingError, readRef, registeredWorktrees, runGit, } from "./repository.js";
|
|
7
|
+
import { captureWorkspaceTree } from "./workspace.js";
|
|
8
|
+
export async function observeTargetHead(repository, target) {
|
|
9
|
+
const value = await readRef(repository, target);
|
|
10
|
+
return value === null ? null : mintSnapshotId(value);
|
|
11
|
+
}
|
|
12
|
+
function diagnostic(error) {
|
|
13
|
+
return error instanceof Error ? error.message : String(error);
|
|
14
|
+
}
|
|
15
|
+
function nulPaths(bytes) {
|
|
16
|
+
const fields = bytes.toString("utf8").split("\0");
|
|
17
|
+
if (fields.at(-1) !== "")
|
|
18
|
+
throw new Error("Git path output is not NUL terminated");
|
|
19
|
+
return [...new Set(fields.slice(0, -1))].sort();
|
|
20
|
+
}
|
|
21
|
+
async function gitPaths(repository, path, args) {
|
|
22
|
+
return nulPaths(await runGit(repository, ["-C", path, ...args]));
|
|
23
|
+
}
|
|
24
|
+
function literalPath(path) {
|
|
25
|
+
return `:(literal)${path}`;
|
|
26
|
+
}
|
|
27
|
+
async function commitTree(repository, snapshot) {
|
|
28
|
+
return gitObjectId((await runGit(repository, ["show", "-s", "--format=%T", gitObjectIdForSnapshot(snapshot)]))
|
|
29
|
+
.toString("utf8")
|
|
30
|
+
.trim(), "commit tree");
|
|
31
|
+
}
|
|
32
|
+
function checkoutRefusal(contractId, target, path, reason, paths) {
|
|
33
|
+
return { kind: "checkout-not-followable", contractId, target: target.target, path, reason, paths };
|
|
34
|
+
}
|
|
35
|
+
async function changedPaths(repository, path, predecessor, candidate, filter) {
|
|
36
|
+
return await gitPaths(repository, path, [
|
|
37
|
+
"diff",
|
|
38
|
+
"--name-only",
|
|
39
|
+
"--no-renames",
|
|
40
|
+
...(filter === undefined ? [] : [`--diff-filter=${filter}`]),
|
|
41
|
+
"-z",
|
|
42
|
+
predecessor,
|
|
43
|
+
candidate,
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
async function dryRunRefusal(input, scopes) {
|
|
47
|
+
const { repository, contractId, target, path, predecessor, candidate } = input;
|
|
48
|
+
const changed = await changedPaths(repository, path, predecessor, candidate);
|
|
49
|
+
const pathspecs = changed.map(literalPath);
|
|
50
|
+
if (pathspecs.length === 0)
|
|
51
|
+
return checkoutRefusal(contractId, target, path, "conflict", []);
|
|
52
|
+
const staged = await gitPaths(repository, path, [
|
|
53
|
+
"diff",
|
|
54
|
+
"--cached",
|
|
55
|
+
"--name-only",
|
|
56
|
+
"-z",
|
|
57
|
+
predecessor,
|
|
58
|
+
"--",
|
|
59
|
+
...pathspecs,
|
|
60
|
+
]);
|
|
61
|
+
if (staged.length > 0)
|
|
62
|
+
return checkoutRefusal(contractId, target, path, "staged", staged);
|
|
63
|
+
const dirty = await gitPaths(repository, path, ["diff-files", "--name-only", "-z", "--", ...pathspecs]);
|
|
64
|
+
if (dirty.length > 0)
|
|
65
|
+
return checkoutRefusal(contractId, target, path, "conflict", dirty);
|
|
66
|
+
const unmerged = await gitPaths(repository, path, ["diff", "--name-only", "--diff-filter=U", "-z", "--", ...pathspecs]);
|
|
67
|
+
if (unmerged.length > 0)
|
|
68
|
+
return checkoutRefusal(contractId, target, path, "conflict", unmerged);
|
|
69
|
+
return await untrackedRefusalWithinScopes(input, scopes, false)
|
|
70
|
+
?? checkoutRefusal(contractId, target, path, "conflict", []);
|
|
71
|
+
}
|
|
72
|
+
async function physicalScope(worktree, candidatePath) {
|
|
73
|
+
const components = candidatePath.split("/");
|
|
74
|
+
for (let index = 0; index < components.length; index += 1) {
|
|
75
|
+
const scope = components.slice(0, index + 1).join("/");
|
|
76
|
+
let stat;
|
|
77
|
+
try {
|
|
78
|
+
stat = await lstat(resolve(worktree, ...components.slice(0, index + 1)));
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const code = error.code;
|
|
82
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
83
|
+
return null;
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
if (!stat.isDirectory())
|
|
87
|
+
return { path: scope, kind: "leaf" };
|
|
88
|
+
if (index + 1 === components.length)
|
|
89
|
+
return { path: scope, kind: "directory" };
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
async function candidateEntryIsBlob(repository, path, candidate, candidatePath) {
|
|
94
|
+
const output = await runGit(repository, ["-C", path, "ls-tree", "-z", candidate, "--", literalPath(candidatePath)]);
|
|
95
|
+
const records = output.toString("utf8").split("\0").filter((record) => record.length > 0);
|
|
96
|
+
if (records.length !== 1)
|
|
97
|
+
throw new Error(`candidate path has no unique Git entry: ${candidatePath}`);
|
|
98
|
+
const separator = records[0].indexOf("\t");
|
|
99
|
+
if (separator < 0)
|
|
100
|
+
throw new Error(`candidate path has a malformed Git entry: ${candidatePath}`);
|
|
101
|
+
const fields = records[0].slice(0, separator).split(" ");
|
|
102
|
+
return fields[1] === "blob";
|
|
103
|
+
}
|
|
104
|
+
async function destructionScopes(repository, path, candidate, writes) {
|
|
105
|
+
const scopes = new Map();
|
|
106
|
+
for (const write of writes) {
|
|
107
|
+
const scope = await physicalScope(path, write);
|
|
108
|
+
if (scope === null)
|
|
109
|
+
continue;
|
|
110
|
+
if (scope.kind === "directory" && !(await candidateEntryIsBlob(repository, path, candidate, write)))
|
|
111
|
+
continue;
|
|
112
|
+
scopes.set(scope.path, scope);
|
|
113
|
+
}
|
|
114
|
+
return [...scopes.values()].sort((left, right) => left.path.localeCompare(right.path));
|
|
115
|
+
}
|
|
116
|
+
function untrackedArgs(ignored) {
|
|
117
|
+
return [
|
|
118
|
+
"ls-files",
|
|
119
|
+
"--others",
|
|
120
|
+
...(ignored ? ["--ignored"] : []),
|
|
121
|
+
"--exclude-standard",
|
|
122
|
+
"--directory",
|
|
123
|
+
"--no-empty",
|
|
124
|
+
"-z",
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
async function untrackedRefusalWithinScopes(input, scopes, ignored) {
|
|
128
|
+
const { repository, contractId, target, path } = input;
|
|
129
|
+
const args = untrackedArgs(ignored);
|
|
130
|
+
const leaves = scopes.filter((scope) => scope.kind === "leaf");
|
|
131
|
+
if (leaves.length > 0) {
|
|
132
|
+
const collisions = await gitPaths(repository, path, [
|
|
133
|
+
...args,
|
|
134
|
+
"--",
|
|
135
|
+
...leaves.map((scope) => literalPath(scope.path)),
|
|
136
|
+
]);
|
|
137
|
+
if (collisions.length > 0)
|
|
138
|
+
return checkoutRefusal(contractId, target, path, "untracked", collisions);
|
|
139
|
+
}
|
|
140
|
+
for (const scope of scopes) {
|
|
141
|
+
if (scope.kind !== "directory")
|
|
142
|
+
continue;
|
|
143
|
+
let found = false;
|
|
144
|
+
await consumeGitStdout(repository, [
|
|
145
|
+
"-C",
|
|
146
|
+
path,
|
|
147
|
+
...args,
|
|
148
|
+
"--",
|
|
149
|
+
literalPath(scope.path),
|
|
150
|
+
], (chunk) => {
|
|
151
|
+
if (chunk.length > 0)
|
|
152
|
+
found = true;
|
|
153
|
+
});
|
|
154
|
+
if (found)
|
|
155
|
+
return checkoutRefusal(contractId, target, path, "untracked", [scope.path]);
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
async function indexMatchesTreeOnPaths(repository, path, tree, paths) {
|
|
160
|
+
return (await gitPaths(repository, path, ["diff-index", "--cached", "--name-only", "-z", tree, "--", ...paths])).length === 0;
|
|
161
|
+
}
|
|
162
|
+
async function workspaceMatchesTreeOnPaths(repository, path, tree, workspaceTree, paths) {
|
|
163
|
+
return (await gitPaths(repository, path, ["diff", "--name-only", "-z", tree, workspaceTree, "--", ...paths])).length === 0;
|
|
164
|
+
}
|
|
165
|
+
async function sourceWorktree(repository) {
|
|
166
|
+
return resolve((await runGit(repository, ["rev-parse", "--show-toplevel"])).toString("utf8").trim());
|
|
167
|
+
}
|
|
168
|
+
async function ordinaryPrecheck(repository, contractId, target, path) {
|
|
169
|
+
const predecessor = gitObjectIdForSnapshot(target.expectedOid);
|
|
170
|
+
const candidate = gitObjectIdForSnapshot(target.newOid);
|
|
171
|
+
const observation = { repository, contractId, target, path, predecessor, candidate };
|
|
172
|
+
let dryRunFailed = false;
|
|
173
|
+
try {
|
|
174
|
+
await runGit(repository, ["-C", path, "read-tree", "--dry-run", "-m", "-u", predecessor, candidate]);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (!(error instanceof GitPlumbingError))
|
|
178
|
+
throw error;
|
|
179
|
+
dryRunFailed = true;
|
|
180
|
+
}
|
|
181
|
+
const writes = await changedPaths(repository, path, predecessor, candidate, "ACMRT");
|
|
182
|
+
const scopes = await destructionScopes(repository, path, candidate, writes);
|
|
183
|
+
if (dryRunFailed)
|
|
184
|
+
return await dryRunRefusal(observation, scopes);
|
|
185
|
+
return await untrackedRefusalWithinScopes(observation, scopes, true);
|
|
186
|
+
}
|
|
187
|
+
export async function acquireTargetPlacementFence(repository, target) {
|
|
188
|
+
const locator = gitRefLocator(target);
|
|
189
|
+
return await acquireSqliteTransactionLock({
|
|
190
|
+
path: resolve(commonGitDirectory(repository), "keiyaku", "locks", "target-placement", `${locator}.sqlite`),
|
|
191
|
+
mode: "immediate",
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Observe whether registered target checkouts can follow a predecessor-to-candidate movement. */
|
|
195
|
+
export async function observeTargetPlacement(repository, input) {
|
|
196
|
+
const target = {
|
|
197
|
+
target: input.coordinates.target,
|
|
198
|
+
expectedOid: input.predecessor,
|
|
199
|
+
newOid: input.candidate,
|
|
200
|
+
};
|
|
201
|
+
const worktrees = (await registeredWorktrees(repository))
|
|
202
|
+
.filter((worktree) => worktree.branch === target.target)
|
|
203
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
204
|
+
const hereSource = input.coordinates.workspace === "here" ? await sourceWorktree(repository) : null;
|
|
205
|
+
if (hereSource !== null) {
|
|
206
|
+
const branch = await currentBranch(repository, hereSource);
|
|
207
|
+
if (branch !== target.target) {
|
|
208
|
+
return {
|
|
209
|
+
kind: "refused",
|
|
210
|
+
refusal: {
|
|
211
|
+
kind: "workspace-not-on-target",
|
|
212
|
+
contractId: input.contractId,
|
|
213
|
+
target: target.target,
|
|
214
|
+
branch,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const arms = [];
|
|
220
|
+
for (const worktree of worktrees) {
|
|
221
|
+
const kind = hereSource !== null && resolve(worktree.path) === hereSource ? "here" : "ordinary";
|
|
222
|
+
if (kind === "ordinary") {
|
|
223
|
+
const refusal = await ordinaryPrecheck(repository, input.contractId, target, worktree.path);
|
|
224
|
+
if (refusal !== null)
|
|
225
|
+
return { kind: "refused", refusal };
|
|
226
|
+
}
|
|
227
|
+
arms.push({ kind, path: worktree.path });
|
|
228
|
+
}
|
|
229
|
+
if (hereSource !== null && !arms.some((arm) => arm.kind === "here")) {
|
|
230
|
+
throw new Error("targeted here workspace is not a registered checkout of its target");
|
|
231
|
+
}
|
|
232
|
+
return { kind: "ready", arms };
|
|
233
|
+
}
|
|
234
|
+
/** Adjudicate the complete post-Verification audit target answer without placing. */
|
|
235
|
+
export async function adjudicateAuditTarget(repository, input) {
|
|
236
|
+
const ref = input.coordinates.target;
|
|
237
|
+
const expected = input.predecessor;
|
|
238
|
+
try {
|
|
239
|
+
const head = await observeTargetHead(repository, ref);
|
|
240
|
+
if (head !== expected)
|
|
241
|
+
return { kind: "moved", ref, expected, observed: head };
|
|
242
|
+
const follow = await observeTargetPlacement(repository, input);
|
|
243
|
+
const observed = await observeTargetHead(repository, ref);
|
|
244
|
+
if (observed !== expected)
|
|
245
|
+
return { kind: "moved", ref, expected, observed };
|
|
246
|
+
return follow.kind === "refused"
|
|
247
|
+
? { kind: "refused", refusal: follow.refusal }
|
|
248
|
+
: { kind: "placeable", ref, head: expected };
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (!(error instanceof GitPlumbingError))
|
|
252
|
+
throw error;
|
|
253
|
+
return { kind: "failed", diagnostic: error.message };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
export async function prepareTargetPlacement(repository, state, target) {
|
|
257
|
+
if (state.coordinates.target !== target.target || state.delivery?.data.integration.snapshot !== target.newOid) {
|
|
258
|
+
throw new Error("placement state does not match its offered target movement");
|
|
259
|
+
}
|
|
260
|
+
const observation = await observeTargetPlacement(repository, {
|
|
261
|
+
contractId: state.id,
|
|
262
|
+
coordinates: { ...state.coordinates, target: target.target },
|
|
263
|
+
predecessor: target.expectedOid,
|
|
264
|
+
candidate: target.newOid,
|
|
265
|
+
});
|
|
266
|
+
return observation.kind === "refused"
|
|
267
|
+
? observation
|
|
268
|
+
: { kind: "prepared", placement: { target, arms: observation.arms } };
|
|
269
|
+
}
|
|
270
|
+
export async function followTargetPlacement(repository, prepared) {
|
|
271
|
+
const effects = [];
|
|
272
|
+
const lag = [];
|
|
273
|
+
const predecessor = gitObjectIdForSnapshot(prepared.target.expectedOid);
|
|
274
|
+
const candidate = gitObjectIdForSnapshot(prepared.target.newOid);
|
|
275
|
+
for (const arm of prepared.arms) {
|
|
276
|
+
try {
|
|
277
|
+
if (arm.kind === "here")
|
|
278
|
+
await runGit(repository, ["-C", arm.path, "read-tree", candidate]);
|
|
279
|
+
else
|
|
280
|
+
await runGit(repository, ["-C", arm.path, "read-tree", "-m", "-u", predecessor, candidate]);
|
|
281
|
+
effects.push({ kind: "target-checkout", path: arm.path, target: prepared.target.target, action: "followed" });
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
lag.push({
|
|
285
|
+
kind: "target-checkout-retained",
|
|
286
|
+
path: arm.path,
|
|
287
|
+
target: prepared.target.target,
|
|
288
|
+
diagnostic: diagnostic(error),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { effects, lag };
|
|
293
|
+
}
|
|
294
|
+
function recoveryLag(path, target, detail) {
|
|
295
|
+
return { kind: "target-checkout-retained", path, target, diagnostic: detail };
|
|
296
|
+
}
|
|
297
|
+
async function recoverCheckout(input) {
|
|
298
|
+
const { repository, path, predecessor, candidate, predecessorTree, candidateTree } = input;
|
|
299
|
+
const changedPaths = await gitPaths(repository, path, ["diff", "--name-only", "-z", predecessorTree, candidateTree]);
|
|
300
|
+
if (changedPaths.length === 0)
|
|
301
|
+
return "complete";
|
|
302
|
+
const workspaceTree = (await captureWorkspaceTree(repository, path)).tree;
|
|
303
|
+
const candidateIndex = await indexMatchesTreeOnPaths(repository, path, candidateTree, changedPaths);
|
|
304
|
+
const candidateWorkspace = await workspaceMatchesTreeOnPaths(repository, path, candidateTree, workspaceTree, changedPaths);
|
|
305
|
+
if (candidateIndex && candidateWorkspace)
|
|
306
|
+
return "complete";
|
|
307
|
+
if (workspaceTree === candidateTree) {
|
|
308
|
+
await runGit(repository, ["-C", path, "read-tree", gitObjectIdForSnapshot(candidate)]);
|
|
309
|
+
return "recovered";
|
|
310
|
+
}
|
|
311
|
+
const predecessorIndex = await indexMatchesTreeOnPaths(repository, path, predecessorTree, changedPaths);
|
|
312
|
+
if (predecessorIndex && candidateWorkspace) {
|
|
313
|
+
await runGit(repository, [
|
|
314
|
+
"-C",
|
|
315
|
+
path,
|
|
316
|
+
"read-tree",
|
|
317
|
+
"-i",
|
|
318
|
+
"-m",
|
|
319
|
+
gitObjectIdForSnapshot(predecessor),
|
|
320
|
+
gitObjectIdForSnapshot(candidate),
|
|
321
|
+
]);
|
|
322
|
+
return "recovered";
|
|
323
|
+
}
|
|
324
|
+
const predecessorWorkspace = await workspaceMatchesTreeOnPaths(repository, path, predecessorTree, workspaceTree, changedPaths);
|
|
325
|
+
if (!predecessorIndex || !predecessorWorkspace)
|
|
326
|
+
return "retained";
|
|
327
|
+
await runGit(repository, [
|
|
328
|
+
"-C",
|
|
329
|
+
path,
|
|
330
|
+
"read-tree",
|
|
331
|
+
"-m",
|
|
332
|
+
"-u",
|
|
333
|
+
gitObjectIdForSnapshot(predecessor),
|
|
334
|
+
gitObjectIdForSnapshot(candidate),
|
|
335
|
+
]);
|
|
336
|
+
return "recovered";
|
|
337
|
+
}
|
|
338
|
+
export async function recoverTargetPlacement(repository, state) {
|
|
339
|
+
const target = state.coordinates.target;
|
|
340
|
+
const delivery = state.delivery;
|
|
341
|
+
if (state.terminal?.kind !== "claimed" || target === undefined || delivery === null)
|
|
342
|
+
return { effects: [], lag: [] };
|
|
343
|
+
if (await readRef(repository, target) !== delivery.data.integration.snapshot)
|
|
344
|
+
return { effects: [], lag: [] };
|
|
345
|
+
const candidateTree = await commitTree(repository, delivery.data.integration.snapshot);
|
|
346
|
+
const predecessorTree = await commitTree(repository, delivery.data.integration.predecessor);
|
|
347
|
+
const worktrees = (await registeredWorktrees(repository))
|
|
348
|
+
.filter((worktree) => worktree.branch === target)
|
|
349
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
350
|
+
const effects = [];
|
|
351
|
+
const lag = [];
|
|
352
|
+
for (const worktree of worktrees) {
|
|
353
|
+
try {
|
|
354
|
+
const recovery = await recoverCheckout({
|
|
355
|
+
repository,
|
|
356
|
+
path: worktree.path,
|
|
357
|
+
predecessor: delivery.data.integration.predecessor,
|
|
358
|
+
candidate: delivery.data.integration.snapshot,
|
|
359
|
+
predecessorTree,
|
|
360
|
+
candidateTree,
|
|
361
|
+
});
|
|
362
|
+
if (recovery === "retained") {
|
|
363
|
+
lag.push(recoveryLag(worktree.path, target, "target checkout entries are neither predecessor nor candidate"));
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (recovery === "recovered") {
|
|
367
|
+
effects.push({ kind: "target-checkout", path: worktree.path, target, action: "recovered" });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
lag.push(recoveryLag(worktree.path, target, diagnostic(error)));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return { effects, lag };
|
|
375
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Preparation } from "../core/decide.js";
|
|
2
|
+
import type { ContractCoordinates, ContractId, SnapshotId } from "../core/facts/types.js";
|
|
3
|
+
import { type GitObjectId } from "./identity.js";
|
|
4
|
+
import { type GitRepository } from "./repository.js";
|
|
5
|
+
import { captureWorkspaceTree } from "./workspace.js";
|
|
6
|
+
export type TenderCaptureCoordinates = Readonly<{
|
|
7
|
+
contractId: ContractId;
|
|
8
|
+
coordinates: ContractCoordinates;
|
|
9
|
+
place?: string;
|
|
10
|
+
}>;
|
|
11
|
+
export type TenderCaptureRefusal = Readonly<{
|
|
12
|
+
kind: "worktree-missing";
|
|
13
|
+
contractId: ContractId;
|
|
14
|
+
}>;
|
|
15
|
+
export type WorkspaceDirtyDelta = Readonly<{
|
|
16
|
+
staged: readonly string[];
|
|
17
|
+
unstaged: readonly string[];
|
|
18
|
+
untracked: readonly string[];
|
|
19
|
+
shortStat: Readonly<{
|
|
20
|
+
filesChanged: number;
|
|
21
|
+
insertions: number;
|
|
22
|
+
deletions: number;
|
|
23
|
+
}>;
|
|
24
|
+
}>;
|
|
25
|
+
export type DirtyWorkspaceRefusal = Readonly<{
|
|
26
|
+
kind: "dirty-workspace";
|
|
27
|
+
contractId: ContractId;
|
|
28
|
+
staged: readonly string[];
|
|
29
|
+
unstaged: readonly string[];
|
|
30
|
+
untracked: readonly string[];
|
|
31
|
+
submodules: readonly string[];
|
|
32
|
+
shortStat: WorkspaceDirtyDelta["shortStat"];
|
|
33
|
+
}>;
|
|
34
|
+
export type TenderCapture = Readonly<{
|
|
35
|
+
tree: GitObjectId;
|
|
36
|
+
head: SnapshotId;
|
|
37
|
+
dirty: boolean;
|
|
38
|
+
changes: Awaited<ReturnType<typeof captureWorkspaceTree>>["changes"];
|
|
39
|
+
}>;
|
|
40
|
+
/** Capture the complete workspace tree through Git's private index mechanics. */
|
|
41
|
+
export declare function captureTender(repository: GitRepository, input: TenderCaptureCoordinates): Promise<Preparation<TenderCapture, TenderCaptureRefusal>>;
|
|
42
|
+
export declare function dirtyTenderRefusal(repository: GitRepository, contractId: ContractId, tender: TenderCapture): Promise<DirtyWorkspaceRefusal>;
|
|
43
|
+
export declare function dirtyTenderDelta(repository: GitRepository, tender: TenderCapture): Promise<WorkspaceDirtyDelta | undefined>;
|
|
44
|
+
/** Materialize the tender tree when it contains admitted dirty workspace bytes. */
|
|
45
|
+
export declare function materializeTenderSnapshot(repository: GitRepository, tender: TenderCapture, input: Readonly<{
|
|
46
|
+
contractId: ContractId;
|
|
47
|
+
title: string;
|
|
48
|
+
message?: string;
|
|
49
|
+
}>): Promise<SnapshotId>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { gitObjectIdForSnapshot, mintSnapshotId } from "./identity.js";
|
|
3
|
+
import { decodeGitNumstat, registeredWorktreePaths, runGit, runGitWithEnvironment } from "./repository.js";
|
|
4
|
+
import { captureWorkspaceTree, worktreePath } from "./workspace.js";
|
|
5
|
+
async function workspaceExists(repository, workspace, path) {
|
|
6
|
+
const exists = await access(path).then(() => true, () => false);
|
|
7
|
+
return workspace === "here"
|
|
8
|
+
|| (exists && (await registeredWorktreePaths(repository)).includes(path));
|
|
9
|
+
}
|
|
10
|
+
function workspaceFor(repository, input) {
|
|
11
|
+
if (input.coordinates.workspace === "here")
|
|
12
|
+
return repository.effectiveCwd;
|
|
13
|
+
return input.place === undefined ? undefined : worktreePath(repository, input.place);
|
|
14
|
+
}
|
|
15
|
+
/** Capture the complete workspace tree through Git's private index mechanics. */
|
|
16
|
+
export async function captureTender(repository, input) {
|
|
17
|
+
const workspace = workspaceFor(repository, input);
|
|
18
|
+
if (workspace === undefined || !(await workspaceExists(repository, input.coordinates.workspace, workspace))) {
|
|
19
|
+
return { kind: "refused", refusal: { kind: "worktree-missing", contractId: input.contractId } };
|
|
20
|
+
}
|
|
21
|
+
return { kind: "prepared", data: await captureWorkspaceTree(repository, workspace) };
|
|
22
|
+
}
|
|
23
|
+
async function dirtyShortStat(repository, tender) {
|
|
24
|
+
return decodeGitNumstat(await runGit(repository, [
|
|
25
|
+
"diff",
|
|
26
|
+
"--numstat",
|
|
27
|
+
"-z",
|
|
28
|
+
gitObjectIdForSnapshot(tender.head),
|
|
29
|
+
tender.tree,
|
|
30
|
+
]));
|
|
31
|
+
}
|
|
32
|
+
export async function dirtyTenderRefusal(repository, contractId, tender) {
|
|
33
|
+
return {
|
|
34
|
+
kind: "dirty-workspace",
|
|
35
|
+
contractId,
|
|
36
|
+
...tender.changes,
|
|
37
|
+
shortStat: await dirtyShortStat(repository, tender),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export async function dirtyTenderDelta(repository, tender) {
|
|
41
|
+
if (!tender.dirty)
|
|
42
|
+
return undefined;
|
|
43
|
+
const { staged, unstaged, untracked } = tender.changes;
|
|
44
|
+
return { staged, unstaged, untracked, shortStat: await dirtyShortStat(repository, tender) };
|
|
45
|
+
}
|
|
46
|
+
function commitMessage(contractId, title, message) {
|
|
47
|
+
const subject = message ?? `${contractId}: ${title}`;
|
|
48
|
+
return `${subject}\n\nKeiyaku-Contract: ${contractId}\n`;
|
|
49
|
+
}
|
|
50
|
+
/** Materialize the tender tree when it contains admitted dirty workspace bytes. */
|
|
51
|
+
export async function materializeTenderSnapshot(repository, tender, input) {
|
|
52
|
+
if (!tender.dirty)
|
|
53
|
+
return tender.head;
|
|
54
|
+
const commit = (await runGitWithEnvironment(repository, ["commit-tree", tender.tree, "-p", gitObjectIdForSnapshot(tender.head)], commitMessage(input.contractId, input.title, input.message), {
|
|
55
|
+
GIT_AUTHOR_NAME: "Keiyaku",
|
|
56
|
+
GIT_AUTHOR_EMAIL: "keiyaku@localhost",
|
|
57
|
+
GIT_COMMITTER_NAME: "Keiyaku",
|
|
58
|
+
GIT_COMMITTER_EMAIL: "keiyaku@localhost",
|
|
59
|
+
GIT_AUTHOR_DATE: "Thu, 01 Jan 1970 00:00:00 +0000",
|
|
60
|
+
GIT_COMMITTER_DATE: "Thu, 01 Jan 1970 00:00:00 +0000",
|
|
61
|
+
})).toString("utf8").trim();
|
|
62
|
+
return mintSnapshotId(commit);
|
|
63
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ContractState, SnapshotId } from "../core/facts/types.js";
|
|
2
|
+
import { type GitObjectId } from "./identity.js";
|
|
3
|
+
import { type GitOid, type GitRepository } from "./repository.js";
|
|
4
|
+
export type UnsealedBytes = Readonly<{
|
|
5
|
+
kind: "unsealed-bytes";
|
|
6
|
+
path: string;
|
|
7
|
+
paths: readonly string[];
|
|
8
|
+
head?: SnapshotId;
|
|
9
|
+
}>;
|
|
10
|
+
export type TerminalSealExpectations = Readonly<{
|
|
11
|
+
heads: readonly SnapshotId[];
|
|
12
|
+
trees: readonly GitObjectId[];
|
|
13
|
+
treeBySnapshot: ReadonlyMap<SnapshotId, GitObjectId>;
|
|
14
|
+
}>;
|
|
15
|
+
export type TerminalSealObject = Readonly<{
|
|
16
|
+
kind: "present";
|
|
17
|
+
type: string;
|
|
18
|
+
bytes: Buffer;
|
|
19
|
+
}> | Readonly<{
|
|
20
|
+
kind: "missing";
|
|
21
|
+
}>;
|
|
22
|
+
export declare function terminalSealSnapshots(state: ContractState): readonly SnapshotId[];
|
|
23
|
+
export declare function terminalSealExpectations(state: ContractState, objects: ReadonlyMap<GitOid, TerminalSealObject>): TerminalSealExpectations;
|
|
24
|
+
export declare function unsealedBytes(repository: GitRepository, path: string, expected: TerminalSealExpectations): Promise<UnsealedBytes | null>;
|