@crowley/rag-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.d.ts +4 -0
- package/dist/api-client.js +19 -0
- package/dist/context-enrichment.d.ts +44 -0
- package/dist/context-enrichment.js +190 -0
- package/dist/formatters.d.ts +33 -0
- package/dist/formatters.js +70 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +109 -0
- package/dist/tool-registry.d.ts +20 -0
- package/dist/tool-registry.js +123 -0
- package/dist/tools/advanced.d.ts +9 -0
- package/dist/tools/advanced.js +315 -0
- package/dist/tools/agents.d.ts +8 -0
- package/dist/tools/agents.js +97 -0
- package/dist/tools/analytics.d.ts +9 -0
- package/dist/tools/analytics.js +261 -0
- package/dist/tools/architecture.d.ts +5 -0
- package/dist/tools/architecture.js +720 -0
- package/dist/tools/ask.d.ts +9 -0
- package/dist/tools/ask.js +256 -0
- package/dist/tools/cache.d.ts +5 -0
- package/dist/tools/cache.js +98 -0
- package/dist/tools/clustering.d.ts +9 -0
- package/dist/tools/clustering.js +251 -0
- package/dist/tools/confluence.d.ts +9 -0
- package/dist/tools/confluence.js +147 -0
- package/dist/tools/database.d.ts +5 -0
- package/dist/tools/database.js +429 -0
- package/dist/tools/feedback.d.ts +9 -0
- package/dist/tools/feedback.js +220 -0
- package/dist/tools/guidelines.d.ts +5 -0
- package/dist/tools/guidelines.js +146 -0
- package/dist/tools/indexing.d.ts +9 -0
- package/dist/tools/indexing.js +129 -0
- package/dist/tools/memory.d.ts +9 -0
- package/dist/tools/memory.js +565 -0
- package/dist/tools/pm.d.ts +9 -0
- package/dist/tools/pm.js +680 -0
- package/dist/tools/review.d.ts +8 -0
- package/dist/tools/review.js +213 -0
- package/dist/tools/search.d.ts +9 -0
- package/dist/tools/search.js +377 -0
- package/dist/tools/session.d.ts +10 -0
- package/dist/tools/session.js +386 -0
- package/dist/tools/suggestions.d.ts +9 -0
- package/dist/tools/suggestions.js +301 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +4 -0
- package/package.json +40 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture tools module - ADRs, patterns, tech debt, and structure analysis.
|
|
3
|
+
*/
|
|
4
|
+
import { truncate } from "../formatters.js";
|
|
5
|
+
export function createArchitectureTools(projectName) {
|
|
6
|
+
const tools = [
|
|
7
|
+
{
|
|
8
|
+
name: "record_adr",
|
|
9
|
+
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}
|
|
260
|
+
|
|
261
|
+
## Status
|
|
262
|
+
${status.toUpperCase()}
|
|
263
|
+
|
|
264
|
+
## Context
|
|
265
|
+
${context}
|
|
266
|
+
|
|
267
|
+
## Decision
|
|
268
|
+
${decision}
|
|
269
|
+
|
|
270
|
+
${consequences ? `## Consequences\n${consequences}\n` : ""}
|
|
271
|
+
${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}
|
|
322
|
+
|
|
323
|
+
## Description
|
|
324
|
+
${description}
|
|
325
|
+
|
|
326
|
+
## Structure
|
|
327
|
+
${structure}
|
|
328
|
+
|
|
329
|
+
${example ? `## Example\n\`\`\`\n${example}\n\`\`\`\n` : ""}
|
|
330
|
+
${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.
|
|
422
|
+
|
|
423
|
+
Code to validate:
|
|
424
|
+
\`\`\`
|
|
425
|
+
${code.slice(0, 2000)}
|
|
426
|
+
\`\`\`
|
|
427
|
+
|
|
428
|
+
Patterns to check against:
|
|
429
|
+
${patternRules || "None recorded"}
|
|
430
|
+
|
|
431
|
+
Architectural Decisions (ADRs):
|
|
432
|
+
${adrRules || "None recorded"}
|
|
433
|
+
|
|
434
|
+
Provide a structured analysis:
|
|
435
|
+
1. List any violations of patterns or ADRs
|
|
436
|
+
2. Rate compliance (1-10)
|
|
437
|
+
3. Specific recommendations for improvements`;
|
|
438
|
+
try {
|
|
439
|
+
const validationResponse = await ctx.api.post("/api/ask", {
|
|
440
|
+
collection: `${ctx.collectionPrefix}codebase`,
|
|
441
|
+
question: validationPrompt,
|
|
442
|
+
});
|
|
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}
|
|
567
|
+
|
|
568
|
+
## Impact
|
|
569
|
+
${impact.toUpperCase()}
|
|
570
|
+
|
|
571
|
+
## Description
|
|
572
|
+
${description}
|
|
573
|
+
|
|
574
|
+
${location ? `## Location\n${location}\n` : ""}
|
|
575
|
+
${suggestedFix ? `## Suggested Fix\n${suggestedFix}\n` : ""}
|
|
576
|
+
${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}`;
|
|
694
|
+
}
|
|
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 };
|
|
720
|
+
}
|