awesome-slash 2.4.4 → 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 (144) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +88 -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/delivery-validator.md +2 -2
  59. package/plugins/next-task/agents/implementation-agent.md +3 -4
  60. package/plugins/next-task/agents/planning-agent.md +77 -19
  61. package/plugins/next-task/agents/review-orchestrator.md +21 -122
  62. package/plugins/next-task/agents/task-discoverer.md +164 -23
  63. package/plugins/next-task/commands/next-task.md +180 -14
  64. package/plugins/next-task/lib/index.js +170 -0
  65. package/plugins/next-task/lib/patterns/review-patterns.js +58 -11
  66. package/plugins/next-task/lib/patterns/slop-patterns.js +170 -129
  67. package/plugins/next-task/lib/platform/detect-platform.js +212 -123
  68. package/plugins/next-task/lib/platform/detection-configs.js +93 -0
  69. package/plugins/next-task/lib/platform/verify-tools.js +10 -1
  70. package/plugins/next-task/lib/schemas/README.md +195 -0
  71. package/plugins/next-task/lib/schemas/validator.js +205 -0
  72. package/plugins/next-task/lib/sources/custom-handler.js +199 -0
  73. package/plugins/next-task/lib/sources/policy-questions.js +239 -0
  74. package/plugins/next-task/lib/sources/source-cache.js +149 -0
  75. package/plugins/next-task/lib/state/workflow-state.js +382 -484
  76. package/plugins/next-task/lib/types/README.md +292 -0
  77. package/plugins/next-task/lib/types/agent-frontmatter.d.ts +134 -0
  78. package/plugins/next-task/lib/types/command-frontmatter.d.ts +107 -0
  79. package/plugins/next-task/lib/types/hook-frontmatter.d.ts +115 -0
  80. package/plugins/next-task/lib/types/index.d.ts +84 -0
  81. package/plugins/next-task/lib/types/plugin-manifest.d.ts +102 -0
  82. package/plugins/next-task/lib/types/skill-frontmatter.d.ts +89 -0
  83. package/plugins/next-task/lib/utils/cache-manager.js +154 -0
  84. package/plugins/next-task/lib/utils/context-optimizer.js +115 -37
  85. package/plugins/next-task/lib/utils/deprecation.js +37 -0
  86. package/plugins/next-task/lib/utils/shell-escape.js +88 -0
  87. package/plugins/project-review/.claude-plugin/plugin.json +1 -1
  88. package/plugins/project-review/lib/index.js +170 -0
  89. package/plugins/project-review/lib/patterns/review-patterns.js +58 -11
  90. package/plugins/project-review/lib/patterns/slop-patterns.js +170 -129
  91. package/plugins/project-review/lib/platform/detect-platform.js +212 -123
  92. package/plugins/project-review/lib/platform/detection-configs.js +93 -0
  93. package/plugins/project-review/lib/platform/verify-tools.js +10 -1
  94. package/plugins/project-review/lib/schemas/README.md +195 -0
  95. package/plugins/project-review/lib/schemas/validator.js +205 -0
  96. package/plugins/project-review/lib/sources/custom-handler.js +199 -0
  97. package/plugins/project-review/lib/sources/policy-questions.js +239 -0
  98. package/plugins/project-review/lib/sources/source-cache.js +149 -0
  99. package/plugins/project-review/lib/state/workflow-state.js +382 -484
  100. package/plugins/project-review/lib/types/README.md +292 -0
  101. package/plugins/project-review/lib/types/agent-frontmatter.d.ts +134 -0
  102. package/plugins/project-review/lib/types/command-frontmatter.d.ts +107 -0
  103. package/plugins/project-review/lib/types/hook-frontmatter.d.ts +115 -0
  104. package/plugins/project-review/lib/types/index.d.ts +84 -0
  105. package/plugins/project-review/lib/types/plugin-manifest.d.ts +102 -0
  106. package/plugins/project-review/lib/types/skill-frontmatter.d.ts +89 -0
  107. package/plugins/project-review/lib/utils/cache-manager.js +154 -0
  108. package/plugins/project-review/lib/utils/context-optimizer.js +115 -37
  109. package/plugins/project-review/lib/utils/deprecation.js +37 -0
  110. package/plugins/project-review/lib/utils/shell-escape.js +88 -0
  111. package/plugins/reality-check/.claude-plugin/plugin.json +1 -1
  112. package/plugins/reality-check/agents/code-explorer.md +1 -1
  113. package/plugins/ship/.claude-plugin/plugin.json +1 -1
  114. package/plugins/ship/lib/index.js +170 -0
  115. package/plugins/ship/lib/patterns/review-patterns.js +58 -11
  116. package/plugins/ship/lib/patterns/slop-patterns.js +170 -129
  117. package/plugins/ship/lib/platform/detect-platform.js +212 -123
  118. package/plugins/ship/lib/platform/detection-configs.js +93 -0
  119. package/plugins/ship/lib/platform/verify-tools.js +10 -1
  120. package/plugins/ship/lib/schemas/README.md +195 -0
  121. package/plugins/ship/lib/schemas/validator.js +205 -0
  122. package/plugins/ship/lib/sources/custom-handler.js +199 -0
  123. package/plugins/ship/lib/sources/policy-questions.js +239 -0
  124. package/plugins/ship/lib/sources/source-cache.js +149 -0
  125. package/plugins/ship/lib/state/workflow-state.js +382 -484
  126. package/plugins/ship/lib/types/README.md +292 -0
  127. package/plugins/ship/lib/types/agent-frontmatter.d.ts +134 -0
  128. package/plugins/ship/lib/types/command-frontmatter.d.ts +107 -0
  129. package/plugins/ship/lib/types/hook-frontmatter.d.ts +115 -0
  130. package/plugins/ship/lib/types/index.d.ts +84 -0
  131. package/plugins/ship/lib/types/plugin-manifest.d.ts +102 -0
  132. package/plugins/ship/lib/types/skill-frontmatter.d.ts +89 -0
  133. package/plugins/ship/lib/utils/cache-manager.js +154 -0
  134. package/plugins/ship/lib/utils/context-optimizer.js +115 -37
  135. package/plugins/ship/lib/utils/deprecation.js +37 -0
  136. package/plugins/ship/lib/utils/shell-escape.js +88 -0
  137. package/lib/state/workflow-state.schema.json +0 -282
  138. package/plugins/deslop-around/lib/state/workflow-state.schema.json +0 -282
  139. package/plugins/next-task/agents/policy-selector.md +0 -248
  140. package/plugins/next-task/lib/state/tasks-registry.schema.json +0 -85
  141. package/plugins/next-task/lib/state/workflow-state.schema.json +0 -282
  142. package/plugins/next-task/lib/state/worktree-status.schema.json +0 -219
  143. package/plugins/project-review/lib/state/workflow-state.schema.json +0 -282
  144. package/plugins/ship/lib/state/workflow-state.schema.json +0 -282
@@ -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
+ };
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Source Cache
3
+ * File-based persistence for task source preferences
4
+ *
5
+ * @module lib/sources/source-cache
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ const SOURCES_DIR = '.claude/sources';
12
+ const PREFERENCE_FILE = 'preference.json';
13
+
14
+ /**
15
+ * Validate tool name to prevent path traversal
16
+ * @param {string} toolName - Tool name to validate
17
+ * @returns {boolean} True if valid
18
+ */
19
+ function isValidToolName(toolName) {
20
+ // Prevent path traversal and shell metacharacters
21
+ return /^[a-zA-Z0-9_-]+$/.test(toolName);
22
+ }
23
+
24
+ /**
25
+ * Ensure sources directory exists
26
+ * @returns {string} Path to sources directory
27
+ */
28
+ function ensureDir() {
29
+ if (!fs.existsSync(SOURCES_DIR)) {
30
+ fs.mkdirSync(SOURCES_DIR, { recursive: true });
31
+ }
32
+ return SOURCES_DIR;
33
+ }
34
+
35
+ /**
36
+ * Get cached source preference
37
+ * @returns {Object|null} Preference object or null if not cached
38
+ * @example
39
+ * // Returns: { source: 'github' }
40
+ * // Or: { source: 'custom', type: 'cli', tool: 'tea' }
41
+ */
42
+ function getPreference() {
43
+ const filePath = path.join(SOURCES_DIR, PREFERENCE_FILE);
44
+ if (!fs.existsSync(filePath)) {
45
+ return null;
46
+ }
47
+ try {
48
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
49
+ } catch (err) {
50
+ console.error(`Failed to read preference file:`, err.message);
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Save source preference
57
+ * @param {Object} preference - Preference object
58
+ * @param {string} preference.source - Source type (github, gitlab, local, custom, other)
59
+ * @param {string} [preference.type] - For custom: mcp, cli, skill, file
60
+ * @param {string} [preference.tool] - Tool name or path
61
+ * @param {string} [preference.description] - For other: user's free text
62
+ */
63
+ function savePreference(preference) {
64
+ ensureDir();
65
+ const filePath = path.join(SOURCES_DIR, PREFERENCE_FILE);
66
+ fs.writeFileSync(filePath, JSON.stringify({
67
+ ...preference,
68
+ savedAt: new Date().toISOString()
69
+ }, null, 2));
70
+ }
71
+
72
+ /**
73
+ * Get cached tool capabilities (for custom sources)
74
+ * @param {string} toolName - Tool identifier (e.g., 'tea', 'glab')
75
+ * @returns {Object|null} Capabilities object or null
76
+ */
77
+ function getToolCapabilities(toolName) {
78
+ // Prevent path traversal
79
+ if (!isValidToolName(toolName)) {
80
+ console.error(`Invalid tool name: ${toolName}`);
81
+ return null;
82
+ }
83
+ const filePath = path.join(SOURCES_DIR, `${toolName}.json`);
84
+ if (!fs.existsSync(filePath)) {
85
+ return null;
86
+ }
87
+ try {
88
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
89
+ } catch (err) {
90
+ console.error(`Failed to read tool capabilities for ${toolName}:`, err.message);
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Save tool capabilities after discovery
97
+ * @param {string} toolName - Tool identifier
98
+ * @param {Object} capabilities - Discovered capabilities
99
+ * @param {string[]} capabilities.features - Available features (issues, prs, ci)
100
+ * @param {Object} capabilities.commands - Command mappings
101
+ */
102
+ function saveToolCapabilities(toolName, capabilities) {
103
+ // Prevent path traversal
104
+ if (!isValidToolName(toolName)) {
105
+ console.error(`Invalid tool name: ${toolName}`);
106
+ return;
107
+ }
108
+ ensureDir();
109
+ const filePath = path.join(SOURCES_DIR, `${toolName}.json`);
110
+ fs.writeFileSync(filePath, JSON.stringify({
111
+ ...capabilities,
112
+ discoveredAt: new Date().toISOString()
113
+ }, null, 2));
114
+ }
115
+
116
+ /**
117
+ * Clear all cached preferences
118
+ */
119
+ function clearCache() {
120
+ if (fs.existsSync(SOURCES_DIR)) {
121
+ const files = fs.readdirSync(SOURCES_DIR);
122
+ for (const file of files) {
123
+ const filePath = path.join(SOURCES_DIR, file);
124
+ const stats = fs.statSync(filePath);
125
+ if (stats.isFile()) {
126
+ fs.unlinkSync(filePath);
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Check if preference matches a specific source
134
+ * @param {string} source - Source to check
135
+ * @returns {boolean} True if preference matches
136
+ */
137
+ function isPreferred(source) {
138
+ const pref = getPreference();
139
+ return pref?.source === source;
140
+ }
141
+
142
+ module.exports = {
143
+ getPreference,
144
+ savePreference,
145
+ getToolCapabilities,
146
+ saveToolCapabilities,
147
+ clearCache,
148
+ isPreferred
149
+ };