@vibe-validate/git 0.9.3 → 0.9.4

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,62 @@
1
+ /**
2
+ * Smart Branch Sync Checker
3
+ *
4
+ * Safely checks if the current branch is behind a remote branch without auto-merging.
5
+ * Provides clear status reporting and next-step instructions.
6
+ *
7
+ * Key safety features:
8
+ * - Never auto-merges (preserves conflict visibility)
9
+ * - Clear exit codes for CI/agent integration
10
+ * - Explicit instructions when manual intervention needed
11
+ * - Cross-platform compatibility
12
+ */
13
+ export interface SyncCheckResult {
14
+ isUpToDate: boolean;
15
+ behindBy: number;
16
+ currentBranch: string;
17
+ hasRemote: boolean;
18
+ error?: string;
19
+ }
20
+ export type GitExecutor = (_args: string[]) => Promise<{
21
+ stdout: string;
22
+ stderr: string;
23
+ }>;
24
+ export interface SyncCheckOptions {
25
+ remoteBranch?: string;
26
+ gitExecutor?: GitExecutor;
27
+ }
28
+ /**
29
+ * Branch Sync Checker
30
+ *
31
+ * Checks if current branch is behind a remote branch
32
+ */
33
+ export declare class BranchSyncChecker {
34
+ private readonly remoteBranch;
35
+ private readonly gitExecutor;
36
+ constructor(options?: SyncCheckOptions);
37
+ /**
38
+ * Check if the current branch is synchronized with remote branch
39
+ *
40
+ * @returns Promise resolving to sync status information
41
+ */
42
+ checkSync(): Promise<SyncCheckResult>;
43
+ private getCurrentBranch;
44
+ private hasRemoteBranch;
45
+ private fetchRemote;
46
+ private getCommitsBehind;
47
+ /**
48
+ * Get appropriate exit code based on sync result
49
+ *
50
+ * @param result - The sync check result
51
+ * @returns Exit code (0=success, 1=needs merge, 2=error)
52
+ */
53
+ getExitCode(result: SyncCheckResult): number;
54
+ }
55
+ /**
56
+ * Convenience function for quick sync checking
57
+ *
58
+ * @param options - Sync check options
59
+ * @returns Sync check result
60
+ */
61
+ export declare function checkBranchSync(options?: SyncCheckOptions): Promise<SyncCheckResult>;
62
+ //# sourceMappingURL=branch-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-sync.d.ts","sourceRoot":"","sources":["../src/branch-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE3F,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AA2CD;;;;GAIG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;gBAE9B,OAAO,GAAE,gBAAqB;IAK1C;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC;YA0C7B,gBAAgB;YAUhB,eAAe;YASf,WAAW;YAUX,gBAAgB;IAW9B;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM;CAK7C;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAG9F"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Smart Branch Sync Checker
3
+ *
4
+ * Safely checks if the current branch is behind a remote branch without auto-merging.
5
+ * Provides clear status reporting and next-step instructions.
6
+ *
7
+ * Key safety features:
8
+ * - Never auto-merges (preserves conflict visibility)
9
+ * - Clear exit codes for CI/agent integration
10
+ * - Explicit instructions when manual intervention needed
11
+ * - Cross-platform compatibility
12
+ */
13
+ import { spawn } from 'child_process';
14
+ const GIT_TIMEOUT = 30000; // 30 seconds timeout for git operations
15
+ /**
16
+ * Execute git command safely using spawn (prevents command injection)
17
+ *
18
+ * @param args - Git command arguments (e.g., ['rev-parse', '--abbrev-ref', 'HEAD'])
19
+ * @returns Promise resolving to stdout and stderr
20
+ */
21
+ function execGit(args) {
22
+ return new Promise((resolve, reject) => {
23
+ const child = spawn('git', args, {
24
+ timeout: GIT_TIMEOUT
25
+ });
26
+ let stdout = '';
27
+ let stderr = '';
28
+ if (child.stdout) {
29
+ child.stdout.on('data', (data) => {
30
+ stdout += data.toString();
31
+ });
32
+ }
33
+ if (child.stderr) {
34
+ child.stderr.on('data', (data) => {
35
+ stderr += data.toString();
36
+ });
37
+ }
38
+ child.on('error', (error) => {
39
+ reject(error);
40
+ });
41
+ child.on('close', (code) => {
42
+ if (code === 0) {
43
+ resolve({ stdout, stderr });
44
+ }
45
+ else {
46
+ reject(new Error(`git exited with code ${code}: ${stderr}`));
47
+ }
48
+ });
49
+ });
50
+ }
51
+ /**
52
+ * Branch Sync Checker
53
+ *
54
+ * Checks if current branch is behind a remote branch
55
+ */
56
+ export class BranchSyncChecker {
57
+ remoteBranch;
58
+ gitExecutor;
59
+ constructor(options = {}) {
60
+ this.remoteBranch = options.remoteBranch || 'origin/main';
61
+ this.gitExecutor = options.gitExecutor || execGit;
62
+ }
63
+ /**
64
+ * Check if the current branch is synchronized with remote branch
65
+ *
66
+ * @returns Promise resolving to sync status information
67
+ */
68
+ async checkSync() {
69
+ try {
70
+ // Get current branch name
71
+ const currentBranch = await this.getCurrentBranch();
72
+ // Check if remote branch exists
73
+ const hasRemote = await this.hasRemoteBranch();
74
+ if (!hasRemote) {
75
+ return {
76
+ isUpToDate: true,
77
+ behindBy: 0,
78
+ currentBranch,
79
+ hasRemote: false,
80
+ error: `No remote branch ${this.remoteBranch} found`
81
+ };
82
+ }
83
+ // Fetch latest from remote
84
+ await this.fetchRemote();
85
+ // Check how many commits behind
86
+ const behindBy = await this.getCommitsBehind();
87
+ return {
88
+ isUpToDate: behindBy === 0,
89
+ behindBy,
90
+ currentBranch,
91
+ hasRemote: true
92
+ };
93
+ }
94
+ catch (error) {
95
+ const errorMessage = error instanceof Error ? error.message : String(error);
96
+ return {
97
+ isUpToDate: false,
98
+ behindBy: -1,
99
+ currentBranch: 'unknown',
100
+ hasRemote: false,
101
+ error: errorMessage
102
+ };
103
+ }
104
+ }
105
+ async getCurrentBranch() {
106
+ try {
107
+ const { stdout } = await this.gitExecutor(['rev-parse', '--abbrev-ref', 'HEAD']);
108
+ return stdout.trim();
109
+ }
110
+ catch (error) {
111
+ const errorMessage = error instanceof Error ? error.message : String(error);
112
+ throw new Error(`Not in a git repository or unable to determine current branch: ${errorMessage}`);
113
+ }
114
+ }
115
+ async hasRemoteBranch() {
116
+ try {
117
+ await this.gitExecutor(['rev-parse', '--verify', this.remoteBranch]);
118
+ return true;
119
+ }
120
+ catch (_error) {
121
+ return false;
122
+ }
123
+ }
124
+ async fetchRemote() {
125
+ try {
126
+ const [remote, branch] = this.remoteBranch.split('/');
127
+ await this.gitExecutor(['fetch', '--quiet', remote, branch]);
128
+ }
129
+ catch (error) {
130
+ const errorMessage = error instanceof Error ? error.message : String(error);
131
+ throw new Error(`Failed to fetch from ${this.remoteBranch}: ${errorMessage}`);
132
+ }
133
+ }
134
+ async getCommitsBehind() {
135
+ try {
136
+ const { stdout } = await this.gitExecutor(['rev-list', '--count', `HEAD..${this.remoteBranch}`]);
137
+ const count = parseInt(stdout.trim(), 10);
138
+ return isNaN(count) ? 0 : count;
139
+ }
140
+ catch (error) {
141
+ const errorMessage = error instanceof Error ? error.message : String(error);
142
+ throw new Error(`Failed to check commits behind: ${errorMessage}`);
143
+ }
144
+ }
145
+ /**
146
+ * Get appropriate exit code based on sync result
147
+ *
148
+ * @param result - The sync check result
149
+ * @returns Exit code (0=success, 1=needs merge, 2=error)
150
+ */
151
+ getExitCode(result) {
152
+ if (result.error)
153
+ return 2; // Error condition
154
+ if (!result.hasRemote)
155
+ return 0; // No remote, consider OK
156
+ return result.isUpToDate ? 0 : 1; // 0 = up to date, 1 = needs merge
157
+ }
158
+ }
159
+ /**
160
+ * Convenience function for quick sync checking
161
+ *
162
+ * @param options - Sync check options
163
+ * @returns Sync check result
164
+ */
165
+ export async function checkBranchSync(options = {}) {
166
+ const checker = new BranchSyncChecker(options);
167
+ return checker.checkSync();
168
+ }
169
+ //# sourceMappingURL=branch-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-sync.js","sourceRoot":"","sources":["../src/branch-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @vibe-validate/git
3
+ *
4
+ * Git utilities for vibe-validate - deterministic tree hash calculation,
5
+ * branch synchronization, and post-merge cleanup.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ export { getGitTreeHash, getHeadTreeHash, hasWorkingTreeChanges } from './tree-hash.js';
10
+ export { BranchSyncChecker, checkBranchSync, type SyncCheckResult, type SyncCheckOptions } from './branch-sync.js';
11
+ export { PostPRMergeCleanup, cleanupMergedBranches, type CleanupResult, type CleanupOptions } from './post-merge-cleanup.js';
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,KAAK,aAAa,EAClB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @vibe-validate/git
3
+ *
4
+ * Git utilities for vibe-validate - deterministic tree hash calculation,
5
+ * branch synchronization, and post-merge cleanup.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ // Tree hash calculation (deterministic, content-based)
10
+ export { getGitTreeHash, getHeadTreeHash, hasWorkingTreeChanges } from './tree-hash.js';
11
+ // Branch sync checking (safe, no auto-merge)
12
+ export { BranchSyncChecker, checkBranchSync } from './branch-sync.js';
13
+ // Post-merge cleanup (delete merged branches)
14
+ export { PostPRMergeCleanup, cleanupMergedBranches } from './post-merge-cleanup.js';
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,uDAAuD;AACvD,OAAO,EACL,cAAc,EACd,eAAe,EACf,qBAAqB,EACtB,MAAM,gBAAgB,CAAC;AAExB,6CAA6C;AAC7C,OAAO,EACL,iBAAiB,EACjB,eAAe,EAGhB,MAAM,kBAAkB,CAAC;AAE1B,8CAA8C;AAC9C,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EAGtB,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Post-PR Merge Cleanup Tool
3
+ *
4
+ * Comprehensive post-PR cleanup workflow that:
5
+ * 1. Switches to main branch
6
+ * 2. Syncs main branch with remote origin
7
+ * 3. Deletes local branches that have been merged
8
+ * 4. Provides clean workspace for next PR
9
+ *
10
+ * Safe operations:
11
+ * - Only deletes branches that are confirmed merged
12
+ * - Never deletes the current main branch or unmerged branches
13
+ * - Provides clear feedback on all actions taken
14
+ */
15
+ export interface CleanupResult {
16
+ success: boolean;
17
+ error?: string;
18
+ branchesDeleted: string[];
19
+ currentBranch: string;
20
+ mainSynced: boolean;
21
+ }
22
+ export interface CleanupOptions {
23
+ mainBranch?: string;
24
+ remoteName?: string;
25
+ dryRun?: boolean;
26
+ }
27
+ /**
28
+ * Post-PR Merge Cleanup
29
+ *
30
+ * Cleans up merged branches and syncs main branch after PR merge
31
+ */
32
+ export declare class PostPRMergeCleanup {
33
+ private readonly mainBranch;
34
+ private readonly remoteName;
35
+ private readonly dryRun;
36
+ constructor(options?: CleanupOptions);
37
+ /**
38
+ * Run comprehensive post-PR merge cleanup workflow
39
+ */
40
+ runCleanup(): Promise<CleanupResult>;
41
+ /**
42
+ * Get the current git branch name
43
+ */
44
+ private getCurrentBranch;
45
+ /**
46
+ * Switch to main branch
47
+ */
48
+ private switchToMain;
49
+ /**
50
+ * Sync main branch with remote origin
51
+ */
52
+ private syncMainBranch;
53
+ /**
54
+ * Fetch remote branch information
55
+ */
56
+ private fetchRemoteInfo;
57
+ /**
58
+ * Find and delete branches that have been merged
59
+ */
60
+ private deleteMergedBranches;
61
+ /**
62
+ * Check if a branch has been merged into main
63
+ */
64
+ private isBranchMerged;
65
+ /**
66
+ * Clean up remote tracking references
67
+ */
68
+ private pruneRemoteReferences;
69
+ }
70
+ /**
71
+ * Convenience function for quick cleanup
72
+ *
73
+ * @param options - Cleanup options
74
+ * @returns Cleanup result
75
+ */
76
+ export declare function cleanupMergedBranches(options?: CleanupOptions): Promise<CleanupResult>;
77
+ //# sourceMappingURL=post-merge-cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-merge-cleanup.d.ts","sourceRoot":"","sources":["../src/post-merge-cleanup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA4BH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;gBAErB,OAAO,GAAE,cAAmB;IAMxC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,aAAa,CAAC;IAqC1C;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAQxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB;;OAEG;IACH,OAAO,CAAC,eAAe;IAQvB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAuC5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAYtB;;OAEG;IACH,OAAO,CAAC,qBAAqB;CAO9B;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAGhG"}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Post-PR Merge Cleanup Tool
3
+ *
4
+ * Comprehensive post-PR cleanup workflow that:
5
+ * 1. Switches to main branch
6
+ * 2. Syncs main branch with remote origin
7
+ * 3. Deletes local branches that have been merged
8
+ * 4. Provides clean workspace for next PR
9
+ *
10
+ * Safe operations:
11
+ * - Only deletes branches that are confirmed merged
12
+ * - Never deletes the current main branch or unmerged branches
13
+ * - Provides clear feedback on all actions taken
14
+ */
15
+ import { spawnSync } from 'child_process';
16
+ const TIMEOUT = 30000; // 30 seconds timeout for git operations
17
+ /**
18
+ * Execute git command safely using spawnSync with array arguments
19
+ * Prevents command injection by avoiding shell interpretation
20
+ */
21
+ function execGitSync(args) {
22
+ const result = spawnSync('git', args, {
23
+ encoding: 'utf8',
24
+ timeout: TIMEOUT,
25
+ stdio: ['pipe', 'pipe', 'pipe']
26
+ });
27
+ if (result.error) {
28
+ throw result.error;
29
+ }
30
+ if (result.status !== 0) {
31
+ throw new Error(`git ${args[0]} failed: ${result.stderr}`);
32
+ }
33
+ return result.stdout;
34
+ }
35
+ /**
36
+ * Post-PR Merge Cleanup
37
+ *
38
+ * Cleans up merged branches and syncs main branch after PR merge
39
+ */
40
+ export class PostPRMergeCleanup {
41
+ mainBranch;
42
+ remoteName;
43
+ dryRun;
44
+ constructor(options = {}) {
45
+ this.mainBranch = options.mainBranch || 'main';
46
+ this.remoteName = options.remoteName || 'origin';
47
+ this.dryRun = options.dryRun || false;
48
+ }
49
+ /**
50
+ * Run comprehensive post-PR merge cleanup workflow
51
+ */
52
+ async runCleanup() {
53
+ const result = {
54
+ success: false,
55
+ branchesDeleted: [],
56
+ currentBranch: '',
57
+ mainSynced: false
58
+ };
59
+ try {
60
+ // Step 1: Get current branch
61
+ result.currentBranch = this.getCurrentBranch();
62
+ // Step 2: Switch to main branch
63
+ this.switchToMain();
64
+ // Step 3: Sync main branch with remote
65
+ this.syncMainBranch();
66
+ result.mainSynced = true;
67
+ // Step 4: Fetch remote branch information
68
+ this.fetchRemoteInfo();
69
+ // Step 5: Find and delete merged branches
70
+ result.branchesDeleted = this.deleteMergedBranches();
71
+ // Step 6: Clean up remote tracking branches
72
+ this.pruneRemoteReferences();
73
+ result.success = true;
74
+ return result;
75
+ }
76
+ catch (error) {
77
+ result.error = error instanceof Error ? error.message : String(error);
78
+ return result;
79
+ }
80
+ }
81
+ /**
82
+ * Get the current git branch name
83
+ */
84
+ getCurrentBranch() {
85
+ try {
86
+ return execGitSync(['branch', '--show-current']).trim();
87
+ }
88
+ catch (error) {
89
+ throw new Error(`Failed to get current branch: ${error}`);
90
+ }
91
+ }
92
+ /**
93
+ * Switch to main branch
94
+ */
95
+ switchToMain() {
96
+ try {
97
+ execGitSync(['checkout', this.mainBranch]);
98
+ }
99
+ catch (error) {
100
+ throw new Error(`Failed to switch to ${this.mainBranch} branch: ${error}`);
101
+ }
102
+ }
103
+ /**
104
+ * Sync main branch with remote origin
105
+ */
106
+ syncMainBranch() {
107
+ try {
108
+ // Fetch latest changes from remote
109
+ execGitSync(['fetch', this.remoteName, this.mainBranch]);
110
+ // Fast-forward merge remote/main
111
+ execGitSync(['merge', `${this.remoteName}/${this.mainBranch}`, '--ff-only']);
112
+ }
113
+ catch (error) {
114
+ throw new Error(`Failed to sync ${this.mainBranch} branch: ${error}`);
115
+ }
116
+ }
117
+ /**
118
+ * Fetch remote branch information
119
+ */
120
+ fetchRemoteInfo() {
121
+ try {
122
+ execGitSync(['fetch', this.remoteName, '--prune']);
123
+ }
124
+ catch (error) {
125
+ throw new Error(`Failed to fetch remote info: ${error}`);
126
+ }
127
+ }
128
+ /**
129
+ * Find and delete branches that have been merged
130
+ */
131
+ deleteMergedBranches() {
132
+ try {
133
+ // Get list of local branches (excluding main)
134
+ const allBranches = execGitSync(['branch', '--format=%(refname:short)'])
135
+ .trim()
136
+ .split('\n')
137
+ .filter(branch => branch && branch !== this.mainBranch && !branch.startsWith('*'));
138
+ const deletedBranches = [];
139
+ for (const branch of allBranches) {
140
+ if (this.isBranchMerged(branch)) {
141
+ if (this.dryRun) {
142
+ deletedBranches.push(branch);
143
+ continue;
144
+ }
145
+ try {
146
+ execGitSync(['branch', '-d', branch]);
147
+ deletedBranches.push(branch);
148
+ }
149
+ catch (_deleteError) {
150
+ // Try force delete if regular delete fails
151
+ try {
152
+ execGitSync(['branch', '-D', branch]);
153
+ deletedBranches.push(branch);
154
+ }
155
+ catch (_forceDeleteError) {
156
+ // Couldn't delete - skip this branch
157
+ }
158
+ }
159
+ }
160
+ }
161
+ return deletedBranches;
162
+ }
163
+ catch (_error) {
164
+ return [];
165
+ }
166
+ }
167
+ /**
168
+ * Check if a branch has been merged into main
169
+ */
170
+ isBranchMerged(branch) {
171
+ try {
172
+ const mergedBranches = execGitSync(['branch', '--merged', this.mainBranch, '--format=%(refname:short)']);
173
+ return mergedBranches.includes(branch);
174
+ }
175
+ catch (_error) {
176
+ // If we can't determine merge status, don't delete the branch
177
+ return false;
178
+ }
179
+ }
180
+ /**
181
+ * Clean up remote tracking references
182
+ */
183
+ pruneRemoteReferences() {
184
+ try {
185
+ execGitSync(['remote', 'prune', this.remoteName]);
186
+ }
187
+ catch (_error) {
188
+ // Non-critical operation - don't fail on error
189
+ }
190
+ }
191
+ }
192
+ /**
193
+ * Convenience function for quick cleanup
194
+ *
195
+ * @param options - Cleanup options
196
+ * @returns Cleanup result
197
+ */
198
+ export async function cleanupMergedBranches(options = {}) {
199
+ const cleanup = new PostPRMergeCleanup(options);
200
+ return cleanup.runCleanup();
201
+ }
202
+ //# sourceMappingURL=post-merge-cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-merge-cleanup.js","sourceRoot":"","sources":["../src/post-merge-cleanup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Deterministic Git Tree Hash Calculation
3
+ *
4
+ * Provides content-based hashing of working tree state including:
5
+ * - Staged changes (index)
6
+ * - Unstaged changes (working tree modifications)
7
+ * - Untracked files
8
+ *
9
+ * CRITICAL FIX: Uses git write-tree instead of git stash create for determinism.
10
+ * git stash create includes timestamps, making hashes non-deterministic.
11
+ * git write-tree produces content-based hashes only (no timestamps).
12
+ */
13
+ /**
14
+ * Get deterministic git tree hash representing current working tree state
15
+ *
16
+ * Implementation:
17
+ * 1. Mark untracked files with --intent-to-add (no actual staging)
18
+ * 2. Calculate tree hash with git write-tree (content-based, no timestamps)
19
+ * 3. Reset index to clean state (no side effects)
20
+ *
21
+ * Why this is better than git stash create:
22
+ * - git stash create: includes timestamps in commit → different hash each time
23
+ * - git write-tree: content-based only → same content = same hash (deterministic)
24
+ *
25
+ * @returns Git tree SHA-1 hash (40 hex characters)
26
+ * @throws Error if not in a git repository or git command fails
27
+ */
28
+ export declare function getGitTreeHash(): Promise<string>;
29
+ /**
30
+ * Get tree hash for HEAD commit (committed state only, no working tree changes)
31
+ *
32
+ * This is useful for comparing committed state vs working tree state.
33
+ *
34
+ * @returns Git tree SHA-1 hash of HEAD commit
35
+ * @throws Error if not in a git repository or HEAD doesn't exist
36
+ */
37
+ export declare function getHeadTreeHash(): Promise<string>;
38
+ /**
39
+ * Check if working tree has any changes compared to HEAD
40
+ *
41
+ * @returns true if working tree differs from HEAD, false if clean
42
+ */
43
+ export declare function hasWorkingTreeChanges(): Promise<boolean>;
44
+ //# sourceMappingURL=tree-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-hash.d.ts","sourceRoot":"","sources":["../src/tree-hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CA6CtD;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAQvD;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAS9D"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Deterministic Git Tree Hash Calculation
3
+ *
4
+ * Provides content-based hashing of working tree state including:
5
+ * - Staged changes (index)
6
+ * - Unstaged changes (working tree modifications)
7
+ * - Untracked files
8
+ *
9
+ * CRITICAL FIX: Uses git write-tree instead of git stash create for determinism.
10
+ * git stash create includes timestamps, making hashes non-deterministic.
11
+ * git write-tree produces content-based hashes only (no timestamps).
12
+ */
13
+ import { execSync } from 'child_process';
14
+ const GIT_TIMEOUT = 30000; // 30 seconds timeout for git operations
15
+ const GIT_OPTIONS = {
16
+ encoding: 'utf8',
17
+ timeout: GIT_TIMEOUT,
18
+ stdio: ['pipe', 'pipe', 'ignore'],
19
+ };
20
+ /**
21
+ * Get deterministic git tree hash representing current working tree state
22
+ *
23
+ * Implementation:
24
+ * 1. Mark untracked files with --intent-to-add (no actual staging)
25
+ * 2. Calculate tree hash with git write-tree (content-based, no timestamps)
26
+ * 3. Reset index to clean state (no side effects)
27
+ *
28
+ * Why this is better than git stash create:
29
+ * - git stash create: includes timestamps in commit → different hash each time
30
+ * - git write-tree: content-based only → same content = same hash (deterministic)
31
+ *
32
+ * @returns Git tree SHA-1 hash (40 hex characters)
33
+ * @throws Error if not in a git repository or git command fails
34
+ */
35
+ export async function getGitTreeHash() {
36
+ try {
37
+ // Check if we're in a git repository
38
+ execSync('git rev-parse --is-inside-work-tree', GIT_OPTIONS);
39
+ // Step 1: Mark all untracked files with --intent-to-add
40
+ // This adds them to the index WITHOUT staging their content
41
+ // --force: include ignored files for complete state tracking
42
+ try {
43
+ execSync('git add --intent-to-add --all --force', {
44
+ ...GIT_OPTIONS,
45
+ stdio: ['pipe', 'pipe', 'pipe'] // Capture stderr for error handling
46
+ });
47
+ }
48
+ catch (addError) {
49
+ // If no untracked files, git add fails with "nothing to add"
50
+ // This is fine - just means we only have tracked files
51
+ const errorMessage = addError instanceof Error ? addError.message : String(addError);
52
+ if (!errorMessage.includes('nothing')) {
53
+ // Real error - re-throw
54
+ throw addError;
55
+ }
56
+ }
57
+ // Step 2: Get tree hash (content-based, no timestamps)
58
+ const treeHash = execSync('git write-tree', GIT_OPTIONS).trim();
59
+ // Step 3: Reset index to clean state (remove intent-to-add marks)
60
+ // This ensures no side effects from our hash calculation
61
+ execSync('git reset', GIT_OPTIONS);
62
+ return treeHash;
63
+ }
64
+ catch (error) {
65
+ // Handle not-in-git-repo case
66
+ const errorMessage = error instanceof Error ? error.message : String(error);
67
+ if (errorMessage.includes('not a git repository')) {
68
+ // Not in git repo - fall back to timestamp-based hash
69
+ console.warn('⚠️ Not in git repository, using timestamp-based hash');
70
+ return `nogit-${Date.now()}`;
71
+ }
72
+ // Other git errors
73
+ throw new Error(`Failed to calculate git tree hash: ${errorMessage}`);
74
+ }
75
+ }
76
+ /**
77
+ * Get tree hash for HEAD commit (committed state only, no working tree changes)
78
+ *
79
+ * This is useful for comparing committed state vs working tree state.
80
+ *
81
+ * @returns Git tree SHA-1 hash of HEAD commit
82
+ * @throws Error if not in a git repository or HEAD doesn't exist
83
+ */
84
+ export async function getHeadTreeHash() {
85
+ try {
86
+ const treeHash = execSync('git rev-parse HEAD^{tree}', GIT_OPTIONS).trim();
87
+ return treeHash;
88
+ }
89
+ catch (error) {
90
+ const errorMessage = error instanceof Error ? error.message : String(error);
91
+ throw new Error(`Failed to get HEAD tree hash: ${errorMessage}`);
92
+ }
93
+ }
94
+ /**
95
+ * Check if working tree has any changes compared to HEAD
96
+ *
97
+ * @returns true if working tree differs from HEAD, false if clean
98
+ */
99
+ export async function hasWorkingTreeChanges() {
100
+ try {
101
+ const workingTreeHash = await getGitTreeHash();
102
+ const headTreeHash = await getHeadTreeHash();
103
+ return workingTreeHash !== headTreeHash;
104
+ }
105
+ catch (_error) {
106
+ // If we can't determine, assume there are changes (safe default)
107
+ return true;
108
+ }
109
+ }
110
+ //# sourceMappingURL=tree-hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-hash.js","sourceRoot":"","sources":["../src/tree-hash.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-validate/git",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "Git utilities for vibe-validate - tree hash calculation, branch sync, and post-merge cleanup",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",