@crowley/rag-mcp 1.0.5 → 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 (49) 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 +46 -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/review.d.ts +2 -2
  41. package/dist/tools/review.js +142 -189
  42. package/dist/tools/search.d.ts +2 -2
  43. package/dist/tools/search.js +230 -305
  44. package/dist/tools/session.d.ts +2 -2
  45. package/dist/tools/session.js +288 -345
  46. package/dist/tools/suggestions.d.ts +2 -2
  47. package/dist/tools/suggestions.js +425 -512
  48. package/dist/types.d.ts +19 -2
  49. 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,57 +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
- tag: "adr",
295
- });
296
- const results = response.data.results || [];
297
- const adrs = results.filter((r) => r.memory.tags?.includes("adr") &&
298
- (status === "all" || r.memory.metadata?.adrStatus === status));
299
- if (adrs.length === 0) {
300
- return `No ADRs found${query ? ` for "${query}"` : ""}`;
301
- }
302
- const statusIcons = {
303
- proposed: "\uD83D\uDFE1",
304
- accepted: "\uD83D\uDFE2",
305
- deprecated: "\uD83D\uDD34",
306
- superseded: "\u26AB",
307
- };
308
- let result = `# Architecture Decision Records (${adrs.length})\n\n`;
309
- adrs.forEach((r, i) => {
310
- const m = r.memory;
311
- const adrStatus = (m.metadata?.adrStatus || "accepted");
312
- const icon = statusIcons[adrStatus] || "\u26AA";
313
- result += `### ${i + 1}. ${icon} ${m.metadata?.adrTitle || m.relatedTo || "ADR"}\n`;
314
- result += `**Status:** ${adrStatus} | **ID:** \`${m.id}\`\n\n`;
315
- result +=
316
- truncate(m.content, 500) + "\n\n";
317
- });
318
- return result;
319
- }
320
- async function handleRecordPattern(args, ctx) {
321
- const { name, description, structure, example, appliesTo, tags = [], } = args;
322
- 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}
323
111
 
324
112
  ## Description
325
113
  ${description}
@@ -329,100 +117,120 @@ ${structure}
329
117
 
330
118
  ${example ? `## Example\n\`\`\`\n${example}\n\`\`\`\n` : ""}
331
119
  ${appliesTo ? `## Applies To\n${appliesTo}` : ""}`;
332
- const response = await ctx.api.post("/api/memory", {
333
- projectName: ctx.projectName,
334
- content: patternContent,
335
- type: "context",
336
- tags: ["pattern", ...tags],
337
- relatedTo: name,
338
- metadata: { patternName: name, appliesTo },
339
- });
340
- return (`# Pattern Recorded\n\n` +
341
- `- **Name:** ${name}\n` +
342
- `- **ID:** ${response.data.memory.id}\n` +
343
- (appliesTo ? `- **Applies To:** ${appliesTo}\n` : "") +
344
- `- **Tags:** ${["pattern", ...tags].join(", ")}`);
345
- }
346
- async function handleGetPatterns(args, ctx) {
347
- const { query, appliesTo, limit = 10, } = args;
348
- const response = await ctx.api.post("/api/memory/recall", {
349
- projectName: ctx.projectName,
350
- query: query || "architectural pattern structure",
351
- type: "context",
352
- limit,
353
- tag: "pattern",
354
- });
355
- const results = response.data.results || [];
356
- const patterns = results.filter((r) => {
357
- const isPattern = r.memory.tags?.includes("pattern");
358
- const matchesAppliesTo = !appliesTo ||
359
- r.memory.metadata?.appliesTo
360
- ?.toLowerCase()
361
- .includes(appliesTo.toLowerCase());
362
- return isPattern && matchesAppliesTo;
363
- });
364
- if (patterns.length === 0) {
365
- return `No patterns found${query ? ` for "${query}"` : ""}`;
366
- }
367
- let result = `# Architectural Patterns (${patterns.length})\n\n`;
368
- patterns.forEach((r, i) => {
369
- const m = r.memory;
370
- result += `### ${i + 1}. ${m.metadata?.patternName || m.relatedTo || "Pattern"}\n`;
371
- if (m.metadata?.appliesTo) {
372
- result += `**Applies to:** ${m.metadata.appliesTo}\n`;
373
- }
374
- result += `**ID:** \`${m.id}\`\n\n`;
375
- result += truncate(m.content, 600) + "\n\n";
376
- });
377
- return result;
378
- }
379
- async function handleCheckArchitecture(args, ctx) {
380
- const { code, filePath, featureDescription } = args;
381
- const patternQuery = filePath || featureDescription || "architectural patterns";
382
- // Get relevant patterns
383
- const patternsResponse = await ctx.api.post("/api/memory/recall", {
384
- projectName: ctx.projectName,
385
- query: patternQuery,
386
- type: "context",
387
- limit: 5,
388
- tag: "pattern",
389
- });
390
- // Get relevant ADRs
391
- const adrsResponse = await ctx.api.post("/api/memory/recall", {
392
- projectName: ctx.projectName,
393
- query: patternQuery,
394
- type: "decision",
395
- limit: 5,
396
- tag: "adr",
397
- });
398
- // Search similar code in codebase
399
- let similarCode = [];
400
- if (code) {
401
- const codeResponse = await ctx.api.post("/api/search", {
402
- collection: `${ctx.collectionPrefix}codebase`,
403
- query: code.slice(0, 500),
404
- limit: 3,
405
- });
406
- similarCode = codeResponse.data.results || [];
407
- }
408
- const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
409
- const adrs = (adrsResponse.data.results || []).filter((r) => r.memory.tags?.includes("adr"));
410
- let result = `# Architecture Check\n\n`;
411
- if (filePath) {
412
- result += `**File:** ${filePath}\n\n`;
413
- }
414
- if (featureDescription) {
415
- result += `**Feature:** ${featureDescription}\n\n`;
416
- }
417
- // If we have code and patterns/ADRs, perform LLM validation
418
- if (code && (patterns.length > 0 || adrs.length > 0)) {
419
- const patternRules = patterns
420
- .map((p) => `Pattern: ${p.memory.metadata?.patternName || p.memory.relatedTo}\nDescription: ${truncate(p.memory.content, 300)}`)
421
- .join("\n\n");
422
- const adrRules = adrs
423
- .map((a) => `ADR: ${a.memory.metadata?.adrTitle || a.memory.relatedTo}\nDecision: ${truncate(a.memory.content, 300)}`)
424
- .join("\n\n");
425
- 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.
426
234
 
427
235
  Code to validate:
428
236
  \`\`\`
@@ -439,137 +247,159 @@ Provide a structured analysis:
439
247
  1. List any violations of patterns or ADRs
440
248
  2. Rate compliance (1-10)
441
249
  3. Specific recommendations for improvements`;
442
- try {
443
- 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", {
444
335
  collection: `${ctx.collectionPrefix}codebase`,
445
- question: validationPrompt,
336
+ query: `${type} ${feature}`,
337
+ limit: 5,
446
338
  });
447
- result += `## Validation Results\n\n`;
448
- result += validationResponse.data.answer;
449
- result += "\n\n";
450
- }
451
- catch (_e) {
452
- // Continue without LLM validation
453
- }
454
- }
455
- result += `## Applicable Patterns (${patterns.length})\n`;
456
- if (patterns.length === 0) {
457
- result += `_No specific patterns recorded for this area._\n\n`;
458
- }
459
- else {
460
- patterns.forEach((p) => {
461
- result += `- **${p.memory.metadata?.patternName || p.memory.relatedTo}**: ${truncate(p.memory.content, 100)}\n`;
462
- });
463
- result += "\n";
464
- }
465
- result += `## Relevant ADRs (${adrs.length})\n`;
466
- if (adrs.length === 0) {
467
- result += `_No relevant architectural decisions found._\n\n`;
468
- }
469
- else {
470
- adrs.forEach((a) => {
471
- result += `- **${a.memory.metadata?.adrTitle || a.memory.relatedTo}** [${a.memory.metadata?.adrStatus || "accepted"}]: ${truncate(a.memory.content, 100)}\n`;
472
- });
473
- result += "\n";
474
- }
475
- if (similarCode.length > 0) {
476
- result += `## Similar Existing Code\n`;
477
- result += `_Review these for consistency:_\n`;
478
- similarCode.forEach((c) => {
479
- result += `- ${c.file}\n`;
480
- });
481
- result += "\n";
482
- }
483
- result += `## Recommendations\n`;
484
- if (patterns.length > 0) {
485
- result += `- Follow the patterns listed above for consistency\n`;
486
- }
487
- if (adrs.length > 0) {
488
- result += `- Ensure compliance with recorded architectural decisions\n`;
489
- }
490
- if (similarCode.length > 0) {
491
- result += `- Check similar code for established conventions\n`;
492
- }
493
- if (patterns.length === 0 && adrs.length === 0) {
494
- result += `- Consider recording patterns/ADRs for this area with \`record_pattern\` and \`record_adr\`\n`;
495
- }
496
- return result;
497
- }
498
- async function handleSuggestArchitecture(args, ctx) {
499
- const { feature, type = "other" } = args;
500
- // Get patterns for this type
501
- const patternsResponse = await ctx.api.post("/api/memory/recall", {
502
- projectName: ctx.projectName,
503
- query: `${type} ${feature} pattern structure`,
504
- type: "context",
505
- limit: 5,
506
- tag: "pattern",
507
- });
508
- // Get relevant ADRs
509
- const adrsResponse = await ctx.api.post("/api/memory/recall", {
510
- projectName: ctx.projectName,
511
- query: `${type} ${feature}`,
512
- type: "decision",
513
- limit: 3,
514
- tag: "adr",
515
- });
516
- // Get similar implementations
517
- const codeResponse = await ctx.api.post("/api/search", {
518
- collection: `${ctx.collectionPrefix}codebase`,
519
- query: `${type} ${feature}`,
520
- limit: 5,
521
- });
522
- const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
523
- const adrs = (adrsResponse.data.results || []).filter((r) => r.memory.tags?.includes("adr"));
524
- const existingCode = codeResponse.data.results || [];
525
- let result = `# Architecture Suggestion: ${feature}\n\n`;
526
- result += `**Type:** ${type}\n\n`;
527
- result += `## Recommended Patterns\n`;
528
- if (patterns.length === 0) {
529
- result += `_No specific patterns recorded. Consider following existing code conventions._\n\n`;
530
- }
531
- else {
532
- patterns.forEach((p) => {
533
- result += `### ${p.memory.metadata?.patternName || p.memory.relatedTo}\n`;
534
- result += truncate(p.memory.content, 400) + "\n\n";
535
- });
536
- }
537
- result += `## Relevant Decisions (ADRs)\n`;
538
- if (adrs.length === 0) {
539
- result += `_No specific ADRs found for this area._\n\n`;
540
- }
541
- else {
542
- adrs.forEach((a) => {
543
- result += `- **${a.memory.metadata?.adrTitle || a.memory.relatedTo}**: `;
544
- const decision = a.memory.content.match(/## Decision\n([\s\S]*?)(?=\n##|$)/);
545
- result += decision
546
- ? truncate(decision[1].trim(), 150)
547
- : "See full ADR";
548
- result += "\n";
549
- });
550
- result += "\n";
551
- }
552
- result += `## Reference Implementations\n`;
553
- if (existingCode.length === 0) {
554
- result += `_No similar implementations found._\n\n`;
555
- }
556
- else {
557
- result += `_Study these for conventions:_\n`;
558
- existingCode.forEach((c) => {
559
- result += `- \`${c.file}\`\n`;
560
- });
561
- result += "\n";
562
- }
563
- result += `## Next Steps\n`;
564
- result += `1. Review the patterns and ADRs above\n`;
565
- result += `2. Study reference implementations for conventions\n`;
566
- result += `3. Create your implementation following established structure\n`;
567
- result += `4. Use \`check_architecture\` to validate before committing\n`;
568
- return result;
569
- }
570
- async function handleRecordTechDebt(args, ctx) {
571
- const { title, description, location, impact, suggestedFix, relatedAdr } = args;
572
- 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}
573
403
 
574
404
  ## Impact
575
405
  ${impact.toUpperCase()}
@@ -580,149 +410,156 @@ ${description}
580
410
  ${location ? `## Location\n${location}\n` : ""}
581
411
  ${suggestedFix ? `## Suggested Fix\n${suggestedFix}\n` : ""}
582
412
  ${relatedAdr ? `## Related ADR\n${relatedAdr}` : ""}`;
583
- const response = await ctx.api.post("/api/memory", {
584
- projectName: ctx.projectName,
585
- content: debtContent,
586
- type: "insight",
587
- tags: ["tech-debt", `impact-${impact}`],
588
- relatedTo: title,
589
- metadata: { debtTitle: title, impact, location },
590
- });
591
- const impactEmojis = {
592
- low: "\uD83D\uDFE2",
593
- medium: "\uD83D\uDFE1",
594
- high: "\uD83D\uDFE0",
595
- critical: "\uD83D\uDD34",
596
- };
597
- const emoji = impactEmojis[impact] || "\u26AA";
598
- return (`${emoji} **Tech Debt Recorded**\n\n` +
599
- `- **Title:** ${title}\n` +
600
- `- **Impact:** ${impact}\n` +
601
- `- **ID:** ${response.data.memory.id}\n` +
602
- (location ? `- **Location:** ${location}\n` : ""));
603
- }
604
- async function handleGetTechDebt(args, ctx) {
605
- const { impact = "all", limit = 10 } = args;
606
- const response = await ctx.api.post("/api/memory/recall", {
607
- projectName: ctx.projectName,
608
- query: "technical debt violation issue",
609
- type: "insight",
610
- limit: limit * 2, // Fetch extra to account for filtering
611
- tag: "tech-debt",
612
- });
613
- const results = response.data.results || [];
614
- const debts = results
615
- .filter((r) => {
616
- const isDebt = r.memory.tags?.includes("tech-debt");
617
- const matchesImpact = impact === "all" ||
618
- r.memory.metadata?.impact === impact ||
619
- r.memory.tags?.includes(`impact-${impact}`);
620
- return isDebt && matchesImpact;
621
- })
622
- .slice(0, limit);
623
- if (debts.length === 0) {
624
- return `No tech debt found${impact !== "all" ? ` with ${impact} impact` : ""}`;
625
- }
626
- const impactEmojis = {
627
- low: "\uD83D\uDFE2",
628
- medium: "\uD83D\uDFE1",
629
- high: "\uD83D\uDFE0",
630
- critical: "\uD83D\uDD34",
631
- };
632
- let result = `# Technical Debt (${debts.length})\n\n`;
633
- debts.forEach((r, i) => {
634
- const m = r.memory;
635
- const debtImpact = m.metadata?.impact || "medium";
636
- const emoji = impactEmojis[debtImpact] || "\u26AA";
637
- result += `### ${i + 1}. ${emoji} ${m.metadata?.debtTitle || m.relatedTo || "Tech Debt"}\n`;
638
- result += `**Impact:** ${debtImpact}`;
639
- if (m.metadata?.location) {
640
- result += ` | **Location:** ${m.metadata.location}`;
641
- }
642
- result += `\n**ID:** \`${m.id}\`\n\n`;
643
- // Extract description section
644
- const descMatch = m.content.match(/## Description\n([\s\S]*?)(?=\n##|$)/);
645
- if (descMatch) {
646
- result += truncate(descMatch[1].trim(), 200) + "\n\n";
647
- }
648
- });
649
- return result;
650
- }
651
- async function handleAnalyzeProjectStructure(args, ctx) {
652
- const { path, deep = false } = args;
653
- // Get all recorded patterns
654
- const patternsResponse = await ctx.api.post("/api/memory/recall", {
655
- projectName: ctx.projectName,
656
- query: "pattern structure organization",
657
- type: "context",
658
- limit: 10,
659
- tag: "pattern",
660
- });
661
- // Get codebase structure
662
- const codeResponse = await ctx.api.post("/api/search", {
663
- collection: `${ctx.collectionPrefix}codebase`,
664
- query: path || "module service controller",
665
- limit: deep ? 20 : 10,
666
- });
667
- const patterns = (patternsResponse.data.results || []).filter((r) => r.memory.tags?.includes("pattern"));
668
- const codeFiles = codeResponse.data.results || [];
669
- // Analyze file organization by directory
670
- const filesByDir = {};
671
- codeFiles.forEach((c) => {
672
- const dir = c.file.split("/").slice(0, -1).join("/") || "/";
673
- if (!filesByDir[dir])
674
- filesByDir[dir] = [];
675
- filesByDir[dir].push(c.file.split("/").pop());
676
- });
677
- let result = `# Project Structure Analysis\n\n`;
678
- if (path) {
679
- result += `**Scope:** ${path}\n\n`;
680
- }
681
- result += `## Directory Structure\n`;
682
- Object.entries(filesByDir)
683
- .slice(0, 10)
684
- .forEach(([dir, files]) => {
685
- result += `\n**${dir || "/"}/**\n`;
686
- files.slice(0, 5).forEach((f) => {
687
- result += ` - ${f}\n`;
688
- });
689
- if (files.length > 5) {
690
- result += ` - ... and ${files.length - 5} more\n`;
691
- }
692
- });
693
- result += `\n## Recorded Patterns (${patterns.length})\n`;
694
- if (patterns.length === 0) {
695
- result += `_No patterns recorded yet. Consider documenting your architectural patterns._\n\n`;
696
- }
697
- else {
698
- patterns.forEach((p) => {
699
- result += `- ${p.memory.metadata?.patternName || p.memory.relatedTo}`;
700
- if (p.memory.metadata?.appliesTo) {
701
- 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
+ });
702
553
  }
703
- result += "\n";
704
- });
705
- }
706
- result += `\n## Recommendations\n`;
707
- if (patterns.length === 0) {
708
- result += `1. **Record patterns** - Use \`record_pattern\` to document how code should be structured\n`;
709
- }
710
- result += `2. **Document decisions** - Use \`record_adr\` for important architectural choices\n`;
711
- result += `3. **Track tech debt** - Use \`record_tech_debt\` for violations and issues\n`;
712
- result += `4. **Validate changes** - Use \`check_architecture\` before committing new code\n`;
713
- return result;
714
- }
715
- // ── Build handlers map ────────────────────────────────────────────────
716
- const handlers = {
717
- record_adr: handleRecordAdr,
718
- get_adrs: handleGetAdrs,
719
- record_pattern: handleRecordPattern,
720
- get_patterns: handleGetPatterns,
721
- check_architecture: handleCheckArchitecture,
722
- suggest_architecture: handleSuggestArchitecture,
723
- record_tech_debt: handleRecordTechDebt,
724
- get_tech_debt: handleGetTechDebt,
725
- analyze_project_structure: handleAnalyzeProjectStructure,
726
- };
727
- 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
+ ];
728
565
  }