awesome-slash 2.4.4 → 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 +123 -1
- package/README.md +186 -159
- package/SECURITY.md +25 -81
- package/adapters/codex/install.sh +58 -16
- package/adapters/opencode/install.sh +92 -23
- package/lib/index.js +47 -4
- package/lib/patterns/review-patterns.js +58 -11
- package/lib/patterns/slop-patterns.js +154 -147
- package/lib/platform/detect-platform.js +99 -350
- package/lib/platform/detection-configs.js +93 -0
- package/lib/platform/state-dir.js +122 -0
- package/lib/platform/verify-tools.js +10 -78
- package/lib/schemas/README.md +195 -0
- package/lib/schemas/validator.js +247 -0
- package/lib/sources/custom-handler.js +199 -0
- package/lib/sources/policy-questions.js +239 -0
- package/lib/sources/source-cache.js +164 -0
- package/lib/state/workflow-state.js +368 -665
- package/lib/types/README.md +292 -0
- package/lib/types/agent-frontmatter.d.ts +134 -0
- package/lib/types/command-frontmatter.d.ts +107 -0
- package/lib/types/hook-frontmatter.d.ts +115 -0
- package/lib/types/index.d.ts +84 -0
- package/lib/types/plugin-manifest.d.ts +102 -0
- package/lib/types/skill-frontmatter.d.ts +89 -0
- package/lib/utils/cache-manager.js +154 -0
- package/lib/utils/context-optimizer.js +5 -36
- package/lib/utils/deprecation.js +37 -0
- package/lib/utils/shell-escape.js +88 -0
- package/mcp-server/index.js +513 -22
- package/package.json +6 -2
- package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop-around/lib/index.js +170 -0
- package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
- package/plugins/deslop-around/lib/patterns/slop-patterns.js +169 -129
- package/plugins/deslop-around/lib/platform/detect-platform.js +162 -316
- package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
- package/plugins/deslop-around/lib/platform/state-dir.js +122 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +10 -78
- package/plugins/deslop-around/lib/schemas/README.md +195 -0
- package/plugins/deslop-around/lib/schemas/validator.js +247 -0
- package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
- package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
- package/plugins/deslop-around/lib/sources/source-cache.js +164 -0
- package/plugins/deslop-around/lib/state/workflow-state.js +387 -484
- package/plugins/deslop-around/lib/types/README.md +292 -0
- package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/deslop-around/lib/types/index.d.ts +84 -0
- package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
- package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
- package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
- package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/agents/delivery-validator.md +2 -2
- package/plugins/next-task/agents/implementation-agent.md +3 -4
- package/plugins/next-task/agents/planning-agent.md +77 -19
- package/plugins/next-task/agents/review-orchestrator.md +21 -122
- package/plugins/next-task/agents/task-discoverer.md +164 -23
- package/plugins/next-task/commands/next-task.md +180 -14
- package/plugins/next-task/lib/index.js +170 -0
- package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
- package/plugins/next-task/lib/patterns/slop-patterns.js +169 -129
- package/plugins/next-task/lib/platform/detect-platform.js +162 -316
- package/plugins/next-task/lib/platform/detection-configs.js +93 -0
- package/plugins/next-task/lib/platform/state-dir.js +122 -0
- package/plugins/next-task/lib/platform/verify-tools.js +10 -78
- package/plugins/next-task/lib/schemas/README.md +195 -0
- package/plugins/next-task/lib/schemas/validator.js +247 -0
- package/plugins/next-task/lib/sources/custom-handler.js +199 -0
- package/plugins/next-task/lib/sources/policy-questions.js +239 -0
- package/plugins/next-task/lib/sources/source-cache.js +164 -0
- package/plugins/next-task/lib/state/workflow-state.js +387 -484
- package/plugins/next-task/lib/types/README.md +292 -0
- package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/next-task/lib/types/index.d.ts +84 -0
- package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/next-task/lib/utils/cache-manager.js +154 -0
- package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
- package/plugins/next-task/lib/utils/deprecation.js +37 -0
- package/plugins/next-task/lib/utils/shell-escape.js +88 -0
- package/plugins/project-review/.claude-plugin/plugin.json +1 -1
- package/plugins/project-review/lib/index.js +170 -0
- package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
- package/plugins/project-review/lib/patterns/slop-patterns.js +169 -129
- package/plugins/project-review/lib/platform/detect-platform.js +162 -316
- package/plugins/project-review/lib/platform/detection-configs.js +93 -0
- package/plugins/project-review/lib/platform/state-dir.js +122 -0
- package/plugins/project-review/lib/platform/verify-tools.js +10 -78
- package/plugins/project-review/lib/schemas/README.md +195 -0
- package/plugins/project-review/lib/schemas/validator.js +247 -0
- package/plugins/project-review/lib/sources/custom-handler.js +199 -0
- package/plugins/project-review/lib/sources/policy-questions.js +239 -0
- package/plugins/project-review/lib/sources/source-cache.js +164 -0
- package/plugins/project-review/lib/state/workflow-state.js +387 -484
- package/plugins/project-review/lib/types/README.md +292 -0
- package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/project-review/lib/types/index.d.ts +84 -0
- package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/project-review/lib/utils/cache-manager.js +154 -0
- package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
- package/plugins/project-review/lib/utils/deprecation.js +37 -0
- package/plugins/project-review/lib/utils/shell-escape.js +88 -0
- package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
- package/plugins/reality-check/agents/code-explorer.md +1 -1
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/lib/index.js +170 -0
- package/plugins/ship/lib/patterns/review-patterns.js +58 -11
- package/plugins/ship/lib/patterns/slop-patterns.js +169 -129
- package/plugins/ship/lib/platform/detect-platform.js +162 -316
- package/plugins/ship/lib/platform/detection-configs.js +93 -0
- package/plugins/ship/lib/platform/state-dir.js +122 -0
- package/plugins/ship/lib/platform/verify-tools.js +10 -78
- package/plugins/ship/lib/schemas/README.md +195 -0
- package/plugins/ship/lib/schemas/validator.js +247 -0
- package/plugins/ship/lib/sources/custom-handler.js +199 -0
- package/plugins/ship/lib/sources/policy-questions.js +239 -0
- package/plugins/ship/lib/sources/source-cache.js +164 -0
- package/plugins/ship/lib/state/workflow-state.js +387 -484
- package/plugins/ship/lib/types/README.md +292 -0
- package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/ship/lib/types/index.d.ts +84 -0
- package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/ship/lib/utils/cache-manager.js +154 -0
- package/plugins/ship/lib/utils/context-optimizer.js +115 -37
- package/plugins/ship/lib/utils/deprecation.js +37 -0
- package/plugins/ship/lib/utils/shell-escape.js +88 -0
- package/scripts/install/codex.sh +216 -72
- package/scripts/install/opencode.sh +197 -21
- package/lib/state/workflow-state.schema.json +0 -282
- package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
- package/plugins/next-task/agents/policy-selector.md +0 -248
- package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
- package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
- package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
- package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
- package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
|
@@ -11,43 +11,118 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
const fs = require('fs');
|
|
14
|
-
const
|
|
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
|
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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');
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Default timeout for async operations (5 seconds)
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_ASYNC_TIMEOUT_MS = 5000;
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
|
-
*
|
|
31
|
-
* @param {string} filepath - Path to check
|
|
32
|
-
* @returns {boolean}
|
|
35
|
+
* Maximum JSON file size to parse (1MB) - prevents DoS via large files
|
|
33
36
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const MAX_JSON_SIZE_BYTES = 1024 * 1024;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Safely parse JSON content with size limit
|
|
41
|
+
* @param {string} content - JSON string to parse
|
|
42
|
+
* @param {string} filename - Filename for error messages
|
|
43
|
+
* @returns {Object|null} Parsed object or null if invalid/too large
|
|
44
|
+
*/
|
|
45
|
+
function safeJSONParse(content, filename = 'unknown') {
|
|
46
|
+
if (!content || typeof content !== 'string') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (content.length > MAX_JSON_SIZE_BYTES) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(content);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
37
56
|
}
|
|
38
|
-
const exists = fs.existsSync(filepath);
|
|
39
|
-
_existsCache.set(filepath, exists);
|
|
40
|
-
return exists;
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
/**
|
|
44
|
-
*
|
|
60
|
+
* Wrap a promise with a timeout
|
|
61
|
+
* @param {Promise} promise - Promise to wrap
|
|
62
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
63
|
+
* @param {string} operation - Operation name for error message
|
|
64
|
+
* @returns {Promise} Promise that rejects on timeout
|
|
65
|
+
*/
|
|
66
|
+
function withTimeout(promise, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS, operation = 'operation') {
|
|
67
|
+
let timeoutId;
|
|
68
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
69
|
+
timeoutId = setTimeout(() => {
|
|
70
|
+
reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
|
|
71
|
+
}, timeoutMs);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Execute a command with timeout protection
|
|
81
|
+
* @param {string} cmd - Command to execute
|
|
82
|
+
* @param {Object} options - exec options
|
|
83
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
84
|
+
* @returns {Promise<{stdout: string, stderr: string}>}
|
|
85
|
+
*/
|
|
86
|
+
async function execWithTimeout(cmd, options = {}, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS) {
|
|
87
|
+
return withTimeout(execAsync(cmd, options), timeoutMs, `exec: ${cmd.substring(0, 50)}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Maximum cached file size constant
|
|
91
|
+
const MAX_CACHED_FILE_SIZE = 64 * 1024; // 64KB max per cached file
|
|
92
|
+
|
|
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 });
|
|
97
|
+
|
|
98
|
+
/**
|
|
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
|
|
103
|
+
*/
|
|
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
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if a file exists (cached)
|
|
45
119
|
* @param {string} filepath - Path to check
|
|
46
120
|
* @returns {Promise<boolean>}
|
|
47
121
|
*/
|
|
48
|
-
async function
|
|
49
|
-
|
|
50
|
-
|
|
122
|
+
async function existsCached(filepath) {
|
|
123
|
+
const cached = _existsCache.get(filepath);
|
|
124
|
+
if (cached !== undefined) {
|
|
125
|
+
return cached;
|
|
51
126
|
}
|
|
52
127
|
try {
|
|
53
128
|
await fsPromises.access(filepath);
|
|
@@ -61,142 +136,58 @@ async function existsCachedAsync(filepath) {
|
|
|
61
136
|
|
|
62
137
|
/**
|
|
63
138
|
* Read file contents (cached)
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*/
|
|
67
|
-
function readFileCached(filepath) {
|
|
68
|
-
if (_fileCache.has(filepath)) {
|
|
69
|
-
return _fileCache.get(filepath);
|
|
70
|
-
}
|
|
71
|
-
try {
|
|
72
|
-
const content = fs.readFileSync(filepath, 'utf8');
|
|
73
|
-
_fileCache.set(filepath, content);
|
|
74
|
-
return content;
|
|
75
|
-
} catch {
|
|
76
|
-
_fileCache.set(filepath, null);
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Read file contents (cached, async)
|
|
139
|
+
* Only caches files smaller than MAX_CACHED_FILE_SIZE to prevent memory bloat
|
|
140
|
+
* Optimized: normalizes filepath to prevent cache pollution from variant paths
|
|
83
141
|
* @param {string} filepath - Path to read
|
|
84
142
|
* @returns {Promise<string|null>}
|
|
85
143
|
*/
|
|
86
|
-
async function
|
|
87
|
-
|
|
88
|
-
|
|
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;
|
|
89
150
|
}
|
|
90
151
|
try {
|
|
91
|
-
const content = await fsPromises.readFile(
|
|
92
|
-
_fileCache.set(
|
|
152
|
+
const content = await fsPromises.readFile(normalizedPath, 'utf8');
|
|
153
|
+
_fileCache.set(normalizedPath, content);
|
|
93
154
|
return content;
|
|
94
155
|
} catch {
|
|
95
|
-
_fileCache.set(
|
|
156
|
+
_fileCache.set(normalizedPath, null);
|
|
96
157
|
return null;
|
|
97
158
|
}
|
|
98
159
|
}
|
|
99
160
|
|
|
100
161
|
/**
|
|
101
162
|
* Detects CI platform by scanning for configuration files
|
|
102
|
-
* @returns {string|null} CI platform name or null if not detected
|
|
103
|
-
*/
|
|
104
|
-
function detectCI() {
|
|
105
|
-
if (existsCached('.github/workflows')) return 'github-actions';
|
|
106
|
-
if (existsCached('.gitlab-ci.yml')) return 'gitlab-ci';
|
|
107
|
-
if (existsCached('.circleci/config.yml')) return 'circleci';
|
|
108
|
-
if (existsCached('Jenkinsfile')) return 'jenkins';
|
|
109
|
-
if (existsCached('.travis.yml')) return 'travis';
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Detects CI platform by scanning for configuration files (async)
|
|
115
163
|
* @returns {Promise<string|null>} CI platform name or null if not detected
|
|
116
164
|
*/
|
|
117
|
-
async function
|
|
118
|
-
|
|
119
|
-
existsCachedAsync('.github/workflows'),
|
|
120
|
-
existsCachedAsync('.gitlab-ci.yml'),
|
|
121
|
-
existsCachedAsync('.circleci/config.yml'),
|
|
122
|
-
existsCachedAsync('Jenkinsfile'),
|
|
123
|
-
existsCachedAsync('.travis.yml')
|
|
124
|
-
]);
|
|
125
|
-
|
|
126
|
-
if (checks[0]) return 'github-actions';
|
|
127
|
-
if (checks[1]) return 'gitlab-ci';
|
|
128
|
-
if (checks[2]) return 'circleci';
|
|
129
|
-
if (checks[3]) return 'jenkins';
|
|
130
|
-
if (checks[4]) return 'travis';
|
|
131
|
-
return null;
|
|
165
|
+
async function detectCI() {
|
|
166
|
+
return detectFromFiles(CI_CONFIGS, existsCached);
|
|
132
167
|
}
|
|
133
168
|
|
|
134
169
|
/**
|
|
135
170
|
* Detects deployment platform by scanning for platform-specific files
|
|
136
|
-
* @returns {string|null} Deployment platform name or null if not detected
|
|
137
|
-
*/
|
|
138
|
-
function detectDeployment() {
|
|
139
|
-
if (existsCached('railway.json') || existsCached('railway.toml')) return 'railway';
|
|
140
|
-
if (existsCached('vercel.json')) return 'vercel';
|
|
141
|
-
if (existsCached('netlify.toml') || existsCached('.netlify')) return 'netlify';
|
|
142
|
-
if (existsCached('fly.toml')) return 'fly';
|
|
143
|
-
if (existsCached('.platform.sh')) return 'platform-sh';
|
|
144
|
-
if (existsCached('render.yaml')) return 'render';
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Detects deployment platform by scanning for platform-specific files (async)
|
|
150
171
|
* @returns {Promise<string|null>} Deployment platform name or null if not detected
|
|
151
172
|
*/
|
|
152
|
-
async function
|
|
153
|
-
|
|
154
|
-
existsCachedAsync('railway.json'),
|
|
155
|
-
existsCachedAsync('railway.toml'),
|
|
156
|
-
existsCachedAsync('vercel.json'),
|
|
157
|
-
existsCachedAsync('netlify.toml'),
|
|
158
|
-
existsCachedAsync('.netlify'),
|
|
159
|
-
existsCachedAsync('fly.toml'),
|
|
160
|
-
existsCachedAsync('.platform.sh'),
|
|
161
|
-
existsCachedAsync('render.yaml')
|
|
162
|
-
]);
|
|
163
|
-
|
|
164
|
-
if (checks[0] || checks[1]) return 'railway';
|
|
165
|
-
if (checks[2]) return 'vercel';
|
|
166
|
-
if (checks[3] || checks[4]) return 'netlify';
|
|
167
|
-
if (checks[5]) return 'fly';
|
|
168
|
-
if (checks[6]) return 'platform-sh';
|
|
169
|
-
if (checks[7]) return 'render';
|
|
170
|
-
return null;
|
|
173
|
+
async function detectDeployment() {
|
|
174
|
+
return detectFromFiles(DEPLOYMENT_CONFIGS, existsCached);
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
/**
|
|
174
178
|
* Detects project type by scanning for language-specific files
|
|
175
|
-
* @returns {string} Project type identifier
|
|
176
|
-
*/
|
|
177
|
-
function detectProjectType() {
|
|
178
|
-
if (existsCached('package.json')) return 'nodejs';
|
|
179
|
-
if (existsCached('requirements.txt') || existsCached('pyproject.toml') || existsCached('setup.py')) return 'python';
|
|
180
|
-
if (existsCached('Cargo.toml')) return 'rust';
|
|
181
|
-
if (existsCached('go.mod')) return 'go';
|
|
182
|
-
if (existsCached('pom.xml') || existsCached('build.gradle')) return 'java';
|
|
183
|
-
return 'unknown';
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Detects project type by scanning for language-specific files (async)
|
|
188
179
|
* @returns {Promise<string>} Project type identifier
|
|
189
180
|
*/
|
|
190
|
-
async function
|
|
181
|
+
async function detectProjectType() {
|
|
191
182
|
const checks = await Promise.all([
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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')
|
|
200
191
|
]);
|
|
201
192
|
|
|
202
193
|
if (checks[0]) return 'nodejs';
|
|
@@ -209,103 +200,24 @@ async function detectProjectTypeAsync() {
|
|
|
209
200
|
|
|
210
201
|
/**
|
|
211
202
|
* Detects package manager by scanning for lockfiles
|
|
212
|
-
* @returns {string|null} Package manager name or null if not detected
|
|
213
|
-
*/
|
|
214
|
-
function detectPackageManager() {
|
|
215
|
-
if (existsCached('pnpm-lock.yaml')) return 'pnpm';
|
|
216
|
-
if (existsCached('yarn.lock')) return 'yarn';
|
|
217
|
-
if (existsCached('bun.lockb')) return 'bun';
|
|
218
|
-
if (existsCached('package-lock.json')) return 'npm';
|
|
219
|
-
if (existsCached('poetry.lock')) return 'poetry';
|
|
220
|
-
if (existsCached('Pipfile.lock')) return 'pipenv';
|
|
221
|
-
if (existsCached('Cargo.lock')) return 'cargo';
|
|
222
|
-
if (existsCached('go.sum')) return 'go';
|
|
223
|
-
return null;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Detects package manager by scanning for lockfiles (async)
|
|
228
203
|
* @returns {Promise<string|null>} Package manager name or null if not detected
|
|
229
204
|
*/
|
|
230
|
-
async function
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
existsCachedAsync('package-lock.json'),
|
|
236
|
-
existsCachedAsync('poetry.lock'),
|
|
237
|
-
existsCachedAsync('Pipfile.lock'),
|
|
238
|
-
existsCachedAsync('Cargo.lock'),
|
|
239
|
-
existsCachedAsync('go.sum')
|
|
240
|
-
]);
|
|
241
|
-
|
|
242
|
-
if (checks[0]) return 'pnpm';
|
|
243
|
-
if (checks[1]) return 'yarn';
|
|
244
|
-
if (checks[2]) return 'bun';
|
|
245
|
-
if (checks[3]) return 'npm';
|
|
246
|
-
if (checks[4]) return 'poetry';
|
|
247
|
-
if (checks[5]) return 'pipenv';
|
|
248
|
-
if (checks[6]) return 'cargo';
|
|
249
|
-
if (checks[7]) return 'go';
|
|
250
|
-
return null;
|
|
205
|
+
async function detectPackageManager() {
|
|
206
|
+
return detectFromFiles(
|
|
207
|
+
PACKAGE_MANAGER_CONFIGS.map(({ file, manager }) => ({ file, platform: manager })),
|
|
208
|
+
existsCached
|
|
209
|
+
);
|
|
251
210
|
}
|
|
252
211
|
|
|
253
212
|
/**
|
|
254
213
|
* Detects branch strategy (single-branch vs multi-branch with dev+prod)
|
|
255
|
-
* @returns {string} 'single-branch' or 'multi-branch'
|
|
256
|
-
*/
|
|
257
|
-
function detectBranchStrategy() {
|
|
258
|
-
try {
|
|
259
|
-
// Check both local and remote branches
|
|
260
|
-
const localBranches = execSync('git branch', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
261
|
-
let remoteBranches = '';
|
|
262
|
-
try {
|
|
263
|
-
remoteBranches = execSync('git branch -r', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
264
|
-
} catch {}
|
|
265
|
-
|
|
266
|
-
const allBranches = localBranches + remoteBranches;
|
|
267
|
-
|
|
268
|
-
const hasStable = allBranches.includes('stable');
|
|
269
|
-
const hasProduction = allBranches.includes('production') || allBranches.includes('prod');
|
|
270
|
-
|
|
271
|
-
if (hasStable || hasProduction) {
|
|
272
|
-
return 'multi-branch'; // dev + prod workflow
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Check deployment configs for multi-environment setup (uses cache)
|
|
276
|
-
if (existsCached('railway.json')) {
|
|
277
|
-
try {
|
|
278
|
-
const content = readFileCached('railway.json');
|
|
279
|
-
if (content) {
|
|
280
|
-
const config = JSON.parse(content);
|
|
281
|
-
// Validate JSON structure before accessing properties
|
|
282
|
-
if (config &&
|
|
283
|
-
typeof config === 'object' &&
|
|
284
|
-
typeof config.environments === 'object' &&
|
|
285
|
-
config.environments !== null &&
|
|
286
|
-
Object.keys(config.environments).length > 1) {
|
|
287
|
-
return 'multi-branch';
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
} catch {}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return 'single-branch'; // main only
|
|
294
|
-
} catch {
|
|
295
|
-
return 'single-branch';
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Detects branch strategy (single-branch vs multi-branch with dev+prod) (async)
|
|
301
214
|
* @returns {Promise<string>} 'single-branch' or 'multi-branch'
|
|
302
215
|
*/
|
|
303
|
-
async function
|
|
216
|
+
async function detectBranchStrategy() {
|
|
304
217
|
try {
|
|
305
|
-
// Run git commands in parallel
|
|
306
218
|
const [localResult, remoteResult] = await Promise.all([
|
|
307
|
-
|
|
308
|
-
|
|
219
|
+
execWithTimeout('git branch', { encoding: 'utf8' }).catch(() => ({ stdout: '' })),
|
|
220
|
+
execWithTimeout('git branch -r', { encoding: 'utf8' }).catch(() => ({ stdout: '' }))
|
|
309
221
|
]);
|
|
310
222
|
|
|
311
223
|
const allBranches = (localResult.stdout || '') + (remoteResult.stdout || '');
|
|
@@ -317,12 +229,11 @@ async function detectBranchStrategyAsync() {
|
|
|
317
229
|
return 'multi-branch';
|
|
318
230
|
}
|
|
319
231
|
|
|
320
|
-
|
|
321
|
-
if (await existsCachedAsync('railway.json')) {
|
|
232
|
+
if (await existsCached('railway.json')) {
|
|
322
233
|
try {
|
|
323
|
-
const content = await
|
|
234
|
+
const content = await readFileCached('railway.json');
|
|
324
235
|
if (content) {
|
|
325
|
-
const config =
|
|
236
|
+
const config = safeJSONParse(content, 'railway.json');
|
|
326
237
|
if (config &&
|
|
327
238
|
typeof config === 'object' &&
|
|
328
239
|
typeof config.environments === 'object' &&
|
|
@@ -342,43 +253,15 @@ async function detectBranchStrategyAsync() {
|
|
|
342
253
|
|
|
343
254
|
/**
|
|
344
255
|
* Detects the main branch name
|
|
345
|
-
* @returns {string} Main branch name ('main' or 'master')
|
|
346
|
-
*/
|
|
347
|
-
function detectMainBranch() {
|
|
348
|
-
try {
|
|
349
|
-
const defaultBranch = execSync('git symbolic-ref refs/remotes/origin/HEAD', {
|
|
350
|
-
encoding: 'utf8',
|
|
351
|
-
stdio: ['pipe', 'pipe', 'ignore']
|
|
352
|
-
})
|
|
353
|
-
.trim()
|
|
354
|
-
.replace('refs/remotes/origin/', '');
|
|
355
|
-
return defaultBranch;
|
|
356
|
-
} catch {
|
|
357
|
-
// Fallback: check common names
|
|
358
|
-
try {
|
|
359
|
-
execSync('git rev-parse --verify main', {
|
|
360
|
-
encoding: 'utf8',
|
|
361
|
-
stdio: ['pipe', 'pipe', 'ignore']
|
|
362
|
-
});
|
|
363
|
-
return 'main';
|
|
364
|
-
} catch {
|
|
365
|
-
return 'master';
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Detects the main branch name (async)
|
|
372
256
|
* @returns {Promise<string>} Main branch name ('main' or 'master')
|
|
373
257
|
*/
|
|
374
|
-
async function
|
|
258
|
+
async function detectMainBranch() {
|
|
375
259
|
try {
|
|
376
|
-
const { stdout } = await
|
|
260
|
+
const { stdout } = await execWithTimeout('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' });
|
|
377
261
|
return stdout.trim().replace('refs/remotes/origin/', '');
|
|
378
262
|
} catch {
|
|
379
|
-
// Fallback: check common names
|
|
380
263
|
try {
|
|
381
|
-
await
|
|
264
|
+
await execWithTimeout('git rev-parse --verify main', { encoding: 'utf8' });
|
|
382
265
|
return 'main';
|
|
383
266
|
} catch {
|
|
384
267
|
return 'master';
|
|
@@ -387,50 +270,19 @@ async function detectMainBranchAsync() {
|
|
|
387
270
|
}
|
|
388
271
|
|
|
389
272
|
/**
|
|
390
|
-
* Main detection function - aggregates all platform information
|
|
391
|
-
* Uses caching to avoid repeated filesystem/git operations
|
|
392
|
-
* @param {boolean} forceRefresh - Force cache refresh
|
|
393
|
-
* @returns {Object} Platform configuration object
|
|
394
|
-
*/
|
|
395
|
-
function detect(forceRefresh = false) {
|
|
396
|
-
const now = Date.now();
|
|
397
|
-
|
|
398
|
-
// Return cached result if still valid
|
|
399
|
-
if (!forceRefresh && _cachedDetection && now < _cacheExpiry) {
|
|
400
|
-
return _cachedDetection;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
_cachedDetection = {
|
|
404
|
-
ci: detectCI(),
|
|
405
|
-
deployment: detectDeployment(),
|
|
406
|
-
projectType: detectProjectType(),
|
|
407
|
-
packageManager: detectPackageManager(),
|
|
408
|
-
branchStrategy: detectBranchStrategy(),
|
|
409
|
-
mainBranch: detectMainBranch(),
|
|
410
|
-
hasPlanFile: existsCached('PLAN.md'),
|
|
411
|
-
hasTechDebtFile: existsCached('TECHNICAL_DEBT.md'),
|
|
412
|
-
timestamp: new Date(now).toISOString()
|
|
413
|
-
};
|
|
414
|
-
_cacheExpiry = now + CACHE_TTL_MS;
|
|
415
|
-
|
|
416
|
-
return _cachedDetection;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Main detection function - aggregates all platform information (async)
|
|
273
|
+
* Main detection function - aggregates all platform information
|
|
421
274
|
* Uses Promise.all for parallel execution and caching
|
|
422
275
|
* @param {boolean} forceRefresh - Force cache refresh
|
|
423
276
|
* @returns {Promise<Object>} Platform configuration object
|
|
424
277
|
*/
|
|
425
|
-
async function
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
278
|
+
async function detect(forceRefresh = false) {
|
|
279
|
+
if (!forceRefresh) {
|
|
280
|
+
const cached = _detectionCache.get('detection');
|
|
281
|
+
if (cached !== undefined) {
|
|
282
|
+
return cached;
|
|
283
|
+
}
|
|
431
284
|
}
|
|
432
285
|
|
|
433
|
-
// Run all detections in parallel
|
|
434
286
|
const [
|
|
435
287
|
ci,
|
|
436
288
|
deployment,
|
|
@@ -441,17 +293,17 @@ async function detectAsync(forceRefresh = false) {
|
|
|
441
293
|
hasPlanFile,
|
|
442
294
|
hasTechDebtFile
|
|
443
295
|
] = await Promise.all([
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
296
|
+
detectCI(),
|
|
297
|
+
detectDeployment(),
|
|
298
|
+
detectProjectType(),
|
|
299
|
+
detectPackageManager(),
|
|
300
|
+
detectBranchStrategy(),
|
|
301
|
+
detectMainBranch(),
|
|
302
|
+
existsCached('PLAN.md'),
|
|
303
|
+
existsCached('TECHNICAL_DEBT.md')
|
|
452
304
|
]);
|
|
453
305
|
|
|
454
|
-
|
|
306
|
+
const detection = {
|
|
455
307
|
ci,
|
|
456
308
|
deployment,
|
|
457
309
|
projectType,
|
|
@@ -460,35 +312,36 @@ async function detectAsync(forceRefresh = false) {
|
|
|
460
312
|
mainBranch,
|
|
461
313
|
hasPlanFile,
|
|
462
314
|
hasTechDebtFile,
|
|
463
|
-
timestamp: new Date(
|
|
315
|
+
timestamp: new Date().toISOString()
|
|
464
316
|
};
|
|
465
|
-
_cacheExpiry = now + CACHE_TTL_MS;
|
|
466
317
|
|
|
467
|
-
|
|
318
|
+
_detectionCache.set('detection', detection);
|
|
319
|
+
return detection;
|
|
468
320
|
}
|
|
469
321
|
|
|
470
322
|
/**
|
|
471
|
-
* Invalidate
|
|
323
|
+
* Invalidate all detection caches
|
|
472
324
|
* Call this after making changes that affect platform detection
|
|
473
325
|
*/
|
|
474
326
|
function invalidateCache() {
|
|
475
|
-
|
|
476
|
-
_cacheExpiry = 0;
|
|
327
|
+
_detectionCache.clear();
|
|
477
328
|
_fileCache.clear();
|
|
478
329
|
_existsCache.clear();
|
|
479
330
|
}
|
|
480
331
|
|
|
481
|
-
// When run directly, output JSON
|
|
332
|
+
// When run directly, output JSON
|
|
482
333
|
if (require.main === module) {
|
|
483
334
|
(async () => {
|
|
484
335
|
try {
|
|
485
|
-
const result = await
|
|
486
|
-
|
|
336
|
+
const result = await detect();
|
|
337
|
+
const indent = process.stdout.isTTY ? 2 : 0;
|
|
338
|
+
console.log(JSON.stringify(result, null, indent));
|
|
487
339
|
} catch (error) {
|
|
340
|
+
const indent = process.stderr.isTTY ? 2 : 0;
|
|
488
341
|
console.error(JSON.stringify({
|
|
489
342
|
error: error.message,
|
|
490
343
|
timestamp: new Date().toISOString()
|
|
491
|
-
}, null,
|
|
344
|
+
}, null, indent));
|
|
492
345
|
process.exit(1);
|
|
493
346
|
}
|
|
494
347
|
})();
|
|
@@ -497,18 +350,11 @@ if (require.main === module) {
|
|
|
497
350
|
// Export for use as module
|
|
498
351
|
module.exports = {
|
|
499
352
|
detect,
|
|
500
|
-
detectAsync,
|
|
501
353
|
invalidateCache,
|
|
502
354
|
detectCI,
|
|
503
|
-
detectCIAsync,
|
|
504
355
|
detectDeployment,
|
|
505
|
-
detectDeploymentAsync,
|
|
506
356
|
detectProjectType,
|
|
507
|
-
detectProjectTypeAsync,
|
|
508
357
|
detectPackageManager,
|
|
509
|
-
detectPackageManagerAsync,
|
|
510
358
|
detectBranchStrategy,
|
|
511
|
-
|
|
512
|
-
detectMainBranch,
|
|
513
|
-
detectMainBranchAsync
|
|
359
|
+
detectMainBranch
|
|
514
360
|
};
|