awesome-slash 2.4.4 → 2.5.1
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 +123 -1
- package/README.md +186 -159
- 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/state-dir.js +122 -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 +164 -0
- package/lib/state/workflow-state.js +368 -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 -22
- 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 +169 -129
- package/plugins/deslop-around/lib/platform/detect-platform.js +162 -316
- package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
- package/plugins/deslop-around/lib/platform/state-dir.js +122 -0
- package/plugins/deslop-around/lib/platform/verify-tools.js +10 -78
- package/plugins/deslop-around/lib/schemas/README.md +195 -0
- package/plugins/deslop-around/lib/schemas/validator.js +247 -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 +164 -0
- package/plugins/deslop-around/lib/state/workflow-state.js +387 -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/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 +169 -129
- package/plugins/next-task/lib/platform/detect-platform.js +162 -316
- package/plugins/next-task/lib/platform/detection-configs.js +93 -0
- package/plugins/next-task/lib/platform/state-dir.js +122 -0
- package/plugins/next-task/lib/platform/verify-tools.js +10 -78
- package/plugins/next-task/lib/schemas/README.md +195 -0
- package/plugins/next-task/lib/schemas/validator.js +247 -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 +164 -0
- package/plugins/next-task/lib/state/workflow-state.js +387 -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 +169 -129
- package/plugins/project-review/lib/platform/detect-platform.js +162 -316
- package/plugins/project-review/lib/platform/detection-configs.js +93 -0
- package/plugins/project-review/lib/platform/state-dir.js +122 -0
- package/plugins/project-review/lib/platform/verify-tools.js +10 -78
- package/plugins/project-review/lib/schemas/README.md +195 -0
- package/plugins/project-review/lib/schemas/validator.js +247 -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 +164 -0
- package/plugins/project-review/lib/state/workflow-state.js +387 -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/lib/index.js +170 -0
- package/plugins/ship/lib/patterns/review-patterns.js +58 -11
- package/plugins/ship/lib/patterns/slop-patterns.js +169 -129
- package/plugins/ship/lib/platform/detect-platform.js +162 -316
- package/plugins/ship/lib/platform/detection-configs.js +93 -0
- package/plugins/ship/lib/platform/state-dir.js +122 -0
- package/plugins/ship/lib/platform/verify-tools.js +10 -78
- package/plugins/ship/lib/schemas/README.md +195 -0
- package/plugins/ship/lib/schemas/validator.js +247 -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 +164 -0
- package/plugins/ship/lib/state/workflow-state.js +387 -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/scripts/install/codex.sh +216 -72
- package/scripts/install/opencode.sh +197 -21
- 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,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
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Questions Builder
|
|
3
|
+
* Builds AskUserQuestion-ready structure with cache awareness
|
|
4
|
+
*
|
|
5
|
+
* @module lib/sources/policy-questions
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const sourceCache = require('./source-cache');
|
|
9
|
+
const customHandler = require('./custom-handler');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Source label mapping for proper casing
|
|
13
|
+
*/
|
|
14
|
+
const SOURCE_LABELS = {
|
|
15
|
+
github: 'GitHub',
|
|
16
|
+
gitlab: 'GitLab',
|
|
17
|
+
local: 'Local',
|
|
18
|
+
custom: 'Custom',
|
|
19
|
+
other: 'Other'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get policy questions with cache-aware options
|
|
24
|
+
* Call this once - returns full question structure ready for AskUserQuestion
|
|
25
|
+
*
|
|
26
|
+
* @returns {Object} { questions: [...], cachedPreference: {...}|null }
|
|
27
|
+
*/
|
|
28
|
+
function getPolicyQuestions() {
|
|
29
|
+
const cached = sourceCache.getPreference();
|
|
30
|
+
|
|
31
|
+
// Build source options
|
|
32
|
+
const sourceOptions = [];
|
|
33
|
+
|
|
34
|
+
// If cached, add as first option
|
|
35
|
+
if (cached) {
|
|
36
|
+
const cachedLabel = cached.source === 'custom'
|
|
37
|
+
? `${cached.tool} (${cached.type})`
|
|
38
|
+
: SOURCE_LABELS[cached.source] || (cached.source.charAt(0).toUpperCase() + cached.source.slice(1));
|
|
39
|
+
|
|
40
|
+
sourceOptions.push({
|
|
41
|
+
label: `${cachedLabel} (last used)`,
|
|
42
|
+
description: 'Use your previous choice'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Standard options
|
|
47
|
+
sourceOptions.push(
|
|
48
|
+
{ label: 'GitHub Issues', description: 'Use gh CLI to list issues' },
|
|
49
|
+
{ label: 'GitLab Issues', description: 'Use glab CLI to list issues' },
|
|
50
|
+
{ label: 'Local tasks.md', description: 'Read from PLAN.md, tasks.md, or TODO.md' },
|
|
51
|
+
{ label: 'Custom', description: 'Specify your tool: CLI, MCP, Skill, or file path' },
|
|
52
|
+
{ label: 'Other', description: 'Describe your source - agent figures it out' }
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
questions: [
|
|
57
|
+
{
|
|
58
|
+
header: 'Source',
|
|
59
|
+
question: 'Where should I look for tasks?',
|
|
60
|
+
options: sourceOptions,
|
|
61
|
+
multiSelect: false
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
header: 'Priority',
|
|
65
|
+
question: 'What type of tasks to prioritize?',
|
|
66
|
+
options: [
|
|
67
|
+
{ label: 'All', description: 'Consider all tasks, pick by score' },
|
|
68
|
+
{ label: 'Bugs', description: 'Focus on bug fixes' },
|
|
69
|
+
{ label: 'Security', description: 'Security issues first' },
|
|
70
|
+
{ label: 'Features', description: 'New feature development' }
|
|
71
|
+
],
|
|
72
|
+
multiSelect: false
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
header: 'Stop Point',
|
|
76
|
+
question: 'How far should I take this task?',
|
|
77
|
+
options: [
|
|
78
|
+
{ label: 'Merged', description: 'Until PR is merged to main' },
|
|
79
|
+
{ label: 'PR Created', description: 'Stop after creating PR' },
|
|
80
|
+
{ label: 'Implemented', description: 'Stop after local implementation' },
|
|
81
|
+
{ label: 'Deployed', description: 'Deploy to staging' },
|
|
82
|
+
{ label: 'Production', description: 'Full production deployment' }
|
|
83
|
+
],
|
|
84
|
+
multiSelect: false
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
cachedPreference: cached
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get custom source follow-up questions
|
|
93
|
+
* Call after user selects "Custom"
|
|
94
|
+
*
|
|
95
|
+
* @returns {Object} Question structure for custom type selection
|
|
96
|
+
*/
|
|
97
|
+
function getCustomTypeQuestions() {
|
|
98
|
+
return {
|
|
99
|
+
questions: [customHandler.getCustomTypeQuestion()]
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get custom name question based on type
|
|
105
|
+
* @param {string} type - cli, mcp, skill, or file
|
|
106
|
+
* @returns {Object} Question structure for tool/path name
|
|
107
|
+
*/
|
|
108
|
+
function getCustomNameQuestion(type) {
|
|
109
|
+
const q = customHandler.getCustomNameQuestion(type);
|
|
110
|
+
return {
|
|
111
|
+
questions: [{
|
|
112
|
+
header: q.header,
|
|
113
|
+
question: q.question,
|
|
114
|
+
options: [], // Free text input via "Other"
|
|
115
|
+
multiSelect: false
|
|
116
|
+
}]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Parse policy responses and build policy object
|
|
122
|
+
* Also handles caching
|
|
123
|
+
*
|
|
124
|
+
* @param {Object} responses - User's answers
|
|
125
|
+
* @param {string} responses.source - Source selection
|
|
126
|
+
* @param {string} responses.priority - Priority selection
|
|
127
|
+
* @param {string} responses.stopPoint - Stop point selection
|
|
128
|
+
* @param {Object} [responses.custom] - Custom source details (if applicable)
|
|
129
|
+
* @returns {Object} Policy object ready for workflow state
|
|
130
|
+
*/
|
|
131
|
+
function parseAndCachePolicy(responses) {
|
|
132
|
+
const policy = {
|
|
133
|
+
taskSource: mapSource(responses.source, responses.custom),
|
|
134
|
+
priorityFilter: mapPriority(responses.priority),
|
|
135
|
+
stoppingPoint: mapStopPoint(responses.stopPoint)
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Cache source preference (unless "other" which is ad-hoc)
|
|
139
|
+
if (policy.taskSource.source !== 'other') {
|
|
140
|
+
sourceCache.savePreference(policy.taskSource);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return policy;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Map source selection to policy value
|
|
148
|
+
*/
|
|
149
|
+
function mapSource(selection, customDetails) {
|
|
150
|
+
// Check if user selected cached option
|
|
151
|
+
if (selection.includes('(last used)')) {
|
|
152
|
+
return sourceCache.getPreference();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const sourceMap = {
|
|
156
|
+
'GitHub Issues': { source: 'github' },
|
|
157
|
+
'GitLab Issues': { source: 'gitlab' },
|
|
158
|
+
'Local tasks.md': { source: 'local' },
|
|
159
|
+
'Custom': null, // Handled separately
|
|
160
|
+
'Other': null // Handled separately
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
if (selection === 'Custom' && customDetails) {
|
|
164
|
+
// Normalize type label to internal value (e.g., "CLI Tool" -> "cli")
|
|
165
|
+
const normalizedType = customHandler.mapTypeSelection(customDetails.type);
|
|
166
|
+
const config = customHandler.buildCustomConfig(normalizedType, customDetails.name);
|
|
167
|
+
return config;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (selection === 'Other') {
|
|
171
|
+
return { source: 'other', description: customDetails?.description || '' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return sourceMap[selection] || { source: 'github' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Map priority selection to policy value
|
|
179
|
+
*/
|
|
180
|
+
function mapPriority(selection) {
|
|
181
|
+
const map = {
|
|
182
|
+
'All': 'all',
|
|
183
|
+
'Bugs': 'bugs',
|
|
184
|
+
'Security': 'security',
|
|
185
|
+
'Features': 'features'
|
|
186
|
+
};
|
|
187
|
+
return map[selection] || 'all';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Map stop point selection to policy value
|
|
192
|
+
*/
|
|
193
|
+
function mapStopPoint(selection) {
|
|
194
|
+
const map = {
|
|
195
|
+
'Merged': 'merged',
|
|
196
|
+
'PR Created': 'pr-created',
|
|
197
|
+
'Implemented': 'implemented',
|
|
198
|
+
'Deployed': 'deployed',
|
|
199
|
+
'Production': 'production'
|
|
200
|
+
};
|
|
201
|
+
return map[selection] || 'merged';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check if user selected cached preference
|
|
206
|
+
* @param {string} selection - User's source selection
|
|
207
|
+
* @returns {boolean}
|
|
208
|
+
*/
|
|
209
|
+
function isUsingCached(selection) {
|
|
210
|
+
return selection.includes('(last used)');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if custom follow-up is needed
|
|
215
|
+
* @param {string} selection - User's source selection
|
|
216
|
+
* @returns {boolean}
|
|
217
|
+
*/
|
|
218
|
+
function needsCustomFollowUp(selection) {
|
|
219
|
+
return selection === 'Custom';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Check if "other" description is needed
|
|
224
|
+
* @param {string} selection - User's source selection
|
|
225
|
+
* @returns {boolean}
|
|
226
|
+
*/
|
|
227
|
+
function needsOtherDescription(selection) {
|
|
228
|
+
return selection === 'Other';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
module.exports = {
|
|
232
|
+
getPolicyQuestions,
|
|
233
|
+
getCustomTypeQuestions,
|
|
234
|
+
getCustomNameQuestion,
|
|
235
|
+
parseAndCachePolicy,
|
|
236
|
+
isUsingCached,
|
|
237
|
+
needsCustomFollowUp,
|
|
238
|
+
needsOtherDescription
|
|
239
|
+
};
|