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
|
@@ -10,75 +10,19 @@
|
|
|
10
10
|
* @license MIT
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const {
|
|
13
|
+
const { spawn } = require('child_process');
|
|
14
14
|
|
|
15
15
|
// Detect Windows platform
|
|
16
16
|
const isWindows = process.platform === 'win32';
|
|
17
17
|
|
|
18
|
-
// Import shared deprecation utilities
|
|
19
|
-
const { warnDeprecation, _resetDeprecationWarnings } = require('../utils/deprecation');
|
|
20
|
-
|
|
21
18
|
/**
|
|
22
|
-
* Checks if a tool is available and returns its version
|
|
23
|
-
* Uses safe execution methods to avoid shell injection vulnerabilities
|
|
24
|
-
* @deprecated Use checkToolAsync() instead. Will be removed in v3.0.0.
|
|
25
|
-
* @param {string} command - Command to check (e.g., 'git', 'node')
|
|
26
|
-
* @param {string} versionFlag - Flag to get version (default: '--version')
|
|
27
|
-
* @returns {Object} { available: boolean, version: string|null }
|
|
28
|
-
*/
|
|
29
|
-
function checkTool(command, versionFlag = '--version') {
|
|
30
|
-
warnDeprecation('checkTool', 'checkToolAsync');
|
|
31
|
-
// Validate command contains only safe characters (alphanumeric, underscore, hyphen)
|
|
32
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(command)) {
|
|
33
|
-
return { available: false, version: null };
|
|
34
|
-
}
|
|
35
|
-
// Validate versionFlag contains only safe characters
|
|
36
|
-
if (!/^[a-zA-Z0-9_-]+$/.test(versionFlag)) {
|
|
37
|
-
return { available: false, version: null };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
let output;
|
|
42
|
-
|
|
43
|
-
if (isWindows) {
|
|
44
|
-
// On Windows, use spawnSync with shell to handle .cmd/.bat scripts
|
|
45
|
-
// Input is validated above so this is safe
|
|
46
|
-
const result = spawnSync(command, [versionFlag], {
|
|
47
|
-
encoding: 'utf8',
|
|
48
|
-
stdio: ['pipe', 'pipe', 'ignore'],
|
|
49
|
-
timeout: 5000,
|
|
50
|
-
windowsHide: true,
|
|
51
|
-
shell: true
|
|
52
|
-
});
|
|
53
|
-
if (result.error || result.status !== 0) {
|
|
54
|
-
return { available: false, version: null };
|
|
55
|
-
}
|
|
56
|
-
output = (result.stdout || '').trim();
|
|
57
|
-
} else {
|
|
58
|
-
// On Unix, use execFileSync (more secure, no shell)
|
|
59
|
-
output = execFileSync(command, [versionFlag], {
|
|
60
|
-
encoding: 'utf8',
|
|
61
|
-
stdio: ['pipe', 'pipe', 'ignore'],
|
|
62
|
-
timeout: 5000
|
|
63
|
-
}).trim();
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Extract version from first line
|
|
67
|
-
const version = output.split('\n')[0];
|
|
68
|
-
return { available: true, version };
|
|
69
|
-
} catch {
|
|
70
|
-
return { available: false, version: null };
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Checks if a tool is available and returns its version (async)
|
|
19
|
+
* Checks if a tool is available and returns its version
|
|
76
20
|
* Uses safe execution methods to avoid shell injection vulnerabilities
|
|
77
21
|
* @param {string} command - Command to check (e.g., 'git', 'node')
|
|
78
22
|
* @param {string} versionFlag - Flag to get version (default: '--version')
|
|
79
23
|
* @returns {Promise<Object>} { available: boolean, version: string|null }
|
|
80
24
|
*/
|
|
81
|
-
function
|
|
25
|
+
function checkTool(command, versionFlag = '--version') {
|
|
82
26
|
return new Promise((resolve) => {
|
|
83
27
|
// Validate command contains only safe characters (alphanumeric, underscore, hyphen)
|
|
84
28
|
if (!/^[a-zA-Z0-9_-]+$/.test(command)) {
|
|
@@ -92,8 +36,7 @@ function checkToolAsync(command, versionFlag = '--version') {
|
|
|
92
36
|
let child;
|
|
93
37
|
|
|
94
38
|
if (isWindows) {
|
|
95
|
-
// On Windows, spawn shell directly with command as single argument
|
|
96
|
-
// Input is validated above so this is safe
|
|
39
|
+
// On Windows, spawn shell directly with command as single argument
|
|
97
40
|
child = spawn('cmd.exe', ['/c', command, versionFlag], {
|
|
98
41
|
stdio: ['pipe', 'pipe', 'ignore'],
|
|
99
42
|
windowsHide: true
|
|
@@ -183,31 +126,15 @@ const TOOL_DEFINITIONS = [
|
|
|
183
126
|
];
|
|
184
127
|
|
|
185
128
|
/**
|
|
186
|
-
* Verifies all development tools (
|
|
187
|
-
*
|
|
188
|
-
* @returns {Object} Tool availability map
|
|
189
|
-
*/
|
|
190
|
-
function verifyTools() {
|
|
191
|
-
warnDeprecation('verifyTools', 'verifyToolsAsync');
|
|
192
|
-
const result = {};
|
|
193
|
-
for (const tool of TOOL_DEFINITIONS) {
|
|
194
|
-
result[tool.name] = checkTool(tool.name, tool.flag);
|
|
195
|
-
}
|
|
196
|
-
return result;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Verifies all development tools (async, parallel)
|
|
201
|
-
* Runs all tool checks in parallel for ~10x faster execution
|
|
129
|
+
* Verifies all development tools (parallel execution)
|
|
130
|
+
* Runs all tool checks in parallel for fast execution
|
|
202
131
|
* @returns {Promise<Object>} Tool availability map
|
|
203
132
|
*/
|
|
204
|
-
async function
|
|
205
|
-
// Run all checks in parallel
|
|
133
|
+
async function verifyTools() {
|
|
206
134
|
const results = await Promise.all(
|
|
207
|
-
TOOL_DEFINITIONS.map(tool =>
|
|
135
|
+
TOOL_DEFINITIONS.map(tool => checkTool(tool.name, tool.flag))
|
|
208
136
|
);
|
|
209
137
|
|
|
210
|
-
// Build result object
|
|
211
138
|
const toolMap = {};
|
|
212
139
|
TOOL_DEFINITIONS.forEach((tool, index) => {
|
|
213
140
|
toolMap[tool.name] = results[index];
|
|
@@ -216,11 +143,11 @@ async function verifyToolsAsync() {
|
|
|
216
143
|
return toolMap;
|
|
217
144
|
}
|
|
218
145
|
|
|
219
|
-
// When run directly, output JSON
|
|
146
|
+
// When run directly, output JSON
|
|
220
147
|
if (require.main === module) {
|
|
221
148
|
(async () => {
|
|
222
149
|
try {
|
|
223
|
-
const result = await
|
|
150
|
+
const result = await verifyTools();
|
|
224
151
|
console.log(JSON.stringify(result, null, 2));
|
|
225
152
|
} catch (error) {
|
|
226
153
|
console.error(JSON.stringify({
|
|
@@ -235,10 +162,6 @@ if (require.main === module) {
|
|
|
235
162
|
// Export for use as module
|
|
236
163
|
module.exports = {
|
|
237
164
|
verifyTools,
|
|
238
|
-
verifyToolsAsync,
|
|
239
165
|
checkTool,
|
|
240
|
-
|
|
241
|
-
TOOL_DEFINITIONS,
|
|
242
|
-
// Testing utilities (prefixed with _ to indicate internal use)
|
|
243
|
-
_resetDeprecationWarnings
|
|
166
|
+
TOOL_DEFINITIONS
|
|
244
167
|
};
|
|
@@ -34,15 +34,57 @@ class SchemaValidator {
|
|
|
34
34
|
static validate(data, schema) {
|
|
35
35
|
const errors = [];
|
|
36
36
|
|
|
37
|
-
// Check type (handle arrays correctly)
|
|
37
|
+
// Check type (handle arrays correctly and null separately)
|
|
38
38
|
if (schema.type) {
|
|
39
|
-
|
|
39
|
+
let actualType;
|
|
40
|
+
if (data === null) {
|
|
41
|
+
actualType = 'null';
|
|
42
|
+
} else if (Array.isArray(data)) {
|
|
43
|
+
actualType = 'array';
|
|
44
|
+
} else {
|
|
45
|
+
actualType = typeof data;
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
if (actualType !== schema.type) {
|
|
41
49
|
errors.push(`Expected type ${schema.type}, got ${actualType}`);
|
|
42
50
|
return { valid: false, errors };
|
|
43
51
|
}
|
|
44
52
|
}
|
|
45
53
|
|
|
54
|
+
// String validations (for primitive string values)
|
|
55
|
+
if (schema.type === 'string' && typeof data === 'string') {
|
|
56
|
+
if (schema.minLength && data.length < schema.minLength) {
|
|
57
|
+
errors.push(`String too short (min ${schema.minLength})`);
|
|
58
|
+
}
|
|
59
|
+
if (schema.maxLength && data.length > schema.maxLength) {
|
|
60
|
+
errors.push(`String too long (max ${schema.maxLength})`);
|
|
61
|
+
}
|
|
62
|
+
if (schema.pattern && !new RegExp(schema.pattern).test(data)) {
|
|
63
|
+
errors.push(`String does not match pattern ${schema.pattern}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Array validations (for primitive array values)
|
|
68
|
+
if (schema.type === 'array' && Array.isArray(data)) {
|
|
69
|
+
if (schema.minItems && data.length < schema.minItems) {
|
|
70
|
+
errors.push(`Array too short (min ${schema.minItems})`);
|
|
71
|
+
}
|
|
72
|
+
if (schema.maxItems && data.length > schema.maxItems) {
|
|
73
|
+
errors.push(`Array too long (max ${schema.maxItems})`);
|
|
74
|
+
}
|
|
75
|
+
if (schema.uniqueItems) {
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
for (const item of data) {
|
|
78
|
+
const key = JSON.stringify(item);
|
|
79
|
+
if (seen.has(key)) {
|
|
80
|
+
errors.push(`Array contains duplicate items`);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
seen.add(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
46
88
|
// Check required properties
|
|
47
89
|
if (schema.required && Array.isArray(schema.required)) {
|
|
48
90
|
for (const required of schema.required) {
|
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
* Source Cache
|
|
3
3
|
* File-based persistence for task source preferences
|
|
4
4
|
*
|
|
5
|
+
* State directory is platform-aware:
|
|
6
|
+
* - Claude Code: .claude/sources/
|
|
7
|
+
* - OpenCode: .opencode/sources/
|
|
8
|
+
* - Codex CLI: .codex/sources/
|
|
9
|
+
*
|
|
5
10
|
* @module lib/sources/source-cache
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
const fs = require('fs');
|
|
9
14
|
const path = require('path');
|
|
15
|
+
const { getStateDir } = require('../platform/state-dir');
|
|
10
16
|
|
|
11
|
-
const SOURCES_DIR = '.claude/sources';
|
|
12
17
|
const PREFERENCE_FILE = 'preference.json';
|
|
13
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Get the sources directory path (platform-aware)
|
|
21
|
+
* @returns {string} Path to sources directory
|
|
22
|
+
*/
|
|
23
|
+
function getSourcesDir() {
|
|
24
|
+
return path.join(getStateDir(), 'sources');
|
|
25
|
+
}
|
|
26
|
+
|
|
14
27
|
/**
|
|
15
28
|
* Validate tool name to prevent path traversal
|
|
16
29
|
* @param {string} toolName - Tool name to validate
|
|
@@ -26,10 +39,11 @@ function isValidToolName(toolName) {
|
|
|
26
39
|
* @returns {string} Path to sources directory
|
|
27
40
|
*/
|
|
28
41
|
function ensureDir() {
|
|
29
|
-
|
|
30
|
-
|
|
42
|
+
const sourcesDir = getSourcesDir();
|
|
43
|
+
if (!fs.existsSync(sourcesDir)) {
|
|
44
|
+
fs.mkdirSync(sourcesDir, { recursive: true });
|
|
31
45
|
}
|
|
32
|
-
return
|
|
46
|
+
return sourcesDir;
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
/**
|
|
@@ -40,7 +54,7 @@ function ensureDir() {
|
|
|
40
54
|
* // Or: { source: 'custom', type: 'cli', tool: 'tea' }
|
|
41
55
|
*/
|
|
42
56
|
function getPreference() {
|
|
43
|
-
const filePath = path.join(
|
|
57
|
+
const filePath = path.join(getSourcesDir(), PREFERENCE_FILE);
|
|
44
58
|
if (!fs.existsSync(filePath)) {
|
|
45
59
|
return null;
|
|
46
60
|
}
|
|
@@ -62,7 +76,7 @@ function getPreference() {
|
|
|
62
76
|
*/
|
|
63
77
|
function savePreference(preference) {
|
|
64
78
|
ensureDir();
|
|
65
|
-
const filePath = path.join(
|
|
79
|
+
const filePath = path.join(getSourcesDir(), PREFERENCE_FILE);
|
|
66
80
|
fs.writeFileSync(filePath, JSON.stringify({
|
|
67
81
|
...preference,
|
|
68
82
|
savedAt: new Date().toISOString()
|
|
@@ -80,7 +94,7 @@ function getToolCapabilities(toolName) {
|
|
|
80
94
|
console.error(`Invalid tool name: ${toolName}`);
|
|
81
95
|
return null;
|
|
82
96
|
}
|
|
83
|
-
const filePath = path.join(
|
|
97
|
+
const filePath = path.join(getSourcesDir(), `${toolName}.json`);
|
|
84
98
|
if (!fs.existsSync(filePath)) {
|
|
85
99
|
return null;
|
|
86
100
|
}
|
|
@@ -106,7 +120,7 @@ function saveToolCapabilities(toolName, capabilities) {
|
|
|
106
120
|
return;
|
|
107
121
|
}
|
|
108
122
|
ensureDir();
|
|
109
|
-
const filePath = path.join(
|
|
123
|
+
const filePath = path.join(getSourcesDir(), `${toolName}.json`);
|
|
110
124
|
fs.writeFileSync(filePath, JSON.stringify({
|
|
111
125
|
...capabilities,
|
|
112
126
|
discoveredAt: new Date().toISOString()
|
|
@@ -117,10 +131,11 @@ function saveToolCapabilities(toolName, capabilities) {
|
|
|
117
131
|
* Clear all cached preferences
|
|
118
132
|
*/
|
|
119
133
|
function clearCache() {
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
const sourcesDir = getSourcesDir();
|
|
135
|
+
if (fs.existsSync(sourcesDir)) {
|
|
136
|
+
const files = fs.readdirSync(sourcesDir);
|
|
122
137
|
for (const file of files) {
|
|
123
|
-
const filePath = path.join(
|
|
138
|
+
const filePath = path.join(sourcesDir, file);
|
|
124
139
|
const stats = fs.statSync(filePath);
|
|
125
140
|
if (stats.isFile()) {
|
|
126
141
|
fs.unlinkSync(filePath);
|
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
* Simplified workflow state management
|
|
3
3
|
*
|
|
4
4
|
* Two files:
|
|
5
|
-
* - Main project:
|
|
6
|
-
* - Worktree:
|
|
5
|
+
* - Main project: {stateDir}/tasks.json (tracks active worktree/task)
|
|
6
|
+
* - Worktree: {stateDir}/flow.json (tracks workflow progress)
|
|
7
|
+
*
|
|
8
|
+
* State directory is platform-aware:
|
|
9
|
+
* - Claude Code: .claude/
|
|
10
|
+
* - OpenCode: .opencode/
|
|
11
|
+
* - Codex CLI: .codex/
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
14
|
const fs = require('fs');
|
|
10
15
|
const path = require('path');
|
|
11
16
|
const crypto = require('crypto');
|
|
17
|
+
const { getStateDir } = require('../platform/state-dir');
|
|
12
18
|
|
|
13
19
|
// File paths
|
|
14
|
-
const CLAUDE_DIR = '.claude';
|
|
15
20
|
const TASKS_FILE = 'tasks.json';
|
|
16
21
|
const FLOW_FILE = 'flow.json';
|
|
17
22
|
|
|
@@ -74,14 +79,14 @@ const PHASES = [
|
|
|
74
79
|
];
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
|
-
* Ensure
|
|
82
|
+
* Ensure state directory exists (platform-aware)
|
|
78
83
|
*/
|
|
79
|
-
function
|
|
80
|
-
const
|
|
81
|
-
if (!fs.existsSync(
|
|
82
|
-
fs.mkdirSync(
|
|
84
|
+
function ensureStateDir(basePath) {
|
|
85
|
+
const stateDir = path.join(basePath, getStateDir(basePath));
|
|
86
|
+
if (!fs.existsSync(stateDir)) {
|
|
87
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
83
88
|
}
|
|
84
|
-
return
|
|
89
|
+
return stateDir;
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
// =============================================================================
|
|
@@ -93,7 +98,7 @@ function ensureClaudeDir(basePath) {
|
|
|
93
98
|
*/
|
|
94
99
|
function getTasksPath(projectPath = process.cwd()) {
|
|
95
100
|
const validatedBase = validatePath(projectPath);
|
|
96
|
-
const tasksPath = path.join(validatedBase,
|
|
101
|
+
const tasksPath = path.join(validatedBase, getStateDir(projectPath), TASKS_FILE);
|
|
97
102
|
validatePathWithinBase(tasksPath, validatedBase);
|
|
98
103
|
return tasksPath;
|
|
99
104
|
}
|
|
@@ -125,7 +130,7 @@ function readTasks(projectPath = process.cwd()) {
|
|
|
125
130
|
* Write tasks.json to main project
|
|
126
131
|
*/
|
|
127
132
|
function writeTasks(tasks, projectPath = process.cwd()) {
|
|
128
|
-
|
|
133
|
+
ensureStateDir(projectPath);
|
|
129
134
|
const tasksPath = getTasksPath(projectPath);
|
|
130
135
|
fs.writeFileSync(tasksPath, JSON.stringify(tasks, null, 2), 'utf8');
|
|
131
136
|
return true;
|
|
@@ -170,7 +175,7 @@ function hasActiveTask(projectPath = process.cwd()) {
|
|
|
170
175
|
*/
|
|
171
176
|
function getFlowPath(worktreePath = process.cwd()) {
|
|
172
177
|
const validatedBase = validatePath(worktreePath);
|
|
173
|
-
const flowPath = path.join(validatedBase,
|
|
178
|
+
const flowPath = path.join(validatedBase, getStateDir(worktreePath), FLOW_FILE);
|
|
174
179
|
validatePathWithinBase(flowPath, validatedBase);
|
|
175
180
|
return flowPath;
|
|
176
181
|
}
|
|
@@ -198,7 +203,7 @@ function readFlow(worktreePath = process.cwd()) {
|
|
|
198
203
|
* Creates a copy to avoid mutating the original object
|
|
199
204
|
*/
|
|
200
205
|
function writeFlow(flow, worktreePath = process.cwd()) {
|
|
201
|
-
|
|
206
|
+
ensureStateDir(worktreePath);
|
|
202
207
|
// Clone to avoid mutating the original object
|
|
203
208
|
const flowCopy = JSON.parse(JSON.stringify(flow));
|
|
204
209
|
flowCopy.lastUpdate = new Date().toISOString();
|
|
@@ -644,9 +644,8 @@ function isFileExcluded(filePath, excludePatterns) {
|
|
|
644
644
|
const cacheKey = JSON.stringify([filePath, excludePatterns]);
|
|
645
645
|
|
|
646
646
|
// Check cache first (O(1) lookup)
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
647
|
+
const cached = _excludeResultCache.get(cacheKey);
|
|
648
|
+
if (cached !== undefined) return cached;
|
|
650
649
|
|
|
651
650
|
// Compute result
|
|
652
651
|
const result = excludePatterns.some(pattern => {
|