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
@@ -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
+ };