@crowley/rag-mcp 1.0.5 → 1.1.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 (51) hide show
  1. package/dist/annotations.d.ts +16 -0
  2. package/dist/annotations.js +158 -0
  3. package/dist/context-enrichment.js +7 -0
  4. package/dist/formatters.d.ts +2 -0
  5. package/dist/formatters.js +12 -0
  6. package/dist/index.js +99 -47
  7. package/dist/schemas.d.ts +97 -0
  8. package/dist/schemas.js +128 -0
  9. package/dist/tool-middleware.d.ts +40 -0
  10. package/dist/tool-middleware.js +216 -0
  11. package/dist/tool-registry.js +2 -1
  12. package/dist/tools/advanced.d.ts +2 -2
  13. package/dist/tools/advanced.js +200 -275
  14. package/dist/tools/agents.d.ts +2 -2
  15. package/dist/tools/agents.js +59 -78
  16. package/dist/tools/analytics.d.ts +2 -2
  17. package/dist/tools/analytics.js +170 -210
  18. package/dist/tools/architecture.d.ts +2 -2
  19. package/dist/tools/architecture.js +506 -669
  20. package/dist/tools/ask.d.ts +2 -2
  21. package/dist/tools/ask.js +164 -219
  22. package/dist/tools/cache.d.ts +2 -2
  23. package/dist/tools/cache.js +63 -82
  24. package/dist/tools/clustering.d.ts +2 -2
  25. package/dist/tools/clustering.js +154 -215
  26. package/dist/tools/confluence.d.ts +2 -2
  27. package/dist/tools/confluence.js +80 -116
  28. package/dist/tools/database.d.ts +2 -2
  29. package/dist/tools/database.js +303 -380
  30. package/dist/tools/feedback.d.ts +2 -2
  31. package/dist/tools/feedback.js +143 -184
  32. package/dist/tools/guidelines.d.ts +2 -2
  33. package/dist/tools/guidelines.js +123 -135
  34. package/dist/tools/indexing.d.ts +2 -2
  35. package/dist/tools/indexing.js +100 -108
  36. package/dist/tools/memory.d.ts +2 -2
  37. package/dist/tools/memory.js +299 -485
  38. package/dist/tools/pm.d.ts +2 -2
  39. package/dist/tools/pm.js +367 -615
  40. package/dist/tools/quality.d.ts +8 -0
  41. package/dist/tools/quality.js +60 -0
  42. package/dist/tools/review.d.ts +2 -2
  43. package/dist/tools/review.js +142 -189
  44. package/dist/tools/search.d.ts +2 -2
  45. package/dist/tools/search.js +230 -305
  46. package/dist/tools/session.d.ts +2 -2
  47. package/dist/tools/session.js +288 -345
  48. package/dist/tools/suggestions.d.ts +2 -2
  49. package/dist/tools/suggestions.js +517 -512
  50. package/dist/types.d.ts +19 -2
  51. package/package.json +4 -2
package/dist/tools/pm.js CHANGED
@@ -2,617 +2,369 @@
2
2
  * PM tools module - Product Management, requirements analysis, feature estimation,
3
3
  * spec generation, and project status tools.
4
4
  */
5
- import { truncate, pct } from "../formatters.js";
5
+ import { truncate, pct, paginationFooter } from "../formatters.js";
6
+ import { z } from "zod";
7
+ import { TOOL_ANNOTATIONS } from "../annotations.js";
6
8
  /**
7
9
  * Create the PM tools module with project-specific descriptions.
8
10
  */
9
11
  export function createPmTools(projectName) {
10
- const tools = [
12
+ return [
11
13
  {
12
14
  name: "search_requirements",
13
15
  description: `Search technical requirements and product documentation for ${projectName}. Finds relevant requirements, user stories, and specifications from Confluence.`,
14
- inputSchema: {
15
- type: "object",
16
- properties: {
17
- query: {
18
- type: "string",
19
- description: "Search query for requirements (e.g., 'video inspection flow', 'payment integration')",
20
- },
21
- limit: {
22
- type: "number",
23
- description: "Max results (default: 5)",
24
- default: 5,
25
- },
26
- },
27
- required: ["query"],
16
+ schema: z.object({
17
+ query: z.string().describe("Search query for requirements (e.g., 'video inspection flow', 'payment integration')"),
18
+ limit: z.number().optional().describe("Max results (default: 5)"),
19
+ }),
20
+ annotations: TOOL_ANNOTATIONS["search_requirements"],
21
+ handler: async (args, ctx) => {
22
+ const { query, limit = 5 } = args;
23
+ const response = await ctx.api.post("/api/search", {
24
+ collection: `${ctx.collectionPrefix}confluence`,
25
+ query,
26
+ limit,
27
+ });
28
+ const results = response.data.results;
29
+ if (!results || results.length === 0) {
30
+ return "No requirements found. Make sure Confluence documentation is indexed.";
31
+ }
32
+ return (`**Requirements Search: "${query}"**\n\n` +
33
+ results
34
+ .map((r, i) => `### ${i + 1}. ${r.title || "Requirement"}\n` +
35
+ `**Relevance:** ${pct(r.score)}\n` +
36
+ `**Source:** ${r.url || "Confluence"}\n\n` +
37
+ truncate(r.content, 800))
38
+ .join("\n\n---\n\n"));
28
39
  },
29
40
  },
30
41
  {
31
42
  name: "analyze_requirements",
32
43
  description: `Analyze technical requirements and compare with existing implementation in ${projectName}. Identifies gaps, missing features, and implementation status.`,
33
- inputSchema: {
34
- type: "object",
35
- properties: {
36
- feature: {
37
- type: "string",
38
- description: "Feature or requirement to analyze (e.g., 'video inspection', 'notifications')",
39
- },
40
- detailed: {
41
- type: "boolean",
42
- description: "Include detailed code references (default: false)",
43
- default: false,
44
- },
45
- },
46
- required: ["feature"],
44
+ schema: z.object({
45
+ feature: z.string().describe("Feature or requirement to analyze (e.g., 'video inspection', 'notifications')"),
46
+ detailed: z.boolean().optional().describe("Include detailed code references (default: false)"),
47
+ }),
48
+ annotations: TOOL_ANNOTATIONS["analyze_requirements"],
49
+ handler: async (args, ctx) => {
50
+ const { feature, detailed = false } = args;
51
+ // Search requirements in Confluence
52
+ const reqResponse = await ctx.api.post("/api/search", {
53
+ collection: `${ctx.collectionPrefix}confluence`,
54
+ query: feature,
55
+ limit: 5,
56
+ });
57
+ // Search implementation in codebase
58
+ const codeResponse = await ctx.api.post("/api/search", {
59
+ collection: `${ctx.collectionPrefix}codebase`,
60
+ query: feature,
61
+ limit: detailed ? 10 : 5,
62
+ });
63
+ const requirements = reqResponse.data.results || [];
64
+ const implementations = codeResponse.data.results || [];
65
+ let result = `# Requirements Analysis: ${feature}\n\n`;
66
+ result += `## Documented Requirements (${requirements.length} found)\n\n`;
67
+ if (requirements.length === 0) {
68
+ result += "_No documented requirements found in Confluence._\n\n";
69
+ }
70
+ else {
71
+ requirements.forEach((r, i) => {
72
+ result += `### ${i + 1}. ${r.title || "Requirement"}\n`;
73
+ result += truncate(r.content, 400) + "\n\n";
74
+ });
75
+ }
76
+ result += `## Implementation Status (${implementations.length} files found)\n\n`;
77
+ if (implementations.length === 0) {
78
+ result += "_No implementation found in codebase._\n\n";
79
+ }
80
+ else {
81
+ implementations.forEach((r) => {
82
+ result += `- **${r.file}** (${pct(r.score)} match)\n`;
83
+ if (detailed) {
84
+ result +=
85
+ "```" +
86
+ (r.language || "") +
87
+ "\n" +
88
+ truncate(r.content, 300) +
89
+ "\n```\n";
90
+ }
91
+ });
92
+ }
93
+ result += `\n## Summary\n`;
94
+ result += `- Requirements documented: ${requirements.length > 0 ? "Yes" : "No"}\n`;
95
+ result += `- Implementation found: ${implementations.length > 0 ? "Yes" : "No"}\n`;
96
+ if (requirements.length > 0 && implementations.length === 0) {
97
+ result += `\n**Gap detected:** Requirements exist but no implementation found.`;
98
+ }
99
+ else if (requirements.length === 0 && implementations.length > 0) {
100
+ result += `\n**Warning:** Implementation exists but no documented requirements.`;
101
+ }
102
+ return result;
47
103
  },
48
104
  },
49
105
  {
50
106
  name: "estimate_feature",
51
107
  description: `Estimate development effort for a feature based on requirements and codebase analysis. Returns complexity assessment, affected files, and risk factors.`,
52
- inputSchema: {
53
- type: "object",
54
- properties: {
55
- feature: {
56
- type: "string",
57
- description: "Feature description to estimate",
58
- },
59
- includeSubtasks: {
60
- type: "boolean",
61
- description: "Break down into subtasks (default: true)",
62
- default: true,
63
- },
64
- },
65
- required: ["feature"],
108
+ schema: z.object({
109
+ feature: z.string().describe("Feature description to estimate"),
110
+ includeSubtasks: z.boolean().optional().describe("Break down into subtasks (default: true)"),
111
+ }),
112
+ annotations: TOOL_ANNOTATIONS["estimate_feature"],
113
+ handler: async (args, ctx) => {
114
+ const { feature, includeSubtasks = true } = args;
115
+ const response = await ctx.api.post("/api/estimate-feature", {
116
+ projectName: ctx.projectName,
117
+ feature,
118
+ includeSubtasks,
119
+ });
120
+ const d = response.data;
121
+ // Format structured API response as markdown
122
+ let result = `# Feature Estimation: ${feature}\n\n`;
123
+ result += `## Overview\n`;
124
+ result += `| Metric | Value |\n`;
125
+ result += `|--------|-------|\n`;
126
+ result += `| Complexity | **${d.complexity}** (score: ${d.complexityScore}/100) |\n`;
127
+ result += `| Risk Level | **${d.riskLevel}** (score: ${d.riskScore}/100) |\n`;
128
+ result += `| Affected Files | ${d.affectedFiles.length} |\n`;
129
+ result += `| Test Files | ${d.testFiles.length} (ratio: ${(d.testRatio * 100).toFixed(0)}%) |\n`;
130
+ result += `| Integration Points | ${d.integrations.length} |\n`;
131
+ result += `| Avg Cyclomatic Complexity | ${d.avgCyclomaticComplexity} |\n`;
132
+ result += `| Requirements Documented | ${d.hasRequirements ? "Yes" : "No"} |\n\n`;
133
+ if (d.integrations.length > 0) {
134
+ result += `## Integration Points\n`;
135
+ d.integrations.slice(0, 10).forEach((i) => {
136
+ result += `- ${i}\n`;
137
+ });
138
+ result += "\n";
139
+ }
140
+ if (d.affectedFiles.length > 0) {
141
+ result += `## Affected Files\n`;
142
+ d.affectedFiles.slice(0, 15).forEach((f) => {
143
+ const hasTest = d.testFiles.some((t) => t.includes(f.replace(/\.(ts|js|py|go)$/, "")));
144
+ result += `- ${f} ${hasTest ? "(tested)" : "(no tests)"}\n`;
145
+ });
146
+ if (d.affectedFiles.length > 15) {
147
+ result += `- ... and ${d.affectedFiles.length - 15} more\n`;
148
+ }
149
+ result += "\n";
150
+ }
151
+ if (d.complexFunctions.length > 0) {
152
+ result += `## Complex Functions (may need refactoring)\n`;
153
+ d.complexFunctions.slice(0, 5).forEach((f) => {
154
+ result += `- ${f}\n`;
155
+ });
156
+ result += "\n";
157
+ }
158
+ result += `## Risk Factors\n`;
159
+ if (d.riskFactors.length > 0) {
160
+ d.riskFactors.forEach((r) => {
161
+ result += `- ${r}\n`;
162
+ });
163
+ }
164
+ else {
165
+ result += `- No significant risks identified\n`;
166
+ }
167
+ result += "\n";
168
+ if (d.subtasks) {
169
+ result += `## Suggested Subtasks\n`;
170
+ d.subtasks.forEach((t, i) => {
171
+ result += `${i + 1}. ${t}\n`;
172
+ });
173
+ }
174
+ return result;
66
175
  },
67
176
  },
68
177
  {
69
178
  name: "get_feature_status",
70
179
  description: `Get implementation status of a feature by comparing requirements with codebase. Shows what's implemented, in progress, and missing.`,
71
- inputSchema: {
72
- type: "object",
73
- properties: {
74
- feature: {
75
- type: "string",
76
- description: "Feature name to check status",
77
- },
78
- },
79
- required: ["feature"],
180
+ schema: z.object({
181
+ feature: z.string().describe("Feature name to check status"),
182
+ }),
183
+ annotations: TOOL_ANNOTATIONS["get_feature_status"],
184
+ handler: async (args, ctx) => {
185
+ const { feature } = args;
186
+ const reqResponse = await ctx.api.post("/api/search", {
187
+ collection: `${ctx.collectionPrefix}confluence`,
188
+ query: feature,
189
+ limit: 3,
190
+ });
191
+ const codeResponse = await ctx.api.post("/api/search", {
192
+ collection: `${ctx.collectionPrefix}codebase`,
193
+ query: feature,
194
+ limit: 5,
195
+ });
196
+ const requirements = reqResponse.data.results || [];
197
+ const implementations = codeResponse.data.results || [];
198
+ let status = "Unknown";
199
+ let statusEmoji = "?";
200
+ if (requirements.length > 0 && implementations.length > 0) {
201
+ status = "Implemented";
202
+ statusEmoji = "[DONE]";
203
+ }
204
+ else if (requirements.length > 0 && implementations.length === 0) {
205
+ status = "Planned (Not Implemented)";
206
+ statusEmoji = "[PLANNED]";
207
+ }
208
+ else if (requirements.length === 0 && implementations.length > 0) {
209
+ status = "Implemented (Undocumented)";
210
+ statusEmoji = "[WARN]";
211
+ }
212
+ else {
213
+ status = "Not Found";
214
+ statusEmoji = "[MISSING]";
215
+ }
216
+ let result = `# Feature Status: ${feature}\n\n`;
217
+ result += `## ${statusEmoji} Status: ${status}\n\n`;
218
+ if (requirements.length > 0) {
219
+ result += `### Requirements\n`;
220
+ requirements.forEach((r) => {
221
+ result += `- ${r.title || "Requirement"}: ${truncate(r.content, 150)}\n`;
222
+ });
223
+ result += "\n";
224
+ }
225
+ if (implementations.length > 0) {
226
+ result += `### Implementation\n`;
227
+ implementations.forEach((r) => {
228
+ result += `- ${r.file}\n`;
229
+ });
230
+ }
231
+ return result;
80
232
  },
81
233
  },
82
234
  {
83
235
  name: "list_requirements",
84
236
  description: `List all documented requirements/features for ${projectName} from Confluence. Groups by category or status.`,
85
- inputSchema: {
86
- type: "object",
87
- properties: {
88
- category: {
89
- type: "string",
90
- description: "Filter by category (optional)",
91
- },
92
- limit: {
93
- type: "number",
94
- description: "Max results (default: 20)",
95
- default: 20,
96
- },
97
- },
237
+ schema: z.object({
238
+ category: z.string().optional().describe("Filter by category (optional)"),
239
+ limit: z.number().optional().describe("Max results (default: 20)"),
240
+ offset: z.number().optional().describe("Pagination offset (default: 0)"),
241
+ }),
242
+ annotations: TOOL_ANNOTATIONS["list_requirements"],
243
+ handler: async (args, ctx) => {
244
+ const { category, limit = 20, offset = 0 } = args;
245
+ const query = category || "requirements features specifications";
246
+ const response = await ctx.api.post("/api/search", {
247
+ collection: `${ctx.collectionPrefix}confluence`,
248
+ query,
249
+ limit,
250
+ offset,
251
+ });
252
+ const results = response.data.results || [];
253
+ if (results.length === 0) {
254
+ return "No requirements found in Confluence. Make sure documentation is indexed.";
255
+ }
256
+ let result = `# ${ctx.projectName} Requirements\n\n`;
257
+ if (category) {
258
+ result += `**Category filter:** ${category}\n\n`;
259
+ }
260
+ result += `**Found:** ${results.length} items\n\n`;
261
+ results.forEach((r, i) => {
262
+ result += `${offset + i + 1}. **${r.title || "Untitled"}**\n`;
263
+ result += ` ${truncate(r.content.replace(/\n/g, " "), 150)}\n`;
264
+ if (r.url) {
265
+ result += ` [View in Confluence](${r.url})\n`;
266
+ }
267
+ result += "\n";
268
+ });
269
+ result += paginationFooter(results.length, limit, offset);
270
+ return result;
98
271
  },
99
272
  },
100
273
  {
101
274
  name: "ask_pm",
102
275
  description: `Ask product management questions about ${projectName}. Answers questions about requirements, features, priorities, and project status using both documentation and codebase.`,
103
- inputSchema: {
104
- type: "object",
105
- properties: {
106
- question: {
107
- type: "string",
108
- description: "PM question (e.g., 'What features are planned for video inspection?', 'What's the status of notifications?')",
109
- },
110
- },
111
- required: ["question"],
112
- },
113
- },
114
- {
115
- name: "generate_spec",
116
- description: `Generate technical specification from requirements. Creates a structured spec document based on Confluence requirements and existing codebase patterns.`,
117
- inputSchema: {
118
- type: "object",
119
- properties: {
120
- feature: {
121
- type: "string",
122
- description: "Feature to generate spec for",
123
- },
124
- format: {
125
- type: "string",
126
- enum: ["markdown", "jira", "brief"],
127
- description: "Output format (default: markdown)",
128
- default: "markdown",
129
- },
130
- },
131
- required: ["feature"],
132
- },
133
- },
134
- ];
135
- const handlers = {
136
- search_requirements: async (args, ctx) => {
137
- const { query, limit = 5 } = args;
138
- const response = await ctx.api.post("/api/search", {
139
- collection: `${ctx.collectionPrefix}confluence`,
140
- query,
141
- limit,
142
- });
143
- const results = response.data.results;
144
- if (!results || results.length === 0) {
145
- return "No requirements found. Make sure Confluence documentation is indexed.";
146
- }
147
- return (`**Requirements Search: "${query}"**\n\n` +
148
- results
149
- .map((r, i) => `### ${i + 1}. ${r.title || "Requirement"}\n` +
150
- `**Relevance:** ${pct(r.score)}\n` +
151
- `**Source:** ${r.url || "Confluence"}\n\n` +
152
- truncate(r.content, 800))
153
- .join("\n\n---\n\n"));
154
- },
155
- analyze_requirements: async (args, ctx) => {
156
- const { feature, detailed = false } = args;
157
- // Search requirements in Confluence
158
- const reqResponse = await ctx.api.post("/api/search", {
159
- collection: `${ctx.collectionPrefix}confluence`,
160
- query: feature,
161
- limit: 5,
162
- });
163
- // Search implementation in codebase
164
- const codeResponse = await ctx.api.post("/api/search", {
165
- collection: `${ctx.collectionPrefix}codebase`,
166
- query: feature,
167
- limit: detailed ? 10 : 5,
168
- });
169
- const requirements = reqResponse.data.results || [];
170
- const implementations = codeResponse.data.results || [];
171
- let result = `# Requirements Analysis: ${feature}\n\n`;
172
- result += `## Documented Requirements (${requirements.length} found)\n\n`;
173
- if (requirements.length === 0) {
174
- result += "_No documented requirements found in Confluence._\n\n";
175
- }
176
- else {
177
- requirements.forEach((r, i) => {
178
- result += `### ${i + 1}. ${r.title || "Requirement"}\n`;
179
- result += truncate(r.content, 400) + "\n\n";
180
- });
181
- }
182
- result += `## Implementation Status (${implementations.length} files found)\n\n`;
183
- if (implementations.length === 0) {
184
- result += "_No implementation found in codebase._\n\n";
185
- }
186
- else {
187
- implementations.forEach((r) => {
188
- result += `- **${r.file}** (${pct(r.score)} match)\n`;
189
- if (detailed) {
190
- result +=
191
- "```" +
192
- (r.language || "") +
193
- "\n" +
194
- truncate(r.content, 300) +
195
- "\n```\n";
196
- }
197
- });
198
- }
199
- result += `\n## Summary\n`;
200
- result += `- Requirements documented: ${requirements.length > 0 ? "Yes" : "No"}\n`;
201
- result += `- Implementation found: ${implementations.length > 0 ? "Yes" : "No"}\n`;
202
- if (requirements.length > 0 && implementations.length === 0) {
203
- result += `\n**Gap detected:** Requirements exist but no implementation found.`;
204
- }
205
- else if (requirements.length === 0 && implementations.length > 0) {
206
- result += `\n**Warning:** Implementation exists but no documented requirements.`;
207
- }
208
- return result;
209
- },
210
- estimate_feature: async (args, ctx) => {
211
- const { feature, includeSubtasks = true } = args;
212
- // Search for related requirements
213
- const reqResponse = await ctx.api.post("/api/search", {
214
- collection: `${ctx.collectionPrefix}confluence`,
215
- query: feature,
216
- limit: 5,
217
- });
218
- // Search for related code
219
- const codeResponse = await ctx.api.post("/api/search", {
220
- collection: `${ctx.collectionPrefix}codebase`,
221
- query: feature,
222
- limit: 15,
223
- });
224
- // Search for related tests
225
- const testResponse = await ctx.api
226
- .post("/api/search", {
227
- collection: `${ctx.collectionPrefix}codebase`,
228
- query: `${feature} test spec`,
229
- limit: 10,
230
- filter: { must: [{ key: "file", match: { text: "test" } }] },
231
- })
232
- .catch(() => ({ data: { results: [] } }));
233
- const requirements = reqResponse.data.results || [];
234
- const relatedCode = codeResponse.data.results || [];
235
- const relatedTests = testResponse.data.results || [];
236
- // Analyze complexity based on findings
237
- const hasRequirements = requirements.length > 0;
238
- const hasExistingCode = relatedCode.length > 0;
239
- const hasTests = relatedTests.length > 0;
240
- const affectedFiles = Array.from(new Set(relatedCode.map((r) => r.payload?.file || r.file)));
241
- const testFiles = Array.from(new Set(relatedTests.map((r) => r.payload?.file || r.file)));
242
- // Advanced code complexity analysis
243
- let totalComplexityScore = 0;
244
- let totalIntegrationPoints = 0;
245
- const integrations = new Set();
246
- const complexFunctions = [];
247
- for (const result of relatedCode) {
248
- const content = result.payload?.content || result.content || "";
249
- // Count complexity indicators
250
- const ifCount = (content.match(/\bif\s*\(/g) || []).length;
251
- const elseCount = (content.match(/\belse\b/g) || []).length;
252
- const switchCount = (content.match(/\bswitch\s*\(/g) || []).length;
253
- const forCount = (content.match(/\bfor\s*\(/g) || []).length;
254
- const whileCount = (content.match(/\bwhile\s*\(/g) || []).length;
255
- const tryCount = (content.match(/\btry\s*\{/g) || []).length;
256
- const asyncCount = (content.match(/\basync\b/g) || []).length;
257
- const awaitCount = (content.match(/\bawait\b/g) || []).length;
258
- // Cyclomatic complexity approximation
259
- const complexity = 1 + ifCount + elseCount + switchCount + forCount + whileCount + tryCount;
260
- totalComplexityScore += complexity;
261
- // Track complex functions (rough estimate)
262
- if (complexity > 10) {
263
- const funcMatch = content.match(/(?:function|const|async)\s+(\w+)/);
264
- if (funcMatch) {
265
- complexFunctions.push(`${result.payload?.file || result.file}: ${funcMatch[1]}() - complexity ~${complexity}`);
276
+ schema: z.object({
277
+ question: z.string().describe("PM question (e.g., 'What features are planned for video inspection?', 'What\\'s the status of notifications?')"),
278
+ }),
279
+ annotations: TOOL_ANNOTATIONS["ask_pm"],
280
+ handler: async (args, ctx) => {
281
+ const { question } = args;
282
+ // Search both requirements and codebase for context
283
+ const [reqResponse, codeResponse] = await Promise.all([
284
+ ctx.api.post("/api/search", {
285
+ collection: `${ctx.collectionPrefix}confluence`,
286
+ query: question,
287
+ limit: 5,
288
+ }),
289
+ ctx.api.post("/api/search", {
290
+ collection: `${ctx.collectionPrefix}codebase`,
291
+ query: question,
292
+ limit: 3,
293
+ }),
294
+ ]);
295
+ const requirements = reqResponse.data.results || [];
296
+ const code = codeResponse.data.results || [];
297
+ // Use LLM to answer the question with context
298
+ try {
299
+ const response = await ctx.api.post("/api/ask", {
300
+ collection: `${ctx.collectionPrefix}confluence`,
301
+ question: `As a Product Manager, answer this question about the project:\n\n${question}\n\nUse the provided context from requirements documentation.`,
302
+ });
303
+ let result = `# PM Question: ${question}\n\n`;
304
+ result += `## Answer\n${response.data.answer}\n\n`;
305
+ if (requirements.length > 0) {
306
+ result += `## Related Documentation\n`;
307
+ requirements.slice(0, 3).forEach((r) => {
308
+ result += `- ${r.title || "Doc"}: ${truncate(r.content, 100)}\n`;
309
+ });
266
310
  }
267
- }
268
- // Analyze integration points
269
- const imports = content.match(/import\s+.*from\s+['"]([^'"]+)['"]/g) || [];
270
- const requires = content.match(/require\s*\(['"]([^'"]+)['"]\)/g) || [];
271
- const apiCalls = content.match(/(?:axios|fetch|http|api)\.[a-z]+\(/gi) || [];
272
- const dbOps = content.match(/(?:prisma|mongoose|sequelize|knex|db)\.[a-z]+/gi) || [];
273
- const externalServices = content.match(/(?:redis|kafka|rabbitmq|queue|cache)\.[a-z]+/gi) || [];
274
- [...imports, ...requires].forEach((imp) => {
275
- const match = imp.match(/['"]([^'"]+)['"]/);
276
- if (match && !match[1].startsWith(".")) {
277
- integrations.add(`Package: ${match[1]}`);
311
+ if (code.length > 0) {
312
+ result += `\n## Related Code\n`;
313
+ code.slice(0, 3).forEach((r) => {
314
+ result += `- ${r.file}\n`;
315
+ });
278
316
  }
279
- });
280
- if (apiCalls.length > 0)
281
- integrations.add("HTTP/API calls");
282
- if (dbOps.length > 0)
283
- integrations.add("Database operations");
284
- if (externalServices.length > 0)
285
- integrations.add("External services (cache/queue)");
286
- if (asyncCount > 3 || awaitCount > 3)
287
- integrations.add("Heavy async operations");
288
- totalIntegrationPoints +=
289
- imports.length + requires.length + apiCalls.length + dbOps.length;
290
- }
291
- // Determine complexity level
292
- const avgComplexity = affectedFiles.length > 0
293
- ? totalComplexityScore / relatedCode.length
294
- : 0;
295
- let complexity = "Low";
296
- let complexityScore = 0;
297
- // Factor 1: File count (0-30 points)
298
- if (affectedFiles.length > 15)
299
- complexityScore += 30;
300
- else if (affectedFiles.length > 8)
301
- complexityScore += 20;
302
- else if (affectedFiles.length > 3)
303
- complexityScore += 10;
304
- else
305
- complexityScore += 5;
306
- // Factor 2: Code complexity (0-30 points)
307
- if (avgComplexity > 15)
308
- complexityScore += 30;
309
- else if (avgComplexity > 8)
310
- complexityScore += 20;
311
- else if (avgComplexity > 4)
312
- complexityScore += 10;
313
- else
314
- complexityScore += 5;
315
- // Factor 3: Integration points (0-20 points)
316
- if (integrations.size > 6)
317
- complexityScore += 20;
318
- else if (integrations.size > 3)
319
- complexityScore += 15;
320
- else if (integrations.size > 1)
321
- complexityScore += 10;
322
- else
323
- complexityScore += 5;
324
- // Factor 4: Test coverage (0-20 points) - less tests = more risk
325
- const testRatio = affectedFiles.length > 0
326
- ? testFiles.length / affectedFiles.length
327
- : 0;
328
- if (testRatio < 0.2)
329
- complexityScore += 20;
330
- else if (testRatio < 0.5)
331
- complexityScore += 15;
332
- else if (testRatio < 0.8)
333
- complexityScore += 10;
334
- else
335
- complexityScore += 5;
336
- if (complexityScore >= 70)
337
- complexity = "Very High";
338
- else if (complexityScore >= 50)
339
- complexity = "High";
340
- else if (complexityScore >= 30)
341
- complexity = "Medium";
342
- else
343
- complexity = "Low";
344
- // Risk assessment
345
- const riskFactors = [];
346
- let riskScore = 0;
347
- if (!hasRequirements) {
348
- riskFactors.push("No documented requirements - scope unclear");
349
- riskScore += 25;
350
- }
351
- if (affectedFiles.length > 10) {
352
- riskFactors.push(`Wide impact: ${affectedFiles.length} files affected`);
353
- riskScore += 20;
354
- }
355
- if (!hasTests) {
356
- riskFactors.push("No existing tests found - regression risk");
357
- riskScore += 20;
358
- }
359
- if (integrations.has("Database operations")) {
360
- riskFactors.push("Database changes - migration complexity");
361
- riskScore += 15;
362
- }
363
- if (integrations.has("External services (cache/queue)")) {
364
- riskFactors.push("External service dependencies");
365
- riskScore += 15;
366
- }
367
- if (complexFunctions.length > 3) {
368
- riskFactors.push(`${complexFunctions.length} complex functions to modify`);
369
- riskScore += 15;
370
- }
371
- if (!hasExistingCode) {
372
- riskFactors.push("New development - no patterns to follow");
373
- riskScore += 10;
374
- }
375
- let riskLevel = "Low";
376
- if (riskScore >= 60)
377
- riskLevel = "Critical";
378
- else if (riskScore >= 40)
379
- riskLevel = "High";
380
- else if (riskScore >= 20)
381
- riskLevel = "Medium";
382
- // Build result
383
- let result = `# Feature Estimation: ${feature}\n\n`;
384
- result += `## Overview\n`;
385
- result += `| Metric | Value |\n`;
386
- result += `|--------|-------|\n`;
387
- result += `| Complexity | **${complexity}** (score: ${complexityScore}/100) |\n`;
388
- result += `| Risk Level | **${riskLevel}** (score: ${riskScore}/100) |\n`;
389
- result += `| Affected Files | ${affectedFiles.length} |\n`;
390
- result += `| Test Files | ${testFiles.length} (ratio: ${(testRatio * 100).toFixed(0)}%) |\n`;
391
- result += `| Integration Points | ${integrations.size} |\n`;
392
- result += `| Avg Cyclomatic Complexity | ${avgComplexity.toFixed(1)} |\n`;
393
- result += `| Requirements Documented | ${hasRequirements ? "Yes" : "No"} |\n\n`;
394
- if (integrations.size > 0) {
395
- result += `## Integration Points\n`;
396
- Array.from(integrations)
397
- .slice(0, 10)
398
- .forEach((i) => {
399
- result += `- ${i}\n`;
400
- });
401
- result += "\n";
402
- }
403
- if (affectedFiles.length > 0) {
404
- result += `## Affected Files\n`;
405
- affectedFiles.slice(0, 15).forEach((f) => {
406
- const hasTest = testFiles.some((t) => t.includes(f.replace(/\.(ts|js|py|go)$/, "")));
407
- result += `- ${f} ${hasTest ? "(tested)" : "(no tests)"}\n`;
408
- });
409
- if (affectedFiles.length > 15) {
410
- result += `- ... and ${affectedFiles.length - 15} more\n`;
317
+ return result;
411
318
  }
412
- result += "\n";
413
- }
414
- if (complexFunctions.length > 0) {
415
- result += `## Complex Functions (may need refactoring)\n`;
416
- complexFunctions.slice(0, 5).forEach((f) => {
417
- result += `- ${f}\n`;
418
- });
419
- result += "\n";
420
- }
421
- result += `## Risk Factors\n`;
422
- if (riskFactors.length > 0) {
423
- riskFactors.forEach((r) => {
424
- result += `- ${r}\n`;
425
- });
426
- }
427
- else {
428
- result += `- No significant risks identified\n`;
429
- }
430
- result += "\n";
431
- if (includeSubtasks) {
432
- result += `## Suggested Subtasks\n`;
433
- let taskNum = 1;
434
- result += `${taskNum++}. Review and clarify requirements\n`;
435
- if (!hasRequirements) {
436
- result += `${taskNum++}. Document requirements\n`;
437
- }
438
- if (hasExistingCode) {
439
- result += `${taskNum++}. Analyze existing implementation and complexity\n`;
440
- if (complexFunctions.length > 0) {
441
- result += `${taskNum++}. Refactor complex functions if needed\n`;
319
+ catch {
320
+ // Fallback without LLM
321
+ let result = `# PM Question: ${question}\n\n`;
322
+ result += `## Related Information\n\n`;
323
+ if (requirements.length > 0) {
324
+ result += `### From Requirements:\n`;
325
+ requirements.forEach((r) => {
326
+ result += `**${r.title || "Doc"}**\n${truncate(r.content, 300)}\n\n`;
327
+ });
442
328
  }
443
- result += `${taskNum++}. Plan modifications\n`;
444
- }
445
- else {
446
- result += `${taskNum++}. Design solution architecture\n`;
447
- result += `${taskNum++}. Implement core functionality\n`;
448
- }
449
- if (integrations.has("Database operations")) {
450
- result += `${taskNum++}. Create database migrations\n`;
329
+ return result;
451
330
  }
452
- result += `${taskNum++}. Write/update tests (target: >${affectedFiles.length} test cases)\n`;
453
- if (integrations.has("External services (cache/queue)")) {
454
- result += `${taskNum++}. Integration testing with external services\n`;
455
- }
456
- result += `${taskNum++}. Code review & QA\n`;
457
- result += `${taskNum++}. Documentation update\n`;
458
- }
459
- return result;
460
- },
461
- get_feature_status: async (args, ctx) => {
462
- const { feature } = args;
463
- const reqResponse = await ctx.api.post("/api/search", {
464
- collection: `${ctx.collectionPrefix}confluence`,
465
- query: feature,
466
- limit: 3,
467
- });
468
- const codeResponse = await ctx.api.post("/api/search", {
469
- collection: `${ctx.collectionPrefix}codebase`,
470
- query: feature,
471
- limit: 5,
472
- });
473
- const requirements = reqResponse.data.results || [];
474
- const implementations = codeResponse.data.results || [];
475
- let status = "Unknown";
476
- let statusEmoji = "?";
477
- if (requirements.length > 0 && implementations.length > 0) {
478
- status = "Implemented";
479
- statusEmoji = "[DONE]";
480
- }
481
- else if (requirements.length > 0 && implementations.length === 0) {
482
- status = "Planned (Not Implemented)";
483
- statusEmoji = "[PLANNED]";
484
- }
485
- else if (requirements.length === 0 && implementations.length > 0) {
486
- status = "Implemented (Undocumented)";
487
- statusEmoji = "[WARN]";
488
- }
489
- else {
490
- status = "Not Found";
491
- statusEmoji = "[MISSING]";
492
- }
493
- let result = `# Feature Status: ${feature}\n\n`;
494
- result += `## ${statusEmoji} Status: ${status}\n\n`;
495
- if (requirements.length > 0) {
496
- result += `### Requirements\n`;
497
- requirements.forEach((r) => {
498
- result += `- ${r.title || "Requirement"}: ${truncate(r.content, 150)}\n`;
499
- });
500
- result += "\n";
501
- }
502
- if (implementations.length > 0) {
503
- result += `### Implementation\n`;
504
- implementations.forEach((r) => {
505
- result += `- ${r.file}\n`;
506
- });
507
- }
508
- return result;
509
- },
510
- list_requirements: async (args, ctx) => {
511
- const { category, limit = 20 } = args;
512
- const query = category || "requirements features specifications";
513
- const response = await ctx.api.post("/api/search", {
514
- collection: `${ctx.collectionPrefix}confluence`,
515
- query,
516
- limit,
517
- });
518
- const results = response.data.results || [];
519
- if (results.length === 0) {
520
- return "No requirements found in Confluence. Make sure documentation is indexed.";
521
- }
522
- let result = `# ${ctx.projectName} Requirements\n\n`;
523
- if (category) {
524
- result += `**Category filter:** ${category}\n\n`;
525
- }
526
- result += `**Found:** ${results.length} items\n\n`;
527
- results.forEach((r, i) => {
528
- result += `${i + 1}. **${r.title || "Untitled"}**\n`;
529
- result += ` ${truncate(r.content.replace(/\n/g, " "), 150)}\n`;
530
- if (r.url) {
531
- result += ` [View in Confluence](${r.url})\n`;
532
- }
533
- result += "\n";
534
- });
535
- return result;
331
+ },
536
332
  },
537
- ask_pm: async (args, ctx) => {
538
- const { question } = args;
539
- // Search both requirements and codebase for context
540
- const [reqResponse, codeResponse] = await Promise.all([
541
- ctx.api.post("/api/search", {
333
+ {
334
+ name: "generate_spec",
335
+ description: `Generate technical specification from requirements. Creates a structured spec document based on Confluence requirements and existing codebase patterns.`,
336
+ schema: z.object({
337
+ feature: z.string().describe("Feature to generate spec for"),
338
+ format: z.enum(["markdown", "jira", "brief"]).optional().describe("Output format (default: markdown)"),
339
+ }),
340
+ annotations: TOOL_ANNOTATIONS["generate_spec"],
341
+ handler: async (args, ctx) => {
342
+ const { feature, format = "markdown" } = args;
343
+ // Get requirements
344
+ const reqResponse = await ctx.api.post("/api/search", {
542
345
  collection: `${ctx.collectionPrefix}confluence`,
543
- query: question,
346
+ query: feature,
544
347
  limit: 5,
545
- }),
546
- ctx.api.post("/api/search", {
348
+ });
349
+ // Get existing code for patterns
350
+ const codeResponse = await ctx.api.post("/api/search", {
547
351
  collection: `${ctx.collectionPrefix}codebase`,
548
- query: question,
549
- limit: 3,
550
- }),
551
- ]);
552
- const requirements = reqResponse.data.results || [];
553
- const code = codeResponse.data.results || [];
554
- // Use LLM to answer the question with context
555
- try {
556
- const response = await ctx.api.post("/api/ask", {
557
- collection: `${ctx.collectionPrefix}confluence`,
558
- question: `As a Product Manager, answer this question about the project:\n\n${question}\n\nUse the provided context from requirements documentation.`,
352
+ query: feature,
353
+ limit: 5,
559
354
  });
560
- let result = `# PM Question: ${question}\n\n`;
561
- result += `## Answer\n${response.data.answer}\n\n`;
562
- if (requirements.length > 0) {
563
- result += `## Related Documentation\n`;
564
- requirements.slice(0, 3).forEach((r) => {
565
- result += `- ${r.title || "Doc"}: ${truncate(r.content, 100)}\n`;
566
- });
567
- }
568
- if (code.length > 0) {
569
- result += `\n## Related Code\n`;
570
- code.slice(0, 3).forEach((r) => {
571
- result += `- ${r.file}\n`;
572
- });
573
- }
574
- return result;
575
- }
576
- catch {
577
- // Fallback without LLM
578
- let result = `# PM Question: ${question}\n\n`;
579
- result += `## Related Information\n\n`;
580
- if (requirements.length > 0) {
581
- result += `### From Requirements:\n`;
582
- requirements.forEach((r) => {
583
- result += `**${r.title || "Doc"}**\n${truncate(r.content, 300)}\n\n`;
584
- });
585
- }
586
- return result;
587
- }
588
- },
589
- generate_spec: async (args, ctx) => {
590
- const { feature, format = "markdown" } = args;
591
- // Get requirements
592
- const reqResponse = await ctx.api.post("/api/search", {
593
- collection: `${ctx.collectionPrefix}confluence`,
594
- query: feature,
595
- limit: 5,
596
- });
597
- // Get existing code for patterns
598
- const codeResponse = await ctx.api.post("/api/search", {
599
- collection: `${ctx.collectionPrefix}codebase`,
600
- query: feature,
601
- limit: 5,
602
- });
603
- const requirements = reqResponse.data.results || [];
604
- const code = codeResponse.data.results || [];
605
- // Build context for LLM
606
- const requirementsContext = requirements.length > 0
607
- ? requirements.map((r) => r.content).join("\n---\n")
608
- : "No documented requirements found.";
609
- const codeContext = code.length > 0
610
- ? code
611
- .map((c) => `File: ${c.file}\n${truncate(c.content, 300)}`)
612
- .join("\n---\n")
613
- : "No existing implementation found.";
614
- // Use LLM to generate real specification
615
- const specPrompt = `Generate a detailed technical specification for: "${feature}"
355
+ const requirements = reqResponse.data.results || [];
356
+ const code = codeResponse.data.results || [];
357
+ // Build context for LLM
358
+ const requirementsContext = requirements.length > 0
359
+ ? requirements.map((r) => r.content).join("\n---\n")
360
+ : "No documented requirements found.";
361
+ const codeContext = code.length > 0
362
+ ? code
363
+ .map((c) => `File: ${c.file}\n${truncate(c.content, 300)}`)
364
+ .join("\n---\n")
365
+ : "No existing implementation found.";
366
+ // Use LLM to generate real specification
367
+ const specPrompt = `Generate a detailed technical specification for: "${feature}"
616
368
 
617
369
  Requirements from documentation:
618
370
  ${requirementsContext}
@@ -628,53 +380,53 @@ Generate a complete specification including:
628
380
  5. Database changes (if applicable)
629
381
  6. Testing strategy
630
382
  7. Rollout considerations`;
631
- try {
632
- const llmResponse = await ctx.api.post("/api/ask", {
633
- collection: `${ctx.collectionPrefix}codebase`,
634
- question: specPrompt,
635
- });
636
- let result = `# Technical Specification: ${feature}\n\n`;
637
- if (format === "jira") {
638
- // Convert to Jira format
639
- result = `h1. ${feature}\n\n`;
640
- result += llmResponse.data.answer
641
- .replace(/^## /gm, "h2. ")
642
- .replace(/^### /gm, "h3. ")
643
- .replace(/^- \[ \]/gm, "* [ ]")
644
- .replace(/^- /gm, "* ");
645
- }
646
- else if (format === "brief") {
647
- // Brief summary
648
- const answer = llmResponse.data.answer;
649
- const firstParagraph = answer.split("\n\n")[0] || answer.slice(0, 300);
650
- result = `**${feature}**\n\n${firstParagraph}\n\n`;
651
- result += `**Files affected:** ${code.map((c) => c.file).join(", ") || "New implementation"}`;
652
- }
653
- else {
654
- // Full markdown
655
- result += llmResponse.data.answer;
656
- // Add appendix with source files
657
- if (code.length > 0) {
658
- result += `\n\n---\n## Appendix: Related Files\n`;
659
- code.forEach((c) => {
660
- result += `- \`${c.file}\`\n`;
661
- });
383
+ try {
384
+ const llmResponse = await ctx.api.post("/api/ask", {
385
+ collection: `${ctx.collectionPrefix}codebase`,
386
+ question: specPrompt,
387
+ });
388
+ let result = `# Technical Specification: ${feature}\n\n`;
389
+ if (format === "jira") {
390
+ // Convert to Jira format
391
+ result = `h1. ${feature}\n\n`;
392
+ result += llmResponse.data.answer
393
+ .replace(/^## /gm, "h2. ")
394
+ .replace(/^### /gm, "h3. ")
395
+ .replace(/^- \[ \]/gm, "* [ ]")
396
+ .replace(/^- /gm, "* ");
397
+ }
398
+ else if (format === "brief") {
399
+ // Brief summary
400
+ const answer = llmResponse.data.answer;
401
+ const firstParagraph = answer.split("\n\n")[0] || answer.slice(0, 300);
402
+ result = `**${feature}**\n\n${firstParagraph}\n\n`;
403
+ result += `**Files affected:** ${code.map((c) => c.file).join(", ") || "New implementation"}`;
662
404
  }
405
+ else {
406
+ // Full markdown
407
+ result += llmResponse.data.answer;
408
+ // Add appendix with source files
409
+ if (code.length > 0) {
410
+ result += `\n\n---\n## Appendix: Related Files\n`;
411
+ code.forEach((c) => {
412
+ result += `- \`${c.file}\`\n`;
413
+ });
414
+ }
415
+ }
416
+ return result;
663
417
  }
664
- return result;
665
- }
666
- catch {
667
- // Fallback to template if LLM fails
668
- let result = `# Technical Specification: ${feature}\n\n`;
669
- result += `## 1. Overview\n${truncate(requirements[0]?.content, 500) || "_Add feature overview_"}\n\n`;
670
- result += `## 2. Requirements\n_LLM generation failed. Add requirements manually._\n\n`;
671
- result += `## 3. Affected Files\n`;
672
- code.forEach((c) => {
673
- result += `- \`${c.file}\`\n`;
674
- });
675
- return result;
676
- }
418
+ catch {
419
+ // Fallback to template if LLM fails
420
+ let result = `# Technical Specification: ${feature}\n\n`;
421
+ result += `## 1. Overview\n${truncate(requirements[0]?.content, 500) || "_Add feature overview_"}\n\n`;
422
+ result += `## 2. Requirements\n_LLM generation failed. Add requirements manually._\n\n`;
423
+ result += `## 3. Affected Files\n`;
424
+ code.forEach((c) => {
425
+ result += `- \`${c.file}\`\n`;
426
+ });
427
+ return result;
428
+ }
429
+ },
677
430
  },
678
- };
679
- return { tools, handlers };
431
+ ];
680
432
  }