@astrosheep/keiyaku 4.6.3 → 4.6.6

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 (193) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +47 -11
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/references/automation.md +2 -2
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-babysit/SKILL.md +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +8 -9
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +37 -25
  6. package/build/src/akuma/akuma-observe.d.ts +18 -15
  7. package/build/src/akuma/akuma-observe.js +75 -16
  8. package/build/src/akuma/akuma-wait.d.ts +4 -3
  9. package/build/src/akuma/akuma-wait.js +75 -53
  10. package/build/src/akuma/akuma.d.ts +6 -4
  11. package/build/src/akuma/akuma.js +4 -0
  12. package/build/src/akuma/call-initial-tell.d.ts +2 -2
  13. package/build/src/akuma/call-initial-tell.js +1 -1
  14. package/build/src/akuma/call-request.d.ts +2 -2
  15. package/build/src/akuma/heart/index.d.ts +7 -2
  16. package/build/src/akuma/heart/index.js +50 -5
  17. package/build/src/akuma/heart/tells.d.ts +33 -2
  18. package/build/src/akuma/heart/tells.js +24 -18
  19. package/build/src/akuma/heart/timeline.d.ts +23 -1
  20. package/build/src/akuma/heart/timeline.js +28 -21
  21. package/build/src/akuma/projection.d.ts +10 -10
  22. package/build/src/akuma/providers/pi/index.js +72 -39
  23. package/build/src/akuma/selection-observation.d.ts +20 -20
  24. package/build/src/akuma-body.js +1 -0
  25. package/build/src/alias/index.d.ts +0 -1
  26. package/build/src/alias/index.js +0 -4
  27. package/build/src/body/amend.d.ts +0 -4
  28. package/build/src/body/amend.js +0 -3
  29. package/build/src/body/region.d.ts +0 -1
  30. package/build/src/body/region.js +0 -3
  31. package/build/src/body/verification.d.ts +0 -1
  32. package/build/src/cli/commands/akuma-grammar.js +23 -66
  33. package/build/src/cli/commands/akuma.js +17 -30
  34. package/build/src/cli/commands/contract-grammar.d.ts +14 -1
  35. package/build/src/cli/commands/contract-grammar.js +54 -21
  36. package/build/src/cli/commands/contract-help.d.ts +9 -8
  37. package/build/src/cli/commands/contract-help.js +14 -7
  38. package/build/src/cli/commands/contract.js +57 -117
  39. package/build/src/cli/commands/install.d.ts +5 -5
  40. package/build/src/cli/commands/install.js +1 -4
  41. package/build/src/cli/commands/task-grammar.js +10 -66
  42. package/build/src/cli/commands/task.d.ts +0 -1
  43. package/build/src/cli/commands/task.js +34 -53
  44. package/build/src/cli/coordinates.d.ts +1 -2
  45. package/build/src/cli/coordinates.js +1 -4
  46. package/build/src/cli/parse.d.ts +2 -3
  47. package/build/src/cli/parse.js +24 -66
  48. package/build/src/cli/render/akuma.d.ts +3 -16
  49. package/build/src/cli/render/akuma.js +1 -13
  50. package/build/src/cli/render/audit.js +1 -1
  51. package/build/src/cli/render/contract.js +34 -32
  52. package/build/src/cli/render/kanshi-akuma.d.ts +8 -2
  53. package/build/src/cli/render/kanshi-akuma.js +14 -7
  54. package/build/src/cli/render/kanshi.d.ts +3 -1
  55. package/build/src/cli/render/kanshi.js +44 -32
  56. package/build/src/cli/render/receipt.js +6 -1
  57. package/build/src/cli/render/task.d.ts +1 -1
  58. package/build/src/cli/render/task.js +3 -27
  59. package/build/src/cli/render/terminal.d.ts +6 -2
  60. package/build/src/cli/render/terminal.js +8 -2
  61. package/build/src/cli/runtime.js +3 -16
  62. package/build/src/cli/scan.d.ts +26 -0
  63. package/build/src/cli/scan.js +82 -0
  64. package/build/src/cli/streams.d.ts +5 -0
  65. package/build/src/cli/streams.js +11 -0
  66. package/build/src/contract-worktree.js +38 -21
  67. package/build/src/core/facts/codec.d.ts +1 -2
  68. package/build/src/core/facts/codec.js +5 -11
  69. package/build/src/core/facts/types.d.ts +16 -2
  70. package/build/src/core/facts/types.js +15 -0
  71. package/build/src/core/verbs/abandon.js +3 -9
  72. package/build/src/core/verbs/amend.js +2 -9
  73. package/build/src/core/verbs/arc.js +6 -13
  74. package/build/src/core/verbs/attestation.js +2 -9
  75. package/build/src/core/verbs/bind.js +2 -9
  76. package/build/src/core/verbs/deliver.js +2 -9
  77. package/build/src/core/verbs/placement.d.ts +1 -1
  78. package/build/src/core/verbs/placement.js +2 -9
  79. package/build/src/core/verbs/reintegrate.d.ts +1 -1
  80. package/build/src/core/verbs/reintegrate.js +2 -9
  81. package/build/src/dispatch/index.d.ts +2 -2
  82. package/build/src/dispatch/index.js +33 -11
  83. package/build/src/git/hooks.d.ts +2 -4
  84. package/build/src/git/hooks.js +0 -12
  85. package/build/src/git/integration.d.ts +5 -3
  86. package/build/src/git/integration.js +36 -25
  87. package/build/src/git/nuke.js +4 -4
  88. package/build/src/git/observe.d.ts +4 -4
  89. package/build/src/git/observe.js +5 -5
  90. package/build/src/git/process.d.ts +1 -4
  91. package/build/src/git/process.js +1 -11
  92. package/build/src/git/read-observation.js +1 -1
  93. package/build/src/git/reconcile.d.ts +1 -1
  94. package/build/src/git/reconcile.js +15 -13
  95. package/build/src/git/repository.d.ts +2 -6
  96. package/build/src/git/repository.js +18 -31
  97. package/build/src/git/scratch.d.ts +6 -6
  98. package/build/src/git/scratch.js +110 -46
  99. package/build/src/git/target-placement.d.ts +5 -5
  100. package/build/src/git/target-placement.js +18 -33
  101. package/build/src/git/tender.d.ts +6 -2
  102. package/build/src/git/tender.js +19 -12
  103. package/build/src/git/terminal-reconcile.js +24 -13
  104. package/build/src/git/terminal-seal.d.ts +2 -5
  105. package/build/src/git/terminal-seal.js +5 -3
  106. package/build/src/git/workspace.d.ts +17 -13
  107. package/build/src/git/workspace.js +58 -41
  108. package/build/src/kanshi/index.d.ts +2 -2
  109. package/build/src/kanshi/index.js +1 -1
  110. package/build/src/kanshi/read.d.ts +8 -1
  111. package/build/src/kanshi/read.js +72 -7
  112. package/build/src/kanshi/report.d.ts +12 -0
  113. package/build/src/library/akuma-creation.d.ts +4 -4
  114. package/build/src/library/bind.d.ts +1 -63
  115. package/build/src/library/bind.js +3 -9
  116. package/build/src/library/contract-creation.js +2 -30
  117. package/build/src/library/contract-execution.d.ts +0 -1
  118. package/build/src/library/contract-execution.js +9 -34
  119. package/build/src/library/contract-operations.d.ts +5 -1
  120. package/build/src/library/contract-operations.js +7 -3
  121. package/build/src/library/contract-types.d.ts +1 -1
  122. package/build/src/library/delivery.d.ts +91 -93
  123. package/build/src/library/delivery.js +2 -1
  124. package/build/src/library/input.d.ts +0 -2
  125. package/build/src/library/input.js +0 -4
  126. package/build/src/library/invocation.d.ts +0 -1
  127. package/build/src/library/invocation.js +3 -25
  128. package/build/src/library/outcome.d.ts +651 -602
  129. package/build/src/library/outcome.js +4 -4
  130. package/build/src/library/reconcile.d.ts +6 -0
  131. package/build/src/library/reconcile.js +31 -1
  132. package/build/src/library/repo.d.ts +0 -1
  133. package/build/src/library/selection.d.ts +1 -10
  134. package/build/src/library/selection.js +14 -15
  135. package/build/src/protocol/abandon.d.ts +1 -1
  136. package/build/src/protocol/abandon.js +3 -10
  137. package/build/src/protocol/amend.js +2 -5
  138. package/build/src/protocol/arc.d.ts +1 -1
  139. package/build/src/protocol/arc.js +2 -9
  140. package/build/src/protocol/attempt.d.ts +3 -20
  141. package/build/src/protocol/attempt.js +6 -5
  142. package/build/src/protocol/audit.d.ts +1 -0
  143. package/build/src/protocol/audit.js +10 -11
  144. package/build/src/protocol/completion.d.ts +129 -55
  145. package/build/src/protocol/completion.js +62 -73
  146. package/build/src/protocol/deliver.d.ts +7 -5
  147. package/build/src/protocol/deliver.js +74 -44
  148. package/build/src/protocol/execution-observation.d.ts +2 -4
  149. package/build/src/protocol/execution-observation.js +3 -7
  150. package/build/src/protocol/intent.d.ts +1 -1
  151. package/build/src/protocol/intent.js +1 -0
  152. package/build/src/protocol/operations.d.ts +24 -12
  153. package/build/src/protocol/operations.js +36 -15
  154. package/build/src/protocol/placement.d.ts +2 -1
  155. package/build/src/protocol/placement.js +21 -5
  156. package/build/src/protocol/progress.d.ts +1 -1
  157. package/build/src/protocol/read/status.js +58 -41
  158. package/build/src/protocol/reintegrate.d.ts +1 -1
  159. package/build/src/protocol/reintegrate.js +10 -19
  160. package/build/src/protocol/review.d.ts +3 -28
  161. package/build/src/protocol/review.js +10 -37
  162. package/build/src/protocol/run.d.ts +19 -7
  163. package/build/src/protocol/run.js +19 -9
  164. package/build/src/runtime/proc/windows-launch.exe +0 -0
  165. package/build/src/settlement/holder.d.ts +2 -4
  166. package/build/src/settlement/holder.js +3 -6
  167. package/build/src/task/board.d.ts +6 -0
  168. package/build/src/task/board.js +30 -1
  169. package/build/src/task/catalog.js +1 -4
  170. package/build/src/task/compose.d.ts +0 -1
  171. package/build/src/task/compose.js +0 -1
  172. package/build/src/task/context.d.ts +0 -1
  173. package/build/src/task/context.js +0 -3
  174. package/build/src/task/created-observation.d.ts +5 -1
  175. package/build/src/task/created-observation.js +12 -5
  176. package/build/src/task/index.d.ts +1 -1
  177. package/build/src/task/index.js +7 -6
  178. package/build/src/task/input.d.ts +0 -6
  179. package/build/src/task/input.js +2 -14
  180. package/build/src/task/mutation.d.ts +3 -4
  181. package/build/src/task/mutation.js +11 -39
  182. package/build/src/task/operations.d.ts +2 -7
  183. package/build/src/task/operations.js +37 -49
  184. package/build/src/task/query.d.ts +0 -1
  185. package/build/src/task/query.js +1 -4
  186. package/build/src/task/store.d.ts +0 -2
  187. package/build/src/task/store.js +27 -11
  188. package/build/src/verification/declaration.js +3 -2
  189. package/build/src/verification/execution.d.ts +3 -2
  190. package/build/src/verification/execution.js +10 -6
  191. package/build/src/workspace-place.d.ts +0 -1
  192. package/build/src/workspace-place.js +0 -11
  193. package/package.json +7 -3
@@ -3,7 +3,7 @@ import { z } from "zod";
3
3
  import { access } from "node:fs/promises";
4
4
  import { gitObjectIdForSnapshot, mintSnapshotId } from "./identity.js";
5
5
  import { decodeGitNumstat, registeredWorktreePaths } from "./repository.js";
6
- import { GitPlumbingError, runGit, runGitWithEnvironment } from "./process.js";
6
+ import { executeGit, GitPlumbingError } from "./process.js";
7
7
  import { captureWorkspaceTree, unmergedWorkspacePaths, workspaceMergeHead, worktreePath } from "./workspace.js";
8
8
  export const worktreeMissingRefusalSchema = z
9
9
  .object({ kind: z.literal("worktree-missing"), contractId: contractIdSchema })
@@ -69,7 +69,7 @@ export async function captureTender(repository, input) {
69
69
  };
70
70
  }
71
71
  async function dirtyShortStat(repository, tender) {
72
- return decodeGitNumstat(await runGit(repository, ["diff", "--numstat", "-z", gitObjectIdForSnapshot(tender.head), tender.tree]));
72
+ return decodeGitNumstat(await executeGit(repository, ["diff", "--numstat", "-z", gitObjectIdForSnapshot(tender.head), tender.tree]));
73
73
  }
74
74
  export async function dirtyTenderRefusal(repository, contractId, tender) {
75
75
  return {
@@ -87,7 +87,7 @@ export async function dirtyTenderDelta(repository, tender) {
87
87
  }
88
88
  async function effectiveConfig(repository, key) {
89
89
  try {
90
- const value = (await runGit(repository, ["config", "--get", key])).toString("utf8").trim();
90
+ const value = (await executeGit(repository, ["config", "--get", key])).toString("utf8").trim();
91
91
  return value.length === 0 ? undefined : value;
92
92
  }
93
93
  catch (error) {
@@ -119,22 +119,29 @@ export async function prepareDeliveryCommitMetadata(repository, input) {
119
119
  identity,
120
120
  };
121
121
  }
122
- /** Materialize the tender tree when it contains admitted dirty workspace bytes. */
123
- export async function materializeTenderSnapshot(repository, tender, commit) {
124
- if (!tender.dirty && tender.mergeHead === undefined)
125
- return tender.head;
126
- const parents = ["-p", gitObjectIdForSnapshot(tender.head)];
127
- if (tender.mergeHead !== undefined)
128
- parents.push("-p", gitObjectIdForSnapshot(tender.mergeHead));
129
- const oid = (await runGitWithEnvironment(repository, ["commit-tree", tender.tree, ...parents], commit.message, {
122
+ /** The Git identity environment for one delivery snapshot commit. */
123
+ export function deliveryEnvironment(commit) {
124
+ return {
130
125
  GIT_AUTHOR_NAME: commit.identity.name,
131
126
  GIT_AUTHOR_EMAIL: commit.identity.email,
132
127
  GIT_COMMITTER_NAME: commit.identity.name,
133
128
  GIT_COMMITTER_EMAIL: commit.identity.email,
134
129
  GIT_AUTHOR_DATE: commit.at,
135
130
  GIT_COMMITTER_DATE: commit.at,
136
- }))
131
+ };
132
+ }
133
+ /** Materialize one delivery snapshot commit over the given parents. */
134
+ export async function materializeSnapshotCommit(repository, tree, parents, message, environment) {
135
+ const args = parents.flatMap((parent) => ["-p", gitObjectIdForSnapshot(parent)]);
136
+ const oid = (await executeGit(repository, ["commit-tree", tree, ...args], message, environment))
137
137
  .toString("utf8")
138
138
  .trim();
139
139
  return mintSnapshotId(oid);
140
140
  }
141
+ /** Materialize the tender tree when it contains admitted dirty workspace bytes. */
142
+ export async function materializeTenderSnapshot(repository, tender, commit) {
143
+ if (!tender.dirty && tender.mergeHead === undefined)
144
+ return tender.head;
145
+ const parents = tender.mergeHead === undefined ? [tender.head] : [tender.head, tender.mergeHead];
146
+ return await materializeSnapshotCommit(repository, tender.tree, parents, commit.message, deliveryEnvironment(commit));
147
+ }
@@ -3,7 +3,7 @@ import { isKeiyakuOwnedRef, readRef, writeCommit } from "./repository.js";
3
3
  import { gitObjectIdForSnapshot, mintSnapshotId } from "./identity.js";
4
4
  import { runDestroyHooks } from "./hooks.js";
5
5
  import { terminalSealExpectations as decodeTerminalSealExpectations, terminalSealSnapshots, observeTerminalWorkspace, } from "./terminal-seal.js";
6
- import { GitPlumbingError, runGit } from "./process.js";
6
+ import { GitPlumbingError, executeGit } from "./process.js";
7
7
  import { worktreePath } from "./workspace.js";
8
8
  async function pathExists(path) {
9
9
  try {
@@ -16,29 +16,38 @@ async function pathExists(path) {
16
16
  throw error;
17
17
  }
18
18
  }
19
+ /** Attempt removal once. Retention is a lag carrying its reason; no effect claims the worktree state after it. */
19
20
  async function removeTerminalWorktree(repository, topology, path) {
20
21
  const registered = topology.paths.has(path);
21
22
  if (!registered) {
22
- return { retained: await pathExists(path) };
23
+ return (await pathExists(path)) ? { retainedLag: { kind: "worktree-retained", path } } : {};
23
24
  }
24
25
  const present = await pathExists(path);
25
26
  try {
26
- await runGit(repository, ["worktree", "remove", ...(present ? ["--force"] : []), path]);
27
+ await executeGit(repository, ["worktree", "remove", ...(present ? ["--force"] : []), path]);
27
28
  }
28
29
  catch (error) {
29
30
  if (!(error instanceof GitPlumbingError))
30
31
  throw error;
31
- return { effect: { kind: "worktree", path, action: "unchanged" }, retained: true };
32
+ return { retainedLag: { kind: "worktree-retained", path, diagnostic: error.message } };
32
33
  }
33
34
  topology.paths.delete(path);
34
- const retained = await pathExists(path);
35
- return { effect: { kind: "worktree", path, action: retained ? "unchanged" : "removed" }, retained };
35
+ if (await pathExists(path)) {
36
+ return {
37
+ retainedLag: {
38
+ kind: "worktree-retained",
39
+ path,
40
+ diagnostic: `Git reported worktree removal but the path remains: ${path}`,
41
+ },
42
+ };
43
+ }
44
+ return { effect: { kind: "worktree", path, action: "removed" } };
36
45
  }
37
46
  export async function updateRef(repository, ref, desired) {
38
47
  const before = await readRef(repository, ref);
39
48
  if (before === desired)
40
49
  return { kind: "ref", name: ref, action: "unchanged", before, after: desired };
41
- await runGit(repository, [
50
+ await executeGit(repository, [
42
51
  "update-ref",
43
52
  "--no-deref",
44
53
  ref,
@@ -51,7 +60,7 @@ export async function removeRef(repository, ref) {
51
60
  const before = await readRef(repository, ref);
52
61
  if (before === null)
53
62
  return { kind: "ref", name: ref, action: "unchanged", before: null, after: null };
54
- await runGit(repository, ["update-ref", "--no-deref", "-d", ref, before]);
63
+ await executeGit(repository, ["update-ref", "--no-deref", "-d", ref, before]);
55
64
  return { kind: "ref", name: ref, action: "removed", before, after: null };
56
65
  }
57
66
  async function removeRefWithCustody(repository, ref, custodian, expectedCustodian) {
@@ -61,7 +70,7 @@ async function removeRefWithCustody(repository, ref, custodian, expectedCustodia
61
70
  if ((await readRef(repository, custodian)) !== expectedCustodian) {
62
71
  return { kind: "ref", name: ref, action: "unchanged", before, after: before };
63
72
  }
64
- await runGit(repository, ["update-ref", "--stdin", "--no-deref"], [
73
+ await executeGit(repository, ["update-ref", "--stdin", "--no-deref"], [
65
74
  "start",
66
75
  `verify ${custodian} ${gitObjectIdForSnapshot(expectedCustodian)}`,
67
76
  `delete ${ref} ${before}`,
@@ -75,7 +84,7 @@ function retainedRefEffect(ref, snapshot) {
75
84
  return { kind: "ref", name: ref, action: "unchanged", before: snapshot, after: snapshot };
76
85
  }
77
86
  async function refRows(repository) {
78
- const output = (await runGit(repository, ["for-each-ref", "--format=%(refname)%00%(objectname)"])).toString("utf8");
87
+ const output = (await executeGit(repository, ["for-each-ref", "--format=%(refname)%00%(objectname)"])).toString("utf8");
79
88
  if (output.length === 0)
80
89
  return [];
81
90
  return output
@@ -90,7 +99,7 @@ async function refRows(repository) {
90
99
  }
91
100
  async function commitIsAncestor(repository, ancestor, descendant) {
92
101
  try {
93
- await runGit(repository, ["merge-base", "--is-ancestor", gitObjectIdForSnapshot(ancestor), descendant]);
102
+ await executeGit(repository, ["merge-base", "--is-ancestor", gitObjectIdForSnapshot(ancestor), descendant]);
94
103
  return true;
95
104
  }
96
105
  catch (error) {
@@ -181,8 +190,10 @@ async function removeSealedTerminalWorktree({ repository, topology, path, state,
181
190
  }
182
191
  }
183
192
  const removal = await removeTerminalWorktree(repository, topology, path);
184
- if (removal.retained)
185
- return retainTerminalWorktree(path, { kind: "worktree-retained", path }, acc);
193
+ if (removal.retainedLag !== undefined) {
194
+ acc.recordLag(removal.retainedLag);
195
+ return complete(acc.effects, acc.lag, acc.hookRuns);
196
+ }
186
197
  if (removal.effect !== undefined)
187
198
  acc.recordEffect(removal.effect);
188
199
  return null;
@@ -3,7 +3,6 @@ import type { ContractState, SnapshotId } from "../core/facts/types.js";
3
3
  import { type GitObjectId } from "./identity.js";
4
4
  import type { GitOid } from "./repository.js";
5
5
  import { type GitRepository } from "./process.js";
6
- import { captureWorkspaceTree } from "./workspace.js";
7
6
  export declare const unsealedBytesSchema: z.ZodObject<{
8
7
  kind: z.ZodLiteral<"unsealed-bytes">;
9
8
  path: z.ZodString;
@@ -16,25 +15,23 @@ export type TerminalSealExpectations = Readonly<{
16
15
  trees: readonly GitObjectId[];
17
16
  treeBySnapshot: ReadonlyMap<SnapshotId, GitObjectId>;
18
17
  }>;
19
- export type TerminalSealObject = Readonly<{
18
+ type TerminalSealObject = Readonly<{
20
19
  kind: "present";
21
20
  type: string;
22
21
  bytes: Buffer;
23
22
  }> | Readonly<{
24
23
  kind: "missing";
25
24
  }>;
26
- type WorkspaceTree = Awaited<ReturnType<typeof captureWorkspaceTree>>;
27
25
  export type TerminalWorkspace = Readonly<{
28
26
  tree: GitObjectId;
29
27
  head: SnapshotId;
30
28
  submodules: readonly string[];
31
29
  }>;
32
- export type TerminalWorkspaceObservation = Readonly<{
30
+ type TerminalWorkspaceObservation = Readonly<{
33
31
  workspace: TerminalWorkspace;
34
32
  unsealed: UnsealedBytes | null;
35
33
  }>;
36
34
  export declare function terminalSealSnapshots(state: ContractState): readonly SnapshotId[];
37
35
  export declare function terminalSealExpectations(state: ContractState, objects: ReadonlyMap<GitOid, TerminalSealObject>): TerminalSealExpectations;
38
- export declare function unsealedWorkspaceBytes(repository: GitRepository, path: string, expected: TerminalSealExpectations, workspace: WorkspaceTree): Promise<UnsealedBytes | null>;
39
36
  export declare function observeTerminalWorkspace(repository: GitRepository, path: string, expected: TerminalSealExpectations): Promise<TerminalWorkspaceObservation>;
40
37
  export {};
@@ -1,7 +1,7 @@
1
1
  import { mintedSnapshotInputSchema } from "./identity.js";
2
2
  import { z } from "zod";
3
3
  import { gitObjectId, gitObjectIdForSnapshot, mintSnapshotId } from "./identity.js";
4
- import { runGit } from "./process.js";
4
+ import { executeGit } from "./process.js";
5
5
  import { captureWorkspaceTree } from "./workspace.js";
6
6
  export const unsealedBytesSchema = z
7
7
  .object({
@@ -71,12 +71,14 @@ export function terminalSealExpectations(state, objects) {
71
71
  };
72
72
  }
73
73
  async function changedPaths(repository, left, right) {
74
- const fields = (await runGit(repository, ["diff", "--name-only", "-z", left, right])).toString("utf8").split("\0");
74
+ const fields = (await executeGit(repository, ["diff", "--name-only", "-z", left, right]))
75
+ .toString("utf8")
76
+ .split("\0");
75
77
  if (fields.at(-1) !== "")
76
78
  throw new Error("Git tree diff paths are not NUL terminated");
77
79
  return fields.slice(0, -1).sort();
78
80
  }
79
- export async function unsealedWorkspaceBytes(repository, path, expected, workspace) {
81
+ async function unsealedWorkspaceBytes(repository, path, expected, workspace) {
80
82
  const headIsSealed = expected.heads.includes(workspace.head);
81
83
  if (workspace.changes.submodules.length > 0) {
82
84
  return {
@@ -2,8 +2,9 @@ import { z } from "zod";
2
2
  import type { ContractId, SnapshotId } from "../core/facts/types.js";
3
3
  import { type GitObjectId } from "./identity.js";
4
4
  import { type GitRepository } from "./process.js";
5
+ import type { GitReadObservation } from "./read-observation.js";
5
6
  import type { ContractWorkspaceLocation } from "../workspace-place.js";
6
- export type WorkspaceChanges = Readonly<{
7
+ type WorkspaceChanges = Readonly<{
7
8
  staged: readonly string[];
8
9
  unstaged: readonly string[];
9
10
  untracked: readonly string[];
@@ -19,7 +20,7 @@ export type WorkspaceTree = Readonly<{
19
20
  export declare function worktreePath(repository: GitRepository, place: string): string;
20
21
  /** Check out a captured candidate tree in the appointed detached worktree. */
21
22
  export declare function checkoutDetachedSnapshot(repository: GitRepository, workspace: string, snapshot: SnapshotId): Promise<void>;
22
- export type DependentWorktreeFollow = Readonly<{
23
+ type DependentWorktreeFollow = Readonly<{
23
24
  kind: "followed";
24
25
  before: SnapshotId;
25
26
  after: SnapshotId;
@@ -42,7 +43,7 @@ export declare function workspaceMergeHead(repository: GitRepository, workspace:
42
43
  export declare function workspaceMergeStatePresent(repository: GitRepository, workspace: string): Promise<boolean>;
43
44
  /** Sorted unique unmerged index paths from porcelain status. */
44
45
  export declare function unmergedWorkspacePaths(repository: GitRepository, workspace: string): Promise<readonly string[]>;
45
- export type WorkspaceChangeCounts = Readonly<{
46
+ type WorkspaceChangeCounts = Readonly<{
46
47
  staged: number;
47
48
  unstaged: number;
48
49
  untracked: number;
@@ -55,7 +56,7 @@ export declare const conflictRecoverySchema: z.ZodObject<{
55
56
  }, z.core.$strict>;
56
57
  type ConflictRecovery = z.infer<typeof conflictRecoverySchema>;
57
58
  export declare const conflictRecovery: ConflictRecovery;
58
- export type ContractWorkspaceMerge = Readonly<{
59
+ type ContractWorkspaceMerge = Readonly<{
59
60
  head: SnapshotId;
60
61
  unmergedPaths: readonly string[];
61
62
  handoffBase?: SnapshotId;
@@ -100,7 +101,16 @@ export declare function observeWorkspace(repository: GitRepository, location: Co
100
101
  export declare function observeTargetLag(repository: GitRepository, workspace: string, head: SnapshotId | null | undefined): Promise<ContractTargetLag>;
101
102
  /** Capture tracked and untracked workspace bytes without changing its real index. */
102
103
  export declare function captureWorkspaceTree(repository: GitRepository, workspace: string): Promise<WorkspaceTree>;
103
- export type HandoffRetirement = Readonly<{
104
+ export type HandoffReceiptCoordinate = Readonly<{
105
+ contractId: ContractId;
106
+ place: string;
107
+ workspace: string;
108
+ }>;
109
+ export type ConflictHandoff = Readonly<{
110
+ base: SnapshotId;
111
+ mergeHead: SnapshotId;
112
+ }>;
113
+ type HandoffRetirement = Readonly<{
104
114
  kind: "absent" | "active" | "orphan-retired" | "retired";
105
115
  }> | Readonly<{
106
116
  kind: "retained";
@@ -113,14 +123,8 @@ export declare function recordConflictHandoff(repository: GitRepository, input:
113
123
  head: SnapshotId;
114
124
  mergeHead: SnapshotId;
115
125
  }>): Promise<void>;
116
- export declare function conflictHandoffFor(repository: GitRepository, input: Readonly<{
117
- contractId: ContractId;
118
- place: string;
119
- workspace: string;
120
- }>): Promise<Readonly<{
121
- base: SnapshotId;
122
- mergeHead: SnapshotId;
123
- }> | undefined>;
126
+ /** Read selected handoff refs once and decode their receipts through the call's blob channel. */
127
+ export declare function readConflictHandoffs(repository: GitRepository, readBlobs: GitReadObservation["readBlobs"], coordinates: readonly HandoffReceiptCoordinate[]): Promise<ReadonlyMap<ContractId, ConflictHandoff>>;
124
128
  export declare function retireConflictHandoff(repository: GitRepository, input: Readonly<{
125
129
  contractId: ContractId;
126
130
  place: string;
@@ -2,20 +2,21 @@ import { z } from "zod";
2
2
  import { access, mkdtemp, rm } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join, resolve } from "node:path";
5
+ import { AuthorityCorruptionError } from "../core/facts/errors.js";
5
6
  import { contractPhysicalName, gitObjectId, mintSnapshotId } from "./identity.js";
6
- import { GitPlumbingError, runGit, runGitWithEnvironment } from "./process.js";
7
- import { HANDOFF_RECEIPT_REF_NAMESPACE, readBlob, readRef, worktreeGitDirectory, writeBlob } from "./repository.js";
7
+ import { executeGit, GitPlumbingError } from "./process.js";
8
+ import { HANDOFF_RECEIPT_REF_NAMESPACE, readBlob, readRef, readRefs, worktreeGitDirectory, writeBlob, } from "./repository.js";
8
9
  const WORKTREE_DIRECTORY = [".keiyaku", "wt"];
9
10
  export function worktreePath(repository, place) {
10
11
  return resolve(repository.primaryWorktree, ...WORKTREE_DIRECTORY, place);
11
12
  }
12
13
  /** Check out a captured candidate tree in the appointed detached worktree. */
13
14
  export async function checkoutDetachedSnapshot(repository, workspace, snapshot) {
14
- await runGit(repository, ["-C", workspace, "checkout", "--quiet", "--force", "--detach", gitObjectId(snapshot)]);
15
+ await executeGit(repository, ["-C", workspace, "checkout", "--quiet", "--force", "--detach", gitObjectId(snapshot)]);
15
16
  }
16
17
  async function workspaceRevision(repository, workspace, revision) {
17
18
  try {
18
- const value = (await runGit(repository, ["-C", workspace, "rev-parse", "--verify", "--quiet", revision]))
19
+ const value = (await executeGit(repository, ["-C", workspace, "rev-parse", "--verify", "--quiet", revision]))
19
20
  .toString("utf8")
20
21
  .trim();
21
22
  return value.length === 0 ? null : mintSnapshotId(value);
@@ -37,7 +38,7 @@ async function workspaceOperationState(repository, workspace, gitDirectory) {
37
38
  return false;
38
39
  }
39
40
  }));
40
- const unmerged = (await runGit(repository, ["-C", workspace, "ls-files", "--unmerged", "-z"])).length > 0;
41
+ const unmerged = (await executeGit(repository, ["-C", workspace, "ls-files", "--unmerged", "-z"])).length > 0;
41
42
  return { other: other.some(Boolean), unmerged };
42
43
  }
43
44
  /** Follow a dependency baseline only through a clean, detached fast-forward. */
@@ -58,7 +59,7 @@ export async function followDependentManagedWorktree(repository, workspace, targ
58
59
  ...status.unmergedPaths,
59
60
  ]),
60
61
  ].sort();
61
- const attached = await runGit(repository, ["-C", workspace, "symbolic-ref", "--quiet", "HEAD"]).then(() => true, (error) => {
62
+ const attached = await executeGit(repository, ["-C", workspace, "symbolic-ref", "--quiet", "HEAD"]).then(() => true, (error) => {
62
63
  if (error instanceof GitPlumbingError && error.status === 1)
63
64
  return false;
64
65
  throw error;
@@ -72,7 +73,7 @@ export async function followDependentManagedWorktree(repository, workspace, targ
72
73
  return { kind: "unchanged" };
73
74
  let ancestor = false;
74
75
  try {
75
- await runGit(repository, ["merge-base", "--is-ancestor", gitObjectId(head), gitObjectId(target)]);
76
+ await executeGit(repository, ["merge-base", "--is-ancestor", gitObjectId(head), gitObjectId(target)]);
76
77
  ancestor = true;
77
78
  }
78
79
  catch (error) {
@@ -81,17 +82,26 @@ export async function followDependentManagedWorktree(repository, workspace, targ
81
82
  }
82
83
  if (!ancestor)
83
84
  return { kind: "retained", head, reason: "head-moved", paths };
84
- await runGit(repository, ["-C", workspace, "checkout", "--quiet", "--detach", gitObjectId(target)]);
85
+ await executeGit(repository, ["-C", workspace, "checkout", "--quiet", "--detach", gitObjectId(target)]);
85
86
  return { kind: "followed", before: head, after: target };
86
87
  }
87
88
  export async function withPrivateGitIndex(action) {
88
89
  const directory = await mkdtemp(join(tmpdir(), "keiyaku-v4-index-"));
90
+ let value;
89
91
  try {
90
- return await action({ GIT_INDEX_FILE: join(directory, "index") });
92
+ value = await action({ GIT_INDEX_FILE: join(directory, "index") });
91
93
  }
92
- finally {
93
- await rm(directory, { recursive: true, force: true });
94
+ catch (error) {
95
+ try {
96
+ await rm(directory, { recursive: true, force: true });
97
+ }
98
+ catch (cleanup) {
99
+ throw new AggregateError([error, cleanup], "Private Git index capture and cleanup failed");
100
+ }
101
+ throw error;
94
102
  }
103
+ await rm(directory, { recursive: true, force: true });
104
+ return value;
95
105
  }
96
106
  function fields(record, count) {
97
107
  const result = [];
@@ -107,15 +117,7 @@ function fields(record, count) {
107
117
  return result;
108
118
  }
109
119
  async function workspaceStatus(repository, workspace) {
110
- const records = (await runGit(repository, [
111
- "-C",
112
- workspace,
113
- "status",
114
- "--porcelain=v2",
115
- "-z",
116
- "--untracked-files=all",
117
- "--ignore-submodules=none",
118
- ]))
120
+ const records = (await executeGit(repository, ["-C", workspace, "status", "--porcelain=v2", "-z", "--untracked-files=all", "--ignore-submodules=none"], undefined, { GIT_OPTIONAL_LOCKS: "0" }))
119
121
  .toString("utf8")
120
122
  .split("\0");
121
123
  const staged = new Set();
@@ -172,7 +174,7 @@ async function workspaceChanges(repository, workspace) {
172
174
  /** Observe MERGE_HEAD in an appointed workspace, or undefined when absent. */
173
175
  export async function workspaceMergeHead(repository, workspace) {
174
176
  try {
175
- return mintSnapshotId((await runGit(repository, ["-C", workspace, "rev-parse", "-q", "--verify", "MERGE_HEAD"]))
177
+ return mintSnapshotId((await executeGit(repository, ["-C", workspace, "rev-parse", "-q", "--verify", "MERGE_HEAD"]))
176
178
  .toString("utf8")
177
179
  .trim());
178
180
  }
@@ -250,7 +252,7 @@ export async function observeTargetLag(repository, workspace, head) {
250
252
  if (head === null)
251
253
  return { kind: "unknown", subject };
252
254
  try {
253
- const text = (await runGit(repository, ["-C", workspace, "rev-list", "--count", `HEAD..${head}`]))
255
+ const text = (await executeGit(repository, ["-C", workspace, "rev-list", "--count", `HEAD..${head}`]))
254
256
  .toString("utf8")
255
257
  .trim();
256
258
  if (!/^[0-9]+$/u.test(text))
@@ -265,7 +267,7 @@ export async function observeTargetLag(repository, workspace, head) {
265
267
  }
266
268
  /** Capture tracked and untracked workspace bytes without changing its real index. */
267
269
  export async function captureWorkspaceTree(repository, workspace) {
268
- const identities = (await runGit(repository, ["-C", workspace, "show", "-s", "--format=%H%n%T%n%cI", "HEAD"]))
270
+ const identities = (await executeGit(repository, ["-C", workspace, "show", "-s", "--format=%H%n%T%n%cI", "HEAD"]))
269
271
  .toString("utf8")
270
272
  .split("\n");
271
273
  if (identities.length !== 4 ||
@@ -285,10 +287,10 @@ export async function captureWorkspaceTree(repository, workspace) {
285
287
  if (!dirty)
286
288
  return { tree, head, at, dirty, changes };
287
289
  return await withPrivateGitIndex(async (environment) => {
288
- await runGitWithEnvironment(repository, ["-C", workspace, "read-tree", "HEAD"], undefined, environment);
289
- await runGitWithEnvironment(repository, ["-C", workspace, "add", "--all"], undefined, environment);
290
+ await executeGit(repository, ["-C", workspace, "-c", "core.splitIndex=false", "read-tree", gitObjectId(head)], undefined, environment);
291
+ await executeGit(repository, ["-C", workspace, "-c", "core.splitIndex=false", "add", "--all"], undefined, environment);
290
292
  return {
291
- tree: gitObjectId((await runGitWithEnvironment(repository, ["-C", workspace, "write-tree"], undefined, environment))
293
+ tree: gitObjectId((await executeGit(repository, ["-C", workspace, "-c", "core.splitIndex=false", "write-tree"], undefined, environment))
292
294
  .toString("utf8")
293
295
  .trim(), "workspace tree"),
294
296
  head,
@@ -320,6 +322,11 @@ function receiptFor(value) {
320
322
  mergeHead: mintSnapshotId(record.mergeHead),
321
323
  };
322
324
  }
325
+ function receiptMatches(receipt, coordinate) {
326
+ return (receipt.contractId === coordinate.contractId &&
327
+ receipt.place === coordinate.place &&
328
+ receipt.workspace === resolve(coordinate.workspace));
329
+ }
323
330
  async function storedHandoffReceipt(repository, contractId, place) {
324
331
  const ref = handoffRefFor(contractId, place);
325
332
  const oid = await readRef(repository, ref);
@@ -329,7 +336,7 @@ async function storedHandoffReceipt(repository, contractId, place) {
329
336
  return { receipt, ref, oid };
330
337
  }
331
338
  async function removeHandoffReceipt(repository, stored) {
332
- await runGit(repository, ["update-ref", "--no-deref", "-d", stored.ref, stored.oid]);
339
+ await executeGit(repository, ["update-ref", "--no-deref", "-d", stored.ref, stored.oid]);
333
340
  }
334
341
  export async function recordConflictHandoff(repository, input) {
335
342
  const ref = handoffRefFor(input.contractId, input.place);
@@ -345,27 +352,37 @@ export async function recordConflictHandoff(repository, input) {
345
352
  mergeHead: input.mergeHead,
346
353
  };
347
354
  const oid = await writeBlob(repository, `${JSON.stringify(receipt)}\n`);
348
- await runGit(repository, ["update-ref", "--no-deref", ref, oid, "0".repeat(oid.length)]);
355
+ await executeGit(repository, ["update-ref", "--no-deref", ref, oid, "0".repeat(oid.length)]);
349
356
  }
350
- export async function conflictHandoffFor(repository, input) {
351
- const stored = await storedHandoffReceipt(repository, input.contractId, input.place);
352
- if (stored === null)
353
- return undefined;
354
- if (stored.receipt.contractId !== input.contractId ||
355
- stored.receipt.place !== input.place ||
356
- stored.receipt.workspace !== resolve(input.workspace)) {
357
- return undefined;
357
+ /** Read selected handoff refs once and decode their receipts through the call's blob channel. */
358
+ export async function readConflictHandoffs(repository, readBlobs, coordinates) {
359
+ const byRef = new Map();
360
+ for (const coordinate of coordinates)
361
+ byRef.set(handoffRefFor(coordinate.contractId, coordinate.place), coordinate);
362
+ if (byRef.size === 0)
363
+ return new Map();
364
+ const resolved = await readRefs(repository, [...byRef.keys()]);
365
+ const present = [...resolved].flatMap(([ref, oid]) => (oid === null ? [] : [{ ref, oid }]));
366
+ const blobs = await readBlobs(present.map(({ oid }) => oid));
367
+ const handoffs = new Map();
368
+ for (const { ref, oid } of present) {
369
+ const result = blobs.get(oid);
370
+ if (result?.kind !== "present")
371
+ throw new AuthorityCorruptionError(`Git handoff receipt is missing: ${ref}`);
372
+ const receipt = receiptFor(JSON.parse(result.bytes.toString("utf8")));
373
+ const coordinate = byRef.get(ref);
374
+ if (!receiptMatches(receipt, coordinate))
375
+ continue;
376
+ handoffs.set(coordinate.contractId, { base: receipt.head, mergeHead: receipt.mergeHead });
358
377
  }
359
- return { base: stored.receipt.head, mergeHead: stored.receipt.mergeHead };
378
+ return handoffs;
360
379
  }
361
380
  export async function retireConflictHandoff(repository, input) {
362
381
  const stored = await storedHandoffReceipt(repository, input.contractId, input.place);
363
382
  if (stored === null)
364
383
  return { kind: "absent" };
365
384
  const { receipt } = stored;
366
- if (receipt.contractId !== input.contractId ||
367
- receipt.place !== input.place ||
368
- receipt.workspace !== resolve(input.workspace)) {
385
+ if (!receiptMatches(receipt, input)) {
369
386
  return { kind: "retained", reason: "receipt-mismatch" };
370
387
  }
371
388
  const gitDirectory = await worktreeGitDirectory(repository, input.workspace);
@@ -389,7 +406,7 @@ export async function retireConflictHandoff(repository, input) {
389
406
  }
390
407
  if (!input.consume)
391
408
  return { kind: "active" };
392
- await runGit(repository, ["-C", input.workspace, "merge", "--quit"]);
409
+ await executeGit(repository, ["-C", input.workspace, "merge", "--quit"]);
393
410
  await removeHandoffReceipt(repository, stored);
394
411
  return { kind: "retired" };
395
412
  }
@@ -1,5 +1,5 @@
1
- export { kanshi, observeKanshi } from "./read.js";
1
+ export { kanshi, kanshiCategory, observeKanshi } from "./read.js";
2
2
  export { selectKanshi, selectRegion } from "./select.js";
3
3
  export type { KanshiInput } from "./read.js";
4
- export type { AkumaKanshiRow, AkumaKanshiWorld, ContractEndpointObservation, ContractHolderObservation, ContractKanshiBoard, ContractKanshiRow, KanshiReport, Section, TaskKanshiRow, TaskKanshiWorld, KanshiRegionSelection, RegionDeclaration, RegionOverlap, RegionRead, } from "./report.js";
4
+ export type { AkumaKanshiRow, AkumaKanshiWorld, ContractEndpointObservation, ContractHolderObservation, ContractKanshiBoard, ContractKanshiRow, KanshiCategoryReport, KanshiReport, Section, TaskKanshiRow, TaskKanshiWorld, KanshiRegionSelection, RegionDeclaration, RegionOverlap, RegionRead, } from "./report.js";
5
5
  export type { KanshiSelection } from "./select.js";
@@ -1,2 +1,2 @@
1
- export { kanshi, observeKanshi } from "./read.js";
1
+ export { kanshi, kanshiCategory, observeKanshi } from "./read.js";
2
2
  export { selectKanshi, selectRegion } from "./select.js";
@@ -2,7 +2,7 @@ import { type AliasBinding } from "../alias/index.js";
2
2
  import { Repo } from "../library/repo.js";
3
3
  import type { ContractBoard } from "../protocol/read/status.js";
4
4
  import type { WorldRoot } from "../world.js";
5
- import type { KanshiRegionSelection, KanshiReport, Section } from "./report.js";
5
+ import type { KanshiCategoryReport, KanshiRegionSelection, KanshiReport, Section } from "./report.js";
6
6
  export type KanshiInput = Readonly<{
7
7
  world: WorldRoot | null;
8
8
  repo?: Repo;
@@ -15,3 +15,10 @@ export type KanshiObservation = Readonly<{
15
15
  }>;
16
16
  export declare function observeKanshi(input: KanshiInput): Promise<KanshiObservation>;
17
17
  export declare function kanshi(input: KanshiInput): Promise<KanshiReport>;
18
+ export type KanshiCategoryInput = Readonly<{
19
+ world: WorldRoot | null;
20
+ repo?: Repo;
21
+ category: KanshiCategoryReport["category"];
22
+ limit?: number;
23
+ }>;
24
+ export declare function kanshiCategory(input: KanshiCategoryInput): Promise<KanshiCategoryReport>;