@crowley/rag-mcp 1.0.4 → 1.0.6

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 (50) hide show
  1. package/dist/annotations.d.ts +16 -0
  2. package/dist/annotations.js +158 -0
  3. package/dist/context-enrichment.d.ts +2 -2
  4. package/dist/context-enrichment.js +37 -14
  5. package/dist/formatters.d.ts +2 -0
  6. package/dist/formatters.js +12 -0
  7. package/dist/index.js +64 -47
  8. package/dist/schemas.d.ts +97 -0
  9. package/dist/schemas.js +128 -0
  10. package/dist/tool-middleware.d.ts +40 -0
  11. package/dist/tool-middleware.js +216 -0
  12. package/dist/tool-registry.js +2 -1
  13. package/dist/tools/advanced.d.ts +2 -2
  14. package/dist/tools/advanced.js +200 -275
  15. package/dist/tools/agents.d.ts +2 -2
  16. package/dist/tools/agents.js +59 -78
  17. package/dist/tools/analytics.d.ts +2 -2
  18. package/dist/tools/analytics.js +170 -210
  19. package/dist/tools/architecture.d.ts +2 -2
  20. package/dist/tools/architecture.js +506 -661
  21. package/dist/tools/ask.d.ts +2 -2
  22. package/dist/tools/ask.js +164 -219
  23. package/dist/tools/cache.d.ts +2 -2
  24. package/dist/tools/cache.js +63 -82
  25. package/dist/tools/clustering.d.ts +2 -2
  26. package/dist/tools/clustering.js +154 -215
  27. package/dist/tools/confluence.d.ts +2 -2
  28. package/dist/tools/confluence.js +80 -116
  29. package/dist/tools/database.d.ts +2 -2
  30. package/dist/tools/database.js +303 -380
  31. package/dist/tools/feedback.d.ts +2 -2
  32. package/dist/tools/feedback.js +143 -184
  33. package/dist/tools/guidelines.d.ts +2 -2
  34. package/dist/tools/guidelines.js +123 -135
  35. package/dist/tools/indexing.d.ts +2 -2
  36. package/dist/tools/indexing.js +104 -100
  37. package/dist/tools/memory.d.ts +2 -2
  38. package/dist/tools/memory.js +299 -485
  39. package/dist/tools/pm.d.ts +2 -2
  40. package/dist/tools/pm.js +367 -615
  41. package/dist/tools/review.d.ts +2 -2
  42. package/dist/tools/review.js +142 -189
  43. package/dist/tools/search.d.ts +2 -2
  44. package/dist/tools/search.js +230 -305
  45. package/dist/tools/session.d.ts +2 -2
  46. package/dist/tools/session.js +288 -345
  47. package/dist/tools/suggestions.d.ts +2 -2
  48. package/dist/tools/suggestions.js +444 -255
  49. package/dist/types.d.ts +19 -2
  50. package/package.json +4 -2
@@ -2,261 +2,26 @@
2
2
  * Architecture tools module - ADRs, patterns, tech debt, and structure analysis.
3
3
  */
4
4
  import { truncate } from "../formatters.js";
5
+ import { z } from "zod";
6
+ import { TOOL_ANNOTATIONS } from "../annotations.js";
5
7
  export function createArchitectureTools(projectName) {
6
- const tools = [
8
+ return [
7
9
  {
8
10
  name: "record_adr",
9
11
  description: `Record an Architecture Decision Record (ADR). Use this to document important architectural decisions, technology choices, and design patterns for ${projectName}.`,
10
- inputSchema: {
11
- type: "object",
12
- properties: {
13
- title: {
14
- type: "string",
15
- description: "Short title for the decision (e.g., 'Use WebSocket for real-time updates')",
16
- },
17
- context: {
18
- type: "string",
19
- description: "Why this decision was needed - the problem or requirement",
20
- },
21
- decision: {
22
- type: "string",
23
- description: "What was decided",
24
- },
25
- consequences: {
26
- type: "string",
27
- description: "Positive and negative consequences of this decision",
28
- },
29
- alternatives: {
30
- type: "string",
31
- description: "What alternatives were considered",
32
- },
33
- status: {
34
- type: "string",
35
- enum: ["proposed", "accepted", "deprecated", "superseded"],
36
- description: "Status of the decision (default: accepted)",
37
- default: "accepted",
38
- },
39
- tags: {
40
- type: "array",
41
- items: { type: "string" },
42
- description: "Tags for categorization (e.g., ['api', 'security', 'database'])",
43
- },
44
- },
45
- required: ["title", "context", "decision"],
46
- },
47
- },
48
- {
49
- name: "get_adrs",
50
- description: `Get Architecture Decision Records for ${projectName}. Search by topic or list all ADRs.`,
51
- inputSchema: {
52
- type: "object",
53
- properties: {
54
- query: {
55
- type: "string",
56
- description: "Search query (optional - returns all if empty)",
57
- },
58
- status: {
59
- type: "string",
60
- enum: [
61
- "proposed",
62
- "accepted",
63
- "deprecated",
64
- "superseded",
65
- "all",
66
- ],
67
- description: "Filter by status",
68
- default: "all",
69
- },
70
- limit: {
71
- type: "number",
72
- description: "Max results (default: 10)",
73
- default: 10,
74
- },
75
- },
76
- },
77
- },
78
- {
79
- name: "record_pattern",
80
- description: `Record an architectural pattern used in ${projectName}. Patterns define how specific types of code should be structured.`,
81
- inputSchema: {
82
- type: "object",
83
- properties: {
84
- name: {
85
- type: "string",
86
- description: "Pattern name (e.g., 'Service Layer', 'Repository Pattern', 'API Endpoint')",
87
- },
88
- description: {
89
- type: "string",
90
- description: "What this pattern is for and when to use it",
91
- },
92
- structure: {
93
- type: "string",
94
- description: "How code following this pattern should be structured (file organization, naming, etc.)",
95
- },
96
- example: {
97
- type: "string",
98
- description: "Example code or file reference demonstrating the pattern",
99
- },
100
- appliesTo: {
101
- type: "string",
102
- description: "Where this pattern applies (e.g., 'backend/src/modules/*', 'all API endpoints')",
103
- },
104
- tags: {
105
- type: "array",
106
- items: { type: "string" },
107
- description: "Tags (e.g., ['backend', 'api', 'module'])",
108
- },
109
- },
110
- required: ["name", "description", "structure"],
111
- },
112
- },
113
- {
114
- name: "get_patterns",
115
- description: `Get architectural patterns for ${projectName}. Use to understand how to structure new code.`,
116
- inputSchema: {
117
- type: "object",
118
- properties: {
119
- query: {
120
- type: "string",
121
- description: "Search for patterns by name or description",
122
- },
123
- appliesTo: {
124
- type: "string",
125
- description: "Filter by what patterns apply to (e.g., 'api', 'module')",
126
- },
127
- limit: {
128
- type: "number",
129
- description: "Max results (default: 10)",
130
- default: 10,
131
- },
132
- },
133
- },
134
- },
135
- {
136
- name: "check_architecture",
137
- description: `Check if code or a feature follows established architectural patterns. Analyzes code against recorded patterns and ADRs.`,
138
- inputSchema: {
139
- type: "object",
140
- properties: {
141
- code: {
142
- type: "string",
143
- description: "Code snippet to check",
144
- },
145
- filePath: {
146
- type: "string",
147
- description: "File path for context (helps determine which patterns apply)",
148
- },
149
- featureDescription: {
150
- type: "string",
151
- description: "Description of what the code does (alternative to providing code)",
152
- },
153
- },
154
- },
155
- },
156
- {
157
- name: "suggest_architecture",
158
- description: `Get architectural guidance for implementing a new feature. Suggests structure, patterns to follow, and relevant ADRs.`,
159
- inputSchema: {
160
- type: "object",
161
- properties: {
162
- feature: {
163
- type: "string",
164
- description: "Feature to implement",
165
- },
166
- type: {
167
- type: "string",
168
- enum: [
169
- "api",
170
- "module",
171
- "service",
172
- "component",
173
- "integration",
174
- "other",
175
- ],
176
- description: "Type of feature",
177
- },
178
- },
179
- required: ["feature"],
180
- },
181
- },
182
- {
183
- name: "record_tech_debt",
184
- description: `Record technical debt or architectural violation that needs to be addressed later.`,
185
- inputSchema: {
186
- type: "object",
187
- properties: {
188
- title: {
189
- type: "string",
190
- description: "Short description of the tech debt",
191
- },
192
- description: {
193
- type: "string",
194
- description: "Detailed description of the issue",
195
- },
196
- location: {
197
- type: "string",
198
- description: "Where in the codebase (file paths, modules)",
199
- },
200
- impact: {
201
- type: "string",
202
- enum: ["low", "medium", "high", "critical"],
203
- description: "Impact level",
204
- },
205
- suggestedFix: {
206
- type: "string",
207
- description: "How to fix this debt",
208
- },
209
- relatedAdr: {
210
- type: "string",
211
- description: "Related ADR ID if this violates a decision",
212
- },
213
- },
214
- required: ["title", "description", "impact"],
215
- },
216
- },
217
- {
218
- name: "get_tech_debt",
219
- description: `List technical debt items for ${projectName}.`,
220
- inputSchema: {
221
- type: "object",
222
- properties: {
223
- impact: {
224
- type: "string",
225
- enum: ["low", "medium", "high", "critical", "all"],
226
- description: "Filter by impact",
227
- default: "all",
228
- },
229
- limit: {
230
- type: "number",
231
- description: "Max results (default: 10)",
232
- default: 10,
233
- },
234
- },
235
- },
236
- },
237
- {
238
- name: "analyze_project_structure",
239
- description: `Analyze the current project structure and compare with established patterns. Identifies inconsistencies and suggests improvements.`,
240
- inputSchema: {
241
- type: "object",
242
- properties: {
243
- path: {
244
- type: "string",
245
- description: "Specific path to analyze (default: entire project)",
246
- },
247
- deep: {
248
- type: "boolean",
249
- description: "Perform deep analysis including code patterns (default: false)",
250
- default: false,
251
- },
252
- },
253
- },
254
- },
255
- ];
256
- // ── Handlers ──────────────────────────────────────────────────────────
257
- async function handleRecordAdr(args, ctx) {
258
- const { title, context, decision, consequences, alternatives, status = "accepted", tags = [], } = args;
259
- const adrContent = `# ADR: ${title}
12
+ schema: z.object({
13
+ title: z.string().describe("Short title for the decision (e.g., 'Use WebSocket for real-time updates')"),
14
+ context: z.string().describe("Why this decision was needed - the problem or requirement"),
15
+ decision: z.string().describe("What was decided"),
16
+ consequences: z.string().optional().describe("Positive and negative consequences of this decision"),
17
+ alternatives: z.string().optional().describe("What alternatives were considered"),
18
+ status: z.enum(["proposed", "accepted", "deprecated", "superseded"]).optional().describe("Status of the decision (default: accepted)"),
19
+ tags: z.array(z.string()).optional().describe("Tags for categorization (e.g., ['api', 'security', 'database'])"),
20
+ }),
21
+ annotations: TOOL_ANNOTATIONS["record_adr"],
22
+ handler: async (args, ctx) => {
23
+ const { title, context, decision, consequences, alternatives, status = "accepted", tags = [], } = args;
24
+ const adrContent = `# ADR: ${title}
260
25
 
261
26
  ## Status
262
27
  ${status.toUpperCase()}
@@ -269,56 +34,80 @@ ${decision}
269
34
 
270
35
  ${consequences ? `## Consequences\n${consequences}\n` : ""}
271
36
  ${alternatives ? `## Alternatives Considered\n${alternatives}` : ""}`;
272
- const response = await ctx.api.post("/api/memory", {
273
- projectName: ctx.projectName,
274
- content: adrContent,
275
- type: "decision",
276
- tags: ["adr", ...tags],
277
- relatedTo: title,
278
- metadata: { adrTitle: title, adrStatus: status },
279
- });
280
- return (`# ADR Recorded\n\n` +
281
- `- **ID:** ${response.data.memory.id}\n` +
282
- `- **Title:** ${title}\n` +
283
- `- **Status:** ${status}\n` +
284
- `- **Tags:** ${["adr", ...tags].join(", ")}\n\n` +
285
- `Use \`get_adrs\` to retrieve this decision later.`);
286
- }
287
- async function handleGetAdrs(args, ctx) {
288
- const { query, status = "all", limit = 10, } = args;
289
- const response = await ctx.api.post("/api/memory/recall", {
290
- projectName: ctx.projectName,
291
- query: query || "architecture decision ADR",
292
- type: "decision",
293
- limit,
294
- });
295
- const results = response.data.results || [];
296
- const adrs = results.filter((r) => r.memory.tags?.includes("adr") &&
297
- (status === "all" || r.memory.metadata?.adrStatus === status));
298
- if (adrs.length === 0) {
299
- return `No ADRs found${query ? ` for "${query}"` : ""}`;
300
- }
301
- const statusIcons = {
302
- proposed: "\uD83D\uDFE1",
303
- accepted: "\uD83D\uDFE2",
304
- deprecated: "\uD83D\uDD34",
305
- superseded: "\u26AB",
306
- };
307
- let result = `# Architecture Decision Records (${adrs.length})\n\n`;
308
- adrs.forEach((r, i) => {
309
- const m = r.memory;
310
- const adrStatus = (m.metadata?.adrStatus || "accepted");
311
- const icon = statusIcons[adrStatus] || "\u26AA";
312
- result += `### ${i + 1}. ${icon} ${m.metadata?.adrTitle || m.relatedTo || "ADR"}\n`;
313
- result += `**Status:** ${adrStatus} | **ID:** \`${m.id}\`\n\n`;
314
- result +=
315
- truncate(m.content, 500) + "\n\n";
316
- });
317
- return result;
318
- }
319
- async function handleRecordPattern(args, ctx) {
320
- const { name, description, structure, example, appliesTo, tags = [], } = args;
321
- const patternContent = `# Pattern: ${name}
37
+ const response = await ctx.api.post("/api/memory", {
38
+ projectName: ctx.projectName,
39
+ content: adrContent,
40
+ type: "decision",
41
+ tags: ["adr", ...tags],
42
+ relatedTo: title,
43
+ metadata: { adrTitle: title, adrStatus: status },
44
+ });
45
+ return (`# ADR Recorded\n\n` +
46
+ `- **ID:** ${response.data.memory.id}\n` +
47
+ `- **Title:** ${title}\n` +
48
+ `- **Status:** ${status}\n` +
49
+ `- **Tags:** ${["adr", ...tags].join(", ")}\n\n` +
50
+ `Use \`get_adrs\` to retrieve this decision later.`);
51
+ },
52
+ },
53
+ {
54
+ name: "get_adrs",
55
+ description: `Get Architecture Decision Records for ${projectName}. Search by topic or list all ADRs.`,
56
+ schema: z.object({
57
+ query: z.string().optional().describe("Search query (optional - returns all if empty)"),
58
+ status: z.enum(["proposed", "accepted", "deprecated", "superseded", "all"]).optional().describe("Filter by status"),
59
+ limit: z.number().optional().describe("Max results (default: 10)"),
60
+ }),
61
+ annotations: TOOL_ANNOTATIONS["get_adrs"],
62
+ handler: async (args, ctx) => {
63
+ const { query, status = "all", limit = 10, } = args;
64
+ const response = await ctx.api.post("/api/memory/recall", {
65
+ projectName: ctx.projectName,
66
+ query: query || "architecture decision ADR",
67
+ type: "decision",
68
+ limit,
69
+ tag: "adr",
70
+ });
71
+ const results = response.data.results || [];
72
+ const adrs = results.filter((r) => r.memory.tags?.includes("adr") &&
73
+ (status === "all" || r.memory.metadata?.adrStatus === status));
74
+ if (adrs.length === 0) {
75
+ return `No ADRs found${query ? ` for "${query}"` : ""}`;
76
+ }
77
+ const statusIcons = {
78
+ proposed: "\uD83D\uDFE1",
79
+ accepted: "\uD83D\uDFE2",
80
+ deprecated: "\uD83D\uDD34",
81
+ superseded: "\u26AB",
82
+ };
83
+ let result = `# Architecture Decision Records (${adrs.length})\n\n`;
84
+ adrs.forEach((r, i) => {
85
+ const m = r.memory;
86
+ const adrStatus = (m.metadata?.adrStatus || "accepted");
87
+ const icon = statusIcons[adrStatus] || "\u26AA";
88
+ result += `### ${i + 1}. ${icon} ${m.metadata?.adrTitle || m.relatedTo || "ADR"}\n`;
89
+ result += `**Status:** ${adrStatus} | **ID:** \`${m.id}\`\n\n`;
90
+ result +=
91
+ truncate(m.content, 500) + "\n\n";
92
+ });
93
+ return result;
94
+ },
95
+ },
96
+ {
97
+ name: "record_pattern",
98
+ description: `Record an architectural pattern used in ${projectName}. Patterns define how specific types of code should be structured.`,
99
+ schema: z.object({
100
+ name: z.string().describe("Pattern name (e.g., 'Service Layer', 'Repository Pattern', 'API Endpoint')"),
101
+ description: z.string().describe("What this pattern is for and when to use it"),
102
+ structure: z.string().describe("How code following this pattern should be structured (file organization, naming, etc.)"),
103
+ example: z.string().optional().describe("Example code or file reference demonstrating the pattern"),
104
+ appliesTo: z.string().optional().describe("Where this pattern applies (e.g., 'backend/src/modules/*', 'all API endpoints')"),
105
+ tags: z.array(z.string()).optional().describe("Tags (e.g., ['backend', 'api', 'module'])"),
106
+ }),
107
+ annotations: TOOL_ANNOTATIONS["record_pattern"],
108
+ handler: async (args, ctx) => {
109
+ const { name, description, structure, example, appliesTo, tags = [], } = args;
110
+ const patternContent = `# Pattern: ${name}
322
111
 
323
112
  ## Description
324
113
  ${description}
@@ -328,97 +117,120 @@ ${structure}
328
117
 
329
118
  ${example ? `## Example\n\`\`\`\n${example}\n\`\`\`\n` : ""}
330
119
  ${appliesTo ? `## Applies To\n${appliesTo}` : ""}`;
331
- const response = await ctx.api.post("/api/memory", {
332
- projectName: ctx.projectName,
333
- content: patternContent,
334
- type: "context",
335
- tags: ["pattern", ...tags],
336
- relatedTo: name,
337
- metadata: { patternName: name, appliesTo },
338
- });
339
- return (`# Pattern Recorded\n\n` +
340
- `- **Name:** ${name}\n` +
341
- `- **ID:** ${response.data.memory.id}\n` +
342
- (appliesTo ? `- **Applies To:** ${appliesTo}\n` : "") +
343
- `- **Tags:** ${["pattern", ...tags].join(", ")}`);
344
- }
345
- async function handleGetPatterns(args, ctx) {
346
- const { query, appliesTo, limit = 10, } = args;
347
- const response = await ctx.api.post("/api/memory/recall", {
348
- projectName: ctx.projectName,
349
- query: query || "architectural pattern structure",
350
- type: "context",
351
- limit,
352
- });
353
- const results = response.data.results || [];
354
- const patterns = results.filter((r) => {
355
- const isPattern = r.memory.tags?.includes("pattern");
356
- const matchesAppliesTo = !appliesTo ||
357
- r.memory.metadata?.appliesTo
358
- ?.toLowerCase()
359
- .includes(appliesTo.toLowerCase());
360
- return isPattern && matchesAppliesTo;
361
- });
362
- if (patterns.length === 0) {
363
- return `No patterns found${query ? ` for "${query}"` : ""}`;
364
- }
365
- let result = `# Architectural Patterns (${patterns.length})\n\n`;
366
- patterns.forEach((r, i) => {
367
- const m = r.memory;
368
- result += `### ${i + 1}. ${m.metadata?.patternName || m.relatedTo || "Pattern"}\n`;
369
- if (m.metadata?.appliesTo) {
370
- result += `**Applies to:** ${m.metadata.appliesTo}\n`;
371
- }
372
- result += `**ID:** \`${m.id}\`\n\n`;
373
- result += truncate(m.content, 600) + "\n\n";
374
- });
375
- return result;
376
- }
377
- async function handleCheckArchitecture(args, ctx) {
378
- const { code, filePath, featureDescription } = args;
379
- const patternQuery = filePath || featureDescription || "architectural patterns";
380
- // Get relevant patterns
381
- const patternsResponse = await ctx.api.post("/api/memory/recall", {
382
- projectName: ctx.projectName,
383
- query: patternQuery,
384
- type: "context",
385
- limit: 5,
386
- });
387
- // Get relevant ADRs
388
- const adrsResponse = await ctx.api.post("/api/memory/recall", {
389
- projectName: ctx.projectName,
390
- query: patternQuery,
391
- type: "decision",
392
- limit: 5,
393
- });
394
- // Search similar code in codebase
395
- let similarCode = [];
396
- if (code) {
397
- const codeResponse = await ctx.api.post("/api/search", {
398
- collection: `${ctx.collectionPrefix}codebase`,
399
- query: code.slice(0, 500),
400
- limit: 3,
401
- });
402
- similarCode = codeResponse.data.results || [];
403
- }
404
- const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
405
- const adrs = (adrsResponse.data.results || []).filter((r) => r.memory.tags?.includes("adr"));
406
- let result = `# Architecture Check\n\n`;
407
- if (filePath) {
408
- result += `**File:** ${filePath}\n\n`;
409
- }
410
- if (featureDescription) {
411
- result += `**Feature:** ${featureDescription}\n\n`;
412
- }
413
- // If we have code and patterns/ADRs, perform LLM validation
414
- if (code && (patterns.length > 0 || adrs.length > 0)) {
415
- const patternRules = patterns
416
- .map((p) => `Pattern: ${p.memory.metadata?.patternName || p.memory.relatedTo}\nDescription: ${truncate(p.memory.content, 300)}`)
417
- .join("\n\n");
418
- const adrRules = adrs
419
- .map((a) => `ADR: ${a.memory.metadata?.adrTitle || a.memory.relatedTo}\nDecision: ${truncate(a.memory.content, 300)}`)
420
- .join("\n\n");
421
- const validationPrompt = `Analyze if this code follows the established architectural patterns and decisions.
120
+ const response = await ctx.api.post("/api/memory", {
121
+ projectName: ctx.projectName,
122
+ content: patternContent,
123
+ type: "context",
124
+ tags: ["pattern", ...tags],
125
+ relatedTo: name,
126
+ metadata: { patternName: name, appliesTo },
127
+ });
128
+ return (`# Pattern Recorded\n\n` +
129
+ `- **Name:** ${name}\n` +
130
+ `- **ID:** ${response.data.memory.id}\n` +
131
+ (appliesTo ? `- **Applies To:** ${appliesTo}\n` : "") +
132
+ `- **Tags:** ${["pattern", ...tags].join(", ")}`);
133
+ },
134
+ },
135
+ {
136
+ name: "get_patterns",
137
+ description: `Get architectural patterns for ${projectName}. Use to understand how to structure new code.`,
138
+ schema: z.object({
139
+ query: z.string().optional().describe("Search for patterns by name or description"),
140
+ appliesTo: z.string().optional().describe("Filter by what patterns apply to (e.g., 'api', 'module')"),
141
+ limit: z.number().optional().describe("Max results (default: 10)"),
142
+ }),
143
+ annotations: TOOL_ANNOTATIONS["get_patterns"],
144
+ handler: async (args, ctx) => {
145
+ const { query, appliesTo, limit = 10, } = args;
146
+ const response = await ctx.api.post("/api/memory/recall", {
147
+ projectName: ctx.projectName,
148
+ query: query || "architectural pattern structure",
149
+ type: "context",
150
+ limit,
151
+ tag: "pattern",
152
+ });
153
+ const results = response.data.results || [];
154
+ const patterns = results.filter((r) => {
155
+ const isPattern = r.memory.tags?.includes("pattern");
156
+ const matchesAppliesTo = !appliesTo ||
157
+ r.memory.metadata?.appliesTo
158
+ ?.toLowerCase()
159
+ .includes(appliesTo.toLowerCase());
160
+ return isPattern && matchesAppliesTo;
161
+ });
162
+ if (patterns.length === 0) {
163
+ return `No patterns found${query ? ` for "${query}"` : ""}`;
164
+ }
165
+ let result = `# Architectural Patterns (${patterns.length})\n\n`;
166
+ patterns.forEach((r, i) => {
167
+ const m = r.memory;
168
+ result += `### ${i + 1}. ${m.metadata?.patternName || m.relatedTo || "Pattern"}\n`;
169
+ if (m.metadata?.appliesTo) {
170
+ result += `**Applies to:** ${m.metadata.appliesTo}\n`;
171
+ }
172
+ result += `**ID:** \`${m.id}\`\n\n`;
173
+ result += truncate(m.content, 600) + "\n\n";
174
+ });
175
+ return result;
176
+ },
177
+ },
178
+ {
179
+ name: "check_architecture",
180
+ description: `Check if code or a feature follows established architectural patterns. Analyzes code against recorded patterns and ADRs.`,
181
+ schema: z.object({
182
+ code: z.string().optional().describe("Code snippet to check"),
183
+ filePath: z.string().optional().describe("File path for context (helps determine which patterns apply)"),
184
+ featureDescription: z.string().optional().describe("Description of what the code does (alternative to providing code)"),
185
+ }),
186
+ annotations: TOOL_ANNOTATIONS["check_architecture"],
187
+ handler: async (args, ctx) => {
188
+ const { code, filePath, featureDescription } = args;
189
+ const patternQuery = filePath || featureDescription || "architectural patterns";
190
+ // Get relevant patterns
191
+ const patternsResponse = await ctx.api.post("/api/memory/recall", {
192
+ projectName: ctx.projectName,
193
+ query: patternQuery,
194
+ type: "context",
195
+ limit: 5,
196
+ tag: "pattern",
197
+ });
198
+ // Get relevant ADRs
199
+ const adrsResponse = await ctx.api.post("/api/memory/recall", {
200
+ projectName: ctx.projectName,
201
+ query: patternQuery,
202
+ type: "decision",
203
+ limit: 5,
204
+ tag: "adr",
205
+ });
206
+ // Search similar code in codebase
207
+ let similarCode = [];
208
+ if (code) {
209
+ const codeResponse = await ctx.api.post("/api/search", {
210
+ collection: `${ctx.collectionPrefix}codebase`,
211
+ query: code.slice(0, 500),
212
+ limit: 3,
213
+ });
214
+ similarCode = codeResponse.data.results || [];
215
+ }
216
+ const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
217
+ const adrs = (adrsResponse.data.results || []).filter((r) => r.memory.tags?.includes("adr"));
218
+ let result = `# Architecture Check\n\n`;
219
+ if (filePath) {
220
+ result += `**File:** ${filePath}\n\n`;
221
+ }
222
+ if (featureDescription) {
223
+ result += `**Feature:** ${featureDescription}\n\n`;
224
+ }
225
+ // If we have code and patterns/ADRs, perform LLM validation
226
+ if (code && (patterns.length > 0 || adrs.length > 0)) {
227
+ const patternRules = patterns
228
+ .map((p) => `Pattern: ${p.memory.metadata?.patternName || p.memory.relatedTo}\nDescription: ${truncate(p.memory.content, 300)}`)
229
+ .join("\n\n");
230
+ const adrRules = adrs
231
+ .map((a) => `ADR: ${a.memory.metadata?.adrTitle || a.memory.relatedTo}\nDecision: ${truncate(a.memory.content, 300)}`)
232
+ .join("\n\n");
233
+ const validationPrompt = `Analyze if this code follows the established architectural patterns and decisions.
422
234
 
423
235
  Code to validate:
424
236
  \`\`\`
@@ -435,135 +247,159 @@ Provide a structured analysis:
435
247
  1. List any violations of patterns or ADRs
436
248
  2. Rate compliance (1-10)
437
249
  3. Specific recommendations for improvements`;
438
- try {
439
- const validationResponse = await ctx.api.post("/api/ask", {
250
+ try {
251
+ const validationResponse = await ctx.api.post("/api/ask", {
252
+ collection: `${ctx.collectionPrefix}codebase`,
253
+ question: validationPrompt,
254
+ });
255
+ result += `## Validation Results\n\n`;
256
+ result += validationResponse.data.answer;
257
+ result += "\n\n";
258
+ }
259
+ catch (_e) {
260
+ // Continue without LLM validation
261
+ }
262
+ }
263
+ result += `## Applicable Patterns (${patterns.length})\n`;
264
+ if (patterns.length === 0) {
265
+ result += `_No specific patterns recorded for this area._\n\n`;
266
+ }
267
+ else {
268
+ patterns.forEach((p) => {
269
+ result += `- **${p.memory.metadata?.patternName || p.memory.relatedTo}**: ${truncate(p.memory.content, 100)}\n`;
270
+ });
271
+ result += "\n";
272
+ }
273
+ result += `## Relevant ADRs (${adrs.length})\n`;
274
+ if (adrs.length === 0) {
275
+ result += `_No relevant architectural decisions found._\n\n`;
276
+ }
277
+ else {
278
+ adrs.forEach((a) => {
279
+ result += `- **${a.memory.metadata?.adrTitle || a.memory.relatedTo}** [${a.memory.metadata?.adrStatus || "accepted"}]: ${truncate(a.memory.content, 100)}\n`;
280
+ });
281
+ result += "\n";
282
+ }
283
+ if (similarCode.length > 0) {
284
+ result += `## Similar Existing Code\n`;
285
+ result += `_Review these for consistency:_\n`;
286
+ similarCode.forEach((c) => {
287
+ result += `- ${c.file}\n`;
288
+ });
289
+ result += "\n";
290
+ }
291
+ result += `## Recommendations\n`;
292
+ if (patterns.length > 0) {
293
+ result += `- Follow the patterns listed above for consistency\n`;
294
+ }
295
+ if (adrs.length > 0) {
296
+ result += `- Ensure compliance with recorded architectural decisions\n`;
297
+ }
298
+ if (similarCode.length > 0) {
299
+ result += `- Check similar code for established conventions\n`;
300
+ }
301
+ if (patterns.length === 0 && adrs.length === 0) {
302
+ result += `- Consider recording patterns/ADRs for this area with \`record_pattern\` and \`record_adr\`\n`;
303
+ }
304
+ return result;
305
+ },
306
+ },
307
+ {
308
+ name: "suggest_architecture",
309
+ description: `Get architectural guidance for implementing a new feature. Suggests structure, patterns to follow, and relevant ADRs.`,
310
+ schema: z.object({
311
+ feature: z.string().describe("Feature to implement"),
312
+ type: z.enum(["api", "module", "service", "component", "integration", "other"]).optional().describe("Type of feature"),
313
+ }),
314
+ annotations: TOOL_ANNOTATIONS["suggest_architecture"],
315
+ handler: async (args, ctx) => {
316
+ const { feature, type = "other" } = args;
317
+ // Get patterns for this type
318
+ const patternsResponse = await ctx.api.post("/api/memory/recall", {
319
+ projectName: ctx.projectName,
320
+ query: `${type} ${feature} pattern structure`,
321
+ type: "context",
322
+ limit: 5,
323
+ tag: "pattern",
324
+ });
325
+ // Get relevant ADRs
326
+ const adrsResponse = await ctx.api.post("/api/memory/recall", {
327
+ projectName: ctx.projectName,
328
+ query: `${type} ${feature}`,
329
+ type: "decision",
330
+ limit: 3,
331
+ tag: "adr",
332
+ });
333
+ // Get similar implementations
334
+ const codeResponse = await ctx.api.post("/api/search", {
440
335
  collection: `${ctx.collectionPrefix}codebase`,
441
- question: validationPrompt,
336
+ query: `${type} ${feature}`,
337
+ limit: 5,
442
338
  });
443
- result += `## Validation Results\n\n`;
444
- result += validationResponse.data.answer;
445
- result += "\n\n";
446
- }
447
- catch (_e) {
448
- // Continue without LLM validation
449
- }
450
- }
451
- result += `## Applicable Patterns (${patterns.length})\n`;
452
- if (patterns.length === 0) {
453
- result += `_No specific patterns recorded for this area._\n\n`;
454
- }
455
- else {
456
- patterns.forEach((p) => {
457
- result += `- **${p.memory.metadata?.patternName || p.memory.relatedTo}**: ${truncate(p.memory.content, 100)}\n`;
458
- });
459
- result += "\n";
460
- }
461
- result += `## Relevant ADRs (${adrs.length})\n`;
462
- if (adrs.length === 0) {
463
- result += `_No relevant architectural decisions found._\n\n`;
464
- }
465
- else {
466
- adrs.forEach((a) => {
467
- result += `- **${a.memory.metadata?.adrTitle || a.memory.relatedTo}** [${a.memory.metadata?.adrStatus || "accepted"}]: ${truncate(a.memory.content, 100)}\n`;
468
- });
469
- result += "\n";
470
- }
471
- if (similarCode.length > 0) {
472
- result += `## Similar Existing Code\n`;
473
- result += `_Review these for consistency:_\n`;
474
- similarCode.forEach((c) => {
475
- result += `- ${c.file}\n`;
476
- });
477
- result += "\n";
478
- }
479
- result += `## Recommendations\n`;
480
- if (patterns.length > 0) {
481
- result += `- Follow the patterns listed above for consistency\n`;
482
- }
483
- if (adrs.length > 0) {
484
- result += `- Ensure compliance with recorded architectural decisions\n`;
485
- }
486
- if (similarCode.length > 0) {
487
- result += `- Check similar code for established conventions\n`;
488
- }
489
- if (patterns.length === 0 && adrs.length === 0) {
490
- result += `- Consider recording patterns/ADRs for this area with \`record_pattern\` and \`record_adr\`\n`;
491
- }
492
- return result;
493
- }
494
- async function handleSuggestArchitecture(args, ctx) {
495
- const { feature, type = "other" } = args;
496
- // Get patterns for this type
497
- const patternsResponse = await ctx.api.post("/api/memory/recall", {
498
- projectName: ctx.projectName,
499
- query: `${type} ${feature} pattern structure`,
500
- type: "context",
501
- limit: 5,
502
- });
503
- // Get relevant ADRs
504
- const adrsResponse = await ctx.api.post("/api/memory/recall", {
505
- projectName: ctx.projectName,
506
- query: `${type} ${feature}`,
507
- type: "decision",
508
- limit: 3,
509
- });
510
- // Get similar implementations
511
- const codeResponse = await ctx.api.post("/api/search", {
512
- collection: `${ctx.collectionPrefix}codebase`,
513
- query: `${type} ${feature}`,
514
- limit: 5,
515
- });
516
- const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
517
- const adrs = (adrsResponse.data.results || []).filter((r) => r.memory.tags?.includes("adr"));
518
- const existingCode = codeResponse.data.results || [];
519
- let result = `# Architecture Suggestion: ${feature}\n\n`;
520
- result += `**Type:** ${type}\n\n`;
521
- result += `## Recommended Patterns\n`;
522
- if (patterns.length === 0) {
523
- result += `_No specific patterns recorded. Consider following existing code conventions._\n\n`;
524
- }
525
- else {
526
- patterns.forEach((p) => {
527
- result += `### ${p.memory.metadata?.patternName || p.memory.relatedTo}\n`;
528
- result += truncate(p.memory.content, 400) + "\n\n";
529
- });
530
- }
531
- result += `## Relevant Decisions (ADRs)\n`;
532
- if (adrs.length === 0) {
533
- result += `_No specific ADRs found for this area._\n\n`;
534
- }
535
- else {
536
- adrs.forEach((a) => {
537
- result += `- **${a.memory.metadata?.adrTitle || a.memory.relatedTo}**: `;
538
- const decision = a.memory.content.match(/## Decision\n([\s\S]*?)(?=\n##|$)/);
539
- result += decision
540
- ? truncate(decision[1].trim(), 150)
541
- : "See full ADR";
542
- result += "\n";
543
- });
544
- result += "\n";
545
- }
546
- result += `## Reference Implementations\n`;
547
- if (existingCode.length === 0) {
548
- result += `_No similar implementations found._\n\n`;
549
- }
550
- else {
551
- result += `_Study these for conventions:_\n`;
552
- existingCode.forEach((c) => {
553
- result += `- \`${c.file}\`\n`;
554
- });
555
- result += "\n";
556
- }
557
- result += `## Next Steps\n`;
558
- result += `1. Review the patterns and ADRs above\n`;
559
- result += `2. Study reference implementations for conventions\n`;
560
- result += `3. Create your implementation following established structure\n`;
561
- result += `4. Use \`check_architecture\` to validate before committing\n`;
562
- return result;
563
- }
564
- async function handleRecordTechDebt(args, ctx) {
565
- const { title, description, location, impact, suggestedFix, relatedAdr } = args;
566
- const debtContent = `# Tech Debt: ${title}
339
+ const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
340
+ const adrs = (adrsResponse.data.results || []).filter((r) => r.memory.tags?.includes("adr"));
341
+ const existingCode = codeResponse.data.results || [];
342
+ let result = `# Architecture Suggestion: ${feature}\n\n`;
343
+ result += `**Type:** ${type}\n\n`;
344
+ result += `## Recommended Patterns\n`;
345
+ if (patterns.length === 0) {
346
+ result += `_No specific patterns recorded. Consider following existing code conventions._\n\n`;
347
+ }
348
+ else {
349
+ patterns.forEach((p) => {
350
+ result += `### ${p.memory.metadata?.patternName || p.memory.relatedTo}\n`;
351
+ result += truncate(p.memory.content, 400) + "\n\n";
352
+ });
353
+ }
354
+ result += `## Relevant Decisions (ADRs)\n`;
355
+ if (adrs.length === 0) {
356
+ result += `_No specific ADRs found for this area._\n\n`;
357
+ }
358
+ else {
359
+ adrs.forEach((a) => {
360
+ result += `- **${a.memory.metadata?.adrTitle || a.memory.relatedTo}**: `;
361
+ const decision = a.memory.content.match(/## Decision\n([\s\S]*?)(?=\n##|$)/);
362
+ result += decision
363
+ ? truncate(decision[1].trim(), 150)
364
+ : "See full ADR";
365
+ result += "\n";
366
+ });
367
+ result += "\n";
368
+ }
369
+ result += `## Reference Implementations\n`;
370
+ if (existingCode.length === 0) {
371
+ result += `_No similar implementations found._\n\n`;
372
+ }
373
+ else {
374
+ result += `_Study these for conventions:_\n`;
375
+ existingCode.forEach((c) => {
376
+ result += `- \`${c.file}\`\n`;
377
+ });
378
+ result += "\n";
379
+ }
380
+ result += `## Next Steps\n`;
381
+ result += `1. Review the patterns and ADRs above\n`;
382
+ result += `2. Study reference implementations for conventions\n`;
383
+ result += `3. Create your implementation following established structure\n`;
384
+ result += `4. Use \`check_architecture\` to validate before committing\n`;
385
+ return result;
386
+ },
387
+ },
388
+ {
389
+ name: "record_tech_debt",
390
+ description: `Record technical debt or architectural violation that needs to be addressed later.`,
391
+ schema: z.object({
392
+ title: z.string().describe("Short description of the tech debt"),
393
+ description: z.string().describe("Detailed description of the issue"),
394
+ location: z.string().optional().describe("Where in the codebase (file paths, modules)"),
395
+ impact: z.enum(["low", "medium", "high", "critical"]).describe("Impact level"),
396
+ suggestedFix: z.string().optional().describe("How to fix this debt"),
397
+ relatedAdr: z.string().optional().describe("Related ADR ID if this violates a decision"),
398
+ }),
399
+ annotations: TOOL_ANNOTATIONS["record_tech_debt"],
400
+ handler: async (args, ctx) => {
401
+ const { title, description, location, impact, suggestedFix, relatedAdr } = args;
402
+ const debtContent = `# Tech Debt: ${title}
567
403
 
568
404
  ## Impact
569
405
  ${impact.toUpperCase()}
@@ -574,147 +410,156 @@ ${description}
574
410
  ${location ? `## Location\n${location}\n` : ""}
575
411
  ${suggestedFix ? `## Suggested Fix\n${suggestedFix}\n` : ""}
576
412
  ${relatedAdr ? `## Related ADR\n${relatedAdr}` : ""}`;
577
- const response = await ctx.api.post("/api/memory", {
578
- projectName: ctx.projectName,
579
- content: debtContent,
580
- type: "insight",
581
- tags: ["tech-debt", `impact-${impact}`],
582
- relatedTo: title,
583
- metadata: { debtTitle: title, impact, location },
584
- });
585
- const impactEmojis = {
586
- low: "\uD83D\uDFE2",
587
- medium: "\uD83D\uDFE1",
588
- high: "\uD83D\uDFE0",
589
- critical: "\uD83D\uDD34",
590
- };
591
- const emoji = impactEmojis[impact] || "\u26AA";
592
- return (`${emoji} **Tech Debt Recorded**\n\n` +
593
- `- **Title:** ${title}\n` +
594
- `- **Impact:** ${impact}\n` +
595
- `- **ID:** ${response.data.memory.id}\n` +
596
- (location ? `- **Location:** ${location}\n` : ""));
597
- }
598
- async function handleGetTechDebt(args, ctx) {
599
- const { impact = "all", limit = 10 } = args;
600
- const response = await ctx.api.post("/api/memory/recall", {
601
- projectName: ctx.projectName,
602
- query: "technical debt violation issue",
603
- type: "insight",
604
- limit: limit * 2, // Fetch extra to account for filtering
605
- });
606
- const results = response.data.results || [];
607
- const debts = results
608
- .filter((r) => {
609
- const isDebt = r.memory.tags?.includes("tech-debt");
610
- const matchesImpact = impact === "all" ||
611
- r.memory.metadata?.impact === impact ||
612
- r.memory.tags?.includes(`impact-${impact}`);
613
- return isDebt && matchesImpact;
614
- })
615
- .slice(0, limit);
616
- if (debts.length === 0) {
617
- return `No tech debt found${impact !== "all" ? ` with ${impact} impact` : ""}`;
618
- }
619
- const impactEmojis = {
620
- low: "\uD83D\uDFE2",
621
- medium: "\uD83D\uDFE1",
622
- high: "\uD83D\uDFE0",
623
- critical: "\uD83D\uDD34",
624
- };
625
- let result = `# Technical Debt (${debts.length})\n\n`;
626
- debts.forEach((r, i) => {
627
- const m = r.memory;
628
- const debtImpact = m.metadata?.impact || "medium";
629
- const emoji = impactEmojis[debtImpact] || "\u26AA";
630
- result += `### ${i + 1}. ${emoji} ${m.metadata?.debtTitle || m.relatedTo || "Tech Debt"}\n`;
631
- result += `**Impact:** ${debtImpact}`;
632
- if (m.metadata?.location) {
633
- result += ` | **Location:** ${m.metadata.location}`;
634
- }
635
- result += `\n**ID:** \`${m.id}\`\n\n`;
636
- // Extract description section
637
- const descMatch = m.content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
638
- if (descMatch) {
639
- result += truncate(descMatch[1].trim(), 200) + "\n\n";
640
- }
641
- });
642
- return result;
643
- }
644
- async function handleAnalyzeProjectStructure(args, ctx) {
645
- const { path, deep = false } = args;
646
- // Get all recorded patterns
647
- const patternsResponse = await ctx.api.post("/api/memory/recall", {
648
- projectName: ctx.projectName,
649
- query: "pattern structure organization",
650
- type: "context",
651
- limit: 10,
652
- });
653
- // Get codebase structure
654
- const codeResponse = await ctx.api.post("/api/search", {
655
- collection: `${ctx.collectionPrefix}codebase`,
656
- query: path || "module service controller",
657
- limit: deep ? 20 : 10,
658
- });
659
- const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
660
- const codeFiles = codeResponse.data.results || [];
661
- // Analyze file organization by directory
662
- const filesByDir = {};
663
- codeFiles.forEach((c) => {
664
- const dir = c.file.split("/").slice(0, -1).join("/") || "/";
665
- if (!filesByDir[dir])
666
- filesByDir[dir] = [];
667
- filesByDir[dir].push(c.file.split("/").pop());
668
- });
669
- let result = `# Project Structure Analysis\n\n`;
670
- if (path) {
671
- result += `**Scope:** ${path}\n\n`;
672
- }
673
- result += `## Directory Structure\n`;
674
- Object.entries(filesByDir)
675
- .slice(0, 10)
676
- .forEach(([dir, files]) => {
677
- result += `\n**${dir || "/"}/**\n`;
678
- files.slice(0, 5).forEach((f) => {
679
- result += ` - ${f}\n`;
680
- });
681
- if (files.length > 5) {
682
- result += ` - ... and ${files.length - 5} more\n`;
683
- }
684
- });
685
- result += `\n## Recorded Patterns (${patterns.length})\n`;
686
- if (patterns.length === 0) {
687
- result += `_No patterns recorded yet. Consider documenting your architectural patterns._\n\n`;
688
- }
689
- else {
690
- patterns.forEach((p) => {
691
- result += `- ${p.memory.metadata?.patternName || p.memory.relatedTo}`;
692
- if (p.memory.metadata?.appliesTo) {
693
- result += ` -> ${p.memory.metadata.appliesTo}`;
413
+ const response = await ctx.api.post("/api/memory", {
414
+ projectName: ctx.projectName,
415
+ content: debtContent,
416
+ type: "insight",
417
+ tags: ["tech-debt", `impact-${impact}`],
418
+ relatedTo: title,
419
+ metadata: { debtTitle: title, impact, location },
420
+ });
421
+ const impactEmojis = {
422
+ low: "\uD83D\uDFE2",
423
+ medium: "\uD83D\uDFE1",
424
+ high: "\uD83D\uDFE0",
425
+ critical: "\uD83D\uDD34",
426
+ };
427
+ const emoji = impactEmojis[impact] || "\u26AA";
428
+ return (`${emoji} **Tech Debt Recorded**\n\n` +
429
+ `- **Title:** ${title}\n` +
430
+ `- **Impact:** ${impact}\n` +
431
+ `- **ID:** ${response.data.memory.id}\n` +
432
+ (location ? `- **Location:** ${location}\n` : ""));
433
+ },
434
+ },
435
+ {
436
+ name: "get_tech_debt",
437
+ description: `List technical debt items for ${projectName}.`,
438
+ schema: z.object({
439
+ impact: z.enum(["low", "medium", "high", "critical", "all"]).optional().describe("Filter by impact"),
440
+ limit: z.number().optional().describe("Max results (default: 10)"),
441
+ }),
442
+ annotations: TOOL_ANNOTATIONS["get_tech_debt"],
443
+ handler: async (args, ctx) => {
444
+ const { impact = "all", limit = 10 } = args;
445
+ const response = await ctx.api.post("/api/memory/recall", {
446
+ projectName: ctx.projectName,
447
+ query: "technical debt violation issue",
448
+ type: "insight",
449
+ limit: limit * 2, // Fetch extra to account for filtering
450
+ tag: "tech-debt",
451
+ });
452
+ const results = response.data.results || [];
453
+ const debts = results
454
+ .filter((r) => {
455
+ const isDebt = r.memory.tags?.includes("tech-debt");
456
+ const matchesImpact = impact === "all" ||
457
+ r.memory.metadata?.impact === impact ||
458
+ r.memory.tags?.includes(`impact-${impact}`);
459
+ return isDebt && matchesImpact;
460
+ })
461
+ .slice(0, limit);
462
+ if (debts.length === 0) {
463
+ return `No tech debt found${impact !== "all" ? ` with ${impact} impact` : ""}`;
464
+ }
465
+ const impactEmojis = {
466
+ low: "\uD83D\uDFE2",
467
+ medium: "\uD83D\uDFE1",
468
+ high: "\uD83D\uDFE0",
469
+ critical: "\uD83D\uDD34",
470
+ };
471
+ let result = `# Technical Debt (${debts.length})\n\n`;
472
+ debts.forEach((r, i) => {
473
+ const m = r.memory;
474
+ const debtImpact = m.metadata?.impact || "medium";
475
+ const emoji = impactEmojis[debtImpact] || "\u26AA";
476
+ result += `### ${i + 1}. ${emoji} ${m.metadata?.debtTitle || m.relatedTo || "Tech Debt"}\n`;
477
+ result += `**Impact:** ${debtImpact}`;
478
+ if (m.metadata?.location) {
479
+ result += ` | **Location:** ${m.metadata.location}`;
480
+ }
481
+ result += `\n**ID:** \`${m.id}\`\n\n`;
482
+ // Extract description section
483
+ const descMatch = m.content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
484
+ if (descMatch) {
485
+ result += truncate(descMatch[1].trim(), 200) + "\n\n";
486
+ }
487
+ });
488
+ return result;
489
+ },
490
+ },
491
+ {
492
+ name: "analyze_project_structure",
493
+ description: `Analyze the current project structure and compare with established patterns. Identifies inconsistencies and suggests improvements.`,
494
+ schema: z.object({
495
+ path: z.string().optional().describe("Specific path to analyze (default: entire project)"),
496
+ deep: z.boolean().optional().describe("Perform deep analysis including code patterns (default: false)"),
497
+ }),
498
+ annotations: TOOL_ANNOTATIONS["analyze_project_structure"],
499
+ handler: async (args, ctx) => {
500
+ const { path, deep = false } = args;
501
+ // Get all recorded patterns
502
+ const patternsResponse = await ctx.api.post("/api/memory/recall", {
503
+ projectName: ctx.projectName,
504
+ query: "pattern structure organization",
505
+ type: "context",
506
+ limit: 10,
507
+ tag: "pattern",
508
+ });
509
+ // Get codebase structure
510
+ const codeResponse = await ctx.api.post("/api/search", {
511
+ collection: `${ctx.collectionPrefix}codebase`,
512
+ query: path || "module service controller",
513
+ limit: deep ? 20 : 10,
514
+ });
515
+ const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
516
+ const codeFiles = codeResponse.data.results || [];
517
+ // Analyze file organization by directory
518
+ const filesByDir = {};
519
+ codeFiles.forEach((c) => {
520
+ const dir = c.file.split("/").slice(0, -1).join("/") || "/";
521
+ if (!filesByDir[dir])
522
+ filesByDir[dir] = [];
523
+ filesByDir[dir].push(c.file.split("/").pop());
524
+ });
525
+ let result = `# Project Structure Analysis\n\n`;
526
+ if (path) {
527
+ result += `**Scope:** ${path}\n\n`;
528
+ }
529
+ result += `## Directory Structure\n`;
530
+ Object.entries(filesByDir)
531
+ .slice(0, 10)
532
+ .forEach(([dir, files]) => {
533
+ result += `\n**${dir || "/"}/**\n`;
534
+ files.slice(0, 5).forEach((f) => {
535
+ result += ` - ${f}\n`;
536
+ });
537
+ if (files.length > 5) {
538
+ result += ` - ... and ${files.length - 5} more\n`;
539
+ }
540
+ });
541
+ result += `\n## Recorded Patterns (${patterns.length})\n`;
542
+ if (patterns.length === 0) {
543
+ result += `_No patterns recorded yet. Consider documenting your architectural patterns._\n\n`;
544
+ }
545
+ else {
546
+ patterns.forEach((p) => {
547
+ result += `- ${p.memory.metadata?.patternName || p.memory.relatedTo}`;
548
+ if (p.memory.metadata?.appliesTo) {
549
+ result += ` -> ${p.memory.metadata.appliesTo}`;
550
+ }
551
+ result += "\n";
552
+ });
694
553
  }
695
- result += "\n";
696
- });
697
- }
698
- result += `\n## Recommendations\n`;
699
- if (patterns.length === 0) {
700
- result += `1. **Record patterns** - Use \`record_pattern\` to document how code should be structured\n`;
701
- }
702
- result += `2. **Document decisions** - Use \`record_adr\` for important architectural choices\n`;
703
- result += `3. **Track tech debt** - Use \`record_tech_debt\` for violations and issues\n`;
704
- result += `4. **Validate changes** - Use \`check_architecture\` before committing new code\n`;
705
- return result;
706
- }
707
- // ── Build handlers map ────────────────────────────────────────────────
708
- const handlers = {
709
- record_adr: handleRecordAdr,
710
- get_adrs: handleGetAdrs,
711
- record_pattern: handleRecordPattern,
712
- get_patterns: handleGetPatterns,
713
- check_architecture: handleCheckArchitecture,
714
- suggest_architecture: handleSuggestArchitecture,
715
- record_tech_debt: handleRecordTechDebt,
716
- get_tech_debt: handleGetTechDebt,
717
- analyze_project_structure: handleAnalyzeProjectStructure,
718
- };
719
- return { tools, handlers };
554
+ result += `\n## Recommendations\n`;
555
+ if (patterns.length === 0) {
556
+ result += `1. **Record patterns** - Use \`record_pattern\` to document how code should be structured\n`;
557
+ }
558
+ result += `2. **Document decisions** - Use \`record_adr\` for important architectural choices\n`;
559
+ result += `3. **Track tech debt** - Use \`record_tech_debt\` for violations and issues\n`;
560
+ result += `4. **Validate changes** - Use \`check_architecture\` before committing new code\n`;
561
+ return result;
562
+ },
563
+ },
564
+ ];
720
565
  }