@astrosheep/keiyaku 2.9.11 → 2.9.13

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 (218) hide show
  1. package/README.md +79 -85
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/call-terms.js +1 -0
  4. package/build/agents/providers/claude-agent-sdk/adapter.js +17 -4
  5. package/build/cli/commands/akuma/akuma/handler.js +2 -1
  6. package/build/cli/commands/akuma/akuma/meta.js +4 -3
  7. package/build/cli/commands/akuma/catalog.js +2 -0
  8. package/build/cli/commands/akuma/list/handler.js +1 -1
  9. package/build/cli/commands/akuma/list/meta-list.js +12 -0
  10. package/build/cli/commands/akuma/list/meta-ls.js +2 -2
  11. package/build/cli/commands/akuma.js +5 -0
  12. package/build/cli/commands/contract/amend/meta.js +4 -3
  13. package/build/cli/commands/contract/arc/meta.js +2 -0
  14. package/build/cli/commands/contract/audit/handler.js +28 -2
  15. package/build/cli/commands/contract/audit/meta.js +4 -3
  16. package/build/cli/commands/contract/bind/meta.js +4 -4
  17. package/build/cli/commands/contract/petition/handler.js +2 -1
  18. package/build/cli/commands/contract/petition/meta.js +2 -2
  19. package/build/cli/commands/contract/renew/handler.js +44 -3
  20. package/build/cli/commands/contract/renew/meta.js +5 -4
  21. package/build/cli/commands/metadata.js +14 -10
  22. package/build/cli/commands/projection/call/handler.js +1 -1
  23. package/build/cli/commands/projection/call/meta.js +1 -1
  24. package/build/cli/commands/projection/catalog.js +2 -2
  25. package/build/cli/commands/projection/{revive → fork}/handler.js +10 -5
  26. package/build/cli/commands/projection/fork/meta.js +12 -0
  27. package/build/cli/commands/projection/history/handler.js +46 -2
  28. package/build/cli/commands/projection/history/meta.js +3 -3
  29. package/build/cli/commands/projection/kill/handler.js +4 -5
  30. package/build/cli/commands/projection/kill/meta.js +2 -2
  31. package/build/cli/commands/projection/status/handler.js +29 -13
  32. package/build/cli/commands/projection/status/meta.js +14 -3
  33. package/build/cli/commands/projection/tell/handler.js +8 -4
  34. package/build/cli/commands/projection/tell/meta.js +1 -1
  35. package/build/cli/commands/projection/wait/handler.js +45 -20
  36. package/build/cli/commands/projection/wait/meta.js +1 -1
  37. package/build/cli/commands/shared.js +2 -2
  38. package/build/cli/commands/system/catalog.js +2 -0
  39. package/build/cli/commands/task/add/handler.js +28 -12
  40. package/build/cli/commands/task/add/meta.js +18 -6
  41. package/build/cli/commands/task/catalog.js +4 -0
  42. package/build/cli/commands/task/compose/handler.js +18 -0
  43. package/build/cli/commands/task/compose/meta.js +10 -0
  44. package/build/cli/commands/task/doctor/handler.js +2 -3
  45. package/build/cli/commands/task/doctor/meta.js +2 -2
  46. package/build/cli/commands/task/done/meta.js +2 -2
  47. package/build/cli/commands/task/drop/handler.js +7 -2
  48. package/build/cli/commands/task/drop/meta.js +6 -3
  49. package/build/cli/commands/task/hold/meta.js +2 -2
  50. package/build/cli/commands/task/log/meta.js +2 -2
  51. package/build/cli/commands/task/ls/meta.js +2 -2
  52. package/build/cli/commands/task/note/handler.js +6 -0
  53. package/build/cli/commands/task/note/meta.js +11 -0
  54. package/build/cli/commands/task/resume/meta.js +2 -2
  55. package/build/cli/commands/task/shared.js +29 -15
  56. package/build/cli/commands/task/show/meta.js +2 -2
  57. package/build/cli/commands/task/start/meta.js +2 -2
  58. package/build/cli/commands/task/stop/meta.js +2 -2
  59. package/build/cli/commands/task/update/meta.js +3 -2
  60. package/build/cli/commands/verification/handler.js +43 -0
  61. package/build/cli/commands/verification/meta.js +13 -0
  62. package/build/cli/completion.js +16 -13
  63. package/build/cli/flags.js +48 -12
  64. package/build/cli/help.js +29 -30
  65. package/build/cli/index.js +14 -8
  66. package/build/cli/parse-flags.js +60 -21
  67. package/build/cli/parse-metadata.js +10 -4
  68. package/build/cli/parse-selectors.js +22 -4
  69. package/build/cli/parse.js +43 -30
  70. package/build/cli/projection-address.js +7 -7
  71. package/build/cli/render/arc.js +12 -3
  72. package/build/cli/render/audit.js +2 -2
  73. package/build/cli/render/call.js +4 -4
  74. package/build/cli/render/kanshi.js +122 -85
  75. package/build/cli/render/misc.js +2 -2
  76. package/build/cli/render/petition.js +16 -1
  77. package/build/cli/render/projection-history.js +17 -4
  78. package/build/cli/render/shared.js +5 -5
  79. package/build/cli/render/status-indicator.js +47 -0
  80. package/build/cli/render/status.js +106 -76
  81. package/build/cli/render/success-response.js +20 -5
  82. package/build/cli/render/task.js +33 -5
  83. package/build/cli/render/tell.js +10 -4
  84. package/build/cli/render/tool-command-normalization.js +137 -0
  85. package/build/cli/render/tool-presentation.js +2 -1
  86. package/build/cli/render/verification.js +23 -0
  87. package/build/cli/render/wait.js +101 -33
  88. package/build/cli/types.js +2 -1
  89. package/build/config/akuma-loader.js +21 -1
  90. package/build/config/settings/knobs.js +7 -1
  91. package/build/config/settings/schema.js +10 -5
  92. package/build/core/addressing.js +3 -3
  93. package/build/core/amend.js +102 -17
  94. package/build/core/arc.js +4 -4
  95. package/build/core/audit/candidate.js +10 -18
  96. package/build/core/audit/coordinates.js +12 -17
  97. package/build/core/audit/evidence.js +6 -4
  98. package/build/core/audit/facade.js +3 -3
  99. package/build/core/audit/report.js +1 -1
  100. package/build/core/bind-reconciliation.js +42 -23
  101. package/build/core/bind.js +182 -81
  102. package/build/core/call/call.js +6 -7
  103. package/build/core/call/context.js +46 -33
  104. package/build/core/call/execution.js +21 -6
  105. package/build/core/call/prompt.js +11 -42
  106. package/build/core/contract-carrier-runtime.js +209 -105
  107. package/build/core/contract-carrier.js +156 -73
  108. package/build/core/contract-view.js +43 -6
  109. package/build/core/contract.js +2 -1
  110. package/build/core/derived-replay.js +168 -35
  111. package/build/core/draft.js +60 -27
  112. package/build/core/forfeit.js +1 -1
  113. package/build/core/lifecycle-history.js +13 -0
  114. package/build/core/lifecycle-recovery.js +32 -27
  115. package/build/core/lifecycle-runner.js +14 -14
  116. package/build/core/log.js +1 -1
  117. package/build/core/outcome-base.js +2 -2
  118. package/build/core/projection/akuma-name.js +43 -0
  119. package/build/core/projection/generation/database.js +141 -79
  120. package/build/core/projection/generation/ledger.js +91 -0
  121. package/build/core/projection/generation/model.js +19 -14
  122. package/build/core/projection/generation/projection-generation-continuation.js +1 -1
  123. package/build/core/projection/generation/projection-generation-doorbell.js +88 -0
  124. package/build/core/projection/generation/projection-generation-execution.js +24 -0
  125. package/build/core/projection/generation/projection-generation-identity.js +4 -0
  126. package/build/core/projection/generation/projection-generation-launcher.js +72 -138
  127. package/build/core/projection/generation/projection-generation-process.js +16 -15
  128. package/build/core/projection/generation/projection-generation-runner.js +132 -76
  129. package/build/core/projection/generation/projection-generation-runtime.js +10 -61
  130. package/build/core/projection/generation/protocol.js +68 -0
  131. package/build/core/projection/generation/store.js +17 -7
  132. package/build/core/projection/generation/transitions.js +24 -14
  133. package/build/core/projection/index.js +5 -5
  134. package/build/core/projection/projection-core.js +6 -2
  135. package/build/core/projection/projection-execution-observer.js +1 -1
  136. package/build/core/projection/projection-history.js +8 -8
  137. package/build/core/projection/projection-kill.js +20 -10
  138. package/build/core/projection/projection-life-observer.js +8 -8
  139. package/build/core/projection/projection-mint.js +6 -8
  140. package/build/core/projection/projection-status-observation.js +77 -8
  141. package/build/core/projection/projection-status.js +151 -4
  142. package/build/core/projection/projection-wait.js +48 -29
  143. package/build/core/projection/projection-wake.js +28 -88
  144. package/build/core/projection/tell/launch-store.js +1 -1
  145. package/build/core/projection/tell/store.js +1 -1
  146. package/build/core/renew-build.js +204 -74
  147. package/build/core/renew-plan.js +34 -9
  148. package/build/core/renew-rewrite.js +56 -15
  149. package/build/core/renew.js +39 -35
  150. package/build/core/repository-ledger/accepted-fold-read.js +6 -3
  151. package/build/core/repository-ledger/claim-evidence.js +1 -1
  152. package/build/core/repository-ledger/codec.js +11 -5
  153. package/build/core/repository-ledger/current-state-store.js +464 -349
  154. package/build/core/repository-ledger/fold-repository.js +5 -6
  155. package/build/core/repository-ledger/identity.js +60 -0
  156. package/build/core/repository-ledger/inventory.js +3 -3
  157. package/build/core/repository-ledger/read-model.js +57 -9
  158. package/build/core/repository-ledger/write-transaction.js +24 -17
  159. package/build/core/run-control/connection-bound-close.js +41 -0
  160. package/build/core/run-control/detached-lease-bootstrap.js +208 -0
  161. package/build/core/run-control/detached-process.js +14 -0
  162. package/build/core/run-control/index.js +6 -0
  163. package/build/core/{process-group.js → run-control/process-tree.js} +1 -1
  164. package/build/core/run-control/runner-lease.js +172 -0
  165. package/build/core/run-control/sqlite-process-lifetime-lock.js +174 -0
  166. package/build/core/seal.js +44 -32
  167. package/build/core/settlement/claim-delivery.js +22 -9
  168. package/build/core/settlement/claim.js +8 -6
  169. package/build/core/settlement/petition-claim-gates.js +9 -8
  170. package/build/core/settlement/petition-head-guard.js +5 -6
  171. package/build/core/settlement/petition-preview.js +21 -5
  172. package/build/core/settlement/petition.js +51 -17
  173. package/build/core/settlement/settlement.js +20 -15
  174. package/build/core/settlement/verdict.js +2 -2
  175. package/build/core/settlement/verification.js +492 -364
  176. package/build/core/status/board.js +184 -75
  177. package/build/core/status/drift.js +8 -10
  178. package/build/core/task/board.js +1 -0
  179. package/build/core/task/commands.js +25 -0
  180. package/build/core/task/compose.js +328 -0
  181. package/build/core/task/document.js +102 -39
  182. package/build/core/task/index.js +4 -3
  183. package/build/core/task/settlement-git.js +70 -30
  184. package/build/core/task/settlement-policy.js +27 -14
  185. package/build/core/task/source-board.js +1 -0
  186. package/build/core/task/task-bind-preparation.js +59 -9
  187. package/build/core/task/task-contract.js +2 -2
  188. package/build/core/task/task-git-store.js +1 -0
  189. package/build/core/task/task-store-repository.js +2 -0
  190. package/build/core/task/task.js +2 -2
  191. package/build/core/transcripts.js +4 -4
  192. package/build/core/verification-declaration.js +95 -0
  193. package/build/core/worktree-bootstrap.js +1 -1
  194. package/build/core/worktree-path.js +7 -11
  195. package/build/flow-error.js +15 -2
  196. package/build/generated/version.js +2 -2
  197. package/build/git/branches.js +41 -70
  198. package/build/git/commits.js +92 -21
  199. package/build/git/core.js +9 -161
  200. package/build/git/diff/preview.js +4 -4
  201. package/build/git/diff/read.js +4 -4
  202. package/build/git/diff/structured.js +5 -5
  203. package/build/git/process.js +229 -0
  204. package/build/git/refs.js +30 -5
  205. package/build/git/staging.js +39 -115
  206. package/build/git/streaming-batch.js +9 -16
  207. package/build/git/worktree.js +104 -91
  208. package/build/index.js +7 -0
  209. package/package.json +2 -2
  210. package/skills/keiyaku-akuma/SKILL.md +27 -21
  211. package/skills/keiyaku-task/SKILL.md +61 -5
  212. package/skills/keiyaku-workflow/SKILL.md +81 -26
  213. package/build/cli/commands/projection/revive/meta.js +0 -12
  214. package/build/core/projection/projection-life-protocol.js +0 -115
  215. package/build/core/projection/projection-runner-lock.js +0 -382
  216. package/build/core/projection/tell/database.js +0 -127
  217. package/build/core/settlement/verification-coordination.js +0 -305
  218. package/build/core/settlement/verification-supervisor.js +0 -275
@@ -0,0 +1,229 @@
1
+ import { spawn } from "node:child_process";
2
+ import { getConfig } from "../config/env.js";
3
+ import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
4
+ export const GIT_ABORT_FORCE_KILL_MS = 250;
5
+ const ADMITTED_GIT_ENV_KEYS = new Set([
6
+ "GIT_ALTERNATE_OBJECT_DIRECTORIES",
7
+ "GIT_AUTHOR_DATE",
8
+ "GIT_AUTHOR_EMAIL",
9
+ "GIT_AUTHOR_NAME",
10
+ "GIT_COMMITTER_DATE",
11
+ "GIT_COMMITTER_EMAIL",
12
+ "GIT_COMMITTER_NAME",
13
+ "GIT_INDEX_FILE",
14
+ "GIT_NO_LAZY_FETCH",
15
+ "GIT_OBJECT_DIRECTORY",
16
+ "GIT_SHALLOW_FILE",
17
+ ]);
18
+ const GIT_HOST_ENV_KEYS = [
19
+ "APPDATA",
20
+ "COMSPEC",
21
+ "ComSpec",
22
+ "HOME",
23
+ "HOMEDRIVE",
24
+ "HOMEPATH",
25
+ "LOCALAPPDATA",
26
+ "PATH",
27
+ "PATHEXT",
28
+ "SYSTEMDRIVE",
29
+ "SYSTEMROOT",
30
+ "SystemRoot",
31
+ "TEMP",
32
+ "TMP",
33
+ "TMPDIR",
34
+ "USER",
35
+ "USERNAME",
36
+ "WINDIR",
37
+ ];
38
+ function initializedRuntimeEnv() {
39
+ try {
40
+ return getConfig().runtimeEnv;
41
+ }
42
+ catch {
43
+ return undefined;
44
+ }
45
+ }
46
+ /** Resolve one initialized executable coordinate. Values are never split into shell words. */
47
+ export function resolveGitExecutable() {
48
+ const configured = initializedRuntimeEnv()?.KEIYAKU_GIT;
49
+ return configured !== undefined && configured.trim().length > 0 ? configured : "git";
50
+ }
51
+ /** Configured Git timeout, with a bootstrap-safe default before env loads. */
52
+ export function resolveGitTimeoutMs() {
53
+ try {
54
+ return getConfig().git.timeoutMs;
55
+ }
56
+ catch {
57
+ return 60_000;
58
+ }
59
+ }
60
+ function isAdmittedGitEnvironmentKey(key) {
61
+ return ADMITTED_GIT_ENV_KEYS.has(key) || key.startsWith("GIT_TRACE2");
62
+ }
63
+ /** Non-interactive controlled Git environment shared by all owned Git processes. */
64
+ export function gitProcessEnv(extra) {
65
+ const runtimeEnv = initializedRuntimeEnv() ?? process.env;
66
+ const environment = {};
67
+ for (const key of GIT_HOST_ENV_KEYS) {
68
+ const value = runtimeEnv[key];
69
+ if (typeof value === "string")
70
+ environment[key] = value;
71
+ }
72
+ for (const [key, value] of Object.entries(process.env)) {
73
+ if (typeof value === "string" && key.startsWith("GIT_TRACE2"))
74
+ environment[key] = value;
75
+ }
76
+ for (const [key, value] of Object.entries(runtimeEnv)) {
77
+ if (typeof value === "string" && key.startsWith("GIT_TRACE2"))
78
+ environment[key] = value;
79
+ }
80
+ for (const [key, value] of Object.entries(extra ?? {})) {
81
+ if (typeof value === "string" && isAdmittedGitEnvironmentKey(key))
82
+ environment[key] = value;
83
+ }
84
+ return {
85
+ ...environment,
86
+ GIT_TERMINAL_PROMPT: "0",
87
+ GCM_INTERACTIVE: "Never",
88
+ GIT_MERGE_AUTOEDIT: "no",
89
+ LC_ALL: "C",
90
+ PATH: runtimeEnv.PATH ?? "",
91
+ };
92
+ }
93
+ /**
94
+ * The only physical Git process factory. It resolves the executable once and
95
+ * preserves normalized cwd, inherited environment, argv, and timeout facts.
96
+ */
97
+ export function openGitProcess(cwd, args, options = {}) {
98
+ const normalizedCwd = normalizeFilesystemCoordinate(cwd);
99
+ const env = gitProcessEnv(options.env);
100
+ const executable = resolveGitExecutable();
101
+ return {
102
+ child: spawn(executable, [...args], {
103
+ cwd: normalizedCwd,
104
+ env,
105
+ shell: false,
106
+ stdio: ["pipe", "pipe", "pipe"],
107
+ }),
108
+ cwd: normalizedCwd,
109
+ executable,
110
+ env,
111
+ timeoutMs: options.timeoutMs ?? resolveGitTimeoutMs(),
112
+ };
113
+ }
114
+ /** Terminate one non-detached Git child and wait until Node drains its stdio close. */
115
+ export async function terminateGitChild(child, closed, graceMs = GIT_ABORT_FORCE_KILL_MS) {
116
+ if (child.exitCode === null && child.signalCode === null)
117
+ child.kill("SIGTERM");
118
+ let grace;
119
+ const exited = await Promise.race([
120
+ closed.then(() => true),
121
+ new Promise((resolve) => {
122
+ grace = setTimeout(() => resolve(false), graceMs);
123
+ grace.unref();
124
+ }),
125
+ ]);
126
+ if (grace)
127
+ clearTimeout(grace);
128
+ if (!exited)
129
+ child.kill("SIGKILL");
130
+ await closed;
131
+ }
132
+ function processError(result) {
133
+ if (result.error)
134
+ return result.error;
135
+ if (result.status === 0)
136
+ return undefined;
137
+ const error = new Error(`git exited with status ${result.status ?? "unknown"}`);
138
+ Object.assign(error, {
139
+ stdout: result.stdout.toString("utf8"),
140
+ stderr: result.stderr.toString("utf8"),
141
+ status: result.status,
142
+ });
143
+ return error;
144
+ }
145
+ /** Soft-result Git process mouth: preserves output, exit, spawn, timeout, and abort facts. */
146
+ export async function runGitProcessBytes(cwd, args, options = {}) {
147
+ options.signal?.throwIfAborted();
148
+ const process = openGitProcess(cwd, args, options);
149
+ const { child } = process;
150
+ const stdoutChunks = [];
151
+ const stderrChunks = [];
152
+ return await new Promise((resolve) => {
153
+ let settled = false;
154
+ let timedOut = false;
155
+ let aborting = false;
156
+ let termination;
157
+ const closed = new Promise((resolveClosed) => child.once("close", () => resolveClosed()));
158
+ const finish = (result) => {
159
+ if (settled)
160
+ return;
161
+ settled = true;
162
+ clearTimeout(timeout);
163
+ options.signal?.removeEventListener("abort", abort);
164
+ resolve(result);
165
+ };
166
+ const abort = () => {
167
+ if (aborting)
168
+ return;
169
+ aborting = true;
170
+ termination = terminateGitChild(child, closed);
171
+ };
172
+ const timeout = setTimeout(() => {
173
+ timedOut = true;
174
+ child.kill("SIGKILL");
175
+ }, process.timeoutMs);
176
+ options.signal?.addEventListener("abort", abort, { once: true });
177
+ if (options.signal?.aborted)
178
+ abort();
179
+ child.stdout?.on("data", (chunk) => stdoutChunks.push(chunk));
180
+ child.stderr?.on("data", (chunk) => stderrChunks.push(chunk));
181
+ child.on("error", (error) => {
182
+ Object.assign(error, { gitCwd: process.cwd });
183
+ finish({ stdout: Buffer.concat(stdoutChunks), stderr: Buffer.concat(stderrChunks), status: null, error });
184
+ });
185
+ child.on("close", async (code, signal) => {
186
+ if (termination)
187
+ await termination;
188
+ const stdout = Buffer.concat(stdoutChunks);
189
+ const stderr = Buffer.concat(stderrChunks);
190
+ if (timedOut)
191
+ return finish({ stdout, stderr, status: null, error: new Error("git timed out") });
192
+ if (signal) {
193
+ const error = options.signal?.aborted
194
+ ? options.signal.reason instanceof Error ? options.signal.reason : new DOMException("The operation was aborted", "AbortError")
195
+ : new Error(`git exited from signal ${signal}`);
196
+ return finish({ stdout, stderr, status: null, error });
197
+ }
198
+ finish({ stdout, stderr, status: code });
199
+ });
200
+ if (options.input === undefined)
201
+ child.stdin?.end();
202
+ else if (typeof options.input === "string")
203
+ child.stdin?.end(options.input, "utf8");
204
+ else
205
+ child.stdin?.end(options.input);
206
+ });
207
+ }
208
+ export async function runGitProcess(cwd, args, options = {}) {
209
+ const result = await runGitProcessBytes(cwd, args, options);
210
+ return {
211
+ stdout: result.stdout.toString("utf8"),
212
+ stderr: result.stderr.toString("utf8"),
213
+ status: result.status,
214
+ ...(result.error ? { error: result.error } : {}),
215
+ };
216
+ }
217
+ /** Throwing Git process mouth for commands whose non-zero exit is exceptional. */
218
+ export async function runGitOrThrow(cwd, args, options = {}) {
219
+ const result = await runGitProcess(cwd, args, options);
220
+ const error = processError({
221
+ stdout: Buffer.from(result.stdout),
222
+ stderr: Buffer.from(result.stderr),
223
+ status: result.status,
224
+ ...(result.error ? { error: result.error } : {}),
225
+ });
226
+ if (error)
227
+ throw error;
228
+ return result;
229
+ }
package/build/git/refs.js CHANGED
@@ -61,10 +61,35 @@ export async function resolveLocalBranchHead(cwd, branch, run = runGitProcess) {
61
61
  }
62
62
  return throwGitProcessFailure(`show-ref ${ref}`, result, cwd);
63
63
  }
64
- async function validateTransactionOperation(cwd, operation, objectIdLength) {
65
- const refCheck = await runGitProcess(cwd, ["check-ref-format", operation.ref]);
66
- if (refCheck.status !== 0 || refCheck.error) {
67
- throw new RefTransactionError(`invalid transaction ref ${JSON.stringify(operation.ref)}`, { cause: refCheck.error });
64
+ /** Resolve an exact set of direct refs with one Git process. */
65
+ export async function resolveExactRefHeads(cwd, refs, run = runGitProcess) {
66
+ const unique = [...new Set(refs.map((ref) => normalizeLocalBranchRef(ref)))];
67
+ if (unique.length === 0)
68
+ return new Map();
69
+ const result = await run(cwd, ["show-ref", ...unique]);
70
+ if (result.error || (result.status !== 0 && result.status !== 1)) {
71
+ return throwGitProcessFailure(`show-ref ${unique.join(" ")}`, result, cwd);
72
+ }
73
+ const rows = result.stdout === "" ? [] : parseShowRefRows(result.stdout);
74
+ if (rows === null)
75
+ return throwGitProcessFailure(`show-ref ${unique.join(" ")}`, result, cwd);
76
+ const requested = new Set(unique);
77
+ const found = new Map();
78
+ for (const row of rows) {
79
+ if (!requested.has(row.ref) || found.has(row.ref)) {
80
+ return throwGitProcessFailure(`show-ref ${unique.join(" ")}`, {
81
+ ...result,
82
+ status: 2,
83
+ stderr: `show-ref returned an invalid exact value for ${row.ref}`,
84
+ }, cwd);
85
+ }
86
+ found.set(row.ref, row.objectId);
87
+ }
88
+ return new Map(unique.map((ref) => [ref, found.get(ref) ?? null]));
89
+ }
90
+ async function validateTransactionOperation(operation, objectIdLength) {
91
+ if (!/^refs\/(?![./])(?!.*(?:\.\.|@\{|[~^:?*\\\[\x00-\x20\x7f]))(?!.*\/\.)(?!.*\.lock(?:\/|$))(?!.*\/$).+$/.test(operation.ref)) {
92
+ throw new RefTransactionError(`invalid transaction ref ${JSON.stringify(operation.ref)}`);
68
93
  }
69
94
  const objectIds = operation.kind === "update"
70
95
  ? [["new", operation.newOid], ["old", operation.expectedOldOid]]
@@ -160,7 +185,7 @@ export async function commitRefTransaction(cwd, operations, objectIdLength, opti
160
185
  throw new RefTransactionError("a ref transaction requires at least one operation");
161
186
  const seen = new Set();
162
187
  for (const operation of operations) {
163
- await validateTransactionOperation(cwd, operation, objectIdLength);
188
+ await validateTransactionOperation(operation, objectIdLength);
164
189
  if (seen.has(operation.ref))
165
190
  throw new RefTransactionError(`duplicate transaction ref ${operation.ref}`);
166
191
  seen.add(operation.ref);
@@ -1,155 +1,79 @@
1
1
  import { getConfig } from "../config/env.js";
2
- import { createGit, wrapGitError } from "./core.js";
3
- export async function addFiles(cwd, files) {
4
- const git = createGit(cwd);
2
+ import { wrapGitError } from "./core.js";
3
+ import { runGitOrThrow } from "./process.js";
4
+ async function run(cwd, args, label) {
5
5
  try {
6
- await git.add(files);
6
+ return (await runGitOrThrow(cwd, args)).stdout;
7
7
  }
8
- catch (err) {
9
- throw wrapGitError("add", err, cwd);
8
+ catch (error) {
9
+ throw wrapGitError(label, error, cwd);
10
10
  }
11
11
  }
12
+ export async function addFiles(cwd, files) {
13
+ await run(cwd, ["add", "--", ...(typeof files === "string" ? [files] : files)], "add");
14
+ }
12
15
  export async function addFilesForce(cwd, files) {
13
- const git = createGit(cwd);
14
- try {
15
- await git.raw(["add", "--force", "--", ...files]);
16
- }
17
- catch (err) {
18
- throw wrapGitError("add --force", err, cwd);
19
- }
16
+ await run(cwd, ["add", "--force", "--", ...files], "add --force");
20
17
  }
21
18
  export async function addUpdatedFiles(cwd) {
22
- const git = createGit(cwd);
23
- try {
24
- await git.raw(["add", "-u"]);
25
- }
26
- catch (err) {
27
- throw wrapGitError("add -u", err, cwd);
28
- }
19
+ await run(cwd, ["add", "-u"], "add -u");
29
20
  }
30
21
  export async function resetStaging(cwd) {
31
- const git = createGit(cwd);
32
- try {
33
- await git.raw(["reset"]);
34
- }
35
- catch (err) {
36
- throw wrapGitError("reset", err, cwd);
37
- }
22
+ await run(cwd, ["reset"], "reset");
38
23
  }
39
- export async function commit(cwd, message) {
40
- const git = createGit(cwd);
24
+ function commitArgs(message, allowEmpty = false) {
41
25
  const { noVerify: skipVerify, noGpgSign } = getConfig().git;
42
- const args = ["commit", "-m", message];
26
+ const args = ["commit", ...(allowEmpty ? ["--allow-empty"] : []), "-m", message];
43
27
  if (skipVerify)
44
28
  args.push("--no-verify");
45
29
  if (noGpgSign)
46
30
  args.push("--no-gpg-sign");
47
- try {
48
- await git.raw(args);
49
- }
50
- catch (err) {
51
- throw wrapGitError(args.join(" "), err, cwd);
52
- }
31
+ return args;
32
+ }
33
+ export async function commit(cwd, message) {
34
+ const args = commitArgs(message);
35
+ await run(cwd, args, args.join(" "));
53
36
  }
54
37
  export async function commitAllowEmpty(cwd, message) {
55
- const git = createGit(cwd);
56
- const { noVerify: skipVerify, noGpgSign } = getConfig().git;
57
- const args = ["commit", "--allow-empty", "-m", message];
58
- if (skipVerify)
59
- args.push("--no-verify");
60
- if (noGpgSign)
61
- args.push("--no-gpg-sign");
62
- try {
63
- await git.raw(args);
64
- }
65
- catch (err) {
66
- throw wrapGitError(args.join(" "), err, cwd);
67
- }
38
+ const args = commitArgs(message, true);
39
+ await run(cwd, args, args.join(" "));
68
40
  }
69
41
  export async function revertCommit(cwd, commitHash, message) {
70
- const git = createGit(cwd);
71
- try {
72
- await git.raw(["revert", "--no-commit", commitHash]);
73
- }
74
- catch (err) {
75
- throw wrapGitError(`revert --no-commit ${commitHash}`, err, cwd);
76
- }
42
+ await run(cwd, ["revert", "--no-commit", commitHash], `revert --no-commit ${commitHash}`);
77
43
  await commit(cwd, message);
78
44
  }
79
45
  export async function merge(cwd, branchName, message, options = {}) {
80
- const git = createGit(cwd);
81
46
  const { squash = true } = options;
82
47
  const { noVerify: skipVerify, noGpgSign } = getConfig().git;
83
- if (!squash) {
84
- const mergeOptions = [branchName, "--no-ff", "--no-edit", "-m", message];
85
- if (skipVerify)
86
- mergeOptions.push("--no-verify");
87
- if (noGpgSign)
88
- mergeOptions.push("--no-gpg-sign");
89
- try {
90
- await git.merge(mergeOptions);
91
- return;
92
- }
93
- catch (err) {
94
- throw wrapGitError(`merge ${mergeOptions.join(" ")}`, err, cwd);
95
- }
48
+ if (squash) {
49
+ await mergeSquashStaged(cwd, branchName);
50
+ await commitAllowEmpty(cwd, message);
51
+ return;
96
52
  }
97
- await mergeSquashStaged(cwd, branchName);
98
- await commitAllowEmpty(cwd, message);
53
+ const args = ["merge", branchName, "--no-ff", "--no-edit", "-m", message];
54
+ if (skipVerify)
55
+ args.push("--no-verify");
56
+ if (noGpgSign)
57
+ args.push("--no-gpg-sign");
58
+ await run(cwd, args, `merge ${args.slice(1).join(" ")}`);
99
59
  }
100
60
  export async function mergeSquashStaged(cwd, branchName) {
101
- const git = createGit(cwd);
102
- const squashArgs = ["merge", "--squash", branchName];
103
- try {
104
- await git.raw(squashArgs);
105
- }
106
- catch (err) {
107
- throw wrapGitError(squashArgs.join(" "), err, cwd);
108
- }
61
+ const args = ["merge", "--squash", branchName];
62
+ await run(cwd, args, args.join(" "));
109
63
  }
110
64
  export async function restoreFilesFromHead(cwd, files) {
111
65
  if (files.length === 0)
112
66
  return;
113
- const git = createGit(cwd);
114
67
  const args = ["restore", "--source=HEAD", "--staged", "--worktree", "--", ...files];
115
- try {
116
- await git.raw(args);
117
- }
118
- catch (err) {
119
- throw wrapGitError(args.join(" "), err, cwd);
120
- }
68
+ await run(cwd, args, args.join(" "));
121
69
  }
122
70
  export async function getUnmergedFiles(cwd) {
123
- const git = createGit(cwd);
124
- let output;
125
- try {
126
- output = await git.raw(["diff", "--name-only", "--diff-filter=U"]);
127
- }
128
- catch (err) {
129
- throw wrapGitError("diff --name-only --diff-filter=U", err, cwd);
130
- }
131
- const files = output
132
- .split(/\r?\n/)
133
- .map((line) => line.trim())
134
- .filter((line) => line.length > 0);
135
- return Array.from(new Set(files));
71
+ const output = await run(cwd, ["diff", "--name-only", "--diff-filter=U", "-z"], "diff --name-only --diff-filter=U");
72
+ return Array.from(new Set(output.split("\0").filter((file) => file.length > 0)));
136
73
  }
137
74
  export async function getLatestCommitHash(cwd) {
138
- const git = createGit(cwd);
139
- try {
140
- const hash = await git.revparse(["--short", "HEAD"]);
141
- return hash.trim();
142
- }
143
- catch (err) {
144
- throw wrapGitError("rev-parse --short HEAD", err, cwd);
145
- }
75
+ return (await run(cwd, ["rev-parse", "--short", "HEAD"], "rev-parse --short HEAD")).trim();
146
76
  }
147
77
  export async function createTagAtHead(cwd, tagName) {
148
- const git = createGit(cwd);
149
- try {
150
- await git.raw(["tag", tagName, "HEAD"]);
151
- }
152
- catch (err) {
153
- throw wrapGitError(`tag ${tagName} HEAD`, err, cwd);
154
- }
78
+ await run(cwd, ["tag", tagName, "HEAD"], `tag ${tagName} HEAD`);
155
79
  }
@@ -1,7 +1,5 @@
1
- import { spawn } from "node:child_process";
2
1
  import { isFullGitObjectId } from "./core.js";
3
- import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
4
- import { gitProcessEnv, resolveGitTimeoutMs, terminateGitChild, } from "./core.js";
2
+ import { openGitProcess, terminateGitChild, } from "./process.js";
5
3
  /** Default long-lived batch argv. One-shot calls must not use this gate. */
6
4
  export const GIT_STREAMING_BATCH_DEFAULT_ARGS = ["cat-file", "--batch"];
7
5
  /** Safety cap for a single response header line before the body frame. */
@@ -43,7 +41,8 @@ class BufferedReader {
43
41
  if (newline > maxBytes) {
44
42
  throw new GitStreamingBatchFramingError(`git streaming batch header exceeds ${maxBytes} bytes`);
45
43
  }
46
- const line = this.#buffer.subarray(0, newline).toString("ascii");
44
+ const rawLine = this.#buffer.subarray(0, newline);
45
+ const line = rawLine.subarray(0, rawLine.length > 0 && rawLine[rawLine.length - 1] === 0x0d ? -1 : rawLine.length).toString("ascii");
47
46
  this.#buffer = this.#buffer.subarray(newline + 1);
48
47
  return line;
49
48
  }
@@ -114,10 +113,6 @@ export class GitStreamingBatchSession {
114
113
  });
115
114
  this.#maxHeaderBytes = maxHeaderBytes;
116
115
  this.#signal = signal;
117
- this.#abortListener = () => this.abort();
118
- signal?.addEventListener("abort", this.#abortListener, { once: true });
119
- if (signal?.aborted)
120
- this.abort();
121
116
  this.#child.stderr.on("data", (chunk) => this.#stderr.push(chunk));
122
117
  this.#child.stdin.on("error", (error) => {
123
118
  this.#inputError = error;
@@ -130,6 +125,10 @@ export class GitStreamingBatchSession {
130
125
  resolve({ code, signal });
131
126
  });
132
127
  });
128
+ this.#abortListener = () => this.abort();
129
+ signal?.addEventListener("abort", this.#abortListener, { once: true });
130
+ if (signal?.aborted)
131
+ this.abort();
133
132
  }
134
133
  #armTimeout(owner) {
135
134
  this.#clearTimeout();
@@ -158,15 +157,9 @@ export class GitStreamingBatchSession {
158
157
  */
159
158
  static open(cwd, options) {
160
159
  const args = options?.args ?? GIT_STREAMING_BATCH_DEFAULT_ARGS;
161
- const timeoutMs = options?.timeoutMs ?? resolveGitTimeoutMs();
160
+ const process = openGitProcess(cwd, args, options);
162
161
  const maxHeaderBytes = options?.maxHeaderBytes ?? GIT_STREAMING_BATCH_MAX_HEADER_BYTES;
163
- const child = spawn("git", [...args], {
164
- cwd: normalizeFilesystemCoordinate(cwd),
165
- env: gitProcessEnv(options?.env),
166
- shell: false,
167
- stdio: ["pipe", "pipe", "pipe"],
168
- });
169
- return new GitStreamingBatchSession(cwd, child, timeoutMs, maxHeaderBytes, options?.signal);
162
+ return new GitStreamingBatchSession(process.cwd, process.child, process.timeoutMs, maxHeaderBytes, options?.signal);
170
163
  }
171
164
  /**
172
165
  * Write one request line and read one framed response record.