@noyrax/5d-database-plugin 0.1.8 → 0.1.11-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MCP_SERVER_SETUP.md +14 -0
- package/README.md +49 -3
- package/SETUP_NEW_PROJECT.md +245 -14
- package/out/api/adr-api.d.ts +15 -2
- package/out/api/adr-api.d.ts.map +1 -1
- package/out/api/adr-api.js +98 -4
- package/out/api/adr-api.js.map +1 -1
- package/out/api/index.d.ts +1 -0
- package/out/api/index.d.ts.map +1 -1
- package/out/api/index.js +3 -1
- package/out/api/index.js.map +1 -1
- package/out/api/ingestion-api.d.ts +42 -0
- package/out/api/ingestion-api.d.ts.map +1 -0
- package/out/api/ingestion-api.js +148 -0
- package/out/api/ingestion-api.js.map +1 -0
- package/out/api/module-api.d.ts +11 -1
- package/out/api/module-api.d.ts.map +1 -1
- package/out/api/module-api.js +54 -2
- package/out/api/module-api.js.map +1 -1
- package/out/cli/ingest-cli.js +43 -19
- package/out/cli/ingest-cli.js.map +1 -1
- package/out/cli/merge-workspaces-cli.d.ts +3 -0
- package/out/cli/merge-workspaces-cli.d.ts.map +1 -0
- package/out/cli/merge-workspaces-cli.js +199 -0
- package/out/cli/merge-workspaces-cli.js.map +1 -0
- package/out/cli/tool-cli.js +59 -4
- package/out/cli/tool-cli.js.map +1 -1
- package/out/core/chromadb-vector-database.d.ts.map +1 -1
- package/out/core/chromadb-vector-database.js +6 -2
- package/out/core/chromadb-vector-database.js.map +1 -1
- package/out/core/migration-manager.d.ts +42 -2
- package/out/core/migration-manager.d.ts.map +1 -1
- package/out/core/migration-manager.js +218 -11
- package/out/core/migration-manager.js.map +1 -1
- package/out/core/multi-db-manager.d.ts +10 -0
- package/out/core/multi-db-manager.d.ts.map +1 -1
- package/out/core/multi-db-manager.js +51 -0
- package/out/core/multi-db-manager.js.map +1 -1
- package/out/ingestors/adr-ingestor.d.ts +7 -0
- package/out/ingestors/adr-ingestor.d.ts.map +1 -1
- package/out/ingestors/adr-ingestor.js +41 -9
- package/out/ingestors/adr-ingestor.js.map +1 -1
- package/out/mcp/server.d.ts +3 -0
- package/out/mcp/server.d.ts.map +1 -1
- package/out/mcp/server.js +106 -2
- package/out/mcp/server.js.map +1 -1
- package/out/mcp/tools/adr-generator.d.ts +123 -0
- package/out/mcp/tools/adr-generator.d.ts.map +1 -0
- package/out/mcp/tools/adr-generator.js +1113 -0
- package/out/mcp/tools/adr-generator.js.map +1 -0
- package/out/mcp/tools/gap-analysis.d.ts +6 -2
- package/out/mcp/tools/gap-analysis.d.ts.map +1 -1
- package/out/mcp/tools/gap-analysis.js +32 -5
- package/out/mcp/tools/gap-analysis.js.map +1 -1
- package/out/repositories/module-repository.d.ts +5 -0
- package/out/repositories/module-repository.d.ts.map +1 -1
- package/out/repositories/module-repository.js +24 -0
- package/out/repositories/module-repository.js.map +1 -1
- package/out/services/adr-context-builder.d.ts +70 -0
- package/out/services/adr-context-builder.d.ts.map +1 -0
- package/out/services/adr-context-builder.js +141 -0
- package/out/services/adr-context-builder.js.map +1 -0
- package/out/services/adr-pattern-analyzer.d.ts +75 -0
- package/out/services/adr-pattern-analyzer.d.ts.map +1 -0
- package/out/services/adr-pattern-analyzer.js +339 -0
- package/out/services/adr-pattern-analyzer.js.map +1 -0
- package/out/services/adr-reasoning-service.d.ts +63 -0
- package/out/services/adr-reasoning-service.d.ts.map +1 -0
- package/out/services/adr-reasoning-service.js +760 -0
- package/out/services/adr-reasoning-service.js.map +1 -0
- package/out/services/navigation-builder.d.ts.map +1 -1
- package/out/services/navigation-builder.js +70 -11
- package/out/services/navigation-builder.js.map +1 -1
- package/out/services/noyrax-integration-service.d.ts +51 -0
- package/out/services/noyrax-integration-service.d.ts.map +1 -0
- package/out/services/noyrax-integration-service.js +215 -0
- package/out/services/noyrax-integration-service.js.map +1 -0
- package/out/services/semantic-pattern-matcher.d.ts +61 -0
- package/out/services/semantic-pattern-matcher.d.ts.map +1 -0
- package/out/services/semantic-pattern-matcher.js +183 -0
- package/out/services/semantic-pattern-matcher.js.map +1 -0
- package/out/services/workflow-orchestrator.d.ts +63 -0
- package/out/services/workflow-orchestrator.d.ts.map +1 -0
- package/out/services/workflow-orchestrator.js +111 -0
- package/out/services/workflow-orchestrator.js.map +1 -0
- package/out/ui/commands.d.ts.map +1 -1
- package/out/ui/commands.js +112 -1
- package/out/ui/commands.js.map +1 -1
- package/out/ui/database-explorer.d.ts.map +1 -1
- package/out/ui/database-explorer.js +47 -1
- package/out/ui/database-explorer.js.map +1 -1
- package/package.json +22 -4
|
@@ -0,0 +1,1113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AdrGeneratorTool = void 0;
|
|
37
|
+
const cross_dimension_api_1 = require("../../api/cross-dimension-api");
|
|
38
|
+
const module_api_1 = require("../../api/module-api");
|
|
39
|
+
const embedding_generator_1 = require("../../embedding/embedding-generator");
|
|
40
|
+
const adr_pattern_analyzer_1 = require("../../services/adr-pattern-analyzer");
|
|
41
|
+
const semantic_pattern_matcher_1 = require("../../services/semantic-pattern-matcher");
|
|
42
|
+
const adr_context_builder_1 = require("../../services/adr-context-builder");
|
|
43
|
+
const adr_reasoning_service_1 = require("../../services/adr-reasoning-service");
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
/**
|
|
47
|
+
* MCP Tool: adr_generator
|
|
48
|
+
* Deterministically reconstructs ADRs from 5D dimensions.
|
|
49
|
+
* Uses similar modules, existing ADRs, and patterns to reconstruct implicit knowledge.
|
|
50
|
+
*/
|
|
51
|
+
class AdrGeneratorTool {
|
|
52
|
+
constructor(dbManager, idMapper, workspaceRoot) {
|
|
53
|
+
this.dbManager = dbManager;
|
|
54
|
+
this.idMapper = idMapper;
|
|
55
|
+
this.workspaceRoot = workspaceRoot;
|
|
56
|
+
this.crossDimensionApi = new cross_dimension_api_1.CrossDimensionApi(dbManager, idMapper);
|
|
57
|
+
this.moduleApi = new module_api_1.ModuleApi(dbManager);
|
|
58
|
+
this.patternAnalyzer = new adr_pattern_analyzer_1.AdrPatternAnalyzer(dbManager);
|
|
59
|
+
const embeddingGenerator = new embedding_generator_1.EmbeddingGenerator();
|
|
60
|
+
this.patternMatcher = new semantic_pattern_matcher_1.SemanticPatternMatcher(dbManager, idMapper, embeddingGenerator);
|
|
61
|
+
this.contextBuilder = new adr_context_builder_1.AdrContextBuilder(dbManager, idMapper, this.patternMatcher);
|
|
62
|
+
this.reasoningService = new adr_reasoning_service_1.AdrReasoningService();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Executes the adr_generator tool.
|
|
66
|
+
* Reconstructs ADRs for modules without ADRs.
|
|
67
|
+
*
|
|
68
|
+
* @param args Arguments: pluginId, minDependencies (default: 5), dryRun (default: false), limit (default: 10), useLLM (default: false), llmModel (default: gpt-4o-mini)
|
|
69
|
+
* @returns JSON string with generation results
|
|
70
|
+
*/
|
|
71
|
+
async execute(args) {
|
|
72
|
+
// Default: Only generate ADRs for modules with at least 5 dependencies
|
|
73
|
+
// This prevents system overload from too many simple modules
|
|
74
|
+
const minDeps = args.minDependencies !== undefined ? args.minDependencies : 5;
|
|
75
|
+
const dryRun = args.dryRun !== undefined ? args.dryRun : false;
|
|
76
|
+
const limit = args.limit || 10;
|
|
77
|
+
// Get architectural view to find gaps
|
|
78
|
+
const architecturalView = await this.crossDimensionApi.buildArchitecturalView(args.pluginId);
|
|
79
|
+
// Find modules without ADRs
|
|
80
|
+
// Filter: must have at least minDeps dependencies (default: 3)
|
|
81
|
+
// This prevents system overload from generating ADRs for too many simple modules
|
|
82
|
+
const modulesWithoutAdrs = architecturalView
|
|
83
|
+
.filter(view => view.dependencies.length >= minDeps && view.adrs.length === 0)
|
|
84
|
+
.sort((a, b) => b.dependencies.length - a.dependencies.length)
|
|
85
|
+
.slice(0, limit);
|
|
86
|
+
const results = [];
|
|
87
|
+
// Find next available ADR number
|
|
88
|
+
const nextAdrNumber = await this.findNextAdrNumber(args.pluginId);
|
|
89
|
+
for (let i = 0; i < modulesWithoutAdrs.length; i++) {
|
|
90
|
+
const view = modulesWithoutAdrs[i];
|
|
91
|
+
const module = view.module;
|
|
92
|
+
const adrNumber = this.formatAdrNumber(nextAdrNumber + i);
|
|
93
|
+
const adrFileName = `${adrNumber}-${this.generateSlug(module.file_path)}.md`;
|
|
94
|
+
const adrPath = path.join(this.workspaceRoot, 'docs', 'adr', adrFileName);
|
|
95
|
+
// Reconstruct ADR content (with or without LLM)
|
|
96
|
+
const useLLM = args.useLLM === true && this.reasoningService.isAvailable();
|
|
97
|
+
const adrContent = await this.reconstructAdr(module, args.pluginId, adrNumber, useLLM);
|
|
98
|
+
if (!dryRun) {
|
|
99
|
+
// Ensure docs/adr directory exists
|
|
100
|
+
const adrDir = path.dirname(adrPath);
|
|
101
|
+
if (!fs.existsSync(adrDir)) {
|
|
102
|
+
fs.mkdirSync(adrDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
// Write ADR file
|
|
105
|
+
fs.writeFileSync(adrPath, adrContent, 'utf-8');
|
|
106
|
+
}
|
|
107
|
+
results.push({
|
|
108
|
+
module_path: module.file_path,
|
|
109
|
+
adr_number: adrNumber,
|
|
110
|
+
adr_file: adrFileName,
|
|
111
|
+
generated: !dryRun,
|
|
112
|
+
content_preview: adrContent.substring(0, 500) + '...'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return JSON.stringify({
|
|
116
|
+
summary: {
|
|
117
|
+
modules_analyzed: architecturalView.length,
|
|
118
|
+
modules_without_adrs: modulesWithoutAdrs.length,
|
|
119
|
+
adrs_generated: results.length,
|
|
120
|
+
dry_run: dryRun,
|
|
121
|
+
next_adr_number: nextAdrNumber
|
|
122
|
+
},
|
|
123
|
+
generated_adrs: results,
|
|
124
|
+
recommendations: dryRun
|
|
125
|
+
? [`Run without --dry-run to actually generate ${results.length} ADR(s)`]
|
|
126
|
+
: [`Generated ${results.length} ADR(s) deterministically from 5D dimensions.`]
|
|
127
|
+
}, null, 2);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Calculates module complexity based on dependencies, symbols, and incoming dependencies.
|
|
131
|
+
* Returns 'simple', 'medium', or 'complex'.
|
|
132
|
+
*
|
|
133
|
+
* Rules:
|
|
134
|
+
* - >= 20 dependencies → automatically 'complex'
|
|
135
|
+
* - >= 15 incoming dependencies → automatically 'complex'
|
|
136
|
+
* - >= 30 symbols → automatically 'complex'
|
|
137
|
+
* - Otherwise: scoring system
|
|
138
|
+
*/
|
|
139
|
+
calculateComplexity(context) {
|
|
140
|
+
const depCount = context.dependencies.length;
|
|
141
|
+
const incomingCount = context.incomingDependencies.length;
|
|
142
|
+
const symbolCount = context.symbols.length;
|
|
143
|
+
// Automatic 'complex' for very high numbers
|
|
144
|
+
if (depCount >= 20)
|
|
145
|
+
return 'complex';
|
|
146
|
+
if (incomingCount >= 15)
|
|
147
|
+
return 'complex';
|
|
148
|
+
if (symbolCount >= 30)
|
|
149
|
+
return 'complex';
|
|
150
|
+
// Scoring: 0-2 = simple, 3-5 = medium, 6+ = complex
|
|
151
|
+
let score = 0;
|
|
152
|
+
// Dependencies scoring (more granular)
|
|
153
|
+
if (depCount >= 5)
|
|
154
|
+
score++;
|
|
155
|
+
if (depCount >= 10)
|
|
156
|
+
score++;
|
|
157
|
+
if (depCount >= 15)
|
|
158
|
+
score++; // Additional point for high dependency count
|
|
159
|
+
// Incoming dependencies scoring
|
|
160
|
+
if (incomingCount >= 3)
|
|
161
|
+
score++;
|
|
162
|
+
if (incomingCount >= 8)
|
|
163
|
+
score++; // Increased threshold
|
|
164
|
+
// Symbols scoring
|
|
165
|
+
if (symbolCount >= 5)
|
|
166
|
+
score++;
|
|
167
|
+
if (symbolCount >= 15)
|
|
168
|
+
score++;
|
|
169
|
+
if (symbolCount >= 25)
|
|
170
|
+
score++; // Additional point for very high symbol count
|
|
171
|
+
if (score <= 2)
|
|
172
|
+
return 'simple';
|
|
173
|
+
if (score <= 5)
|
|
174
|
+
return 'medium';
|
|
175
|
+
return 'complex';
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Reconstructs an ADR from 5D dimensions (with optional LLM for "Why").
|
|
179
|
+
*/
|
|
180
|
+
async reconstructAdr(module, pluginId, adrNumber, useLLM = false) {
|
|
181
|
+
// Build complete context from all dimensions
|
|
182
|
+
const context = await this.contextBuilder.buildModuleContext(module, pluginId);
|
|
183
|
+
// Calculate complexity
|
|
184
|
+
const complexity = this.calculateComplexity(context);
|
|
185
|
+
// Find similar ADR patterns
|
|
186
|
+
const similarPatterns = await this.patternAnalyzer.findSimilarAdrPatterns(module, pluginId);
|
|
187
|
+
// Get ADRs for similar modules
|
|
188
|
+
const similarAdrs = await this.patternMatcher.findAdrsForSimilarModules(module, pluginId);
|
|
189
|
+
// Extract title from module or similar ADRs
|
|
190
|
+
const title = this.extractTitle(module, similarAdrs);
|
|
191
|
+
// Use LLM for "Why" reconstruction if available and requested
|
|
192
|
+
let reasoning = null;
|
|
193
|
+
if (useLLM) {
|
|
194
|
+
if (!this.reasoningService.isAvailable()) {
|
|
195
|
+
console.warn(`[AdrGeneratorTool] LLM not available (OpenAI API key not configured). Falling back to deterministic method.`);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
try {
|
|
199
|
+
const similarAdrList = similarAdrs.map(sa => sa.adr);
|
|
200
|
+
reasoning = await this.reasoningService.reconstructWhy(context, similarAdrList, context.patterns, complexity);
|
|
201
|
+
console.log(`[AdrGeneratorTool] Successfully reconstructed reasoning using LLM for ${module.file_path}`);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
205
|
+
console.warn(`[AdrGeneratorTool] LLM reasoning failed, falling back to deterministic method: ${errorMsg}`);
|
|
206
|
+
// Fall through to deterministic method
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Reconstruct each section (use LLM reasoning if available)
|
|
211
|
+
const status = this.reconstructStatus();
|
|
212
|
+
const contextSection = await this.reconstructContext(context, similarAdrs, reasoning, complexity);
|
|
213
|
+
const decisionSection = await this.reconstructDecision(context, similarAdrs, reasoning, complexity);
|
|
214
|
+
const consequencesSection = await this.reconstructConsequences(context, reasoning, complexity);
|
|
215
|
+
const refactoringSection = this.reconstructRefactoringRecommendations(reasoning, complexity);
|
|
216
|
+
const verweiseSection = this.reconstructVerweise(context, similarAdrs);
|
|
217
|
+
// Build ADR content
|
|
218
|
+
let adrContent = `# ADR-${adrNumber}: ${title}
|
|
219
|
+
|
|
220
|
+
## Status
|
|
221
|
+
|
|
222
|
+
${status}
|
|
223
|
+
|
|
224
|
+
## Kontext
|
|
225
|
+
|
|
226
|
+
${contextSection}
|
|
227
|
+
|
|
228
|
+
## Entscheidung
|
|
229
|
+
|
|
230
|
+
${decisionSection}
|
|
231
|
+
|
|
232
|
+
## Konsequenzen
|
|
233
|
+
|
|
234
|
+
${consequencesSection}
|
|
235
|
+
|
|
236
|
+
## Verweise
|
|
237
|
+
|
|
238
|
+
${verweiseSection}
|
|
239
|
+
`;
|
|
240
|
+
// Add refactoring recommendations if available
|
|
241
|
+
if (refactoringSection) {
|
|
242
|
+
adrContent += `\n${refactoringSection}\n`;
|
|
243
|
+
}
|
|
244
|
+
// Add implementation section for complex modules (similar to ADR-038)
|
|
245
|
+
if (complexity === 'complex') {
|
|
246
|
+
const implementationSection = this.reconstructImplementation(context, reasoning);
|
|
247
|
+
if (implementationSection) {
|
|
248
|
+
adrContent += `\n${implementationSection}\n`;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return adrContent;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Reconstructs Refactoring Recommendations section from LLM reasoning.
|
|
255
|
+
* Skips recommendations for simple modules.
|
|
256
|
+
*/
|
|
257
|
+
reconstructRefactoringRecommendations(reasoning, complexity = 'complex') {
|
|
258
|
+
// Skip refactoring recommendations for simple modules
|
|
259
|
+
if (complexity === 'simple') {
|
|
260
|
+
return '';
|
|
261
|
+
}
|
|
262
|
+
if (!reasoning || !reasoning.refactoring_recommendations || reasoning.refactoring_recommendations.length === 0) {
|
|
263
|
+
return '';
|
|
264
|
+
}
|
|
265
|
+
let refactoringText = `## Refactoring-Empfehlungen\n\n`;
|
|
266
|
+
// Limit recommendations based on complexity
|
|
267
|
+
const maxRecommendations = complexity === 'medium' ? 2 : reasoning.refactoring_recommendations.length;
|
|
268
|
+
for (const recommendation of reasoning.refactoring_recommendations.slice(0, maxRecommendations)) {
|
|
269
|
+
refactoringText += `- ${recommendation}\n`;
|
|
270
|
+
}
|
|
271
|
+
return refactoringText;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Reconstructs Status section.
|
|
275
|
+
*/
|
|
276
|
+
reconstructStatus() {
|
|
277
|
+
return 'Proposed';
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Reconstructs Context section from module context and similar ADRs.
|
|
281
|
+
* Uses LLM reasoning if available for problem identification.
|
|
282
|
+
* Adjusts detail level based on module complexity.
|
|
283
|
+
*/
|
|
284
|
+
async reconstructContext(context, similarAdrs, reasoning = null, complexity = 'complex') {
|
|
285
|
+
const module = context.module;
|
|
286
|
+
const moduleName = path.basename(module.file_path, path.extname(module.file_path));
|
|
287
|
+
const moduleDir = path.dirname(module.file_path);
|
|
288
|
+
// Parse module documentation for detailed information
|
|
289
|
+
const moduleDoc = this.parseModuleDocumentation(module.content_markdown);
|
|
290
|
+
let contextText = `Dieses Modul (\`${module.file_path}\`) ist Teil der Architektur.`;
|
|
291
|
+
// Add problems from LLM reasoning if available
|
|
292
|
+
// Limit problems based on complexity
|
|
293
|
+
if (reasoning && reasoning.problems && reasoning.problems.length > 0) {
|
|
294
|
+
contextText += `\n\n**Problem:**\n\n`;
|
|
295
|
+
const maxProblems = complexity === 'simple' ? 2 : complexity === 'medium' ? 3 : reasoning.problems.length;
|
|
296
|
+
for (let i = 0; i < Math.min(reasoning.problems.length, maxProblems); i++) {
|
|
297
|
+
contextText += `**Problem ${i + 1}:** ${reasoning.problems[i]}\n\n`;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Add module purpose from documentation
|
|
301
|
+
if (moduleDoc.purpose) {
|
|
302
|
+
contextText += `**Zweck:** ${moduleDoc.purpose}\n\n`;
|
|
303
|
+
}
|
|
304
|
+
// Add detailed class/interface information (limit based on complexity)
|
|
305
|
+
if (moduleDoc.classes.length > 0) {
|
|
306
|
+
const maxClasses = complexity === 'simple' ? 2 : complexity === 'medium' ? 3 : 5;
|
|
307
|
+
contextText += `\n\n**Hauptklassen/Interfaces:**`;
|
|
308
|
+
for (const cls of moduleDoc.classes.slice(0, maxClasses)) {
|
|
309
|
+
contextText += `\n- \`${cls.name}\` (${cls.role || 'other'})`;
|
|
310
|
+
if (cls.methodCount > 0 && complexity !== 'simple') {
|
|
311
|
+
contextText += ` - ${cls.methodCount} Methoden`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Add key functions/methods (limit based on complexity)
|
|
316
|
+
if (moduleDoc.keyFunctions.length > 0) {
|
|
317
|
+
const maxFunctions = complexity === 'simple' ? 2 : complexity === 'medium' ? 5 : 10;
|
|
318
|
+
contextText += `\n\n**Hauptfunktionen:**`;
|
|
319
|
+
for (const func of moduleDoc.keyFunctions.slice(0, maxFunctions)) {
|
|
320
|
+
contextText += `\n- \`${func.name}()\` - ${func.role || 'other'}`;
|
|
321
|
+
if (func.signature && complexity !== 'simple') {
|
|
322
|
+
const sigPreview = func.signature.length > 80 ? func.signature.substring(0, 80) + '...' : func.signature;
|
|
323
|
+
contextText += `\n \`${sigPreview}\``;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Add detailed dependencies analysis (simplified for simple modules)
|
|
328
|
+
if (context.dependencies.length > 0) {
|
|
329
|
+
if (complexity === 'simple') {
|
|
330
|
+
// For simple modules, just show count
|
|
331
|
+
contextText += `\n\n**Dependencies (${context.dependencies.length}):**`;
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
contextText += `\n\n**Dependencies (${context.dependencies.length}):**`;
|
|
335
|
+
// Group by dependency category
|
|
336
|
+
const coreDeps = [];
|
|
337
|
+
const apiDeps = [];
|
|
338
|
+
const serviceDeps = [];
|
|
339
|
+
const otherDeps = [];
|
|
340
|
+
for (const dep of context.dependencies) {
|
|
341
|
+
const toModule = dep.to_module || '';
|
|
342
|
+
if (toModule.includes('/core/')) {
|
|
343
|
+
coreDeps.push(toModule);
|
|
344
|
+
}
|
|
345
|
+
else if (toModule.includes('/api/')) {
|
|
346
|
+
apiDeps.push(toModule);
|
|
347
|
+
}
|
|
348
|
+
else if (toModule.includes('/services/')) {
|
|
349
|
+
serviceDeps.push(toModule);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
otherDeps.push(toModule);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const maxDeps = complexity === 'medium' ? 5 : 10;
|
|
356
|
+
if (coreDeps.length > 0) {
|
|
357
|
+
contextText += `\n\n**Core Dependencies (${coreDeps.length}):**`;
|
|
358
|
+
for (const dep of coreDeps.slice(0, maxDeps)) {
|
|
359
|
+
contextText += `\n- \`${dep}\``;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (apiDeps.length > 0) {
|
|
363
|
+
contextText += `\n\n**API Dependencies (${apiDeps.length}):**`;
|
|
364
|
+
for (const dep of apiDeps.slice(0, maxDeps)) {
|
|
365
|
+
contextText += `\n- \`${dep}\``;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (serviceDeps.length > 0) {
|
|
369
|
+
contextText += `\n\n**Service Dependencies (${serviceDeps.length}):**`;
|
|
370
|
+
for (const dep of serviceDeps.slice(0, maxDeps)) {
|
|
371
|
+
contextText += `\n- \`${dep}\``;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (otherDeps.length > 0 && (complexity === 'complex' || otherDeps.length <= 10)) {
|
|
375
|
+
contextText += `\n\n**Weitere Dependencies (${otherDeps.length}):**`;
|
|
376
|
+
const maxOtherDeps = complexity === 'medium' ? 5 : otherDeps.length;
|
|
377
|
+
for (const dep of otherDeps.slice(0, maxOtherDeps)) {
|
|
378
|
+
contextText += `\n- \`${dep}\``;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Add incoming dependencies (who uses this module) - skip for simple modules
|
|
384
|
+
if (context.incomingDependencies.length > 0 && complexity !== 'simple') {
|
|
385
|
+
const maxUsers = complexity === 'medium' ? 5 : 10;
|
|
386
|
+
contextText += `\n\n**Wird genutzt von (${context.incomingDependencies.length} Modulen):**`;
|
|
387
|
+
const uniqueUsers = new Set(context.incomingDependencies.map((d) => d.from_module || d.from));
|
|
388
|
+
for (const user of Array.from(uniqueUsers).slice(0, maxUsers)) {
|
|
389
|
+
contextText += `\n- \`${user}\``;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Add pattern information with evidence (skip for simple modules)
|
|
393
|
+
if (context.patterns.length > 0 && complexity !== 'simple') {
|
|
394
|
+
const primaryPattern = context.patterns[0];
|
|
395
|
+
contextText += `\n\n**Erkanntes Pattern:** ${primaryPattern.pattern} (${primaryPattern.confidence} confidence)`;
|
|
396
|
+
if (primaryPattern.evidence.length > 0 && complexity === 'complex') {
|
|
397
|
+
contextText += `\n\n**Evidence:**`;
|
|
398
|
+
for (const evidence of primaryPattern.evidence.slice(0, 5)) {
|
|
399
|
+
contextText += `\n- ${evidence}`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// Add similar modules with context (skip for simple modules)
|
|
404
|
+
if (context.similarModules.length > 0 && complexity !== 'simple') {
|
|
405
|
+
contextText += `\n\n**Ähnliche Module:**`;
|
|
406
|
+
const maxSimilar = complexity === 'medium' ? 2 : 3;
|
|
407
|
+
for (const similar of context.similarModules.slice(0, maxSimilar)) {
|
|
408
|
+
contextText += `\n- \`${similar.module.file_path}\` (Similarity: ${(similar.score * 100).toFixed(0)}%)`;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// Add symbols summary with details (simplified for simple modules)
|
|
412
|
+
if (context.symbols.length > 0 && complexity !== 'simple') {
|
|
413
|
+
contextText += `\n\n**Symbole (${context.symbols.length}):**`;
|
|
414
|
+
const symbolKinds = new Map();
|
|
415
|
+
for (const symbol of context.symbols) {
|
|
416
|
+
const kind = symbol.kind || 'unknown';
|
|
417
|
+
if (!symbolKinds.has(kind)) {
|
|
418
|
+
symbolKinds.set(kind, []);
|
|
419
|
+
}
|
|
420
|
+
const sig = symbol.signature_json ? JSON.parse(symbol.signature_json) : null;
|
|
421
|
+
symbolKinds.get(kind).push({
|
|
422
|
+
name: symbol.name || 'unknown',
|
|
423
|
+
signature: sig ? JSON.stringify(sig).substring(0, 100) : undefined
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
const maxSymbolsPerKind = complexity === 'medium' ? 3 : 5;
|
|
427
|
+
for (const [kind, symbols] of symbolKinds.entries()) {
|
|
428
|
+
contextText += `\n\n**${kind} (${symbols.length}):**`;
|
|
429
|
+
for (const sym of symbols.slice(0, maxSymbolsPerKind)) {
|
|
430
|
+
contextText += `\n- \`${sym.name}\``;
|
|
431
|
+
}
|
|
432
|
+
if (symbols.length > maxSymbolsPerKind) {
|
|
433
|
+
contextText += `\n- ... und ${symbols.length - maxSymbolsPerKind} weitere`;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return contextText;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Parses module documentation markdown to extract structured information.
|
|
441
|
+
*/
|
|
442
|
+
parseModuleDocumentation(content) {
|
|
443
|
+
const lines = content.split('\n');
|
|
444
|
+
const result = {
|
|
445
|
+
purpose: null,
|
|
446
|
+
classes: [],
|
|
447
|
+
keyFunctions: []
|
|
448
|
+
};
|
|
449
|
+
let currentClass = null;
|
|
450
|
+
let methodCount = 0;
|
|
451
|
+
for (let i = 0; i < lines.length; i++) {
|
|
452
|
+
const line = lines[i];
|
|
453
|
+
// Extract purpose from first paragraph after title
|
|
454
|
+
if (line.startsWith('# Modul:') && i + 1 < lines.length) {
|
|
455
|
+
const nextLine = lines[i + 1].trim();
|
|
456
|
+
if (nextLine && !nextLine.startsWith('#')) {
|
|
457
|
+
result.purpose = nextLine.substring(0, 200);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// Extract class information
|
|
461
|
+
const classMatch = line.match(/^###\s+(?:class|interface):\s*(.+)$/);
|
|
462
|
+
if (classMatch) {
|
|
463
|
+
if (currentClass) {
|
|
464
|
+
currentClass.methodCount = methodCount;
|
|
465
|
+
result.classes.push(currentClass);
|
|
466
|
+
}
|
|
467
|
+
const className = classMatch[1].trim();
|
|
468
|
+
const roleMatch = line.match(/Rolle:\s*([^,]+)/);
|
|
469
|
+
const role = roleMatch ? roleMatch[1].trim() : null;
|
|
470
|
+
currentClass = { name: className, role, methodCount: 0 };
|
|
471
|
+
methodCount = 0;
|
|
472
|
+
}
|
|
473
|
+
// Extract method information
|
|
474
|
+
const methodMatch = line.match(/^###\s+(?:method|function):\s*(.+)$/);
|
|
475
|
+
if (methodMatch) {
|
|
476
|
+
methodCount++;
|
|
477
|
+
const methodName = methodMatch[1].trim();
|
|
478
|
+
const roleMatch = line.match(/Rolle:\s*([^,]+)/);
|
|
479
|
+
const role = roleMatch ? roleMatch[1].trim() : null;
|
|
480
|
+
// Try to find signature in next lines
|
|
481
|
+
let signature = null;
|
|
482
|
+
for (let j = i + 1; j < Math.min(i + 10, lines.length); j++) {
|
|
483
|
+
if (lines[j].includes('Signatur:') || lines[j].includes('```')) {
|
|
484
|
+
const sigMatch = lines[j].match(/Signatur:\s*`(.+)`/) ||
|
|
485
|
+
lines[j + 1]?.match(/```ts\n(.+)\n```/);
|
|
486
|
+
if (sigMatch) {
|
|
487
|
+
signature = sigMatch[1].trim();
|
|
488
|
+
break;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
result.keyFunctions.push({ name: methodName, role, signature });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (currentClass) {
|
|
496
|
+
currentClass.methodCount = methodCount;
|
|
497
|
+
result.classes.push(currentClass);
|
|
498
|
+
}
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Reconstructs Decision section from patterns and similar ADRs.
|
|
503
|
+
* Uses LLM reasoning if available for decision and rationale.
|
|
504
|
+
* Adjusts detail level based on module complexity.
|
|
505
|
+
*/
|
|
506
|
+
async reconstructDecision(context, similarAdrs, reasoning = null, complexity = 'complex') {
|
|
507
|
+
let decisionText = '';
|
|
508
|
+
// Use LLM reasoning if available
|
|
509
|
+
if (reasoning && reasoning.decision) {
|
|
510
|
+
decisionText += reasoning.decision;
|
|
511
|
+
if (reasoning.rationale) {
|
|
512
|
+
decisionText += `\n\n**Begründung:**\n\n${reasoning.rationale}`;
|
|
513
|
+
}
|
|
514
|
+
// Limit alternatives based on complexity
|
|
515
|
+
if (reasoning.alternatives && reasoning.alternatives.length > 0) {
|
|
516
|
+
const maxAlternatives = complexity === 'simple' ? 1 : complexity === 'medium' ? 2 : reasoning.alternatives.length;
|
|
517
|
+
if (maxAlternatives > 0) {
|
|
518
|
+
decisionText += `\n\n**Alternativen, die erwogen wurden:**\n\n`;
|
|
519
|
+
for (const alt of reasoning.alternatives.slice(0, maxAlternatives)) {
|
|
520
|
+
decisionText += `- ${alt}\n`;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
return decisionText;
|
|
525
|
+
}
|
|
526
|
+
// Fallback to deterministic method
|
|
527
|
+
// Parse module documentation for decision context
|
|
528
|
+
const moduleDoc = this.parseModuleDocumentation(context.module.content_markdown);
|
|
529
|
+
// Use similar ADRs as template with deeper analysis
|
|
530
|
+
if (similarAdrs.length > 0) {
|
|
531
|
+
const bestMatch = similarAdrs[0];
|
|
532
|
+
const similarAdrContent = bestMatch.adr.content_markdown;
|
|
533
|
+
// Extract full Context and Decision sections from similar ADR
|
|
534
|
+
const contextMatch = similarAdrContent.match(/##\s+Kontext\s*\n([\s\S]*?)(?=##\s+(?:Entscheidung|Decision)|$)/i);
|
|
535
|
+
const decisionMatch = similarAdrContent.match(/##\s+Entscheidung\s*\n([\s\S]*?)(?=##|$)/i);
|
|
536
|
+
if (decisionMatch) {
|
|
537
|
+
decisionText += `Basierend auf ähnlichem Modul \`${bestMatch.module.file_path}\` (ADR-${bestMatch.adr.adr_number}):\n\n`;
|
|
538
|
+
// Extract decision structure from similar ADR
|
|
539
|
+
const similarDecision = decisionMatch[1].trim();
|
|
540
|
+
// Analyze decision structure (subsections, implementation details)
|
|
541
|
+
const decisionStructure = this.analyzeDecisionStructure(similarDecision);
|
|
542
|
+
// Adapt decision with module-specific details
|
|
543
|
+
decisionText += this.adaptDecisionTextDeep(similarDecision, decisionStructure, context.module, bestMatch.module, moduleDoc, context);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
decisionText += this.generateDecisionFromPatterns(context, moduleDoc);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
decisionText += this.generateDecisionFromPatterns(context, moduleDoc);
|
|
551
|
+
}
|
|
552
|
+
return decisionText;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Analyzes decision structure from similar ADR.
|
|
556
|
+
*/
|
|
557
|
+
analyzeDecisionStructure(decisionText) {
|
|
558
|
+
const lines = decisionText.split('\n');
|
|
559
|
+
const result = {
|
|
560
|
+
subsections: [],
|
|
561
|
+
implementationDetails: [],
|
|
562
|
+
rationale: null
|
|
563
|
+
};
|
|
564
|
+
let currentSubsection = null;
|
|
565
|
+
let currentContent = [];
|
|
566
|
+
for (const line of lines) {
|
|
567
|
+
// Check for subsection (### or ####)
|
|
568
|
+
const subsectionMatch = line.match(/^###+\s+(.+)$/);
|
|
569
|
+
if (subsectionMatch) {
|
|
570
|
+
if (currentSubsection) {
|
|
571
|
+
currentSubsection.content = currentContent.join('\n').trim();
|
|
572
|
+
result.subsections.push(currentSubsection);
|
|
573
|
+
}
|
|
574
|
+
currentSubsection = { title: subsectionMatch[1].trim(), content: '' };
|
|
575
|
+
currentContent = [];
|
|
576
|
+
}
|
|
577
|
+
else if (currentSubsection) {
|
|
578
|
+
currentContent.push(line);
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
// Main rationale
|
|
582
|
+
if (line.trim() && !line.startsWith('#')) {
|
|
583
|
+
if (!result.rationale) {
|
|
584
|
+
result.rationale = '';
|
|
585
|
+
}
|
|
586
|
+
result.rationale += line + '\n';
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// Extract implementation details (file paths, function names)
|
|
590
|
+
const fileMatch = line.match(/`([^`]+\.ts)`/);
|
|
591
|
+
if (fileMatch) {
|
|
592
|
+
result.implementationDetails.push(fileMatch[1]);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (currentSubsection) {
|
|
596
|
+
currentSubsection.content = currentContent.join('\n').trim();
|
|
597
|
+
result.subsections.push(currentSubsection);
|
|
598
|
+
}
|
|
599
|
+
if (result.rationale) {
|
|
600
|
+
result.rationale = result.rationale.trim();
|
|
601
|
+
}
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Deep adaptation of decision text with module-specific details.
|
|
606
|
+
*/
|
|
607
|
+
adaptDecisionTextDeep(similarDecision, decisionStructure, targetModule, similarModule, moduleDoc, context) {
|
|
608
|
+
let adapted = similarDecision;
|
|
609
|
+
// Replace module paths
|
|
610
|
+
adapted = adapted.replace(new RegExp(similarModule.file_path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), targetModule.file_path);
|
|
611
|
+
// Replace module names
|
|
612
|
+
const similarName = path.basename(similarModule.file_path, path.extname(similarModule.file_path));
|
|
613
|
+
const targetName = path.basename(targetModule.file_path, path.extname(targetModule.file_path));
|
|
614
|
+
adapted = adapted.replace(new RegExp(similarName, 'gi'), targetName);
|
|
615
|
+
// Add module-specific implementation details
|
|
616
|
+
if (moduleDoc.classes.length > 0) {
|
|
617
|
+
adapted += `\n\n**Implementierung:**\n\n`;
|
|
618
|
+
for (const cls of moduleDoc.classes.slice(0, 3)) {
|
|
619
|
+
adapted += `- **\`${cls.name}\`** (${cls.role || 'other'})`;
|
|
620
|
+
if (cls.methodCount > 0) {
|
|
621
|
+
adapted += ` - ${cls.methodCount} Methoden`;
|
|
622
|
+
}
|
|
623
|
+
adapted += '\n';
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// Add key functions
|
|
627
|
+
if (moduleDoc.keyFunctions.length > 0) {
|
|
628
|
+
adapted += `\n\n**Hauptfunktionen:**\n\n`;
|
|
629
|
+
for (const func of moduleDoc.keyFunctions.slice(0, 5)) {
|
|
630
|
+
adapted += `- \`${func.name}()\``;
|
|
631
|
+
if (func.role) {
|
|
632
|
+
adapted += ` - ${func.role}`;
|
|
633
|
+
}
|
|
634
|
+
adapted += '\n';
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Add dependency rationale
|
|
638
|
+
if (context.dependencies.length > 0) {
|
|
639
|
+
const coreDeps = context.dependencies.filter((d) => (d.to_module || '').includes('/core/'));
|
|
640
|
+
const apiDeps = context.dependencies.filter((d) => (d.to_module || '').includes('/api/'));
|
|
641
|
+
if (coreDeps.length > 0 || apiDeps.length > 0) {
|
|
642
|
+
adapted += `\n\n**Dependency-Struktur:**\n\n`;
|
|
643
|
+
if (coreDeps.length > 0) {
|
|
644
|
+
adapted += `- Nutzt ${coreDeps.length} Core-Module für Basis-Funktionalität\n`;
|
|
645
|
+
}
|
|
646
|
+
if (apiDeps.length > 0) {
|
|
647
|
+
adapted += `- Nutzt ${apiDeps.length} API-Module für Datenzugriff\n`;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return adapted;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Adapts decision text from similar ADR to target module.
|
|
655
|
+
*/
|
|
656
|
+
adaptDecisionText(similarDecision, targetModule, similarModule) {
|
|
657
|
+
// Replace module paths
|
|
658
|
+
let adapted = similarDecision.replace(new RegExp(similarModule.file_path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), targetModule.file_path);
|
|
659
|
+
// Replace module names
|
|
660
|
+
const similarName = path.basename(similarModule.file_path, path.extname(similarModule.file_path));
|
|
661
|
+
const targetName = path.basename(targetModule.file_path, path.extname(targetModule.file_path));
|
|
662
|
+
adapted = adapted.replace(new RegExp(similarName, 'gi'), targetName);
|
|
663
|
+
return adapted;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Generates decision text from patterns with module-specific details.
|
|
667
|
+
*/
|
|
668
|
+
generateDecisionFromPatterns(context, moduleDoc) {
|
|
669
|
+
let decisionText = '';
|
|
670
|
+
if (context.patterns.length > 0) {
|
|
671
|
+
const primaryPattern = context.patterns[0];
|
|
672
|
+
decisionText += `**Pattern:** ${primaryPattern.pattern}\n\n`;
|
|
673
|
+
// Generate decision based on pattern type with module details
|
|
674
|
+
if (primaryPattern.pattern.includes('Repository')) {
|
|
675
|
+
decisionText += 'Repository Pattern wurde gewählt für Datenbank-Zugriff und Abstraktion der Datenpersistenz.\n\n';
|
|
676
|
+
if (moduleDoc.classes.length > 0) {
|
|
677
|
+
decisionText += `**Implementierung:**\n`;
|
|
678
|
+
decisionText += `- \`${moduleDoc.classes[0].name}\` Klasse implementiert Repository-Interface\n`;
|
|
679
|
+
if (moduleDoc.classes[0].methodCount > 0) {
|
|
680
|
+
decisionText += `- ${moduleDoc.classes[0].methodCount} Methoden für CRUD-Operationen\n`;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else if (primaryPattern.pattern.includes('API')) {
|
|
685
|
+
decisionText += 'API Layer wurde gewählt für öffentliche Schnittstelle und Abstraktion der internen Implementierung.\n\n';
|
|
686
|
+
if (moduleDoc.keyFunctions.length > 0) {
|
|
687
|
+
decisionText += `**Public API:**\n`;
|
|
688
|
+
for (const func of moduleDoc.keyFunctions.slice(0, 5)) {
|
|
689
|
+
decisionText += `- \`${func.name}()\` - ${func.role || 'public method'}\n`;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
else if (primaryPattern.pattern.includes('Service')) {
|
|
694
|
+
decisionText += 'Service Layer wurde gewählt für Geschäftslogik und Orchestrierung.\n\n';
|
|
695
|
+
if (moduleDoc.classes.length > 0) {
|
|
696
|
+
decisionText += `**Service-Klasse:**\n`;
|
|
697
|
+
decisionText += `- \`${moduleDoc.classes[0].name}\` orchestriert Geschäftslogik\n`;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else if (primaryPattern.pattern.includes('Builder')) {
|
|
701
|
+
decisionText += 'Builder Pattern wurde gewählt für komplexe Objekt-Konstruktion.\n\n';
|
|
702
|
+
if (moduleDoc.classes.length > 0) {
|
|
703
|
+
decisionText += `**Builder-Klasse:**\n`;
|
|
704
|
+
decisionText += `- \`${moduleDoc.classes[0].name}\` ermöglicht schrittweise Objekt-Konstruktion\n`;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
else if (primaryPattern.pattern.includes('UI') || primaryPattern.pattern.includes('View')) {
|
|
708
|
+
decisionText += 'UI-Komponente wurde implementiert für Benutzerinteraktion.\n\n';
|
|
709
|
+
if (moduleDoc.classes.length > 0) {
|
|
710
|
+
decisionText += `**UI-Klasse:**\n`;
|
|
711
|
+
decisionText += `- \`${moduleDoc.classes[0].name}\` stellt UI-Funktionalität bereit\n`;
|
|
712
|
+
if (moduleDoc.classes[0].methodCount > 0) {
|
|
713
|
+
decisionText += `- ${moduleDoc.classes[0].methodCount} Methoden für UI-Operationen\n`;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
decisionText += `${primaryPattern.pattern} wurde implementiert basierend auf Architektur-Anforderungen.\n\n`;
|
|
719
|
+
if (moduleDoc.purpose) {
|
|
720
|
+
decisionText += `**Zweck:** ${moduleDoc.purpose}\n\n`;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
// Add evidence from pattern
|
|
724
|
+
if (primaryPattern.evidence.length > 0) {
|
|
725
|
+
decisionText += `**Begründung:**\n`;
|
|
726
|
+
for (const evidence of primaryPattern.evidence.slice(0, 3)) {
|
|
727
|
+
decisionText += `- ${evidence}\n`;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
// Fallback: Generate from module structure
|
|
733
|
+
decisionText += 'Dieses Modul wurde implementiert als Teil der System-Architektur.\n\n';
|
|
734
|
+
if (moduleDoc.purpose) {
|
|
735
|
+
decisionText += `**Zweck:** ${moduleDoc.purpose}\n\n`;
|
|
736
|
+
}
|
|
737
|
+
if (moduleDoc.classes.length > 0) {
|
|
738
|
+
decisionText += `**Implementierung:**\n`;
|
|
739
|
+
for (const cls of moduleDoc.classes.slice(0, 3)) {
|
|
740
|
+
decisionText += `- \`${cls.name}\` Klasse`;
|
|
741
|
+
if (cls.role) {
|
|
742
|
+
decisionText += ` (${cls.role})`;
|
|
743
|
+
}
|
|
744
|
+
if (cls.methodCount > 0) {
|
|
745
|
+
decisionText += ` - ${cls.methodCount} Methoden`;
|
|
746
|
+
}
|
|
747
|
+
decisionText += '\n';
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (context.dependencies.length > 0) {
|
|
751
|
+
const coreDeps = context.dependencies.filter((d) => (d.to_module || '').includes('/core/'));
|
|
752
|
+
const apiDeps = context.dependencies.filter((d) => (d.to_module || '').includes('/api/'));
|
|
753
|
+
decisionText += `\n**Dependencies:**\n`;
|
|
754
|
+
if (coreDeps.length > 0) {
|
|
755
|
+
decisionText += `- ${coreDeps.length} Core-Module für Basis-Funktionalität\n`;
|
|
756
|
+
}
|
|
757
|
+
if (apiDeps.length > 0) {
|
|
758
|
+
decisionText += `- ${apiDeps.length} API-Module für Datenzugriff\n`;
|
|
759
|
+
}
|
|
760
|
+
if (context.dependencies.length > coreDeps.length + apiDeps.length) {
|
|
761
|
+
decisionText += `- ${context.dependencies.length - coreDeps.length - apiDeps.length} weitere Module\n`;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (context.incomingDependencies.length > 0) {
|
|
765
|
+
decisionText += `\n**Nutzung:** Wird von ${context.incomingDependencies.length} anderen Modulen genutzt.\n`;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return decisionText;
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Reconstructs Consequences section from dependencies and context.
|
|
772
|
+
* Uses LLM reasoning if available for trade-offs.
|
|
773
|
+
* Adjusts detail level based on module complexity.
|
|
774
|
+
* For complex modules, uses structured format with ✅ and ⚠️ (similar to ADR-038).
|
|
775
|
+
*/
|
|
776
|
+
async reconstructConsequences(context, reasoning = null, complexity = 'complex') {
|
|
777
|
+
// For complex modules, use structured format like ADR-038
|
|
778
|
+
if (complexity === 'complex') {
|
|
779
|
+
let consequencesText = '### Vorteile\n\n';
|
|
780
|
+
// Use LLM reasoning if available
|
|
781
|
+
if (reasoning && reasoning.tradeoffs) {
|
|
782
|
+
const positive = reasoning.tradeoffs.positive || [];
|
|
783
|
+
const negative = reasoning.tradeoffs.negative || [];
|
|
784
|
+
if (positive.length > 0) {
|
|
785
|
+
consequencesText += positive.map((p) => `✅ ${p}`).join('\n');
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
consequencesText += '✅ Keine spezifischen Vorteile identifiziert\n';
|
|
789
|
+
}
|
|
790
|
+
consequencesText += '\n\n### Nachteile\n\n';
|
|
791
|
+
if (negative.length > 0) {
|
|
792
|
+
consequencesText += negative.map((n) => `⚠️ ${n}`).join('\n');
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
consequencesText += '⚠️ Keine spezifischen Nachteile identifiziert\n';
|
|
796
|
+
}
|
|
797
|
+
// Don't add technical details here - they're in the Implementation section
|
|
798
|
+
return consequencesText;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
// For simple/medium modules, use simpler format
|
|
802
|
+
let consequencesText = '### Positive\n\n';
|
|
803
|
+
// Use LLM reasoning if available
|
|
804
|
+
if (reasoning && reasoning.tradeoffs) {
|
|
805
|
+
const positive = reasoning.tradeoffs.positive || [];
|
|
806
|
+
const negative = reasoning.tradeoffs.negative || [];
|
|
807
|
+
// Limit based on complexity
|
|
808
|
+
const maxPositive = complexity === 'simple' ? 2 : complexity === 'medium' ? 3 : positive.length;
|
|
809
|
+
const maxNegative = complexity === 'simple' ? 1 : complexity === 'medium' ? 2 : negative.length;
|
|
810
|
+
if (positive.length > 0) {
|
|
811
|
+
consequencesText += positive.slice(0, maxPositive).map((p) => `- ${p}`).join('\n');
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
consequencesText += '- Keine spezifischen Vorteile identifiziert\n';
|
|
815
|
+
}
|
|
816
|
+
consequencesText += '\n\n### Negative\n\n';
|
|
817
|
+
if (negative.length > 0) {
|
|
818
|
+
consequencesText += negative.slice(0, maxNegative).map((n) => `- ${n}`).join('\n');
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
if (complexity !== 'simple') {
|
|
822
|
+
consequencesText += '- Keine spezifischen Nachteile identifiziert\n';
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return consequencesText;
|
|
826
|
+
}
|
|
827
|
+
// Fallback to deterministic method
|
|
828
|
+
const moduleDoc = this.parseModuleDocumentation(context.module.content_markdown);
|
|
829
|
+
const positive = [];
|
|
830
|
+
const negative = [];
|
|
831
|
+
// Positive: Based on incoming dependencies (who uses this module?) - skip for simple modules
|
|
832
|
+
if (context.incomingDependencies.length > 0 && complexity !== 'simple') {
|
|
833
|
+
const uniqueUsers = new Set(context.incomingDependencies.map((d) => d.from_module || d.from));
|
|
834
|
+
positive.push(`Wird von ${uniqueUsers.size} Modul(en) genutzt - zentrale Funktionalität`);
|
|
835
|
+
// Show which modules use it (only for medium/complex)
|
|
836
|
+
if (uniqueUsers.size <= 5) {
|
|
837
|
+
positive.push(` - Genutzt von: ${Array.from(uniqueUsers).map(u => `\`${u}\``).join(', ')}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// Positive: Based on module structure
|
|
841
|
+
if (moduleDoc.classes.length > 0) {
|
|
842
|
+
const publicClasses = moduleDoc.classes.filter(c => c.role === 'service-api' || c.role === 'public');
|
|
843
|
+
if (publicClasses.length > 0) {
|
|
844
|
+
positive.push(`Klare öffentliche API durch ${publicClasses.length} öffentliche Klasse(n)`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (moduleDoc.keyFunctions.length > 0) {
|
|
848
|
+
const publicFunctions = moduleDoc.keyFunctions.filter(f => f.role === 'service-api' || f.role === 'public');
|
|
849
|
+
if (publicFunctions.length > 0) {
|
|
850
|
+
positive.push(`${publicFunctions.length} öffentliche Funktion(en) für externe Nutzung`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
// Positive: Based on patterns
|
|
854
|
+
if (context.patterns.length > 0) {
|
|
855
|
+
const primaryPattern = context.patterns[0];
|
|
856
|
+
if (primaryPattern.pattern.includes('Repository')) {
|
|
857
|
+
positive.push('Abstraktion der Datenpersistenz');
|
|
858
|
+
positive.push('Testbarkeit durch Mocking');
|
|
859
|
+
positive.push('Einheitliche Datenzugriffs-Schnittstelle');
|
|
860
|
+
}
|
|
861
|
+
else if (primaryPattern.pattern.includes('API')) {
|
|
862
|
+
positive.push('Klare öffentliche Schnittstelle');
|
|
863
|
+
positive.push('Abstraktion der internen Implementierung');
|
|
864
|
+
positive.push('Stabile API für externe Nutzer');
|
|
865
|
+
}
|
|
866
|
+
else if (primaryPattern.pattern.includes('Service')) {
|
|
867
|
+
positive.push('Zentrale Geschäftslogik');
|
|
868
|
+
positive.push('Wiederverwendbarkeit');
|
|
869
|
+
positive.push('Orchestrierung komplexer Operationen');
|
|
870
|
+
}
|
|
871
|
+
else if (primaryPattern.pattern.includes('UI') || primaryPattern.pattern.includes('View')) {
|
|
872
|
+
positive.push('Benutzerfreundliche Oberfläche');
|
|
873
|
+
positive.push('Zentrale UI-Logik');
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
// Positive: Based on symbol count and structure
|
|
877
|
+
if (context.symbols.length > 0) {
|
|
878
|
+
const methodCount = context.symbols.filter((s) => (s.kind || '').includes('method') || (s.kind || '').includes('function')).length;
|
|
879
|
+
if (methodCount > 10) {
|
|
880
|
+
positive.push(`Umfangreiche Funktionalität (${methodCount} Methoden/Funktionen)`);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
// Negative: Based on dependencies count and structure
|
|
884
|
+
if (context.dependencies.length > 10) {
|
|
885
|
+
negative.push(`Viele Dependencies (${context.dependencies.length}) - erhöhte Komplexität und Kopplung`);
|
|
886
|
+
// Analyze dependency depth
|
|
887
|
+
const coreDeps = context.dependencies.filter((d) => (d.to_module || '').includes('/core/'));
|
|
888
|
+
const apiDeps = context.dependencies.filter((d) => (d.to_module || '').includes('/api/'));
|
|
889
|
+
const serviceDeps = context.dependencies.filter((d) => (d.to_module || '').includes('/services/'));
|
|
890
|
+
if (coreDeps.length + apiDeps.length + serviceDeps.length > 10) {
|
|
891
|
+
negative.push(`Tiefe Dependency-Kette (${coreDeps.length} Core, ${apiDeps.length} API, ${serviceDeps.length} Service)`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
else if (context.dependencies.length > 5) {
|
|
895
|
+
negative.push(`Mehrere Dependencies (${context.dependencies.length}) - mittlere Komplexität`);
|
|
896
|
+
}
|
|
897
|
+
// Negative: Based on patterns
|
|
898
|
+
if (context.patterns.length > 0) {
|
|
899
|
+
const primaryPattern = context.patterns[0];
|
|
900
|
+
if (primaryPattern.pattern.includes('Repository') || primaryPattern.pattern.includes('API')) {
|
|
901
|
+
negative.push('Zusätzliche Abstraktionsschicht - Overhead bei einfachen Operationen');
|
|
902
|
+
}
|
|
903
|
+
if (primaryPattern.pattern.includes('UI') || primaryPattern.pattern.includes('View')) {
|
|
904
|
+
negative.push('UI-Komponente - Abhängigkeit von VS Code APIs');
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
// Negative: Based on module size
|
|
908
|
+
if (context.symbols.length > 50) {
|
|
909
|
+
negative.push(`Große Modul-Größe (${context.symbols.length} Symbole) - mögliche Wartungsprobleme`);
|
|
910
|
+
}
|
|
911
|
+
// Negative: Based on incoming dependencies (high coupling)
|
|
912
|
+
if (context.incomingDependencies.length > 10) {
|
|
913
|
+
negative.push(`Hohe Kopplung - ${context.incomingDependencies.length} Module abhängig - Änderungen haben große Auswirkungen`);
|
|
914
|
+
}
|
|
915
|
+
if (positive.length === 0) {
|
|
916
|
+
positive.push('Modulare Architektur');
|
|
917
|
+
if (moduleDoc.classes.length > 0 && complexity !== 'simple') {
|
|
918
|
+
positive.push(`Strukturierte Implementierung (${moduleDoc.classes.length} Klasse(n))`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (negative.length === 0 && complexity !== 'simple') {
|
|
922
|
+
negative.push('Keine bekannten signifikanten Nachteile');
|
|
923
|
+
}
|
|
924
|
+
// Limit based on complexity
|
|
925
|
+
const maxPositive = complexity === 'simple' ? 2 : complexity === 'medium' ? 3 : positive.length;
|
|
926
|
+
const maxNegative = complexity === 'simple' ? 1 : complexity === 'medium' ? 2 : negative.length;
|
|
927
|
+
consequencesText += positive.slice(0, maxPositive).map(p => `- ${p}`).join('\n');
|
|
928
|
+
consequencesText += '\n\n### Negative\n\n';
|
|
929
|
+
if (maxNegative > 0) {
|
|
930
|
+
consequencesText += negative.slice(0, maxNegative).map(n => `- ${n}`).join('\n');
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
consequencesText += '- Keine spezifischen Nachteile\n';
|
|
934
|
+
}
|
|
935
|
+
return consequencesText;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Reconstructs Verweise section from dependencies and similar ADRs.
|
|
939
|
+
*/
|
|
940
|
+
reconstructVerweise(context, similarAdrs) {
|
|
941
|
+
const verweise = [];
|
|
942
|
+
// Add similar ADRs
|
|
943
|
+
if (similarAdrs.length > 0) {
|
|
944
|
+
verweise.push('**Ähnliche ADRs:**');
|
|
945
|
+
for (const similar of similarAdrs.slice(0, 3)) {
|
|
946
|
+
verweise.push(`- ADR-${similar.adr.adr_number}: ${similar.adr.title} (\`${similar.module.file_path}\`)`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
// Add dependencies
|
|
950
|
+
if (context.dependencies.length > 0) {
|
|
951
|
+
verweise.push('\n**Verwandte Module (Dependencies):**');
|
|
952
|
+
const uniqueDeps = new Set(context.dependencies.map((d) => d.to_module || d.to));
|
|
953
|
+
for (const dep of Array.from(uniqueDeps).slice(0, 10)) {
|
|
954
|
+
verweise.push(`- \`${dep}\``);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
// Add incoming dependencies
|
|
958
|
+
if (context.incomingDependencies.length > 0) {
|
|
959
|
+
verweise.push('\n**Nutzer dieses Moduls:**');
|
|
960
|
+
const uniqueUsers = new Set(context.incomingDependencies.map((d) => d.from_module || d.from));
|
|
961
|
+
for (const user of Array.from(uniqueUsers).slice(0, 5)) {
|
|
962
|
+
verweise.push(`- \`${user}\``);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return verweise.length > 0 ? verweise.join('\n') : 'Keine Verweise verfügbar.';
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Reconstructs Implementation section for complex modules (similar to ADR-038).
|
|
969
|
+
* Includes technical details, affected components, and implementation notes.
|
|
970
|
+
*/
|
|
971
|
+
reconstructImplementation(context, reasoning = null) {
|
|
972
|
+
let implementationText = '## Implementierung\n\n';
|
|
973
|
+
// Affected components
|
|
974
|
+
const affectedComponents = [];
|
|
975
|
+
affectedComponents.push(context.module.file_path);
|
|
976
|
+
// Add ALL dependencies as affected components (no limit for complex modules)
|
|
977
|
+
for (const dep of context.dependencies) {
|
|
978
|
+
const depPath = typeof dep === 'string' ? dep : (dep.to_module || dep.to);
|
|
979
|
+
if (depPath && !affectedComponents.includes(depPath)) {
|
|
980
|
+
affectedComponents.push(depPath);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
if (affectedComponents.length > 0) {
|
|
984
|
+
implementationText += '**Betroffene Komponenten:**\n';
|
|
985
|
+
for (const component of affectedComponents) {
|
|
986
|
+
implementationText += `- \`${component}\`\n`;
|
|
987
|
+
}
|
|
988
|
+
implementationText += '\n';
|
|
989
|
+
}
|
|
990
|
+
// Technical details
|
|
991
|
+
implementationText += '**Technische Details:**\n\n';
|
|
992
|
+
implementationText += `**Implementierung:**\n`;
|
|
993
|
+
// Detect VS Code Runtime as incoming dependency if 'vscode' is imported
|
|
994
|
+
let incomingDepsCount = context.incomingDependencies.length;
|
|
995
|
+
const hasVscodeImport = context.dependencies.some((dep) => {
|
|
996
|
+
const depPath = typeof dep === 'string' ? dep : (dep.to_module || dep.to);
|
|
997
|
+
return depPath === 'vscode' || depPath?.includes('vscode');
|
|
998
|
+
});
|
|
999
|
+
if (hasVscodeImport && incomingDepsCount === 0) {
|
|
1000
|
+
incomingDepsCount = 1; // VS Code Runtime
|
|
1001
|
+
}
|
|
1002
|
+
// Extract symbol count from module documentation if not in DB
|
|
1003
|
+
let symbolCount = context.symbols.length;
|
|
1004
|
+
if (symbolCount === 0) {
|
|
1005
|
+
// Try to extract from module documentation
|
|
1006
|
+
const moduleDoc = this.parseModuleDocumentation(context.module.content_markdown);
|
|
1007
|
+
symbolCount = moduleDoc.classes.length + moduleDoc.keyFunctions.length;
|
|
1008
|
+
}
|
|
1009
|
+
// Add implementation details
|
|
1010
|
+
implementationText += `- Modul: \`${context.module.file_path}\`\n`;
|
|
1011
|
+
implementationText += `- Dependencies: ${context.dependencies.length} (ausgehend)\n`;
|
|
1012
|
+
if (hasVscodeImport && context.incomingDependencies.length === 0) {
|
|
1013
|
+
implementationText += `- Incoming Dependencies: ${incomingDepsCount} (VS Code Runtime)\n`;
|
|
1014
|
+
}
|
|
1015
|
+
else {
|
|
1016
|
+
implementationText += `- Incoming Dependencies: ${incomingDepsCount}\n`;
|
|
1017
|
+
}
|
|
1018
|
+
implementationText += `- Symbole: ${symbolCount}\n`;
|
|
1019
|
+
// Add pattern information
|
|
1020
|
+
if (context.patterns.length > 0) {
|
|
1021
|
+
const primaryPattern = context.patterns[0];
|
|
1022
|
+
implementationText += `- Pattern: ${primaryPattern.pattern} (${primaryPattern.confidence} confidence)\n`;
|
|
1023
|
+
}
|
|
1024
|
+
// Add architectural characteristics
|
|
1025
|
+
if (context.dependencies.length >= 20) {
|
|
1026
|
+
implementationText += `- God Object Pattern: ${context.dependencies.length} Dependencies (höchster Wert im System)\n`;
|
|
1027
|
+
}
|
|
1028
|
+
if (symbolCount >= 20) {
|
|
1029
|
+
implementationText += `- Hohe Symbol-Dichte: ${symbolCount} Symbole in einem Modul\n`;
|
|
1030
|
+
}
|
|
1031
|
+
if (incomingDepsCount >= 10) {
|
|
1032
|
+
implementationText += `- Single Point of Failure: ${incomingDepsCount} Module abhängig\n`;
|
|
1033
|
+
}
|
|
1034
|
+
implementationText += '\n';
|
|
1035
|
+
// Add architectural notes if available from reasoning
|
|
1036
|
+
if (reasoning && reasoning.decision) {
|
|
1037
|
+
implementationText += '**Architektur-Notizen:**\n';
|
|
1038
|
+
implementationText += `- ${reasoning.decision.split('\n')[0]}\n\n`;
|
|
1039
|
+
}
|
|
1040
|
+
return implementationText;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Extracts title from module or similar ADRs.
|
|
1044
|
+
*/
|
|
1045
|
+
extractTitle(module, similarAdrs) {
|
|
1046
|
+
// Try to extract from module markdown
|
|
1047
|
+
const moduleLines = module.content_markdown?.split('\n') || [];
|
|
1048
|
+
for (const line of moduleLines) {
|
|
1049
|
+
if (line.startsWith('# Modul:')) {
|
|
1050
|
+
const title = line.replace('# Modul:', '').trim();
|
|
1051
|
+
if (title) {
|
|
1052
|
+
return title;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
// Use similar ADR title as template
|
|
1057
|
+
if (similarAdrs.length > 0) {
|
|
1058
|
+
const similarTitle = similarAdrs[0].adr.title;
|
|
1059
|
+
const similarModule = similarAdrs[0].module;
|
|
1060
|
+
const targetModuleName = path.basename(module.file_path, path.extname(module.file_path));
|
|
1061
|
+
const similarModuleName = path.basename(similarModule.file_path, path.extname(similarModule.file_path));
|
|
1062
|
+
// Replace module name in title
|
|
1063
|
+
return similarTitle.replace(similarModuleName, targetModuleName);
|
|
1064
|
+
}
|
|
1065
|
+
// Fallback: Generate from file path
|
|
1066
|
+
const moduleName = path.basename(module.file_path, path.extname(module.file_path));
|
|
1067
|
+
const dirName = path.dirname(module.file_path).split('/').pop() || '';
|
|
1068
|
+
return `${dirName ? dirName + ' - ' : ''}${moduleName}`;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Finds the next available ADR number.
|
|
1072
|
+
*/
|
|
1073
|
+
async findNextAdrNumber(pluginId) {
|
|
1074
|
+
const adrDir = path.join(this.workspaceRoot, 'docs', 'adr');
|
|
1075
|
+
if (!fs.existsSync(adrDir)) {
|
|
1076
|
+
return 1;
|
|
1077
|
+
}
|
|
1078
|
+
const files = fs.readdirSync(adrDir)
|
|
1079
|
+
.filter(file => file.endsWith('.md') && file.match(/^\d{3}-/))
|
|
1080
|
+
.map(file => {
|
|
1081
|
+
const match = file.match(/^(\d{3})-/);
|
|
1082
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
1083
|
+
})
|
|
1084
|
+
.filter(num => num > 0);
|
|
1085
|
+
if (files.length === 0) {
|
|
1086
|
+
return 1;
|
|
1087
|
+
}
|
|
1088
|
+
return Math.max(...files) + 1;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Formats ADR number with leading zeros.
|
|
1092
|
+
*/
|
|
1093
|
+
formatAdrNumber(num) {
|
|
1094
|
+
return num.toString().padStart(3, '0');
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Generates a URL-friendly slug from file path.
|
|
1098
|
+
*/
|
|
1099
|
+
generateSlug(filePath) {
|
|
1100
|
+
// Remove extension and path separators
|
|
1101
|
+
const baseName = path.basename(filePath, path.extname(filePath));
|
|
1102
|
+
const dirName = path.dirname(filePath).replace(/[\/\\]/g, '-');
|
|
1103
|
+
// Combine and sanitize
|
|
1104
|
+
const slug = dirName ? `${dirName}-${baseName}` : baseName;
|
|
1105
|
+
return slug
|
|
1106
|
+
.toLowerCase()
|
|
1107
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
1108
|
+
.replace(/-+/g, '-')
|
|
1109
|
+
.replace(/^-|-$/g, '');
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
exports.AdrGeneratorTool = AdrGeneratorTool;
|
|
1113
|
+
//# sourceMappingURL=adr-generator.js.map
|