awesome-slash 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.claude-plugin/marketplace.json +54 -0
  2. package/.claude-plugin/plugin.json +11 -0
  3. package/.mcp.json +8 -0
  4. package/CHANGELOG.md +261 -0
  5. package/LICENSE +21 -0
  6. package/README.md +363 -0
  7. package/SECURITY.md +101 -0
  8. package/adapters/README.md +256 -0
  9. package/adapters/codex/README.md +272 -0
  10. package/adapters/codex/install.sh +179 -0
  11. package/adapters/opencode/README.md +301 -0
  12. package/adapters/opencode/install.sh +223 -0
  13. package/lib/patterns/review-patterns.js +511 -0
  14. package/lib/patterns/slop-patterns.js +647 -0
  15. package/lib/platform/detect-platform.js +535 -0
  16. package/lib/platform/verify-tools.js +235 -0
  17. package/lib/state/workflow-state.js +635 -0
  18. package/lib/state/workflow-state.schema.json +282 -0
  19. package/lib/utils/context-optimizer.js +227 -0
  20. package/mcp-server/index.js +303 -0
  21. package/mcp-server/package.json +23 -0
  22. package/package.json +63 -0
  23. package/plugins/deslop-around/.claude-plugin/plugin.json +20 -0
  24. package/plugins/deslop-around/commands/deslop-around.md +220 -0
  25. package/plugins/deslop-around/lib/patterns/review-patterns.js +511 -0
  26. package/plugins/deslop-around/lib/patterns/slop-patterns.js +641 -0
  27. package/plugins/deslop-around/lib/platform/detect-platform.js +514 -0
  28. package/plugins/deslop-around/lib/platform/verify-tools.js +235 -0
  29. package/plugins/deslop-around/lib/state/workflow-state.js +635 -0
  30. package/plugins/deslop-around/lib/state/workflow-state.schema.json +282 -0
  31. package/plugins/deslop-around/lib/utils/context-optimizer.js +222 -0
  32. package/plugins/next-task/.claude-plugin/plugin.json +24 -0
  33. package/plugins/next-task/agents/ci-fixer.md +236 -0
  34. package/plugins/next-task/agents/ci-monitor.md +291 -0
  35. package/plugins/next-task/agents/delivery-validator.md +451 -0
  36. package/plugins/next-task/agents/deslop-work.md +272 -0
  37. package/plugins/next-task/agents/docs-updater.md +506 -0
  38. package/plugins/next-task/agents/exploration-agent.md +277 -0
  39. package/plugins/next-task/agents/implementation-agent.md +427 -0
  40. package/plugins/next-task/agents/planning-agent.md +236 -0
  41. package/plugins/next-task/agents/policy-selector.md +248 -0
  42. package/plugins/next-task/agents/review-orchestrator.md +521 -0
  43. package/plugins/next-task/agents/simple-fixer.md +136 -0
  44. package/plugins/next-task/agents/task-discoverer.md +357 -0
  45. package/plugins/next-task/agents/test-coverage-checker.md +447 -0
  46. package/plugins/next-task/agents/worktree-manager.md +419 -0
  47. package/plugins/next-task/commands/delivery-approval.md +331 -0
  48. package/plugins/next-task/commands/next-task.md +627 -0
  49. package/plugins/next-task/commands/update-docs-around.md +418 -0
  50. package/plugins/next-task/hooks/hooks.json +14 -0
  51. package/plugins/next-task/lib/patterns/review-patterns.js +511 -0
  52. package/plugins/next-task/lib/patterns/slop-patterns.js +641 -0
  53. package/plugins/next-task/lib/platform/detect-platform.js +514 -0
  54. package/plugins/next-task/lib/platform/verify-tools.js +235 -0
  55. package/plugins/next-task/lib/state/tasks-registry.schema.json +85 -0
  56. package/plugins/next-task/lib/state/workflow-state.js +635 -0
  57. package/plugins/next-task/lib/state/workflow-state.schema.json +282 -0
  58. package/plugins/next-task/lib/state/worktree-status.schema.json +219 -0
  59. package/plugins/next-task/lib/utils/context-optimizer.js +222 -0
  60. package/plugins/project-review/.claude-plugin/plugin.json +20 -0
  61. package/plugins/project-review/commands/project-review-agents.md +286 -0
  62. package/plugins/project-review/commands/project-review-github.md +142 -0
  63. package/plugins/project-review/commands/project-review.md +273 -0
  64. package/plugins/project-review/lib/patterns/review-patterns.js +511 -0
  65. package/plugins/project-review/lib/patterns/slop-patterns.js +641 -0
  66. package/plugins/project-review/lib/platform/detect-platform.js +514 -0
  67. package/plugins/project-review/lib/platform/verify-tools.js +235 -0
  68. package/plugins/project-review/lib/state/workflow-state.js +635 -0
  69. package/plugins/project-review/lib/state/workflow-state.schema.json +282 -0
  70. package/plugins/project-review/lib/utils/context-optimizer.js +222 -0
  71. package/plugins/reality-check/.claude-plugin/plugin.json +23 -0
  72. package/plugins/reality-check/README.md +156 -0
  73. package/plugins/reality-check/agents/code-explorer.md +353 -0
  74. package/plugins/reality-check/agents/doc-analyzer.md +337 -0
  75. package/plugins/reality-check/agents/issue-scanner.md +231 -0
  76. package/plugins/reality-check/agents/plan-synthesizer.md +479 -0
  77. package/plugins/reality-check/commands/scan.md +242 -0
  78. package/plugins/reality-check/commands/set.md +203 -0
  79. package/plugins/reality-check/lib/state/reality-check-state.js +509 -0
  80. package/plugins/reality-check/skills/reality-analysis/SKILL.md +317 -0
  81. package/plugins/ship/.claude-plugin/plugin.json +21 -0
  82. package/plugins/ship/commands/ship-ci-review-loop.md +443 -0
  83. package/plugins/ship/commands/ship-deployment.md +330 -0
  84. package/plugins/ship/commands/ship-error-handling.md +254 -0
  85. package/plugins/ship/commands/ship.md +370 -0
  86. package/plugins/ship/lib/patterns/review-patterns.js +511 -0
  87. package/plugins/ship/lib/patterns/slop-patterns.js +641 -0
  88. package/plugins/ship/lib/platform/detect-platform.js +514 -0
  89. package/plugins/ship/lib/platform/verify-tools.js +235 -0
  90. package/plugins/ship/lib/state/workflow-state.js +635 -0
  91. package/plugins/ship/lib/state/workflow-state.schema.json +282 -0
  92. package/plugins/ship/lib/utils/context-optimizer.js +222 -0
  93. package/scripts/install/claude.sh +50 -0
  94. package/scripts/install/codex.sh +181 -0
  95. package/scripts/install/opencode.sh +211 -0
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Platform Detection Infrastructure
4
+ * Auto-detects project configuration for zero-config slash commands
5
+ *
6
+ * Usage: node lib/platform/detect-platform.js
7
+ * Output: JSON with detected platform information
8
+ *
9
+ * @author Avi Fenesh
10
+ * @license MIT
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const { execSync, exec } = require('child_process');
15
+ const { promisify } = require('util');
16
+
17
+ const execAsync = promisify(exec);
18
+ const fsPromises = fs.promises;
19
+
20
+ // Detection cache for performance (platform rarely changes during session)
21
+ let _cachedDetection = null;
22
+ let _cacheExpiry = 0;
23
+ const CACHE_TTL_MS = 60000; // 1 minute cache
24
+
25
+ // File read cache to avoid reading the same file multiple times (#17)
26
+ // Limited to prevent unbounded memory growth in long-running processes
27
+ const MAX_CACHE_SIZE = 100;
28
+ const _fileCache = new Map();
29
+ const _existsCache = new Map();
30
+
31
+ /**
32
+ * Enforce cache size limits using FIFO eviction
33
+ * @param {Map} cache - Cache to limit
34
+ * @param {number} maxSize - Maximum entries
35
+ */
36
+ function enforceMaxCacheSize(cache, maxSize = MAX_CACHE_SIZE) {
37
+ if (cache.size > maxSize) {
38
+ const keysToDelete = Array.from(cache.keys()).slice(0, cache.size - maxSize);
39
+ keysToDelete.forEach(key => cache.delete(key));
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Check if a file exists (cached)
45
+ * @param {string} filepath - Path to check
46
+ * @returns {boolean}
47
+ */
48
+ function existsCached(filepath) {
49
+ if (_existsCache.has(filepath)) {
50
+ return _existsCache.get(filepath);
51
+ }
52
+ const exists = fs.existsSync(filepath);
53
+ _existsCache.set(filepath, exists);
54
+ enforceMaxCacheSize(_existsCache);
55
+ return exists;
56
+ }
57
+
58
+ /**
59
+ * Check if a file exists (cached, async)
60
+ * @param {string} filepath - Path to check
61
+ * @returns {Promise<boolean>}
62
+ */
63
+ async function existsCachedAsync(filepath) {
64
+ if (_existsCache.has(filepath)) {
65
+ return _existsCache.get(filepath);
66
+ }
67
+ try {
68
+ await fsPromises.access(filepath);
69
+ _existsCache.set(filepath, true);
70
+ enforceMaxCacheSize(_existsCache);
71
+ return true;
72
+ } catch {
73
+ _existsCache.set(filepath, false);
74
+ enforceMaxCacheSize(_existsCache);
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Read file contents (cached)
81
+ * @param {string} filepath - Path to read
82
+ * @returns {string|null}
83
+ */
84
+ function readFileCached(filepath) {
85
+ if (_fileCache.has(filepath)) {
86
+ return _fileCache.get(filepath);
87
+ }
88
+ try {
89
+ const content = fs.readFileSync(filepath, 'utf8');
90
+ _fileCache.set(filepath, content);
91
+ enforceMaxCacheSize(_fileCache);
92
+ return content;
93
+ } catch {
94
+ _fileCache.set(filepath, null);
95
+ enforceMaxCacheSize(_fileCache);
96
+ return null;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Read file contents (cached, async)
102
+ * @param {string} filepath - Path to read
103
+ * @returns {Promise<string|null>}
104
+ */
105
+ async function readFileCachedAsync(filepath) {
106
+ if (_fileCache.has(filepath)) {
107
+ return _fileCache.get(filepath);
108
+ }
109
+ try {
110
+ const content = await fsPromises.readFile(filepath, 'utf8');
111
+ _fileCache.set(filepath, content);
112
+ enforceMaxCacheSize(_fileCache);
113
+ return content;
114
+ } catch {
115
+ _fileCache.set(filepath, null);
116
+ enforceMaxCacheSize(_fileCache);
117
+ return null;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Detects CI platform by scanning for configuration files
123
+ * @returns {string|null} CI platform name or null if not detected
124
+ */
125
+ function detectCI() {
126
+ if (existsCached('.github/workflows')) return 'github-actions';
127
+ if (existsCached('.gitlab-ci.yml')) return 'gitlab-ci';
128
+ if (existsCached('.circleci/config.yml')) return 'circleci';
129
+ if (existsCached('Jenkinsfile')) return 'jenkins';
130
+ if (existsCached('.travis.yml')) return 'travis';
131
+ return null;
132
+ }
133
+
134
+ /**
135
+ * Detects CI platform by scanning for configuration files (async)
136
+ * @returns {Promise<string|null>} CI platform name or null if not detected
137
+ */
138
+ async function detectCIAsync() {
139
+ const checks = await Promise.all([
140
+ existsCachedAsync('.github/workflows'),
141
+ existsCachedAsync('.gitlab-ci.yml'),
142
+ existsCachedAsync('.circleci/config.yml'),
143
+ existsCachedAsync('Jenkinsfile'),
144
+ existsCachedAsync('.travis.yml')
145
+ ]);
146
+
147
+ if (checks[0]) return 'github-actions';
148
+ if (checks[1]) return 'gitlab-ci';
149
+ if (checks[2]) return 'circleci';
150
+ if (checks[3]) return 'jenkins';
151
+ if (checks[4]) return 'travis';
152
+ return null;
153
+ }
154
+
155
+ /**
156
+ * Detects deployment platform by scanning for platform-specific files
157
+ * @returns {string|null} Deployment platform name or null if not detected
158
+ */
159
+ function detectDeployment() {
160
+ if (existsCached('railway.json') || existsCached('railway.toml')) return 'railway';
161
+ if (existsCached('vercel.json')) return 'vercel';
162
+ if (existsCached('netlify.toml') || existsCached('.netlify')) return 'netlify';
163
+ if (existsCached('fly.toml')) return 'fly';
164
+ if (existsCached('.platform.sh')) return 'platform-sh';
165
+ if (existsCached('render.yaml')) return 'render';
166
+ return null;
167
+ }
168
+
169
+ /**
170
+ * Detects deployment platform by scanning for platform-specific files (async)
171
+ * @returns {Promise<string|null>} Deployment platform name or null if not detected
172
+ */
173
+ async function detectDeploymentAsync() {
174
+ const checks = await Promise.all([
175
+ existsCachedAsync('railway.json'),
176
+ existsCachedAsync('railway.toml'),
177
+ existsCachedAsync('vercel.json'),
178
+ existsCachedAsync('netlify.toml'),
179
+ existsCachedAsync('.netlify'),
180
+ existsCachedAsync('fly.toml'),
181
+ existsCachedAsync('.platform.sh'),
182
+ existsCachedAsync('render.yaml')
183
+ ]);
184
+
185
+ if (checks[0] || checks[1]) return 'railway';
186
+ if (checks[2]) return 'vercel';
187
+ if (checks[3] || checks[4]) return 'netlify';
188
+ if (checks[5]) return 'fly';
189
+ if (checks[6]) return 'platform-sh';
190
+ if (checks[7]) return 'render';
191
+ return null;
192
+ }
193
+
194
+ /**
195
+ * Detects project type by scanning for language-specific files
196
+ * @returns {string} Project type identifier
197
+ */
198
+ function detectProjectType() {
199
+ if (existsCached('package.json')) return 'nodejs';
200
+ if (existsCached('requirements.txt') || existsCached('pyproject.toml') || existsCached('setup.py')) return 'python';
201
+ if (existsCached('Cargo.toml')) return 'rust';
202
+ if (existsCached('go.mod')) return 'go';
203
+ if (existsCached('pom.xml') || existsCached('build.gradle')) return 'java';
204
+ return 'unknown';
205
+ }
206
+
207
+ /**
208
+ * Detects project type by scanning for language-specific files (async)
209
+ * @returns {Promise<string>} Project type identifier
210
+ */
211
+ async function detectProjectTypeAsync() {
212
+ const checks = await Promise.all([
213
+ existsCachedAsync('package.json'),
214
+ existsCachedAsync('requirements.txt'),
215
+ existsCachedAsync('pyproject.toml'),
216
+ existsCachedAsync('setup.py'),
217
+ existsCachedAsync('Cargo.toml'),
218
+ existsCachedAsync('go.mod'),
219
+ existsCachedAsync('pom.xml'),
220
+ existsCachedAsync('build.gradle')
221
+ ]);
222
+
223
+ if (checks[0]) return 'nodejs';
224
+ if (checks[1] || checks[2] || checks[3]) return 'python';
225
+ if (checks[4]) return 'rust';
226
+ if (checks[5]) return 'go';
227
+ if (checks[6] || checks[7]) return 'java';
228
+ return 'unknown';
229
+ }
230
+
231
+ /**
232
+ * Detects package manager by scanning for lockfiles
233
+ * @returns {string|null} Package manager name or null if not detected
234
+ */
235
+ function detectPackageManager() {
236
+ if (existsCached('pnpm-lock.yaml')) return 'pnpm';
237
+ if (existsCached('yarn.lock')) return 'yarn';
238
+ if (existsCached('bun.lockb')) return 'bun';
239
+ if (existsCached('package-lock.json')) return 'npm';
240
+ if (existsCached('poetry.lock')) return 'poetry';
241
+ if (existsCached('Pipfile.lock')) return 'pipenv';
242
+ if (existsCached('Cargo.lock')) return 'cargo';
243
+ if (existsCached('go.sum')) return 'go';
244
+ return null;
245
+ }
246
+
247
+ /**
248
+ * Detects package manager by scanning for lockfiles (async)
249
+ * @returns {Promise<string|null>} Package manager name or null if not detected
250
+ */
251
+ async function detectPackageManagerAsync() {
252
+ const checks = await Promise.all([
253
+ existsCachedAsync('pnpm-lock.yaml'),
254
+ existsCachedAsync('yarn.lock'),
255
+ existsCachedAsync('bun.lockb'),
256
+ existsCachedAsync('package-lock.json'),
257
+ existsCachedAsync('poetry.lock'),
258
+ existsCachedAsync('Pipfile.lock'),
259
+ existsCachedAsync('Cargo.lock'),
260
+ existsCachedAsync('go.sum')
261
+ ]);
262
+
263
+ if (checks[0]) return 'pnpm';
264
+ if (checks[1]) return 'yarn';
265
+ if (checks[2]) return 'bun';
266
+ if (checks[3]) return 'npm';
267
+ if (checks[4]) return 'poetry';
268
+ if (checks[5]) return 'pipenv';
269
+ if (checks[6]) return 'cargo';
270
+ if (checks[7]) return 'go';
271
+ return null;
272
+ }
273
+
274
+ /**
275
+ * Detects branch strategy (single-branch vs multi-branch with dev+prod)
276
+ * @returns {string} 'single-branch' or 'multi-branch'
277
+ */
278
+ function detectBranchStrategy() {
279
+ try {
280
+ // Check both local and remote branches
281
+ const localBranches = execSync('git branch', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
282
+ let remoteBranches = '';
283
+ try {
284
+ remoteBranches = execSync('git branch -r', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
285
+ } catch {}
286
+
287
+ const allBranches = localBranches + remoteBranches;
288
+
289
+ const hasStable = allBranches.includes('stable');
290
+ const hasProduction = allBranches.includes('production') || allBranches.includes('prod');
291
+
292
+ if (hasStable || hasProduction) {
293
+ return 'multi-branch'; // dev + prod workflow
294
+ }
295
+
296
+ // Check deployment configs for multi-environment setup (uses cache)
297
+ if (existsCached('railway.json')) {
298
+ try {
299
+ const content = readFileCached('railway.json');
300
+ if (content) {
301
+ const config = JSON.parse(content);
302
+ // Validate JSON structure before accessing properties
303
+ if (config &&
304
+ typeof config === 'object' &&
305
+ typeof config.environments === 'object' &&
306
+ config.environments !== null &&
307
+ Object.keys(config.environments).length > 1) {
308
+ return 'multi-branch';
309
+ }
310
+ }
311
+ } catch {}
312
+ }
313
+
314
+ return 'single-branch'; // main only
315
+ } catch {
316
+ return 'single-branch';
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Detects branch strategy (single-branch vs multi-branch with dev+prod) (async)
322
+ * @returns {Promise<string>} 'single-branch' or 'multi-branch'
323
+ */
324
+ async function detectBranchStrategyAsync() {
325
+ try {
326
+ // Run git commands in parallel
327
+ const [localResult, remoteResult] = await Promise.all([
328
+ execAsync('git branch', { encoding: 'utf8' }).catch(() => ({ stdout: '' })),
329
+ execAsync('git branch -r', { encoding: 'utf8' }).catch(() => ({ stdout: '' }))
330
+ ]);
331
+
332
+ const allBranches = (localResult.stdout || '') + (remoteResult.stdout || '');
333
+
334
+ const hasStable = allBranches.includes('stable');
335
+ const hasProduction = allBranches.includes('production') || allBranches.includes('prod');
336
+
337
+ if (hasStable || hasProduction) {
338
+ return 'multi-branch';
339
+ }
340
+
341
+ // Check deployment configs for multi-environment setup (uses cache)
342
+ if (await existsCachedAsync('railway.json')) {
343
+ try {
344
+ const content = await readFileCachedAsync('railway.json');
345
+ if (content) {
346
+ const config = JSON.parse(content);
347
+ if (config &&
348
+ typeof config === 'object' &&
349
+ typeof config.environments === 'object' &&
350
+ config.environments !== null &&
351
+ Object.keys(config.environments).length > 1) {
352
+ return 'multi-branch';
353
+ }
354
+ }
355
+ } catch {}
356
+ }
357
+
358
+ return 'single-branch';
359
+ } catch {
360
+ return 'single-branch';
361
+ }
362
+ }
363
+
364
+ /**
365
+ * Detects the main branch name
366
+ * @returns {string} Main branch name ('main' or 'master')
367
+ */
368
+ function detectMainBranch() {
369
+ try {
370
+ const defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
371
+ encoding: 'utf8',
372
+ stdio: ['pipe', 'pipe', 'ignore']
373
+ })
374
+ .trim()
375
+ .replace('refs/remotes/origin/', '');
376
+ return defaultBranch;
377
+ } catch {
378
+ // Fallback: check common names
379
+ try {
380
+ execSync('git rev-parse --verify main', {
381
+ encoding: 'utf8',
382
+ stdio: ['pipe', 'pipe', 'ignore']
383
+ });
384
+ return 'main';
385
+ } catch {
386
+ return 'master';
387
+ }
388
+ }
389
+ }
390
+
391
+ /**
392
+ * Detects the main branch name (async)
393
+ * @returns {Promise<string>} Main branch name ('main' or 'master')
394
+ */
395
+ async function detectMainBranchAsync() {
396
+ try {
397
+ const { stdout } = await execAsync('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' });
398
+ return stdout.trim().replace('refs/remotes/origin/', '');
399
+ } catch {
400
+ // Fallback: check common names
401
+ try {
402
+ await execAsync('git rev-parse --verify main', { encoding: 'utf8' });
403
+ return 'main';
404
+ } catch {
405
+ return 'master';
406
+ }
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Main detection function - aggregates all platform information (sync)
412
+ * Uses caching to avoid repeated filesystem/git operations
413
+ * @param {boolean} forceRefresh - Force cache refresh
414
+ * @returns {Object} Platform configuration object
415
+ */
416
+ function detect(forceRefresh = false) {
417
+ const now = Date.now();
418
+
419
+ // Return cached result if still valid
420
+ if (!forceRefresh && _cachedDetection && now < _cacheExpiry) {
421
+ return _cachedDetection;
422
+ }
423
+
424
+ _cachedDetection = {
425
+ ci: detectCI(),
426
+ deployment: detectDeployment(),
427
+ projectType: detectProjectType(),
428
+ packageManager: detectPackageManager(),
429
+ branchStrategy: detectBranchStrategy(),
430
+ mainBranch: detectMainBranch(),
431
+ hasPlanFile: existsCached('PLAN.md'),
432
+ hasTechDebtFile: existsCached('TECHNICAL_DEBT.md'),
433
+ timestamp: new Date(now).toISOString()
434
+ };
435
+ _cacheExpiry = now + CACHE_TTL_MS;
436
+
437
+ return _cachedDetection;
438
+ }
439
+
440
+ /**
441
+ * Main detection function - aggregates all platform information (async)
442
+ * Uses Promise.all for parallel execution and caching
443
+ * @param {boolean} forceRefresh - Force cache refresh
444
+ * @returns {Promise<Object>} Platform configuration object
445
+ */
446
+ async function detectAsync(forceRefresh = false) {
447
+ const now = Date.now();
448
+
449
+ // Return cached result if still valid
450
+ if (!forceRefresh && _cachedDetection && now < _cacheExpiry) {
451
+ return _cachedDetection;
452
+ }
453
+
454
+ // Run all detections in parallel
455
+ const [
456
+ ci,
457
+ deployment,
458
+ projectType,
459
+ packageManager,
460
+ branchStrategy,
461
+ mainBranch,
462
+ hasPlanFile,
463
+ hasTechDebtFile
464
+ ] = await Promise.all([
465
+ detectCIAsync(),
466
+ detectDeploymentAsync(),
467
+ detectProjectTypeAsync(),
468
+ detectPackageManagerAsync(),
469
+ detectBranchStrategyAsync(),
470
+ detectMainBranchAsync(),
471
+ existsCachedAsync('PLAN.md'),
472
+ existsCachedAsync('TECHNICAL_DEBT.md')
473
+ ]);
474
+
475
+ _cachedDetection = {
476
+ ci,
477
+ deployment,
478
+ projectType,
479
+ packageManager,
480
+ branchStrategy,
481
+ mainBranch,
482
+ hasPlanFile,
483
+ hasTechDebtFile,
484
+ timestamp: new Date(now).toISOString()
485
+ };
486
+ _cacheExpiry = now + CACHE_TTL_MS;
487
+
488
+ return _cachedDetection;
489
+ }
490
+
491
+ /**
492
+ * Invalidate the detection cache
493
+ * Call this after making changes that affect platform detection
494
+ */
495
+ function invalidateCache() {
496
+ _cachedDetection = null;
497
+ _cacheExpiry = 0;
498
+ _fileCache.clear();
499
+ _existsCache.clear();
500
+ }
501
+
502
+ // When run directly, output JSON (uses async for better performance)
503
+ if (require.main === module) {
504
+ (async () => {
505
+ try {
506
+ const result = await detectAsync();
507
+ console.log(JSON.stringify(result, null, 2));
508
+ } catch (error) {
509
+ console.error(JSON.stringify({
510
+ error: error.message,
511
+ timestamp: new Date().toISOString()
512
+ }, null, 2));
513
+ process.exit(1);
514
+ }
515
+ })();
516
+ }
517
+
518
+ // Export for use as module
519
+ module.exports = {
520
+ detect,
521
+ detectAsync,
522
+ invalidateCache,
523
+ detectCI,
524
+ detectCIAsync,
525
+ detectDeployment,
526
+ detectDeploymentAsync,
527
+ detectProjectType,
528
+ detectProjectTypeAsync,
529
+ detectPackageManager,
530
+ detectPackageManagerAsync,
531
+ detectBranchStrategy,
532
+ detectBranchStrategyAsync,
533
+ detectMainBranch,
534
+ detectMainBranchAsync
535
+ };