awesome-slash 2.4.3 → 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 (146) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +99 -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/ci-monitor.md +19 -0
  59. package/plugins/next-task/agents/delivery-validator.md +2 -2
  60. package/plugins/next-task/agents/implementation-agent.md +3 -4
  61. package/plugins/next-task/agents/planning-agent.md +77 -19
  62. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  63. package/plugins/next-task/agents/task-discoverer.md +164 -23
  64. package/plugins/next-task/commands/next-task.md +180 -14
  65. package/plugins/next-task/lib/index.js +170 -0
  66. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  67. package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
  68. package/plugins/next-task/lib/platform/detect-platform.js +212 -123
  69. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  70. package/plugins/next-task/lib/platform/verify-tools.js +10 -1
  71. package/plugins/next-task/lib/schemas/README.md +195 -0
  72. package/plugins/next-task/lib/schemas/validator.js +205 -0
  73. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  74. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  75. package/plugins/next-task/lib/sources/source-cache.js +149 -0
  76. package/plugins/next-task/lib/state/workflow-state.js +382 -484
  77. package/plugins/next-task/lib/types/README.md +292 -0
  78. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  79. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  80. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  81. package/plugins/next-task/lib/types/index.d.ts +84 -0
  82. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  83. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  84. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  85. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  86. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  87. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  88. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  89. package/plugins/project-review/lib/index.js +170 -0
  90. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  91. package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
  92. package/plugins/project-review/lib/platform/detect-platform.js +212 -123
  93. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  94. package/plugins/project-review/lib/platform/verify-tools.js +10 -1
  95. package/plugins/project-review/lib/schemas/README.md +195 -0
  96. package/plugins/project-review/lib/schemas/validator.js +205 -0
  97. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  98. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  99. package/plugins/project-review/lib/sources/source-cache.js +149 -0
  100. package/plugins/project-review/lib/state/workflow-state.js +382 -484
  101. package/plugins/project-review/lib/types/README.md +292 -0
  102. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  103. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  104. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  105. package/plugins/project-review/lib/types/index.d.ts +84 -0
  106. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  107. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  108. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  109. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  110. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  111. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  112. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  113. package/plugins/reality-check/agents/code-explorer.md +1 -1
  114. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  115. package/plugins/ship/commands/ship-ci-review-loop.md +19 -0
  116. package/plugins/ship/lib/index.js +170 -0
  117. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  118. package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
  119. package/plugins/ship/lib/platform/detect-platform.js +212 -123
  120. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  121. package/plugins/ship/lib/platform/verify-tools.js +10 -1
  122. package/plugins/ship/lib/schemas/README.md +195 -0
  123. package/plugins/ship/lib/schemas/validator.js +205 -0
  124. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  125. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  126. package/plugins/ship/lib/sources/source-cache.js +149 -0
  127. package/plugins/ship/lib/state/workflow-state.js +382 -484
  128. package/plugins/ship/lib/types/README.md +292 -0
  129. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  130. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  131. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  132. package/plugins/ship/lib/types/index.d.ts +84 -0
  133. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  134. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  135. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  136. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  137. package/plugins/ship/lib/utils/deprecation.js +37 -0
  138. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  139. package/lib/state/workflow-state.schema.json +0 -282
  140. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  141. package/plugins/next-task/agents/policy-selector.md +0 -248
  142. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  143. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  144. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  145. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  146. package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Detection Configurations
3
+ * Centralized config for platform detection logic
4
+ *
5
+ * @module lib/platform/detection-configs
6
+ * @author Avi Fenesh
7
+ * @license MIT
8
+ */
9
+
10
+ /**
11
+ * CI platform detection configuration
12
+ * Order matters - first match wins
13
+ */
14
+ const CI_CONFIGS = [
15
+ { file: '.github/workflows', platform: 'github-actions' },
16
+ { file: '.gitlab-ci.yml', platform: 'gitlab-ci' },
17
+ { file: '.circleci/config.yml', platform: 'circleci' },
18
+ { file: 'Jenkinsfile', platform: 'jenkins' },
19
+ { file: '.travis.yml', platform: 'travis' }
20
+ ];
21
+
22
+ /**
23
+ * Deployment platform detection configuration
24
+ * Order matters - first match wins
25
+ */
26
+ const DEPLOYMENT_CONFIGS = [
27
+ { file: 'railway.json', platform: 'railway' },
28
+ { file: 'railway.toml', platform: 'railway' }, // Legacy Railway marker
29
+ { file: 'vercel.json', platform: 'vercel' },
30
+ { file: 'netlify.toml', platform: 'netlify' },
31
+ { file: '.netlify', platform: 'netlify' }, // Legacy Netlify marker
32
+ { file: 'fly.toml', platform: 'fly' },
33
+ { file: '.platform.app.yaml', platform: 'platformsh' },
34
+ { file: 'render.yaml', platform: 'render' }
35
+ ];
36
+
37
+ /**
38
+ * Project type detection configuration
39
+ * Checks package.json for framework indicators
40
+ */
41
+ const PROJECT_TYPE_CONFIGS = {
42
+ dependencies: [
43
+ { name: 'next', type: 'nextjs' },
44
+ { name: 'react', type: 'react' },
45
+ { name: 'vue', type: 'vue' },
46
+ { name: '@angular/core', type: 'angular' },
47
+ { name: 'svelte', type: 'svelte' },
48
+ { name: 'express', type: 'express' },
49
+ { name: '@nestjs/core', type: 'nestjs' },
50
+ { name: 'gatsby', type: 'gatsby' },
51
+ { name: '@remix-run/react', type: 'remix' },
52
+ { name: 'astro', type: 'astro' }
53
+ ]
54
+ };
55
+
56
+ /**
57
+ * Package manager detection configuration
58
+ * Lock files indicate package manager used
59
+ * Order matters - first match wins (prioritize pnpm > yarn > bun > npm for Node.js)
60
+ */
61
+ const PACKAGE_MANAGER_CONFIGS = [
62
+ { file: 'pnpm-lock.yaml', manager: 'pnpm' },
63
+ { file: 'yarn.lock', manager: 'yarn' },
64
+ { file: 'bun.lockb', manager: 'bun' },
65
+ { file: 'package-lock.json', manager: 'npm' },
66
+ { file: 'poetry.lock', manager: 'poetry' },
67
+ { file: 'Pipfile.lock', manager: 'pipenv' },
68
+ { file: 'Cargo.lock', manager: 'cargo' },
69
+ { file: 'go.sum', manager: 'go' }
70
+ ];
71
+
72
+ /**
73
+ * Branch strategy patterns
74
+ */
75
+ const BRANCH_STRATEGIES = {
76
+ gitflow: ['develop', 'main', 'master'],
77
+ githubflow: ['main'],
78
+ trunkbased: ['main', 'trunk']
79
+ };
80
+
81
+ /**
82
+ * Main branch candidates (in priority order)
83
+ */
84
+ const MAIN_BRANCH_CANDIDATES = ['main', 'master', 'trunk', 'develop'];
85
+
86
+ module.exports = {
87
+ CI_CONFIGS,
88
+ DEPLOYMENT_CONFIGS,
89
+ PROJECT_TYPE_CONFIGS,
90
+ PACKAGE_MANAGER_CONFIGS,
91
+ BRANCH_STRATEGIES,
92
+ MAIN_BRANCH_CANDIDATES
93
+ };
@@ -10,70 +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
18
  /**
19
- * Checks if a tool is available and returns its version (sync)
20
- * Uses safe execution methods to avoid shell injection vulnerabilities
21
- * @param {string} command - Command to check (e.g., 'git', 'node')
22
- * @param {string} versionFlag - Flag to get version (default: '--version')
23
- * @returns {Object} { available: boolean, version: string|null }
24
- */
25
- function checkTool(command, versionFlag = '--version') {
26
- // Validate command contains only safe characters (alphanumeric, underscore, hyphen)
27
- if (!/^[a-zA-Z0-9_-]+$/.test(command)) {
28
- return { available: false, version: null };
29
- }
30
- // Validate versionFlag contains only safe characters
31
- if (!/^[a-zA-Z0-9_-]+$/.test(versionFlag)) {
32
- return { available: false, version: null };
33
- }
34
-
35
- try {
36
- let output;
37
-
38
- if (isWindows) {
39
- // On Windows, use spawnSync with shell to handle .cmd/.bat scripts
40
- // Input is validated above so this is safe
41
- const result = spawnSync(command, [versionFlag], {
42
- encoding: 'utf8',
43
- stdio: ['pipe', 'pipe', 'ignore'],
44
- timeout: 5000,
45
- windowsHide: true,
46
- shell: true
47
- });
48
- if (result.error || result.status !== 0) {
49
- return { available: false, version: null };
50
- }
51
- output = (result.stdout || '').trim();
52
- } else {
53
- // On Unix, use execFileSync (more secure, no shell)
54
- output = execFileSync(command, [versionFlag], {
55
- encoding: 'utf8',
56
- stdio: ['pipe', 'pipe', 'ignore'],
57
- timeout: 5000
58
- }).trim();
59
- }
60
-
61
- // Extract version from first line
62
- const version = output.split('\n')[0];
63
- return { available: true, version };
64
- } catch {
65
- return { available: false, version: null };
66
- }
67
- }
68
-
69
- /**
70
- * Checks if a tool is available and returns its version (async)
19
+ * Checks if a tool is available and returns its version
71
20
  * Uses safe execution methods to avoid shell injection vulnerabilities
72
21
  * @param {string} command - Command to check (e.g., 'git', 'node')
73
22
  * @param {string} versionFlag - Flag to get version (default: '--version')
74
23
  * @returns {Promise<Object>} { available: boolean, version: string|null }
75
24
  */
76
- function checkToolAsync(command, versionFlag = '--version') {
25
+ function checkTool(command, versionFlag = '--version') {
77
26
  return new Promise((resolve) => {
78
27
  // Validate command contains only safe characters (alphanumeric, underscore, hyphen)
79
28
  if (!/^[a-zA-Z0-9_-]+$/.test(command)) {
@@ -87,8 +36,7 @@ function checkToolAsync(command, versionFlag = '--version') {
87
36
  let child;
88
37
 
89
38
  if (isWindows) {
90
- // On Windows, spawn shell directly with command as single argument to avoid deprecation warning
91
- // Input is validated above so this is safe
39
+ // On Windows, spawn shell directly with command as single argument
92
40
  child = spawn('cmd.exe', ['/c', command, versionFlag], {
93
41
  stdio: ['pipe', 'pipe', 'ignore'],
94
42
  windowsHide: true
@@ -178,29 +126,15 @@ const TOOL_DEFINITIONS = [
178
126
  ];
179
127
 
180
128
  /**
181
- * Verifies all development tools (sync)
182
- * @returns {Object} Tool availability map
183
- */
184
- function verifyTools() {
185
- const result = {};
186
- for (const tool of TOOL_DEFINITIONS) {
187
- result[tool.name] = checkTool(tool.name, tool.flag);
188
- }
189
- return result;
190
- }
191
-
192
- /**
193
- * Verifies all development tools (async, parallel)
194
- * 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
195
131
  * @returns {Promise<Object>} Tool availability map
196
132
  */
197
- async function verifyToolsAsync() {
198
- // Run all checks in parallel
133
+ async function verifyTools() {
199
134
  const results = await Promise.all(
200
- TOOL_DEFINITIONS.map(tool => checkToolAsync(tool.name, tool.flag))
135
+ TOOL_DEFINITIONS.map(tool => checkTool(tool.name, tool.flag))
201
136
  );
202
137
 
203
- // Build result object
204
138
  const toolMap = {};
205
139
  TOOL_DEFINITIONS.forEach((tool, index) => {
206
140
  toolMap[tool.name] = results[index];
@@ -209,11 +143,11 @@ async function verifyToolsAsync() {
209
143
  return toolMap;
210
144
  }
211
145
 
212
- // When run directly, output JSON (uses async for better performance)
146
+ // When run directly, output JSON
213
147
  if (require.main === module) {
214
148
  (async () => {
215
149
  try {
216
- const result = await verifyToolsAsync();
150
+ const result = await verifyTools();
217
151
  console.log(JSON.stringify(result, null, 2));
218
152
  } catch (error) {
219
153
  console.error(JSON.stringify({
@@ -228,8 +162,6 @@ if (require.main === module) {
228
162
  // Export for use as module
229
163
  module.exports = {
230
164
  verifyTools,
231
- verifyToolsAsync,
232
165
  checkTool,
233
- checkToolAsync,
234
166
  TOOL_DEFINITIONS
235
167
  };
@@ -0,0 +1,195 @@
1
+ # JSON Schema Definitions
2
+
3
+ JSON Schema validation for plugin manifests and configuration files.
4
+
5
+ ## Overview
6
+
7
+ This directory contains JSON Schema definitions and validators for:
8
+
9
+ - **Plugin Manifest** (`plugin.json`) - Plugin metadata validation
10
+ - Additional schemas can be added for other JSON config files
11
+
12
+ ## Files
13
+
14
+ - `plugin-manifest.schema.json` - JSON Schema for plugin.json
15
+ - `validator.js` - Schema validation utility
16
+ - `README.md` - This file
17
+
18
+ ## Usage
19
+
20
+ ### Command Line
21
+
22
+ Validate a plugin manifest:
23
+
24
+ ```bash
25
+ # Validate default location
26
+ node lib/schemas/validator.js
27
+
28
+ # Validate specific file
29
+ node lib/schemas/validator.js plugins/next-task/.claude-plugin/plugin.json
30
+
31
+ # Output on success:
32
+ # ✓ Manifest is valid
33
+ # Plugin: next-task v2.3.1
34
+ # Author: Avi Fenesh
35
+
36
+ # Output on failure:
37
+ # ✗ Manifest is invalid:
38
+ # - Missing required property: name
39
+ # - version: does not match pattern
40
+ ```
41
+
42
+ ### Programmatic Use
43
+
44
+ ```javascript
45
+ const { validateManifestFile, SchemaValidator } = require('./lib/schemas/validator');
46
+
47
+ // Validate a manifest file
48
+ const result = validateManifestFile('.claude-plugin/plugin.json');
49
+ if (result.valid) {
50
+ console.log('Valid!', result.manifest);
51
+ } else {
52
+ console.error('Errors:', result.errors);
53
+ }
54
+
55
+ // Validate manifest object directly
56
+ const manifest = {
57
+ name: "my-plugin",
58
+ version: "1.0.0",
59
+ description: "My awesome plugin",
60
+ author: { name: "John Doe" },
61
+ license: "MIT"
62
+ };
63
+
64
+ const validation = SchemaValidator.validatePluginManifest(manifest);
65
+ if (!validation.valid) {
66
+ console.error(validation.errors);
67
+ }
68
+ ```
69
+
70
+ ### Integration in Tests
71
+
72
+ ```javascript
73
+ const { validateManifestFile } = require('./lib/schemas/validator');
74
+ const path = require('path');
75
+
76
+ describe('Plugin Manifest', () => {
77
+ it('should be valid', () => {
78
+ const result = validateManifestFile(
79
+ path.join(__dirname, '../.claude-plugin/plugin.json')
80
+ );
81
+ expect(result.valid).toBe(true);
82
+ expect(result.errors).toHaveLength(0);
83
+ });
84
+
85
+ it('should have required fields', () => {
86
+ const result = validateManifestFile(
87
+ path.join(__dirname, '../.claude-plugin/plugin.json')
88
+ );
89
+ expect(result.manifest.name).toBeTruthy();
90
+ expect(result.manifest.version).toMatch(/^\d+\.\d+\.\d+$/);
91
+ });
92
+ });
93
+ ```
94
+
95
+ ## Schema Details
96
+
97
+ ### plugin-manifest.schema.json
98
+
99
+ Validates `plugin.json` files with the following rules:
100
+
101
+ **Required fields:**
102
+ - `name` - Kebab-case identifier (e.g., "awesome-slash")
103
+ - `version` - Semantic version (e.g., "1.0.0")
104
+ - `description` - 10-500 characters
105
+ - `author` - Object with `name` field
106
+ - `license` - SPDX identifier (e.g., "MIT")
107
+
108
+ **Optional fields:**
109
+ - `homepage` - URL to plugin homepage
110
+ - `repository` - URL to source repository
111
+ - `keywords` - Array of search terms (1-20 items)
112
+ - `minClaudeVersion` - Minimum required version
113
+ - `dependencies` - Plugin dependencies
114
+ - `config` - Plugin configuration
115
+
116
+ **Constraints:**
117
+ - `name` must be lowercase, kebab-case
118
+ - `version` must follow semver (X.Y.Z)
119
+ - `keywords` must be unique, 2-50 chars each
120
+ - `author.email` must be valid email format
121
+ - `author.url` must be valid URI format
122
+
123
+ ### Example Valid Manifest
124
+
125
+ ```json
126
+ {
127
+ "name": "awesome-slash",
128
+ "version": "2.4.2",
129
+ "description": "Professional-grade slash commands for Claude Code",
130
+ "author": {
131
+ "name": "Avi Fenesh",
132
+ "email": "[email protected]",
133
+ "url": "https://github.com/avifenesh"
134
+ },
135
+ "homepage": "https://github.com/avifenesh/awesome-slash",
136
+ "repository": "https://github.com/avifenesh/awesome-slash",
137
+ "license": "MIT",
138
+ "keywords": ["workflow", "automation", "productivity"],
139
+ "minClaudeVersion": "1.0.0"
140
+ }
141
+ ```
142
+
143
+ ## Validation Errors
144
+
145
+ Common validation errors and fixes:
146
+
147
+ ### "Missing required property: name"
148
+ **Fix:** Add `name` field to plugin.json
149
+
150
+ ### "name: does not match pattern"
151
+ **Fix:** Use lowercase, kebab-case (e.g., "my-plugin" not "MyPlugin")
152
+
153
+ ### "version: does not match pattern"
154
+ **Fix:** Use semantic version format: "1.0.0" not "v1.0" or "1.0"
155
+
156
+ ### "description: string too short"
157
+ **Fix:** Provide description with at least 10 characters
158
+
159
+ ### "Unexpected property: xyz"
160
+ **Fix:** Remove invalid field or update schema if it's a new field
161
+
162
+ ### "keywords: array too long"
163
+ **Fix:** Use maximum 20 keywords
164
+
165
+ ## Adding New Schemas
166
+
167
+ To add validation for other JSON files:
168
+
169
+ 1. Create `{name}.schema.json` in this directory
170
+ 2. Add validator method to `validator.js`:
171
+ ```javascript
172
+ static validate{Name}(data) {
173
+ const schema = this.loadSchema(path.join(__dirname, '{name}.schema.json'));
174
+ return this.validate(data, schema);
175
+ }
176
+ ```
177
+ 3. Export validation function
178
+ 4. Update this README
179
+
180
+ ## External Tools
181
+
182
+ For more advanced validation, consider using:
183
+
184
+ - [ajv](https://ajv.js.org/) - Production-grade JSON Schema validator
185
+ - [json-schema-validator](https://www.jsonschemavalidator.net/) - Online validator
186
+ - [VSCode JSON Schema](https://code.visualstudio.com/docs/languages/json#_json-schemas) - IDE integration
187
+
188
+ ## Related
189
+
190
+ - **TypeScript Types**: `lib/types/` - TypeScript definitions for same structures
191
+ - **Plugin Manifest Spec**: See `lib/types/README.md` for detailed type documentation
192
+
193
+ ## License
194
+
195
+ MIT © Avi Fenesh
@@ -0,0 +1,247 @@
1
+ /**
2
+ * JSON Schema Validator
3
+ * Validates plugin manifests against JSON Schema
4
+ *
5
+ * @module lib/schemas/validator
6
+ * @author Avi Fenesh
7
+ * @license MIT
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+
13
+ /**
14
+ * Simple JSON Schema validator (minimal implementation)
15
+ * For production use, consider using a library like ajv
16
+ */
17
+ class SchemaValidator {
18
+ /**
19
+ * Load a JSON Schema from file
20
+ * @param {string} schemaPath - Path to schema file
21
+ * @returns {Object} Loaded schema
22
+ */
23
+ static loadSchema(schemaPath) {
24
+ const content = fs.readFileSync(schemaPath, 'utf8');
25
+ return JSON.parse(content);
26
+ }
27
+
28
+ /**
29
+ * Validate data against a schema
30
+ * @param {Object} data - Data to validate
31
+ * @param {Object} schema - JSON Schema
32
+ * @returns {{valid: boolean, errors: string[]}} Validation result
33
+ */
34
+ static validate(data, schema) {
35
+ const errors = [];
36
+
37
+ // Check type (handle arrays correctly and null separately)
38
+ if (schema.type) {
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
+
48
+ if (actualType !== schema.type) {
49
+ errors.push(`Expected type ${schema.type}, got ${actualType}`);
50
+ return { valid: false, errors };
51
+ }
52
+ }
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
+
88
+ // Check required properties
89
+ if (schema.required && Array.isArray(schema.required)) {
90
+ for (const required of schema.required) {
91
+ if (!(required in data)) {
92
+ errors.push(`Missing required property: ${required}`);
93
+ }
94
+ }
95
+ }
96
+
97
+ // Check properties (guard against null)
98
+ if (schema.properties && typeof data === 'object' && data !== null) {
99
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
100
+ if (key in data) {
101
+ const propResult = this.validateProperty(data[key], propSchema, key);
102
+ errors.push(...propResult.errors);
103
+ }
104
+ }
105
+ }
106
+
107
+ // Check additional properties (guard against null, honor patternProperties)
108
+ if (schema.additionalProperties === false && typeof data === 'object' && data !== null) {
109
+ const allowedKeys = new Set(Object.keys(schema.properties || {}));
110
+
111
+ // Honor patternProperties - don't reject keys that match patterns
112
+ const patternProps = schema.patternProperties || {};
113
+ const patternRegexes = Object.keys(patternProps).map(p => new RegExp(p));
114
+
115
+ for (const key of Object.keys(data)) {
116
+ const matchesPattern = patternRegexes.some(regex => regex.test(key));
117
+ if (!allowedKeys.has(key) && !matchesPattern) {
118
+ errors.push(`Unexpected property: ${key}`);
119
+ }
120
+ }
121
+ }
122
+
123
+ return { valid: errors.length === 0, errors };
124
+ }
125
+
126
+ /**
127
+ * Validate a single property
128
+ * @param {*} value - Property value
129
+ * @param {Object} schema - Property schema
130
+ * @param {string} path - Property path for error messages
131
+ * @returns {{valid: boolean, errors: string[]}} Validation result
132
+ */
133
+ static validateProperty(value, schema, path) {
134
+ const errors = [];
135
+
136
+ // Type check
137
+ if (schema.type) {
138
+ const actualType = Array.isArray(value) ? 'array' : typeof value;
139
+ if (actualType !== schema.type) {
140
+ errors.push(`${path}: expected type ${schema.type}, got ${actualType}`);
141
+ return { valid: false, errors };
142
+ }
143
+ }
144
+
145
+ // String validations
146
+ if (schema.type === 'string' && typeof value === 'string') {
147
+ if (schema.minLength && value.length < schema.minLength) {
148
+ errors.push(`${path}: string too short (min ${schema.minLength})`);
149
+ }
150
+ if (schema.maxLength && value.length > schema.maxLength) {
151
+ errors.push(`${path}: string too long (max ${schema.maxLength})`);
152
+ }
153
+ if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
154
+ errors.push(`${path}: does not match pattern ${schema.pattern}`);
155
+ }
156
+ }
157
+
158
+ // Array validations
159
+ if (schema.type === 'array' && Array.isArray(value)) {
160
+ if (schema.minItems && value.length < schema.minItems) {
161
+ errors.push(`${path}: array too short (min ${schema.minItems})`);
162
+ }
163
+ if (schema.maxItems && value.length > schema.maxItems) {
164
+ errors.push(`${path}: array too long (max ${schema.maxItems})`);
165
+ }
166
+ if (schema.uniqueItems) {
167
+ const seen = new Set();
168
+ for (const item of value) {
169
+ const key = JSON.stringify(item);
170
+ if (seen.has(key)) {
171
+ errors.push(`${path}: duplicate items not allowed`);
172
+ break;
173
+ }
174
+ seen.add(key);
175
+ }
176
+ }
177
+ }
178
+
179
+ // Object validations
180
+ if (schema.type === 'object' && typeof value === 'object' && value !== null) {
181
+ const result = this.validate(value, schema);
182
+ for (const error of result.errors) {
183
+ errors.push(`${path}.${error}`);
184
+ }
185
+ }
186
+
187
+ return { valid: errors.length === 0, errors };
188
+ }
189
+
190
+ /**
191
+ * Validate a plugin manifest
192
+ * @param {Object} manifest - Plugin manifest to validate
193
+ * @returns {{valid: boolean, errors: string[]}} Validation result
194
+ */
195
+ static validatePluginManifest(manifest) {
196
+ const schemaPath = path.join(__dirname, 'plugin-manifest.schema.json');
197
+ const schema = this.loadSchema(schemaPath);
198
+ return this.validate(manifest, schema);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Validate a plugin manifest file
204
+ * @param {string} manifestPath - Path to plugin.json
205
+ * @returns {{valid: boolean, errors: string[], manifest?: Object}} Validation result
206
+ */
207
+ function validateManifestFile(manifestPath) {
208
+ try {
209
+ const content = fs.readFileSync(manifestPath, 'utf8');
210
+ const manifest = JSON.parse(content);
211
+ const result = SchemaValidator.validatePluginManifest(manifest);
212
+ return { ...result, manifest };
213
+ } catch (error) {
214
+ return {
215
+ valid: false,
216
+ errors: [`Failed to load manifest: ${error.message}`]
217
+ };
218
+ }
219
+ }
220
+
221
+ module.exports = {
222
+ SchemaValidator,
223
+ validateManifestFile
224
+ };
225
+
226
+ // CLI usage
227
+ if (require.main === module) {
228
+ const manifestPath = process.argv[2] || '.claude-plugin/plugin.json';
229
+
230
+ console.log(`Validating: ${manifestPath}`);
231
+ const result = validateManifestFile(manifestPath);
232
+
233
+ if (result.valid) {
234
+ console.log('✓ Manifest is valid');
235
+ if (result.manifest) {
236
+ console.log(` Plugin: ${result.manifest.name} v${result.manifest.version}`);
237
+ console.log(` Author: ${result.manifest.author.name}`);
238
+ }
239
+ process.exit(0);
240
+ } else {
241
+ console.error('✗ Manifest is invalid:');
242
+ for (const error of result.errors) {
243
+ console.error(` - ${error}`);
244
+ }
245
+ process.exit(1);
246
+ }
247
+ }