@astrosheep/keiyaku 4.0.4 → 4.0.5

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.
Files changed (173) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +14 -0
  6. package/build/src/akuma/akuma.d.ts +0 -1
  7. package/build/src/akuma/akuma.js +1 -6
  8. package/build/src/akuma/allowed.d.ts +1 -1
  9. package/build/src/akuma/allowed.js +3 -10
  10. package/build/src/akuma/body.d.ts +5 -2
  11. package/build/src/akuma/body.js +29 -23
  12. package/build/src/akuma/heart/facts.d.ts +35 -9
  13. package/build/src/akuma/heart/index.d.ts +4 -13
  14. package/build/src/akuma/heart/index.js +2 -133
  15. package/build/src/akuma/heart/request-authority.d.ts +12 -0
  16. package/build/src/akuma/heart/request-authority.js +119 -0
  17. package/build/src/akuma/heart/request-rows.d.ts +16 -0
  18. package/build/src/akuma/heart/request-rows.js +88 -0
  19. package/build/src/akuma/heart/rows.d.ts +1 -28
  20. package/build/src/akuma/heart/rows.js +0 -137
  21. package/build/src/akuma/heart/schema.d.ts +1 -1
  22. package/build/src/akuma/heart/schema.js +9 -2
  23. package/build/src/akuma/heart/soul.js +0 -36
  24. package/build/src/akuma/index.d.ts +1 -1
  25. package/build/src/akuma/provider-recipe.js +1 -11
  26. package/build/src/akuma/provider.d.ts +1 -6
  27. package/build/src/akuma/providers/acp/core.js +6 -2
  28. package/build/src/akuma/providers/acp/index.js +0 -1
  29. package/build/src/akuma/providers/claude/index.d.ts +0 -4
  30. package/build/src/akuma/providers/claude/index.js +7 -3
  31. package/build/src/akuma/providers/codex-app-server/index.d.ts +2 -27
  32. package/build/src/akuma/providers/codex-app-server/index.js +0 -2
  33. package/build/src/akuma/providers/grok-build/index.js +0 -1
  34. package/build/src/akuma/providers/opencode-sdk/index.d.ts +2 -28
  35. package/build/src/akuma/providers/opencode-sdk/index.js +8 -4
  36. package/build/src/akuma/providers/opencode-sdk/session.d.ts +1 -1
  37. package/build/src/akuma/providers/opencode-sdk/session.js +1 -1
  38. package/build/src/akuma/providers/pi/index.js +11 -4
  39. package/build/src/akuma/request-serve.d.ts +87 -0
  40. package/build/src/akuma/request-serve.js +484 -0
  41. package/build/src/akuma/request-wire.d.ts +124 -0
  42. package/build/src/akuma/request-wire.js +380 -0
  43. package/build/src/akuma/requests.d.ts +28 -94
  44. package/build/src/akuma/requests.js +52 -562
  45. package/build/src/akuma-body.d.ts +7 -0
  46. package/build/src/akuma-body.js +54 -1
  47. package/build/src/body/arc.js +4 -29
  48. package/build/src/body/decode.js +5 -39
  49. package/build/src/body/envelope.d.ts +7 -0
  50. package/build/src/body/envelope.js +37 -0
  51. package/build/src/cli/accepted.js +17 -9
  52. package/build/src/cli/commands/akuma.js +2 -2
  53. package/build/src/cli/commands/contract.d.ts +6 -6
  54. package/build/src/cli/commands/contract.js +3 -3
  55. package/build/src/cli/commands/task-invoke.js +98 -9
  56. package/build/src/cli/coordinates.d.ts +1 -0
  57. package/build/src/cli/coordinates.js +7 -4
  58. package/build/src/cli/invoke.js +28 -4
  59. package/build/src/cli/parse.d.ts +2 -2
  60. package/build/src/cli/parse.js +21 -8
  61. package/build/src/cli/render/akuma.js +56 -13
  62. package/build/src/cli/render/audit.js +2 -6
  63. package/build/src/cli/render/contract.js +40 -0
  64. package/build/src/cli/render/receipt.js +28 -1
  65. package/build/src/cli/render/terminal.d.ts +0 -2
  66. package/build/src/cli/render/terminal.js +0 -29
  67. package/build/src/cli/result.d.ts +15 -7
  68. package/build/src/contract-worktree.d.ts +1 -2
  69. package/build/src/contract-worktree.js +4 -9
  70. package/build/src/core/facts/codec.js +9 -0
  71. package/build/src/core/facts/fold.js +13 -1
  72. package/build/src/core/facts/types.d.ts +8 -1
  73. package/build/src/core/subject.js +4 -3
  74. package/build/src/core/verbs/placement.d.ts +9 -1
  75. package/build/src/core/verbs/placement.js +24 -6
  76. package/build/src/core/verbs/reintegrate.d.ts +13 -0
  77. package/build/src/core/verbs/reintegrate.js +24 -0
  78. package/build/src/dispatch/index.d.ts +1 -1
  79. package/build/src/git/admission.d.ts +2 -1
  80. package/build/src/git/integration.d.ts +5 -2
  81. package/build/src/git/integration.js +54 -1
  82. package/build/src/git/observe.d.ts +2 -1
  83. package/build/src/git/observe.js +6 -5
  84. package/build/src/git/process.d.ts +26 -0
  85. package/build/src/git/process.js +94 -0
  86. package/build/src/git/read-observation.d.ts +2 -1
  87. package/build/src/git/read-observation.js +3 -2
  88. package/build/src/git/reconcile.d.ts +2 -1
  89. package/build/src/git/reconcile.js +9 -6
  90. package/build/src/git/repository.d.ts +2 -29
  91. package/build/src/git/repository.js +4 -118
  92. package/build/src/git/scratch.d.ts +1 -1
  93. package/build/src/git/scratch.js +1 -1
  94. package/build/src/git/target-placement.d.ts +1 -1
  95. package/build/src/git/target-placement.js +9 -7
  96. package/build/src/git/tender.d.ts +1 -1
  97. package/build/src/git/tender.js +2 -1
  98. package/build/src/git/terminal-seal.d.ts +2 -2
  99. package/build/src/git/terminal-seal.js +3 -6
  100. package/build/src/git/workspace.d.ts +1 -1
  101. package/build/src/git/workspace.js +1 -1
  102. package/build/src/index.d.ts +1 -2
  103. package/build/src/index.js +1 -1
  104. package/build/src/library/address.js +7 -1
  105. package/build/src/library/audit.d.ts +2 -1
  106. package/build/src/library/audit.js +1 -1
  107. package/build/src/library/bind.d.ts +2 -1
  108. package/build/src/library/bind.js +1 -1
  109. package/build/src/library/configuration.d.ts +1 -1
  110. package/build/src/library/configuration.js +52 -18
  111. package/build/src/library/contract.d.ts +55 -2
  112. package/build/src/library/contract.js +162 -23
  113. package/build/src/library/delivery.d.ts +1 -1
  114. package/build/src/library/fleet.d.ts +27 -7
  115. package/build/src/library/fleet.js +79 -39
  116. package/build/src/library/input.d.ts +0 -1
  117. package/build/src/library/input.js +0 -5
  118. package/build/src/library/keiyaku.d.ts +1 -1
  119. package/build/src/library/mutation.d.ts +2 -1
  120. package/build/src/library/reconcile.d.ts +2 -1
  121. package/build/src/library/reconcile.js +3 -1
  122. package/build/src/library/region.d.ts +1 -1
  123. package/build/src/library/repo.d.ts +3 -1
  124. package/build/src/library/repo.js +8 -4
  125. package/build/src/protocol/abandon.d.ts +9 -0
  126. package/build/src/protocol/abandon.js +15 -0
  127. package/build/src/protocol/amend.d.ts +16 -0
  128. package/build/src/protocol/amend.js +77 -0
  129. package/build/src/protocol/arc.d.ts +6 -0
  130. package/build/src/protocol/arc.js +12 -0
  131. package/build/src/protocol/attempt.d.ts +2 -1
  132. package/build/src/protocol/audit.d.ts +53 -0
  133. package/build/src/protocol/audit.js +140 -0
  134. package/build/src/protocol/bind.d.ts +1 -1
  135. package/build/src/protocol/completion.d.ts +32 -0
  136. package/build/src/protocol/completion.js +130 -0
  137. package/build/src/protocol/deliver.d.ts +37 -0
  138. package/build/src/protocol/deliver.js +160 -0
  139. package/build/src/protocol/intent.d.ts +1 -1
  140. package/build/src/protocol/intent.js +1 -1
  141. package/build/src/protocol/operations.d.ts +64 -200
  142. package/build/src/protocol/operations.js +21 -640
  143. package/build/src/protocol/placement.d.ts +4 -13
  144. package/build/src/protocol/placement.js +3 -15
  145. package/build/src/protocol/read/status.d.ts +1 -1
  146. package/build/src/protocol/read/status.js +3 -1
  147. package/build/src/protocol/reconcile.d.ts +35 -0
  148. package/build/src/protocol/reconcile.js +41 -0
  149. package/build/src/protocol/reintegrate.d.ts +31 -0
  150. package/build/src/protocol/reintegrate.js +74 -0
  151. package/build/src/protocol/review.d.ts +33 -0
  152. package/build/src/protocol/review.js +148 -0
  153. package/build/src/protocol/run.d.ts +1 -1
  154. package/build/src/settlement/fence.d.ts +1 -1
  155. package/build/src/settlement/holder.d.ts +1 -1
  156. package/build/src/settlement/settle.d.ts +1 -1
  157. package/build/src/task/board.d.ts +0 -1
  158. package/build/src/task/board.js +0 -3
  159. package/build/src/task/compose.d.ts +1 -1
  160. package/build/src/task/compose.js +11 -4
  161. package/build/src/task/index.d.ts +1 -1
  162. package/build/src/task/index.js +2 -127
  163. package/build/src/task/input.d.ts +18 -0
  164. package/build/src/task/input.js +189 -0
  165. package/build/src/task/mutation.d.ts +39 -0
  166. package/build/src/task/mutation.js +85 -0
  167. package/build/src/task/operations.d.ts +2 -0
  168. package/build/src/task/operations.js +35 -11
  169. package/build/src/task/store.js +0 -2
  170. package/build/src/verification/execution.d.ts +1 -1
  171. package/build/src/workspace-place.d.ts +1 -4
  172. package/build/src/workspace-place.js +0 -15
  173. package/package.json +3 -3
@@ -1,16 +1,34 @@
1
1
  import { gatesSatisfied } from "../facts/gate.js";
2
- import { activeContract, prerequisiteStatus } from "../facts/observation.js";
2
+ import { activeContract, contractState } from "../facts/observation.js";
3
+ function unmetPrerequisites(prerequisites, observation) {
4
+ const unmet = [];
5
+ for (const contractId of prerequisites) {
6
+ const state = contractState(observation, contractId);
7
+ if (state === null) {
8
+ unmet.push({ contractId, state: "missing" });
9
+ }
10
+ else if (state.terminal?.kind === "abandoned") {
11
+ unmet.push({ contractId, state: "abandoned" });
12
+ }
13
+ else if (state.terminal?.kind !== "claimed") {
14
+ unmet.push({ contractId, state: "active" });
15
+ }
16
+ }
17
+ return unmet;
18
+ }
3
19
  export function decidePlacement({ input, attempt, observation }) {
4
20
  const id = input.contractId;
5
21
  const current = activeContract(observation, id);
6
22
  if ("kind" in current)
7
23
  return { kind: "refused", refusal: current };
8
24
  const delivery = current.delivery;
9
- if (!delivery) {
25
+ const integration = current.currentIntegration;
26
+ if (!delivery || !integration) {
10
27
  return { kind: "refused", refusal: { kind: "delivery-missing", contractId: id } };
11
28
  }
12
- if (prerequisiteStatus(current.terms.after, observation) !== "claimed") {
13
- return { kind: "refused", refusal: { kind: "prerequisites-unsatisfied", contractId: id } };
29
+ const unmet = unmetPrerequisites(current.terms.after, observation);
30
+ if (unmet.length > 0) {
31
+ return { kind: "refused", refusal: { kind: "prerequisites-unsatisfied", contractId: id, unmet } };
14
32
  }
15
33
  if (!gatesSatisfied(current)) {
16
34
  return { kind: "refused", refusal: { kind: "gates-unsatisfied", contractId: id } };
@@ -31,8 +49,8 @@ export function decidePlacement({ input, attempt, observation }) {
31
49
  ...(current.coordinates.target === undefined ? {} : {
32
50
  target: {
33
51
  target: current.coordinates.target,
34
- expectedOid: delivery.data.integration.predecessor,
35
- newOid: delivery.data.integration.snapshot,
52
+ expectedOid: integration.predecessor,
53
+ newOid: integration.snapshot,
36
54
  },
37
55
  }),
38
56
  },
@@ -0,0 +1,13 @@
1
+ import type { DecideInput, OfferDecision, Preparation } from "../decide.js";
2
+ import type { ActorId, ContractId, ReintegratedData } from "../facts/types.js";
3
+ export type ReintegrateInput<Failure = never> = Readonly<{
4
+ contractId: ContractId;
5
+ actor?: ActorId;
6
+ at: string;
7
+ preparation: Preparation<ReintegratedData, Failure>;
8
+ }>;
9
+ export type ReintegrateRefusal = Readonly<{
10
+ kind: "contract-missing" | "delivery-missing" | "terminal";
11
+ contractId: ContractId;
12
+ }>;
13
+ export declare function decideReintegrate<Failure>({ input, attempt, observation, }: DecideInput<ReintegrateInput<Failure>>): OfferDecision<ReintegrateRefusal | Failure>;
@@ -0,0 +1,24 @@
1
+ import { activeContract } from "../facts/observation.js";
2
+ export function decideReintegrate({ input, attempt, observation, }) {
3
+ const state = activeContract(observation, input.contractId);
4
+ if ("kind" in state)
5
+ return { kind: "refused", refusal: state };
6
+ if (state.delivery === null) {
7
+ return { kind: "refused", refusal: { kind: "delivery-missing", contractId: input.contractId } };
8
+ }
9
+ if (input.preparation.kind === "refused")
10
+ return { kind: "refused", refusal: input.preparation.refusal };
11
+ const reintegrated = {
12
+ v: 1,
13
+ kind: "reintegrated",
14
+ contract: input.contractId,
15
+ entry: attempt.entryUlids[0],
16
+ at: input.at,
17
+ ...(input.actor === undefined ? {} : { actor: input.actor }),
18
+ data: input.preparation.data,
19
+ };
20
+ return {
21
+ kind: "offer",
22
+ offer: { facts: [{ contractId: input.contractId, expectedHead: state.head, entries: [reintegrated] }] },
23
+ };
24
+ }
@@ -1,6 +1,6 @@
1
1
  import { type ContractId } from "../core/facts/types.js";
2
2
  import { type AkuId } from "../akuma/identity.js";
3
- import { type GitRepository } from "../git/repository.js";
3
+ import type { GitRepository } from "../git/process.js";
4
4
  import { type GitReadObservation } from "../git/read-observation.js";
5
5
  export type Dispatch = Readonly<{
6
6
  akuId: AkuId;
@@ -1,6 +1,7 @@
1
1
  import type { Offer } from "../core/facts/offer.js";
2
2
  import type { GitAdmissionSnapshot } from "./observe.js";
3
- import { type GitRepository, type GitRefAssertion } from "./repository.js";
3
+ import { type GitRefAssertion } from "./repository.js";
4
+ import type { GitRepository } from "./process.js";
4
5
  import { type ContractHead } from "../core/facts/types.js";
5
6
  type Accepted = Readonly<{
6
7
  kind: "accepted";
@@ -1,8 +1,9 @@
1
1
  import type { Preparation } from "../core/decide.js";
2
2
  import type { ChangeId, ContractCoordinates, ContractId, SnapshotId } from "../core/facts/types.js";
3
3
  import { type GitObjectId } from "./identity.js";
4
- import { type GitRepository } from "./repository.js";
4
+ import { type GitRepository } from "./process.js";
5
5
  import type { DeliveryCommitMetadata, TenderCapture } from "./tender.js";
6
+ type IntegrationTender = Pick<TenderCapture, "head" | "tree">;
6
7
  declare const REQUIRED_GIT: "2.38";
7
8
  export type IntegrationPreparationRefusal = Readonly<{
8
9
  kind: "integration-failed";
@@ -27,7 +28,9 @@ export type IntegrationPlan = Readonly<{
27
28
  export declare function worktreeChangeId(repository: GitRepository, input: IntegrationCoordinates, tender: TenderCapture): Promise<ChangeId>;
28
29
  /** Materialize the exact integration tree against its observed predecessor. */
29
30
  export declare function materializeIntegrationSnapshot(repository: GitRepository, tree: GitObjectId, parent: SnapshotId, metadata: DeliveryCommitMetadata): Promise<SnapshotId>;
30
- export declare function planIntegration(repository: GitRepository, input: IntegrationCoordinates, tender: TenderCapture, requireBranchesToBeUpToDate: boolean): Promise<Preparation<IntegrationPlan, Readonly<{
31
+ export declare function persistedTender(repository: GitRepository, snapshot: SnapshotId): Promise<IntegrationTender>;
32
+ export declare function materializeReintegrationSnapshot(repository: GitRepository, tree: GitObjectId, parent: SnapshotId, template: SnapshotId): Promise<SnapshotId>;
33
+ export declare function planIntegration(repository: GitRepository, input: IntegrationCoordinates, tender: IntegrationTender, requireBranchesToBeUpToDate: boolean): Promise<Preparation<IntegrationPlan, Readonly<{
31
34
  kind: "target-missing";
32
35
  contractId: ContractId;
33
36
  }> | IntegrationPreparationRefusal>>;
@@ -1,6 +1,7 @@
1
1
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
2
2
  import { gitObjectId, gitObjectIdForSnapshot, mintChangeId, mintSnapshotId } from "./identity.js";
3
- import { decodeGitNameOnly, decodeGitNumstat, GitPlumbingError, readRef, runGit, runGitWithEnvironment, } from "./repository.js";
3
+ import { decodeGitNameOnly, decodeGitNumstat, readRef, } from "./repository.js";
4
+ import { GitPlumbingError, runGit, runGitWithEnvironment, } from "./process.js";
4
5
  const REQUIRED_GIT = "2.38";
5
6
  async function runAllowingNonzero(repository, args, input) {
6
7
  try {
@@ -55,6 +56,58 @@ export async function materializeIntegrationSnapshot(repository, tree, parent, m
55
56
  })).toString("utf8").trim();
56
57
  return mintSnapshotId(commit);
57
58
  }
59
+ export async function persistedTender(repository, snapshot) {
60
+ const tree = (await runGit(repository, ["show", "-s", "--format=%T", gitObjectIdForSnapshot(snapshot)]))
61
+ .toString("ascii")
62
+ .trim();
63
+ return { head: snapshot, tree: gitObjectId(tree, "persisted tender tree") };
64
+ }
65
+ export async function materializeReintegrationSnapshot(repository, tree, parent, template) {
66
+ const templateObject = gitObjectIdForSnapshot(template);
67
+ let raw;
68
+ try {
69
+ raw = await runGit(repository, ["cat-file", "commit", templateObject]);
70
+ }
71
+ catch (error) {
72
+ if (error instanceof GitPlumbingError) {
73
+ throw new AuthorityCorruptionError(`recorded delivery snapshot cannot be read: ${template}`);
74
+ }
75
+ throw error;
76
+ }
77
+ const separator = raw.indexOf(Buffer.from("\n\n"));
78
+ if (separator < 0)
79
+ throw new AuthorityCorruptionError(`recorded delivery snapshot has no commit message: ${template}`);
80
+ const message = raw.subarray(separator + 2);
81
+ let fields;
82
+ try {
83
+ const formatted = (await runGit(repository, [
84
+ "show",
85
+ "-s",
86
+ "--format=%an%x00%ae%x00%aI%x00%cn%x00%ce%x00%cI",
87
+ templateObject,
88
+ ])).toString("utf8").replace(/\n$/u, "");
89
+ fields = formatted.split("\0");
90
+ }
91
+ catch (error) {
92
+ if (error instanceof GitPlumbingError) {
93
+ throw new AuthorityCorruptionError(`recorded delivery snapshot metadata cannot be read: ${template}`);
94
+ }
95
+ throw error;
96
+ }
97
+ if (fields.length !== 6 || fields.some((field) => field.length === 0)) {
98
+ throw new AuthorityCorruptionError(`recorded delivery snapshot metadata is malformed: ${template}`);
99
+ }
100
+ const [authorName, authorEmail, authorDate, committerName, committerEmail, committerDate] = fields;
101
+ const commit = (await runGitWithEnvironment(repository, ["commit-tree", tree, "-p", gitObjectIdForSnapshot(parent)], message, {
102
+ GIT_AUTHOR_NAME: authorName,
103
+ GIT_AUTHOR_EMAIL: authorEmail,
104
+ GIT_AUTHOR_DATE: authorDate,
105
+ GIT_COMMITTER_NAME: committerName,
106
+ GIT_COMMITTER_EMAIL: committerEmail,
107
+ GIT_COMMITTER_DATE: committerDate,
108
+ })).toString("utf8").trim();
109
+ return mintSnapshotId(commit);
110
+ }
58
111
  async function isAncestor(repository, ancestor, descendant) {
59
112
  const result = await runAllowingNonzero(repository, [
60
113
  "merge-base",
@@ -1,6 +1,7 @@
1
1
  import { type ContractId, type ContractState, type JournalEntry, type SnapshotId } from "../core/facts/types.js";
2
2
  import type { ContractsObservation } from "../core/facts/observation.js";
3
- import { type GitSnapshot, type GitOid, type GitRepository } from "./repository.js";
3
+ import { type GitSnapshot, type GitOid } from "./repository.js";
4
+ import { type GitRepository } from "./process.js";
4
5
  import { type GitDecodeChannel, type GitReadObservation, type GitTreeSelection } from "./read-observation.js";
5
6
  export type TargetObservation = Readonly<{
6
7
  head: SnapshotId | null;
@@ -2,7 +2,8 @@ import { decodeJournal } from "../core/facts/codec.js";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { foldJournal } from "../core/facts/fold.js";
4
4
  import { contractJournalPath, contractJournalPaths, mintContractHead, mintSnapshotId, } from "./identity.js";
5
- import { GitPlumbingError, isKeiyakuOwnedRef, runGit, } from "./repository.js";
5
+ import { isKeiyakuOwnedRef, } from "./repository.js";
6
+ import { GitPlumbingError, runGit } from "./process.js";
6
7
  import { readGitTreeSelection, withGitTargetedReadObservation, } from "./read-observation.js";
7
8
  export async function observeDeliveryTargetAt(observation, state) {
8
9
  const target = state.coordinates.target;
@@ -10,12 +11,12 @@ export async function observeDeliveryTargetAt(observation, state) {
10
11
  return null;
11
12
  const value = await observation.resolveRef(target);
12
13
  const head = value === null ? null : mintSnapshotId(value);
13
- const delivery = state.delivery?.data;
14
- if (delivery === undefined)
14
+ const integration = state.currentIntegration;
15
+ if (integration === null)
15
16
  return { head, drift: false };
16
17
  const expected = state.terminal?.kind === "claimed"
17
- ? delivery.integration.snapshot
18
- : delivery.integration.predecessor;
18
+ ? integration.snapshot
19
+ : integration.predecessor;
19
20
  return { head, drift: head !== expected };
20
21
  }
21
22
  const TARGET_COORDINATES_FORMAT = "%(refname)%00%(objectname)%00";
@@ -0,0 +1,26 @@
1
+ export type GitRepository = Readonly<{
2
+ /** The executable selected when this repository capability is created. */
3
+ readonly gitPath: string;
4
+ /** The invocation's effective working directory, including a caller -C worktree. */
5
+ readonly effectiveCwd: string;
6
+ /** The canonical primary worktree root for this repository. */
7
+ readonly primaryWorktree: string;
8
+ /** The canonical common Git directory pinned when this capability is created. */
9
+ readonly commonDirectory: string;
10
+ }>;
11
+ export declare class GitPlumbingError extends Error {
12
+ readonly stdout: Buffer;
13
+ readonly stderr: Buffer;
14
+ readonly status: number | null;
15
+ readonly pid: number | null;
16
+ constructor(input: Readonly<{
17
+ stdout?: string | Uint8Array;
18
+ stderr: string | Uint8Array;
19
+ status: number | null;
20
+ message: string;
21
+ pid?: number | null;
22
+ }>);
23
+ }
24
+ export declare function runGit(repository: GitRepository, args: readonly string[], input?: string | Uint8Array): Promise<Buffer>;
25
+ export declare function consumeGitStdout(repository: GitRepository, args: readonly string[], consume: (chunk: Buffer) => void): Promise<void>;
26
+ export declare function runGitWithEnvironment(repository: GitRepository, args: readonly string[], input: string | Uint8Array | undefined, environment: NodeJS.ProcessEnv): Promise<Buffer>;
@@ -0,0 +1,94 @@
1
+ import { spawn } from "node:child_process";
2
+ import { consumeProcessStdout } from "../runtime/proc/run.js";
3
+ export class GitPlumbingError extends Error {
4
+ stdout;
5
+ stderr;
6
+ status;
7
+ pid;
8
+ constructor(input) {
9
+ super(input.message);
10
+ this.name = "GitPlumbingError";
11
+ this.stdout = Buffer.from(input.stdout ?? "");
12
+ this.stderr = Buffer.from(input.stderr);
13
+ this.status = input.status;
14
+ this.pid = input.pid ?? null;
15
+ }
16
+ }
17
+ const GIT_STDERR_BYTES = 16 * 1024;
18
+ function commandError(command, error) {
19
+ const candidate = error;
20
+ const stderr = candidate.stderr === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stderr);
21
+ const stdout = candidate.stdout === undefined ? Buffer.alloc(0) : Buffer.from(candidate.stdout);
22
+ const detail = stderr.length === 0 ? candidate.message ?? "git command failed" : stderr.toString("utf8");
23
+ return new GitPlumbingError({
24
+ stdout,
25
+ stderr,
26
+ status: candidate.status ?? null,
27
+ message: `${command.join(" ")}: ${detail}`,
28
+ pid: candidate.pid ?? null,
29
+ });
30
+ }
31
+ async function executeGit(repository, args, input, environment) {
32
+ const stdout = [];
33
+ const stderr = [];
34
+ let stderrBytes = 0;
35
+ let inputError;
36
+ const child = spawn(repository.gitPath, [...args], {
37
+ cwd: repository.effectiveCwd,
38
+ ...(environment === undefined ? {} : { env: { ...process.env, ...environment } }),
39
+ stdio: ["pipe", "pipe", "pipe"],
40
+ windowsHide: true,
41
+ });
42
+ child.stdout.on("data", (chunk) => stdout.push(chunk));
43
+ child.stderr.on("data", (chunk) => {
44
+ if (stderrBytes >= GIT_STDERR_BYTES)
45
+ return;
46
+ const retained = chunk.subarray(0, GIT_STDERR_BYTES - stderrBytes);
47
+ stderr.push(retained);
48
+ stderrBytes += retained.length;
49
+ });
50
+ child.stdin.on("error", (error) => { inputError = error; });
51
+ child.stdin.end(input);
52
+ const terminal = await new Promise((resolveTerminal) => {
53
+ child.once("error", (error) => resolveTerminal({ code: null, error }));
54
+ child.once("close", (code) => resolveTerminal({ code }));
55
+ });
56
+ const output = Buffer.concat(stdout);
57
+ if (terminal.code === 0 && inputError === undefined)
58
+ return output;
59
+ throw commandError(args, {
60
+ message: terminal.error?.message ?? inputError?.message ?? `git exited with status ${terminal.code ?? "unknown"}`,
61
+ stdout: output,
62
+ stderr: Buffer.concat(stderr),
63
+ status: terminal.code,
64
+ pid: child.pid,
65
+ });
66
+ }
67
+ export async function runGit(repository, args, input) {
68
+ return await executeGit(repository, args, input);
69
+ }
70
+ export async function consumeGitStdout(repository, args, consume) {
71
+ const result = await consumeProcessStdout({
72
+ argv: [repository.gitPath, ...args],
73
+ cwd: repository.effectiveCwd,
74
+ }, consume);
75
+ const { outcome } = result;
76
+ if (outcome.kind === "terminal" && outcome.code === 0)
77
+ return;
78
+ const status = outcome.kind === "terminal" ? outcome.code : null;
79
+ const stderr = outcome.kind === "terminal" ? outcome.stderr : "";
80
+ const message = outcome.kind === "terminal"
81
+ ? `git exited with status ${outcome.code}`
82
+ : outcome.kind === "spawn-error" || outcome.kind === "stream-error"
83
+ ? outcome.diagnostic
84
+ : `git process ended with ${outcome.kind}`;
85
+ throw commandError(args, {
86
+ message,
87
+ stderr,
88
+ status,
89
+ pid: result.pid ?? undefined,
90
+ });
91
+ }
92
+ export async function runGitWithEnvironment(repository, args, input, environment) {
93
+ return await executeGit(repository, args, input, environment);
94
+ }
@@ -1,5 +1,6 @@
1
1
  import { type TreeEntry } from "./tree.js";
2
- import { type GitOid, type GitRepository, type GitSnapshot } from "./repository.js";
2
+ import { type GitOid, type GitSnapshot } from "./repository.js";
3
+ import { type GitRepository } from "./process.js";
3
4
  declare const gitReadObservationBrand: unique symbol;
4
5
  declare const gitDecodeChannelBrand: unique symbol;
5
6
  export type GitBlobResult = Readonly<{
@@ -2,7 +2,8 @@ import { spawn } from "node:child_process";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { gitObjectId } from "./identity.js";
4
4
  import { parseTreeObject, validPath } from "./tree.js";
5
- import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, GitPlumbingError, readRef, } from "./repository.js";
5
+ import { GIT_FORMAT_BYTES, GIT_FORMAT_PATH, GIT_REF, readRef, } from "./repository.js";
6
+ import { GitPlumbingError } from "./process.js";
6
7
  const gitReadObservationBrand = Symbol("GitReadObservation");
7
8
  const gitDecodeChannelBrand = Symbol("GitDecodeChannel");
8
9
  function streamCursor(stream) {
@@ -36,7 +37,7 @@ function streamCursor(stream) {
36
37
  return { line, exact };
37
38
  }
38
39
  function batchObjectReader(repository) {
39
- const child = spawn("git", ["cat-file", "--batch"], {
40
+ const child = spawn(repository.gitPath, ["cat-file", "--batch"], {
40
41
  cwd: repository.effectiveCwd,
41
42
  stdio: ["pipe", "pipe", "pipe"],
42
43
  windowsHide: true,
@@ -1,4 +1,5 @@
1
- import { type GitOid, type GitRepository } from "./repository.js";
1
+ import { type GitOid } from "./repository.js";
2
+ import { type GitRepository } from "./process.js";
2
3
  import type { ContractId, ContractState, SnapshotId } from "../core/facts/types.js";
3
4
  import type { GitDecodeChannel } from "./read-observation.js";
4
5
  import { type TargetCheckoutEffect, type TargetCheckoutLag } from "./target-placement.js";
@@ -2,7 +2,8 @@ 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, GitPlumbingError, isKeiyakuOwnedRef, readRef, registeredWorktreePaths, runGit, writeCommit, worktreeGitDirectory, } from "./repository.js";
5
+ import { CANDIDATE_PIN_REF_NAMESPACE, commonGitDirectory, DELIVERY_REF_NAMESPACE, isKeiyakuOwnedRef, readRef, registeredWorktreePaths, writeCommit, worktreeGitDirectory, } from "./repository.js";
6
+ import { GitPlumbingError, runGit } from "./process.js";
6
7
  import { contractLocator, contractPhysicalName, gitObjectIdForSnapshot, mintSnapshotId, } from "./identity.js";
7
8
  import { observeContractAt } from "./observe.js";
8
9
  import { acquireTargetPlacementFence, recoverTargetPlacement, } from "./target-placement.js";
@@ -206,7 +207,9 @@ async function reconcileHereWorkspaceRefs({ repository }, state, { effects, lag
206
207
  if (state.delivery === null)
207
208
  effects.push(await removeRef(repository, pin));
208
209
  else {
209
- const integration = state.delivery.data.integration.snapshot;
210
+ const integration = state.currentIntegration?.snapshot;
211
+ if (integration === undefined)
212
+ return complete();
210
213
  const custodian = await snapshotCustodian(repository, integration);
211
214
  effects.push(await (custodian === null
212
215
  ? await updateRef(repository, pin, integration)
@@ -216,7 +219,7 @@ async function reconcileHereWorkspaceRefs({ repository }, state, { effects, lag
216
219
  else if (state.bound) {
217
220
  effects.push(await (state.delivery === null
218
221
  ? await removeRef(repository, pin)
219
- : await updateRef(repository, pin, state.delivery.data.integration.snapshot)));
222
+ : await updateRef(repository, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot)));
220
223
  }
221
224
  return complete(effects, lag);
222
225
  }
@@ -314,7 +317,7 @@ async function releaseTerminalCustody({ repository, state, expected, ref, pin, a
314
317
  return;
315
318
  }
316
319
  const tender = state.delivery.data.tenderSnapshot;
317
- const integration = state.delivery.data.integration.snapshot;
320
+ const integration = state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot;
318
321
  const target = await targetCustodyForClaimedIntegration(repository, state, integration);
319
322
  if (sealedTree(expected, tender) === sealedTree(expected, integration) && target !== null) {
320
323
  effects.push(await removeRefWithCustody(repository, ref, target.ref, target.oid));
@@ -337,7 +340,7 @@ async function reconcileTerminalManagedWorktree({ repository, channel, hooks, re
337
340
  const expected = await terminalSealExpectations(channel, state);
338
341
  acc.effects.push(await updateRef(primary, ref, state.delivery?.data.tenderSnapshot ?? state.coordinates.start));
339
342
  if (state.delivery !== null) {
340
- acc.effects.push(await updateRef(primary, pin, state.delivery.data.integration.snapshot));
343
+ acc.effects.push(await updateRef(primary, pin, state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot));
341
344
  }
342
345
  if (retainTerminalWorktree === true)
343
346
  return complete(acc.effects, acc.lag);
@@ -367,7 +370,7 @@ async function reconcileActiveManagedWorktree({ repository, hooks, retryHooks, p
367
370
  if (hookLag !== null)
368
371
  lag.push(hookLag);
369
372
  effects.push(await (state.delivery
370
- ? await updateRef(repository, candidatePinRefFor(state.id), state.delivery.data.integration.snapshot)
373
+ ? await updateRef(repository, candidatePinRefFor(state.id), state.currentIntegration?.snapshot ?? state.delivery.data.integration.snapshot)
371
374
  : await removeRef(repository, candidatePinRefFor(state.id))));
372
375
  return complete(effects, lag);
373
376
  }
@@ -1,3 +1,4 @@
1
+ import { type GitRepository } from "./process.js";
1
2
  import { type TreeChange, type TreeEntry } from "./tree.js";
2
3
  export type { TreeChange } from "./tree.js";
3
4
  export declare const GIT_REF = "refs/heads/keiyaku-state";
@@ -19,14 +20,6 @@ export type GitRefAssertion = Readonly<{
19
20
  readonly ref: string;
20
21
  readonly oid: GitOid;
21
22
  }>;
22
- export type GitRepository = Readonly<{
23
- /** The invocation's effective working directory, including a caller -C worktree. */
24
- readonly effectiveCwd: string;
25
- /** The canonical primary worktree root for this repository. */
26
- readonly primaryWorktree: string;
27
- /** The canonical common Git directory pinned when this capability is created. */
28
- readonly commonDirectory: string;
29
- }>;
30
23
  export type GitSnapshot = Readonly<{
31
24
  readonly commit: GitOid | null;
32
25
  readonly tree: GitOid | null;
@@ -36,41 +29,21 @@ export declare class NoGitWorldError extends Error {
36
29
  readonly path: string;
37
30
  constructor(path: string);
38
31
  }
39
- export declare class GitPlumbingError extends Error {
40
- readonly stdout: Buffer;
41
- readonly stderr: Buffer;
42
- readonly status: number | null;
43
- readonly pid: number | null;
44
- constructor(input: Readonly<{
45
- stdout?: string | Uint8Array;
46
- stderr: string | Uint8Array;
47
- status: number | null;
48
- message: string;
49
- pid?: number | null;
50
- }>);
51
- }
52
32
  export type RegisteredWorktree = Readonly<{
53
33
  path: string;
54
34
  branch: string | null;
55
35
  }>;
56
36
  export declare function registeredWorktrees(repository: GitRepository): Promise<readonly RegisteredWorktree[]>;
57
37
  export declare function registeredWorktreePaths(repository: GitRepository): Promise<readonly string[]>;
58
- export declare function repositoryAt(cwd: string): Promise<GitRepository>;
38
+ export declare function repositoryAt(cwd: string, gitPath?: string): Promise<GitRepository>;
59
39
  export declare function commonGitDirectory(repository: GitRepository): string;
60
40
  export declare function worktreeGitDirectory(repository: GitRepository, worktree: string): Promise<string>;
61
41
  export declare function worktreeRoot(repository: GitRepository): Promise<string>;
62
- export declare function runGit(repository: GitRepository, args: readonly string[], input?: string | Uint8Array): Promise<Buffer>;
63
- export declare function consumeGitStdout(repository: GitRepository, args: readonly string[], consume: (chunk: Buffer) => void): Promise<void>;
64
- export declare function runGitWithEnvironment(repository: GitRepository, args: readonly string[], input: string | Uint8Array | undefined, environment: NodeJS.ProcessEnv): Promise<Buffer>;
65
42
  export declare function readRef(repository: GitRepository, ref: string): Promise<GitOid | null>;
66
43
  export declare function readTreeEntries(repository: GitRepository, tree: GitOid): Promise<Map<string, TreeEntry>>;
67
44
  export declare function readGit(repository: GitRepository): Promise<GitSnapshot>;
68
45
  /** Read only the requested private Git paths from one immutable Git tree. */
69
46
  export declare function readGitPaths(repository: GitRepository, requestedPaths: readonly string[]): Promise<GitSnapshot>;
70
- /** Add paths from the same immutable Git tree without rereading its ref. */
71
- export declare function extendGitPaths(repository: GitRepository, snapshot: GitSnapshot, requestedPaths: readonly string[]): Promise<GitSnapshot>;
72
- /** Expand a targeted immutable snapshot to its complete already-pinned tree. */
73
- export declare function expandGitSnapshot(repository: GitRepository, snapshot: GitSnapshot): Promise<GitSnapshot>;
74
47
  export declare function writeBlob(repository: GitRepository, bytes: string | Uint8Array): Promise<GitOid>;
75
48
  export declare function readBlob(repository: GitRepository, oid: GitOid): Promise<Buffer>;
76
49
  /** Copy only the changed paths' tree ancestors, preserving untouched subtrees by object ID. */