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.
- package/.claude-plugin/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +99 -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/ci-monitor.md +19 -0
- 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/commands/ship-ci-review-loop.md +19 -0
- 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
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
182
|
-
*
|
|
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
|
|
198
|
-
// Run all checks in parallel
|
|
133
|
+
async function verifyTools() {
|
|
199
134
|
const results = await Promise.all(
|
|
200
|
-
TOOL_DEFINITIONS.map(tool =>
|
|
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
|
|
146
|
+
// When run directly, output JSON
|
|
213
147
|
if (require.main === module) {
|
|
214
148
|
(async () => {
|
|
215
149
|
try {
|
|
216
|
-
const result = await
|
|
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
|
+
}
|