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.
Files changed (144) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +88 -1
  4. package/README.md +173 -161
  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/verify-tools.js +10 -78
  14. package/lib/schemas/README.md +195 -0
  15. package/lib/schemas/validator.js +247 -0
  16. package/lib/sources/custom-handler.js +199 -0
  17. package/lib/sources/policy-questions.js +239 -0
  18. package/lib/sources/source-cache.js +149 -0
  19. package/lib/state/workflow-state.js +363 -665
  20. package/lib/types/README.md +292 -0
  21. package/lib/types/agent-frontmatter.d.ts +134 -0
  22. package/lib/types/command-frontmatter.d.ts +107 -0
  23. package/lib/types/hook-frontmatter.d.ts +115 -0
  24. package/lib/types/index.d.ts +84 -0
  25. package/lib/types/plugin-manifest.d.ts +102 -0
  26. package/lib/types/skill-frontmatter.d.ts +89 -0
  27. package/lib/utils/cache-manager.js +154 -0
  28. package/lib/utils/context-optimizer.js +5 -36
  29. package/lib/utils/deprecation.js +37 -0
  30. package/lib/utils/shell-escape.js +88 -0
  31. package/mcp-server/index.js +513 -18
  32. package/package.json +6 -2
  33. package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
  34. package/plugins/deslop-around/lib/index.js +170 -0
  35. package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
  36. package/plugins/deslop-around/lib/patterns/slop-patterns.js +170 -129
  37. package/plugins/deslop-around/lib/platform/detect-platform.js +212 -123
  38. package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
  39. package/plugins/deslop-around/lib/platform/verify-tools.js +10 -1
  40. package/plugins/deslop-around/lib/schemas/README.md +195 -0
  41. package/plugins/deslop-around/lib/schemas/validator.js +205 -0
  42. package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
  43. package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
  44. package/plugins/deslop-around/lib/sources/source-cache.js +149 -0
  45. package/plugins/deslop-around/lib/state/workflow-state.js +382 -484
  46. package/plugins/deslop-around/lib/types/README.md +292 -0
  47. package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
  48. package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
  49. package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
  50. package/plugins/deslop-around/lib/types/index.d.ts +84 -0
  51. package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
  52. package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
  53. package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
  54. package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
  55. package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
  56. package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
  57. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  58. package/plugins/next-task/agents/delivery-validator.md +2 -2
  59. package/plugins/next-task/agents/implementation-agent.md +3 -4
  60. package/plugins/next-task/agents/planning-agent.md +77 -19
  61. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  62. package/plugins/next-task/agents/task-discoverer.md +164 -23
  63. package/plugins/next-task/commands/next-task.md +180 -14
  64. package/plugins/next-task/lib/index.js +170 -0
  65. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  66. package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
  67. package/plugins/next-task/lib/platform/detect-platform.js +212 -123
  68. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  69. package/plugins/next-task/lib/platform/verify-tools.js +10 -1
  70. package/plugins/next-task/lib/schemas/README.md +195 -0
  71. package/plugins/next-task/lib/schemas/validator.js +205 -0
  72. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  73. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  74. package/plugins/next-task/lib/sources/source-cache.js +149 -0
  75. package/plugins/next-task/lib/state/workflow-state.js +382 -484
  76. package/plugins/next-task/lib/types/README.md +292 -0
  77. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  78. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  79. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  80. package/plugins/next-task/lib/types/index.d.ts +84 -0
  81. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  82. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  83. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  84. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  85. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  86. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  87. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  88. package/plugins/project-review/lib/index.js +170 -0
  89. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  90. package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
  91. package/plugins/project-review/lib/platform/detect-platform.js +212 -123
  92. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  93. package/plugins/project-review/lib/platform/verify-tools.js +10 -1
  94. package/plugins/project-review/lib/schemas/README.md +195 -0
  95. package/plugins/project-review/lib/schemas/validator.js +205 -0
  96. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  97. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  98. package/plugins/project-review/lib/sources/source-cache.js +149 -0
  99. package/plugins/project-review/lib/state/workflow-state.js +382 -484
  100. package/plugins/project-review/lib/types/README.md +292 -0
  101. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  102. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  103. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  104. package/plugins/project-review/lib/types/index.d.ts +84 -0
  105. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  106. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  107. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  108. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  109. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  110. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  111. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  112. package/plugins/reality-check/agents/code-explorer.md +1 -1
  113. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  114. package/plugins/ship/lib/index.js +170 -0
  115. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  116. package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
  117. package/plugins/ship/lib/platform/detect-platform.js +212 -123
  118. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  119. package/plugins/ship/lib/platform/verify-tools.js +10 -1
  120. package/plugins/ship/lib/schemas/README.md +195 -0
  121. package/plugins/ship/lib/schemas/validator.js +205 -0
  122. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  123. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  124. package/plugins/ship/lib/sources/source-cache.js +149 -0
  125. package/plugins/ship/lib/state/workflow-state.js +382 -484
  126. package/plugins/ship/lib/types/README.md +292 -0
  127. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  128. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  129. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  130. package/plugins/ship/lib/types/index.d.ts +84 -0
  131. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  132. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  133. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  134. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  135. package/plugins/ship/lib/utils/deprecation.js +37 -0
  136. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  137. package/lib/state/workflow-state.schema.json +0 -282
  138. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  139. package/plugins/next-task/agents/policy-selector.md +0 -248
  140. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  141. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  142. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  143. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  144. 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: []
@@ -231,24 +239,428 @@ const toolHandlers = {
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
+ // 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
- return {
247
- content: [{
248
- type: 'text',
249
- text: `Code review would analyze ${files?.length || 'changed'} files with max ${maxIterations || 3} iterations.`
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.0.0',
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
- main().catch(console.error);
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.4.4",
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
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deslop-around",
3
- "version": "2.3.1",
3
+ "version": "2.5.0",
4
4
  "description": "AI slop cleanup with minimal diffs and behavior preservation",
5
5
  "author": {
6
6
  "name": "Avi Fenesh",