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.
Files changed (151) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +123 -1
  4. package/README.md +186 -159
  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/state-dir.js +122 -0
  14. package/lib/platform/verify-tools.js +10 -78
  15. package/lib/schemas/README.md +195 -0
  16. package/lib/schemas/validator.js +247 -0
  17. package/lib/sources/custom-handler.js +199 -0
  18. package/lib/sources/policy-questions.js +239 -0
  19. package/lib/sources/source-cache.js +164 -0
  20. package/lib/state/workflow-state.js +368 -665
  21. package/lib/types/README.md +292 -0
  22. package/lib/types/agent-frontmatter.d.ts +134 -0
  23. package/lib/types/command-frontmatter.d.ts +107 -0
  24. package/lib/types/hook-frontmatter.d.ts +115 -0
  25. package/lib/types/index.d.ts +84 -0
  26. package/lib/types/plugin-manifest.d.ts +102 -0
  27. package/lib/types/skill-frontmatter.d.ts +89 -0
  28. package/lib/utils/cache-manager.js +154 -0
  29. package/lib/utils/context-optimizer.js +5 -36
  30. package/lib/utils/deprecation.js +37 -0
  31. package/lib/utils/shell-escape.js +88 -0
  32. package/mcp-server/index.js +513 -22
  33. package/package.json +6 -2
  34. package/plugins/deslop-around/.claude-plugin/plugin.json +1 -1
  35. package/plugins/deslop-around/lib/index.js +170 -0
  36. package/plugins/deslop-around/lib/patterns/review-patterns.js +58 -11
  37. package/plugins/deslop-around/lib/patterns/slop-patterns.js +169 -129
  38. package/plugins/deslop-around/lib/platform/detect-platform.js +162 -316
  39. package/plugins/deslop-around/lib/platform/detection-configs.js +93 -0
  40. package/plugins/deslop-around/lib/platform/state-dir.js +122 -0
  41. package/plugins/deslop-around/lib/platform/verify-tools.js +10 -78
  42. package/plugins/deslop-around/lib/schemas/README.md +195 -0
  43. package/plugins/deslop-around/lib/schemas/validator.js +247 -0
  44. package/plugins/deslop-around/lib/sources/custom-handler.js +199 -0
  45. package/plugins/deslop-around/lib/sources/policy-questions.js +239 -0
  46. package/plugins/deslop-around/lib/sources/source-cache.js +164 -0
  47. package/plugins/deslop-around/lib/state/workflow-state.js +387 -484
  48. package/plugins/deslop-around/lib/types/README.md +292 -0
  49. package/plugins/deslop-around/lib/types/agent-frontmatter.d.ts +134 -0
  50. package/plugins/deslop-around/lib/types/command-frontmatter.d.ts +107 -0
  51. package/plugins/deslop-around/lib/types/hook-frontmatter.d.ts +115 -0
  52. package/plugins/deslop-around/lib/types/index.d.ts +84 -0
  53. package/plugins/deslop-around/lib/types/plugin-manifest.d.ts +102 -0
  54. package/plugins/deslop-around/lib/types/skill-frontmatter.d.ts +89 -0
  55. package/plugins/deslop-around/lib/utils/cache-manager.js +154 -0
  56. package/plugins/deslop-around/lib/utils/context-optimizer.js +115 -37
  57. package/plugins/deslop-around/lib/utils/deprecation.js +37 -0
  58. package/plugins/deslop-around/lib/utils/shell-escape.js +88 -0
  59. package/plugins/next-task/.claude-plugin/plugin.json +1 -1
  60. package/plugins/next-task/agents/delivery-validator.md +2 -2
  61. package/plugins/next-task/agents/implementation-agent.md +3 -4
  62. package/plugins/next-task/agents/planning-agent.md +77 -19
  63. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  64. package/plugins/next-task/agents/task-discoverer.md +164 -23
  65. package/plugins/next-task/commands/next-task.md +180 -14
  66. package/plugins/next-task/lib/index.js +170 -0
  67. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  68. package/plugins/next-task/lib/patterns/slop-patterns.js +169 -129
  69. package/plugins/next-task/lib/platform/detect-platform.js +162 -316
  70. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  71. package/plugins/next-task/lib/platform/state-dir.js +122 -0
  72. package/plugins/next-task/lib/platform/verify-tools.js +10 -78
  73. package/plugins/next-task/lib/schemas/README.md +195 -0
  74. package/plugins/next-task/lib/schemas/validator.js +247 -0
  75. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  76. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  77. package/plugins/next-task/lib/sources/source-cache.js +164 -0
  78. package/plugins/next-task/lib/state/workflow-state.js +387 -484
  79. package/plugins/next-task/lib/types/README.md +292 -0
  80. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  81. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  82. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  83. package/plugins/next-task/lib/types/index.d.ts +84 -0
  84. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  85. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  86. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  87. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  88. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  89. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  90. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  91. package/plugins/project-review/lib/index.js +170 -0
  92. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  93. package/plugins/project-review/lib/patterns/slop-patterns.js +169 -129
  94. package/plugins/project-review/lib/platform/detect-platform.js +162 -316
  95. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  96. package/plugins/project-review/lib/platform/state-dir.js +122 -0
  97. package/plugins/project-review/lib/platform/verify-tools.js +10 -78
  98. package/plugins/project-review/lib/schemas/README.md +195 -0
  99. package/plugins/project-review/lib/schemas/validator.js +247 -0
  100. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  101. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  102. package/plugins/project-review/lib/sources/source-cache.js +164 -0
  103. package/plugins/project-review/lib/state/workflow-state.js +387 -484
  104. package/plugins/project-review/lib/types/README.md +292 -0
  105. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  106. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  107. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  108. package/plugins/project-review/lib/types/index.d.ts +84 -0
  109. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  110. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  111. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  112. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  113. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  114. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  115. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  116. package/plugins/reality-check/agents/code-explorer.md +1 -1
  117. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  118. package/plugins/ship/lib/index.js +170 -0
  119. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  120. package/plugins/ship/lib/patterns/slop-patterns.js +169 -129
  121. package/plugins/ship/lib/platform/detect-platform.js +162 -316
  122. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  123. package/plugins/ship/lib/platform/state-dir.js +122 -0
  124. package/plugins/ship/lib/platform/verify-tools.js +10 -78
  125. package/plugins/ship/lib/schemas/README.md +195 -0
  126. package/plugins/ship/lib/schemas/validator.js +247 -0
  127. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  128. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  129. package/plugins/ship/lib/sources/source-cache.js +164 -0
  130. package/plugins/ship/lib/state/workflow-state.js +387 -484
  131. package/plugins/ship/lib/types/README.md +292 -0
  132. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  133. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  134. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  135. package/plugins/ship/lib/types/index.d.ts +84 -0
  136. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  137. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  138. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  139. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  140. package/plugins/ship/lib/utils/deprecation.js +37 -0
  141. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  142. package/scripts/install/codex.sh +216 -72
  143. package/scripts/install/opencode.sh +197 -21
  144. package/lib/state/workflow-state.schema.json +0 -282
  145. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  146. package/plugins/next-task/agents/policy-selector.md +0 -248
  147. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  148. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  149. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  150. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  151. 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
+ };