awesome-slash 2.5.0 → 2.5.1
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.
- package/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +35 -0
- package/README.md +23 -8
- package/lib/platform/state-dir.js +122 -0
- package/lib/sources/source-cache.js +26 -11
- package/lib/state/workflow-state.js +18 -13
- package/mcp-server/index.js +7 -11
- package/package.json +1 -1
- package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop-around/lib/patterns/slop-patterns.js +2 -3
- package/plugins/deslop-around/lib/platform/detect-platform.js +44 -287
- package/plugins/deslop-around/lib/platform/state-dir.js +122 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +11 -88
- package/plugins/deslop-around/lib/schemas/validator.js +44 -2
- package/plugins/deslop-around/lib/sources/source-cache.js +26 -11
- package/plugins/deslop-around/lib/state/workflow-state.js +18 -13
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/lib/patterns/slop-patterns.js +2 -3
- package/plugins/next-task/lib/platform/detect-platform.js +44 -287
- package/plugins/next-task/lib/platform/state-dir.js +122 -0
- package/plugins/next-task/lib/platform/verify-tools.js +11 -88
- package/plugins/next-task/lib/schemas/validator.js +44 -2
- package/plugins/next-task/lib/sources/source-cache.js +26 -11
- package/plugins/next-task/lib/state/workflow-state.js +18 -13
- package/plugins/project-review/.claude-plugin/plugin.json +1 -1
- package/plugins/project-review/lib/patterns/slop-patterns.js +2 -3
- package/plugins/project-review/lib/platform/detect-platform.js +44 -287
- package/plugins/project-review/lib/platform/state-dir.js +122 -0
- package/plugins/project-review/lib/platform/verify-tools.js +11 -88
- package/plugins/project-review/lib/schemas/validator.js +44 -2
- package/plugins/project-review/lib/sources/source-cache.js +26 -11
- package/plugins/project-review/lib/state/workflow-state.js +18 -13
- package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/lib/patterns/slop-patterns.js +2 -3
- package/plugins/ship/lib/platform/detect-platform.js +44 -287
- package/plugins/ship/lib/platform/state-dir.js +122 -0
- package/plugins/ship/lib/platform/verify-tools.js +11 -88
- package/plugins/ship/lib/schemas/validator.js +44 -2
- package/plugins/ship/lib/sources/source-cache.js +26 -11
- package/plugins/ship/lib/state/workflow-state.js +18 -13
- package/scripts/install/codex.sh +216 -72
- package/scripts/install/opencode.sh +197 -21
|
@@ -12,14 +12,13 @@
|
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const path = require('path');
|
|
15
|
-
const {
|
|
15
|
+
const { exec } = require('child_process');
|
|
16
16
|
const { promisify } = require('util');
|
|
17
17
|
|
|
18
18
|
const execAsync = promisify(exec);
|
|
19
19
|
const fsPromises = fs.promises;
|
|
20
20
|
|
|
21
21
|
// Import shared utilities
|
|
22
|
-
const { warnDeprecation, _resetDeprecationWarnings } = require('../utils/deprecation');
|
|
23
22
|
const { CacheManager } = require('../utils/cache-manager');
|
|
24
23
|
const {
|
|
25
24
|
CI_CONFIGS,
|
|
@@ -48,7 +47,6 @@ function safeJSONParse(content, filename = 'unknown') {
|
|
|
48
47
|
return null;
|
|
49
48
|
}
|
|
50
49
|
if (content.length > MAX_JSON_SIZE_BYTES) {
|
|
51
|
-
// File too large - skip parsing to prevent DoS
|
|
52
50
|
return null;
|
|
53
51
|
}
|
|
54
52
|
try {
|
|
@@ -85,48 +83,29 @@ function withTimeout(promise, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS, operation =
|
|
|
85
83
|
* @param {number} timeoutMs - Timeout in milliseconds
|
|
86
84
|
* @returns {Promise<{stdout: string, stderr: string}>}
|
|
87
85
|
*/
|
|
88
|
-
async function
|
|
86
|
+
async function execWithTimeout(cmd, options = {}, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS) {
|
|
89
87
|
return withTimeout(execAsync(cmd, options), timeoutMs, `exec: ${cmd.substring(0, 50)}`);
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
// Maximum
|
|
90
|
+
// Maximum cached file size constant
|
|
93
91
|
const MAX_CACHED_FILE_SIZE = 64 * 1024; // 64KB max per cached file
|
|
94
92
|
|
|
95
93
|
// Cache instances using CacheManager abstraction
|
|
96
|
-
const _detectionCache = new CacheManager({ maxSize: 1, ttl: 60000 });
|
|
94
|
+
const _detectionCache = new CacheManager({ maxSize: 1, ttl: 60000 });
|
|
97
95
|
const _fileCache = new CacheManager({ maxSize: 100, ttl: 60000, maxValueSize: MAX_CACHED_FILE_SIZE });
|
|
98
96
|
const _existsCache = new CacheManager({ maxSize: 100, ttl: 60000 });
|
|
99
97
|
|
|
100
|
-
// Note: enforceMaxCacheSize() removed - now handled by CacheManager internally
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Generic file-based detector (synchronous)
|
|
104
|
-
* @param {Array} configs - Array of {file, platform} objects
|
|
105
|
-
* @param {Function} existsChecker - Function to check file existence
|
|
106
|
-
* @returns {string|null} Detected platform or null
|
|
107
|
-
*/
|
|
108
|
-
function detectFromFiles(configs, existsChecker) {
|
|
109
|
-
for (const { file, platform } of configs) {
|
|
110
|
-
if (existsChecker(file)) {
|
|
111
|
-
return platform;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
98
|
/**
|
|
118
|
-
* Generic file-based detector
|
|
99
|
+
* Generic file-based detector
|
|
119
100
|
* @param {Array} configs - Array of {file, platform} objects
|
|
120
101
|
* @param {Function} existsChecker - Async function to check file existence
|
|
121
102
|
* @returns {Promise<string|null>} Detected platform or null
|
|
122
103
|
*/
|
|
123
|
-
async function
|
|
124
|
-
// Check all files in parallel for better performance
|
|
104
|
+
async function detectFromFiles(configs, existsChecker) {
|
|
125
105
|
const checks = await Promise.all(
|
|
126
106
|
configs.map(({ file }) => existsChecker(file))
|
|
127
107
|
);
|
|
128
108
|
|
|
129
|
-
// Return first match (maintains priority order)
|
|
130
109
|
for (let i = 0; i < checks.length; i++) {
|
|
131
110
|
if (checks[i]) {
|
|
132
111
|
return configs[i].platform;
|
|
@@ -138,24 +117,9 @@ async function detectFromFilesAsync(configs, existsChecker) {
|
|
|
138
117
|
/**
|
|
139
118
|
* Check if a file exists (cached)
|
|
140
119
|
* @param {string} filepath - Path to check
|
|
141
|
-
* @returns {boolean}
|
|
142
|
-
*/
|
|
143
|
-
function existsCached(filepath) {
|
|
144
|
-
const cached = _existsCache.get(filepath);
|
|
145
|
-
if (cached !== undefined) {
|
|
146
|
-
return cached;
|
|
147
|
-
}
|
|
148
|
-
const exists = fs.existsSync(filepath);
|
|
149
|
-
_existsCache.set(filepath, exists);
|
|
150
|
-
return exists;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Check if a file exists (cached, async)
|
|
155
|
-
* @param {string} filepath - Path to check
|
|
156
120
|
* @returns {Promise<boolean>}
|
|
157
121
|
*/
|
|
158
|
-
async function
|
|
122
|
+
async function existsCached(filepath) {
|
|
159
123
|
const cached = _existsCache.get(filepath);
|
|
160
124
|
if (cached !== undefined) {
|
|
161
125
|
return cached;
|
|
@@ -175,39 +139,9 @@ async function existsCachedAsync(filepath) {
|
|
|
175
139
|
* Only caches files smaller than MAX_CACHED_FILE_SIZE to prevent memory bloat
|
|
176
140
|
* Optimized: normalizes filepath to prevent cache pollution from variant paths
|
|
177
141
|
* @param {string} filepath - Path to read
|
|
178
|
-
* @returns {string|null}
|
|
179
|
-
*/
|
|
180
|
-
function readFileCached(filepath) {
|
|
181
|
-
// Normalize filepath to prevent cache pollution (./foo vs foo vs /abs/foo)
|
|
182
|
-
// This ensures that different representations of the same path use the same cache entry
|
|
183
|
-
const normalizedPath = path.resolve(filepath);
|
|
184
|
-
|
|
185
|
-
const cached = _fileCache.get(normalizedPath);
|
|
186
|
-
if (cached !== undefined) {
|
|
187
|
-
return cached;
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
const content = fs.readFileSync(normalizedPath, 'utf8');
|
|
191
|
-
// CacheManager enforces maxValueSize, so small files are cached automatically
|
|
192
|
-
_fileCache.set(normalizedPath, content);
|
|
193
|
-
return content;
|
|
194
|
-
} catch {
|
|
195
|
-
// Cache null for missing files (small memory footprint)
|
|
196
|
-
_fileCache.set(normalizedPath, null);
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Read file contents (cached, async)
|
|
203
|
-
* Only caches files smaller than MAX_CACHED_FILE_SIZE to prevent memory bloat
|
|
204
|
-
* Optimized: normalizes filepath to prevent cache pollution from variant paths
|
|
205
|
-
* @param {string} filepath - Path to read
|
|
206
142
|
* @returns {Promise<string|null>}
|
|
207
143
|
*/
|
|
208
|
-
async function
|
|
209
|
-
// Normalize filepath to prevent cache pollution (./foo vs foo vs /abs/foo)
|
|
210
|
-
// This ensures that different representations of the same path use the same cache entry
|
|
144
|
+
async function readFileCached(filepath) {
|
|
211
145
|
const normalizedPath = path.resolve(filepath);
|
|
212
146
|
|
|
213
147
|
const cached = _fileCache.get(normalizedPath);
|
|
@@ -216,11 +150,9 @@ async function readFileCachedAsync(filepath) {
|
|
|
216
150
|
}
|
|
217
151
|
try {
|
|
218
152
|
const content = await fsPromises.readFile(normalizedPath, 'utf8');
|
|
219
|
-
// CacheManager enforces maxValueSize, so small files are cached automatically
|
|
220
153
|
_fileCache.set(normalizedPath, content);
|
|
221
154
|
return content;
|
|
222
155
|
} catch {
|
|
223
|
-
// Cache null for missing files (small memory footprint)
|
|
224
156
|
_fileCache.set(normalizedPath, null);
|
|
225
157
|
return null;
|
|
226
158
|
}
|
|
@@ -228,69 +160,34 @@ async function readFileCachedAsync(filepath) {
|
|
|
228
160
|
|
|
229
161
|
/**
|
|
230
162
|
* Detects CI platform by scanning for configuration files
|
|
231
|
-
* @deprecated Use detectCIAsync() instead. Will be removed in v3.0.0.
|
|
232
|
-
* @returns {string|null} CI platform name or null if not detected
|
|
233
|
-
*/
|
|
234
|
-
function detectCI() {
|
|
235
|
-
warnDeprecation('detectCI', 'detectCIAsync');
|
|
236
|
-
return detectFromFiles(CI_CONFIGS, existsCached);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Detects CI platform by scanning for configuration files (async)
|
|
241
163
|
* @returns {Promise<string|null>} CI platform name or null if not detected
|
|
242
164
|
*/
|
|
243
|
-
async function
|
|
244
|
-
return
|
|
165
|
+
async function detectCI() {
|
|
166
|
+
return detectFromFiles(CI_CONFIGS, existsCached);
|
|
245
167
|
}
|
|
246
168
|
|
|
247
169
|
/**
|
|
248
170
|
* Detects deployment platform by scanning for platform-specific files
|
|
249
|
-
* @deprecated Use detectDeploymentAsync() instead. Will be removed in v3.0.0.
|
|
250
|
-
* @returns {string|null} Deployment platform name or null if not detected
|
|
251
|
-
*/
|
|
252
|
-
function detectDeployment() {
|
|
253
|
-
warnDeprecation('detectDeployment', 'detectDeploymentAsync');
|
|
254
|
-
return detectFromFiles(DEPLOYMENT_CONFIGS, existsCached);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Detects deployment platform by scanning for platform-specific files (async)
|
|
259
171
|
* @returns {Promise<string|null>} Deployment platform name or null if not detected
|
|
260
172
|
*/
|
|
261
|
-
async function
|
|
262
|
-
return
|
|
173
|
+
async function detectDeployment() {
|
|
174
|
+
return detectFromFiles(DEPLOYMENT_CONFIGS, existsCached);
|
|
263
175
|
}
|
|
264
176
|
|
|
265
177
|
/**
|
|
266
178
|
* Detects project type by scanning for language-specific files
|
|
267
|
-
* @deprecated Use detectProjectTypeAsync() instead. Will be removed in v3.0.0.
|
|
268
|
-
* @returns {string} Project type identifier
|
|
269
|
-
*/
|
|
270
|
-
function detectProjectType() {
|
|
271
|
-
warnDeprecation('detectProjectType', 'detectProjectTypeAsync');
|
|
272
|
-
if (existsCached('package.json')) return 'nodejs';
|
|
273
|
-
if (existsCached('requirements.txt') || existsCached('pyproject.toml') || existsCached('setup.py')) return 'python';
|
|
274
|
-
if (existsCached('Cargo.toml')) return 'rust';
|
|
275
|
-
if (existsCached('go.mod')) return 'go';
|
|
276
|
-
if (existsCached('pom.xml') || existsCached('build.gradle')) return 'java';
|
|
277
|
-
return 'unknown';
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Detects project type by scanning for language-specific files (async)
|
|
282
179
|
* @returns {Promise<string>} Project type identifier
|
|
283
180
|
*/
|
|
284
|
-
async function
|
|
181
|
+
async function detectProjectType() {
|
|
285
182
|
const checks = await Promise.all([
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
183
|
+
existsCached('package.json'),
|
|
184
|
+
existsCached('requirements.txt'),
|
|
185
|
+
existsCached('pyproject.toml'),
|
|
186
|
+
existsCached('setup.py'),
|
|
187
|
+
existsCached('Cargo.toml'),
|
|
188
|
+
existsCached('go.mod'),
|
|
189
|
+
existsCached('pom.xml'),
|
|
190
|
+
existsCached('build.gradle')
|
|
294
191
|
]);
|
|
295
192
|
|
|
296
193
|
if (checks[0]) return 'nodejs';
|
|
@@ -303,86 +200,24 @@ async function detectProjectTypeAsync() {
|
|
|
303
200
|
|
|
304
201
|
/**
|
|
305
202
|
* Detects package manager by scanning for lockfiles
|
|
306
|
-
* @
|
|
307
|
-
* @returns {string|null} Package manager name or null if not detected
|
|
203
|
+
* @returns {Promise<string|null>} Package manager name or null if not detected
|
|
308
204
|
*/
|
|
309
|
-
function detectPackageManager() {
|
|
310
|
-
warnDeprecation('detectPackageManager', 'detectPackageManagerAsync');
|
|
205
|
+
async function detectPackageManager() {
|
|
311
206
|
return detectFromFiles(
|
|
312
207
|
PACKAGE_MANAGER_CONFIGS.map(({ file, manager }) => ({ file, platform: manager })),
|
|
313
208
|
existsCached
|
|
314
209
|
);
|
|
315
210
|
}
|
|
316
211
|
|
|
317
|
-
/**
|
|
318
|
-
* Detects package manager by scanning for lockfiles (async)
|
|
319
|
-
* @returns {Promise<string|null>} Package manager name or null if not detected
|
|
320
|
-
*/
|
|
321
|
-
async function detectPackageManagerAsync() {
|
|
322
|
-
return detectFromFilesAsync(
|
|
323
|
-
PACKAGE_MANAGER_CONFIGS.map(({ file, manager }) => ({ file, platform: manager })),
|
|
324
|
-
existsCachedAsync
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
212
|
/**
|
|
329
213
|
* Detects branch strategy (single-branch vs multi-branch with dev+prod)
|
|
330
|
-
* @deprecated Use detectBranchStrategyAsync() instead. Will be removed in v3.0.0.
|
|
331
|
-
* @returns {string} 'single-branch' or 'multi-branch'
|
|
332
|
-
*/
|
|
333
|
-
function detectBranchStrategy() {
|
|
334
|
-
warnDeprecation('detectBranchStrategy', 'detectBranchStrategyAsync');
|
|
335
|
-
try {
|
|
336
|
-
// Check both local and remote branches
|
|
337
|
-
const localBranches = execSync('git branch', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
338
|
-
let remoteBranches = '';
|
|
339
|
-
try {
|
|
340
|
-
remoteBranches = execSync('git branch -r', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
341
|
-
} catch {}
|
|
342
|
-
|
|
343
|
-
const allBranches = localBranches + remoteBranches;
|
|
344
|
-
|
|
345
|
-
const hasStable = allBranches.includes('stable');
|
|
346
|
-
const hasProduction = allBranches.includes('production') || allBranches.includes('prod');
|
|
347
|
-
|
|
348
|
-
if (hasStable || hasProduction) {
|
|
349
|
-
return 'multi-branch'; // dev + prod workflow
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Check deployment configs for multi-environment setup (uses cache)
|
|
353
|
-
if (existsCached('railway.json')) {
|
|
354
|
-
try {
|
|
355
|
-
const content = readFileCached('railway.json');
|
|
356
|
-
if (content) {
|
|
357
|
-
const config = safeJSONParse(content, 'railway.json');
|
|
358
|
-
// Validate JSON structure before accessing properties
|
|
359
|
-
if (config &&
|
|
360
|
-
typeof config === 'object' &&
|
|
361
|
-
typeof config.environments === 'object' &&
|
|
362
|
-
config.environments !== null &&
|
|
363
|
-
Object.keys(config.environments).length > 1) {
|
|
364
|
-
return 'multi-branch';
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
} catch {}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return 'single-branch'; // main only
|
|
371
|
-
} catch {
|
|
372
|
-
return 'single-branch';
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* Detects branch strategy (single-branch vs multi-branch with dev+prod) (async)
|
|
378
214
|
* @returns {Promise<string>} 'single-branch' or 'multi-branch'
|
|
379
215
|
*/
|
|
380
|
-
async function
|
|
216
|
+
async function detectBranchStrategy() {
|
|
381
217
|
try {
|
|
382
|
-
// Run git commands in parallel with timeout protection
|
|
383
218
|
const [localResult, remoteResult] = await Promise.all([
|
|
384
|
-
|
|
385
|
-
|
|
219
|
+
execWithTimeout('git branch', { encoding: 'utf8' }).catch(() => ({ stdout: '' })),
|
|
220
|
+
execWithTimeout('git branch -r', { encoding: 'utf8' }).catch(() => ({ stdout: '' }))
|
|
386
221
|
]);
|
|
387
222
|
|
|
388
223
|
const allBranches = (localResult.stdout || '') + (remoteResult.stdout || '');
|
|
@@ -394,10 +229,9 @@ async function detectBranchStrategyAsync() {
|
|
|
394
229
|
return 'multi-branch';
|
|
395
230
|
}
|
|
396
231
|
|
|
397
|
-
|
|
398
|
-
if (await existsCachedAsync('railway.json')) {
|
|
232
|
+
if (await existsCached('railway.json')) {
|
|
399
233
|
try {
|
|
400
|
-
const content = await
|
|
234
|
+
const content = await readFileCached('railway.json');
|
|
401
235
|
if (content) {
|
|
402
236
|
const config = safeJSONParse(content, 'railway.json');
|
|
403
237
|
if (config &&
|
|
@@ -419,45 +253,15 @@ async function detectBranchStrategyAsync() {
|
|
|
419
253
|
|
|
420
254
|
/**
|
|
421
255
|
* Detects the main branch name
|
|
422
|
-
* @deprecated Use detectMainBranchAsync() instead. Will be removed in v3.0.0.
|
|
423
|
-
* @returns {string} Main branch name ('main' or 'master')
|
|
424
|
-
*/
|
|
425
|
-
function detectMainBranch() {
|
|
426
|
-
warnDeprecation('detectMainBranch', 'detectMainBranchAsync');
|
|
427
|
-
try {
|
|
428
|
-
const defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
429
|
-
encoding: 'utf8',
|
|
430
|
-
stdio: ['pipe', 'pipe', 'ignore']
|
|
431
|
-
})
|
|
432
|
-
.trim()
|
|
433
|
-
.replace('refs/remotes/origin/', '');
|
|
434
|
-
return defaultBranch;
|
|
435
|
-
} catch {
|
|
436
|
-
// Fallback: check common names
|
|
437
|
-
try {
|
|
438
|
-
execSync('git rev-parse --verify main', {
|
|
439
|
-
encoding: 'utf8',
|
|
440
|
-
stdio: ['pipe', 'pipe', 'ignore']
|
|
441
|
-
});
|
|
442
|
-
return 'main';
|
|
443
|
-
} catch {
|
|
444
|
-
return 'master';
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Detects the main branch name (async)
|
|
451
256
|
* @returns {Promise<string>} Main branch name ('main' or 'master')
|
|
452
257
|
*/
|
|
453
|
-
async function
|
|
258
|
+
async function detectMainBranch() {
|
|
454
259
|
try {
|
|
455
|
-
const { stdout } = await
|
|
260
|
+
const { stdout } = await execWithTimeout('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' });
|
|
456
261
|
return stdout.trim().replace('refs/remotes/origin/', '');
|
|
457
262
|
} catch {
|
|
458
|
-
// Fallback: check common names
|
|
459
263
|
try {
|
|
460
|
-
await
|
|
264
|
+
await execWithTimeout('git rev-parse --verify main', { encoding: 'utf8' });
|
|
461
265
|
return 'main';
|
|
462
266
|
} catch {
|
|
463
267
|
return 'master';
|
|
@@ -466,47 +270,12 @@ async function detectMainBranchAsync() {
|
|
|
466
270
|
}
|
|
467
271
|
|
|
468
272
|
/**
|
|
469
|
-
* Main detection function - aggregates all platform information
|
|
470
|
-
* Uses caching to avoid repeated filesystem/git operations
|
|
471
|
-
* @deprecated Use detectAsync() instead. Will be removed in v3.0.0.
|
|
472
|
-
* @param {boolean} forceRefresh - Force cache refresh
|
|
473
|
-
* @returns {Object} Platform configuration object
|
|
474
|
-
*/
|
|
475
|
-
function detect(forceRefresh = false) {
|
|
476
|
-
warnDeprecation('detect', 'detectAsync');
|
|
477
|
-
|
|
478
|
-
// Return cached result if still valid
|
|
479
|
-
if (!forceRefresh) {
|
|
480
|
-
const cached = _detectionCache.get('detection');
|
|
481
|
-
if (cached !== undefined) {
|
|
482
|
-
return cached;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const detection = {
|
|
487
|
-
ci: detectCI(),
|
|
488
|
-
deployment: detectDeployment(),
|
|
489
|
-
projectType: detectProjectType(),
|
|
490
|
-
packageManager: detectPackageManager(),
|
|
491
|
-
branchStrategy: detectBranchStrategy(),
|
|
492
|
-
mainBranch: detectMainBranch(),
|
|
493
|
-
hasPlanFile: existsCached('PLAN.md'),
|
|
494
|
-
hasTechDebtFile: existsCached('TECHNICAL_DEBT.md'),
|
|
495
|
-
timestamp: new Date().toISOString()
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
_detectionCache.set('detection', detection);
|
|
499
|
-
return detection;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
/**
|
|
503
|
-
* Main detection function - aggregates all platform information (async)
|
|
273
|
+
* Main detection function - aggregates all platform information
|
|
504
274
|
* Uses Promise.all for parallel execution and caching
|
|
505
275
|
* @param {boolean} forceRefresh - Force cache refresh
|
|
506
276
|
* @returns {Promise<Object>} Platform configuration object
|
|
507
277
|
*/
|
|
508
|
-
async function
|
|
509
|
-
// Return cached result if still valid
|
|
278
|
+
async function detect(forceRefresh = false) {
|
|
510
279
|
if (!forceRefresh) {
|
|
511
280
|
const cached = _detectionCache.get('detection');
|
|
512
281
|
if (cached !== undefined) {
|
|
@@ -514,7 +283,6 @@ async function detectAsync(forceRefresh = false) {
|
|
|
514
283
|
}
|
|
515
284
|
}
|
|
516
285
|
|
|
517
|
-
// Run all detections in parallel
|
|
518
286
|
const [
|
|
519
287
|
ci,
|
|
520
288
|
deployment,
|
|
@@ -525,14 +293,14 @@ async function detectAsync(forceRefresh = false) {
|
|
|
525
293
|
hasPlanFile,
|
|
526
294
|
hasTechDebtFile
|
|
527
295
|
] = await Promise.all([
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
296
|
+
detectCI(),
|
|
297
|
+
detectDeployment(),
|
|
298
|
+
detectProjectType(),
|
|
299
|
+
detectPackageManager(),
|
|
300
|
+
detectBranchStrategy(),
|
|
301
|
+
detectMainBranch(),
|
|
302
|
+
existsCached('PLAN.md'),
|
|
303
|
+
existsCached('TECHNICAL_DEBT.md')
|
|
536
304
|
]);
|
|
537
305
|
|
|
538
306
|
const detection = {
|
|
@@ -561,13 +329,11 @@ function invalidateCache() {
|
|
|
561
329
|
_existsCache.clear();
|
|
562
330
|
}
|
|
563
331
|
|
|
564
|
-
// When run directly, output JSON
|
|
332
|
+
// When run directly, output JSON
|
|
565
333
|
if (require.main === module) {
|
|
566
334
|
(async () => {
|
|
567
335
|
try {
|
|
568
|
-
const result = await
|
|
569
|
-
// Optimize: only use pretty-printing when output is to terminal (TTY)
|
|
570
|
-
// When piped to another program, use compact JSON for better performance
|
|
336
|
+
const result = await detect();
|
|
571
337
|
const indent = process.stdout.isTTY ? 2 : 0;
|
|
572
338
|
console.log(JSON.stringify(result, null, indent));
|
|
573
339
|
} catch (error) {
|
|
@@ -584,20 +350,11 @@ if (require.main === module) {
|
|
|
584
350
|
// Export for use as module
|
|
585
351
|
module.exports = {
|
|
586
352
|
detect,
|
|
587
|
-
detectAsync,
|
|
588
353
|
invalidateCache,
|
|
589
354
|
detectCI,
|
|
590
|
-
detectCIAsync,
|
|
591
355
|
detectDeployment,
|
|
592
|
-
detectDeploymentAsync,
|
|
593
356
|
detectProjectType,
|
|
594
|
-
detectProjectTypeAsync,
|
|
595
357
|
detectPackageManager,
|
|
596
|
-
detectPackageManagerAsync,
|
|
597
358
|
detectBranchStrategy,
|
|
598
|
-
|
|
599
|
-
detectMainBranch,
|
|
600
|
-
detectMainBranchAsync,
|
|
601
|
-
// Testing utilities (prefixed with _ to indicate internal use)
|
|
602
|
-
_resetDeprecationWarnings
|
|
359
|
+
detectMainBranch
|
|
603
360
|
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-aware state directory detection
|
|
3
|
+
*
|
|
4
|
+
* Determines the appropriate state directory based on the AI coding assistant
|
|
5
|
+
* being used (Claude Code, OpenCode, or Codex CLI).
|
|
6
|
+
*
|
|
7
|
+
* @module lib/platform/state-dir
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cached state directory name (relative, without leading dot handling)
|
|
15
|
+
* @type {string|null}
|
|
16
|
+
*/
|
|
17
|
+
let _cachedStateDir = null;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detect which AI coding assistant is running and return appropriate state directory
|
|
21
|
+
*
|
|
22
|
+
* Detection order:
|
|
23
|
+
* 1. AI_STATE_DIR env var (user override)
|
|
24
|
+
* 2. OpenCode detection (OPENCODE_CONFIG env or .opencode/ exists)
|
|
25
|
+
* 3. Codex detection (CODEX_HOME env or .codex/ exists)
|
|
26
|
+
* 4. Default to .claude (Claude Code or unknown)
|
|
27
|
+
*
|
|
28
|
+
* @param {string} [basePath=process.cwd()] - Base path to check for project directories
|
|
29
|
+
* @returns {string} State directory name (e.g., '.claude', '.opencode', '.codex')
|
|
30
|
+
*/
|
|
31
|
+
function getStateDir(basePath = process.cwd()) {
|
|
32
|
+
// Check user override first
|
|
33
|
+
if (process.env.AI_STATE_DIR) {
|
|
34
|
+
return process.env.AI_STATE_DIR;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Return cached value if available
|
|
38
|
+
if (_cachedStateDir) {
|
|
39
|
+
return _cachedStateDir;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// OpenCode detection
|
|
43
|
+
if (process.env.OPENCODE_CONFIG || process.env.OPENCODE_CONFIG_DIR) {
|
|
44
|
+
_cachedStateDir = '.opencode';
|
|
45
|
+
return _cachedStateDir;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check for .opencode directory in project
|
|
49
|
+
try {
|
|
50
|
+
const opencodePath = path.join(basePath, '.opencode');
|
|
51
|
+
if (fs.existsSync(opencodePath) && fs.statSync(opencodePath).isDirectory()) {
|
|
52
|
+
_cachedStateDir = '.opencode';
|
|
53
|
+
return _cachedStateDir;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// Ignore errors, continue detection
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Codex detection
|
|
60
|
+
if (process.env.CODEX_HOME) {
|
|
61
|
+
_cachedStateDir = '.codex';
|
|
62
|
+
return _cachedStateDir;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for .codex directory in project
|
|
66
|
+
try {
|
|
67
|
+
const codexPath = path.join(basePath, '.codex');
|
|
68
|
+
if (fs.existsSync(codexPath) && fs.statSync(codexPath).isDirectory()) {
|
|
69
|
+
_cachedStateDir = '.codex';
|
|
70
|
+
return _cachedStateDir;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// Ignore errors, continue detection
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Default to Claude Code
|
|
77
|
+
_cachedStateDir = '.claude';
|
|
78
|
+
return _cachedStateDir;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get the full path to the state directory
|
|
83
|
+
* @param {string} [basePath=process.cwd()] - Base path
|
|
84
|
+
* @returns {string} Full path to state directory
|
|
85
|
+
*/
|
|
86
|
+
function getStateDirPath(basePath = process.cwd()) {
|
|
87
|
+
return path.join(basePath, getStateDir(basePath));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the detected platform name
|
|
92
|
+
* @param {string} [basePath=process.cwd()] - Base path
|
|
93
|
+
* @returns {string} Platform name ('claude', 'opencode', 'codex', or 'custom')
|
|
94
|
+
*/
|
|
95
|
+
function getPlatformName(basePath = process.cwd()) {
|
|
96
|
+
const stateDir = getStateDir(basePath);
|
|
97
|
+
|
|
98
|
+
if (process.env.AI_STATE_DIR) {
|
|
99
|
+
return 'custom';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
switch (stateDir) {
|
|
103
|
+
case '.opencode': return 'opencode';
|
|
104
|
+
case '.codex': return 'codex';
|
|
105
|
+
case '.claude': return 'claude';
|
|
106
|
+
default: return 'unknown';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Clear the cached state directory (useful for testing)
|
|
112
|
+
*/
|
|
113
|
+
function clearCache() {
|
|
114
|
+
_cachedStateDir = null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
getStateDir,
|
|
119
|
+
getStateDirPath,
|
|
120
|
+
getPlatformName,
|
|
121
|
+
clearCache
|
|
122
|
+
};
|