@vibe-validate/cli 0.12.1 → 0.13.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.
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOzC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6CtD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAiK9C"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUzC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkLtD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAiK9C"}
@@ -6,6 +6,9 @@
6
6
  import { loadConfig } from '../utils/config-loader.js';
7
7
  import { detectContext } from '../utils/context-detector.js';
8
8
  import { runValidateWorkflow } from '../utils/validate-workflow.js';
9
+ import { acquireLock, releaseLock, checkLock, waitForLock } from '../utils/pid-lock.js';
10
+ import { detectProjectId } from '../utils/project-id.js';
11
+ import { getGitTreeHash } from '@vibe-validate/git';
9
12
  import chalk from 'chalk';
10
13
  export function validateCommand(program) {
11
14
  program
@@ -15,9 +18,26 @@ export function validateCommand(program) {
15
18
  .option('-v, --verbose', 'Show detailed progress and output')
16
19
  .option('-y, --yaml', 'Output validation result as YAML to stdout')
17
20
  .option('-c, --check', 'Check if validation has already passed (do not run)')
21
+ .option('--no-lock', 'Allow concurrent validation runs (disables single-instance mode)')
22
+ .option('--no-wait', 'Exit immediately if validation is already running (for background hooks)')
23
+ .option('--wait-timeout <seconds>', 'Maximum time to wait for running validation (default: 300)', '300')
18
24
  .action(async (options) => {
25
+ let lockFile = null;
19
26
  try {
20
- // Load configuration
27
+ // Normalize conflicting options
28
+ // When using --check (just checking state, not running validation):
29
+ // - lock is meaningless (no validation process to lock)
30
+ // - force is meaningless (no validation to force)
31
+ if (options.check) {
32
+ options.lock = false;
33
+ options.force = false;
34
+ }
35
+ // Default behavior: lock is enabled (single-instance mode)
36
+ // Users can opt out with --no-lock for concurrent runs
37
+ if (options.lock === undefined) {
38
+ options.lock = true;
39
+ }
40
+ // Load configuration first (needed for lock config)
21
41
  const config = await loadConfig();
22
42
  if (!config) {
23
43
  console.error(chalk.red('❌ No configuration found'));
@@ -25,6 +45,94 @@ export function validateCommand(program) {
25
45
  }
26
46
  // Detect context (Claude Code, CI, etc.)
27
47
  const context = detectContext();
48
+ // Determine lock options from config
49
+ const lockConfig = config.locking ?? { enabled: true, concurrencyScope: 'directory' };
50
+ // If config disables locking, override CLI flag
51
+ if (!lockConfig.enabled) {
52
+ options.lock = false;
53
+ }
54
+ let lockOptions = {};
55
+ if (lockConfig.concurrencyScope === 'project') {
56
+ // Project-scoped locking - need projectId
57
+ const projectId = lockConfig.projectId ?? detectProjectId();
58
+ if (!projectId) {
59
+ console.error(chalk.red('❌ ERROR: concurrencyScope=project but projectId cannot be detected'));
60
+ console.error(chalk.yellow('Solutions:'));
61
+ console.error(' 1. Add locking.projectId to vibe-validate.config.yaml');
62
+ console.error(' 2. Ensure git remote is configured');
63
+ console.error(' 3. Ensure package.json has name field');
64
+ process.exit(1);
65
+ }
66
+ lockOptions = { scope: 'project', projectId };
67
+ }
68
+ else {
69
+ // Directory-scoped locking (default)
70
+ lockOptions = { scope: 'directory' };
71
+ }
72
+ // Default behavior: wait is enabled (wait for running validation)
73
+ // Users can opt out with --no-wait for background hooks
74
+ const shouldWait = options.wait !== false;
75
+ // Handle wait mode (default: wait for running validation to complete)
76
+ if (shouldWait) {
77
+ const cwd = process.cwd();
78
+ const existingLock = await checkLock(cwd, lockOptions);
79
+ if (existingLock) {
80
+ const waitTimeout = parseInt(options.waitTimeout, 10) || 300;
81
+ if (!options.yaml) {
82
+ console.log(chalk.yellow('⏳ Waiting for running validation to complete...'));
83
+ console.log(` PID: ${existingLock.pid}`);
84
+ console.log(` Started: ${new Date(existingLock.startTime).toLocaleTimeString()}`);
85
+ console.log(` Timeout: ${waitTimeout}s`);
86
+ }
87
+ const waitResult = await waitForLock(cwd, waitTimeout, 1000, lockOptions);
88
+ if (waitResult.timedOut) {
89
+ if (!options.yaml) {
90
+ console.log(chalk.yellow('⏱️ Wait timed out, proceeding with validation'));
91
+ }
92
+ // Continue with normal validation flow
93
+ }
94
+ else if (!options.yaml) {
95
+ console.log(chalk.green('✓ Background validation completed'));
96
+ // Continue to check cache/run validation
97
+ }
98
+ }
99
+ // If no lock exists, proceed normally
100
+ }
101
+ // Handle lock mode (single-instance execution)
102
+ if (options.lock) {
103
+ const cwd = process.cwd();
104
+ const treeHash = await getGitTreeHash();
105
+ const lockResult = await acquireLock(cwd, treeHash, lockOptions);
106
+ if (!lockResult.acquired && lockResult.existingLock) {
107
+ // Another validation is already running
108
+ // If --no-wait specified, exit immediately (for background hooks)
109
+ if (!shouldWait) {
110
+ const existing = lockResult.existingLock;
111
+ const isCurrentHash = existing.treeHash === treeHash;
112
+ const hashStatus = isCurrentHash
113
+ ? 'same as current'
114
+ : `stale - current is ${treeHash.substring(0, 7)}`;
115
+ const elapsed = Math.floor((Date.now() - new Date(existing.startTime).getTime()) / 1000);
116
+ const elapsedStr = elapsed < 60
117
+ ? `${elapsed} seconds ago`
118
+ : `${Math.floor(elapsed / 60)} minutes ago`;
119
+ if (!options.yaml) {
120
+ console.log(chalk.yellow('⚠️ Validation already running'));
121
+ console.log(` Directory: ${existing.directory}`);
122
+ console.log(` Tree Hash: ${existing.treeHash.substring(0, 7)} (${hashStatus})`);
123
+ console.log(` PID: ${existing.pid}`);
124
+ console.log(` Started: ${elapsedStr}`);
125
+ }
126
+ process.exit(0); // Exit 0 to not trigger errors in hooks
127
+ }
128
+ // If wait is enabled (default), the wait logic above already handled it
129
+ // Just don't try to acquire lock again
130
+ }
131
+ else {
132
+ // Lock acquired successfully
133
+ lockFile = lockResult.lockFile;
134
+ }
135
+ }
28
136
  // Run shared validation workflow
29
137
  const result = await runValidateWorkflow(config, {
30
138
  force: options.force,
@@ -49,6 +157,12 @@ export function validateCommand(program) {
49
157
  // Error already logged by runValidateWorkflow
50
158
  process.exit(1);
51
159
  }
160
+ finally {
161
+ // Always release lock when done
162
+ if (lockFile) {
163
+ await releaseLock(lockFile);
164
+ }
165
+ }
52
166
  });
53
167
  }
54
168
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO;SACJ,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,2CAA2C,CAAC;SACxD,MAAM,CAAC,aAAa,EAAE,yCAAyC,CAAC;SAChE,MAAM,CAAC,eAAe,EAAE,mCAAmC,CAAC;SAC5D,MAAM,CAAC,YAAY,EAAE,4CAA4C,CAAC;SAClE,MAAM,CAAC,aAAa,EAAE,qDAAqD,CAAC;SAC5E,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,IAAI,CAAC;YACH,qBAAqB;YACrB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;YAClC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC;gBACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YAED,yCAAyC;YACzC,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;YAEhC,iCAAiC;YACjC,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,MAAM,EAAE;gBAC/C,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,OAAO;aACR,CAAC,CAAC;YAEH,gDAAgD;YAChD,4EAA4E;YAC5E,MAAM,eAAe,GAAG,MAAqD,CAAC;YAC9E,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC;gBAChC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACtC,CAAC;YACD,qEAAqE;QACvE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,6CAA6C;YAC7C,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;gBACvE,MAAM,KAAK,CAAC;YACd,CAAC;YACD,8CAA8C;YAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB;IACrC,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Jb,CAAC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"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"}
@@ -52,9 +52,6 @@ export interface CheckStatus {
52
52
  }
53
53
  /**
54
54
  * Validation result extracted from CI logs
55
- *
56
- * Note: This is NOT about the deprecated .vibe-validate-state.yaml file.
57
- * This represents the validation result that's displayed in CI logs.
58
55
  */
59
56
  export interface ValidationResultContents {
60
57
  /** Whether validation passed */
@@ -1 +1 @@
1
- {"version":3,"file":"ci-provider.d.ts","sourceRoot":"","sources":["../../src/services/ci-provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ci-provider.d.ts","sourceRoot":"","sources":["../../src/services/ci-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * PID-based locking for single-instance validation execution
3
+ *
4
+ * Prevents concurrent validation runs using PID file mechanism.
5
+ * Cross-platform (Node.js), works on Windows, macOS, Linux.
6
+ */
7
+ /**
8
+ * Lock file information
9
+ */
10
+ export interface LockInfo {
11
+ /** Process ID holding the lock */
12
+ pid: number;
13
+ /** Project directory path */
14
+ directory: string;
15
+ /** Git tree hash at time of lock acquisition */
16
+ treeHash: string;
17
+ /** ISO timestamp when lock was acquired */
18
+ startTime: string;
19
+ }
20
+ /**
21
+ * Result of lock acquisition attempt
22
+ */
23
+ export interface LockResult {
24
+ /** Whether lock was successfully acquired */
25
+ acquired: boolean;
26
+ /** Path to lock file */
27
+ lockFile: string;
28
+ /** Information about existing lock (if acquisition failed) */
29
+ existingLock?: LockInfo;
30
+ }
31
+ /**
32
+ * Lock scope options
33
+ */
34
+ export interface LockOptions {
35
+ /**
36
+ * Concurrency scope for lock files
37
+ * - 'directory': Each working directory has its own lock (default, allows parallel worktrees)
38
+ * - 'project': All directories for the same project share a lock (prevents port/DB conflicts)
39
+ */
40
+ scope?: 'directory' | 'project';
41
+ /**
42
+ * Project identifier for project-scoped locking
43
+ * Used when scope='project' to generate lock filename
44
+ */
45
+ projectId?: string;
46
+ }
47
+ /**
48
+ * Acquire validation lock
49
+ *
50
+ * Attempts to create a lock file for single-instance execution.
51
+ * If a lock already exists, checks if the process is still running.
52
+ * Stale locks (dead processes) are automatically cleaned up.
53
+ *
54
+ * @param directory - Project directory to lock
55
+ * @param treeHash - Current git tree hash
56
+ * @param options - Lock scope options
57
+ * @returns Lock acquisition result
58
+ */
59
+ export declare function acquireLock(directory: string, treeHash: string, options?: LockOptions): Promise<LockResult>;
60
+ /**
61
+ * Release validation lock
62
+ *
63
+ * Removes the lock file to allow other processes to run.
64
+ * Safe to call even if lock file doesn't exist.
65
+ *
66
+ * @param lockFile - Path to lock file to release
67
+ */
68
+ export declare function releaseLock(lockFile: string): Promise<void>;
69
+ /**
70
+ * Check current lock status
71
+ *
72
+ * Returns information about existing lock, or null if no lock exists.
73
+ * Automatically cleans up stale locks.
74
+ *
75
+ * @param directory - Project directory to check
76
+ * @param options - Lock scope options
77
+ * @returns Lock information or null
78
+ */
79
+ export declare function checkLock(directory: string, options?: LockOptions): Promise<LockInfo | null>;
80
+ /**
81
+ * Wait for lock to be released
82
+ *
83
+ * Polls the lock file until it's released or timeout is reached.
84
+ * Useful for pre-commit hooks that want to wait for background
85
+ * validation to complete before proceeding.
86
+ *
87
+ * @param directory - Project directory to check
88
+ * @param timeoutSeconds - Maximum time to wait (default: 300 seconds / 5 minutes)
89
+ * @param pollIntervalMs - How often to check lock status (default: 1000ms)
90
+ * @param options - Lock scope options
91
+ * @returns Lock info when released, or null if timeout
92
+ */
93
+ export declare function waitForLock(directory: string, timeoutSeconds?: number, pollIntervalMs?: number, options?: LockOptions): Promise<{
94
+ released: boolean;
95
+ timedOut: boolean;
96
+ finalLock: LockInfo | null;
97
+ }>;
98
+ //# sourceMappingURL=pid-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pid-lock.d.ts","sourceRoot":"","sources":["../../src/utils/pid-lock.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAC;IAClB,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,QAAQ,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAChC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8DD;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC,CAyCrB;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjE;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAuBtG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,cAAc,GAAE,MAAY,EAC5B,cAAc,GAAE,MAAa,EAC7B,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,CAAC,CA6B/E"}
@@ -0,0 +1,198 @@
1
+ /**
2
+ * PID-based locking for single-instance validation execution
3
+ *
4
+ * Prevents concurrent validation runs using PID file mechanism.
5
+ * Cross-platform (Node.js), works on Windows, macOS, Linux.
6
+ */
7
+ import { readFileSync, writeFileSync, unlinkSync, existsSync } from 'fs';
8
+ import { tmpdir } from 'os';
9
+ import { join } from 'path';
10
+ /**
11
+ * Encode directory path for use in lock file name
12
+ *
13
+ * Replaces path separators and special characters to create
14
+ * a human-readable lock file name.
15
+ *
16
+ * Examples:
17
+ * - /Users/jeff/project → _Users_jeff_project.lock
18
+ * - C:\Users\jeff\project → C-_Users_jeff_project.lock
19
+ *
20
+ * @param directory - Project directory path
21
+ * @returns Encoded path suitable for filename
22
+ */
23
+ function encodeDirectoryPath(directory) {
24
+ return directory
25
+ .replace(/^([A-Za-z]):/, '$1-') // Windows drive letter (C: → C-)
26
+ .replace(/\\/g, '_') // Windows backslashes
27
+ .replace(/\//g, '_'); // Unix forward slashes
28
+ }
29
+ /**
30
+ * Get lock file path based on scope
31
+ *
32
+ * @param directory - Project directory
33
+ * @param options - Lock scope options
34
+ * @returns Lock file path
35
+ */
36
+ function getLockFilePath(directory, options = {}) {
37
+ const scope = options.scope ?? 'directory';
38
+ if (scope === 'project') {
39
+ if (!options.projectId) {
40
+ throw new Error('projectId is required when scope is "project"');
41
+ }
42
+ // Project-scoped: /tmp/vibe-validate-project-{projectId}.lock
43
+ return join(tmpdir(), `vibe-validate-project-${options.projectId}.lock`);
44
+ }
45
+ // Directory-scoped (default): /tmp/vibe-validate-{encoded-dir}.lock
46
+ const encoded = encodeDirectoryPath(directory);
47
+ return join(tmpdir(), `vibe-validate-${encoded}.lock`);
48
+ }
49
+ /**
50
+ * Check if a process is running
51
+ *
52
+ * Cross-platform process check using Node.js process.kill(pid, 0)
53
+ * which doesn't actually kill the process, just tests if it exists.
54
+ */
55
+ function isProcessRunning(pid) {
56
+ try {
57
+ // Signal 0 tests for process existence without killing it
58
+ process.kill(pid, 0);
59
+ return true;
60
+ }
61
+ catch (_err) {
62
+ // ESRCH = no such process
63
+ return false;
64
+ }
65
+ }
66
+ /**
67
+ * Acquire validation lock
68
+ *
69
+ * Attempts to create a lock file for single-instance execution.
70
+ * If a lock already exists, checks if the process is still running.
71
+ * Stale locks (dead processes) are automatically cleaned up.
72
+ *
73
+ * @param directory - Project directory to lock
74
+ * @param treeHash - Current git tree hash
75
+ * @param options - Lock scope options
76
+ * @returns Lock acquisition result
77
+ */
78
+ export async function acquireLock(directory, treeHash, options = {}) {
79
+ const lockFile = getLockFilePath(directory, options);
80
+ // Check for existing lock
81
+ if (existsSync(lockFile)) {
82
+ try {
83
+ const lockData = JSON.parse(readFileSync(lockFile, 'utf-8'));
84
+ // Verify process is actually running
85
+ if (isProcessRunning(lockData.pid)) {
86
+ // Lock is valid - another process is running
87
+ return {
88
+ acquired: false,
89
+ lockFile,
90
+ existingLock: lockData,
91
+ };
92
+ }
93
+ // Stale lock - process no longer exists
94
+ // Clean it up and proceed
95
+ unlinkSync(lockFile);
96
+ }
97
+ catch (_err) {
98
+ // Corrupted lock file - remove and proceed
99
+ unlinkSync(lockFile);
100
+ }
101
+ }
102
+ // Acquire lock
103
+ const lockInfo = {
104
+ pid: process.pid,
105
+ directory,
106
+ treeHash,
107
+ startTime: new Date().toISOString(),
108
+ };
109
+ writeFileSync(lockFile, JSON.stringify(lockInfo, null, 2));
110
+ return {
111
+ acquired: true,
112
+ lockFile,
113
+ };
114
+ }
115
+ /**
116
+ * Release validation lock
117
+ *
118
+ * Removes the lock file to allow other processes to run.
119
+ * Safe to call even if lock file doesn't exist.
120
+ *
121
+ * @param lockFile - Path to lock file to release
122
+ */
123
+ export async function releaseLock(lockFile) {
124
+ if (existsSync(lockFile)) {
125
+ unlinkSync(lockFile);
126
+ }
127
+ }
128
+ /**
129
+ * Check current lock status
130
+ *
131
+ * Returns information about existing lock, or null if no lock exists.
132
+ * Automatically cleans up stale locks.
133
+ *
134
+ * @param directory - Project directory to check
135
+ * @param options - Lock scope options
136
+ * @returns Lock information or null
137
+ */
138
+ export async function checkLock(directory, options = {}) {
139
+ const lockFile = getLockFilePath(directory, options);
140
+ if (!existsSync(lockFile)) {
141
+ return null;
142
+ }
143
+ try {
144
+ const lockData = JSON.parse(readFileSync(lockFile, 'utf-8'));
145
+ // Verify process is still running
146
+ if (isProcessRunning(lockData.pid)) {
147
+ return lockData;
148
+ }
149
+ // Stale lock - clean up
150
+ unlinkSync(lockFile);
151
+ return null;
152
+ }
153
+ catch (_err) {
154
+ // Corrupted lock file - clean up
155
+ unlinkSync(lockFile);
156
+ return null;
157
+ }
158
+ }
159
+ /**
160
+ * Wait for lock to be released
161
+ *
162
+ * Polls the lock file until it's released or timeout is reached.
163
+ * Useful for pre-commit hooks that want to wait for background
164
+ * validation to complete before proceeding.
165
+ *
166
+ * @param directory - Project directory to check
167
+ * @param timeoutSeconds - Maximum time to wait (default: 300 seconds / 5 minutes)
168
+ * @param pollIntervalMs - How often to check lock status (default: 1000ms)
169
+ * @param options - Lock scope options
170
+ * @returns Lock info when released, or null if timeout
171
+ */
172
+ export async function waitForLock(directory, timeoutSeconds = 300, pollIntervalMs = 1000, options = {}) {
173
+ const startTime = Date.now();
174
+ const timeoutMs = timeoutSeconds * 1000;
175
+ while (true) {
176
+ const lock = await checkLock(directory, options);
177
+ // Lock released (or never existed)
178
+ if (!lock) {
179
+ return {
180
+ released: true,
181
+ timedOut: false,
182
+ finalLock: null,
183
+ };
184
+ }
185
+ // Check timeout
186
+ const elapsed = Date.now() - startTime;
187
+ if (elapsed >= timeoutMs) {
188
+ return {
189
+ released: false,
190
+ timedOut: true,
191
+ finalLock: lock,
192
+ };
193
+ }
194
+ // Wait before next poll
195
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
196
+ }
197
+ }
198
+ //# sourceMappingURL=pid-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pid-lock.js","sourceRoot":"","sources":["../../src/utils/pid-lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Project ID Detection
3
+ *
4
+ * Detects a unique project identifier for lock scoping.
5
+ * Used when locking.concurrencyScope=project to ensure
6
+ * multiple directories (worktrees, clones) share the same lock.
7
+ */
8
+ /**
9
+ * Get project ID from git remote
10
+ *
11
+ * Tries to extract project name from git remote URL.
12
+ * Works for GitHub, GitLab, Bitbucket, and other git hosts.
13
+ *
14
+ * @param cwd - Directory to check (defaults to process.cwd())
15
+ * @returns Project ID or null
16
+ */
17
+ export declare function getProjectIdFromGit(cwd?: string): string | null;
18
+ /**
19
+ * Get project ID from package.json
20
+ *
21
+ * Extracts the "name" field from package.json.
22
+ * Removes scope prefix (e.g., @scope/package → package).
23
+ *
24
+ * @param cwd - Directory to check (defaults to process.cwd())
25
+ * @returns Project ID or null
26
+ */
27
+ export declare function getProjectIdFromPackageJson(cwd?: string): string | null;
28
+ /**
29
+ * Detect project ID with fallback chain
30
+ *
31
+ * Detection priority:
32
+ * 1. Git remote URL (e.g., github.com/user/repo → repo)
33
+ * 2. package.json name field (removes scope prefix)
34
+ * 3. null (no detection possible)
35
+ *
36
+ * @param cwd - Directory to check (defaults to process.cwd())
37
+ * @returns Detected project ID or null
38
+ */
39
+ export declare function detectProjectId(cwd?: string): string | null;
40
+ //# sourceMappingURL=project-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-id.d.ts","sourceRoot":"","sources":["../../src/utils/project-id.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA6BH;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAkB9E;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAoBtF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,GAAG,IAAI,CAe1E"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Project ID Detection
3
+ *
4
+ * Detects a unique project identifier for lock scoping.
5
+ * Used when locking.concurrencyScope=project to ensure
6
+ * multiple directories (worktrees, clones) share the same lock.
7
+ */
8
+ import { execSync } from 'child_process';
9
+ import { readFileSync, existsSync } from 'fs';
10
+ import { join } from 'path';
11
+ /**
12
+ * Extract project name from git remote URL
13
+ *
14
+ * Supports common formats:
15
+ * - https://github.com/user/repo.git → repo
16
+ * - git@github.com:user/repo.git → repo
17
+ * - https://github.com/user/repo → repo
18
+ *
19
+ * @param remoteUrl - Git remote URL
20
+ * @returns Project name or null
21
+ */
22
+ function extractProjectFromGitUrl(remoteUrl) {
23
+ // Remove .git suffix if present
24
+ const cleaned = remoteUrl.replace(/\.git$/, '');
25
+ // Extract repo name from various formats
26
+ // HTTPS: https://github.com/user/repo
27
+ // SSH: git@github.com:user/repo
28
+ const match = cleaned.match(/[/:]([^/]+)$/);
29
+ return match ? match[1] : null;
30
+ }
31
+ /**
32
+ * Get project ID from git remote
33
+ *
34
+ * Tries to extract project name from git remote URL.
35
+ * Works for GitHub, GitLab, Bitbucket, and other git hosts.
36
+ *
37
+ * @param cwd - Directory to check (defaults to process.cwd())
38
+ * @returns Project ID or null
39
+ */
40
+ export function getProjectIdFromGit(cwd = process.cwd()) {
41
+ try {
42
+ // Get remote URL for origin
43
+ const remoteUrl = execSync('git config --get remote.origin.url', {
44
+ cwd,
45
+ encoding: 'utf8',
46
+ stdio: ['pipe', 'pipe', 'pipe'],
47
+ }).trim();
48
+ if (!remoteUrl) {
49
+ return null;
50
+ }
51
+ return extractProjectFromGitUrl(remoteUrl);
52
+ }
53
+ catch (_err) {
54
+ // Not a git repo or no remote configured
55
+ return null;
56
+ }
57
+ }
58
+ /**
59
+ * Get project ID from package.json
60
+ *
61
+ * Extracts the "name" field from package.json.
62
+ * Removes scope prefix (e.g., @scope/package → package).
63
+ *
64
+ * @param cwd - Directory to check (defaults to process.cwd())
65
+ * @returns Project ID or null
66
+ */
67
+ export function getProjectIdFromPackageJson(cwd = process.cwd()) {
68
+ try {
69
+ const packageJsonPath = join(cwd, 'package.json');
70
+ if (!existsSync(packageJsonPath)) {
71
+ return null;
72
+ }
73
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
74
+ const name = packageJson.name;
75
+ if (!name || typeof name !== 'string') {
76
+ return null;
77
+ }
78
+ // Remove scope prefix (@scope/package → package)
79
+ return name.replace(/^@[^/]+\//, '');
80
+ }
81
+ catch (_err) {
82
+ return null;
83
+ }
84
+ }
85
+ /**
86
+ * Detect project ID with fallback chain
87
+ *
88
+ * Detection priority:
89
+ * 1. Git remote URL (e.g., github.com/user/repo → repo)
90
+ * 2. package.json name field (removes scope prefix)
91
+ * 3. null (no detection possible)
92
+ *
93
+ * @param cwd - Directory to check (defaults to process.cwd())
94
+ * @returns Detected project ID or null
95
+ */
96
+ export function detectProjectId(cwd = process.cwd()) {
97
+ // Try git remote first (most reliable for worktrees/clones)
98
+ const gitProject = getProjectIdFromGit(cwd);
99
+ if (gitProject) {
100
+ return gitProject;
101
+ }
102
+ // Fall back to package.json
103
+ const packageProject = getProjectIdFromPackageJson(cwd);
104
+ if (packageProject) {
105
+ return packageProject;
106
+ }
107
+ // No detection possible
108
+ return null;
109
+ }
110
+ //# sourceMappingURL=project-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-id.js","sourceRoot":"","sources":["../../src/utils/project-id.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;;;;;;;;GAUG;AACH,SAAS,wBAAwB,CAAC,SAAiB;IACjD,gCAAgC;IAChC,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEhD,yCAAyC;IACzC,sCAAsC;IACtC,gCAAgC;IAChC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAE5C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC7D,IAAI,CAAC;QACH,4BAA4B;QAC5B,MAAM,SAAS,GAAG,QAAQ,CAAC,oCAAoC,EAAE;YAC/D,GAAG;YACH,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEV,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,wBAAwB,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,IAAI,EAAE,CAAC;QACd,yCAAyC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACrE,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAElD,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;QACtE,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC;QAE9B,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,iDAAiD;QACjD,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,IAAI,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IACzD,4DAA4D;IAC5D,MAAM,UAAU,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,4BAA4B;IAC5B,MAAM,cAAc,GAAG,2BAA2B,CAAC,GAAG,CAAC,CAAC;IACxD,IAAI,cAAc,EAAE,CAAC;QACnB,OAAO,cAAc,CAAC;IACxB,CAAC;IAED,wBAAwB;IACxB,OAAO,IAAI,CAAC;AACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-validate/cli",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "Command-line interface for vibe-validate validation framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -59,11 +59,11 @@
59
59
  "yaml": "^2.6.1",
60
60
  "zod": "^3.24.1",
61
61
  "zod-to-json-schema": "^3.24.6",
62
- "@vibe-validate/config": "0.12.1",
63
- "@vibe-validate/git": "0.12.1",
64
- "@vibe-validate/core": "0.12.1",
65
- "@vibe-validate/extractors": "0.12.1",
66
- "@vibe-validate/history": "0.12.1"
62
+ "@vibe-validate/config": "0.13.0",
63
+ "@vibe-validate/extractors": "0.13.0",
64
+ "@vibe-validate/core": "0.13.0",
65
+ "@vibe-validate/git": "0.13.0",
66
+ "@vibe-validate/history": "0.13.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@types/node": "^20.14.8",