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
|
@@ -15,14 +15,19 @@ const { execFileSync, spawnSync, spawn } = require('child_process');
|
|
|
15
15
|
// Detect Windows platform
|
|
16
16
|
const isWindows = process.platform === 'win32';
|
|
17
17
|
|
|
18
|
+
// Import shared deprecation utilities
|
|
19
|
+
const { warnDeprecation, _resetDeprecationWarnings } = require('../utils/deprecation');
|
|
20
|
+
|
|
18
21
|
/**
|
|
19
22
|
* Checks if a tool is available and returns its version (sync)
|
|
20
23
|
* Uses safe execution methods to avoid shell injection vulnerabilities
|
|
24
|
+
* @deprecated Use checkToolAsync() instead. Will be removed in v3.0.0.
|
|
21
25
|
* @param {string} command - Command to check (e.g., 'git', 'node')
|
|
22
26
|
* @param {string} versionFlag - Flag to get version (default: '--version')
|
|
23
27
|
* @returns {Object} { available: boolean, version: string|null }
|
|
24
28
|
*/
|
|
25
29
|
function checkTool(command, versionFlag = '--version') {
|
|
30
|
+
warnDeprecation('checkTool', 'checkToolAsync');
|
|
26
31
|
// Validate command contains only safe characters (alphanumeric, underscore, hyphen)
|
|
27
32
|
if (!/^[a-zA-Z0-9_-]+$/.test(command)) {
|
|
28
33
|
return { available: false, version: null };
|
|
@@ -179,9 +184,11 @@ const TOOL_DEFINITIONS = [
|
|
|
179
184
|
|
|
180
185
|
/**
|
|
181
186
|
* Verifies all development tools (sync)
|
|
187
|
+
* @deprecated Use verifyToolsAsync() instead. Will be removed in v3.0.0.
|
|
182
188
|
* @returns {Object} Tool availability map
|
|
183
189
|
*/
|
|
184
190
|
function verifyTools() {
|
|
191
|
+
warnDeprecation('verifyTools', 'verifyToolsAsync');
|
|
185
192
|
const result = {};
|
|
186
193
|
for (const tool of TOOL_DEFINITIONS) {
|
|
187
194
|
result[tool.name] = checkTool(tool.name, tool.flag);
|
|
@@ -231,5 +238,7 @@ module.exports = {
|
|
|
231
238
|
verifyToolsAsync,
|
|
232
239
|
checkTool,
|
|
233
240
|
checkToolAsync,
|
|
234
|
-
TOOL_DEFINITIONS
|
|
241
|
+
TOOL_DEFINITIONS,
|
|
242
|
+
// Testing utilities (prefixed with _ to indicate internal use)
|
|
243
|
+
_resetDeprecationWarnings
|
|
235
244
|
};
|
|
@@ -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,205 @@
|
|
|
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)
|
|
38
|
+
if (schema.type) {
|
|
39
|
+
const actualType = Array.isArray(data) ? 'array' : typeof data;
|
|
40
|
+
if (actualType !== schema.type) {
|
|
41
|
+
errors.push(`Expected type ${schema.type}, got ${actualType}`);
|
|
42
|
+
return { valid: false, errors };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check required properties
|
|
47
|
+
if (schema.required && Array.isArray(schema.required)) {
|
|
48
|
+
for (const required of schema.required) {
|
|
49
|
+
if (!(required in data)) {
|
|
50
|
+
errors.push(`Missing required property: ${required}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check properties (guard against null)
|
|
56
|
+
if (schema.properties && typeof data === 'object' && data !== null) {
|
|
57
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
58
|
+
if (key in data) {
|
|
59
|
+
const propResult = this.validateProperty(data[key], propSchema, key);
|
|
60
|
+
errors.push(...propResult.errors);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check additional properties (guard against null, honor patternProperties)
|
|
66
|
+
if (schema.additionalProperties === false && typeof data === 'object' && data !== null) {
|
|
67
|
+
const allowedKeys = new Set(Object.keys(schema.properties || {}));
|
|
68
|
+
|
|
69
|
+
// Honor patternProperties - don't reject keys that match patterns
|
|
70
|
+
const patternProps = schema.patternProperties || {};
|
|
71
|
+
const patternRegexes = Object.keys(patternProps).map(p => new RegExp(p));
|
|
72
|
+
|
|
73
|
+
for (const key of Object.keys(data)) {
|
|
74
|
+
const matchesPattern = patternRegexes.some(regex => regex.test(key));
|
|
75
|
+
if (!allowedKeys.has(key) && !matchesPattern) {
|
|
76
|
+
errors.push(`Unexpected property: ${key}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { valid: errors.length === 0, errors };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Validate a single property
|
|
86
|
+
* @param {*} value - Property value
|
|
87
|
+
* @param {Object} schema - Property schema
|
|
88
|
+
* @param {string} path - Property path for error messages
|
|
89
|
+
* @returns {{valid: boolean, errors: string[]}} Validation result
|
|
90
|
+
*/
|
|
91
|
+
static validateProperty(value, schema, path) {
|
|
92
|
+
const errors = [];
|
|
93
|
+
|
|
94
|
+
// Type check
|
|
95
|
+
if (schema.type) {
|
|
96
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
97
|
+
if (actualType !== schema.type) {
|
|
98
|
+
errors.push(`${path}: expected type ${schema.type}, got ${actualType}`);
|
|
99
|
+
return { valid: false, errors };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// String validations
|
|
104
|
+
if (schema.type === 'string' && typeof value === 'string') {
|
|
105
|
+
if (schema.minLength && value.length < schema.minLength) {
|
|
106
|
+
errors.push(`${path}: string too short (min ${schema.minLength})`);
|
|
107
|
+
}
|
|
108
|
+
if (schema.maxLength && value.length > schema.maxLength) {
|
|
109
|
+
errors.push(`${path}: string too long (max ${schema.maxLength})`);
|
|
110
|
+
}
|
|
111
|
+
if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
|
|
112
|
+
errors.push(`${path}: does not match pattern ${schema.pattern}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Array validations
|
|
117
|
+
if (schema.type === 'array' && Array.isArray(value)) {
|
|
118
|
+
if (schema.minItems && value.length < schema.minItems) {
|
|
119
|
+
errors.push(`${path}: array too short (min ${schema.minItems})`);
|
|
120
|
+
}
|
|
121
|
+
if (schema.maxItems && value.length > schema.maxItems) {
|
|
122
|
+
errors.push(`${path}: array too long (max ${schema.maxItems})`);
|
|
123
|
+
}
|
|
124
|
+
if (schema.uniqueItems) {
|
|
125
|
+
const seen = new Set();
|
|
126
|
+
for (const item of value) {
|
|
127
|
+
const key = JSON.stringify(item);
|
|
128
|
+
if (seen.has(key)) {
|
|
129
|
+
errors.push(`${path}: duplicate items not allowed`);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
seen.add(key);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Object validations
|
|
138
|
+
if (schema.type === 'object' && typeof value === 'object' && value !== null) {
|
|
139
|
+
const result = this.validate(value, schema);
|
|
140
|
+
for (const error of result.errors) {
|
|
141
|
+
errors.push(`${path}.${error}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { valid: errors.length === 0, errors };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validate a plugin manifest
|
|
150
|
+
* @param {Object} manifest - Plugin manifest to validate
|
|
151
|
+
* @returns {{valid: boolean, errors: string[]}} Validation result
|
|
152
|
+
*/
|
|
153
|
+
static validatePluginManifest(manifest) {
|
|
154
|
+
const schemaPath = path.join(__dirname, 'plugin-manifest.schema.json');
|
|
155
|
+
const schema = this.loadSchema(schemaPath);
|
|
156
|
+
return this.validate(manifest, schema);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate a plugin manifest file
|
|
162
|
+
* @param {string} manifestPath - Path to plugin.json
|
|
163
|
+
* @returns {{valid: boolean, errors: string[], manifest?: Object}} Validation result
|
|
164
|
+
*/
|
|
165
|
+
function validateManifestFile(manifestPath) {
|
|
166
|
+
try {
|
|
167
|
+
const content = fs.readFileSync(manifestPath, 'utf8');
|
|
168
|
+
const manifest = JSON.parse(content);
|
|
169
|
+
const result = SchemaValidator.validatePluginManifest(manifest);
|
|
170
|
+
return { ...result, manifest };
|
|
171
|
+
} catch (error) {
|
|
172
|
+
return {
|
|
173
|
+
valid: false,
|
|
174
|
+
errors: [`Failed to load manifest: ${error.message}`]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
SchemaValidator,
|
|
181
|
+
validateManifestFile
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// CLI usage
|
|
185
|
+
if (require.main === module) {
|
|
186
|
+
const manifestPath = process.argv[2] || '.claude-plugin/plugin.json';
|
|
187
|
+
|
|
188
|
+
console.log(`Validating: ${manifestPath}`);
|
|
189
|
+
const result = validateManifestFile(manifestPath);
|
|
190
|
+
|
|
191
|
+
if (result.valid) {
|
|
192
|
+
console.log('✓ Manifest is valid');
|
|
193
|
+
if (result.manifest) {
|
|
194
|
+
console.log(` Plugin: ${result.manifest.name} v${result.manifest.version}`);
|
|
195
|
+
console.log(` Author: ${result.manifest.author.name}`);
|
|
196
|
+
}
|
|
197
|
+
process.exit(0);
|
|
198
|
+
} else {
|
|
199
|
+
console.error('✗ Manifest is invalid:');
|
|
200
|
+
for (const error of result.errors) {
|
|
201
|
+
console.error(` - ${error}`);
|
|
202
|
+
}
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Source Handler
|
|
3
|
+
* Handles follow-up questions and tool probing for custom sources
|
|
4
|
+
*
|
|
5
|
+
* @module lib/sources/custom-handler
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { execFileSync } = require('child_process');
|
|
9
|
+
const sourceCache = require('./source-cache');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate tool name to prevent command injection
|
|
13
|
+
* Only allows alphanumeric, hyphens, and underscores
|
|
14
|
+
* @param {string} toolName - Tool name to validate
|
|
15
|
+
* @returns {boolean} True if valid
|
|
16
|
+
*/
|
|
17
|
+
function isValidToolName(toolName) {
|
|
18
|
+
return /^[a-zA-Z0-9_-]+$/.test(toolName);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Source types for custom selection
|
|
23
|
+
*/
|
|
24
|
+
const SOURCE_TYPES = {
|
|
25
|
+
MCP: 'mcp',
|
|
26
|
+
CLI: 'cli',
|
|
27
|
+
SKILL: 'skill',
|
|
28
|
+
FILE: 'file'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build follow-up questions for custom source
|
|
33
|
+
* Returns AskUserQuestion-compatible structure
|
|
34
|
+
* @returns {Object} Questions object for AskUserQuestion tool
|
|
35
|
+
*/
|
|
36
|
+
function getCustomTypeQuestion() {
|
|
37
|
+
return {
|
|
38
|
+
header: 'Source Type',
|
|
39
|
+
question: 'What type of source is this?',
|
|
40
|
+
options: [
|
|
41
|
+
{ label: 'CLI Tool', description: 'Command-line tool (e.g., tea, glab, jira-cli)' },
|
|
42
|
+
{ label: 'MCP Server', description: 'Model Context Protocol server' },
|
|
43
|
+
{ label: 'Skill/Plugin', description: 'Claude Code skill or plugin' },
|
|
44
|
+
{ label: 'File Path', description: 'Local file with tasks (markdown, JSON, etc.)' }
|
|
45
|
+
],
|
|
46
|
+
multiSelect: false
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build name/path question based on type
|
|
52
|
+
* @param {string} type - Source type (mcp, cli, skill, file)
|
|
53
|
+
* @returns {Object} Questions object for AskUserQuestion tool
|
|
54
|
+
*/
|
|
55
|
+
function getCustomNameQuestion(type) {
|
|
56
|
+
const prompts = {
|
|
57
|
+
cli: { header: 'CLI Tool', question: 'What is the CLI tool name?', hint: 'e.g., tea, glab, jira' },
|
|
58
|
+
mcp: { header: 'MCP Server', question: 'What is the MCP server name?', hint: 'e.g., gitea-mcp, linear-mcp' },
|
|
59
|
+
skill: { header: 'Skill Name', question: 'What is the skill name?', hint: 'e.g., linear:list-issues' },
|
|
60
|
+
file: { header: 'File Path', question: 'What is the file path?', hint: 'e.g., ./backlog.md, /docs/tasks.json' }
|
|
61
|
+
};
|
|
62
|
+
return prompts[type] || prompts.cli;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Map user's type selection to internal type
|
|
67
|
+
* @param {string} selection - User's selection label
|
|
68
|
+
* @returns {string} Internal type (mcp, cli, skill, file)
|
|
69
|
+
*/
|
|
70
|
+
function mapTypeSelection(selection) {
|
|
71
|
+
const map = {
|
|
72
|
+
'CLI Tool': SOURCE_TYPES.CLI,
|
|
73
|
+
'MCP Server': SOURCE_TYPES.MCP,
|
|
74
|
+
'Skill/Plugin': SOURCE_TYPES.SKILL,
|
|
75
|
+
'File Path': SOURCE_TYPES.FILE
|
|
76
|
+
};
|
|
77
|
+
return map[selection] || SOURCE_TYPES.CLI;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Probe CLI tool for available commands
|
|
82
|
+
* @param {string} toolName - CLI tool name
|
|
83
|
+
* @returns {Object} Discovered capabilities
|
|
84
|
+
*/
|
|
85
|
+
function probeCLI(toolName) {
|
|
86
|
+
const capabilities = {
|
|
87
|
+
type: 'cli',
|
|
88
|
+
tool: toolName,
|
|
89
|
+
available: false,
|
|
90
|
+
features: [],
|
|
91
|
+
commands: {}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Validate tool name to prevent command injection
|
|
95
|
+
if (!isValidToolName(toolName)) {
|
|
96
|
+
console.error(`Invalid tool name: ${toolName}`);
|
|
97
|
+
return capabilities;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
// Check if tool exists using execFileSync (prevents command injection)
|
|
102
|
+
execFileSync(toolName, ['--version'], { encoding: 'utf8', stdio: 'pipe' });
|
|
103
|
+
capabilities.available = true;
|
|
104
|
+
} catch {
|
|
105
|
+
return capabilities;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Known CLI patterns
|
|
109
|
+
const knownPatterns = {
|
|
110
|
+
tea: {
|
|
111
|
+
features: ['issues', 'prs', 'reviews'],
|
|
112
|
+
commands: {
|
|
113
|
+
list_issues: 'tea issues list',
|
|
114
|
+
get_issue: 'tea issues view {id}',
|
|
115
|
+
create_pr: 'tea pulls create --title {title} --base {base} --head {head}',
|
|
116
|
+
list_prs: 'tea pulls list',
|
|
117
|
+
get_pr: 'tea pr {id}'
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
glab: {
|
|
121
|
+
features: ['issues', 'prs', 'ci'],
|
|
122
|
+
commands: {
|
|
123
|
+
list_issues: 'glab issue list',
|
|
124
|
+
get_issue: 'glab issue view {id}',
|
|
125
|
+
create_pr: 'glab mr create --title {title} --target-branch {base} --source-branch {head}',
|
|
126
|
+
list_prs: 'glab mr list',
|
|
127
|
+
get_pr: 'glab mr view {id}',
|
|
128
|
+
ci_status: 'glab ci status'
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
gh: {
|
|
132
|
+
features: ['issues', 'prs', 'ci'],
|
|
133
|
+
commands: {
|
|
134
|
+
list_issues: 'gh issue list',
|
|
135
|
+
get_issue: 'gh issue view {id}',
|
|
136
|
+
create_pr: 'gh pr create --title {title} --base {base} --head {head}',
|
|
137
|
+
list_prs: 'gh pr list',
|
|
138
|
+
get_pr: 'gh pr view {id}',
|
|
139
|
+
ci_status: 'gh pr checks {id}'
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Use known pattern if available
|
|
145
|
+
if (knownPatterns[toolName]) {
|
|
146
|
+
capabilities.features = knownPatterns[toolName].features;
|
|
147
|
+
capabilities.commands = knownPatterns[toolName].commands;
|
|
148
|
+
capabilities.pattern = 'known';
|
|
149
|
+
} else {
|
|
150
|
+
// Unknown tool - try to discover via help
|
|
151
|
+
capabilities.pattern = 'discovered';
|
|
152
|
+
capabilities.features = ['unknown'];
|
|
153
|
+
capabilities.commands = {
|
|
154
|
+
help: `${toolName} --help`
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return capabilities;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build complete custom source config and cache it
|
|
163
|
+
* @param {string} type - Source type
|
|
164
|
+
* @param {string} name - Tool name or path
|
|
165
|
+
* @returns {Object} Complete source configuration
|
|
166
|
+
*/
|
|
167
|
+
function buildCustomConfig(type, name) {
|
|
168
|
+
const config = {
|
|
169
|
+
source: 'custom',
|
|
170
|
+
type: type,
|
|
171
|
+
tool: name
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Probe capabilities for CLI tools
|
|
175
|
+
if (type === SOURCE_TYPES.CLI) {
|
|
176
|
+
const capabilities = probeCLI(name);
|
|
177
|
+
config.capabilities = capabilities;
|
|
178
|
+
|
|
179
|
+
// Cache capabilities for fast access
|
|
180
|
+
if (capabilities.available) {
|
|
181
|
+
sourceCache.saveToolCapabilities(name, capabilities);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Save preference
|
|
186
|
+
sourceCache.savePreference(config);
|
|
187
|
+
|
|
188
|
+
return config;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
SOURCE_TYPES,
|
|
193
|
+
getCustomTypeQuestion,
|
|
194
|
+
getCustomNameQuestion,
|
|
195
|
+
mapTypeSelection,
|
|
196
|
+
probeCLI,
|
|
197
|
+
buildCustomConfig,
|
|
198
|
+
isValidToolName
|
|
199
|
+
};
|