@astrosheep/keiyaku 4.0.1 → 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 +137 -5
- 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/package.json +1 -1
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
- package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
- package/build/src/akuma/abort.d.ts +2 -0
- package/build/src/akuma/abort.js +50 -0
- package/build/src/akuma/akuma.d.ts +23 -14
- package/build/src/akuma/akuma.js +120 -104
- package/build/src/akuma/archetype.d.ts +7 -6
- package/build/src/akuma/archetype.js +68 -41
- package/build/src/akuma/body.d.ts +3 -5
- package/build/src/akuma/body.js +408 -213
- package/build/src/akuma/heart/facts.d.ts +25 -34
- package/build/src/akuma/heart/facts.js +18 -7
- package/build/src/akuma/heart/index.d.ts +39 -42
- package/build/src/akuma/heart/index.js +134 -93
- package/build/src/akuma/heart/rows.d.ts +9 -31
- package/build/src/akuma/heart/rows.js +26 -44
- package/build/src/akuma/heart/schema.d.ts +1 -1
- package/build/src/akuma/heart/schema.js +7 -15
- 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 +28 -14
- package/build/src/akuma/heart/storage.js +86 -26
- package/build/src/akuma/identity.d.ts +2 -2
- package/build/src/akuma/identity.js +9 -9
- package/build/src/akuma/index.d.ts +6 -1
- package/build/src/akuma/index.js +6 -0
- package/build/src/akuma/projection.d.ts +117 -38
- package/build/src/akuma/projection.js +208 -97
- 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 +12 -4
- package/build/src/akuma/provider.js +117 -70
- 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/events.js +66 -13
- package/build/src/akuma/providers/claude/index.d.ts +2 -1
- package/build/src/akuma/providers/claude/index.js +35 -21
- package/build/src/akuma/providers/codex-app-server/events.js +4 -1
- package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
- package/build/src/akuma/providers/codex-app-server/index.js +24 -31
- 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 +24 -59
- package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
- package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
- package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
- package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
- package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
- package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
- package/build/src/akuma/providers/pi/events.js +49 -11
- package/build/src/akuma/providers/pi/index.d.ts +1 -1
- package/build/src/akuma/providers/pi/index.js +50 -21
- 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 -7
- package/build/src/akuma/requests.js +92 -65
- package/build/src/alias/index.d.ts +2 -2
- package/build/src/alias/index.js +10 -10
- 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/cli/accepted.d.ts +17 -13
- package/build/src/cli/accepted.js +72 -34
- package/build/src/cli/actor.js +3 -3
- package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
- package/build/src/cli/commands/akuma-invoke.js +14 -9
- package/build/src/cli/commands/akuma.d.ts +13 -3
- package/build/src/cli/commands/akuma.js +67 -34
- package/build/src/cli/commands/contract.d.ts +18 -5
- package/build/src/cli/commands/contract.js +9 -3
- package/build/src/cli/commands/task-invoke.d.ts +17 -4
- package/build/src/cli/commands/task-invoke.js +43 -20
- 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 +51 -13
- 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 +4 -3
- package/build/src/cli/invoke.js +160 -179
- package/build/src/cli/main.js +34 -6
- package/build/src/cli/parse.d.ts +9 -2
- package/build/src/cli/parse.js +29 -7
- 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 +2 -2
- package/build/src/cli/render/akuma-tool.js +30 -4
- package/build/src/cli/render/akuma.d.ts +1 -0
- package/build/src/cli/render/akuma.js +211 -116
- package/build/src/cli/render/audit.d.ts +3 -0
- package/build/src/cli/render/audit.js +104 -0
- package/build/src/cli/render/contract.d.ts +3 -3
- package/build/src/cli/render/contract.js +192 -79
- package/build/src/cli/render/kanshi.js +263 -168
- 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 -20
- 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 +7 -0
- package/build/src/cli/render/terminal.js +46 -0
- package/build/src/cli/render/text.js +6 -3
- package/build/src/cli/result.d.ts +115 -20
- package/build/src/cli/usage.d.ts +1 -0
- package/build/src/cli/usage.js +3 -0
- package/build/src/contract-worktree.d.ts +6 -5
- package/build/src/contract-worktree.js +95 -57
- package/build/src/coordination/durable-file.d.ts +4 -3
- package/build/src/coordination/durable-file.js +29 -25
- 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/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/verbs/amend.d.ts +1 -1
- package/build/src/core/verbs/amend.js +0 -11
- package/build/src/core/verbs/deliver.d.ts +1 -1
- package/build/src/core/verbs/deliver.js +1 -7
- package/build/src/core/verbs/placement.d.ts +1 -1
- package/build/src/core/verbs/placement.js +4 -1
- package/build/src/dispatch/index.d.ts +2 -2
- package/build/src/dispatch/index.js +17 -17
- package/build/src/git/admission.d.ts +1 -1
- package/build/src/git/admission.js +11 -11
- package/build/src/git/hooks.js +13 -13
- package/build/src/git/integration.d.ts +14 -15
- package/build/src/git/integration.js +80 -44
- package/build/src/git/observe.d.ts +5 -3
- package/build/src/git/observe.js +18 -12
- package/build/src/git/read-observation.js +9 -9
- package/build/src/git/reconcile.d.ts +2 -0
- package/build/src/git/reconcile.js +138 -95
- package/build/src/git/repository.d.ts +27 -19
- package/build/src/git/repository.js +155 -83
- package/build/src/git/scratch.d.ts +7 -3
- package/build/src/git/scratch.js +44 -37
- package/build/src/git/target-placement.d.ts +40 -4
- package/build/src/git/target-placement.js +221 -101
- package/build/src/git/tender.d.ts +6 -5
- package/build/src/git/tender.js +24 -43
- package/build/src/git/terminal-seal.d.ts +1 -1
- package/build/src/git/terminal-seal.js +7 -5
- package/build/src/git/workspace.d.ts +36 -4
- package/build/src/git/workspace.js +53 -18
- package/build/src/identity/selector.js +1 -1
- package/build/src/index.d.ts +3 -3
- package/build/src/index.js +1 -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 +2 -1
- package/build/src/kanshi/read.js +118 -27
- package/build/src/kanshi/report.d.ts +50 -0
- package/build/src/kanshi/select.d.ts +5 -0
- package/build/src/kanshi/select.js +32 -0
- package/build/src/library/address.d.ts +12 -3
- package/build/src/library/address.js +77 -46
- package/build/src/library/akuma-creation.d.ts +6 -1
- package/build/src/library/akuma-creation.js +86 -13
- package/build/src/library/audit.d.ts +18 -0
- package/build/src/library/audit.js +44 -0
- package/build/src/library/bind.js +4 -4
- package/build/src/library/catalog.d.ts +1 -1
- package/build/src/library/catalog.js +5 -8
- package/build/src/library/contract.d.ts +5 -6
- package/build/src/library/contract.js +11 -35
- package/build/src/library/delivery.d.ts +2 -1
- package/build/src/library/fleet.d.ts +2 -2
- package/build/src/library/fleet.js +35 -71
- package/build/src/library/keiyaku.d.ts +3 -2
- package/build/src/library/keiyaku.js +1 -0
- package/build/src/library/mutation.d.ts +7 -6
- package/build/src/library/mutation.js +23 -25
- package/build/src/library/reconcile.d.ts +29 -0
- package/build/src/library/reconcile.js +179 -0
- package/build/src/library/region.d.ts +12 -0
- package/build/src/library/region.js +15 -1
- package/build/src/library/repo.d.ts +4 -16
- package/build/src/library/repo.js +13 -38
- package/build/src/protocol/attempt.js +6 -6
- package/build/src/protocol/bind.js +4 -4
- package/build/src/protocol/intent.d.ts +15 -2
- package/build/src/protocol/intent.js +21 -6
- package/build/src/protocol/operations.d.ts +73 -22
- package/build/src/protocol/operations.js +292 -88
- package/build/src/protocol/outcome.d.ts +9 -3
- package/build/src/protocol/outcome.js +3 -1
- package/build/src/protocol/placement.d.ts +15 -4
- package/build/src/protocol/placement.js +58 -25
- package/build/src/protocol/read/status.d.ts +8 -1
- package/build/src/protocol/read/status.js +61 -14
- package/build/src/runtime/proc/line-rpc.d.ts +3 -8
- package/build/src/runtime/proc/line-rpc.js +16 -43
- package/build/src/runtime/proc/run.d.ts +15 -29
- package/build/src/runtime/proc/run.js +113 -105
- 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 +2 -2
- package/build/src/settings.js +8 -8
- package/build/src/settlement/fence.d.ts +0 -4
- package/build/src/settlement/fence.js +0 -9
- package/build/src/settlement/holder.d.ts +11 -1
- package/build/src/settlement/holder.js +25 -0
- package/build/src/settlement/settle.d.ts +1 -1
- package/build/src/settlement/settle.js +52 -89
- package/build/src/task/board.d.ts +3 -3
- package/build/src/task/board.js +6 -9
- package/build/src/task/compose.d.ts +1 -1
- package/build/src/task/compose.js +18 -10
- package/build/src/task/context.d.ts +3 -3
- package/build/src/task/context.js +14 -14
- package/build/src/task/document.d.ts +2 -1
- package/build/src/task/document.js +15 -5
- package/build/src/task/index.d.ts +19 -7
- package/build/src/task/index.js +42 -23
- package/build/src/task/operations.d.ts +15 -9
- package/build/src/task/operations.js +78 -48
- package/build/src/task/query.d.ts +67 -0
- package/build/src/task/query.js +279 -0
- package/build/src/task/store.d.ts +2 -2
- package/build/src/task/store.js +40 -25
- package/build/src/verification/execution.d.ts +4 -2
- package/build/src/verification/execution.js +9 -5
- package/build/src/workspace-place.d.ts +41 -0
- package/build/src/workspace-place.js +386 -0
- package/build/src/world.d.ts +13 -2
- package/build/src/world.js +81 -48
- package/package.json +3 -2
- package/build/src/protocol/read/audit.d.ts +0 -31
- package/build/src/protocol/read/audit.js +0 -55
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import type { AuditReport,
|
|
1
|
+
import type { AuditReport, ContractId, Fact, MutationResult, PlacementStop, ReconcileReport, Review, VerificationReuse, VerificationStop } from "../index.js";
|
|
2
2
|
import type { KanshiReport } from "../kanshi/index.js";
|
|
3
|
+
import type { RegionRead, Section } from "../kanshi/index.js";
|
|
3
4
|
import type { Catalog } from "../index.js";
|
|
5
|
+
import type { RegionObservation } from "../library/region.js";
|
|
6
|
+
export type BindDraftReceipt = Readonly<{
|
|
7
|
+
path: string;
|
|
8
|
+
warning?: string;
|
|
9
|
+
} | {
|
|
10
|
+
path?: never;
|
|
11
|
+
warning: string;
|
|
12
|
+
}>;
|
|
4
13
|
export type Effect = ReconcileReport["effects"][number];
|
|
5
14
|
export type AcceptedFact = Readonly<{
|
|
6
15
|
contract: ContractId;
|
|
@@ -8,36 +17,117 @@ export type AcceptedFact = Readonly<{
|
|
|
8
17
|
kind: Fact["kind"];
|
|
9
18
|
}>;
|
|
10
19
|
export type Lag = ReconcileReport["lag"][number];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
integrationSnapshot: SnapshotId;
|
|
14
|
-
changeId: ChangeId;
|
|
15
|
-
}>;
|
|
16
|
-
export type AcceptedResult = Readonly<{
|
|
20
|
+
type MutationEnvelope = Pick<MutationResult<unknown>, "head" | "effects" | "settlement">;
|
|
21
|
+
export type AcceptedEnvelope = Readonly<{
|
|
17
22
|
kind: "accepted";
|
|
18
|
-
verb: string;
|
|
19
23
|
contract: ContractId;
|
|
20
|
-
head:
|
|
24
|
+
head: MutationEnvelope["head"];
|
|
21
25
|
facts: readonly AcceptedFact[];
|
|
22
|
-
effects:
|
|
23
|
-
settlement:
|
|
24
|
-
|
|
26
|
+
effects: MutationEnvelope["effects"];
|
|
27
|
+
settlement: MutationEnvelope["settlement"];
|
|
28
|
+
lag?: readonly [Lag, ...Lag[]];
|
|
29
|
+
}>;
|
|
30
|
+
export type AcceptedBindResult = AcceptedEnvelope & Readonly<{
|
|
31
|
+
verb: "bind";
|
|
32
|
+
target: string | null;
|
|
33
|
+
verification?: never;
|
|
34
|
+
verificationReuse?: never;
|
|
35
|
+
placement?: never;
|
|
36
|
+
cleanup?: never;
|
|
37
|
+
leak?: never;
|
|
38
|
+
report?: never;
|
|
39
|
+
workspace?: never;
|
|
40
|
+
diff?: never;
|
|
41
|
+
}> & RegionObservation;
|
|
42
|
+
export type AcceptedAmendResult = AcceptedEnvelope & Readonly<{
|
|
43
|
+
verb: "amend";
|
|
44
|
+
diff: string;
|
|
45
|
+
target?: never;
|
|
46
|
+
verification?: never;
|
|
47
|
+
verificationReuse?: never;
|
|
48
|
+
placement?: never;
|
|
49
|
+
cleanup?: never;
|
|
50
|
+
leak?: never;
|
|
51
|
+
report?: never;
|
|
52
|
+
workspace?: never;
|
|
53
|
+
}> & RegionObservation;
|
|
54
|
+
export type AcceptedDeliverResult = AcceptedEnvelope & Readonly<{
|
|
55
|
+
verb: "deliver";
|
|
25
56
|
verification?: VerificationStop;
|
|
57
|
+
verificationReuse?: VerificationReuse;
|
|
58
|
+
placement?: PlacementStop;
|
|
59
|
+
cleanup?: MutationResult<unknown>["cleanup"];
|
|
60
|
+
leak?: MutationResult<unknown>["leak"];
|
|
61
|
+
target?: never;
|
|
62
|
+
overlaps?: never;
|
|
63
|
+
overlapFailure?: never;
|
|
64
|
+
report?: never;
|
|
65
|
+
workspace?: never;
|
|
66
|
+
diff?: never;
|
|
67
|
+
}>;
|
|
68
|
+
export type AcceptedReviewResult = AcceptedEnvelope & Readonly<{
|
|
69
|
+
verb: "review";
|
|
26
70
|
placement?: PlacementStop;
|
|
27
|
-
cleanup?: NonNullable<AuditReport["cleanup"]>;
|
|
28
|
-
leak?: NonNullable<AuditReport["leak"]>;
|
|
29
|
-
overlaps?: readonly RegionOverlap[];
|
|
30
|
-
overlapFailure?: string;
|
|
31
|
-
report?: AuditReport;
|
|
32
71
|
workspace?: Review["workspace"];
|
|
33
|
-
|
|
34
|
-
|
|
72
|
+
target?: never;
|
|
73
|
+
verification?: never;
|
|
74
|
+
verificationReuse?: never;
|
|
75
|
+
cleanup?: never;
|
|
76
|
+
leak?: never;
|
|
77
|
+
overlaps?: never;
|
|
78
|
+
overlapFailure?: never;
|
|
79
|
+
report?: never;
|
|
80
|
+
diff?: never;
|
|
81
|
+
}>;
|
|
82
|
+
export type AcceptedArcResult = AcceptedEnvelope & Readonly<{
|
|
83
|
+
verb: "arc";
|
|
84
|
+
target?: never;
|
|
85
|
+
verification?: never;
|
|
86
|
+
verificationReuse?: never;
|
|
87
|
+
placement?: never;
|
|
88
|
+
cleanup?: never;
|
|
89
|
+
leak?: never;
|
|
90
|
+
overlaps?: never;
|
|
91
|
+
overlapFailure?: never;
|
|
92
|
+
report?: never;
|
|
93
|
+
workspace?: never;
|
|
94
|
+
diff?: never;
|
|
95
|
+
}>;
|
|
96
|
+
export type AcceptedAbandonResult = AcceptedEnvelope & Readonly<{
|
|
97
|
+
verb: "abandon";
|
|
98
|
+
target?: never;
|
|
99
|
+
verification?: never;
|
|
100
|
+
verificationReuse?: never;
|
|
101
|
+
placement?: never;
|
|
102
|
+
cleanup?: never;
|
|
103
|
+
leak?: never;
|
|
104
|
+
overlaps?: never;
|
|
105
|
+
overlapFailure?: never;
|
|
106
|
+
report?: never;
|
|
107
|
+
workspace?: never;
|
|
108
|
+
diff?: never;
|
|
35
109
|
}>;
|
|
110
|
+
export type AcceptedAuditResult = AcceptedEnvelope & Readonly<{
|
|
111
|
+
verb: "audit";
|
|
112
|
+
report: AuditReport;
|
|
113
|
+
cleanup?: MutationResult<unknown>["cleanup"];
|
|
114
|
+
leak?: MutationResult<unknown>["leak"];
|
|
115
|
+
target?: never;
|
|
116
|
+
verification?: never;
|
|
117
|
+
verificationReuse?: never;
|
|
118
|
+
placement?: never;
|
|
119
|
+
overlaps?: never;
|
|
120
|
+
overlapFailure?: never;
|
|
121
|
+
workspace?: never;
|
|
122
|
+
diff?: never;
|
|
123
|
+
}>;
|
|
124
|
+
export type AcceptedResult = AcceptedBindResult | AcceptedAmendResult | AcceptedDeliverResult | AcceptedReviewResult | AcceptedArcResult | AcceptedAbandonResult | AcceptedAuditResult;
|
|
36
125
|
export type RefusedResult = Readonly<{
|
|
37
126
|
kind: "refused";
|
|
38
127
|
verb: string;
|
|
39
128
|
contract?: ContractId;
|
|
40
129
|
refusal: unknown;
|
|
130
|
+
draft?: BindDraftReceipt;
|
|
41
131
|
}>;
|
|
42
132
|
export type RetryResult = Readonly<{
|
|
43
133
|
kind: "retry";
|
|
@@ -60,8 +150,13 @@ export type StatusResult = Readonly<{
|
|
|
60
150
|
report: KanshiReport;
|
|
61
151
|
selection: "world" | "contract";
|
|
62
152
|
}>;
|
|
153
|
+
export type RegionResult = Readonly<{
|
|
154
|
+
kind: "region";
|
|
155
|
+
region: Section<RegionRead>;
|
|
156
|
+
}>;
|
|
63
157
|
export type CatalogResult = Readonly<{
|
|
64
158
|
kind: "catalog";
|
|
65
159
|
catalog: Catalog;
|
|
66
160
|
}>;
|
|
67
|
-
export type InvocationResult = AcceptedResult | RefusedResult | RetryResult | ObservationResult | GuidanceResult | StatusResult | CatalogResult;
|
|
161
|
+
export type InvocationResult = AcceptedResult | RefusedResult | RetryResult | ObservationResult | GuidanceResult | StatusResult | RegionResult | CatalogResult;
|
|
162
|
+
export {};
|
package/build/src/cli/usage.d.ts
CHANGED
|
@@ -3,4 +3,5 @@ export declare class CliUsageError extends Error {
|
|
|
3
3
|
readonly projection?: string | undefined;
|
|
4
4
|
constructor(diagnostic: string, projection?: string | undefined);
|
|
5
5
|
}
|
|
6
|
+
export declare function isBlankInput(value: string): boolean;
|
|
6
7
|
export declare function usageLine(usage: string): string;
|
package/build/src/cli/usage.js
CHANGED
|
@@ -8,6 +8,9 @@ export class CliUsageError extends Error {
|
|
|
8
8
|
this.name = "CliUsageError";
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
export function isBlankInput(value) {
|
|
12
|
+
return value.trim().length === 0;
|
|
13
|
+
}
|
|
11
14
|
export function usageLine(usage) {
|
|
12
15
|
return usage.split("\n").map((line, index) => {
|
|
13
16
|
if (index === 0)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type DerivedFileAction } from "./coordination/durable-file.js";
|
|
2
2
|
import { type ContractId, type ContractState } from "./core/facts/types.js";
|
|
3
3
|
import { type GitRepository } from "./git/repository.js";
|
|
4
|
+
import { type PlaceRegister } from "./workspace-place.js";
|
|
4
5
|
export declare function renderContractGuidance(state: ContractState): string;
|
|
5
6
|
export type ContractFileEffect = Readonly<{
|
|
6
7
|
kind: "contract-file";
|
|
@@ -28,7 +29,7 @@ export type ContractAppointment = Readonly<{
|
|
|
28
29
|
kind: "invalid";
|
|
29
30
|
path: string;
|
|
30
31
|
}>;
|
|
31
|
-
export declare function readContractAppointment(repository: GitRepository): ContractAppointment
|
|
32
|
+
export declare function readContractAppointment(repository: GitRepository): Promise<ContractAppointment>;
|
|
32
33
|
export type ContractReservation = Readonly<{
|
|
33
34
|
kind: "reserved";
|
|
34
35
|
path: string;
|
|
@@ -36,8 +37,8 @@ export type ContractReservation = Readonly<{
|
|
|
36
37
|
kind: "absent";
|
|
37
38
|
path: string;
|
|
38
39
|
}>>;
|
|
39
|
-
export declare function reserveContractWorktree(repository: GitRepository, contract: ContractId): ContractReservation
|
|
40
|
+
export declare function reserveContractWorktree(repository: GitRepository, contract: ContractId): Promise<ContractReservation>;
|
|
40
41
|
export declare function withContractWorktreeAppointment<T>(repository: GitRepository, action: () => T | Promise<T>): Promise<T>;
|
|
41
|
-
export declare function releaseContractWorktree(repository: GitRepository, contract: ContractId): void
|
|
42
|
-
export declare function removeContractWorktreeAppointment(repository: GitRepository, contract: ContractId): void
|
|
43
|
-
export declare function projectContractWorktree(repository: GitRepository, state: ContractState | null): ContractWorktreeResult
|
|
42
|
+
export declare function releaseContractWorktree(repository: GitRepository, contract: ContractId): Promise<void>;
|
|
43
|
+
export declare function removeContractWorktreeAppointment(repository: GitRepository, contract: ContractId): Promise<void>;
|
|
44
|
+
export declare function projectContractWorktree(repository: GitRepository, state: ContractState | null, register?: PlaceRegister): Promise<ContractWorktreeResult>;
|
|
@@ -1,13 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { chmod, lstat, readFile, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { acquireSqliteTransactionLock } from "./coordination/sqlite-transaction-lock.js";
|
|
4
4
|
import { createFileDurablyExclusive, repairDerivedFile, } from "./coordination/durable-file.js";
|
|
5
5
|
import { contractId } from "./core/facts/types.js";
|
|
6
6
|
import { GitPlumbingError, runGit, worktreeGitDirectory, worktreeRoot, } from "./git/repository.js";
|
|
7
|
-
import {
|
|
7
|
+
import { worktreePath } from "./git/workspace.js";
|
|
8
|
+
import { appointmentFor, placeRegisterPath, readPlaceRegister, } from "./workspace-place.js";
|
|
8
9
|
const IGNORE_BYTES = ".gitignore\nKEIYAKU.md\n";
|
|
10
|
+
const APPOINTMENT_DESCRIPTION = "This is a read-only projection. Do not edit manually.";
|
|
9
11
|
function appointmentBytes(contract) {
|
|
10
|
-
return `---\ncontract: ${contract}\n---\n`;
|
|
12
|
+
return `---\ncontract: ${contract}\ndescription: ${APPOINTMENT_DESCRIPTION}\n---\n`;
|
|
13
|
+
}
|
|
14
|
+
function appointedContract(bytes) {
|
|
15
|
+
const lines = bytes.split(/\r?\n/u);
|
|
16
|
+
if (lines[0] !== "---")
|
|
17
|
+
return undefined;
|
|
18
|
+
const close = lines.indexOf("---", 1);
|
|
19
|
+
if (close !== 2 && close !== 3)
|
|
20
|
+
return undefined;
|
|
21
|
+
const identity = lines[1];
|
|
22
|
+
const description = lines[2];
|
|
23
|
+
if (identity === undefined || !identity.startsWith("contract: "))
|
|
24
|
+
return undefined;
|
|
25
|
+
if (close === 3 && (description === undefined || !description.startsWith("description: ")))
|
|
26
|
+
return undefined;
|
|
27
|
+
try {
|
|
28
|
+
return contractId(identity.slice("contract: ".length));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
11
33
|
}
|
|
12
34
|
function renderArc(arc) {
|
|
13
35
|
return [
|
|
@@ -25,10 +47,12 @@ const FULFILLMENT = [
|
|
|
25
47
|
"Read the complete Contract before acting and keep work inside that worktree.", "",
|
|
26
48
|
"### Deliverer", "",
|
|
27
49
|
"Implement and verify the Objective under the Design, Region, and Criteria in this Contract.",
|
|
50
|
+
"When an Arc is active, stay within that current chapter.",
|
|
28
51
|
"Leave lifecycle decisions to the caller; report the candidate, verification performed, and any unmet term.", "",
|
|
29
52
|
"### Reviewer", "",
|
|
30
|
-
"Review
|
|
31
|
-
"
|
|
53
|
+
"Review this Contract worktree against the complete Contract.",
|
|
54
|
+
"Review the complete current worktree snapshot, not a named candidate commit.",
|
|
55
|
+
"Do not modify it; report covered Criteria, findings, and missing evidence.",
|
|
32
56
|
].join("\n");
|
|
33
57
|
export function renderContractGuidance(state) {
|
|
34
58
|
return [
|
|
@@ -38,9 +62,9 @@ export function renderContractGuidance(state) {
|
|
|
38
62
|
FULFILLMENT,
|
|
39
63
|
].join("\n\n").concat("\n");
|
|
40
64
|
}
|
|
41
|
-
function isTracked(repository, relativePath) {
|
|
65
|
+
async function isTracked(repository, relativePath) {
|
|
42
66
|
try {
|
|
43
|
-
runGit(repository, ["ls-files", "--error-unmatch", "--", relativePath]);
|
|
67
|
+
await runGit(repository, ["ls-files", "--error-unmatch", "--", relativePath]);
|
|
44
68
|
return true;
|
|
45
69
|
}
|
|
46
70
|
catch (error) {
|
|
@@ -52,42 +76,39 @@ function isTracked(repository, relativePath) {
|
|
|
52
76
|
function generatedPath(worktree, name) {
|
|
53
77
|
return join(worktree, ".keiyaku", name);
|
|
54
78
|
}
|
|
55
|
-
export function readContractAppointment(repository) {
|
|
56
|
-
const path = generatedPath(worktreeRoot(repository), "KEIYAKU.md");
|
|
57
|
-
const stat =
|
|
79
|
+
export async function readContractAppointment(repository) {
|
|
80
|
+
const path = generatedPath(await worktreeRoot(repository), "KEIYAKU.md");
|
|
81
|
+
const stat = await lstat(path).catch((error) => {
|
|
82
|
+
if (error.code === "ENOENT")
|
|
83
|
+
return undefined;
|
|
84
|
+
throw error;
|
|
85
|
+
});
|
|
58
86
|
if (stat === undefined)
|
|
59
87
|
return { kind: "absent", path };
|
|
60
88
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
61
89
|
return { kind: "invalid", path };
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
return { kind: "invalid", path };
|
|
65
|
-
try {
|
|
66
|
-
return { kind: "appointed", path, contract: contractId(match[1]) };
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return { kind: "invalid", path };
|
|
70
|
-
}
|
|
90
|
+
const contract = appointedContract(await readFile(path, "utf8"));
|
|
91
|
+
return contract === undefined ? { kind: "invalid", path } : { kind: "appointed", path, contract };
|
|
71
92
|
}
|
|
72
|
-
export function reserveContractWorktree(repository, contract) {
|
|
73
|
-
const worktree = worktreeRoot(repository);
|
|
93
|
+
export async function reserveContractWorktree(repository, contract) {
|
|
94
|
+
const worktree = await worktreeRoot(repository);
|
|
74
95
|
const scoped = { ...repository, effectiveCwd: worktree };
|
|
75
96
|
const ignore = generatedPath(worktree, ".gitignore");
|
|
76
|
-
if (isTracked(scoped, ".keiyaku/.gitignore"))
|
|
97
|
+
if (await isTracked(scoped, ".keiyaku/.gitignore"))
|
|
77
98
|
throw new Error(`generated path is tracked by Git: ${ignore}`);
|
|
78
|
-
repairDerivedFile(ignore, IGNORE_BYTES);
|
|
99
|
+
await repairDerivedFile(ignore, IGNORE_BYTES);
|
|
79
100
|
const path = generatedPath(worktree, "KEIYAKU.md");
|
|
80
|
-
if (isTracked(scoped, ".keiyaku/KEIYAKU.md"))
|
|
101
|
+
if (await isTracked(scoped, ".keiyaku/KEIYAKU.md"))
|
|
81
102
|
throw new Error(`generated path is tracked by Git: ${path}`);
|
|
82
|
-
if (createFileDurablyExclusive(path, appointmentBytes(contract), 0o444))
|
|
103
|
+
if (await createFileDurablyExclusive(path, appointmentBytes(contract), 0o444))
|
|
83
104
|
return { kind: "reserved", path };
|
|
84
|
-
const appointment = readContractAppointment(repository);
|
|
105
|
+
const appointment = await readContractAppointment(repository);
|
|
85
106
|
return appointment.kind === "absent" ? { kind: "invalid", path } : appointment;
|
|
86
107
|
}
|
|
87
108
|
export async function withContractWorktreeAppointment(repository, action) {
|
|
88
|
-
const root = worktreeRoot(repository);
|
|
109
|
+
const root = await worktreeRoot(repository);
|
|
89
110
|
const lock = await acquireSqliteTransactionLock({
|
|
90
|
-
path: join(worktreeGitDirectory(repository, root), "keiyaku", "contract-worktree.sqlite"),
|
|
111
|
+
path: join(await worktreeGitDirectory(repository, root), "keiyaku", "contract-worktree.sqlite"),
|
|
91
112
|
mode: "immediate",
|
|
92
113
|
});
|
|
93
114
|
try {
|
|
@@ -97,38 +118,42 @@ export async function withContractWorktreeAppointment(repository, action) {
|
|
|
97
118
|
lock.close();
|
|
98
119
|
}
|
|
99
120
|
}
|
|
100
|
-
export function releaseContractWorktree(repository, contract) {
|
|
101
|
-
const appointment = readContractAppointment(repository);
|
|
121
|
+
export async function releaseContractWorktree(repository, contract) {
|
|
122
|
+
const appointment = await readContractAppointment(repository);
|
|
102
123
|
if (appointment.kind !== "appointed" || appointment.contract !== contract)
|
|
103
124
|
return;
|
|
104
|
-
if (
|
|
125
|
+
if (await readFile(appointment.path, "utf8") !== appointmentBytes(contract))
|
|
105
126
|
return;
|
|
106
127
|
try {
|
|
107
|
-
|
|
128
|
+
await unlink(appointment.path);
|
|
108
129
|
}
|
|
109
130
|
catch { /* reservation cleanup is best effort */ }
|
|
110
131
|
}
|
|
111
|
-
export function removeContractWorktreeAppointment(repository, contract) {
|
|
112
|
-
const appointment = readContractAppointment(repository);
|
|
132
|
+
export async function removeContractWorktreeAppointment(repository, contract) {
|
|
133
|
+
const appointment = await readContractAppointment(repository);
|
|
113
134
|
if (appointment.kind !== "appointed" || appointment.contract !== contract)
|
|
114
135
|
return;
|
|
115
|
-
|
|
136
|
+
await unlink(appointment.path);
|
|
116
137
|
}
|
|
117
|
-
function repair(repository, worktree, relativePath, bytes) {
|
|
138
|
+
async function repair(repository, worktree, relativePath, bytes) {
|
|
118
139
|
const path = join(worktree, relativePath);
|
|
119
|
-
if (isTracked(repository, relativePath))
|
|
140
|
+
if (await isTracked(repository, relativePath))
|
|
120
141
|
throw new Error(`generated path is tracked by Git: ${path}`);
|
|
121
|
-
const action = repairDerivedFile(path, bytes);
|
|
142
|
+
const action = await repairDerivedFile(path, bytes);
|
|
122
143
|
if (relativePath === ".keiyaku/KEIYAKU.md") {
|
|
123
144
|
try {
|
|
124
|
-
|
|
145
|
+
await chmod(path, 0o444);
|
|
125
146
|
}
|
|
126
147
|
catch { /* advisory protection only */ }
|
|
127
148
|
}
|
|
128
149
|
return { kind: "contract-file", path, action };
|
|
129
150
|
}
|
|
130
|
-
function materialize(repository, worktree, guidance) {
|
|
131
|
-
const stat =
|
|
151
|
+
async function materialize(repository, worktree, guidance) {
|
|
152
|
+
const stat = await lstat(worktree).catch((error) => {
|
|
153
|
+
if (error.code === "ENOENT")
|
|
154
|
+
return undefined;
|
|
155
|
+
throw error;
|
|
156
|
+
});
|
|
132
157
|
if (stat === undefined || !stat.isDirectory()) {
|
|
133
158
|
return {
|
|
134
159
|
effects: [],
|
|
@@ -147,7 +172,7 @@ function materialize(repository, worktree, guidance) {
|
|
|
147
172
|
[".keiyaku/KEIYAKU.md", guidance],
|
|
148
173
|
]) {
|
|
149
174
|
try {
|
|
150
|
-
effects.push(repair(scoped, worktree, relativePath, bytes));
|
|
175
|
+
effects.push(await repair(scoped, worktree, relativePath, bytes));
|
|
151
176
|
}
|
|
152
177
|
catch (error) {
|
|
153
178
|
return {
|
|
@@ -163,11 +188,11 @@ function materialize(repository, worktree, guidance) {
|
|
|
163
188
|
}
|
|
164
189
|
return { effects, lag: [] };
|
|
165
190
|
}
|
|
166
|
-
function here(repository, state, guidance) {
|
|
167
|
-
const worktree = worktreeRoot(repository);
|
|
168
|
-
const appointment = readContractAppointment(repository);
|
|
191
|
+
async function here(repository, state, guidance) {
|
|
192
|
+
const worktree = await worktreeRoot(repository);
|
|
193
|
+
const appointment = await readContractAppointment(repository);
|
|
169
194
|
if (appointment.kind === "appointed" && appointment.contract === state.id) {
|
|
170
|
-
return materialize(repository, worktree, guidance);
|
|
195
|
+
return await materialize(repository, worktree, guidance);
|
|
171
196
|
}
|
|
172
197
|
const diagnostic = appointment.kind === "appointed"
|
|
173
198
|
? `here worktree is appointed to ${appointment.contract}`
|
|
@@ -177,9 +202,9 @@ function here(repository, state, guidance) {
|
|
|
177
202
|
lag: [{ kind: "contract-file-failed", worktree, path: appointment.path, diagnostic }],
|
|
178
203
|
};
|
|
179
204
|
}
|
|
180
|
-
function removeHere(repository, contract) {
|
|
181
|
-
const worktree = worktreeRoot(repository);
|
|
182
|
-
const appointment = readContractAppointment(repository);
|
|
205
|
+
async function removeHere(repository, contract) {
|
|
206
|
+
const worktree = await worktreeRoot(repository);
|
|
207
|
+
const appointment = await readContractAppointment(repository);
|
|
183
208
|
if (appointment.kind === "absent" || (appointment.kind === "appointed" && appointment.contract !== contract)) {
|
|
184
209
|
return { effects: [], lag: [] };
|
|
185
210
|
}
|
|
@@ -189,7 +214,7 @@ function removeHere(repository, contract) {
|
|
|
189
214
|
}] };
|
|
190
215
|
}
|
|
191
216
|
try {
|
|
192
|
-
|
|
217
|
+
await unlink(appointment.path);
|
|
193
218
|
return { effects: [{ kind: "contract-file", path: appointment.path, action: "removed" }], lag: [] };
|
|
194
219
|
}
|
|
195
220
|
catch (error) {
|
|
@@ -201,13 +226,26 @@ function removeHere(repository, contract) {
|
|
|
201
226
|
}] };
|
|
202
227
|
}
|
|
203
228
|
}
|
|
204
|
-
export function projectContractWorktree(repository, state) {
|
|
205
|
-
if (state === null)
|
|
206
|
-
return
|
|
229
|
+
export async function projectContractWorktree(repository, state, register) {
|
|
230
|
+
if (state === null || state.terminal) {
|
|
231
|
+
return state?.coordinates.workspace === "here"
|
|
232
|
+
? await removeHere(repository, state.id)
|
|
233
|
+
: { effects: [], lag: [] };
|
|
234
|
+
}
|
|
207
235
|
if (state.coordinates.workspace === "here") {
|
|
208
|
-
return
|
|
236
|
+
return await here(repository, state, renderContractGuidance(state));
|
|
237
|
+
}
|
|
238
|
+
const appointed = appointmentFor(register ?? await readPlaceRegister(repository), state.id);
|
|
239
|
+
if (appointed === undefined) {
|
|
240
|
+
return {
|
|
241
|
+
effects: [],
|
|
242
|
+
lag: [{
|
|
243
|
+
kind: "contract-file-failed",
|
|
244
|
+
worktree: repository.primaryWorktree,
|
|
245
|
+
path: placeRegisterPath(repository),
|
|
246
|
+
diagnostic: "managed Contract is unappointed",
|
|
247
|
+
}],
|
|
248
|
+
};
|
|
209
249
|
}
|
|
210
|
-
return state
|
|
211
|
-
? { effects: [], lag: [] }
|
|
212
|
-
: materialize(repository, deliveryWorktreePath(repository, state.id), renderContractGuidance(state));
|
|
250
|
+
return await materialize(repository, worktreePath(repository, appointed.place), renderContractGuidance(state));
|
|
213
251
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export declare function
|
|
1
|
+
export declare function syncDirectory(path: string): void;
|
|
2
|
+
export declare function replaceFileDurably(path: string, bytes: string | Uint8Array): Promise<void>;
|
|
2
3
|
export type DerivedFileAction = "created" | "updated" | "unchanged";
|
|
3
|
-
export declare function createFileDurablyExclusive(path: string, bytes: string | Uint8Array, mode?: number): boolean
|
|
4
|
-
export declare function repairDerivedFile(path: string, bytes: string | Uint8Array): DerivedFileAction
|
|
4
|
+
export declare function createFileDurablyExclusive(path: string, bytes: string | Uint8Array, mode?: number): Promise<boolean>;
|
|
5
|
+
export declare function repairDerivedFile(path: string, bytes: string | Uint8Array): Promise<DerivedFileAction>;
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { closeSync, fsyncSync,
|
|
2
|
+
import { closeSync, fsyncSync, openSync, renameSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { lstat, mkdir, readFile, unlink } from "node:fs/promises";
|
|
3
4
|
import { dirname, resolve } from "node:path";
|
|
4
|
-
export function
|
|
5
|
+
export function syncDirectory(path) {
|
|
6
|
+
if (process.platform === "win32")
|
|
7
|
+
return;
|
|
8
|
+
const directory = openSync(path, "r");
|
|
9
|
+
try {
|
|
10
|
+
fsyncSync(directory);
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
closeSync(directory);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function replaceFileDurably(path, bytes) {
|
|
5
17
|
const parent = dirname(path);
|
|
6
18
|
for (;;) {
|
|
7
19
|
const temporary = resolve(parent, `.tmp-${randomBytes(8).toString("hex")}`);
|
|
@@ -13,20 +25,14 @@ export function replaceFileDurably(path, bytes) {
|
|
|
13
25
|
closeSync(descriptor);
|
|
14
26
|
descriptor = undefined;
|
|
15
27
|
renameSync(temporary, path);
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
fsyncSync(directory);
|
|
19
|
-
}
|
|
20
|
-
finally {
|
|
21
|
-
closeSync(directory);
|
|
22
|
-
}
|
|
28
|
+
syncDirectory(parent);
|
|
23
29
|
return;
|
|
24
30
|
}
|
|
25
31
|
catch (error) {
|
|
26
32
|
if (descriptor !== undefined)
|
|
27
33
|
closeSync(descriptor);
|
|
28
34
|
try {
|
|
29
|
-
|
|
35
|
+
await unlink(temporary);
|
|
30
36
|
}
|
|
31
37
|
catch { /* renamed, absent, or best-effort cleanup */ }
|
|
32
38
|
if (error.code !== "EEXIST")
|
|
@@ -34,9 +40,9 @@ export function replaceFileDurably(path, bytes) {
|
|
|
34
40
|
}
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
|
-
export function createFileDurablyExclusive(path, bytes, mode = 0o600) {
|
|
43
|
+
export async function createFileDurablyExclusive(path, bytes, mode = 0o600) {
|
|
38
44
|
const parent = dirname(path);
|
|
39
|
-
|
|
45
|
+
await mkdir(parent, { recursive: true });
|
|
40
46
|
let descriptor;
|
|
41
47
|
try {
|
|
42
48
|
descriptor = openSync(path, "wx", mode);
|
|
@@ -44,13 +50,7 @@ export function createFileDurablyExclusive(path, bytes, mode = 0o600) {
|
|
|
44
50
|
fsyncSync(descriptor);
|
|
45
51
|
closeSync(descriptor);
|
|
46
52
|
descriptor = undefined;
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
fsyncSync(directory);
|
|
50
|
-
}
|
|
51
|
-
finally {
|
|
52
|
-
closeSync(directory);
|
|
53
|
-
}
|
|
53
|
+
syncDirectory(parent);
|
|
54
54
|
return true;
|
|
55
55
|
}
|
|
56
56
|
catch (error) {
|
|
@@ -59,21 +59,25 @@ export function createFileDurablyExclusive(path, bytes, mode = 0o600) {
|
|
|
59
59
|
if (error.code === "EEXIST")
|
|
60
60
|
return false;
|
|
61
61
|
try {
|
|
62
|
-
|
|
62
|
+
await unlink(path);
|
|
63
63
|
}
|
|
64
64
|
catch { /* absent or best-effort cleanup */ }
|
|
65
65
|
throw error;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
export function repairDerivedFile(path, bytes) {
|
|
68
|
+
export async function repairDerivedFile(path, bytes) {
|
|
69
69
|
const expected = Buffer.from(bytes);
|
|
70
|
-
|
|
71
|
-
const stat =
|
|
70
|
+
await mkdir(dirname(path), { recursive: true });
|
|
71
|
+
const stat = await lstat(path).catch((error) => {
|
|
72
|
+
if (error.code === "ENOENT")
|
|
73
|
+
return undefined;
|
|
74
|
+
throw error;
|
|
75
|
+
});
|
|
72
76
|
if (stat !== undefined && (!stat.isFile() || stat.isSymbolicLink())) {
|
|
73
77
|
throw new Error(`derived file is not a regular file: ${path}`);
|
|
74
78
|
}
|
|
75
|
-
if (stat !== undefined &&
|
|
79
|
+
if (stat !== undefined && (await readFile(path)).equals(expected))
|
|
76
80
|
return "unchanged";
|
|
77
|
-
replaceFileDurably(path, expected);
|
|
81
|
+
await replaceFileDurably(path, expected);
|
|
78
82
|
return stat === undefined ? "created" : "updated";
|
|
79
83
|
}
|
|
@@ -12,3 +12,7 @@ export declare function acquireSqliteTransactionLock(input: {
|
|
|
12
12
|
timeoutMs?: number;
|
|
13
13
|
signal?: AbortSignal;
|
|
14
14
|
}): Promise<HeldSqliteTransactionLock>;
|
|
15
|
+
export declare function tryAcquireSqliteTransactionLock(input: {
|
|
16
|
+
path: string;
|
|
17
|
+
mode: SqliteTransactionLockMode;
|
|
18
|
+
}): Promise<HeldSqliteTransactionLock | null>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomInt } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { lstat, mkdir } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { DatabaseSync } from "node:sqlite";
|
|
5
5
|
const SQLITE_BUSY = 5;
|
|
@@ -41,13 +41,17 @@ function closeDatabase(database) {
|
|
|
41
41
|
}
|
|
42
42
|
return failure;
|
|
43
43
|
}
|
|
44
|
-
function openLock(path, mode) {
|
|
44
|
+
async function openLock(path, mode) {
|
|
45
45
|
try {
|
|
46
|
-
const stat =
|
|
46
|
+
const stat = await lstat(path).catch((error) => {
|
|
47
|
+
if (error.code === "ENOENT")
|
|
48
|
+
return undefined;
|
|
49
|
+
throw error;
|
|
50
|
+
});
|
|
47
51
|
if (stat && (!stat.isFile() || stat.isSymbolicLink())) {
|
|
48
52
|
throw new SqliteTransactionLockError(`SQLite lock is not a regular file: ${path}`, "invalid");
|
|
49
53
|
}
|
|
50
|
-
|
|
54
|
+
await mkdir(dirname(path), { recursive: true });
|
|
51
55
|
}
|
|
52
56
|
catch (error) {
|
|
53
57
|
if (error instanceof SqliteTransactionLockError)
|
|
@@ -101,7 +105,7 @@ export async function acquireSqliteTransactionLock(input) {
|
|
|
101
105
|
for (;;) {
|
|
102
106
|
input.signal?.throwIfAborted();
|
|
103
107
|
try {
|
|
104
|
-
return openLock(input.path, input.mode);
|
|
108
|
+
return await openLock(input.path, input.mode);
|
|
105
109
|
}
|
|
106
110
|
catch (error) {
|
|
107
111
|
if (!isBusy(error))
|
|
@@ -117,3 +121,16 @@ export async function acquireSqliteTransactionLock(input) {
|
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
}
|
|
124
|
+
export async function tryAcquireSqliteTransactionLock(input) {
|
|
125
|
+
if (input.path.length === 0) {
|
|
126
|
+
throw new SqliteTransactionLockError("SQLite lock path must be valid", "invalid");
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
return await openLock(input.path, input.mode);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (isBusy(error))
|
|
133
|
+
return null;
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|