agileflow 3.4.0 → 3.4.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 (112) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +4 -4
  3. package/package.json +1 -1
  4. package/scripts/agileflow-welcome.js +79 -0
  5. package/scripts/claude-tmux.sh +12 -36
  6. package/scripts/lib/ac-test-matcher.js +452 -0
  7. package/scripts/lib/audit-registry.js +58 -2
  8. package/scripts/lib/configure-features.js +35 -0
  9. package/scripts/lib/model-profiles.js +25 -5
  10. package/scripts/lib/quality-gates.js +163 -0
  11. package/scripts/lib/signal-detectors.js +43 -0
  12. package/scripts/lib/status-writer.js +255 -0
  13. package/scripts/lib/story-claiming.js +128 -45
  14. package/scripts/lib/task-sync.js +32 -38
  15. package/scripts/lib/tmux-audit-monitor.js +611 -0
  16. package/scripts/lib/tool-registry.yaml +241 -0
  17. package/scripts/lib/tool-shed.js +441 -0
  18. package/scripts/native-team-observer.js +219 -0
  19. package/scripts/obtain-context.js +14 -0
  20. package/scripts/ralph-loop.js +30 -5
  21. package/scripts/smart-detect.js +21 -0
  22. package/scripts/spawn-audit-sessions.js +372 -44
  23. package/scripts/team-manager.js +19 -0
  24. package/src/core/agents/a11y-analyzer-aria.md +155 -0
  25. package/src/core/agents/a11y-analyzer-forms.md +162 -0
  26. package/src/core/agents/a11y-analyzer-keyboard.md +175 -0
  27. package/src/core/agents/a11y-analyzer-semantic.md +153 -0
  28. package/src/core/agents/a11y-analyzer-visual.md +158 -0
  29. package/src/core/agents/a11y-consensus.md +248 -0
  30. package/src/core/agents/ads-consensus.md +74 -0
  31. package/src/core/agents/ads-generate.md +145 -0
  32. package/src/core/agents/ads-performance-tracker.md +197 -0
  33. package/src/core/agents/api-quality-analyzer-conventions.md +148 -0
  34. package/src/core/agents/api-quality-analyzer-docs.md +176 -0
  35. package/src/core/agents/api-quality-analyzer-errors.md +183 -0
  36. package/src/core/agents/api-quality-analyzer-pagination.md +171 -0
  37. package/src/core/agents/api-quality-analyzer-versioning.md +143 -0
  38. package/src/core/agents/api-quality-consensus.md +214 -0
  39. package/src/core/agents/arch-analyzer-circular.md +148 -0
  40. package/src/core/agents/arch-analyzer-complexity.md +171 -0
  41. package/src/core/agents/arch-analyzer-coupling.md +146 -0
  42. package/src/core/agents/arch-analyzer-layering.md +151 -0
  43. package/src/core/agents/arch-analyzer-patterns.md +162 -0
  44. package/src/core/agents/arch-consensus.md +227 -0
  45. package/src/core/commands/adr.md +1 -0
  46. package/src/core/commands/ads/generate.md +238 -0
  47. package/src/core/commands/ads/health.md +327 -0
  48. package/src/core/commands/ads/test-plan.md +317 -0
  49. package/src/core/commands/ads/track.md +288 -0
  50. package/src/core/commands/ads.md +28 -16
  51. package/src/core/commands/assign.md +1 -0
  52. package/src/core/commands/audit.md +43 -6
  53. package/src/core/commands/babysit.md +90 -6
  54. package/src/core/commands/baseline.md +1 -0
  55. package/src/core/commands/blockers.md +1 -0
  56. package/src/core/commands/board.md +1 -0
  57. package/src/core/commands/changelog.md +1 -0
  58. package/src/core/commands/choose.md +1 -0
  59. package/src/core/commands/ci.md +1 -0
  60. package/src/core/commands/code/accessibility.md +347 -0
  61. package/src/core/commands/code/api.md +297 -0
  62. package/src/core/commands/code/architecture.md +297 -0
  63. package/src/core/commands/code/completeness.md +43 -6
  64. package/src/core/commands/code/legal.md +43 -6
  65. package/src/core/commands/code/logic.md +43 -6
  66. package/src/core/commands/code/performance.md +43 -6
  67. package/src/core/commands/code/security.md +43 -6
  68. package/src/core/commands/code/test.md +43 -6
  69. package/src/core/commands/configure.md +1 -0
  70. package/src/core/commands/council.md +1 -0
  71. package/src/core/commands/deploy.md +1 -0
  72. package/src/core/commands/diagnose.md +1 -0
  73. package/src/core/commands/docs.md +1 -0
  74. package/src/core/commands/epic/edit.md +213 -0
  75. package/src/core/commands/epic.md +1 -0
  76. package/src/core/commands/export.md +238 -0
  77. package/src/core/commands/help.md +16 -1
  78. package/src/core/commands/ideate/discover.md +7 -3
  79. package/src/core/commands/ideate/features.md +65 -4
  80. package/src/core/commands/ideate/new.md +158 -124
  81. package/src/core/commands/impact.md +1 -0
  82. package/src/core/commands/learn/explain.md +118 -0
  83. package/src/core/commands/learn/glossary.md +135 -0
  84. package/src/core/commands/learn/patterns.md +138 -0
  85. package/src/core/commands/learn/tour.md +126 -0
  86. package/src/core/commands/migrate/codemods.md +151 -0
  87. package/src/core/commands/migrate/plan.md +131 -0
  88. package/src/core/commands/migrate/scan.md +114 -0
  89. package/src/core/commands/migrate/validate.md +119 -0
  90. package/src/core/commands/multi-expert.md +1 -0
  91. package/src/core/commands/pr.md +1 -0
  92. package/src/core/commands/review.md +1 -0
  93. package/src/core/commands/sprint.md +1 -0
  94. package/src/core/commands/status/undo.md +191 -0
  95. package/src/core/commands/status.md +1 -0
  96. package/src/core/commands/story/edit.md +204 -0
  97. package/src/core/commands/story/view.md +29 -7
  98. package/src/core/commands/story-validate.md +1 -0
  99. package/src/core/commands/story.md +1 -0
  100. package/src/core/commands/tdd.md +1 -0
  101. package/src/core/commands/team/start.md +10 -6
  102. package/src/core/commands/tests.md +1 -0
  103. package/src/core/commands/verify.md +27 -1
  104. package/src/core/commands/workflow.md +2 -0
  105. package/src/core/teams/backend.json +41 -0
  106. package/src/core/teams/frontend.json +41 -0
  107. package/src/core/teams/qa.json +41 -0
  108. package/src/core/teams/solo.json +35 -0
  109. package/src/core/templates/agileflow-metadata.json +5 -0
  110. package/tools/cli/commands/setup.js +85 -3
  111. package/tools/cli/commands/update.js +42 -0
  112. package/tools/cli/installers/ide/claude-code.js +68 -0
@@ -0,0 +1,241 @@
1
+ # Tool Shed Registry - MCP Tool Definitions
2
+ #
3
+ # Central registry of MCP tools and their capabilities.
4
+ # Used by tool-shed.js to recommend relevant tools per task.
5
+ #
6
+ # Format:
7
+ # - name: Tool name as exposed by MCP server
8
+ # server: MCP server name
9
+ # category: Grouping category
10
+ # description: What the tool does
11
+ # keywords: Search terms for matching tasks to tools
12
+ # cost: token cost estimate (low/medium/high)
13
+ # requires_auth: Whether the tool needs credentials
14
+
15
+ # =============================================================================
16
+ # GitHub Tools (via @anthropic/mcp-server-github or similar)
17
+ # =============================================================================
18
+ github:
19
+ - name: create_issue
20
+ description: Create a new GitHub issue
21
+ keywords: [issue, bug, feature request, ticket, report]
22
+ cost: low
23
+ requires_auth: true
24
+
25
+ - name: list_issues
26
+ description: List and search GitHub issues
27
+ keywords: [issues, bugs, search, filter, backlog]
28
+ cost: low
29
+ requires_auth: true
30
+
31
+ - name: create_pull_request
32
+ description: Create a pull request
33
+ keywords: [pr, pull request, merge, review, code review]
34
+ cost: low
35
+ requires_auth: true
36
+
37
+ - name: get_pull_request
38
+ description: Get pull request details and diff
39
+ keywords: [pr, pull request, diff, changes, review]
40
+ cost: medium
41
+ requires_auth: true
42
+
43
+ - name: list_pull_requests
44
+ description: List pull requests with filters
45
+ keywords: [prs, pull requests, open, merged, review]
46
+ cost: low
47
+ requires_auth: true
48
+
49
+ - name: create_branch
50
+ description: Create a new git branch
51
+ keywords: [branch, feature branch, git]
52
+ cost: low
53
+ requires_auth: true
54
+
55
+ - name: search_code
56
+ description: Search code across repositories
57
+ keywords: [search, find, code, grep, pattern]
58
+ cost: medium
59
+ requires_auth: true
60
+
61
+ - name: get_file_contents
62
+ description: Get file contents from a repository
63
+ keywords: [read, file, contents, source]
64
+ cost: low
65
+ requires_auth: true
66
+
67
+ # =============================================================================
68
+ # Filesystem Tools (via @anthropic/mcp-server-filesystem)
69
+ # =============================================================================
70
+ filesystem:
71
+ - name: read_file
72
+ description: Read contents of a file
73
+ keywords: [read, file, contents, view]
74
+ cost: low
75
+ requires_auth: false
76
+
77
+ - name: write_file
78
+ description: Write contents to a file
79
+ keywords: [write, file, create, save]
80
+ cost: low
81
+ requires_auth: false
82
+
83
+ - name: list_directory
84
+ description: List directory contents
85
+ keywords: [list, directory, files, ls, find]
86
+ cost: low
87
+ requires_auth: false
88
+
89
+ - name: search_files
90
+ description: Search for files by name pattern
91
+ keywords: [search, find, glob, pattern, files]
92
+ cost: low
93
+ requires_auth: false
94
+
95
+ # =============================================================================
96
+ # Database Tools (via various MCP servers)
97
+ # =============================================================================
98
+ database:
99
+ - name: query
100
+ description: Execute a SQL query
101
+ keywords: [sql, query, select, database, table, join]
102
+ cost: medium
103
+ requires_auth: true
104
+
105
+ - name: list_tables
106
+ description: List database tables
107
+ keywords: [tables, schema, database, structure]
108
+ cost: low
109
+ requires_auth: true
110
+
111
+ - name: describe_table
112
+ description: Get table schema and columns
113
+ keywords: [schema, columns, table, types, structure]
114
+ cost: low
115
+ requires_auth: true
116
+
117
+ # =============================================================================
118
+ # Browser / Web Tools
119
+ # =============================================================================
120
+ browser:
121
+ - name: navigate
122
+ description: Navigate browser to a URL
123
+ keywords: [browse, navigate, url, page, website]
124
+ cost: medium
125
+ requires_auth: false
126
+
127
+ - name: screenshot
128
+ description: Take a screenshot of the current page
129
+ keywords: [screenshot, capture, visual, image, qa]
130
+ cost: medium
131
+ requires_auth: false
132
+
133
+ - name: click
134
+ description: Click an element on the page
135
+ keywords: [click, interact, button, link]
136
+ cost: low
137
+ requires_auth: false
138
+
139
+ - name: fill
140
+ description: Fill in a form field
141
+ keywords: [fill, input, form, type, text]
142
+ cost: low
143
+ requires_auth: false
144
+
145
+ # =============================================================================
146
+ # Slack / Communication Tools
147
+ # =============================================================================
148
+ slack:
149
+ - name: send_message
150
+ description: Send a message to a Slack channel
151
+ keywords: [slack, message, notify, channel, team]
152
+ cost: low
153
+ requires_auth: true
154
+
155
+ - name: search_messages
156
+ description: Search Slack message history
157
+ keywords: [slack, search, messages, history, find]
158
+ cost: medium
159
+ requires_auth: true
160
+
161
+ # =============================================================================
162
+ # Notion / Documentation Tools
163
+ # =============================================================================
164
+ notion:
165
+ - name: search
166
+ description: Search Notion pages and databases
167
+ keywords: [notion, search, wiki, docs, knowledge]
168
+ cost: medium
169
+ requires_auth: true
170
+
171
+ - name: create_page
172
+ description: Create a new Notion page
173
+ keywords: [notion, create, page, document, wiki]
174
+ cost: low
175
+ requires_auth: true
176
+
177
+ - name: update_page
178
+ description: Update an existing Notion page
179
+ keywords: [notion, update, edit, page, wiki]
180
+ cost: low
181
+ requires_auth: true
182
+
183
+ # =============================================================================
184
+ # Memory / Knowledge Tools
185
+ # =============================================================================
186
+ memory:
187
+ - name: store
188
+ description: Store a key-value pair in persistent memory
189
+ keywords: [remember, store, save, memory, persist]
190
+ cost: low
191
+ requires_auth: false
192
+
193
+ - name: retrieve
194
+ description: Retrieve a value from persistent memory
195
+ keywords: [recall, retrieve, get, memory, remember]
196
+ cost: low
197
+ requires_auth: false
198
+
199
+ - name: search_memory
200
+ description: Search across stored memories
201
+ keywords: [search, memory, find, recall]
202
+ cost: low
203
+ requires_auth: false
204
+
205
+ # =============================================================================
206
+ # Sentry / Error Tracking Tools
207
+ # =============================================================================
208
+ sentry:
209
+ - name: list_issues
210
+ description: List Sentry error issues
211
+ keywords: [errors, bugs, sentry, tracking, crashes, exceptions]
212
+ cost: medium
213
+ requires_auth: true
214
+
215
+ - name: get_issue
216
+ description: Get details of a specific Sentry issue
217
+ keywords: [error, bug, sentry, stack trace, crash]
218
+ cost: medium
219
+ requires_auth: true
220
+
221
+ # =============================================================================
222
+ # Linear / Project Management Tools
223
+ # =============================================================================
224
+ linear:
225
+ - name: create_issue
226
+ description: Create a Linear issue
227
+ keywords: [ticket, issue, task, linear, project]
228
+ cost: low
229
+ requires_auth: true
230
+
231
+ - name: list_issues
232
+ description: List Linear issues with filters
233
+ keywords: [tickets, issues, tasks, linear, backlog, sprint]
234
+ cost: low
235
+ requires_auth: true
236
+
237
+ - name: update_issue
238
+ description: Update a Linear issue status or details
239
+ keywords: [update, ticket, status, assign, linear]
240
+ cost: low
241
+ requires_auth: true
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * tool-shed.js - MCP Meta-Tool Registry
4
+ *
5
+ * Central registry for MCP tools. When agents need tools, this module
6
+ * queries the registry by task description and returns a filtered list
7
+ * of relevant tools, avoiding token explosion from sending all tools.
8
+ *
9
+ * Inspired by Stripe's "Tool Shed" pattern where a meta-tool selects
10
+ * relevant tools per task from a large registry.
11
+ *
12
+ * Usage (as module):
13
+ * const { getToolsForTask, getAvailableServers, getToolCount } = require('./tool-shed');
14
+ * const tools = getToolsForTask('create a pull request for auth changes', { projectRoot });
15
+ *
16
+ * Usage (standalone):
17
+ * node scripts/lib/tool-shed.js --task "search for bugs" --project-root /path/to/project
18
+ * node scripts/lib/tool-shed.js --list-servers --project-root /path/to/project
19
+ * node scripts/lib/tool-shed.js --stats
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ // Lazy-load js-yaml only when needed
28
+ let _yaml;
29
+ function getYaml() {
30
+ if (!_yaml) {
31
+ try {
32
+ _yaml = require('js-yaml');
33
+ } catch {
34
+ // Fallback: provide a minimal YAML parser for simple structures
35
+ _yaml = null;
36
+ }
37
+ }
38
+ return _yaml;
39
+ }
40
+
41
+ // =============================================================================
42
+ // Registry Loading
43
+ // =============================================================================
44
+
45
+ /**
46
+ * Load the built-in tool registry from YAML
47
+ * @returns {Object} Parsed registry object keyed by server name
48
+ */
49
+ function loadBuiltinRegistry() {
50
+ const registryPath = path.join(__dirname, 'tool-registry.yaml');
51
+ try {
52
+ const content = fs.readFileSync(registryPath, 'utf8');
53
+ const yaml = getYaml();
54
+ if (yaml) {
55
+ return yaml.load(content) || {};
56
+ }
57
+ // Minimal fallback if js-yaml not available
58
+ return {};
59
+ } catch {
60
+ return {};
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Detect which MCP servers are configured in the project
66
+ * @param {string} projectRoot - Project root directory
67
+ * @returns {string[]} List of configured server names
68
+ */
69
+ function detectConfiguredServers(projectRoot) {
70
+ const servers = [];
71
+
72
+ // Check .mcp.json (Claude Code MCP config)
73
+ const mcpJsonPath = path.join(projectRoot, '.mcp.json');
74
+ if (fs.existsSync(mcpJsonPath)) {
75
+ try {
76
+ const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
77
+ if (mcpConfig.mcpServers) {
78
+ servers.push(...Object.keys(mcpConfig.mcpServers));
79
+ }
80
+ } catch {
81
+ // Ignore parse errors
82
+ }
83
+ }
84
+
85
+ // Check .claude/mcp.json (alternative location)
86
+ const claudeMcpPath = path.join(projectRoot, '.claude', 'mcp.json');
87
+ if (fs.existsSync(claudeMcpPath)) {
88
+ try {
89
+ const mcpConfig = JSON.parse(fs.readFileSync(claudeMcpPath, 'utf8'));
90
+ if (mcpConfig.mcpServers) {
91
+ servers.push(...Object.keys(mcpConfig.mcpServers));
92
+ }
93
+ } catch {
94
+ // Ignore parse errors
95
+ }
96
+ }
97
+
98
+ // Check agileflow-metadata.json for enabled MCP features
99
+ const metadataPath = path.join(projectRoot, 'docs', '00-meta', 'agileflow-metadata.json');
100
+ if (fs.existsSync(metadataPath)) {
101
+ try {
102
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
103
+ const mcp =
104
+ metadata.agileflow && metadata.agileflow.features && metadata.agileflow.features.mcp;
105
+ if (mcp) {
106
+ for (const [server, enabled] of Object.entries(mcp)) {
107
+ if (enabled && !servers.includes(server)) {
108
+ servers.push(server);
109
+ }
110
+ }
111
+ }
112
+ } catch {
113
+ // Ignore parse errors
114
+ }
115
+ }
116
+
117
+ return [...new Set(servers)];
118
+ }
119
+
120
+ // =============================================================================
121
+ // Tool Matching
122
+ // =============================================================================
123
+
124
+ /**
125
+ * Tokenize a task description into searchable terms
126
+ * @param {string} text - Text to tokenize
127
+ * @returns {string[]} Lowercased tokens
128
+ */
129
+ function tokenize(text) {
130
+ if (!text) return [];
131
+ return text
132
+ .toLowerCase()
133
+ .replace(/[^a-z0-9\s-]/g, ' ')
134
+ .split(/\s+/)
135
+ .filter(t => t.length > 1);
136
+ }
137
+
138
+ /**
139
+ * Score a tool's relevance to a task description
140
+ * @param {Object} tool - Tool definition from registry
141
+ * @param {string[]} taskTokens - Tokenized task description
142
+ * @returns {number} Relevance score (0-100)
143
+ */
144
+ function scoreToolRelevance(tool, taskTokens) {
145
+ if (!tool.keywords || !Array.isArray(tool.keywords)) return 0;
146
+ if (taskTokens.length === 0) return 0;
147
+
148
+ let matchCount = 0;
149
+ const toolKeywords = tool.keywords.map(k => k.toLowerCase());
150
+
151
+ for (const token of taskTokens) {
152
+ for (const keyword of toolKeywords) {
153
+ // Exact match or substring match
154
+ if (keyword === token || keyword.includes(token) || token.includes(keyword)) {
155
+ matchCount++;
156
+ break;
157
+ }
158
+ }
159
+ }
160
+
161
+ if (matchCount === 0) return 0;
162
+
163
+ // Score based on how many task tokens matched tool keywords
164
+ const coverage = matchCount / taskTokens.length;
165
+ // Bonus for tools where many keywords matched
166
+ const density = matchCount / toolKeywords.length;
167
+
168
+ return Math.round(coverage * 60 + density * 40);
169
+ }
170
+
171
+ // =============================================================================
172
+ // Public API
173
+ // =============================================================================
174
+
175
+ /**
176
+ * Get tools relevant to a task description.
177
+ * Filters by configured MCP servers and scores by keyword relevance.
178
+ *
179
+ * @param {string} taskDescription - Natural language task description
180
+ * @param {Object} [options] - Options
181
+ * @param {string} [options.projectRoot] - Project root directory
182
+ * @param {number} [options.minScore] - Minimum relevance score (default: 15)
183
+ * @param {number} [options.maxResults] - Maximum tools to return (default: 10)
184
+ * @param {boolean} [options.includeUnconfigured] - Include tools from unconfigured servers (default: false)
185
+ * @returns {Object} Results with matched tools and metadata
186
+ */
187
+ function getToolsForTask(taskDescription, options = {}) {
188
+ const {
189
+ projectRoot = process.cwd(),
190
+ minScore = 15,
191
+ maxResults = 10,
192
+ includeUnconfigured = false,
193
+ } = options;
194
+
195
+ const registry = loadBuiltinRegistry();
196
+ const configuredServers = detectConfiguredServers(projectRoot);
197
+ const taskTokens = tokenize(taskDescription);
198
+
199
+ const scoredTools = [];
200
+
201
+ for (const [serverName, tools] of Object.entries(registry)) {
202
+ if (!Array.isArray(tools)) continue;
203
+
204
+ const isConfigured = configuredServers.some(
205
+ s =>
206
+ s.toLowerCase().includes(serverName.toLowerCase()) ||
207
+ serverName.toLowerCase().includes(s.toLowerCase())
208
+ );
209
+
210
+ if (!isConfigured && !includeUnconfigured) continue;
211
+
212
+ for (const tool of tools) {
213
+ const score = scoreToolRelevance(tool, taskTokens);
214
+ if (score >= minScore) {
215
+ scoredTools.push({
216
+ ...tool,
217
+ server: serverName,
218
+ score,
219
+ configured: isConfigured,
220
+ });
221
+ }
222
+ }
223
+ }
224
+
225
+ // Sort by score descending, then by configured status
226
+ scoredTools.sort((a, b) => {
227
+ if (a.configured !== b.configured) return a.configured ? -1 : 1;
228
+ return b.score - a.score;
229
+ });
230
+
231
+ const results = scoredTools.slice(0, maxResults);
232
+
233
+ return {
234
+ task: taskDescription,
235
+ configured_servers: configuredServers,
236
+ total_matched: scoredTools.length,
237
+ tools: results,
238
+ suggestions:
239
+ !includeUnconfigured && scoredTools.length === 0
240
+ ? getUnconfiguredSuggestions(taskDescription, registry, configuredServers)
241
+ : [],
242
+ };
243
+ }
244
+
245
+ /**
246
+ * Get suggestions for unconfigured servers that could help with a task
247
+ * @param {string} taskDescription - Task description
248
+ * @param {Object} registry - Full registry
249
+ * @param {string[]} configuredServers - Already configured servers
250
+ * @returns {Object[]} Suggested servers to configure
251
+ */
252
+ function getUnconfiguredSuggestions(taskDescription, registry, configuredServers) {
253
+ const taskTokens = tokenize(taskDescription);
254
+ const suggestions = [];
255
+
256
+ for (const [serverName, tools] of Object.entries(registry)) {
257
+ if (!Array.isArray(tools)) continue;
258
+
259
+ const isConfigured = configuredServers.some(s =>
260
+ s.toLowerCase().includes(serverName.toLowerCase())
261
+ );
262
+ if (isConfigured) continue;
263
+
264
+ let bestScore = 0;
265
+ let bestTool = null;
266
+ for (const tool of tools) {
267
+ const score = scoreToolRelevance(tool, taskTokens);
268
+ if (score > bestScore) {
269
+ bestScore = score;
270
+ bestTool = tool;
271
+ }
272
+ }
273
+
274
+ if (bestScore >= 20 && bestTool) {
275
+ suggestions.push({
276
+ server: serverName,
277
+ best_match: bestTool.name,
278
+ score: bestScore,
279
+ description: `Configure ${serverName} MCP server to use ${bestTool.name} (${bestTool.description})`,
280
+ });
281
+ }
282
+ }
283
+
284
+ return suggestions.sort((a, b) => b.score - a.score).slice(0, 3);
285
+ }
286
+
287
+ /**
288
+ * Get list of available MCP servers (both configured and in registry)
289
+ * @param {string} [projectRoot] - Project root directory
290
+ * @returns {Object} Server listing
291
+ */
292
+ function getAvailableServers(projectRoot = process.cwd()) {
293
+ const registry = loadBuiltinRegistry();
294
+ const configuredServers = detectConfiguredServers(projectRoot);
295
+
296
+ const servers = {};
297
+ for (const [name, tools] of Object.entries(registry)) {
298
+ if (!Array.isArray(tools)) continue;
299
+ servers[name] = {
300
+ tool_count: tools.length,
301
+ configured: configuredServers.some(
302
+ s =>
303
+ s.toLowerCase().includes(name.toLowerCase()) ||
304
+ name.toLowerCase().includes(s.toLowerCase())
305
+ ),
306
+ tools: tools.map(t => t.name),
307
+ };
308
+ }
309
+
310
+ return {
311
+ configured: configuredServers,
312
+ registry: servers,
313
+ total_servers: Object.keys(servers).length,
314
+ total_tools: Object.values(servers).reduce((sum, s) => sum + s.tool_count, 0),
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Get tool count statistics
320
+ * @returns {Object} Statistics
321
+ */
322
+ function getToolCount() {
323
+ const registry = loadBuiltinRegistry();
324
+ let total = 0;
325
+ const byServer = {};
326
+
327
+ for (const [name, tools] of Object.entries(registry)) {
328
+ if (!Array.isArray(tools)) continue;
329
+ byServer[name] = tools.length;
330
+ total += tools.length;
331
+ }
332
+
333
+ return { total, byServer };
334
+ }
335
+
336
+ /**
337
+ * Format tool results for display
338
+ * @param {Object} result - Result from getToolsForTask
339
+ * @returns {string} Formatted display text
340
+ */
341
+ function formatToolResults(result) {
342
+ const lines = [];
343
+
344
+ lines.push(`## Tool Shed Results for: "${result.task}"`);
345
+ lines.push('');
346
+
347
+ if (result.configured_servers.length > 0) {
348
+ lines.push(`**Configured MCP servers**: ${result.configured_servers.join(', ')}`);
349
+ } else {
350
+ lines.push('**No MCP servers configured.** Tools are recommended based on registry only.');
351
+ }
352
+ lines.push('');
353
+
354
+ if (result.tools.length === 0) {
355
+ lines.push('No matching tools found.');
356
+ lines.push('');
357
+ } else {
358
+ lines.push(
359
+ `### Matched Tools (${result.total_matched} total, showing top ${result.tools.length})`
360
+ );
361
+ lines.push('');
362
+
363
+ for (const tool of result.tools) {
364
+ const status = tool.configured ? '✅' : '⚠️';
365
+ lines.push(`- ${status} **${tool.server}/${tool.name}** (score: ${tool.score})`);
366
+ lines.push(` ${tool.description}`);
367
+ if (!tool.configured) {
368
+ lines.push(` _Server not configured - add ${tool.server} to .mcp.json_`);
369
+ }
370
+ }
371
+ lines.push('');
372
+ }
373
+
374
+ if (result.suggestions.length > 0) {
375
+ lines.push('### Suggested Servers to Configure');
376
+ lines.push('');
377
+ for (const suggestion of result.suggestions) {
378
+ lines.push(`- **${suggestion.server}** - ${suggestion.description}`);
379
+ }
380
+ lines.push('');
381
+ }
382
+
383
+ return lines.join('\n');
384
+ }
385
+
386
+ // =============================================================================
387
+ // CLI
388
+ // =============================================================================
389
+
390
+ if (require.main === module) {
391
+ const args = process.argv.slice(2);
392
+ const projectRoot = getArg(args, '--project-root') || process.cwd();
393
+
394
+ if (args.includes('--list-servers')) {
395
+ const servers = getAvailableServers(projectRoot);
396
+ console.log(JSON.stringify(servers, null, 2));
397
+ } else if (args.includes('--stats')) {
398
+ const stats = getToolCount();
399
+ console.log(JSON.stringify(stats, null, 2));
400
+ } else {
401
+ const task = getArg(args, '--task');
402
+ if (!task) {
403
+ console.error('Usage: node tool-shed.js --task "description" [--project-root /path]');
404
+ console.error(' node tool-shed.js --list-servers [--project-root /path]');
405
+ console.error(' node tool-shed.js --stats');
406
+ process.exit(1);
407
+ }
408
+
409
+ const result = getToolsForTask(task, {
410
+ projectRoot,
411
+ includeUnconfigured: args.includes('--include-unconfigured'),
412
+ });
413
+
414
+ if (args.includes('--json')) {
415
+ console.log(JSON.stringify(result, null, 2));
416
+ } else {
417
+ console.log(formatToolResults(result));
418
+ }
419
+ }
420
+ }
421
+
422
+ function getArg(args, flag) {
423
+ const idx = args.indexOf(flag);
424
+ if (idx === -1 || idx + 1 >= args.length) return null;
425
+ return args[idx + 1];
426
+ }
427
+
428
+ // =============================================================================
429
+ // Exports
430
+ // =============================================================================
431
+
432
+ module.exports = {
433
+ loadBuiltinRegistry,
434
+ detectConfiguredServers,
435
+ tokenize,
436
+ scoreToolRelevance,
437
+ getToolsForTask,
438
+ getAvailableServers,
439
+ getToolCount,
440
+ formatToolResults,
441
+ };