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.
Files changed (151) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +123 -1
  4. package/README.md +186 -159
  5. package/SECURITY.md +25 -81
  6. package/adapters/codex/install.sh +58 -16
  7. package/adapters/opencode/install.sh +92 -23
  8. package/lib/index.js +47 -4
  9. package/lib/patterns/review-patterns.js +58 -11
  10. package/lib/patterns/slop-patterns.js +154 -147
  11. package/lib/platform/detect-platform.js +99 -350
  12. package/lib/platform/detection-configs.js +93 -0
  13. package/lib/platform/state-dir.js +122 -0
  14. package/lib/platform/verify-tools.js +10 -78
  15. package/lib/schemas/README.md +195 -0
  16. package/lib/schemas/validator.js +247 -0
  17. package/lib/sources/custom-handler.js +199 -0
  18. package/lib/sources/policy-questions.js +239 -0
  19. package/lib/sources/source-cache.js +164 -0
  20. package/lib/state/workflow-state.js +368 -665
  21. package/lib/types/README.md +292 -0
  22. package/lib/types/agent-frontmatter.d.ts +134 -0
  23. package/lib/types/command-frontmatter.d.ts +107 -0
  24. package/lib/types/hook-frontmatter.d.ts +115 -0
  25. package/lib/types/index.d.ts +84 -0
  26. package/lib/types/plugin-manifest.d.ts +102 -0
  27. package/lib/types/skill-frontmatter.d.ts +89 -0
  28. package/lib/utils/cache-manager.js +154 -0
  29. package/lib/utils/context-optimizer.js +5 -36
  30. package/lib/utils/deprecation.js +37 -0
  31. package/lib/utils/shell-escape.js +88 -0
  32. package/mcp-server/index.js +513 -22
  33. package/package.json +6 -2
  34. package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
  35. package/plugins/deslop-around/lib/index.js +170 -0
  36. package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
  37. package/plugins/deslop-around/lib/patterns/slop-patterns.js +169 -129
  38. package/plugins/deslop-around/lib/platform/detect-platform.js +162 -316
  39. package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
  40. package/plugins/deslop-around/lib/platform/state-dir.js +122 -0
  41. package/plugins/deslop-around/lib/platform/verify-tools.js +10 -78
  42. package/plugins/deslop-around/lib/schemas/README.md +195 -0
  43. package/plugins/deslop-around/lib/schemas/validator.js +247 -0
  44. package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
  45. package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
  46. package/plugins/deslop-around/lib/sources/source-cache.js +164 -0
  47. package/plugins/deslop-around/lib/state/workflow-state.js +387 -484
  48. package/plugins/deslop-around/lib/types/README.md +292 -0
  49. package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
  50. package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
  51. package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
  52. package/plugins/deslop-around/lib/types/index.d.ts +84 -0
  53. package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
  54. package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
  55. package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
  56. package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
  57. package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
  58. package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
  59. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  60. package/plugins/next-task/agents/delivery-validator.md +2 -2
  61. package/plugins/next-task/agents/implementation-agent.md +3 -4
  62. package/plugins/next-task/agents/planning-agent.md +77 -19
  63. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  64. package/plugins/next-task/agents/task-discoverer.md +164 -23
  65. package/plugins/next-task/commands/next-task.md +180 -14
  66. package/plugins/next-task/lib/index.js +170 -0
  67. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  68. package/plugins/next-task/lib/patterns/slop-patterns.js +169 -129
  69. package/plugins/next-task/lib/platform/detect-platform.js +162 -316
  70. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  71. package/plugins/next-task/lib/platform/state-dir.js +122 -0
  72. package/plugins/next-task/lib/platform/verify-tools.js +10 -78
  73. package/plugins/next-task/lib/schemas/README.md +195 -0
  74. package/plugins/next-task/lib/schemas/validator.js +247 -0
  75. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  76. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  77. package/plugins/next-task/lib/sources/source-cache.js +164 -0
  78. package/plugins/next-task/lib/state/workflow-state.js +387 -484
  79. package/plugins/next-task/lib/types/README.md +292 -0
  80. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  81. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  82. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  83. package/plugins/next-task/lib/types/index.d.ts +84 -0
  84. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  85. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  86. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  87. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  88. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  89. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  90. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  91. package/plugins/project-review/lib/index.js +170 -0
  92. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  93. package/plugins/project-review/lib/patterns/slop-patterns.js +169 -129
  94. package/plugins/project-review/lib/platform/detect-platform.js +162 -316
  95. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  96. package/plugins/project-review/lib/platform/state-dir.js +122 -0
  97. package/plugins/project-review/lib/platform/verify-tools.js +10 -78
  98. package/plugins/project-review/lib/schemas/README.md +195 -0
  99. package/plugins/project-review/lib/schemas/validator.js +247 -0
  100. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  101. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  102. package/plugins/project-review/lib/sources/source-cache.js +164 -0
  103. package/plugins/project-review/lib/state/workflow-state.js +387 -484
  104. package/plugins/project-review/lib/types/README.md +292 -0
  105. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  106. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  107. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  108. package/plugins/project-review/lib/types/index.d.ts +84 -0
  109. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  110. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  111. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  112. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  113. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  114. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  115. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  116. package/plugins/reality-check/agents/code-explorer.md +1 -1
  117. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  118. package/plugins/ship/lib/index.js +170 -0
  119. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  120. package/plugins/ship/lib/patterns/slop-patterns.js +169 -129
  121. package/plugins/ship/lib/platform/detect-platform.js +162 -316
  122. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  123. package/plugins/ship/lib/platform/state-dir.js +122 -0
  124. package/plugins/ship/lib/platform/verify-tools.js +10 -78
  125. package/plugins/ship/lib/schemas/README.md +195 -0
  126. package/plugins/ship/lib/schemas/validator.js +247 -0
  127. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  128. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  129. package/plugins/ship/lib/sources/source-cache.js +164 -0
  130. package/plugins/ship/lib/state/workflow-state.js +387 -484
  131. package/plugins/ship/lib/types/README.md +292 -0
  132. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  133. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  134. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  135. package/plugins/ship/lib/types/index.d.ts +84 -0
  136. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  137. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  138. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  139. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  140. package/plugins/ship/lib/utils/deprecation.js +37 -0
  141. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  142. package/scripts/install/codex.sh +216 -72
  143. package/scripts/install/opencode.sh +197 -21
  144. package/lib/state/workflow-state.schema.json +0 -282
  145. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  146. package/plugins/next-task/agents/policy-selector.md +0 -248
  147. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  148. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  149. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  150. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  151. package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
@@ -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: []
@@ -170,7 +178,7 @@ const toolHandlers = {
170
178
  return {
171
179
  content: [{
172
180
  type: 'text',
173
- text: `Workflow started: ${state.workflow.id}\nPolicy: ${JSON.stringify(policy, null, 2)}`
181
+ text: `Workflow started: ${state.task.id}\nPolicy: ${JSON.stringify(policy, null, 2)}`
174
182
  }]
175
183
  };
176
184
  },
@@ -191,7 +199,7 @@ const toolHandlers = {
191
199
  };
192
200
  }
193
201
 
194
- if (!state.checkpoints?.canResume) {
202
+ if (state.status !== 'in_progress' || state.phase === 'complete') {
195
203
  return {
196
204
  content: [{ type: 'text', text: 'Workflow cannot be resumed from current state.' }],
197
205
  isError: true
@@ -201,7 +209,7 @@ const toolHandlers = {
201
209
  return {
202
210
  content: [{
203
211
  type: 'text',
204
- text: `Resuming workflow ${state.workflow.id} from phase: ${state.checkpoints.resumeFrom}`
212
+ text: `Resuming workflow ${state.task.id} from phase: ${state.phase}`
205
213
  }]
206
214
  };
207
215
  },
@@ -226,29 +234,429 @@ const toolHandlers = {
226
234
  return {
227
235
  content: [{
228
236
  type: 'text',
229
- text: `Workflow ${state.workflow.id} aborted. Cleanup: worktree and branches should be removed manually.`
237
+ text: `Workflow ${state.task.id} aborted. Cleanup: worktree and branches should be removed manually.`
230
238
  }]
231
239
  };
232
240
  },
233
241
 
234
- async task_discover({ source, filter, limit }) {
235
- // This would integrate with gh/linear/etc
236
- // For now, return placeholder
237
- return {
238
- content: [{
239
- type: 'text',
240
- text: `Task discovery would search ${source || 'gh-issues'} with filter "${filter || 'all'}" (limit: ${limit || 10})`
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
+ // File doesn't exist, try next one
387
+ }
388
+ }
389
+
390
+ if (content) {
391
+ const lines = content.split('\n');
392
+ const taskLines = lines.filter(line => /^[-*]\s+\[\s*\]\s+/.test(line));
393
+
394
+ tasks = taskLines.slice(0, maxTasks).map((line, index) => {
395
+ const text = line.replace(/^[-*]\s+\[\s*\]\s+/, '').trim();
396
+ const isBug = /\b(bug|fix|error|issue)\b/i.test(text);
397
+ const isFeature = /\b(feature|add|implement|create)\b/i.test(text);
398
+
399
+ return {
400
+ id: `task-${index + 1}`,
401
+ title: text,
402
+ type: isBug ? 'bug' : isFeature ? 'feature' : 'task',
403
+ labels: [],
404
+ source: foundFile
405
+ };
406
+ });
407
+
408
+ if (filter && filter !== 'all') {
409
+ tasks = tasks.filter(task => task.type === filter.toLowerCase());
410
+ }
411
+ } else {
412
+ return {
413
+ content: [{
414
+ type: 'text',
415
+ text: 'No task files found (looked for TASKS.md, PLAN.md, TODO.md)'
416
+ }]
417
+ };
418
+ }
419
+ } else if (taskSource === 'custom') {
420
+ if (!customFile) {
421
+ return {
422
+ content: [{
423
+ type: 'text',
424
+ text: 'Error: customFile parameter is required when source is "custom"'
425
+ }],
426
+ isError: true
427
+ };
428
+ }
429
+
430
+ // Validate file path - prevent path traversal
431
+ const normalizedPath = path.normalize(customFile);
432
+ // Note: absolute paths and '..' are allowed but monitored via file access
433
+
434
+ try {
435
+ const content = await fs.readFile(customFile, 'utf-8');
436
+ const lines = content.split('\n');
437
+ const taskLines = lines.filter(line => /^[-*]\s+\[\s*\]\s+/.test(line));
438
+
439
+ tasks = taskLines.slice(0, maxTasks).map((line, index) => {
440
+ const text = line.replace(/^[-*]\s+\[\s*\]\s+/, '').trim();
441
+ const isBug = /\b(bug|fix|error|issue)\b/i.test(text);
442
+ const isFeature = /\b(feature|add|implement|create)\b/i.test(text);
443
+ const isSecurity = /\b(security|vulnerability|cve|auth)\b/i.test(text);
444
+
445
+ return {
446
+ id: `custom-${index + 1}`,
447
+ title: text,
448
+ type: isSecurity ? 'security' : isBug ? 'bug' : isFeature ? 'feature' : 'task',
449
+ labels: [],
450
+ source: customFile
451
+ };
452
+ });
453
+
454
+ if (filter && filter !== 'all') {
455
+ tasks = tasks.filter(task => task.type === filter.toLowerCase());
456
+ }
457
+
458
+ } catch (error) {
459
+ return {
460
+ content: [{
461
+ type: 'text',
462
+ text: `Error reading custom file "${customFile}": ${error.message}`
463
+ }],
464
+ isError: true
465
+ };
466
+ }
467
+ } else {
468
+ return {
469
+ content: [{
470
+ type: 'text',
471
+ text: `Unknown task source: "${taskSource}"`
472
+ }],
473
+ isError: true
474
+ };
475
+ }
476
+
477
+ return {
478
+ content: [{
479
+ type: 'text',
480
+ text: JSON.stringify({
481
+ source: taskSource,
482
+ filter: filter || 'all',
483
+ count: tasks.length,
484
+ tasks: tasks
485
+ }, null, 2)
486
+ }]
487
+ };
488
+
489
+ } catch (error) {
490
+ console.error('Error discovering tasks:', error);
491
+ return {
492
+ content: [{
493
+ type: 'text',
494
+ text: `Error discovering tasks: An internal error occurred. Please check server logs for details.`
495
+ }],
496
+ isError: true
497
+ };
498
+ }
243
499
  },
244
500
 
245
501
  async review_code({ files, maxIterations }) {
246
- return {
247
- content: [{
248
- type: 'text',
249
- text: `Code review would analyze ${files?.length || 'changed'} files with max ${maxIterations || 3} iterations.`
250
- }]
251
- };
502
+ try {
503
+ let filesToReview = files || [];
504
+
505
+ if (!filesToReview.length) {
506
+ try {
507
+ const { stdout } = await execAsync('git diff --name-only HEAD');
508
+ filesToReview = stdout.trim().split('\n').filter(f => f);
509
+
510
+ if (!filesToReview.length) {
511
+ const { stdout: stagedOut } = await execAsync('git diff --cached --name-only');
512
+ filesToReview = stagedOut.trim().split('\n').filter(f => f);
513
+ }
514
+
515
+ if (!filesToReview.length) {
516
+ try {
517
+ const { stdout: lastCommit } = await execAsync('git diff --name-only HEAD~1');
518
+ filesToReview = lastCommit.trim().split('\n').filter(f => f);
519
+ } catch (e) {
520
+ // HEAD~1 doesn't exist (single-commit repo) - no files to review
521
+ }
522
+ }
523
+
524
+ } catch (error) {
525
+ return {
526
+ content: [{
527
+ type: 'text',
528
+ text: `Error getting changed files: ${error.message}\nCommand: git diff\nPlease specify files explicitly.`
529
+ }],
530
+ isError: true
531
+ };
532
+ }
533
+ }
534
+
535
+ if (!filesToReview.length) {
536
+ return {
537
+ content: [{
538
+ type: 'text',
539
+ text: 'No files to review. No changes detected in working directory or last commit.'
540
+ }]
541
+ };
542
+ }
543
+
544
+ const findings = [];
545
+ const patterns = {
546
+ console_log: {
547
+ pattern: /console\.(log|debug|info|warn|error)\(/g,
548
+ severity: 'warning',
549
+ message: 'Debug console statement found'
550
+ },
551
+ todo_fixme: {
552
+ pattern: /\/\/\s*(TODO|FIXME|HACK|XXX|NOTE):/gi,
553
+ severity: 'info',
554
+ message: 'Comment marker found'
555
+ },
556
+ commented_code: {
557
+ pattern: /^\s*\/\/.*[{};].*$/gm,
558
+ severity: 'info',
559
+ message: 'Possible commented-out code'
560
+ },
561
+ debugger: {
562
+ pattern: /\bdebugger\b/g,
563
+ severity: 'error',
564
+ message: 'Debugger statement found'
565
+ },
566
+ empty_catch: {
567
+ pattern: /catch\s*\([^)]*\)\s*{\s*}/g,
568
+ severity: 'warning',
569
+ message: 'Empty catch block - silent error swallowing'
570
+ },
571
+ any_type: {
572
+ pattern: /:\s*any\b/g,
573
+ severity: 'warning',
574
+ message: 'TypeScript "any" type used'
575
+ },
576
+ hardcoded_secret: {
577
+ pattern: /(api[_-]?key|secret|password|token)\s*[:=]\s*["'][^"']+["']/gi,
578
+ severity: 'error',
579
+ message: 'Possible hardcoded secret'
580
+ }
581
+ };
582
+
583
+ for (const file of filesToReview) {
584
+ try {
585
+ const content = await fs.readFile(file, 'utf-8');
586
+ const lines = content.split('\n');
587
+ const fileFindings = [];
588
+
589
+ for (const [name, check] of Object.entries(patterns)) {
590
+ const matches = [...content.matchAll(check.pattern)];
591
+
592
+ for (const match of matches) {
593
+ const position = match.index;
594
+ let lineNum = 1;
595
+ let charCount = 0;
596
+
597
+ for (let i = 0; i < lines.length; i++) {
598
+ charCount += lines[i].length + 1; // +1 for newline
599
+ if (charCount > position) {
600
+ lineNum = i + 1;
601
+ break;
602
+ }
603
+ }
604
+
605
+ fileFindings.push({
606
+ type: name,
607
+ line: lineNum,
608
+ column: match.index - content.lastIndexOf('\n', match.index),
609
+ severity: check.severity,
610
+ message: check.message,
611
+ match: match[0].substring(0, 100) // Truncate long matches
612
+ });
613
+ }
614
+ }
615
+
616
+ if (fileFindings.length > 0) {
617
+ findings.push({
618
+ file: file,
619
+ issues: fileFindings
620
+ });
621
+ }
622
+
623
+ } catch (error) {
624
+ findings.push({
625
+ file: file,
626
+ error: `Could not read file: ${error.message}`
627
+ });
628
+ }
629
+ }
630
+
631
+ const totalIssues = findings.reduce((sum, f) => sum + (f.issues?.length || 0), 0);
632
+ const bySeverity = {
633
+ error: findings.flatMap(f => f.issues || []).filter(i => i.severity === 'error').length,
634
+ warning: findings.flatMap(f => f.issues || []).filter(i => i.severity === 'warning').length,
635
+ info: findings.flatMap(f => f.issues || []).filter(i => i.severity === 'info').length
636
+ };
637
+
638
+ return {
639
+ content: [{
640
+ type: 'text',
641
+ text: JSON.stringify({
642
+ filesReviewed: filesToReview.length,
643
+ totalIssues: totalIssues,
644
+ bySeverity: bySeverity,
645
+ findings: findings
646
+ }, null, 2)
647
+ }]
648
+ };
649
+
650
+ } catch (error) {
651
+ console.error('Error during code review:', error);
652
+ return {
653
+ content: [{
654
+ type: 'text',
655
+ text: `Error during code review: An internal error occurred.`
656
+ }],
657
+ isError: true
658
+ };
659
+ }
252
660
  }
253
661
  };
254
662
 
@@ -257,7 +665,7 @@ async function main() {
257
665
  const server = new Server(
258
666
  {
259
667
  name: 'awesome-slash',
260
- version: '2.0.0',
668
+ version: '2.5.1',
261
669
  },
262
670
  {
263
671
  capabilities: {
@@ -300,4 +708,87 @@ async function main() {
300
708
  console.error('awesome-slash MCP server running');
301
709
  }
302
710
 
303
- main().catch(console.error);
711
+ // Export for testing
712
+ if (typeof module !== 'undefined' && module.exports) {
713
+ module.exports = { toolHandlers };
714
+ }
715
+
716
+ /**
717
+ * Error boundary for uncaught errors
718
+ * Prevents server crashes and provides meaningful error messages
719
+ */
720
+ function setupErrorBoundary() {
721
+ let errorCount = 0;
722
+ const MAX_ERRORS = 10;
723
+ const ERROR_WINDOW = 60000; // 1 minute
724
+ let firstErrorTime = null;
725
+
726
+ // Handle uncaught exceptions
727
+ process.on('uncaughtException', (error) => {
728
+ console.error('Uncaught Exception:', error);
729
+ console.error('Stack:', error.stack);
730
+
731
+ // Track error rate for graceful shutdown decision
732
+ const now = Date.now();
733
+ if (!firstErrorTime || (now - firstErrorTime) > ERROR_WINDOW) {
734
+ errorCount = 1;
735
+ firstErrorTime = now;
736
+ } else {
737
+ errorCount++;
738
+ }
739
+
740
+ // If too many errors in short time, exit gracefully
741
+ if (errorCount >= MAX_ERRORS) {
742
+ console.error(`${MAX_ERRORS} uncaught exceptions in ${ERROR_WINDOW}ms - exiting to prevent corrupted state`);
743
+ process.exit(1);
744
+ }
745
+
746
+ console.error(`Error count: ${errorCount}/${MAX_ERRORS} in current window`);
747
+ });
748
+
749
+ // Handle unhandled promise rejections
750
+ process.on('unhandledRejection', (reason, promise) => {
751
+ console.error('Unhandled Rejection at:', promise);
752
+ console.error('Reason:', reason);
753
+
754
+ // Track error rate
755
+ const now = Date.now();
756
+ if (!firstErrorTime || (now - firstErrorTime) > ERROR_WINDOW) {
757
+ errorCount = 1;
758
+ firstErrorTime = now;
759
+ } else {
760
+ errorCount++;
761
+ }
762
+
763
+ // If too many errors in short time, exit gracefully
764
+ if (errorCount >= MAX_ERRORS) {
765
+ console.error(`${MAX_ERRORS} unhandled rejections in ${ERROR_WINDOW}ms - exiting to prevent corrupted state`);
766
+ process.exit(1);
767
+ }
768
+
769
+ console.error(`Error count: ${errorCount}/${MAX_ERRORS} in current window`);
770
+ });
771
+
772
+ // Handle SIGINT gracefully
773
+ process.on('SIGINT', () => {
774
+ console.error('Received SIGINT, shutting down gracefully...');
775
+ process.exit(0);
776
+ });
777
+
778
+ // Handle SIGTERM gracefully
779
+ process.on('SIGTERM', () => {
780
+ console.error('Received SIGTERM, shutting down gracefully...');
781
+ process.exit(0);
782
+ });
783
+ }
784
+
785
+ // Only run main if this is the main module
786
+ if (require.main === module) {
787
+ setupErrorBoundary();
788
+
789
+ main().catch((error) => {
790
+ console.error('Fatal error in main():', error);
791
+ console.error('Stack:', error.stack);
792
+ process.exit(1);
793
+ });
794
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "awesome-slash",
3
- "version": "2.4.4",
3
+ "version": "2.5.1",
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop-around",
3
- "version": "2.3.1",
3
+ "version": "2.5.1",
4
4
  "description": "AI slop cleanup with minimal diffs and behavior preservation",
5
5
  "author": {
6
6
  "name": "Avi Fenesh",