@vibe-validate/git 0.17.0-rc4 → 0.17.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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Git Command Utilities
3
+ *
4
+ * High-level git operations built on top of the secure git-executor.
5
+ * These functions provide convenient access to common git commands.
6
+ */
7
+ /**
8
+ * Check if the current directory is inside a git repository
9
+ * @returns true if inside a git repository, false otherwise
10
+ */
11
+ export declare function isGitRepository(): boolean;
12
+ /**
13
+ * Get the path to the .git directory
14
+ * @returns The absolute path to the .git directory
15
+ * @throws Error if not in a git repository
16
+ */
17
+ export declare function getGitDir(): string;
18
+ /**
19
+ * Get the root directory of the git repository
20
+ * @returns The absolute path to the repository root
21
+ * @throws Error if not in a git repository
22
+ */
23
+ export declare function getRepositoryRoot(): string;
24
+ /**
25
+ * Get the current branch name
26
+ * @returns The name of the current branch
27
+ * @throws Error if not on a branch (detached HEAD)
28
+ */
29
+ export declare function getCurrentBranch(): string;
30
+ /**
31
+ * Get the commit SHA of HEAD
32
+ * @returns The full SHA of the current commit
33
+ * @throws Error if not in a git repository or HEAD is invalid
34
+ */
35
+ export declare function getHeadCommitSha(): string;
36
+ /**
37
+ * Get the tree hash of the current HEAD commit
38
+ * @returns The tree hash of HEAD
39
+ * @throws Error if not in a git repository or HEAD is invalid
40
+ */
41
+ export declare function getHeadTreeSha(): string;
42
+ /**
43
+ * Verify that a git reference exists
44
+ * @param ref - The reference to verify (branch, tag, commit SHA, etc.)
45
+ * @returns true if the reference exists, false otherwise
46
+ */
47
+ export declare function verifyRef(ref: string): boolean;
48
+ /**
49
+ * Verify that a git reference exists (alternate form for backwards compatibility)
50
+ * @param ref - The reference to verify
51
+ * @returns The SHA of the reference if it exists
52
+ * @throws Error if the reference doesn't exist
53
+ */
54
+ export declare function verifyRefOrThrow(ref: string): string;
55
+ /**
56
+ * Check if git notes exist for a specific ref
57
+ * @param notesRef - The notes reference to check (e.g., 'refs/notes/vibe-validate/validate')
58
+ * @returns true if the notes ref exists, false otherwise
59
+ */
60
+ export declare function hasNotesRef(notesRef: string): boolean;
61
+ //# sourceMappingURL=git-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-commands.d.ts","sourceRoot":"","sources":["../src/git-commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAErD"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Git Command Utilities
3
+ *
4
+ * High-level git operations built on top of the secure git-executor.
5
+ * These functions provide convenient access to common git commands.
6
+ */
7
+ import { execGitCommand, tryGitCommand } from './git-executor.js';
8
+ /**
9
+ * Check if the current directory is inside a git repository
10
+ * @returns true if inside a git repository, false otherwise
11
+ */
12
+ export function isGitRepository() {
13
+ return tryGitCommand(['rev-parse', '--is-inside-work-tree']);
14
+ }
15
+ /**
16
+ * Get the path to the .git directory
17
+ * @returns The absolute path to the .git directory
18
+ * @throws Error if not in a git repository
19
+ */
20
+ export function getGitDir() {
21
+ return execGitCommand(['rev-parse', '--git-dir']);
22
+ }
23
+ /**
24
+ * Get the root directory of the git repository
25
+ * @returns The absolute path to the repository root
26
+ * @throws Error if not in a git repository
27
+ */
28
+ export function getRepositoryRoot() {
29
+ return execGitCommand(['rev-parse', '--show-toplevel']);
30
+ }
31
+ /**
32
+ * Get the current branch name
33
+ * @returns The name of the current branch
34
+ * @throws Error if not on a branch (detached HEAD)
35
+ */
36
+ export function getCurrentBranch() {
37
+ return execGitCommand(['rev-parse', '--abbrev-ref', 'HEAD']);
38
+ }
39
+ /**
40
+ * Get the commit SHA of HEAD
41
+ * @returns The full SHA of the current commit
42
+ * @throws Error if not in a git repository or HEAD is invalid
43
+ */
44
+ export function getHeadCommitSha() {
45
+ return execGitCommand(['rev-parse', 'HEAD']);
46
+ }
47
+ /**
48
+ * Get the tree hash of the current HEAD commit
49
+ * @returns The tree hash of HEAD
50
+ * @throws Error if not in a git repository or HEAD is invalid
51
+ */
52
+ export function getHeadTreeSha() {
53
+ return execGitCommand(['rev-parse', 'HEAD^{tree}']);
54
+ }
55
+ /**
56
+ * Verify that a git reference exists
57
+ * @param ref - The reference to verify (branch, tag, commit SHA, etc.)
58
+ * @returns true if the reference exists, false otherwise
59
+ */
60
+ export function verifyRef(ref) {
61
+ return tryGitCommand(['rev-parse', '--verify', ref]);
62
+ }
63
+ /**
64
+ * Verify that a git reference exists (alternate form for backwards compatibility)
65
+ * @param ref - The reference to verify
66
+ * @returns The SHA of the reference if it exists
67
+ * @throws Error if the reference doesn't exist
68
+ */
69
+ export function verifyRefOrThrow(ref) {
70
+ return execGitCommand(['rev-parse', '--verify', ref]);
71
+ }
72
+ /**
73
+ * Check if git notes exist for a specific ref
74
+ * @param notesRef - The notes reference to check (e.g., 'refs/notes/vibe-validate/validate')
75
+ * @returns true if the notes ref exists, false otherwise
76
+ */
77
+ export function hasNotesRef(notesRef) {
78
+ return tryGitCommand(['rev-parse', '--verify', notesRef]);
79
+ }
80
+ //# sourceMappingURL=git-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-commands.js","sourceRoot":"","sources":["../src/git-commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,aAAa,CAAC,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,cAAc,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,cAAc,CAAC,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,cAAc,CAAC,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,cAAc,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,cAAc,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,aAAa,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAO,cAAc,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,OAAO,aAAa,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AAC5D,CAAC"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Secure Git Command Execution
3
+ *
4
+ * This module provides a centralized, secure way to execute git commands.
5
+ * ALL git command execution in vibe-validate MUST go through this module.
6
+ *
7
+ * Security principles:
8
+ * 1. Use spawnSync with array arguments (never string interpolation)
9
+ * 2. Validate all user-controlled inputs
10
+ * 3. No shell piping or heredocs
11
+ * 4. Explicit argument construction
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ export interface GitExecutionOptions {
16
+ /**
17
+ * Maximum time to wait for git command (ms)
18
+ * @default 30000
19
+ */
20
+ timeout?: number;
21
+ /**
22
+ * Encoding for stdout/stderr
23
+ * @default 'utf8'
24
+ */
25
+ encoding?: BufferEncoding;
26
+ /**
27
+ * Standard input to pass to command
28
+ */
29
+ stdin?: string;
30
+ /**
31
+ * Whether to ignore errors (return empty string instead of throwing)
32
+ * @default false
33
+ */
34
+ ignoreErrors?: boolean;
35
+ /**
36
+ * Whether to suppress stderr
37
+ * @default false
38
+ */
39
+ suppressStderr?: boolean;
40
+ }
41
+ /**
42
+ * Result of a git command execution
43
+ */
44
+ export interface GitExecutionResult {
45
+ /** Standard output from the command */
46
+ stdout: string;
47
+ /** Standard error from the command */
48
+ stderr: string;
49
+ /** Exit code (0 for success) */
50
+ exitCode: number;
51
+ /** Whether the command succeeded */
52
+ success: boolean;
53
+ }
54
+ /**
55
+ * Error thrown when a git command fails
56
+ */
57
+ export interface GitCommandError extends Error {
58
+ /** Exit code from the git command */
59
+ exitCode: number;
60
+ /** Standard error output */
61
+ stderr: string;
62
+ /** Standard output */
63
+ stdout: string;
64
+ }
65
+ /**
66
+ * Execute a git command securely using spawnSync with array arguments
67
+ *
68
+ * This is the ONLY function that should execute git commands. All other
69
+ * git operations must go through this function or higher-level abstractions.
70
+ *
71
+ * @param args - Git command arguments (e.g., ['rev-parse', '--git-dir'])
72
+ * @param options - Execution options
73
+ * @returns Execution result
74
+ * @throws Error if command fails and ignoreErrors is false
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * // Get git directory
79
+ * const result = executeGitCommand(['rev-parse', '--git-dir']);
80
+ * console.log(result.stdout); // ".git"
81
+ *
82
+ * // Add note with stdin
83
+ * executeGitCommand(
84
+ * ['notes', '--ref=vibe-validate/validate', 'add', '-f', '-F', '-', treeHash],
85
+ * { stdin: noteContent }
86
+ * );
87
+ * ```
88
+ */
89
+ export declare function executeGitCommand(args: string[], options?: GitExecutionOptions): GitExecutionResult;
90
+ /**
91
+ * Execute a git command and return stdout, throwing on error
92
+ *
93
+ * Convenience wrapper for the common case of executing a git command
94
+ * and only caring about the stdout result.
95
+ *
96
+ * @param args - Git command arguments
97
+ * @param options - Execution options
98
+ * @returns Command stdout, trimmed
99
+ * @throws Error if command fails
100
+ */
101
+ export declare function execGitCommand(args: string[], options?: GitExecutionOptions): string;
102
+ /**
103
+ * Execute a git command and return success status (no throw)
104
+ *
105
+ * Useful for checking if a git operation would succeed without
106
+ * handling exceptions.
107
+ *
108
+ * @param args - Git command arguments
109
+ * @param options - Execution options
110
+ * @returns true if command succeeded, false otherwise
111
+ */
112
+ export declare function tryGitCommand(args: string[], options?: GitExecutionOptions): boolean;
113
+ /**
114
+ * Validate that a string is safe to use as a git ref
115
+ *
116
+ * Git refs must:
117
+ * - Not contain special shell characters
118
+ * - Not start with a dash (looks like an option)
119
+ * - Not contain path traversal sequences
120
+ * - Match git's ref format rules
121
+ *
122
+ * @param ref - The ref to validate
123
+ * @throws Error if ref is invalid
124
+ */
125
+ export declare function validateGitRef(ref: string): void;
126
+ /**
127
+ * Validate that a string is safe to use as a git notes ref
128
+ *
129
+ * Notes refs have additional restrictions beyond normal refs.
130
+ *
131
+ * @param notesRef - The notes ref to validate
132
+ * @throws Error if notes ref is invalid
133
+ */
134
+ export declare function validateNotesRef(notesRef: string): void;
135
+ /**
136
+ * Validate that a string is safe to use as a tree hash
137
+ *
138
+ * Tree hashes must be valid git object IDs (40-char hex or abbreviated).
139
+ *
140
+ * @param treeHash - The tree hash to validate
141
+ * @throws Error if tree hash is invalid
142
+ */
143
+ export declare function validateTreeHash(treeHash: string): void;
144
+ //# sourceMappingURL=git-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-executor.d.ts","sourceRoot":"","sources":["../src/git-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AASH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAE1B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,KAAK;IAC5C,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAqDpB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAGxF;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAGxF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CA6BhD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAWvD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAcvD"}
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Secure Git Command Execution
3
+ *
4
+ * This module provides a centralized, secure way to execute git commands.
5
+ * ALL git command execution in vibe-validate MUST go through this module.
6
+ *
7
+ * Security principles:
8
+ * 1. Use spawnSync with array arguments (never string interpolation)
9
+ * 2. Validate all user-controlled inputs
10
+ * 3. No shell piping or heredocs
11
+ * 4. Explicit argument construction
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ import { spawnSync } from 'node:child_process';
16
+ /**
17
+ * Standard options for git command execution
18
+ */
19
+ const GIT_TIMEOUT = 30000; // 30 seconds
20
+ /**
21
+ * Execute a git command securely using spawnSync with array arguments
22
+ *
23
+ * This is the ONLY function that should execute git commands. All other
24
+ * git operations must go through this function or higher-level abstractions.
25
+ *
26
+ * @param args - Git command arguments (e.g., ['rev-parse', '--git-dir'])
27
+ * @param options - Execution options
28
+ * @returns Execution result
29
+ * @throws Error if command fails and ignoreErrors is false
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * // Get git directory
34
+ * const result = executeGitCommand(['rev-parse', '--git-dir']);
35
+ * console.log(result.stdout); // ".git"
36
+ *
37
+ * // Add note with stdin
38
+ * executeGitCommand(
39
+ * ['notes', '--ref=vibe-validate/validate', 'add', '-f', '-F', '-', treeHash],
40
+ * { stdin: noteContent }
41
+ * );
42
+ * ```
43
+ */
44
+ export function executeGitCommand(args, options = {}) {
45
+ const { timeout = GIT_TIMEOUT, encoding = 'utf8', stdin, ignoreErrors = false, suppressStderr = false, } = options;
46
+ // Validate arguments
47
+ if (!Array.isArray(args) || args.length === 0) {
48
+ throw new Error('Git command arguments must be a non-empty array');
49
+ }
50
+ // Build spawn options
51
+ const spawnOptions = {
52
+ encoding,
53
+ timeout,
54
+ maxBuffer: 10 * 1024 * 1024, // 10MB buffer
55
+ };
56
+ // Configure stdio
57
+ if (stdin !== undefined) {
58
+ spawnOptions.input = stdin;
59
+ spawnOptions.stdio = ['pipe', 'pipe', suppressStderr ? 'ignore' : 'pipe'];
60
+ }
61
+ else {
62
+ spawnOptions.stdio = ['ignore', 'pipe', suppressStderr ? 'ignore' : 'pipe'];
63
+ }
64
+ // Execute command
65
+ const result = spawnSync('git', args, spawnOptions);
66
+ const stdout = (result.stdout?.toString() || '').trim();
67
+ const stderr = (result.stderr?.toString() || '').trim();
68
+ const exitCode = result.status ?? 1;
69
+ const success = exitCode === 0;
70
+ // Handle errors
71
+ if (success || ignoreErrors) {
72
+ return {
73
+ stdout,
74
+ stderr,
75
+ exitCode,
76
+ success,
77
+ };
78
+ }
79
+ const errorMessage = stderr || stdout || 'Git command failed';
80
+ const error = new Error(`Git command failed: git ${args.join(' ')}\n${errorMessage}`);
81
+ error.exitCode = exitCode;
82
+ error.stderr = stderr;
83
+ error.stdout = stdout;
84
+ throw error;
85
+ }
86
+ /**
87
+ * Execute a git command and return stdout, throwing on error
88
+ *
89
+ * Convenience wrapper for the common case of executing a git command
90
+ * and only caring about the stdout result.
91
+ *
92
+ * @param args - Git command arguments
93
+ * @param options - Execution options
94
+ * @returns Command stdout, trimmed
95
+ * @throws Error if command fails
96
+ */
97
+ export function execGitCommand(args, options = {}) {
98
+ const result = executeGitCommand(args, options);
99
+ return result.stdout;
100
+ }
101
+ /**
102
+ * Execute a git command and return success status (no throw)
103
+ *
104
+ * Useful for checking if a git operation would succeed without
105
+ * handling exceptions.
106
+ *
107
+ * @param args - Git command arguments
108
+ * @param options - Execution options
109
+ * @returns true if command succeeded, false otherwise
110
+ */
111
+ export function tryGitCommand(args, options = {}) {
112
+ const result = executeGitCommand(args, { ...options, ignoreErrors: true });
113
+ return result.success;
114
+ }
115
+ /**
116
+ * Validate that a string is safe to use as a git ref
117
+ *
118
+ * Git refs must:
119
+ * - Not contain special shell characters
120
+ * - Not start with a dash (looks like an option)
121
+ * - Not contain path traversal sequences
122
+ * - Match git's ref format rules
123
+ *
124
+ * @param ref - The ref to validate
125
+ * @throws Error if ref is invalid
126
+ */
127
+ export function validateGitRef(ref) {
128
+ if (typeof ref !== 'string' || ref.length === 0) {
129
+ throw new Error('Git ref must be a non-empty string');
130
+ }
131
+ // Check for shell special characters
132
+ if (/[;&|`$(){}[\]<>!\\"]/.test(ref)) {
133
+ throw new Error(`Invalid git ref: contains shell special characters: ${ref}`);
134
+ }
135
+ // Check for leading dash (looks like an option)
136
+ if (ref.startsWith('-')) {
137
+ throw new Error(`Invalid git ref: starts with dash: ${ref}`);
138
+ }
139
+ // Check for path traversal
140
+ if (ref.includes('..') || ref.includes('//')) {
141
+ throw new Error(`Invalid git ref: contains path traversal: ${ref}`);
142
+ }
143
+ // Check for null bytes
144
+ if (ref.includes('\0')) {
145
+ throw new Error('Invalid git ref: contains null byte');
146
+ }
147
+ // Check for newlines (could break command)
148
+ if (ref.includes('\n') || ref.includes('\r')) {
149
+ throw new Error('Invalid git ref: contains newline');
150
+ }
151
+ }
152
+ /**
153
+ * Validate that a string is safe to use as a git notes ref
154
+ *
155
+ * Notes refs have additional restrictions beyond normal refs.
156
+ *
157
+ * @param notesRef - The notes ref to validate
158
+ * @throws Error if notes ref is invalid
159
+ */
160
+ export function validateNotesRef(notesRef) {
161
+ validateGitRef(notesRef);
162
+ // Notes refs should follow refs/notes/* pattern or short form
163
+ // Short form: 'vibe-validate/validate' → 'refs/notes/vibe-validate/validate'
164
+ if (!notesRef.startsWith('refs/notes/') && notesRef.includes('/')) {
165
+ // Short form is valid, but must not contain spaces
166
+ if (/\s/.test(notesRef)) {
167
+ throw new Error(`Invalid notes ref: contains whitespace: ${notesRef}`);
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * Validate that a string is safe to use as a tree hash
173
+ *
174
+ * Tree hashes must be valid git object IDs (40-char hex or abbreviated).
175
+ *
176
+ * @param treeHash - The tree hash to validate
177
+ * @throws Error if tree hash is invalid
178
+ */
179
+ export function validateTreeHash(treeHash) {
180
+ if (typeof treeHash !== 'string' || treeHash.length === 0) {
181
+ throw new Error('Tree hash must be a non-empty string');
182
+ }
183
+ // Must be hex characters only
184
+ if (!/^[0-9a-f]+$/.test(treeHash)) {
185
+ throw new Error(`Invalid tree hash: must be hexadecimal: ${treeHash}`);
186
+ }
187
+ // Must be reasonable length (4-40 chars for abbreviated or full hash)
188
+ if (treeHash.length < 4 || treeHash.length > 40) {
189
+ throw new Error(`Invalid tree hash: invalid length: ${treeHash}`);
190
+ }
191
+ }
192
+ //# sourceMappingURL=git-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-executor.js","sourceRoot":"","sources":["../src/git-executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAyB,MAAM,oBAAoB,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,aAAa;AA2DxC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAc,EACd,UAA+B,EAAE;IAEjC,MAAM,EACJ,OAAO,GAAG,WAAW,EACrB,QAAQ,GAAG,MAAM,EACjB,KAAK,EACL,YAAY,GAAG,KAAK,EACpB,cAAc,GAAG,KAAK,GACvB,GAAG,OAAO,CAAC;IAEZ,qBAAqB;IACrB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IAED,sBAAsB;IACtB,MAAM,YAAY,GAAqB;QACrC,QAAQ;QACR,OAAO;QACP,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,cAAc;KAC5C,CAAC;IAEF,kBAAkB;IAClB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,YAAY,CAAC,KAAK,GAAG,KAAK,CAAC;QAC3B,YAAY,CAAC,KAAK,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5E,CAAC;SAAM,CAAC;QACN,YAAY,CAAC,KAAK,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9E,CAAC;IAED,kBAAkB;IAClB,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;IAEpD,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxD,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,QAAQ,KAAK,CAAC,CAAC;IAE/B,gBAAgB;IAChB,IAAI,OAAO,IAAI,YAAY,EAAE,CAAC;QAC5B,OAAO;YACL,MAAM;YACN,MAAM;YACN,QAAQ;YACR,OAAO;SACR,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,IAAI,MAAM,IAAI,oBAAoB,CAAC;IAC9D,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,2BAA2B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,YAAY,EAAE,CAAoB,CAAC;IACzG,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1B,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,MAAM,KAAK,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,IAAc,EAAE,UAA+B,EAAE;IAC9E,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAChD,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAAC,IAAc,EAAE,UAA+B,EAAE;IAC7E,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,EAAE,EAAE,GAAG,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3E,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,qCAAqC;IACrC,IAAI,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,uDAAuD,GAAG,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,gDAAgD;IAChD,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,sCAAsC,GAAG,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,2BAA2B;IAC3B,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,6CAA6C,GAAG,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,uBAAuB;IACvB,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IAED,2CAA2C;IAC3C,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,cAAc,CAAC,QAAQ,CAAC,CAAC;IAEzB,8DAA8D;IAC9D,6EAA6E;IAC7E,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAClE,mDAAmD;QACnD,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,2CAA2C,QAAQ,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,8BAA8B;IAC9B,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,2CAA2C,QAAQ,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,sEAAsE;IACtE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,sCAAsC,QAAQ,EAAE,CAAC,CAAC;IACpE,CAAC;AACH,CAAC"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Git Notes Operations
3
+ *
4
+ * High-level abstraction for git notes operations. All notes-related
5
+ * commands in vibe-validate must use these functions.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ /**
10
+ * Add or update a git note
11
+ *
12
+ * @param notesRef - The notes reference (e.g., 'vibe-validate/validate')
13
+ * @param object - The git object to attach the note to (tree hash, commit SHA, etc.)
14
+ * @param content - The note content
15
+ * @param force - Whether to overwrite existing note
16
+ * @returns true if note was added successfully
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * addNote('vibe-validate/validate', treeHash, noteContent, true);
21
+ * ```
22
+ */
23
+ export declare function addNote(notesRef: string, object: string, content: string, force?: boolean): boolean;
24
+ /**
25
+ * Read a git note
26
+ *
27
+ * @param notesRef - The notes reference
28
+ * @param object - The git object to read the note from
29
+ * @returns The note content, or null if no note exists
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const note = readNote('vibe-validate/validate', treeHash);
34
+ * if (note) {
35
+ * console.log('Note content:', note);
36
+ * }
37
+ * ```
38
+ */
39
+ export declare function readNote(notesRef: string, object: string): string | null;
40
+ /**
41
+ * Remove a git note
42
+ *
43
+ * @param notesRef - The notes reference
44
+ * @param object - The git object to remove the note from
45
+ * @returns true if note was removed, false if it didn't exist
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const removed = removeNote('vibe-validate/validate', treeHash);
50
+ * console.log(removed ? 'Removed' : 'Did not exist');
51
+ * ```
52
+ */
53
+ export declare function removeNote(notesRef: string, object: string): boolean;
54
+ /**
55
+ * List all notes in a notes reference
56
+ *
57
+ * @param notesRef - The notes reference
58
+ * @returns Array of [object, content] pairs, or empty array if no notes
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * const notes = listNotes('vibe-validate/validate');
63
+ * for (const [treeHash, content] of notes) {
64
+ * console.log(`${treeHash}: ${content}`);
65
+ * }
66
+ * ```
67
+ */
68
+ export declare function listNotes(notesRef: string): Array<[string, string]>;
69
+ /**
70
+ * Check if a note exists
71
+ *
72
+ * @param notesRef - The notes reference
73
+ * @param object - The git object to check
74
+ * @returns true if note exists, false otherwise
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * if (hasNote('vibe-validate/validate', treeHash)) {
79
+ * console.log('Note exists');
80
+ * }
81
+ * ```
82
+ */
83
+ export declare function hasNote(notesRef: string, object: string): boolean;
84
+ /**
85
+ * List all refs under a notes namespace
86
+ *
87
+ * This is useful for finding all notes under a particular path,
88
+ * such as all run cache entries under refs/notes/vibe-validate/run/
89
+ *
90
+ * @param notesPath - The notes path (e.g., 'refs/notes/vibe-validate/run')
91
+ * @returns Array of full ref names
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * const refs = listNotesRefs('refs/notes/vibe-validate/run');
96
+ * for (const ref of refs) {
97
+ * console.log('Found note ref:', ref);
98
+ * }
99
+ * ```
100
+ */
101
+ export declare function listNotesRefs(notesPath: string): string[];
102
+ /**
103
+ * Remove all notes refs under a namespace
104
+ *
105
+ * This is used for bulk cleanup operations, such as pruning all
106
+ * run cache entries under a tree hash.
107
+ *
108
+ * SECURITY: This function validates each ref before deletion to prevent
109
+ * accidental deletion of non-notes refs.
110
+ *
111
+ * @param notesPath - The notes path (e.g., 'refs/notes/vibe-validate/run/abc123')
112
+ * @returns Number of refs deleted
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const deleted = removeNotesRefs('refs/notes/vibe-validate/run/abc123');
117
+ * console.log(`Deleted ${deleted} refs`);
118
+ * ```
119
+ */
120
+ export declare function removeNotesRefs(notesPath: string): number;
121
+ /**
122
+ * Check if a notes ref exists
123
+ *
124
+ * @param notesRef - The notes reference
125
+ * @returns true if the notes ref exists, false otherwise
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * if (hasNotesRef('vibe-validate/validate')) {
130
+ * console.log('Notes ref exists');
131
+ * }
132
+ * ```
133
+ */
134
+ export declare function hasNotesRef(notesRef: string): boolean;
135
+ /**
136
+ * Get the commit SHA that a notes ref points to
137
+ *
138
+ * @param notesRef - The notes reference
139
+ * @returns The commit SHA, or null if ref doesn't exist
140
+ */
141
+ export declare function getNotesRefSha(notesRef: string): string | null;
142
+ //# sourceMappingURL=git-notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-notes.d.ts","sourceRoot":"","sources":["../src/git-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,OAAe,GACrB,OAAO,CAiBT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAOpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA4BnE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAOjE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAczD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAgCzD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAWrD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa9D"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Git Notes Operations
3
+ *
4
+ * High-level abstraction for git notes operations. All notes-related
5
+ * commands in vibe-validate must use these functions.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+ import { executeGitCommand, tryGitCommand, validateNotesRef, validateTreeHash, } from './git-executor.js';
10
+ /**
11
+ * Add or update a git note
12
+ *
13
+ * @param notesRef - The notes reference (e.g., 'vibe-validate/validate')
14
+ * @param object - The git object to attach the note to (tree hash, commit SHA, etc.)
15
+ * @param content - The note content
16
+ * @param force - Whether to overwrite existing note
17
+ * @returns true if note was added successfully
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * addNote('vibe-validate/validate', treeHash, noteContent, true);
22
+ * ```
23
+ */
24
+ export function addNote(notesRef, object, content, force = false) {
25
+ validateNotesRef(notesRef);
26
+ validateTreeHash(object);
27
+ const args = ['notes', `--ref=${notesRef}`, 'add'];
28
+ if (force) {
29
+ args.push('-f');
30
+ }
31
+ args.push('-F', '-', object);
32
+ const result = executeGitCommand(args, {
33
+ stdin: content,
34
+ ignoreErrors: true,
35
+ suppressStderr: true,
36
+ });
37
+ return result.success;
38
+ }
39
+ /**
40
+ * Read a git note
41
+ *
42
+ * @param notesRef - The notes reference
43
+ * @param object - The git object to read the note from
44
+ * @returns The note content, or null if no note exists
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const note = readNote('vibe-validate/validate', treeHash);
49
+ * if (note) {
50
+ * console.log('Note content:', note);
51
+ * }
52
+ * ```
53
+ */
54
+ export function readNote(notesRef, object) {
55
+ validateNotesRef(notesRef);
56
+ validateTreeHash(object);
57
+ const result = executeGitCommand(['notes', `--ref=${notesRef}`, 'show', object], {
58
+ ignoreErrors: true,
59
+ suppressStderr: true,
60
+ });
61
+ return result.success ? result.stdout : null;
62
+ }
63
+ /**
64
+ * Remove a git note
65
+ *
66
+ * @param notesRef - The notes reference
67
+ * @param object - The git object to remove the note from
68
+ * @returns true if note was removed, false if it didn't exist
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const removed = removeNote('vibe-validate/validate', treeHash);
73
+ * console.log(removed ? 'Removed' : 'Did not exist');
74
+ * ```
75
+ */
76
+ export function removeNote(notesRef, object) {
77
+ validateNotesRef(notesRef);
78
+ validateTreeHash(object);
79
+ return tryGitCommand(['notes', `--ref=${notesRef}`, 'remove', object], {
80
+ suppressStderr: true,
81
+ });
82
+ }
83
+ /**
84
+ * List all notes in a notes reference
85
+ *
86
+ * @param notesRef - The notes reference
87
+ * @returns Array of [object, content] pairs, or empty array if no notes
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const notes = listNotes('vibe-validate/validate');
92
+ * for (const [treeHash, content] of notes) {
93
+ * console.log(`${treeHash}: ${content}`);
94
+ * }
95
+ * ```
96
+ */
97
+ export function listNotes(notesRef) {
98
+ validateNotesRef(notesRef);
99
+ // Get list of objects that have notes
100
+ const objectsResult = executeGitCommand(['notes', `--ref=${notesRef}`, 'list'], {
101
+ ignoreErrors: true,
102
+ suppressStderr: true,
103
+ });
104
+ if (!objectsResult.success || !objectsResult.stdout) {
105
+ return [];
106
+ }
107
+ const notes = [];
108
+ // Parse "note_sha object_sha" pairs
109
+ for (const line of objectsResult.stdout.split('\n')) {
110
+ const [, objectSha] = line.split(/\s+/);
111
+ if (!objectSha)
112
+ continue;
113
+ // Read the note content
114
+ const content = readNote(notesRef, objectSha);
115
+ if (content !== null) {
116
+ notes.push([objectSha, content]);
117
+ }
118
+ }
119
+ return notes;
120
+ }
121
+ /**
122
+ * Check if a note exists
123
+ *
124
+ * @param notesRef - The notes reference
125
+ * @param object - The git object to check
126
+ * @returns true if note exists, false otherwise
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * if (hasNote('vibe-validate/validate', treeHash)) {
131
+ * console.log('Note exists');
132
+ * }
133
+ * ```
134
+ */
135
+ export function hasNote(notesRef, object) {
136
+ validateNotesRef(notesRef);
137
+ validateTreeHash(object);
138
+ return tryGitCommand(['notes', `--ref=${notesRef}`, 'show', object], {
139
+ suppressStderr: true,
140
+ });
141
+ }
142
+ /**
143
+ * List all refs under a notes namespace
144
+ *
145
+ * This is useful for finding all notes under a particular path,
146
+ * such as all run cache entries under refs/notes/vibe-validate/run/
147
+ *
148
+ * @param notesPath - The notes path (e.g., 'refs/notes/vibe-validate/run')
149
+ * @returns Array of full ref names
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const refs = listNotesRefs('refs/notes/vibe-validate/run');
154
+ * for (const ref of refs) {
155
+ * console.log('Found note ref:', ref);
156
+ * }
157
+ * ```
158
+ */
159
+ export function listNotesRefs(notesPath) {
160
+ // Normalize path
161
+ const fullPath = notesPath.startsWith('refs/') ? notesPath : `refs/notes/${notesPath}`;
162
+ const result = executeGitCommand(['for-each-ref', '--format=%(refname)', fullPath], { ignoreErrors: true, suppressStderr: true });
163
+ if (!result.success || !result.stdout) {
164
+ return [];
165
+ }
166
+ return result.stdout.split('\n').filter(Boolean);
167
+ }
168
+ /**
169
+ * Remove all notes refs under a namespace
170
+ *
171
+ * This is used for bulk cleanup operations, such as pruning all
172
+ * run cache entries under a tree hash.
173
+ *
174
+ * SECURITY: This function validates each ref before deletion to prevent
175
+ * accidental deletion of non-notes refs.
176
+ *
177
+ * @param notesPath - The notes path (e.g., 'refs/notes/vibe-validate/run/abc123')
178
+ * @returns Number of refs deleted
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * const deleted = removeNotesRefs('refs/notes/vibe-validate/run/abc123');
183
+ * console.log(`Deleted ${deleted} refs`);
184
+ * ```
185
+ */
186
+ export function removeNotesRefs(notesPath) {
187
+ // Normalize path
188
+ const fullPath = notesPath.startsWith('refs/') ? notesPath : `refs/notes/${notesPath}`;
189
+ // Security: Only allow deletion under refs/notes/vibe-validate/
190
+ if (!fullPath.startsWith('refs/notes/vibe-validate/')) {
191
+ throw new Error(`Refusing to delete refs outside vibe-validate namespace: ${fullPath}`);
192
+ }
193
+ // Get list of refs
194
+ const refs = listNotesRefs(fullPath);
195
+ let deleted = 0;
196
+ for (const ref of refs) {
197
+ // Double-check each ref is under the expected namespace
198
+ if (!ref.startsWith('refs/notes/vibe-validate/')) {
199
+ console.warn(`Skipping ref outside vibe-validate namespace: ${ref}`);
200
+ continue;
201
+ }
202
+ const success = tryGitCommand(['update-ref', '-d', ref], {
203
+ suppressStderr: true,
204
+ });
205
+ if (success) {
206
+ deleted++;
207
+ }
208
+ }
209
+ return deleted;
210
+ }
211
+ /**
212
+ * Check if a notes ref exists
213
+ *
214
+ * @param notesRef - The notes reference
215
+ * @returns true if the notes ref exists, false otherwise
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * if (hasNotesRef('vibe-validate/validate')) {
220
+ * console.log('Notes ref exists');
221
+ * }
222
+ * ```
223
+ */
224
+ export function hasNotesRef(notesRef) {
225
+ validateNotesRef(notesRef);
226
+ // Convert short form to full form if needed
227
+ const fullRef = notesRef.startsWith('refs/')
228
+ ? notesRef
229
+ : `refs/notes/${notesRef}`;
230
+ return tryGitCommand(['rev-parse', '--verify', fullRef], {
231
+ suppressStderr: true,
232
+ });
233
+ }
234
+ /**
235
+ * Get the commit SHA that a notes ref points to
236
+ *
237
+ * @param notesRef - The notes reference
238
+ * @returns The commit SHA, or null if ref doesn't exist
239
+ */
240
+ export function getNotesRefSha(notesRef) {
241
+ validateNotesRef(notesRef);
242
+ const fullRef = notesRef.startsWith('refs/')
243
+ ? notesRef
244
+ : `refs/notes/${notesRef}`;
245
+ const result = executeGitCommand(['rev-parse', '--verify', fullRef], {
246
+ ignoreErrors: true,
247
+ suppressStderr: true,
248
+ });
249
+ return result.success ? result.stdout : null;
250
+ }
251
+ //# sourceMappingURL=git-notes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-notes.js","sourceRoot":"","sources":["../src/git-notes.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -11,4 +11,9 @@ export { BranchSyncChecker, checkBranchSync, type SyncCheckResult, type SyncChec
11
11
  export { PostPRMergeCleanup, cleanupMergedBranches, type CleanupResult, type CleanupOptions } from './post-merge-cleanup.js';
12
12
  export { encodeRunCacheKey } from './cache-key.js';
13
13
  export { extractYamlContent, extractYamlWithPreamble } from './yaml-detection.js';
14
+ export { isGitRepository, getGitDir, getRepositoryRoot, getCurrentBranch, getHeadCommitSha, getHeadTreeSha, verifyRef, verifyRefOrThrow, hasNotesRef } from './git-commands.js';
15
+ export { executeGitCommand, execGitCommand, tryGitCommand, validateGitRef, validateNotesRef, validateTreeHash, type GitExecutionOptions, type GitExecutionResult } from './git-executor.js';
16
+ export { addNote, readNote, removeNote, listNotes, hasNote, listNotesRefs, removeNotesRefs, getNotesRefSha } from './git-notes.js';
17
+ export { getPartiallyStagedFiles } from './staging.js';
18
+ export { isCurrentBranchBehindTracking } from './tracking-branch.js';
14
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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;AAGjC,OAAO,EACL,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,qBAAqB,CAAC"}
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;AAGjC,OAAO,EACL,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,OAAO,EACP,QAAQ,EACR,UAAU,EACV,SAAS,EACT,OAAO,EACP,aAAa,EACb,eAAe,EACf,cAAc,EACf,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,uBAAuB,EACxB,MAAM,cAAc,CAAC;AAGtB,OAAO,EACL,6BAA6B,EAC9B,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -16,4 +16,14 @@ export { PostPRMergeCleanup, cleanupMergedBranches } from './post-merge-cleanup.
16
16
  export { encodeRunCacheKey } from './cache-key.js';
17
17
  // YAML output detection
18
18
  export { extractYamlContent, extractYamlWithPreamble } from './yaml-detection.js';
19
+ // Git command utilities (standardized rev-parse operations)
20
+ export { isGitRepository, getGitDir, getRepositoryRoot, getCurrentBranch, getHeadCommitSha, getHeadTreeSha, verifyRef, verifyRefOrThrow, hasNotesRef } from './git-commands.js';
21
+ // Secure git command execution (low-level - use high-level APIs when possible)
22
+ export { executeGitCommand, execGitCommand, tryGitCommand, validateGitRef, validateNotesRef, validateTreeHash } from './git-executor.js';
23
+ // Git notes operations (high-level abstraction)
24
+ export { addNote, readNote, removeNote, listNotes, hasNote, listNotesRefs, removeNotesRefs, getNotesRefSha } from './git-notes.js';
25
+ // Git staging detection (prevent partially staged files in pre-commit)
26
+ export { getPartiallyStagedFiles } from './staging.js';
27
+ // Git tracking branch detection (check if current branch is behind remote)
28
+ export { isCurrentBranchBehindTracking } from './tracking-branch.js';
19
29
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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;AAEjC,qCAAqC;AACrC,OAAO,EACL,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAExB,wBAAwB;AACxB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,qBAAqB,CAAC"}
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;AAEjC,qCAAqC;AACrC,OAAO,EACL,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAExB,wBAAwB;AACxB,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,4DAA4D;AAC5D,OAAO,EACL,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,WAAW,EACZ,MAAM,mBAAmB,CAAC;AAE3B,+EAA+E;AAC/E,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAGjB,MAAM,mBAAmB,CAAC;AAE3B,gDAAgD;AAChD,OAAO,EACL,OAAO,EACP,QAAQ,EACR,UAAU,EACV,SAAS,EACT,OAAO,EACP,aAAa,EACb,eAAe,EACf,cAAc,EACf,MAAM,gBAAgB,CAAC;AAExB,uEAAuE;AACvE,OAAO,EACL,uBAAuB,EACxB,MAAM,cAAc,CAAC;AAEtB,2EAA2E;AAC3E,OAAO,EACL,6BAA6B,EAC9B,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Git Staging Detection
3
+ *
4
+ * Detects partially staged files to prevent validation mismatches.
5
+ *
6
+ * ## The Problem
7
+ *
8
+ * If a file has BOTH staged and unstaged changes:
9
+ * 1. vibe-validate validates the FULL working tree state (staged + unstaged)
10
+ * 2. git commit only commits the STAGED portion
11
+ * 3. Result: Validated code != committed code
12
+ *
13
+ * ## Solution
14
+ *
15
+ * Pre-commit hook must detect and block partially staged files.
16
+ * Users can:
17
+ * - Stage all changes: `git add <file>`
18
+ * - Unstage all changes: `git restore --staged <file>`
19
+ * - Skip validation: `git commit --no-verify` (not recommended)
20
+ */
21
+ /**
22
+ * Get list of files with partially staged changes
23
+ *
24
+ * A file is "partially staged" if it has BOTH:
25
+ * - Changes in the staging area (git diff --cached)
26
+ * - Changes in the working tree (git diff)
27
+ *
28
+ * This indicates the user staged some changes but not others,
29
+ * which is incompatible with validation.
30
+ *
31
+ * @returns Array of file paths with partially staged changes, empty if none
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const files = getPartiallyStagedFiles();
36
+ * if (files.length > 0) {
37
+ * console.error('Partially staged files detected:', files);
38
+ * console.error('Stage all changes with: git add ' + files.join(' '));
39
+ * }
40
+ * ```
41
+ */
42
+ export declare function getPartiallyStagedFiles(): string[];
43
+ //# sourceMappingURL=staging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../src/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAWH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,EAAE,CAkClD"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Git Staging Detection
3
+ *
4
+ * Detects partially staged files to prevent validation mismatches.
5
+ *
6
+ * ## The Problem
7
+ *
8
+ * If a file has BOTH staged and unstaged changes:
9
+ * 1. vibe-validate validates the FULL working tree state (staged + unstaged)
10
+ * 2. git commit only commits the STAGED portion
11
+ * 3. Result: Validated code != committed code
12
+ *
13
+ * ## Solution
14
+ *
15
+ * Pre-commit hook must detect and block partially staged files.
16
+ * Users can:
17
+ * - Stage all changes: `git add <file>`
18
+ * - Unstage all changes: `git restore --staged <file>`
19
+ * - Skip validation: `git commit --no-verify` (not recommended)
20
+ */
21
+ import { execSync } from 'node:child_process';
22
+ const GIT_TIMEOUT = 30000;
23
+ const GIT_OPTIONS = {
24
+ encoding: 'utf8',
25
+ timeout: GIT_TIMEOUT,
26
+ stdio: ['pipe', 'pipe', 'ignore'],
27
+ };
28
+ /**
29
+ * Get list of files with partially staged changes
30
+ *
31
+ * A file is "partially staged" if it has BOTH:
32
+ * - Changes in the staging area (git diff --cached)
33
+ * - Changes in the working tree (git diff)
34
+ *
35
+ * This indicates the user staged some changes but not others,
36
+ * which is incompatible with validation.
37
+ *
38
+ * @returns Array of file paths with partially staged changes, empty if none
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const files = getPartiallyStagedFiles();
43
+ * if (files.length > 0) {
44
+ * console.error('Partially staged files detected:', files);
45
+ * console.error('Stage all changes with: git add ' + files.join(' '));
46
+ * }
47
+ * ```
48
+ */
49
+ export function getPartiallyStagedFiles() {
50
+ try {
51
+ // Get list of files with staged changes
52
+ const stagedOutput = execSync('git diff --name-only --cached', GIT_OPTIONS);
53
+ const stagedFiles = stagedOutput
54
+ .trim()
55
+ .split('\n')
56
+ .filter(Boolean);
57
+ // No staged files = no partially staged files
58
+ if (stagedFiles.length === 0) {
59
+ return [];
60
+ }
61
+ // Get list of files with unstaged changes
62
+ const unstagedOutput = execSync('git diff --name-only', GIT_OPTIONS);
63
+ const unstagedFiles = new Set(unstagedOutput
64
+ .trim()
65
+ .split('\n')
66
+ .filter(Boolean));
67
+ // Find intersection: files that appear in BOTH staged and unstaged
68
+ const partiallyStagedFiles = stagedFiles.filter((file) => unstagedFiles.has(file));
69
+ return partiallyStagedFiles;
70
+ }
71
+ catch {
72
+ // Not a git repository, or git command failed
73
+ // Return empty array - let pre-commit continue and fail elsewhere if needed
74
+ return [];
75
+ }
76
+ }
77
+ //# sourceMappingURL=staging.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staging.js","sourceRoot":"","sources":["../src/staging.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,MAAM,WAAW,GAAG;IAClB,QAAQ,EAAE,MAAe;IACzB,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAA+B;CAChE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,uBAAuB;IACrC,IAAI,CAAC;QACH,wCAAwC;QACxC,MAAM,YAAY,GAAG,QAAQ,CAAC,+BAA+B,EAAE,WAAW,CAAC,CAAC;QAC5E,MAAM,WAAW,GAAG,YAAY;aAC7B,IAAI,EAAE;aACN,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC,CAAC;QAEnB,8CAA8C;QAC9C,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,0CAA0C;QAC1C,MAAM,cAAc,GAAG,QAAQ,CAAC,sBAAsB,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,aAAa,GAAG,IAAI,GAAG,CAC3B,cAAc;aACX,IAAI,EAAE;aACN,KAAK,CAAC,IAAI,CAAC;aACX,MAAM,CAAC,OAAO,CAAC,CACnB,CAAC;QAEF,mEAAmE;QACnE,MAAM,oBAAoB,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACvD,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CACxB,CAAC;QAEF,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,8CAA8C;QAC9C,4EAA4E;QAC5E,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Git Tracking Branch Detection
3
+ *
4
+ * Detects if current branch is behind its remote tracking branch.
5
+ *
6
+ * ## The Problem
7
+ *
8
+ * If you're working on local `fix-issue-X` and someone else pushes to `origin/fix-issue-X`:
9
+ * 1. Your local branch is now behind the remote tracking branch
10
+ * 2. If you commit and push, you may create conflicts or lose their changes
11
+ * 3. You should pull/merge before committing
12
+ *
13
+ * ## Solution
14
+ *
15
+ * Pre-commit hook should detect and warn when current branch is behind its tracking branch.
16
+ */
17
+ /**
18
+ * Check if current branch is behind its remote tracking branch
19
+ *
20
+ * @returns Number of commits behind (0 = up to date), or null if no tracking branch
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const behindBy = isCurrentBranchBehindTracking();
25
+ * if (behindBy === null) {
26
+ * console.log('No remote tracking branch');
27
+ * } else if (behindBy > 0) {
28
+ * console.error(`Behind by ${behindBy} commit(s)`);
29
+ * console.error('Pull changes with: git pull');
30
+ * }
31
+ * ```
32
+ */
33
+ export declare function isCurrentBranchBehindTracking(): number | null;
34
+ //# sourceMappingURL=tracking-branch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracking-branch.d.ts","sourceRoot":"","sources":["../src/tracking-branch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,IAAI,MAAM,GAAG,IAAI,CAmC7D"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Git Tracking Branch Detection
3
+ *
4
+ * Detects if current branch is behind its remote tracking branch.
5
+ *
6
+ * ## The Problem
7
+ *
8
+ * If you're working on local `fix-issue-X` and someone else pushes to `origin/fix-issue-X`:
9
+ * 1. Your local branch is now behind the remote tracking branch
10
+ * 2. If you commit and push, you may create conflicts or lose their changes
11
+ * 3. You should pull/merge before committing
12
+ *
13
+ * ## Solution
14
+ *
15
+ * Pre-commit hook should detect and warn when current branch is behind its tracking branch.
16
+ */
17
+ import { execSync } from 'node:child_process';
18
+ const GIT_TIMEOUT = 30000;
19
+ const GIT_OPTIONS = {
20
+ encoding: 'utf8',
21
+ timeout: GIT_TIMEOUT,
22
+ stdio: ['pipe', 'pipe', 'ignore'],
23
+ };
24
+ /**
25
+ * Check if current branch is behind its remote tracking branch
26
+ *
27
+ * @returns Number of commits behind (0 = up to date), or null if no tracking branch
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const behindBy = isCurrentBranchBehindTracking();
32
+ * if (behindBy === null) {
33
+ * console.log('No remote tracking branch');
34
+ * } else if (behindBy > 0) {
35
+ * console.error(`Behind by ${behindBy} commit(s)`);
36
+ * console.error('Pull changes with: git pull');
37
+ * }
38
+ * ```
39
+ */
40
+ export function isCurrentBranchBehindTracking() {
41
+ try {
42
+ // Get the upstream tracking branch for current branch
43
+ // Example output: "origin/fix-issue-X"
44
+ // Throws error if no upstream configured
45
+ const trackingBranch = execSync('git rev-parse --abbrev-ref --symbolic-full-name @{u}', GIT_OPTIONS).trim();
46
+ if (!trackingBranch) {
47
+ // No tracking branch
48
+ return null;
49
+ }
50
+ // Count commits behind: HEAD..@{u}
51
+ // This shows commits in tracking branch that are not in HEAD
52
+ const behindOutput = execSync('git rev-list --count HEAD..@{u}', GIT_OPTIONS).trim();
53
+ const behindCount = Number.parseInt(behindOutput, 10);
54
+ // Return 0 if parsing failed (defensive)
55
+ return Number.isNaN(behindCount) ? 0 : behindCount;
56
+ }
57
+ catch (error) {
58
+ // Check if error is "no upstream configured" (expected for new branches)
59
+ if (error instanceof Error && error.message.includes('no upstream')) {
60
+ return null;
61
+ }
62
+ // Other errors (not in git repo, etc.) - return null
63
+ return null;
64
+ }
65
+ }
66
+ //# sourceMappingURL=tracking-branch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracking-branch.js","sourceRoot":"","sources":["../src/tracking-branch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,WAAW,GAAG,KAAK,CAAC;AAC1B,MAAM,WAAW,GAAG;IAClB,QAAQ,EAAE,MAAe;IACzB,OAAO,EAAE,WAAW;IACpB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAA+B;CAChE,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,6BAA6B;IAC3C,IAAI,CAAC;QACH,sDAAsD;QACtD,uCAAuC;QACvC,yCAAyC;QACzC,MAAM,cAAc,GAAG,QAAQ,CAC7B,sDAAsD,EACtD,WAAW,CACZ,CAAC,IAAI,EAAE,CAAC;QAET,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,qBAAqB;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,mCAAmC;QACnC,6DAA6D;QAC7D,MAAM,YAAY,GAAG,QAAQ,CAC3B,iCAAiC,EACjC,WAAW,CACZ,CAAC,IAAI,EAAE,CAAC;QAET,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAEtD,yCAAyC;QACzC,OAAO,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC;IACrD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yEAAyE;QACzE,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACpE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,qDAAqD;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"yaml-detection.d.ts","sourceRoot":"","sources":["../src/yaml-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA+BH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWjG"}
1
+ {"version":3,"file":"yaml-detection.d.ts","sourceRoot":"","sources":["../src/yaml-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgCH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGhE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWjG"}
@@ -43,6 +43,7 @@
43
43
  * - `"---\ntitle: Post\n---\nContent"` → matches, captures `"---\ntitle: Post\n"` (stops at trailing `---`)
44
44
  * - `"no yaml"` → no match
45
45
  */
46
+ // eslint-disable-next-line security/detect-unsafe-regex -- Safe: Parses controlled YAML frontmatter from command output (not user input), no ReDoS risk
46
47
  const YAML_OUTPUT_PATTERN = /(?:^|\r?\n)(---\r?\n(?:(?!---(?:\r?\n|$))[\s\S])*)/;
47
48
  /**
48
49
  * Extract YAML output content from command output
@@ -1 +1 @@
1
- {"version":3,"file":"yaml-detection.js","sourceRoot":"","sources":["../src/yaml-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,mBAAmB,GAAG,oDAAoD,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC;IAE5D,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"yaml-detection.js","sourceRoot":"","sources":["../src/yaml-detection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wJAAwJ;AACxJ,MAAM,mBAAmB,GAAG,oDAAoD,CAAC;AAEjF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,IAAI,EAAE,CAAC;IAE5D,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AACzC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-validate/git",
3
- "version": "0.17.0-rc4",
3
+ "version": "0.17.0",
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",