@arnilo/prism-coding-agent 0.0.8 → 0.0.10

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.
@@ -0,0 +1,502 @@
1
+ import { enforceExecutionPolicy } from "./execution-policy.js";
2
+ import { createGitOperations, GitError, } from "./git.js";
3
+ import { createCodingCheckTool } from "./checks.js";
4
+ function errorResult(toolName, toolCallId, message) {
5
+ return {
6
+ toolCallId,
7
+ name: toolName,
8
+ content: [{ type: "text", text: message }],
9
+ error: { message },
10
+ };
11
+ }
12
+ function messageOf(error) {
13
+ if (error instanceof GitError)
14
+ return error.message;
15
+ if (error instanceof Error)
16
+ return error.message;
17
+ return String(error);
18
+ }
19
+ function opsFactory(cwd, options) {
20
+ if (options?.operations) {
21
+ const ops = options.operations;
22
+ return async () => ops;
23
+ }
24
+ let cached;
25
+ return () => {
26
+ cached ??= createGitOperations({
27
+ cwd,
28
+ gitPath: options?.gitPath,
29
+ execFile: options?.execFile,
30
+ runner: options?.runner,
31
+ artifactWriter: options?.artifactWriter,
32
+ commitIdentity: options?.commitIdentity,
33
+ ...(options?.limits ?? {}),
34
+ });
35
+ return cached;
36
+ };
37
+ }
38
+ export function createGitStatusTool(cwd, options) {
39
+ const getOps = opsFactory(cwd, options);
40
+ return {
41
+ name: "git_status",
42
+ description: "Return structured Git status (porcelain v2) including branch metadata and dirty-state. Does not follow shell; paths are repository-relative.",
43
+ parameters: {
44
+ type: "object",
45
+ properties: {
46
+ includeIgnored: { type: "boolean", description: "Include ignored files (default false)" },
47
+ },
48
+ additionalProperties: false,
49
+ },
50
+ async execute(args, context) {
51
+ const toolCallId = context.toolCallId;
52
+ if (context.signal?.aborted)
53
+ return errorResult("git_status", toolCallId, "Operation aborted");
54
+ const includeIgnored = args.includeIgnored === true;
55
+ const policy = await enforceExecutionPolicy(options?.executionPolicy, {
56
+ kind: "git",
57
+ operation: "status",
58
+ paths: [cwd],
59
+ risk: "low",
60
+ metadata: { includeIgnored, sessionId: context.sessionId, runId: context.runId },
61
+ }, toolCallId, "git_status");
62
+ if (!policy.allowed)
63
+ return policy.result;
64
+ try {
65
+ const status = await (await getOps()).status({ includeIgnored, signal: context.signal });
66
+ const lines = [
67
+ `branch=${status.branch.head ?? "(detached)"} oid=${status.branch.oid ?? "(initial)"} dirty=${status.dirty}`,
68
+ ...status.entries.map((e) => `${e.kind}\t${e.xy}\t${e.path}${e.origPath ? `\t${e.origPath}` : ""}`),
69
+ ];
70
+ return {
71
+ toolCallId,
72
+ name: "git_status",
73
+ content: [{ type: "text", text: lines.join("\n") }],
74
+ metadata: { ...status },
75
+ };
76
+ }
77
+ catch (error) {
78
+ return errorResult("git_status", toolCallId, messageOf(error));
79
+ }
80
+ },
81
+ };
82
+ }
83
+ export function createGitDiffTool(cwd, options) {
84
+ const getOps = opsFactory(cwd, options);
85
+ return {
86
+ name: "git_diff",
87
+ description: "Return a bounded unified diff (--no-ext-diff --no-textconv). Large diffs truncate inline and may spill through the host artifact writer.",
88
+ parameters: {
89
+ type: "object",
90
+ properties: {
91
+ staged: { type: "boolean", description: "Diff the index (default false)" },
92
+ paths: {
93
+ type: "array",
94
+ items: { type: "string" },
95
+ description: "Optional pathspecs (passed after --)",
96
+ },
97
+ },
98
+ additionalProperties: false,
99
+ },
100
+ async execute(args, context) {
101
+ const toolCallId = context.toolCallId;
102
+ if (context.signal?.aborted)
103
+ return errorResult("git_diff", toolCallId, "Operation aborted");
104
+ const staged = args.staged === true;
105
+ const paths = Array.isArray(args.paths) ? args.paths : undefined;
106
+ const policy = await enforceExecutionPolicy(options?.executionPolicy, {
107
+ kind: "git",
108
+ operation: "diff",
109
+ paths: paths ?? [cwd],
110
+ risk: "low",
111
+ metadata: { staged, sessionId: context.sessionId, runId: context.runId },
112
+ }, toolCallId, "git_diff");
113
+ if (!policy.allowed)
114
+ return policy.result;
115
+ try {
116
+ const result = await (await getOps()).diff({ staged, paths, signal: context.signal });
117
+ const suffix = result.truncated
118
+ ? `\n[truncated after ${result.lineCount} lines${result.artifact ? `; artifact ${result.artifact.uri}` : ""}]`
119
+ : "";
120
+ return {
121
+ toolCallId,
122
+ name: "git_diff",
123
+ content: [{ type: "text", text: `${result.text}${suffix}`.trim() || "(empty diff)" }],
124
+ metadata: {
125
+ truncated: result.truncated,
126
+ lineCount: result.lineCount,
127
+ artifact: result.artifact,
128
+ },
129
+ };
130
+ }
131
+ catch (error) {
132
+ return errorResult("git_diff", toolCallId, messageOf(error));
133
+ }
134
+ },
135
+ };
136
+ }
137
+ export function createGitBranchTool(cwd, options) {
138
+ const getOps = opsFactory(cwd, options);
139
+ return {
140
+ name: "git_branch",
141
+ description: "Validate, list, create, or switch branches. Switch refuses a dirty worktree unless createCheckpoint=true.",
142
+ exclusive: true,
143
+ parameters: {
144
+ type: "object",
145
+ properties: {
146
+ action: {
147
+ type: "string",
148
+ enum: ["validate", "create", "switch", "list"],
149
+ description: "Branch operation",
150
+ },
151
+ name: { type: "string", description: "Branch name (required except for list)" },
152
+ createCheckpoint: {
153
+ type: "boolean",
154
+ description: "Stash a checkpoint before switch when dirty (default false)",
155
+ },
156
+ },
157
+ required: ["action"],
158
+ additionalProperties: false,
159
+ },
160
+ async execute(args, context) {
161
+ const toolCallId = context.toolCallId;
162
+ if (context.signal?.aborted)
163
+ return errorResult("git_branch", toolCallId, "Operation aborted");
164
+ const action = args.action;
165
+ if (action !== "validate" && action !== "create" && action !== "switch" && action !== "list") {
166
+ return errorResult("git_branch", toolCallId, "action must be validate|create|switch|list");
167
+ }
168
+ const name = typeof args.name === "string" ? args.name : undefined;
169
+ const createCheckpoint = args.createCheckpoint === true;
170
+ const risk = action === "list" || action === "validate" ? "low" : "high";
171
+ const policy = await enforceExecutionPolicy(options?.executionPolicy, {
172
+ kind: "git",
173
+ operation: `branch_${action}`,
174
+ paths: [cwd],
175
+ risk,
176
+ metadata: { name, createCheckpoint, sessionId: context.sessionId, runId: context.runId },
177
+ }, toolCallId, "git_branch");
178
+ if (!policy.allowed)
179
+ return policy.result;
180
+ try {
181
+ const result = await (await getOps()).branch({
182
+ action,
183
+ name,
184
+ createCheckpoint,
185
+ signal: context.signal,
186
+ });
187
+ return {
188
+ toolCallId,
189
+ name: "git_branch",
190
+ content: [
191
+ {
192
+ type: "text",
193
+ text: action === "list"
194
+ ? (result.refs ?? []).join("\n") || "(no branches)"
195
+ : `ok action=${action} name=${result.name ?? ""}${result.checkpoint ? ` checkpoint=${result.checkpoint}` : ""}`,
196
+ },
197
+ ],
198
+ metadata: result,
199
+ };
200
+ }
201
+ catch (error) {
202
+ return errorResult("git_branch", toolCallId, messageOf(error));
203
+ }
204
+ },
205
+ };
206
+ }
207
+ export function createGitWorktreeTool(cwd, options) {
208
+ const getOps = opsFactory(cwd, options);
209
+ return {
210
+ name: "git_worktree",
211
+ description: "List, add, or remove Git worktrees within finite caps. Prefer disposable worktrees for mutating transactions.",
212
+ exclusive: true,
213
+ parameters: {
214
+ type: "object",
215
+ properties: {
216
+ action: { type: "string", enum: ["list", "add", "remove"] },
217
+ path: { type: "string", description: "Worktree path (add/remove)" },
218
+ branch: { type: "string", description: "Optional new branch name for add (-b)" },
219
+ force: { type: "boolean", description: "Force remove (default false)" },
220
+ },
221
+ required: ["action"],
222
+ additionalProperties: false,
223
+ },
224
+ async execute(args, context) {
225
+ const toolCallId = context.toolCallId;
226
+ if (context.signal?.aborted)
227
+ return errorResult("git_worktree", toolCallId, "Operation aborted");
228
+ const action = args.action;
229
+ if (action !== "list" && action !== "add" && action !== "remove") {
230
+ return errorResult("git_worktree", toolCallId, "action must be list|add|remove");
231
+ }
232
+ const path = typeof args.path === "string" ? args.path : undefined;
233
+ const branch = typeof args.branch === "string" ? args.branch : undefined;
234
+ const force = args.force === true;
235
+ const policy = await enforceExecutionPolicy(options?.executionPolicy, {
236
+ kind: "git",
237
+ operation: `worktree_${action}`,
238
+ paths: path ? [path] : [cwd],
239
+ risk: action === "list" ? "low" : "high",
240
+ metadata: { branch, force, sessionId: context.sessionId, runId: context.runId },
241
+ }, toolCallId, "git_worktree");
242
+ if (!policy.allowed)
243
+ return policy.result;
244
+ try {
245
+ const result = await (await getOps()).worktree({
246
+ action,
247
+ path,
248
+ branch,
249
+ force,
250
+ signal: context.signal,
251
+ });
252
+ const text = action === "list"
253
+ ? result.worktrees.map((w) => `${w.path}\t${w.branch ?? ""}\t${w.head ?? ""}`).join("\n") ||
254
+ "(no worktrees)"
255
+ : `ok action=${action} path=${result.path ?? ""}`;
256
+ return {
257
+ toolCallId,
258
+ name: "git_worktree",
259
+ content: [{ type: "text", text }],
260
+ metadata: result,
261
+ };
262
+ }
263
+ catch (error) {
264
+ return errorResult("git_worktree", toolCallId, messageOf(error));
265
+ }
266
+ },
267
+ };
268
+ }
269
+ export function createGitApplyTool(cwd, options) {
270
+ const getOps = opsFactory(cwd, options);
271
+ return {
272
+ name: "git_apply",
273
+ description: "Check, apply, or reverse a unified patch. Always runs apply --check first for apply/reverse. Dirty trees require createCheckpoint=true; failures restore the checkpoint or clean tree.",
274
+ exclusive: true,
275
+ parameters: {
276
+ type: "object",
277
+ properties: {
278
+ action: { type: "string", enum: ["check", "apply", "reverse"] },
279
+ patch: { type: "string", description: "Unified diff text" },
280
+ createCheckpoint: {
281
+ type: "boolean",
282
+ description: "Stash checkpoint before mutating apply/reverse when dirty",
283
+ },
284
+ },
285
+ required: ["action", "patch"],
286
+ additionalProperties: false,
287
+ },
288
+ async execute(args, context) {
289
+ const toolCallId = context.toolCallId;
290
+ if (context.signal?.aborted)
291
+ return errorResult("git_apply", toolCallId, "Operation aborted");
292
+ const action = args.action;
293
+ if (action !== "check" && action !== "apply" && action !== "reverse") {
294
+ return errorResult("git_apply", toolCallId, "action must be check|apply|reverse");
295
+ }
296
+ const patch = typeof args.patch === "string" ? args.patch : "";
297
+ const createCheckpoint = args.createCheckpoint === true;
298
+ const policy = await enforceExecutionPolicy(options?.executionPolicy, {
299
+ kind: "git",
300
+ operation: `apply_${action}`,
301
+ paths: [cwd],
302
+ risk: action === "check" ? "low" : "high",
303
+ metadata: { createCheckpoint, sessionId: context.sessionId, runId: context.runId },
304
+ }, toolCallId, "git_apply");
305
+ if (!policy.allowed)
306
+ return policy.result;
307
+ try {
308
+ const result = await (await getOps()).apply({
309
+ action,
310
+ patch,
311
+ createCheckpoint,
312
+ signal: context.signal,
313
+ });
314
+ if (!result.ok) {
315
+ return {
316
+ toolCallId,
317
+ name: "git_apply",
318
+ content: [{ type: "text", text: result.output }],
319
+ error: { message: result.output },
320
+ metadata: result,
321
+ };
322
+ }
323
+ return {
324
+ toolCallId,
325
+ name: "git_apply",
326
+ content: [{ type: "text", text: result.output }],
327
+ metadata: result,
328
+ };
329
+ }
330
+ catch (error) {
331
+ return errorResult("git_apply", toolCallId, messageOf(error));
332
+ }
333
+ },
334
+ };
335
+ }
336
+ export function createGitCommitTool(cwd, options) {
337
+ const getOps = opsFactory(cwd, options);
338
+ return {
339
+ name: "git_commit",
340
+ description: "Stage and commit explicit paths only (never `git add -A`). Refuses dirty unrelated worktrees unless createCheckpoint=true. Uses --no-verify and a temp message file; never pushes.",
341
+ exclusive: true,
342
+ parameters: {
343
+ type: "object",
344
+ properties: {
345
+ paths: {
346
+ type: "array",
347
+ items: { type: "string" },
348
+ description: "Exact paths to stage and commit",
349
+ },
350
+ message: { type: "string", description: "Commit message" },
351
+ createCheckpoint: {
352
+ type: "boolean",
353
+ description: "Stash checkpoint first when the tree is already dirty",
354
+ },
355
+ },
356
+ required: ["paths", "message"],
357
+ additionalProperties: false,
358
+ },
359
+ async execute(args, context) {
360
+ const toolCallId = context.toolCallId;
361
+ if (context.signal?.aborted)
362
+ return errorResult("git_commit", toolCallId, "Operation aborted");
363
+ const paths = Array.isArray(args.paths) ? args.paths : [];
364
+ const message = typeof args.message === "string" ? args.message : "";
365
+ const createCheckpoint = args.createCheckpoint === true;
366
+ const policy = await enforceExecutionPolicy(options?.executionPolicy, {
367
+ kind: "git",
368
+ operation: "commit",
369
+ paths,
370
+ risk: "high",
371
+ metadata: { createCheckpoint, sessionId: context.sessionId, runId: context.runId },
372
+ }, toolCallId, "git_commit");
373
+ if (!policy.allowed)
374
+ return policy.result;
375
+ try {
376
+ const result = await (await getOps()).commit({
377
+ paths,
378
+ message,
379
+ createCheckpoint,
380
+ signal: context.signal,
381
+ });
382
+ return {
383
+ toolCallId,
384
+ name: "git_commit",
385
+ content: [{ type: "text", text: `committed ${result.sha}` }],
386
+ metadata: result,
387
+ };
388
+ }
389
+ catch (error) {
390
+ return errorResult("git_commit", toolCallId, messageOf(error));
391
+ }
392
+ },
393
+ };
394
+ }
395
+ export function createGitPrHandoffTool(cwd, options) {
396
+ const getOps = opsFactory(cwd, options);
397
+ return {
398
+ name: "git_pr_handoff",
399
+ description: "Build a bounded host-owned PR handoff payload (base/head/commits/paths/diffstat/checks/artifact). Never pushes, authenticates, or opens a PR.",
400
+ exclusive: true,
401
+ parameters: {
402
+ type: "object",
403
+ properties: {
404
+ base: { type: "string", description: "Base ref/commit for the handoff" },
405
+ head: { type: "string", description: "Head ref/commit (default HEAD)" },
406
+ includeBundle: {
407
+ type: "boolean",
408
+ description: "Prefer a git bundle artifact over a patch when an artifact writer is configured",
409
+ },
410
+ checks: {
411
+ type: "array",
412
+ description: "Optional check summaries to embed",
413
+ items: {
414
+ type: "object",
415
+ properties: {
416
+ name: { type: "string" },
417
+ exitCode: { type: "number" },
418
+ summary: { type: "string" },
419
+ },
420
+ },
421
+ },
422
+ },
423
+ required: ["base"],
424
+ additionalProperties: false,
425
+ },
426
+ async execute(args, context) {
427
+ const toolCallId = context.toolCallId;
428
+ if (context.signal?.aborted)
429
+ return errorResult("git_pr_handoff", toolCallId, "Operation aborted");
430
+ const base = typeof args.base === "string" ? args.base : "";
431
+ const head = typeof args.head === "string" ? args.head : undefined;
432
+ const includeBundle = args.includeBundle === true;
433
+ const checks = Array.isArray(args.checks)
434
+ ? args.checks
435
+ : undefined;
436
+ const policy = await enforceExecutionPolicy(options?.executionPolicy, {
437
+ kind: "git",
438
+ operation: "pr_handoff",
439
+ paths: [cwd],
440
+ risk: "medium",
441
+ metadata: { base, head, includeBundle, sessionId: context.sessionId, runId: context.runId },
442
+ }, toolCallId, "git_pr_handoff");
443
+ if (!policy.allowed)
444
+ return policy.result;
445
+ try {
446
+ const handoff = await (await getOps()).prHandoff({
447
+ base,
448
+ head,
449
+ checks,
450
+ includeBundle,
451
+ signal: context.signal,
452
+ });
453
+ return {
454
+ toolCallId,
455
+ name: "git_pr_handoff",
456
+ content: [
457
+ {
458
+ type: "text",
459
+ text: JSON.stringify({
460
+ base: handoff.base,
461
+ head: handoff.head,
462
+ commits: handoff.commits.length,
463
+ changedPaths: handoff.changedPaths.length,
464
+ diffstat: handoff.diffstat,
465
+ checks: handoff.checks,
466
+ artifact: handoff.artifact,
467
+ }, null, 2),
468
+ },
469
+ ],
470
+ metadata: { ...handoff },
471
+ };
472
+ }
473
+ catch (error) {
474
+ return errorResult("git_pr_handoff", toolCallId, messageOf(error));
475
+ }
476
+ },
477
+ };
478
+ }
479
+ /**
480
+ * Structured Git tool set. Optionally appends `coding_check` when `checks` are declared.
481
+ * Not included in `createCodingTools()` — hosts opt in explicitly.
482
+ */
483
+ export function createGitTools(cwd, options) {
484
+ const tools = [
485
+ createGitStatusTool(cwd, options),
486
+ createGitDiffTool(cwd, options),
487
+ createGitBranchTool(cwd, options),
488
+ createGitWorktreeTool(cwd, options),
489
+ createGitApplyTool(cwd, options),
490
+ createGitCommitTool(cwd, options),
491
+ createGitPrHandoffTool(cwd, options),
492
+ ];
493
+ if (options?.checks) {
494
+ tools.push(createCodingCheckTool(cwd, {
495
+ checks: options.checks,
496
+ executionPolicy: options.executionPolicy,
497
+ ...(options.checkOptions ?? {}),
498
+ }));
499
+ }
500
+ return tools;
501
+ }
502
+ //# sourceMappingURL=git-tools.js.map
package/dist/git.d.ts ADDED
@@ -0,0 +1,139 @@
1
+ import { type CreateGitRunnerOptions } from "./git-exec.js";
2
+ import { type GitStatusResult } from "./git-status.js";
3
+ export interface GitLimitOptions {
4
+ readonly maxPaths?: number;
5
+ readonly maxRefBytes?: number;
6
+ readonly maxMessageBytes?: number;
7
+ readonly maxOutputBytes?: number;
8
+ readonly maxDiffLines?: number;
9
+ readonly maxChangedFiles?: number;
10
+ readonly maxPatchBytes?: number;
11
+ readonly maxWorktrees?: number;
12
+ readonly maxPrCommits?: number;
13
+ readonly maxPrHandoffBytes?: number;
14
+ }
15
+ export interface ResolvedGitLimits {
16
+ readonly maxPaths: number;
17
+ readonly maxRefBytes: number;
18
+ readonly maxMessageBytes: number;
19
+ readonly maxOutputBytes: number;
20
+ readonly maxDiffLines: number;
21
+ readonly maxChangedFiles: number;
22
+ readonly maxPatchBytes: number;
23
+ readonly maxWorktrees: number;
24
+ readonly maxPrCommits: number;
25
+ readonly maxPrHandoffBytes: number;
26
+ }
27
+ export declare function resolveGitLimits(options?: GitLimitOptions): ResolvedGitLimits;
28
+ export interface ArtifactReference {
29
+ readonly kind: "patch" | "bundle" | "diff" | "other";
30
+ readonly uri: string;
31
+ readonly sha256: string;
32
+ readonly bytes: number;
33
+ }
34
+ export type ArtifactWriter = (input: {
35
+ readonly kind: ArtifactReference["kind"];
36
+ readonly filename: string;
37
+ readonly bytes: Buffer;
38
+ }) => Promise<ArtifactReference>;
39
+ export interface PrHandoff {
40
+ readonly base: string;
41
+ readonly head: string;
42
+ readonly commits: readonly {
43
+ sha: string;
44
+ subject: string;
45
+ }[];
46
+ readonly changedPaths: readonly string[];
47
+ readonly diffstat: string;
48
+ readonly checks: readonly {
49
+ name: string;
50
+ exitCode: number;
51
+ summary: string;
52
+ }[];
53
+ readonly artifact?: ArtifactReference;
54
+ }
55
+ export interface GitOperations {
56
+ status(options?: {
57
+ includeIgnored?: boolean;
58
+ signal?: AbortSignal;
59
+ }): Promise<GitStatusResult>;
60
+ diff(options?: {
61
+ staged?: boolean;
62
+ paths?: readonly string[];
63
+ signal?: AbortSignal;
64
+ }): Promise<{
65
+ text: string;
66
+ truncated: boolean;
67
+ lineCount: number;
68
+ artifact?: ArtifactReference;
69
+ }>;
70
+ branch(options: {
71
+ action: "validate" | "create" | "switch" | "list";
72
+ name?: string;
73
+ createCheckpoint?: boolean;
74
+ signal?: AbortSignal;
75
+ }): Promise<{
76
+ refs?: string[];
77
+ name?: string;
78
+ checkpoint?: string;
79
+ }>;
80
+ worktree(options: {
81
+ action: "list" | "add" | "remove";
82
+ path?: string;
83
+ branch?: string;
84
+ force?: boolean;
85
+ signal?: AbortSignal;
86
+ }): Promise<{
87
+ worktrees: readonly {
88
+ path: string;
89
+ head?: string;
90
+ branch?: string;
91
+ }[];
92
+ path?: string;
93
+ }>;
94
+ apply(options: {
95
+ patch: string;
96
+ action: "check" | "apply" | "reverse";
97
+ createCheckpoint?: boolean;
98
+ signal?: AbortSignal;
99
+ }): Promise<{
100
+ ok: boolean;
101
+ checkpoint?: string;
102
+ restored?: boolean;
103
+ output: string;
104
+ }>;
105
+ commit(options: {
106
+ paths: readonly string[];
107
+ message: string;
108
+ createCheckpoint?: boolean;
109
+ signal?: AbortSignal;
110
+ }): Promise<{
111
+ sha: string;
112
+ checkpoint?: string;
113
+ }>;
114
+ prHandoff(options: {
115
+ base: string;
116
+ head?: string;
117
+ checks?: readonly {
118
+ name: string;
119
+ exitCode: number;
120
+ summary: string;
121
+ }[];
122
+ includeBundle?: boolean;
123
+ signal?: AbortSignal;
124
+ }): Promise<PrHandoff>;
125
+ }
126
+ export interface CreateGitOperationsOptions extends CreateGitRunnerOptions, GitLimitOptions {
127
+ readonly cwd: string;
128
+ readonly artifactWriter?: ArtifactWriter;
129
+ /** Required for `commit` when the repository has no usable identity. */
130
+ readonly commitIdentity?: {
131
+ readonly name: string;
132
+ readonly email: string;
133
+ };
134
+ }
135
+ export declare function createGitOperations(options: CreateGitOperationsOptions): Promise<GitOperations>;
136
+ export { parsePorcelainV2 } from "./git-status.js";
137
+ export type { GitStatusResult, GitStatusEntry, GitStatusBranch, GitStatusEntryKind } from "./git-status.js";
138
+ export { GitError, SAFE_GIT_ENV, SAFE_GIT_CONFIG_ARGS, createBoundGitRunner, runGitCli, } from "./git-exec.js";
139
+ export type { GitRunner, GitExecRequest, GitExecResult, BoundGitRunner, CreateGitRunnerOptions } from "./git-exec.js";