@procedures/git 0.0.0-stage → 0.2.0

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 (120) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1130 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/index.d.ts +20 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +20 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/module.d.ts +22 -0
  10. package/dist/module.d.ts.map +1 -0
  11. package/dist/module.js +514 -0
  12. package/dist/module.js.map +1 -0
  13. package/dist/procedures/git/add.d.ts +12 -0
  14. package/dist/procedures/git/add.d.ts.map +1 -0
  15. package/dist/procedures/git/add.js +26 -0
  16. package/dist/procedures/git/add.js.map +1 -0
  17. package/dist/procedures/git/args.d.ts +15 -0
  18. package/dist/procedures/git/args.d.ts.map +1 -0
  19. package/dist/procedures/git/args.js +20 -0
  20. package/dist/procedures/git/args.js.map +1 -0
  21. package/dist/procedures/git/branch.d.ts +12 -0
  22. package/dist/procedures/git/branch.d.ts.map +1 -0
  23. package/dist/procedures/git/branch.js +54 -0
  24. package/dist/procedures/git/branch.js.map +1 -0
  25. package/dist/procedures/git/checkout.d.ts +12 -0
  26. package/dist/procedures/git/checkout.d.ts.map +1 -0
  27. package/dist/procedures/git/checkout.js +23 -0
  28. package/dist/procedures/git/checkout.js.map +1 -0
  29. package/dist/procedures/git/clone.d.ts +12 -0
  30. package/dist/procedures/git/clone.d.ts.map +1 -0
  31. package/dist/procedures/git/clone.js +28 -0
  32. package/dist/procedures/git/clone.js.map +1 -0
  33. package/dist/procedures/git/commit.d.ts +12 -0
  34. package/dist/procedures/git/commit.d.ts.map +1 -0
  35. package/dist/procedures/git/commit.js +41 -0
  36. package/dist/procedures/git/commit.js.map +1 -0
  37. package/dist/procedures/git/diff.d.ts +18 -0
  38. package/dist/procedures/git/diff.d.ts.map +1 -0
  39. package/dist/procedures/git/diff.js +58 -0
  40. package/dist/procedures/git/diff.js.map +1 -0
  41. package/dist/procedures/git/fetch.d.ts +12 -0
  42. package/dist/procedures/git/fetch.d.ts.map +1 -0
  43. package/dist/procedures/git/fetch.js +29 -0
  44. package/dist/procedures/git/fetch.js.map +1 -0
  45. package/dist/procedures/git/index.d.ts +15 -0
  46. package/dist/procedures/git/index.d.ts.map +1 -0
  47. package/dist/procedures/git/index.js +15 -0
  48. package/dist/procedures/git/index.js.map +1 -0
  49. package/dist/procedures/git/init.d.ts +12 -0
  50. package/dist/procedures/git/init.d.ts.map +1 -0
  51. package/dist/procedures/git/init.js +32 -0
  52. package/dist/procedures/git/init.js.map +1 -0
  53. package/dist/procedures/git/log.d.ts +12 -0
  54. package/dist/procedures/git/log.d.ts.map +1 -0
  55. package/dist/procedures/git/log.js +35 -0
  56. package/dist/procedures/git/log.js.map +1 -0
  57. package/dist/procedures/git/predicates.d.ts +40 -0
  58. package/dist/procedures/git/predicates.d.ts.map +1 -0
  59. package/dist/procedures/git/predicates.js +76 -0
  60. package/dist/procedures/git/predicates.js.map +1 -0
  61. package/dist/procedures/git/pull.d.ts +12 -0
  62. package/dist/procedures/git/pull.d.ts.map +1 -0
  63. package/dist/procedures/git/pull.js +36 -0
  64. package/dist/procedures/git/pull.js.map +1 -0
  65. package/dist/procedures/git/push.d.ts +12 -0
  66. package/dist/procedures/git/push.d.ts.map +1 -0
  67. package/dist/procedures/git/push.js +39 -0
  68. package/dist/procedures/git/push.js.map +1 -0
  69. package/dist/procedures/git/remote.d.ts +13 -0
  70. package/dist/procedures/git/remote.d.ts.map +1 -0
  71. package/dist/procedures/git/remote.js +30 -0
  72. package/dist/procedures/git/remote.js.map +1 -0
  73. package/dist/procedures/git/run.d.ts +32 -0
  74. package/dist/procedures/git/run.d.ts.map +1 -0
  75. package/dist/procedures/git/run.js +60 -0
  76. package/dist/procedures/git/run.js.map +1 -0
  77. package/dist/procedures/git/stash.d.ts +38 -0
  78. package/dist/procedures/git/stash.d.ts.map +1 -0
  79. package/dist/procedures/git/stash.js +211 -0
  80. package/dist/procedures/git/stash.js.map +1 -0
  81. package/dist/procedures/git/status.d.ts +17 -0
  82. package/dist/procedures/git/status.d.ts.map +1 -0
  83. package/dist/procedures/git/status.js +107 -0
  84. package/dist/procedures/git/status.js.map +1 -0
  85. package/dist/procedures/git.d.ts +5 -0
  86. package/dist/procedures/git.d.ts.map +1 -0
  87. package/dist/procedures/git.js +5 -0
  88. package/dist/procedures/git.js.map +1 -0
  89. package/dist/register.d.ts +7 -0
  90. package/dist/register.d.ts.map +1 -0
  91. package/dist/register.js +9 -0
  92. package/dist/register.js.map +1 -0
  93. package/dist/types.d.ts +350 -0
  94. package/dist/types.d.ts.map +1 -0
  95. package/dist/types.js +397 -0
  96. package/dist/types.js.map +1 -0
  97. package/package.json +77 -3
  98. package/src/index.ts +20 -0
  99. package/src/module.ts +656 -0
  100. package/src/procedures/git/add.ts +29 -0
  101. package/src/procedures/git/args.ts +21 -0
  102. package/src/procedures/git/branch.ts +61 -0
  103. package/src/procedures/git/checkout.ts +27 -0
  104. package/src/procedures/git/clone.ts +33 -0
  105. package/src/procedures/git/commit.ts +46 -0
  106. package/src/procedures/git/diff.ts +62 -0
  107. package/src/procedures/git/fetch.ts +32 -0
  108. package/src/procedures/git/index.ts +22 -0
  109. package/src/procedures/git/init.ts +43 -0
  110. package/src/procedures/git/log.ts +40 -0
  111. package/src/procedures/git/predicates.ts +90 -0
  112. package/src/procedures/git/pull.ts +45 -0
  113. package/src/procedures/git/push.ts +42 -0
  114. package/src/procedures/git/remote.ts +31 -0
  115. package/src/procedures/git/run.ts +70 -0
  116. package/src/procedures/git/stash.ts +255 -0
  117. package/src/procedures/git/status.ts +98 -0
  118. package/src/procedures/git.ts +5 -0
  119. package/src/register.ts +11 -0
  120. package/src/types.ts +815 -0
package/README.md CHANGED
@@ -1,3 +1,1131 @@
1
- # Temporary Holding Version
1
+ # @procedures/git
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@procedures/git.svg)](https://www.npmjs.com/package/@procedures/git)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ > Git operations as RPC procedures - `git.add`, `git.commit`, `git.push`, `git.status`, and 22 more git commands exposed as type-safe procedures.
7
+
8
+ ## Overview
9
+
10
+ `@procedures/git` exposes comprehensive Git functionality as RPC procedures through the client system. It provides 26 procedures covering all major Git operations including repository state management, staging, commits, branching, remote operations, stashing, and repository predicates.
11
+
12
+ This package wraps Git commands directly (not through `client-shell`), providing structured input/output with full type safety via Zod schemas.
13
+
14
+ ## Architecture
15
+
16
+ ```mermaid
17
+ graph TB
18
+ A[client-git] -->|depends on| B[@procedures/client]
19
+ A -->|validates with| C[zod]
20
+
21
+ D[Application] -->|calls| E[git.* procedures]
22
+ E -->|executes| F[Git CLI]
23
+ E -->|parses| G[Structured Output]
24
+
25
+ H[Repository State<br/>status, log, diff, branch] -.-> E
26
+ I[Staging & Commits<br/>add, commit, checkout] -.-> E
27
+ J[Remote Sync<br/>push, pull, fetch, clone] -.-> E
28
+ K[Stash Operations<br/>stash.*, export, import] -.-> E
29
+ L[Predicates<br/>hasChanges, isClean, etc] -.-> E
30
+
31
+ style A fill:#e1f5ff
32
+ style E fill:#e1ffe1
33
+ ```
34
+
35
+ ### Procedure Categories
36
+
37
+ ```mermaid
38
+ graph LR
39
+ A[client-git<br/>26 Procedures] --> B[Repository State<br/>5 procedures]
40
+ A --> C[Staging & Commits<br/>3 procedures]
41
+ A --> D[Remote Operations<br/>5 procedures]
42
+ A --> E[Predicates<br/>6 procedures]
43
+ A --> F[Stash Operations<br/>7 procedures]
44
+
45
+ B -.-> B1[status, log, diff,<br/>branch, init]
46
+ C -.-> C1[add, commit,<br/>checkout]
47
+ D -.-> D1[push, pull, fetch,<br/>clone, remote]
48
+ E -.-> E1[hasChanges, isClean,<br/>hasStagedChanges,<br/>hasUnstagedChanges,<br/>hasUntrackedFiles,<br/>hasLocalCommits]
49
+ F -.-> F1[stash.list, stash.push,<br/>stash.pop, stash.apply,<br/>stash.drop, stash.export,<br/>stash.import]
50
+
51
+ style A fill:#e1f5ff
52
+ style B fill:#ffe1e1
53
+ style C fill:#ffe1e1
54
+ style D fill:#ffe1e1
55
+ style E fill:#fff4e1
56
+ style F fill:#e1ffe1
57
+ ```
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ # Using pnpm (recommended)
63
+ pnpm add @procedures/git
64
+
65
+ # Using npm
66
+ npm install @procedures/git
67
+
68
+ # Using yarn
69
+ yarn add @procedures/git
70
+ ```
71
+
72
+ ## Quick Start
73
+
74
+ ```typescript
75
+ import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
76
+ import "@procedures/git/register"; // registers its procedures
77
+
78
+ const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
79
+
80
+ // Get repository status
81
+ const status = await client.exec(["git", "status"], {
82
+ cwd: "/path/to/repo",
83
+ });
84
+ console.log(status.branch, status.files);
85
+
86
+ // Stage and commit
87
+ await client.exec(["git", "add"], { all: true });
88
+ await client.exec(["git", "commit"], { message: "feat: add new feature" });
89
+
90
+ // Push to remote
91
+ await client.exec(["git", "push"], { remote: "origin" });
92
+ ```
93
+
94
+ ## API Reference
95
+
96
+ ### Repository State Procedures
97
+
98
+ #### `git.status`
99
+
100
+ Get current repository status including branch, tracking info, and file changes.
101
+
102
+ **Path:** `["git", "status"]`
103
+
104
+ ```typescript
105
+ interface GitStatusInput {
106
+ /** Working directory (default: process.cwd()) */
107
+ cwd?: string;
108
+ /** Also give `lines` in the format of `git status --short` (default: false) */
109
+ short?: boolean;
110
+ }
111
+
112
+ interface GitStatusFile {
113
+ /** The path (for a rename or a copy, the new path). Paths are exact: no quotes. */
114
+ path: string;
115
+ /** For a rename or a copy, the old path */
116
+ from?: string;
117
+ status: "modified" | "added" | "deleted" | "renamed" | "copied" | "untracked" | "ignored";
118
+ staged: boolean;
119
+ }
120
+
121
+ interface GitStatusOutput {
122
+ branch: string;
123
+ ahead: number;
124
+ behind: number;
125
+ files: GitStatusFile[];
126
+ clean: boolean;
127
+ /** With `short: true`: one line per file, for example "R old.ts -> new.ts" */
128
+ lines?: string[];
129
+ }
130
+ ```
131
+
132
+ **Example:**
133
+ ```typescript
134
+ const status = await client.exec(["git", "status"], { cwd: "./my-repo" });
135
+ console.log(`On branch ${status.branch}`);
136
+ console.log(`${status.ahead} commits ahead, ${status.behind} behind`);
137
+ console.log(`${status.files.length} changed files`);
138
+ ```
139
+
140
+ #### `git.log`
141
+
142
+ Show commit history.
143
+
144
+ **Path:** `["git", "log"]`
145
+
146
+ ```typescript
147
+ interface GitLogInput {
148
+ /** Number of commits to show (default: 10) */
149
+ count?: number;
150
+ /** Also give `lines`: one line per commit, the short hash and the subject (default: false) */
151
+ oneline?: boolean;
152
+ /** Branch/tag/commit to start from (default: HEAD) */
153
+ ref?: string;
154
+ /** Working directory */
155
+ cwd?: string;
156
+ }
157
+
158
+ interface GitLogCommit {
159
+ hash: string;
160
+ shortHash: string;
161
+ author: string;
162
+ email: string;
163
+ date: string;
164
+ message: string;
165
+ }
166
+
167
+ interface GitLogOutput {
168
+ commits: GitLogCommit[];
169
+ /** With `oneline: true` */
170
+ lines?: string[];
171
+ }
172
+ ```
173
+
174
+ **Example:**
175
+ ```typescript
176
+ const log = await client.exec(["git", "log"], { count: 5 });
177
+ for (const commit of log.commits) {
178
+ console.log(`${commit.shortHash} - ${commit.message}`);
179
+ }
180
+ ```
181
+
182
+ #### `git.diff`
183
+
184
+ Show changes between commits, branches, or working directory.
185
+
186
+ **Path:** `["git", "diff"]`
187
+
188
+ ```typescript
189
+ interface GitDiffInput {
190
+ /** Show staged changes (default: false, shows unstaged) */
191
+ staged?: boolean;
192
+ /** Compare against ref (branch/commit) */
193
+ ref?: string;
194
+ /** Specific paths to diff */
195
+ paths?: string[];
196
+ /** Show stat summary only (default: false) */
197
+ stat?: boolean;
198
+ /** Working directory */
199
+ cwd?: string;
200
+ }
201
+
202
+ interface GitDiffFile {
203
+ /** The path (for a rename, the new path) */
204
+ path: string;
205
+ /** For a rename, the old path */
206
+ from?: string;
207
+ additions: number;
208
+ deletions: number;
209
+ }
210
+
211
+ interface GitDiffOutput {
212
+ files: GitDiffFile[];
213
+ additions: number;
214
+ deletions: number;
215
+ diff?: string;
216
+ }
217
+ ```
218
+
219
+ **Example:**
220
+ ```typescript
221
+ // Show unstaged changes
222
+ const diff = await client.exec(["git", "diff"], { stat: true });
223
+ console.log(`${diff.additions} additions, ${diff.deletions} deletions`);
224
+
225
+ // Show staged changes
226
+ const staged = await client.exec(["git", "diff"], { staged: true });
227
+ ```
228
+
229
+ #### `git.branch`
230
+
231
+ Branch operations - list, create, delete branches.
232
+
233
+ **Path:** `["git", "branch"]`
234
+
235
+ ```typescript
236
+ interface GitBranchInput {
237
+ /** Branch name (for create/delete) */
238
+ name?: string;
239
+ /** Delete the branch (default: false) */
240
+ delete?: boolean;
241
+ /** List branches (default: true if no name provided) */
242
+ list?: boolean;
243
+ /** Include remote branches in list (default: false) */
244
+ remote?: boolean;
245
+ /** Working directory */
246
+ cwd?: string;
247
+ }
248
+
249
+ interface GitBranchInfo {
250
+ name: string;
251
+ current: boolean;
252
+ remote: boolean;
253
+ tracking?: string;
254
+ }
255
+
256
+ interface GitBranchOutput {
257
+ branches?: GitBranchInfo[];
258
+ created?: string;
259
+ deleted?: string;
260
+ current: string;
261
+ }
262
+ ```
263
+
264
+ **Example:**
265
+ ```typescript
266
+ // List all branches
267
+ const branches = await client.exec(["git", "branch"], { list: true });
268
+ console.log("Branches:", branches.branches.map(b => b.name));
269
+
270
+ // Create new branch
271
+ await client.exec(["git", "branch"], { name: "feature/new-feature" });
272
+
273
+ // Delete branch
274
+ await client.exec(["git", "branch"], { name: "old-feature", delete: true });
275
+ ```
276
+
277
+ #### `git.init`
278
+
279
+ Initialize a new git repository.
280
+
281
+ **Path:** `["git", "init"]`
282
+
283
+ ```typescript
284
+ interface GitInitInput {
285
+ /** Directory to initialize (default: process.cwd()) */
286
+ cwd?: string;
287
+ /** Create a bare repository (default: false) */
288
+ bare?: boolean;
289
+ /** Name for the initial branch */
290
+ initialBranch?: string;
291
+ }
292
+
293
+ interface GitInitOutput {
294
+ /** The initialized repository path */
295
+ path: string;
296
+ /** Whether the repo was newly created */
297
+ created: boolean;
298
+ }
299
+ ```
300
+
301
+ ### Staging & Commit Procedures
302
+
303
+ #### `git.add`
304
+
305
+ Stage files for commit.
306
+
307
+ **Path:** `["git", "add"]`
308
+
309
+ ```typescript
310
+ interface GitAddInput {
311
+ /** Paths to stage (default: []) */
312
+ paths?: string[];
313
+ /** Stage all changes (default: false) */
314
+ all?: boolean;
315
+ /** Working directory */
316
+ cwd?: string;
317
+ }
318
+
319
+ interface GitAddOutput {
320
+ staged: string[];
321
+ }
322
+ ```
323
+
324
+ **Example:**
325
+ ```typescript
326
+ // Stage specific files
327
+ await client.exec(["git", "add"], { paths: ["file1.ts", "file2.ts"] });
328
+
329
+ // Stage all changes
330
+ await client.exec(["git", "add"], { all: true });
331
+ ```
332
+
333
+ #### `git.commit`
334
+
335
+ Create a commit with staged changes.
336
+
337
+ **Path:** `["git", "commit"]`
338
+
339
+ ```typescript
340
+ interface GitCommitInput {
341
+ /** Commit message */
342
+ message: string;
343
+ /** Automatically stage modified files (default: false) */
344
+ all?: boolean;
345
+ /** Amend the last commit (default: false) */
346
+ amend?: boolean;
347
+ /** Working directory */
348
+ cwd?: string;
349
+ }
350
+
351
+ interface GitCommitOutput {
352
+ hash: string;
353
+ message: string;
354
+ author: string;
355
+ date: string;
356
+ /** True if commit was skipped (nothing to commit) */
357
+ skipped?: boolean;
358
+ }
359
+ ```
360
+
361
+ **Example:**
362
+ ```typescript
363
+ const commit = await client.exec(["git", "commit"], {
364
+ message: "feat: add new feature\n\nDetailed description here",
365
+ all: true, // Auto-stage modified files
366
+ });
367
+ console.log(`Created commit ${commit.hash}`);
368
+ ```
369
+
370
+ #### `git.checkout`
371
+
372
+ Checkout branch, tag, or restore files.
373
+
374
+ **Path:** `["git", "checkout"]`
375
+
376
+ ```typescript
377
+ interface GitCheckoutInput {
378
+ /** Branch, tag, or commit to checkout */
379
+ ref: string;
380
+ /** Create branch if it doesn't exist (default: false) */
381
+ create?: boolean;
382
+ /** Specific paths to checkout (default: all) */
383
+ paths?: string[];
384
+ /** Working directory */
385
+ cwd?: string;
386
+ }
387
+
388
+ interface GitCheckoutOutput {
389
+ ref: string;
390
+ created: boolean;
391
+ }
392
+ ```
393
+
394
+ **Example:**
395
+ ```typescript
396
+ // Checkout existing branch
397
+ await client.exec(["git", "checkout"], { ref: "main" });
398
+
399
+ // Create and checkout new branch
400
+ await client.exec(["git", "checkout"], { ref: "feature/new", create: true });
401
+
402
+ // Restore specific file
403
+ await client.exec(["git", "checkout"], { ref: "HEAD", paths: ["file.ts"] });
404
+ ```
405
+
406
+ ### Remote Operations
407
+
408
+ #### `git.push`
409
+
410
+ Push commits to remote repository.
411
+
412
+ **Path:** `["git", "push"]`
413
+
414
+ ```typescript
415
+ interface GitPushInput {
416
+ /** Remote name (default: origin) */
417
+ remote?: string;
418
+ /** Branch to push (default: current branch) */
419
+ branch?: string;
420
+ /** Force push (default: false) */
421
+ force?: boolean;
422
+ /** Set upstream tracking (default: false) */
423
+ setUpstream?: boolean;
424
+ /** Working directory */
425
+ cwd?: string;
426
+ }
427
+
428
+ interface GitPushOutput {
429
+ remote: string;
430
+ branch: string;
431
+ commits: number;
432
+ }
433
+ ```
434
+
435
+ **Example:**
436
+ ```typescript
437
+ // Push to origin
438
+ await client.exec(["git", "push"], {});
439
+
440
+ // Push and set upstream
441
+ await client.exec(["git", "push"], {
442
+ remote: "origin",
443
+ branch: "feature/new",
444
+ setUpstream: true,
445
+ });
446
+
447
+ // Force push (use with caution!)
448
+ await client.exec(["git", "push"], { force: true });
449
+ ```
450
+
451
+ #### `git.pull`
452
+
453
+ Pull changes from remote repository.
454
+
455
+ **Path:** `["git", "pull"]`
456
+
457
+ ```typescript
458
+ interface GitPullInput {
459
+ /** Remote name (default: origin) */
460
+ remote?: string;
461
+ /** Branch to pull (default: current branch) */
462
+ branch?: string;
463
+ /** Rebase instead of merge (default: false) */
464
+ rebase?: boolean;
465
+ /** Working directory */
466
+ cwd?: string;
467
+ }
468
+
469
+ interface GitPullOutput {
470
+ remote: string;
471
+ branch: string;
472
+ commits: number;
473
+ fastForward: boolean;
474
+ }
475
+ ```
476
+
477
+ #### `git.fetch`
478
+
479
+ Fetch from remote without merging.
480
+
481
+ **Path:** `["git", "fetch"]`
482
+
483
+ ```typescript
484
+ interface GitFetchInput {
485
+ /** Remote name (default: origin) */
486
+ remote?: string;
487
+ /** Specific branch to fetch (default: all) */
488
+ branch?: string;
489
+ /** Fetch all remotes (default: false) */
490
+ all?: boolean;
491
+ /** Prune deleted branches (default: false) */
492
+ prune?: boolean;
493
+ /** Working directory */
494
+ cwd?: string;
495
+ }
496
+
497
+ interface GitFetchOutput {
498
+ remote: string;
499
+ fetched: boolean;
500
+ }
501
+ ```
502
+
503
+ #### `git.clone`
504
+
505
+ Clone a repository.
506
+
507
+ **Path:** `["git", "clone"]`
508
+
509
+ ```typescript
510
+ interface GitCloneInput {
511
+ /** Repository URL */
512
+ url: string;
513
+ /** Destination directory (default: derived from URL) */
514
+ dest?: string;
515
+ /** Branch to clone (default: default branch) */
516
+ branch?: string;
517
+ /** Create shallow clone with depth (default: full clone) */
518
+ depth?: number;
519
+ /** Working directory */
520
+ cwd?: string;
521
+ }
522
+
523
+ interface GitCloneOutput {
524
+ path: string;
525
+ branch: string;
526
+ }
527
+ ```
528
+
529
+ **Example:**
530
+ ```typescript
531
+ const clone = await client.exec(["git", "clone"], {
532
+ url: "https://github.com/user/repo.git",
533
+ dest: "./my-repo",
534
+ depth: 1, // Shallow clone
535
+ });
536
+ console.log(`Cloned to ${clone.path}`);
537
+ ```
538
+
539
+ #### `git.remote`
540
+
541
+ Get the URL of a remote. It changes nothing.
542
+
543
+ **Path:** `["git", "remote"]`
544
+
545
+ ```typescript
546
+ interface GitRemoteInput {
547
+ /** Remote name (default: origin) */
548
+ name?: string;
549
+ /** Working directory */
550
+ cwd?: string;
551
+ }
552
+
553
+ interface GitRemoteOutput {
554
+ /** Remote name */
555
+ name: string;
556
+ /** Remote URL */
557
+ url: string;
558
+ }
559
+ ```
560
+
561
+ #### `git.remote.set`
562
+
563
+ Set the URL of a remote, or add the remote when it is missing. Before 0.2.0, `git.remote` with `url` did this.
564
+
565
+ **Path:** `["git", "remote", "set"]`
566
+
567
+ ```typescript
568
+ interface GitRemoteSetInput {
569
+ /** Remote name (default: origin) */
570
+ name?: string;
571
+ /** The URL of the remote */
572
+ url: string;
573
+ /** Working directory */
574
+ cwd?: string;
575
+ }
576
+ ```
577
+
578
+ ### Predicate Procedures
579
+
580
+ These procedures return boolean predicates useful for conditional logic.
581
+
582
+ #### `git.hasChanges`
583
+
584
+ Check if there are any changes (unstaged, staged, or untracked).
585
+
586
+ **Path:** `["git", "hasChanges"]`
587
+
588
+ ```typescript
589
+ interface GitPredicateInput {
590
+ cwd?: string;
591
+ }
592
+
593
+ interface GitPredicateOutput {
594
+ value: boolean;
595
+ }
596
+ ```
597
+
598
+ #### `git.hasStagedChanges`
599
+
600
+ Check if there are staged changes ready to commit.
601
+
602
+ **Path:** `["git", "hasStagedChanges"]`
603
+
604
+ #### `git.hasUnstagedChanges`
605
+
606
+ Check if there are unstaged changes.
607
+
608
+ **Path:** `["git", "hasUnstagedChanges"]`
609
+
610
+ #### `git.hasUntrackedFiles`
611
+
612
+ Check if there are untracked files.
613
+
614
+ **Path:** `["git", "hasUntrackedFiles"]`
615
+
616
+ #### `git.hasLocalCommits`
617
+
618
+ Check if there are local commits that haven't been pushed.
619
+
620
+ **Path:** `["git", "hasLocalCommits"]`
621
+
622
+ #### `git.isClean`
623
+
624
+ Check if the working directory is clean (no changes).
625
+
626
+ **Path:** `["git", "isClean"]`
627
+
628
+ **Example:**
629
+ ```typescript
630
+ const { value: hasChanges } = await client.exec(["git", "hasChanges"], {});
631
+ if (hasChanges) {
632
+ console.log("Repository has uncommitted changes");
633
+ }
634
+
635
+ const { value: isClean } = await client.exec(["git", "isClean"], {});
636
+ if (isClean) {
637
+ console.log("Working directory is clean");
638
+ }
639
+ ```
640
+
641
+ ### Stash Procedures
642
+
643
+ #### `git.stash.list`
644
+
645
+ List all stashes.
646
+
647
+ **Path:** `["git", "stash", "list"]`
648
+
649
+ ```typescript
650
+ interface GitStashEntry {
651
+ /** Stash index (0 = most recent) */
652
+ index: number;
653
+ /** Stash reference (e.g., stash@{0}) */
654
+ ref: string;
655
+ /** Commit hash of the stash */
656
+ hash: string;
657
+ /** Stash message */
658
+ message: string;
659
+ }
660
+
661
+ interface GitStashListOutput {
662
+ /** List of stashes */
663
+ stashes: GitStashEntry[];
664
+ /** Total count */
665
+ count: number;
666
+ }
667
+ ```
668
+
669
+ #### `git.stash.push`
670
+
671
+ Save changes to stash.
672
+
673
+ **Path:** `["git", "stash", "push"]`
674
+
675
+ ```typescript
676
+ interface GitStashPushInput {
677
+ /** Stash message */
678
+ message?: string;
679
+ /** Include untracked files (default: false) */
680
+ includeUntracked?: boolean;
681
+ /** Keep staged changes in index (default: false) */
682
+ keepIndex?: boolean;
683
+ /** Specific paths to stash */
684
+ paths?: string[];
685
+ /** Working directory */
686
+ cwd?: string;
687
+ }
688
+
689
+ interface GitStashPushOutput {
690
+ /** Whether changes were stashed */
691
+ stashed: boolean;
692
+ /** Stash reference if created */
693
+ ref?: string;
694
+ /** Stash message */
695
+ message?: string;
696
+ }
697
+ ```
698
+
699
+ #### `git.stash.pop`
700
+
701
+ Apply and remove most recent stash.
702
+
703
+ **Path:** `["git", "stash", "pop"]`
704
+
705
+ ```typescript
706
+ interface GitStashPopInput {
707
+ /** Stash index to pop (default: 0 = most recent) */
708
+ index?: number;
709
+ cwd?: string;
710
+ }
711
+
712
+ interface GitStashPopOutput {
713
+ /** Whether stash was applied */
714
+ applied: boolean;
715
+ /** Stash reference that was popped */
716
+ ref: string;
717
+ /** Whether stash was dropped (false if conflict) */
718
+ dropped: boolean;
719
+ /** Whether there was a merge conflict */
720
+ conflict?: boolean;
721
+ }
722
+ ```
723
+
724
+ #### `git.stash.apply`
725
+
726
+ Apply stash without removing it.
727
+
728
+ **Path:** `["git", "stash", "apply"]`
729
+
730
+ ```typescript
731
+ interface GitStashApplyInput {
732
+ /** Stash index to apply (default: 0 = most recent) */
733
+ index?: number;
734
+ cwd?: string;
735
+ }
736
+
737
+ interface GitStashApplyOutput {
738
+ /** Whether stash was applied */
739
+ applied: boolean;
740
+ /** Stash reference that was applied */
741
+ ref: string;
742
+ /** Whether there was a merge conflict */
743
+ conflict?: boolean;
744
+ }
745
+ ```
746
+
747
+ #### `git.stash.drop`
748
+
749
+ Delete a stash.
750
+
751
+ **Path:** `["git", "stash", "drop"]`
752
+
753
+ ```typescript
754
+ interface GitStashDropInput {
755
+ /** Stash index to drop (default: 0 = most recent) */
756
+ index?: number;
757
+ cwd?: string;
758
+ }
759
+
760
+ interface GitStashDropOutput {
761
+ /** Whether stash was dropped */
762
+ dropped: boolean;
763
+ /** Stash reference that was dropped */
764
+ ref: string;
765
+ }
766
+ ```
767
+
768
+ #### `git.stash.export`
769
+
770
+ Export stash as patch for snapshot storage.
771
+
772
+ **Path:** `["git", "stash", "export"]`
773
+
774
+ ```typescript
775
+ interface GitStashExportInput {
776
+ /** Stash index to export (default: 0 = most recent) */
777
+ index?: number;
778
+ cwd?: string;
779
+ }
780
+
781
+ interface GitStashExportOutput {
782
+ /** Stash reference */
783
+ ref: string;
784
+ /** Commit hash */
785
+ hash: string;
786
+ /** Stash message */
787
+ message: string;
788
+ /** Patch content (diff) */
789
+ patch: string;
790
+ /** Whether stash includes untracked files */
791
+ hasUntracked: boolean;
792
+ /** Untracked files patch if present */
793
+ untrackedPatch?: string;
794
+ }
795
+ ```
796
+
797
+ #### `git.stash.import`
798
+
799
+ Import stash from patch.
800
+
801
+ **Path:** `["git", "stash", "import"]`
802
+
803
+ ```typescript
804
+ interface GitStashImportInput {
805
+ /** Patch content to apply and stash */
806
+ patch: string;
807
+ /** Message for the new stash */
808
+ message?: string;
809
+ /** Include untracked files (default: false) */
810
+ includeUntracked?: boolean;
811
+ cwd?: string;
812
+ }
813
+
814
+ interface GitStashImportOutput {
815
+ /** Whether import succeeded */
816
+ imported: boolean;
817
+ /** New stash reference if created */
818
+ ref?: string;
819
+ /** Error message if failed */
820
+ error?: string;
821
+ }
822
+ ```
823
+
824
+ **Stash Example:**
825
+ ```typescript
826
+ // Save current work
827
+ await client.exec(["git", "stash", "push"], {
828
+ message: "WIP: feature in progress",
829
+ includeUntracked: true,
830
+ });
831
+
832
+ // List stashes
833
+ const stashes = await client.exec(["git", "stash", "list"], {});
834
+ console.log(`${stashes.count} stashes`);
835
+
836
+ // Apply most recent stash
837
+ await client.exec(["git", "stash", "apply"], { index: 0 });
838
+
839
+ // Export stash for backup
840
+ const exported = await client.exec(["git", "stash", "export"], { index: 0 });
841
+ fs.writeFileSync("backup.patch", exported.patch);
842
+ ```
843
+
844
+ ## Complete Procedure List
845
+
846
+ | Category | Procedure | Description |
847
+ |----------|-----------|-------------|
848
+ | **Repository State** | `git.status` | Get repository status |
849
+ | | `git.log` | Show commit history |
850
+ | | `git.diff` | Show changes |
851
+ | | `git.branch` | Branch operations |
852
+ | | `git.init` | Initialize repository |
853
+ | **Staging & Commits** | `git.add` | Stage files |
854
+ | | `git.commit` | Create commit |
855
+ | | `git.checkout` | Checkout branch/files |
856
+ | **Remote Operations** | `git.push` | Push to remote |
857
+ | | `git.pull` | Pull from remote |
858
+ | | `git.fetch` | Fetch from remote |
859
+ | | `git.clone` | Clone repository |
860
+ | | `git.remote` | Get the URL of a remote |
861
+ | | `git.remote.set` | Set the URL of a remote |
862
+ | **Predicates** | `git.hasChanges` | Check for any changes |
863
+ | | `git.hasStagedChanges` | Check for staged changes |
864
+ | | `git.hasUnstagedChanges` | Check for unstaged changes |
865
+ | | `git.hasUntrackedFiles` | Check for untracked files |
866
+ | | `git.hasLocalCommits` | Check for unpushed commits |
867
+ | | `git.isClean` | Check if working dir is clean |
868
+ | **Stash** | `git.stash.list` | List stashes |
869
+ | | `git.stash.push` | Save to stash |
870
+ | | `git.stash.pop` | Apply and remove stash |
871
+ | | `git.stash.apply` | Apply stash |
872
+ | | `git.stash.drop` | Delete stash |
873
+ | | `git.stash.export` | Export stash as patch |
874
+ | | `git.stash.import` | Import stash from patch |
875
+
876
+ ## Usage Examples
877
+
878
+ ### Complete Git Workflow
879
+
880
+ ```typescript
881
+ // 1. Check status
882
+ const status = await client.exec(["git", "status"], {});
883
+ console.log(`On branch ${status.branch}`);
884
+
885
+ // 2. Stage changes
886
+ await client.exec(["git", "add"], { all: true });
887
+
888
+ // 3. Create commit
889
+ const commit = await client.exec(["git", "commit"], {
890
+ message: "feat: implement new feature",
891
+ });
892
+
893
+ // 4. Push to remote
894
+ await client.exec(["git", "push"], {
895
+ remote: "origin",
896
+ setUpstream: true,
897
+ });
898
+ ```
899
+
900
+ ### Conditional Operations with Predicates
901
+
902
+ ```typescript
903
+ const { value: hasChanges } = await client.exec(["git", "hasChanges"], {});
904
+ if (hasChanges) {
905
+ await client.exec(["git", "stash", "push"], {
906
+ message: "Auto-stash before pull",
907
+ });
908
+ }
909
+
910
+ await client.exec(["git", "pull"], { rebase: true });
911
+
912
+ const { value: hasStashes } = await client.exec(["git", "stash", "list"], {});
913
+ if (hasStashes.count > 0) {
914
+ await client.exec(["git", "stash", "pop"], {});
915
+ }
916
+ ```
917
+
918
+ ### Branch Management
919
+
920
+ ```typescript
921
+ // Create feature branch
922
+ await client.exec(["git", "checkout"], {
923
+ ref: "feature/new-feature",
924
+ create: true,
925
+ });
926
+
927
+ // Work on feature...
928
+ await client.exec(["git", "add"], { all: true });
929
+ await client.exec(["git", "commit"], { message: "feat: complete feature" });
930
+
931
+ // Merge to main
932
+ await client.exec(["git", "checkout"], { ref: "main" });
933
+ await client.exec(["git", "pull"], {});
934
+
935
+ // Delete feature branch
936
+ await client.exec(["git", "branch"], {
937
+ name: "feature/new-feature",
938
+ delete: true,
939
+ });
940
+ ```
941
+
942
+ ### Working with Remotes
943
+
944
+ ```typescript
945
+ // Clone repository
946
+ const clone = await client.exec(["git", "clone"], {
947
+ url: "https://github.com/user/repo.git",
948
+ dest: "./my-repo",
949
+ });
950
+
951
+ // Set remote
952
+ await client.exec(["git", "remote"], {
953
+ name: "upstream",
954
+ url: "https://github.com/original/repo.git",
955
+ });
956
+
957
+ // Fetch from upstream
958
+ await client.exec(["git", "fetch"], {
959
+ remote: "upstream",
960
+ prune: true,
961
+ });
962
+ ```
963
+
964
+ ## Dependencies
965
+
966
+ ```mermaid
967
+ graph LR
968
+ A[@procedures/git] --> B[@procedures/client]
969
+ A --> C[zod]
970
+
971
+ B -.->|provides| D[Client & Procedure System]
972
+ C -.->|provides| E[Schema Validation]
973
+
974
+ style A fill:#e1f5ff
975
+ style B fill:#ffe1e1
976
+ style C fill:#fff4e1
977
+ ```
978
+
979
+ - **`@procedures/client`** - Core client library providing the procedure system
980
+ - **`zod`** - Schema validation library
981
+
982
+ ## Integration with Ecosystem
983
+
984
+ ### Used By
985
+
986
+ ```typescript
987
+ // bundle-dev/src/register.ts
988
+ import "@procedures/git/register"; // registers its procedures
989
+
990
+ // client-lib uses git procedures internally
991
+ import { gitStatus, gitAdd, gitCommit } from "@procedures/git";
992
+ ```
993
+
994
+ ### Usage in client-lib
995
+
996
+ The `client-lib` package uses git procedures extensively for package management:
997
+
998
+ ```typescript
999
+ // From client-lib/src/git/operations.ts
1000
+ export async function stageAll(cwd: string, ctx: ProcedureContext): Promise<void> {
1001
+ await ctx.client.call(["git", "add"], { all: true, cwd });
1002
+ }
1003
+
1004
+ export async function commit(
1005
+ message: string,
1006
+ cwd: string,
1007
+ ctx: ProcedureContext
1008
+ ): Promise<string> {
1009
+ const result = await ctx.client.call(["git", "commit"], { message, cwd });
1010
+ return result.hash;
1011
+ }
1012
+ ```
1013
+
1014
+ ## Configuration
1015
+
1016
+ ### Auto-Registration
1017
+
1018
+ Procedures are automatically registered when the package is loaded:
1019
+
1020
+ ```json
1021
+ {
1022
+ "client": {
1023
+ "procedures": "./dist/register.js"
1024
+ },
1025
+ "scripts": {
1026
+ "postinstall": "client-registry announce"
1027
+ }
1028
+ }
1029
+ ```
1030
+
1031
+ ### Build Scripts
1032
+
1033
+ ```bash
1034
+ # Build the package
1035
+ pnpm build
1036
+
1037
+ # Type check
1038
+ pnpm typecheck
1039
+
1040
+ # Clean build artifacts
1041
+ pnpm clean
1042
+ ```
1043
+
1044
+ ## Advanced Usage
1045
+
1046
+ ### Direct Function Imports
1047
+
1048
+ The handlers start git themselves. Their context is optional: it gives only the abort `signal`.
1049
+
1050
+ ```typescript
1051
+ import { gitStatus, gitCommit, gitPush } from "@procedures/git";
1052
+
1053
+ const status = await gitStatus({ cwd: "./repo" });
1054
+ await gitCommit({ message: "feat: new feature", cwd: "./repo" });
1055
+ await gitPush({ remote: "origin", cwd: "./repo" }, { signal: AbortSignal.timeout(60_000) });
1056
+ ```
1057
+
1058
+ ### Custom Registration
1059
+
1060
+ ```typescript
1061
+ import { createProcedure, registerProcedures } from "@procedures/client";
1062
+ import { gitStatus, GitStatusInputSchema } from "@procedures/git";
1063
+
1064
+ // Register under custom namespace
1065
+ const customStatusProcedure = createProcedure()
1066
+ .path(["custom", "git", "status"])
1067
+ .input(GitStatusInputSchema)
1068
+ .handler(gitStatus)
1069
+ .build();
1070
+
1071
+ registerProcedures([customStatusProcedure]);
1072
+ ```
1073
+
1074
+ ## Error Handling
1075
+
1076
+ All git procedures may throw errors for invalid operations:
1077
+
1078
+ ```typescript
1079
+ try {
1080
+ await client.exec(["git", "commit"], {
1081
+ message: "feat: new feature",
1082
+ });
1083
+ } catch (error) {
1084
+ if (error.message.includes("nothing to commit")) {
1085
+ console.log("No changes to commit");
1086
+ } else {
1087
+ throw error;
1088
+ }
1089
+ }
1090
+ ```
1091
+
1092
+ ## Performance Considerations
1093
+
1094
+ - **Command Execution**: Each procedure spawns a git process
1095
+ - **Large Repositories**: Operations may be slower on repos with extensive history
1096
+ - **Batch Operations**: Use predicate checks to avoid unnecessary operations
1097
+ - **Stash Export/Import**: Useful for snapshotting state without git history overhead
1098
+
1099
+ ## Troubleshooting
1100
+
1101
+ ### Git Not Found
1102
+
1103
+ Ensure git is installed and in PATH:
1104
+ ```bash
1105
+ git --version
1106
+ ```
1107
+
1108
+ ### Permission Errors
1109
+
1110
+ Check repository permissions:
1111
+ ```bash
1112
+ ls -la .git
1113
+ ```
1114
+
1115
+ ### Detached HEAD State
1116
+
1117
+ Use `git.checkout` to return to a branch:
1118
+ ```typescript
1119
+ await client.exec(["git", "checkout"], { ref: "main" });
1120
+ ```
1121
+
1122
+ ## License
1123
+
1124
+ MIT
1125
+
1126
+ ## Related Packages
1127
+
1128
+ - [@procedures/client](https://github.com/mark1russell7/client) - Core client library
1129
+ - [@procedures/lib](https://github.com/mark1russell7/client-lib) - Library management (uses git procedures)
1130
+ - [@procedures/pnpm](https://github.com/mark1russell7/client-pnpm) - PNPM wrapper
1131
+ - [@procedures/bundle-dev](https://github.com/mark1russell7/bundle-dev) - Development bundle