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.
Files changed (44) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +35 -0
  4. package/README.md +23 -8
  5. package/lib/platform/state-dir.js +122 -0
  6. package/lib/sources/source-cache.js +26 -11
  7. package/lib/state/workflow-state.js +18 -13
  8. package/mcp-server/index.js +7 -11
  9. package/package.json +1 -1
  10. package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
  11. package/plugins/deslop-around/lib/patterns/slop-patterns.js +2 -3
  12. package/plugins/deslop-around/lib/platform/detect-platform.js +44 -287
  13. package/plugins/deslop-around/lib/platform/state-dir.js +122 -0
  14. package/plugins/deslop-around/lib/platform/verify-tools.js +11 -88
  15. package/plugins/deslop-around/lib/schemas/validator.js +44 -2
  16. package/plugins/deslop-around/lib/sources/source-cache.js +26 -11
  17. package/plugins/deslop-around/lib/state/workflow-state.js +18 -13
  18. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  19. package/plugins/next-task/lib/patterns/slop-patterns.js +2 -3
  20. package/plugins/next-task/lib/platform/detect-platform.js +44 -287
  21. package/plugins/next-task/lib/platform/state-dir.js +122 -0
  22. package/plugins/next-task/lib/platform/verify-tools.js +11 -88
  23. package/plugins/next-task/lib/schemas/validator.js +44 -2
  24. package/plugins/next-task/lib/sources/source-cache.js +26 -11
  25. package/plugins/next-task/lib/state/workflow-state.js +18 -13
  26. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  27. package/plugins/project-review/lib/patterns/slop-patterns.js +2 -3
  28. package/plugins/project-review/lib/platform/detect-platform.js +44 -287
  29. package/plugins/project-review/lib/platform/state-dir.js +122 -0
  30. package/plugins/project-review/lib/platform/verify-tools.js +11 -88
  31. package/plugins/project-review/lib/schemas/validator.js +44 -2
  32. package/plugins/project-review/lib/sources/source-cache.js +26 -11
  33. package/plugins/project-review/lib/state/workflow-state.js +18 -13
  34. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  35. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  36. package/plugins/ship/lib/patterns/slop-patterns.js +2 -3
  37. package/plugins/ship/lib/platform/detect-platform.js +44 -287
  38. package/plugins/ship/lib/platform/state-dir.js +122 -0
  39. package/plugins/ship/lib/platform/verify-tools.js +11 -88
  40. package/plugins/ship/lib/schemas/validator.js +44 -2
  41. package/plugins/ship/lib/sources/source-cache.js +26 -11
  42. package/plugins/ship/lib/state/workflow-state.js +18 -13
  43. package/scripts/install/codex.sh +216 -72
  44. package/scripts/install/opencode.sh +197 -21
@@ -10,75 +10,19 @@
10
10
  * @license MIT
11
11
  */
12
12
 
13
- const { execFileSync, spawnSync, spawn } = require('child_process');
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 (sync)
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 checkToolAsync(command, versionFlag = '--version') {
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 to avoid deprecation warning
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 (sync)
187
- * @deprecated Use verifyToolsAsync() instead. Will be removed in v3.0.0.
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 verifyToolsAsync() {
205
- // Run all checks in parallel
133
+ async function verifyTools() {
206
134
  const results = await Promise.all(
207
- TOOL_DEFINITIONS.map(tool => checkToolAsync(tool.name, tool.flag))
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 (uses async for better performance)
146
+ // When run directly, output JSON
220
147
  if (require.main === module) {
221
148
  (async () => {
222
149
  try {
223
- const result = await verifyToolsAsync();
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
- checkToolAsync,
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
- const actualType = Array.isArray(data) ? 'array' : typeof data;
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
- if (!fs.existsSync(SOURCES_DIR)) {
30
- fs.mkdirSync(SOURCES_DIR, { recursive: true });
42
+ const sourcesDir = getSourcesDir();
43
+ if (!fs.existsSync(sourcesDir)) {
44
+ fs.mkdirSync(sourcesDir, { recursive: true });
31
45
  }
32
- return SOURCES_DIR;
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(SOURCES_DIR, PREFERENCE_FILE);
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(SOURCES_DIR, PREFERENCE_FILE);
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(SOURCES_DIR, `${toolName}.json`);
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(SOURCES_DIR, `${toolName}.json`);
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
- if (fs.existsSync(SOURCES_DIR)) {
121
- const files = fs.readdirSync(SOURCES_DIR);
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(SOURCES_DIR, file);
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: .claude/tasks.json (tracks active worktree/task)
6
- * - Worktree: .claude/flow.json (tracks workflow progress)
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 .claude directory exists
82
+ * Ensure state directory exists (platform-aware)
78
83
  */
79
- function ensureClaudeDir(basePath) {
80
- const claudeDir = path.join(basePath, CLAUDE_DIR);
81
- if (!fs.existsSync(claudeDir)) {
82
- fs.mkdirSync(claudeDir, { recursive: true });
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 claudeDir;
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, CLAUDE_DIR, TASKS_FILE);
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
- ensureClaudeDir(projectPath);
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, CLAUDE_DIR, FLOW_FILE);
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
- ensureClaudeDir(worktreePath);
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();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reality-check",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Deep repository analysis to realign project plans with actual code reality - discovers drift, gaps, and produces prioritized reconstruction plans",
5
5
  "author": {
6
6
  "name": "Avi Fenesh",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ship",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Complete PR workflow from commit to production with validation",
5
5
  "author": {
6
6
  "name": "Avi Fenesh",
@@ -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
- if (_excludeResultCache.has(cacheKey)) {
648
- return _excludeResultCache.get(cacheKey);
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 => {