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.
- package/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +88 -1
- package/README.md +173 -161
- 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/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 +149 -0
- package/lib/state/workflow-state.js +363 -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 -18
- 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 +170 -129
- package/plugins/deslop-around/lib/platform/detect-platform.js +212 -123
- package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +10 -1
- package/plugins/deslop-around/lib/schemas/README.md +195 -0
- package/plugins/deslop-around/lib/schemas/validator.js +205 -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 +149 -0
- package/plugins/deslop-around/lib/state/workflow-state.js +382 -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 +170 -129
- package/plugins/next-task/lib/platform/detect-platform.js +212 -123
- package/plugins/next-task/lib/platform/detection-configs.js +93 -0
- package/plugins/next-task/lib/platform/verify-tools.js +10 -1
- package/plugins/next-task/lib/schemas/README.md +195 -0
- package/plugins/next-task/lib/schemas/validator.js +205 -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 +149 -0
- package/plugins/next-task/lib/state/workflow-state.js +382 -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 +170 -129
- package/plugins/project-review/lib/platform/detect-platform.js +212 -123
- package/plugins/project-review/lib/platform/detection-configs.js +93 -0
- package/plugins/project-review/lib/platform/verify-tools.js +10 -1
- package/plugins/project-review/lib/schemas/README.md +195 -0
- package/plugins/project-review/lib/schemas/validator.js +205 -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 +149 -0
- package/plugins/project-review/lib/state/workflow-state.js +382 -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 +170 -129
- package/plugins/ship/lib/platform/detect-platform.js +212 -123
- package/plugins/ship/lib/platform/detection-configs.js +93 -0
- package/plugins/ship/lib/platform/verify-tools.js +10 -1
- package/plugins/ship/lib/schemas/README.md +195 -0
- package/plugins/ship/lib/schemas/validator.js +205 -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 +149 -0
- package/plugins/ship/lib/state/workflow-state.js +382 -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/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
package/mcp-server/index.js
CHANGED
|
@@ -18,6 +18,10 @@ const {
|
|
|
18
18
|
} = require('@modelcontextprotocol/sdk/types.js');
|
|
19
19
|
|
|
20
20
|
const path = require('path');
|
|
21
|
+
const fs = require('fs').promises;
|
|
22
|
+
const { promisify } = require('util');
|
|
23
|
+
const { exec } = require('child_process');
|
|
24
|
+
const execAsync = promisify(exec);
|
|
21
25
|
const workflowState = require('../lib/state/workflow-state.js');
|
|
22
26
|
|
|
23
27
|
// Plugin root for relative paths
|
|
@@ -85,7 +89,7 @@ const TOOLS = [
|
|
|
85
89
|
properties: {
|
|
86
90
|
source: {
|
|
87
91
|
type: 'string',
|
|
88
|
-
enum: ['gh-issues', 'linear', 'tasks-md'],
|
|
92
|
+
enum: ['gh-issues', 'linear', 'tasks-md', 'custom'],
|
|
89
93
|
description: 'Task source to search'
|
|
90
94
|
},
|
|
91
95
|
filter: {
|
|
@@ -95,6 +99,10 @@ const TOOLS = [
|
|
|
95
99
|
limit: {
|
|
96
100
|
type: 'number',
|
|
97
101
|
description: 'Maximum number of tasks to return'
|
|
102
|
+
},
|
|
103
|
+
customFile: {
|
|
104
|
+
type: 'string',
|
|
105
|
+
description: 'Path to custom task file (required when source is "custom"). Parses markdown checkboxes.'
|
|
98
106
|
}
|
|
99
107
|
},
|
|
100
108
|
required: []
|
|
@@ -231,24 +239,428 @@ const toolHandlers = {
|
|
|
231
239
|
};
|
|
232
240
|
},
|
|
233
241
|
|
|
234
|
-
async task_discover({ source, filter, limit }) {
|
|
235
|
-
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
async task_discover({ source, filter, limit, customFile }) {
|
|
243
|
+
const taskSource = source || 'gh-issues';
|
|
244
|
+
// Validate and sanitize limit to prevent command injection
|
|
245
|
+
let maxTasks = 10;
|
|
246
|
+
if (limit !== undefined) {
|
|
247
|
+
const parsed = parseInt(limit, 10);
|
|
248
|
+
if (!isNaN(parsed) && parsed > 0 && parsed <= 100) {
|
|
249
|
+
maxTasks = parsed;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
let tasks = [];
|
|
255
|
+
|
|
256
|
+
if (taskSource === 'gh-issues') {
|
|
257
|
+
try {
|
|
258
|
+
await execAsync('gh --version');
|
|
259
|
+
} catch (error) {
|
|
260
|
+
return {
|
|
261
|
+
content: [{
|
|
262
|
+
type: 'text',
|
|
263
|
+
text: 'Error: GitHub CLI (gh) is not installed or not configured. Please install gh and authenticate.'
|
|
264
|
+
}],
|
|
265
|
+
isError: true
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Use array form to prevent command injection
|
|
270
|
+
const ghArgs = [
|
|
271
|
+
'issue', 'list',
|
|
272
|
+
'--state', 'open',
|
|
273
|
+
'--json', 'number,title,labels,createdAt',
|
|
274
|
+
'--limit', String(maxTasks)
|
|
275
|
+
];
|
|
276
|
+
const { stdout } = await execAsync(`gh ${ghArgs.join(' ')}`);
|
|
277
|
+
|
|
278
|
+
const issues = JSON.parse(stdout || '[]');
|
|
279
|
+
|
|
280
|
+
let filtered = issues;
|
|
281
|
+
if (filter && filter !== 'all') {
|
|
282
|
+
filtered = issues.filter(issue => {
|
|
283
|
+
const labelNames = issue.labels.map(l => l.name.toLowerCase());
|
|
284
|
+
const filterLower = filter.toLowerCase();
|
|
285
|
+
|
|
286
|
+
return labelNames.some(label =>
|
|
287
|
+
label.includes(filterLower) ||
|
|
288
|
+
filterLower.includes(label)
|
|
289
|
+
);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
tasks = filtered.map(issue => ({
|
|
294
|
+
id: `#${issue.number}`,
|
|
295
|
+
title: issue.title,
|
|
296
|
+
type: issue.labels.find(l => ['bug', 'feature', 'security'].includes(l.name.toLowerCase()))?.name || 'task',
|
|
297
|
+
labels: issue.labels.map(l => l.name),
|
|
298
|
+
createdAt: issue.createdAt,
|
|
299
|
+
source: 'github'
|
|
300
|
+
}));
|
|
301
|
+
|
|
302
|
+
} else if (taskSource === 'linear') {
|
|
303
|
+
// Linear integration via GitHub issues containing Linear URLs
|
|
304
|
+
try {
|
|
305
|
+
await execAsync('gh --version');
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return {
|
|
308
|
+
content: [{
|
|
309
|
+
type: 'text',
|
|
310
|
+
text: 'Error: GitHub CLI (gh) is not installed or not configured. Linear integration requires gh to access GitHub issues with Linear links.'
|
|
311
|
+
}],
|
|
312
|
+
isError: true
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const ghArgs = [
|
|
317
|
+
'issue', 'list',
|
|
318
|
+
'--state', 'open',
|
|
319
|
+
'--json', 'number,title,body,labels,createdAt',
|
|
320
|
+
'--limit', String(Math.min(maxTasks * 2, 100)) // Fetch more since we'll filter for Linear links
|
|
321
|
+
];
|
|
322
|
+
const { stdout } = await execAsync(`gh ${ghArgs.join(' ')}`);
|
|
323
|
+
const issues = JSON.parse(stdout || '[]');
|
|
324
|
+
|
|
325
|
+
// Extract Linear URLs from issue bodies (length-limited to prevent ReDoS)
|
|
326
|
+
const linearUrlPattern = /https:\/\/linear\.app\/[^\s)]{1,200}/g;
|
|
327
|
+
const linearIssues = issues
|
|
328
|
+
.map(issue => {
|
|
329
|
+
const linearMatches = issue.body ? issue.body.match(linearUrlPattern) : null;
|
|
330
|
+
if (!linearMatches || linearMatches.length === 0) return null;
|
|
331
|
+
|
|
332
|
+
// Extract Linear ID from URL (e.g., ENG-123 from https://linear.app/company/issue/ENG-123/...)
|
|
333
|
+
const linearUrl = linearMatches[0];
|
|
334
|
+
// Match /issue/ID pattern specifically to avoid false matches
|
|
335
|
+
const linearIdMatch = linearUrl.match(/\/issue\/([A-Z]+-\d+)/);
|
|
336
|
+
const linearId = linearIdMatch ? linearIdMatch[1] : null;
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
number: issue.number,
|
|
340
|
+
title: issue.title,
|
|
341
|
+
body: issue.body,
|
|
342
|
+
labels: issue.labels,
|
|
343
|
+
createdAt: issue.createdAt,
|
|
344
|
+
linearUrl: linearUrl,
|
|
345
|
+
linearId: linearId
|
|
346
|
+
};
|
|
347
|
+
})
|
|
348
|
+
.filter(issue => issue !== null)
|
|
349
|
+
.slice(0, maxTasks);
|
|
350
|
+
|
|
351
|
+
let filtered = linearIssues;
|
|
352
|
+
if (filter && filter !== 'all') {
|
|
353
|
+
filtered = linearIssues.filter(issue => {
|
|
354
|
+
const labelNames = issue.labels.map(l => l.name.toLowerCase());
|
|
355
|
+
const filterLower = filter.toLowerCase();
|
|
356
|
+
|
|
357
|
+
return labelNames.some(label =>
|
|
358
|
+
label.includes(filterLower) ||
|
|
359
|
+
filterLower.includes(label)
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
tasks = filtered.map(issue => ({
|
|
365
|
+
id: issue.linearId || `#${issue.number}`,
|
|
366
|
+
title: issue.title,
|
|
367
|
+
type: issue.labels.find(l => ['bug', 'feature', 'security'].includes(l.name.toLowerCase()))?.name || 'task',
|
|
368
|
+
labels: issue.labels.map(l => l.name),
|
|
369
|
+
createdAt: issue.createdAt,
|
|
370
|
+
source: 'linear',
|
|
371
|
+
linearUrl: issue.linearUrl,
|
|
372
|
+
githubNumber: issue.number
|
|
373
|
+
}));
|
|
374
|
+
|
|
375
|
+
} else if (taskSource === 'tasks-md') {
|
|
376
|
+
const possibleFiles = ['TASKS.md', 'PLAN.md', 'TODO.md'];
|
|
377
|
+
let content = null;
|
|
378
|
+
let foundFile = null;
|
|
379
|
+
|
|
380
|
+
for (const file of possibleFiles) {
|
|
381
|
+
try {
|
|
382
|
+
content = await fs.readFile(file, 'utf-8');
|
|
383
|
+
foundFile = file;
|
|
384
|
+
break;
|
|
385
|
+
} catch (e) {
|
|
386
|
+
// Log error for debugging but continue checking other files
|
|
387
|
+
console.error(`Could not read ${file}: ${e.message}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (content) {
|
|
392
|
+
const lines = content.split('\n');
|
|
393
|
+
const taskLines = lines.filter(line => /^[-*]\s+\[\s*\]\s+/.test(line));
|
|
394
|
+
|
|
395
|
+
tasks = taskLines.slice(0, maxTasks).map((line, index) => {
|
|
396
|
+
const text = line.replace(/^[-*]\s+\[\s*\]\s+/, '').trim();
|
|
397
|
+
const isBug = /\b(bug|fix|error|issue)\b/i.test(text);
|
|
398
|
+
const isFeature = /\b(feature|add|implement|create)\b/i.test(text);
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
id: `task-${index + 1}`,
|
|
402
|
+
title: text,
|
|
403
|
+
type: isBug ? 'bug' : isFeature ? 'feature' : 'task',
|
|
404
|
+
labels: [],
|
|
405
|
+
source: foundFile
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (filter && filter !== 'all') {
|
|
410
|
+
tasks = tasks.filter(task => task.type === filter.toLowerCase());
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
return {
|
|
414
|
+
content: [{
|
|
415
|
+
type: 'text',
|
|
416
|
+
text: 'No task files found (looked for TASKS.md, PLAN.md, TODO.md)'
|
|
417
|
+
}]
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
} else if (taskSource === 'custom') {
|
|
421
|
+
if (!customFile) {
|
|
422
|
+
return {
|
|
423
|
+
content: [{
|
|
424
|
+
type: 'text',
|
|
425
|
+
text: 'Error: customFile parameter is required when source is "custom"'
|
|
426
|
+
}],
|
|
427
|
+
isError: true
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Validate file path - prevent path traversal
|
|
432
|
+
const normalizedPath = path.normalize(customFile);
|
|
433
|
+
if (normalizedPath.includes('..') || path.isAbsolute(normalizedPath)) {
|
|
434
|
+
// Allow absolute paths but log for awareness
|
|
435
|
+
console.error(`Custom task file: ${normalizedPath}`);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
try {
|
|
439
|
+
const content = await fs.readFile(customFile, 'utf-8');
|
|
440
|
+
const lines = content.split('\n');
|
|
441
|
+
const taskLines = lines.filter(line => /^[-*]\s+\[\s*\]\s+/.test(line));
|
|
442
|
+
|
|
443
|
+
tasks = taskLines.slice(0, maxTasks).map((line, index) => {
|
|
444
|
+
const text = line.replace(/^[-*]\s+\[\s*\]\s+/, '').trim();
|
|
445
|
+
const isBug = /\b(bug|fix|error|issue)\b/i.test(text);
|
|
446
|
+
const isFeature = /\b(feature|add|implement|create)\b/i.test(text);
|
|
447
|
+
const isSecurity = /\b(security|vulnerability|cve|auth)\b/i.test(text);
|
|
448
|
+
|
|
449
|
+
return {
|
|
450
|
+
id: `custom-${index + 1}`,
|
|
451
|
+
title: text,
|
|
452
|
+
type: isSecurity ? 'security' : isBug ? 'bug' : isFeature ? 'feature' : 'task',
|
|
453
|
+
labels: [],
|
|
454
|
+
source: customFile
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if (filter && filter !== 'all') {
|
|
459
|
+
tasks = tasks.filter(task => task.type === filter.toLowerCase());
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
} catch (error) {
|
|
463
|
+
return {
|
|
464
|
+
content: [{
|
|
465
|
+
type: 'text',
|
|
466
|
+
text: `Error reading custom file "${customFile}": ${error.message}`
|
|
467
|
+
}],
|
|
468
|
+
isError: true
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
return {
|
|
473
|
+
content: [{
|
|
474
|
+
type: 'text',
|
|
475
|
+
text: `Unknown task source: "${taskSource}"`
|
|
476
|
+
}],
|
|
477
|
+
isError: true
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
content: [{
|
|
483
|
+
type: 'text',
|
|
484
|
+
text: JSON.stringify({
|
|
485
|
+
source: taskSource,
|
|
486
|
+
filter: filter || 'all',
|
|
487
|
+
count: tasks.length,
|
|
488
|
+
tasks: tasks
|
|
489
|
+
}, null, 2)
|
|
490
|
+
}]
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.error('Error discovering tasks:', error);
|
|
495
|
+
return {
|
|
496
|
+
content: [{
|
|
497
|
+
type: 'text',
|
|
498
|
+
text: `Error discovering tasks: An internal error occurred. Please check server logs for details.`
|
|
499
|
+
}],
|
|
500
|
+
isError: true
|
|
501
|
+
};
|
|
502
|
+
}
|
|
243
503
|
},
|
|
244
504
|
|
|
245
505
|
async review_code({ files, maxIterations }) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
506
|
+
try {
|
|
507
|
+
let filesToReview = files || [];
|
|
508
|
+
|
|
509
|
+
if (!filesToReview.length) {
|
|
510
|
+
try {
|
|
511
|
+
const { stdout } = await execAsync('git diff --name-only HEAD');
|
|
512
|
+
filesToReview = stdout.trim().split('\n').filter(f => f);
|
|
513
|
+
|
|
514
|
+
if (!filesToReview.length) {
|
|
515
|
+
const { stdout: stagedOut } = await execAsync('git diff --cached --name-only');
|
|
516
|
+
filesToReview = stagedOut.trim().split('\n').filter(f => f);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
if (!filesToReview.length) {
|
|
520
|
+
try {
|
|
521
|
+
const { stdout: lastCommit } = await execAsync('git diff --name-only HEAD~1');
|
|
522
|
+
filesToReview = lastCommit.trim().split('\n').filter(f => f);
|
|
523
|
+
} catch (e) {
|
|
524
|
+
// HEAD~1 doesn't exist (single-commit repo) - no files to review
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
} catch (error) {
|
|
529
|
+
return {
|
|
530
|
+
content: [{
|
|
531
|
+
type: 'text',
|
|
532
|
+
text: `Error getting changed files: ${error.message}\nCommand: git diff\nPlease specify files explicitly.`
|
|
533
|
+
}],
|
|
534
|
+
isError: true
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (!filesToReview.length) {
|
|
540
|
+
return {
|
|
541
|
+
content: [{
|
|
542
|
+
type: 'text',
|
|
543
|
+
text: 'No files to review. No changes detected in working directory or last commit.'
|
|
544
|
+
}]
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const findings = [];
|
|
549
|
+
const patterns = {
|
|
550
|
+
console_log: {
|
|
551
|
+
pattern: /console\.(log|debug|info|warn|error)\(/g,
|
|
552
|
+
severity: 'warning',
|
|
553
|
+
message: 'Debug console statement found'
|
|
554
|
+
},
|
|
555
|
+
todo_fixme: {
|
|
556
|
+
pattern: /\/\/\s*(TODO|FIXME|HACK|XXX|NOTE):/gi,
|
|
557
|
+
severity: 'info',
|
|
558
|
+
message: 'Comment marker found'
|
|
559
|
+
},
|
|
560
|
+
commented_code: {
|
|
561
|
+
pattern: /^\s*\/\/.*[{};].*$/gm,
|
|
562
|
+
severity: 'info',
|
|
563
|
+
message: 'Possible commented-out code'
|
|
564
|
+
},
|
|
565
|
+
debugger: {
|
|
566
|
+
pattern: /\bdebugger\b/g,
|
|
567
|
+
severity: 'error',
|
|
568
|
+
message: 'Debugger statement found'
|
|
569
|
+
},
|
|
570
|
+
empty_catch: {
|
|
571
|
+
pattern: /catch\s*\([^)]*\)\s*{\s*}/g,
|
|
572
|
+
severity: 'warning',
|
|
573
|
+
message: 'Empty catch block - silent error swallowing'
|
|
574
|
+
},
|
|
575
|
+
any_type: {
|
|
576
|
+
pattern: /:\s*any\b/g,
|
|
577
|
+
severity: 'warning',
|
|
578
|
+
message: 'TypeScript "any" type used'
|
|
579
|
+
},
|
|
580
|
+
hardcoded_secret: {
|
|
581
|
+
pattern: /(api[_-]?key|secret|password|token)\s*[:=]\s*["'][^"']+["']/gi,
|
|
582
|
+
severity: 'error',
|
|
583
|
+
message: 'Possible hardcoded secret'
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
for (const file of filesToReview) {
|
|
588
|
+
try {
|
|
589
|
+
const content = await fs.readFile(file, 'utf-8');
|
|
590
|
+
const lines = content.split('\n');
|
|
591
|
+
const fileFindings = [];
|
|
592
|
+
|
|
593
|
+
for (const [name, check] of Object.entries(patterns)) {
|
|
594
|
+
const matches = [...content.matchAll(check.pattern)];
|
|
595
|
+
|
|
596
|
+
for (const match of matches) {
|
|
597
|
+
const position = match.index;
|
|
598
|
+
let lineNum = 1;
|
|
599
|
+
let charCount = 0;
|
|
600
|
+
|
|
601
|
+
for (let i = 0; i < lines.length; i++) {
|
|
602
|
+
charCount += lines[i].length + 1; // +1 for newline
|
|
603
|
+
if (charCount > position) {
|
|
604
|
+
lineNum = i + 1;
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
fileFindings.push({
|
|
610
|
+
type: name,
|
|
611
|
+
line: lineNum,
|
|
612
|
+
column: match.index - content.lastIndexOf('\n', match.index),
|
|
613
|
+
severity: check.severity,
|
|
614
|
+
message: check.message,
|
|
615
|
+
match: match[0].substring(0, 100) // Truncate long matches
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (fileFindings.length > 0) {
|
|
621
|
+
findings.push({
|
|
622
|
+
file: file,
|
|
623
|
+
issues: fileFindings
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
} catch (error) {
|
|
628
|
+
findings.push({
|
|
629
|
+
file: file,
|
|
630
|
+
error: `Could not read file: ${error.message}`
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const totalIssues = findings.reduce((sum, f) => sum + (f.issues?.length || 0), 0);
|
|
636
|
+
const bySeverity = {
|
|
637
|
+
error: findings.flatMap(f => f.issues || []).filter(i => i.severity === 'error').length,
|
|
638
|
+
warning: findings.flatMap(f => f.issues || []).filter(i => i.severity === 'warning').length,
|
|
639
|
+
info: findings.flatMap(f => f.issues || []).filter(i => i.severity === 'info').length
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
return {
|
|
643
|
+
content: [{
|
|
644
|
+
type: 'text',
|
|
645
|
+
text: JSON.stringify({
|
|
646
|
+
filesReviewed: filesToReview.length,
|
|
647
|
+
totalIssues: totalIssues,
|
|
648
|
+
bySeverity: bySeverity,
|
|
649
|
+
findings: findings
|
|
650
|
+
}, null, 2)
|
|
651
|
+
}]
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
} catch (error) {
|
|
655
|
+
console.error('Error during code review:', error);
|
|
656
|
+
return {
|
|
657
|
+
content: [{
|
|
658
|
+
type: 'text',
|
|
659
|
+
text: `Error during code review: An internal error occurred.`
|
|
660
|
+
}],
|
|
661
|
+
isError: true
|
|
662
|
+
};
|
|
663
|
+
}
|
|
252
664
|
}
|
|
253
665
|
};
|
|
254
666
|
|
|
@@ -257,7 +669,7 @@ async function main() {
|
|
|
257
669
|
const server = new Server(
|
|
258
670
|
{
|
|
259
671
|
name: 'awesome-slash',
|
|
260
|
-
version: '2.
|
|
672
|
+
version: '2.4.7',
|
|
261
673
|
},
|
|
262
674
|
{
|
|
263
675
|
capabilities: {
|
|
@@ -300,4 +712,87 @@ async function main() {
|
|
|
300
712
|
console.error('awesome-slash MCP server running');
|
|
301
713
|
}
|
|
302
714
|
|
|
303
|
-
|
|
715
|
+
// Export for testing
|
|
716
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
717
|
+
module.exports = { toolHandlers };
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Error boundary for uncaught errors
|
|
722
|
+
* Prevents server crashes and provides meaningful error messages
|
|
723
|
+
*/
|
|
724
|
+
function setupErrorBoundary() {
|
|
725
|
+
let errorCount = 0;
|
|
726
|
+
const MAX_ERRORS = 10;
|
|
727
|
+
const ERROR_WINDOW = 60000; // 1 minute
|
|
728
|
+
let firstErrorTime = null;
|
|
729
|
+
|
|
730
|
+
// Handle uncaught exceptions
|
|
731
|
+
process.on('uncaughtException', (error) => {
|
|
732
|
+
console.error('Uncaught Exception:', error);
|
|
733
|
+
console.error('Stack:', error.stack);
|
|
734
|
+
|
|
735
|
+
// Track error rate for graceful shutdown decision
|
|
736
|
+
const now = Date.now();
|
|
737
|
+
if (!firstErrorTime || (now - firstErrorTime) > ERROR_WINDOW) {
|
|
738
|
+
errorCount = 1;
|
|
739
|
+
firstErrorTime = now;
|
|
740
|
+
} else {
|
|
741
|
+
errorCount++;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// If too many errors in short time, exit gracefully
|
|
745
|
+
if (errorCount >= MAX_ERRORS) {
|
|
746
|
+
console.error(`${MAX_ERRORS} uncaught exceptions in ${ERROR_WINDOW}ms - exiting to prevent corrupted state`);
|
|
747
|
+
process.exit(1);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
console.error(`Error count: ${errorCount}/${MAX_ERRORS} in current window`);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
// Handle unhandled promise rejections
|
|
754
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
755
|
+
console.error('Unhandled Rejection at:', promise);
|
|
756
|
+
console.error('Reason:', reason);
|
|
757
|
+
|
|
758
|
+
// Track error rate
|
|
759
|
+
const now = Date.now();
|
|
760
|
+
if (!firstErrorTime || (now - firstErrorTime) > ERROR_WINDOW) {
|
|
761
|
+
errorCount = 1;
|
|
762
|
+
firstErrorTime = now;
|
|
763
|
+
} else {
|
|
764
|
+
errorCount++;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// If too many errors in short time, exit gracefully
|
|
768
|
+
if (errorCount >= MAX_ERRORS) {
|
|
769
|
+
console.error(`${MAX_ERRORS} unhandled rejections in ${ERROR_WINDOW}ms - exiting to prevent corrupted state`);
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
console.error(`Error count: ${errorCount}/${MAX_ERRORS} in current window`);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
// Handle SIGINT gracefully
|
|
777
|
+
process.on('SIGINT', () => {
|
|
778
|
+
console.error('Received SIGINT, shutting down gracefully...');
|
|
779
|
+
process.exit(0);
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// Handle SIGTERM gracefully
|
|
783
|
+
process.on('SIGTERM', () => {
|
|
784
|
+
console.error('Received SIGTERM, shutting down gracefully...');
|
|
785
|
+
process.exit(0);
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Only run main if this is the main module
|
|
790
|
+
if (require.main === module) {
|
|
791
|
+
setupErrorBoundary();
|
|
792
|
+
|
|
793
|
+
main().catch((error) => {
|
|
794
|
+
console.error('Fatal error in main():', error);
|
|
795
|
+
console.error('Stack:', error.stack);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
});
|
|
798
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "awesome-slash",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Professional-grade slash commands for Claude Code that work across any project",
|
|
5
5
|
"main": "lib/platform/detect-platform.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"test:watch": "jest --watch",
|
|
24
24
|
"test:coverage": "jest --coverage",
|
|
25
25
|
"detect": "node lib/platform/detect-platform.js",
|
|
26
|
-
"verify": "node lib/platform/verify-tools.js"
|
|
26
|
+
"verify": "node lib/platform/verify-tools.js",
|
|
27
|
+
"prepare": "node scripts/setup-hooks.js"
|
|
27
28
|
},
|
|
28
29
|
"repository": {
|
|
29
30
|
"type": "git",
|
|
@@ -57,6 +58,9 @@
|
|
|
57
58
|
"engines": {
|
|
58
59
|
"node": ">=18.0.0"
|
|
59
60
|
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"js-yaml": "^4.1.1"
|
|
63
|
+
},
|
|
60
64
|
"devDependencies": {
|
|
61
65
|
"jest": "^29.7.0"
|
|
62
66
|
}
|