awesome-slash 2.4.4 → 2.5.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.
Files changed (144) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +88 -1
  4. package/README.md +173 -161
  5. package/SECURITY.md +25 -81
  6. package/adapters/codex/install.sh +58 -16
  7. package/adapters/opencode/install.sh +92 -23
  8. package/lib/index.js +47 -4
  9. package/lib/patterns/review-patterns.js +58 -11
  10. package/lib/patterns/slop-patterns.js +154 -147
  11. package/lib/platform/detect-platform.js +99 -350
  12. package/lib/platform/detection-configs.js +93 -0
  13. package/lib/platform/verify-tools.js +10 -78
  14. package/lib/schemas/README.md +195 -0
  15. package/lib/schemas/validator.js +247 -0
  16. package/lib/sources/custom-handler.js +199 -0
  17. package/lib/sources/policy-questions.js +239 -0
  18. package/lib/sources/source-cache.js +149 -0
  19. package/lib/state/workflow-state.js +363 -665
  20. package/lib/types/README.md +292 -0
  21. package/lib/types/agent-frontmatter.d.ts +134 -0
  22. package/lib/types/command-frontmatter.d.ts +107 -0
  23. package/lib/types/hook-frontmatter.d.ts +115 -0
  24. package/lib/types/index.d.ts +84 -0
  25. package/lib/types/plugin-manifest.d.ts +102 -0
  26. package/lib/types/skill-frontmatter.d.ts +89 -0
  27. package/lib/utils/cache-manager.js +154 -0
  28. package/lib/utils/context-optimizer.js +5 -36
  29. package/lib/utils/deprecation.js +37 -0
  30. package/lib/utils/shell-escape.js +88 -0
  31. package/mcp-server/index.js +513 -18
  32. package/package.json +6 -2
  33. package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
  34. package/plugins/deslop-around/lib/index.js +170 -0
  35. package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
  36. package/plugins/deslop-around/lib/patterns/slop-patterns.js +170 -129
  37. package/plugins/deslop-around/lib/platform/detect-platform.js +212 -123
  38. package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
  39. package/plugins/deslop-around/lib/platform/verify-tools.js +10 -1
  40. package/plugins/deslop-around/lib/schemas/README.md +195 -0
  41. package/plugins/deslop-around/lib/schemas/validator.js +205 -0
  42. package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
  43. package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
  44. package/plugins/deslop-around/lib/sources/source-cache.js +149 -0
  45. package/plugins/deslop-around/lib/state/workflow-state.js +382 -484
  46. package/plugins/deslop-around/lib/types/README.md +292 -0
  47. package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
  48. package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
  49. package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
  50. package/plugins/deslop-around/lib/types/index.d.ts +84 -0
  51. package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
  52. package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
  53. package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
  54. package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
  55. package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
  56. package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
  57. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  58. package/plugins/next-task/agents/delivery-validator.md +2 -2
  59. package/plugins/next-task/agents/implementation-agent.md +3 -4
  60. package/plugins/next-task/agents/planning-agent.md +77 -19
  61. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  62. package/plugins/next-task/agents/task-discoverer.md +164 -23
  63. package/plugins/next-task/commands/next-task.md +180 -14
  64. package/plugins/next-task/lib/index.js +170 -0
  65. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  66. package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
  67. package/plugins/next-task/lib/platform/detect-platform.js +212 -123
  68. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  69. package/plugins/next-task/lib/platform/verify-tools.js +10 -1
  70. package/plugins/next-task/lib/schemas/README.md +195 -0
  71. package/plugins/next-task/lib/schemas/validator.js +205 -0
  72. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  73. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  74. package/plugins/next-task/lib/sources/source-cache.js +149 -0
  75. package/plugins/next-task/lib/state/workflow-state.js +382 -484
  76. package/plugins/next-task/lib/types/README.md +292 -0
  77. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  78. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  79. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  80. package/plugins/next-task/lib/types/index.d.ts +84 -0
  81. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  82. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  83. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  84. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  85. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  86. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  87. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  88. package/plugins/project-review/lib/index.js +170 -0
  89. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  90. package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
  91. package/plugins/project-review/lib/platform/detect-platform.js +212 -123
  92. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  93. package/plugins/project-review/lib/platform/verify-tools.js +10 -1
  94. package/plugins/project-review/lib/schemas/README.md +195 -0
  95. package/plugins/project-review/lib/schemas/validator.js +205 -0
  96. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  97. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  98. package/plugins/project-review/lib/sources/source-cache.js +149 -0
  99. package/plugins/project-review/lib/state/workflow-state.js +382 -484
  100. package/plugins/project-review/lib/types/README.md +292 -0
  101. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  102. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  103. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  104. package/plugins/project-review/lib/types/index.d.ts +84 -0
  105. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  106. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  107. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  108. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  109. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  110. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  111. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  112. package/plugins/reality-check/agents/code-explorer.md +1 -1
  113. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  114. package/plugins/ship/lib/index.js +170 -0
  115. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  116. package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
  117. package/plugins/ship/lib/platform/detect-platform.js +212 -123
  118. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  119. package/plugins/ship/lib/platform/verify-tools.js +10 -1
  120. package/plugins/ship/lib/schemas/README.md +195 -0
  121. package/plugins/ship/lib/schemas/validator.js +205 -0
  122. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  123. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  124. package/plugins/ship/lib/sources/source-cache.js +149 -0
  125. package/plugins/ship/lib/state/workflow-state.js +382 -484
  126. package/plugins/ship/lib/types/README.md +292 -0
  127. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  128. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  129. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  130. package/plugins/ship/lib/types/index.d.ts +84 -0
  131. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  132. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  133. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  134. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  135. package/plugins/ship/lib/utils/deprecation.js +37 -0
  136. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  137. package/lib/state/workflow-state.schema.json +0 -282
  138. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  139. package/plugins/next-task/agents/policy-selector.md +0 -248
  140. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  141. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  142. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  143. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  144. package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
@@ -11,12 +11,21 @@
11
11
  */
12
12
 
13
13
  const fs = require('fs');
14
- const { execSync, exec } = require('child_process');
14
+ const path = require('path');
15
+ const { exec } = require('child_process');
15
16
  const { promisify } = require('util');
16
17
 
17
18
  const execAsync = promisify(exec);
18
19
  const fsPromises = fs.promises;
19
20
 
21
+ // Import shared utilities
22
+ const { CacheManager } = require('../utils/cache-manager');
23
+ const {
24
+ CI_CONFIGS,
25
+ DEPLOYMENT_CONFIGS,
26
+ PACKAGE_MANAGER_CONFIGS
27
+ } = require('./detection-configs');
28
+
20
29
  /**
21
30
  * Default timeout for async operations (5 seconds)
22
31
  */
@@ -38,7 +47,6 @@ function safeJSONParse(content, filename = 'unknown') {
38
47
  return null;
39
48
  }
40
49
  if (content.length > MAX_JSON_SIZE_BYTES) {
41
- // File too large - skip parsing to prevent DoS
42
50
  return null;
43
51
  }
44
52
  try {
@@ -75,69 +83,53 @@ function withTimeout(promise, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS, operation =
75
83
  * @param {number} timeoutMs - Timeout in milliseconds
76
84
  * @returns {Promise<{stdout: string, stderr: string}>}
77
85
  */
78
- async function execAsyncWithTimeout(cmd, options = {}, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS) {
86
+ async function execWithTimeout(cmd, options = {}, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS) {
79
87
  return withTimeout(execAsync(cmd, options), timeoutMs, `exec: ${cmd.substring(0, 50)}`);
80
88
  }
81
89
 
82
- // Detection cache for performance (platform rarely changes during session)
83
- let _cachedDetection = null;
84
- let _cacheExpiry = 0;
85
- const CACHE_TTL_MS = 60000; // 1 minute cache
86
-
87
- // File read cache to avoid reading the same file multiple times (#17)
88
- // Limited to prevent unbounded memory growth in long-running processes
89
- const MAX_CACHE_SIZE = 100;
90
+ // Maximum cached file size constant
90
91
  const MAX_CACHED_FILE_SIZE = 64 * 1024; // 64KB max per cached file
91
- const _fileCache = new Map();
92
- const _existsCache = new Map();
93
92
 
94
- /**
95
- * Enforce cache size limits using O(1) FIFO eviction
96
- * Uses Map's insertion order guarantee for efficient eviction
97
- * @param {Map} cache - Cache to limit
98
- * @param {number} maxSize - Maximum entries
99
- */
100
- function enforceMaxCacheSize(cache, maxSize = MAX_CACHE_SIZE) {
101
- // O(1) eviction: delete oldest entries one at a time
102
- // Map maintains insertion order, so first key is oldest
103
- while (cache.size > maxSize) {
104
- const firstKey = cache.keys().next().value;
105
- cache.delete(firstKey);
106
- }
107
- }
93
+ // Cache instances using CacheManager abstraction
94
+ const _detectionCache = new CacheManager({ maxSize: 1, ttl: 60000 });
95
+ const _fileCache = new CacheManager({ maxSize: 100, ttl: 60000, maxValueSize: MAX_CACHED_FILE_SIZE });
96
+ const _existsCache = new CacheManager({ maxSize: 100, ttl: 60000 });
108
97
 
109
98
  /**
110
- * Check if a file exists (cached)
111
- * @param {string} filepath - Path to check
112
- * @returns {boolean}
99
+ * Generic file-based detector
100
+ * @param {Array} configs - Array of {file, platform} objects
101
+ * @param {Function} existsChecker - Async function to check file existence
102
+ * @returns {Promise<string|null>} Detected platform or null
113
103
  */
114
- function existsCached(filepath) {
115
- if (_existsCache.has(filepath)) {
116
- return _existsCache.get(filepath);
104
+ async function detectFromFiles(configs, existsChecker) {
105
+ const checks = await Promise.all(
106
+ configs.map(({ file }) => existsChecker(file))
107
+ );
108
+
109
+ for (let i = 0; i < checks.length; i++) {
110
+ if (checks[i]) {
111
+ return configs[i].platform;
112
+ }
117
113
  }
118
- const exists = fs.existsSync(filepath);
119
- _existsCache.set(filepath, exists);
120
- enforceMaxCacheSize(_existsCache);
121
- return exists;
114
+ return null;
122
115
  }
123
116
 
124
117
  /**
125
- * Check if a file exists (cached, async)
118
+ * Check if a file exists (cached)
126
119
  * @param {string} filepath - Path to check
127
120
  * @returns {Promise<boolean>}
128
121
  */
129
- async function existsCachedAsync(filepath) {
130
- if (_existsCache.has(filepath)) {
131
- return _existsCache.get(filepath);
122
+ async function existsCached(filepath) {
123
+ const cached = _existsCache.get(filepath);
124
+ if (cached !== undefined) {
125
+ return cached;
132
126
  }
133
127
  try {
134
128
  await fsPromises.access(filepath);
135
129
  _existsCache.set(filepath, true);
136
- enforceMaxCacheSize(_existsCache);
137
130
  return true;
138
131
  } catch {
139
132
  _existsCache.set(filepath, false);
140
- enforceMaxCacheSize(_existsCache);
141
133
  return false;
142
134
  }
143
135
  }
@@ -145,155 +137,57 @@ async function existsCachedAsync(filepath) {
145
137
  /**
146
138
  * Read file contents (cached)
147
139
  * Only caches files smaller than MAX_CACHED_FILE_SIZE to prevent memory bloat
148
- * @param {string} filepath - Path to read
149
- * @returns {string|null}
150
- */
151
- function readFileCached(filepath) {
152
- if (_fileCache.has(filepath)) {
153
- return _fileCache.get(filepath);
154
- }
155
- try {
156
- const content = fs.readFileSync(filepath, 'utf8');
157
- // Only cache small files to prevent memory bloat
158
- if (content.length <= MAX_CACHED_FILE_SIZE) {
159
- _fileCache.set(filepath, content);
160
- enforceMaxCacheSize(_fileCache);
161
- }
162
- return content;
163
- } catch {
164
- // Cache null for missing files (small memory footprint)
165
- _fileCache.set(filepath, null);
166
- enforceMaxCacheSize(_fileCache);
167
- return null;
168
- }
169
- }
170
-
171
- /**
172
- * Read file contents (cached, async)
173
- * Only caches files smaller than MAX_CACHED_FILE_SIZE to prevent memory bloat
140
+ * Optimized: normalizes filepath to prevent cache pollution from variant paths
174
141
  * @param {string} filepath - Path to read
175
142
  * @returns {Promise<string|null>}
176
143
  */
177
- async function readFileCachedAsync(filepath) {
178
- if (_fileCache.has(filepath)) {
179
- return _fileCache.get(filepath);
144
+ async function readFileCached(filepath) {
145
+ const normalizedPath = path.resolve(filepath);
146
+
147
+ const cached = _fileCache.get(normalizedPath);
148
+ if (cached !== undefined) {
149
+ return cached;
180
150
  }
181
151
  try {
182
- const content = await fsPromises.readFile(filepath, 'utf8');
183
- // Only cache small files to prevent memory bloat
184
- if (content.length <= MAX_CACHED_FILE_SIZE) {
185
- _fileCache.set(filepath, content);
186
- enforceMaxCacheSize(_fileCache);
187
- }
152
+ const content = await fsPromises.readFile(normalizedPath, 'utf8');
153
+ _fileCache.set(normalizedPath, content);
188
154
  return content;
189
155
  } catch {
190
- // Cache null for missing files (small memory footprint)
191
- _fileCache.set(filepath, null);
192
- enforceMaxCacheSize(_fileCache);
156
+ _fileCache.set(normalizedPath, null);
193
157
  return null;
194
158
  }
195
159
  }
196
160
 
197
161
  /**
198
162
  * Detects CI platform by scanning for configuration files
199
- * @returns {string|null} CI platform name or null if not detected
200
- */
201
- function detectCI() {
202
- if (existsCached('.github/workflows')) return 'github-actions';
203
- if (existsCached('.gitlab-ci.yml')) return 'gitlab-ci';
204
- if (existsCached('.circleci/config.yml')) return 'circleci';
205
- if (existsCached('Jenkinsfile')) return 'jenkins';
206
- if (existsCached('.travis.yml')) return 'travis';
207
- return null;
208
- }
209
-
210
- /**
211
- * Detects CI platform by scanning for configuration files (async)
212
163
  * @returns {Promise<string|null>} CI platform name or null if not detected
213
164
  */
214
- async function detectCIAsync() {
215
- const checks = await Promise.all([
216
- existsCachedAsync('.github/workflows'),
217
- existsCachedAsync('.gitlab-ci.yml'),
218
- existsCachedAsync('.circleci/config.yml'),
219
- existsCachedAsync('Jenkinsfile'),
220
- existsCachedAsync('.travis.yml')
221
- ]);
222
-
223
- if (checks[0]) return 'github-actions';
224
- if (checks[1]) return 'gitlab-ci';
225
- if (checks[2]) return 'circleci';
226
- if (checks[3]) return 'jenkins';
227
- if (checks[4]) return 'travis';
228
- return null;
165
+ async function detectCI() {
166
+ return detectFromFiles(CI_CONFIGS, existsCached);
229
167
  }
230
168
 
231
169
  /**
232
170
  * Detects deployment platform by scanning for platform-specific files
233
- * @returns {string|null} Deployment platform name or null if not detected
234
- */
235
- function detectDeployment() {
236
- if (existsCached('railway.json') || existsCached('railway.toml')) return 'railway';
237
- if (existsCached('vercel.json')) return 'vercel';
238
- if (existsCached('netlify.toml') || existsCached('.netlify')) return 'netlify';
239
- if (existsCached('fly.toml')) return 'fly';
240
- if (existsCached('.platform.sh')) return 'platform-sh';
241
- if (existsCached('render.yaml')) return 'render';
242
- return null;
243
- }
244
-
245
- /**
246
- * Detects deployment platform by scanning for platform-specific files (async)
247
171
  * @returns {Promise<string|null>} Deployment platform name or null if not detected
248
172
  */
249
- async function detectDeploymentAsync() {
250
- const checks = await Promise.all([
251
- existsCachedAsync('railway.json'),
252
- existsCachedAsync('railway.toml'),
253
- existsCachedAsync('vercel.json'),
254
- existsCachedAsync('netlify.toml'),
255
- existsCachedAsync('.netlify'),
256
- existsCachedAsync('fly.toml'),
257
- existsCachedAsync('.platform.sh'),
258
- existsCachedAsync('render.yaml')
259
- ]);
260
-
261
- if (checks[0] || checks[1]) return 'railway';
262
- if (checks[2]) return 'vercel';
263
- if (checks[3] || checks[4]) return 'netlify';
264
- if (checks[5]) return 'fly';
265
- if (checks[6]) return 'platform-sh';
266
- if (checks[7]) return 'render';
267
- return null;
173
+ async function detectDeployment() {
174
+ return detectFromFiles(DEPLOYMENT_CONFIGS, existsCached);
268
175
  }
269
176
 
270
177
  /**
271
178
  * Detects project type by scanning for language-specific files
272
- * @returns {string} Project type identifier
273
- */
274
- function detectProjectType() {
275
- if (existsCached('package.json')) return 'nodejs';
276
- if (existsCached('requirements.txt') || existsCached('pyproject.toml') || existsCached('setup.py')) return 'python';
277
- if (existsCached('Cargo.toml')) return 'rust';
278
- if (existsCached('go.mod')) return 'go';
279
- if (existsCached('pom.xml') || existsCached('build.gradle')) return 'java';
280
- return 'unknown';
281
- }
282
-
283
- /**
284
- * Detects project type by scanning for language-specific files (async)
285
179
  * @returns {Promise<string>} Project type identifier
286
180
  */
287
- async function detectProjectTypeAsync() {
181
+ async function detectProjectType() {
288
182
  const checks = await Promise.all([
289
- existsCachedAsync('package.json'),
290
- existsCachedAsync('requirements.txt'),
291
- existsCachedAsync('pyproject.toml'),
292
- existsCachedAsync('setup.py'),
293
- existsCachedAsync('Cargo.toml'),
294
- existsCachedAsync('go.mod'),
295
- existsCachedAsync('pom.xml'),
296
- existsCachedAsync('build.gradle')
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')
297
191
  ]);
298
192
 
299
193
  if (checks[0]) return 'nodejs';
@@ -306,103 +200,24 @@ async function detectProjectTypeAsync() {
306
200
 
307
201
  /**
308
202
  * Detects package manager by scanning for lockfiles
309
- * @returns {string|null} Package manager name or null if not detected
310
- */
311
- function detectPackageManager() {
312
- if (existsCached('pnpm-lock.yaml')) return 'pnpm';
313
- if (existsCached('yarn.lock')) return 'yarn';
314
- if (existsCached('bun.lockb')) return 'bun';
315
- if (existsCached('package-lock.json')) return 'npm';
316
- if (existsCached('poetry.lock')) return 'poetry';
317
- if (existsCached('Pipfile.lock')) return 'pipenv';
318
- if (existsCached('Cargo.lock')) return 'cargo';
319
- if (existsCached('go.sum')) return 'go';
320
- return null;
321
- }
322
-
323
- /**
324
- * Detects package manager by scanning for lockfiles (async)
325
203
  * @returns {Promise<string|null>} Package manager name or null if not detected
326
204
  */
327
- async function detectPackageManagerAsync() {
328
- const checks = await Promise.all([
329
- existsCachedAsync('pnpm-lock.yaml'),
330
- existsCachedAsync('yarn.lock'),
331
- existsCachedAsync('bun.lockb'),
332
- existsCachedAsync('package-lock.json'),
333
- existsCachedAsync('poetry.lock'),
334
- existsCachedAsync('Pipfile.lock'),
335
- existsCachedAsync('Cargo.lock'),
336
- existsCachedAsync('go.sum')
337
- ]);
338
-
339
- if (checks[0]) return 'pnpm';
340
- if (checks[1]) return 'yarn';
341
- if (checks[2]) return 'bun';
342
- if (checks[3]) return 'npm';
343
- if (checks[4]) return 'poetry';
344
- if (checks[5]) return 'pipenv';
345
- if (checks[6]) return 'cargo';
346
- if (checks[7]) return 'go';
347
- return null;
205
+ async function detectPackageManager() {
206
+ return detectFromFiles(
207
+ PACKAGE_MANAGER_CONFIGS.map(({ file, manager }) => ({ file, platform: manager })),
208
+ existsCached
209
+ );
348
210
  }
349
211
 
350
212
  /**
351
213
  * Detects branch strategy (single-branch vs multi-branch with dev+prod)
352
- * @returns {string} 'single-branch' or 'multi-branch'
353
- */
354
- function detectBranchStrategy() {
355
- try {
356
- // Check both local and remote branches
357
- const localBranches = execSync('git branch', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
358
- let remoteBranches = '';
359
- try {
360
- remoteBranches = execSync('git branch -r', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
361
- } catch {}
362
-
363
- const allBranches = localBranches + remoteBranches;
364
-
365
- const hasStable = allBranches.includes('stable');
366
- const hasProduction = allBranches.includes('production') || allBranches.includes('prod');
367
-
368
- if (hasStable || hasProduction) {
369
- return 'multi-branch'; // dev + prod workflow
370
- }
371
-
372
- // Check deployment configs for multi-environment setup (uses cache)
373
- if (existsCached('railway.json')) {
374
- try {
375
- const content = readFileCached('railway.json');
376
- if (content) {
377
- const config = safeJSONParse(content, 'railway.json');
378
- // Validate JSON structure before accessing properties
379
- if (config &&
380
- typeof config === 'object' &&
381
- typeof config.environments === 'object' &&
382
- config.environments !== null &&
383
- Object.keys(config.environments).length > 1) {
384
- return 'multi-branch';
385
- }
386
- }
387
- } catch {}
388
- }
389
-
390
- return 'single-branch'; // main only
391
- } catch {
392
- return 'single-branch';
393
- }
394
- }
395
-
396
- /**
397
- * Detects branch strategy (single-branch vs multi-branch with dev+prod) (async)
398
214
  * @returns {Promise<string>} 'single-branch' or 'multi-branch'
399
215
  */
400
- async function detectBranchStrategyAsync() {
216
+ async function detectBranchStrategy() {
401
217
  try {
402
- // Run git commands in parallel with timeout protection
403
218
  const [localResult, remoteResult] = await Promise.all([
404
- execAsyncWithTimeout('git branch', { encoding: 'utf8' }).catch(() => ({ stdout: '' })),
405
- execAsyncWithTimeout('git branch -r', { encoding: 'utf8' }).catch(() => ({ stdout: '' }))
219
+ execWithTimeout('git branch', { encoding: 'utf8' }).catch(() => ({ stdout: '' })),
220
+ execWithTimeout('git branch -r', { encoding: 'utf8' }).catch(() => ({ stdout: '' }))
406
221
  ]);
407
222
 
408
223
  const allBranches = (localResult.stdout || '') + (remoteResult.stdout || '');
@@ -414,10 +229,9 @@ async function detectBranchStrategyAsync() {
414
229
  return 'multi-branch';
415
230
  }
416
231
 
417
- // Check deployment configs for multi-environment setup (uses cache)
418
- if (await existsCachedAsync('railway.json')) {
232
+ if (await existsCached('railway.json')) {
419
233
  try {
420
- const content = await readFileCachedAsync('railway.json');
234
+ const content = await readFileCached('railway.json');
421
235
  if (content) {
422
236
  const config = safeJSONParse(content, 'railway.json');
423
237
  if (config &&
@@ -439,43 +253,15 @@ async function detectBranchStrategyAsync() {
439
253
 
440
254
  /**
441
255
  * Detects the main branch name
442
- * @returns {string} Main branch name ('main' or 'master')
443
- */
444
- function detectMainBranch() {
445
- try {
446
- const defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
447
- encoding: 'utf8',
448
- stdio: ['pipe', 'pipe', 'ignore']
449
- })
450
- .trim()
451
- .replace('refs/remotes/origin/', '');
452
- return defaultBranch;
453
- } catch {
454
- // Fallback: check common names
455
- try {
456
- execSync('git rev-parse --verify main', {
457
- encoding: 'utf8',
458
- stdio: ['pipe', 'pipe', 'ignore']
459
- });
460
- return 'main';
461
- } catch {
462
- return 'master';
463
- }
464
- }
465
- }
466
-
467
- /**
468
- * Detects the main branch name (async)
469
256
  * @returns {Promise<string>} Main branch name ('main' or 'master')
470
257
  */
471
- async function detectMainBranchAsync() {
258
+ async function detectMainBranch() {
472
259
  try {
473
- const { stdout } = await execAsyncWithTimeout('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' });
260
+ const { stdout } = await execWithTimeout('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' });
474
261
  return stdout.trim().replace('refs/remotes/origin/', '');
475
262
  } catch {
476
- // Fallback: check common names
477
263
  try {
478
- await execAsyncWithTimeout('git rev-parse --verify main', { encoding: 'utf8' });
264
+ await execWithTimeout('git rev-parse --verify main', { encoding: 'utf8' });
479
265
  return 'main';
480
266
  } catch {
481
267
  return 'master';
@@ -484,50 +270,19 @@ async function detectMainBranchAsync() {
484
270
  }
485
271
 
486
272
  /**
487
- * Main detection function - aggregates all platform information (sync)
488
- * Uses caching to avoid repeated filesystem/git operations
489
- * @param {boolean} forceRefresh - Force cache refresh
490
- * @returns {Object} Platform configuration object
491
- */
492
- function detect(forceRefresh = false) {
493
- const now = Date.now();
494
-
495
- // Return cached result if still valid
496
- if (!forceRefresh && _cachedDetection && now < _cacheExpiry) {
497
- return _cachedDetection;
498
- }
499
-
500
- _cachedDetection = {
501
- ci: detectCI(),
502
- deployment: detectDeployment(),
503
- projectType: detectProjectType(),
504
- packageManager: detectPackageManager(),
505
- branchStrategy: detectBranchStrategy(),
506
- mainBranch: detectMainBranch(),
507
- hasPlanFile: existsCached('PLAN.md'),
508
- hasTechDebtFile: existsCached('TECHNICAL_DEBT.md'),
509
- timestamp: new Date(now).toISOString()
510
- };
511
- _cacheExpiry = now + CACHE_TTL_MS;
512
-
513
- return _cachedDetection;
514
- }
515
-
516
- /**
517
- * Main detection function - aggregates all platform information (async)
273
+ * Main detection function - aggregates all platform information
518
274
  * Uses Promise.all for parallel execution and caching
519
275
  * @param {boolean} forceRefresh - Force cache refresh
520
276
  * @returns {Promise<Object>} Platform configuration object
521
277
  */
522
- async function detectAsync(forceRefresh = false) {
523
- const now = Date.now();
524
-
525
- // Return cached result if still valid
526
- if (!forceRefresh && _cachedDetection && now < _cacheExpiry) {
527
- return _cachedDetection;
278
+ async function detect(forceRefresh = false) {
279
+ if (!forceRefresh) {
280
+ const cached = _detectionCache.get('detection');
281
+ if (cached !== undefined) {
282
+ return cached;
283
+ }
528
284
  }
529
285
 
530
- // Run all detections in parallel
531
286
  const [
532
287
  ci,
533
288
  deployment,
@@ -538,17 +293,17 @@ async function detectAsync(forceRefresh = false) {
538
293
  hasPlanFile,
539
294
  hasTechDebtFile
540
295
  ] = await Promise.all([
541
- detectCIAsync(),
542
- detectDeploymentAsync(),
543
- detectProjectTypeAsync(),
544
- detectPackageManagerAsync(),
545
- detectBranchStrategyAsync(),
546
- detectMainBranchAsync(),
547
- existsCachedAsync('PLAN.md'),
548
- existsCachedAsync('TECHNICAL_DEBT.md')
296
+ detectCI(),
297
+ detectDeployment(),
298
+ detectProjectType(),
299
+ detectPackageManager(),
300
+ detectBranchStrategy(),
301
+ detectMainBranch(),
302
+ existsCached('PLAN.md'),
303
+ existsCached('TECHNICAL_DEBT.md')
549
304
  ]);
550
305
 
551
- _cachedDetection = {
306
+ const detection = {
552
307
  ci,
553
308
  deployment,
554
309
  projectType,
@@ -557,35 +312,36 @@ async function detectAsync(forceRefresh = false) {
557
312
  mainBranch,
558
313
  hasPlanFile,
559
314
  hasTechDebtFile,
560
- timestamp: new Date(now).toISOString()
315
+ timestamp: new Date().toISOString()
561
316
  };
562
- _cacheExpiry = now + CACHE_TTL_MS;
563
317
 
564
- return _cachedDetection;
318
+ _detectionCache.set('detection', detection);
319
+ return detection;
565
320
  }
566
321
 
567
322
  /**
568
- * Invalidate the detection cache
323
+ * Invalidate all detection caches
569
324
  * Call this after making changes that affect platform detection
570
325
  */
571
326
  function invalidateCache() {
572
- _cachedDetection = null;
573
- _cacheExpiry = 0;
327
+ _detectionCache.clear();
574
328
  _fileCache.clear();
575
329
  _existsCache.clear();
576
330
  }
577
331
 
578
- // When run directly, output JSON (uses async for better performance)
332
+ // When run directly, output JSON
579
333
  if (require.main === module) {
580
334
  (async () => {
581
335
  try {
582
- const result = await detectAsync();
583
- console.log(JSON.stringify(result, null, 2));
336
+ const result = await detect();
337
+ const indent = process.stdout.isTTY ? 2 : 0;
338
+ console.log(JSON.stringify(result, null, indent));
584
339
  } catch (error) {
340
+ const indent = process.stderr.isTTY ? 2 : 0;
585
341
  console.error(JSON.stringify({
586
342
  error: error.message,
587
343
  timestamp: new Date().toISOString()
588
- }, null, 2));
344
+ }, null, indent));
589
345
  process.exit(1);
590
346
  }
591
347
  })();
@@ -594,18 +350,11 @@ if (require.main === module) {
594
350
  // Export for use as module
595
351
  module.exports = {
596
352
  detect,
597
- detectAsync,
598
353
  invalidateCache,
599
354
  detectCI,
600
- detectCIAsync,
601
355
  detectDeployment,
602
- detectDeploymentAsync,
603
356
  detectProjectType,
604
- detectProjectTypeAsync,
605
357
  detectPackageManager,
606
- detectPackageManagerAsync,
607
358
  detectBranchStrategy,
608
- detectBranchStrategyAsync,
609
- detectMainBranch,
610
- detectMainBranchAsync
359
+ detectMainBranch
611
360
  };