neuronlayer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +127 -0
- package/LICENSE +21 -0
- package/README.md +305 -0
- package/dist/index.js +38016 -0
- package/esbuild.config.js +26 -0
- package/package.json +63 -0
- package/src/cli/commands.ts +382 -0
- package/src/core/adr-exporter.ts +253 -0
- package/src/core/architecture/architecture-enforcement.ts +228 -0
- package/src/core/architecture/duplicate-detector.ts +288 -0
- package/src/core/architecture/index.ts +6 -0
- package/src/core/architecture/pattern-learner.ts +306 -0
- package/src/core/architecture/pattern-library.ts +403 -0
- package/src/core/architecture/pattern-validator.ts +324 -0
- package/src/core/change-intelligence/bug-correlator.ts +444 -0
- package/src/core/change-intelligence/change-intelligence.ts +221 -0
- package/src/core/change-intelligence/change-tracker.ts +334 -0
- package/src/core/change-intelligence/fix-suggester.ts +340 -0
- package/src/core/change-intelligence/index.ts +5 -0
- package/src/core/code-verifier.ts +843 -0
- package/src/core/confidence/confidence-scorer.ts +251 -0
- package/src/core/confidence/conflict-checker.ts +289 -0
- package/src/core/confidence/index.ts +5 -0
- package/src/core/confidence/source-tracker.ts +263 -0
- package/src/core/confidence/warning-detector.ts +241 -0
- package/src/core/context-rot/compaction.ts +284 -0
- package/src/core/context-rot/context-health.ts +243 -0
- package/src/core/context-rot/context-rot-prevention.ts +213 -0
- package/src/core/context-rot/critical-context.ts +221 -0
- package/src/core/context-rot/drift-detector.ts +255 -0
- package/src/core/context-rot/index.ts +7 -0
- package/src/core/context.ts +263 -0
- package/src/core/decision-extractor.ts +339 -0
- package/src/core/decisions.ts +69 -0
- package/src/core/deja-vu.ts +421 -0
- package/src/core/engine.ts +1455 -0
- package/src/core/feature-context.ts +726 -0
- package/src/core/ghost-mode.ts +412 -0
- package/src/core/learning.ts +485 -0
- package/src/core/living-docs/activity-tracker.ts +296 -0
- package/src/core/living-docs/architecture-generator.ts +428 -0
- package/src/core/living-docs/changelog-generator.ts +348 -0
- package/src/core/living-docs/component-generator.ts +230 -0
- package/src/core/living-docs/doc-engine.ts +110 -0
- package/src/core/living-docs/doc-validator.ts +282 -0
- package/src/core/living-docs/index.ts +8 -0
- package/src/core/project-manager.ts +297 -0
- package/src/core/summarizer.ts +267 -0
- package/src/core/test-awareness/change-validator.ts +499 -0
- package/src/core/test-awareness/index.ts +5 -0
- package/src/index.ts +49 -0
- package/src/indexing/ast.ts +563 -0
- package/src/indexing/embeddings.ts +85 -0
- package/src/indexing/indexer.ts +245 -0
- package/src/indexing/watcher.ts +78 -0
- package/src/server/gateways/aggregator.ts +374 -0
- package/src/server/gateways/index.ts +473 -0
- package/src/server/gateways/memory-ghost.ts +343 -0
- package/src/server/gateways/memory-query.ts +452 -0
- package/src/server/gateways/memory-record.ts +346 -0
- package/src/server/gateways/memory-review.ts +410 -0
- package/src/server/gateways/memory-status.ts +517 -0
- package/src/server/gateways/memory-verify.ts +392 -0
- package/src/server/gateways/router.ts +434 -0
- package/src/server/gateways/types.ts +610 -0
- package/src/server/mcp.ts +154 -0
- package/src/server/resources.ts +85 -0
- package/src/server/tools.ts +2261 -0
- package/src/storage/database.ts +262 -0
- package/src/storage/tier1.ts +135 -0
- package/src/storage/tier2.ts +764 -0
- package/src/storage/tier3.ts +123 -0
- package/src/types/documentation.ts +619 -0
- package/src/types/index.ts +222 -0
- package/src/utils/config.ts +193 -0
- package/src/utils/files.ts +117 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/tokens.ts +52 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import type { Decision } from '../types/index.js';
|
|
4
|
+
|
|
5
|
+
export interface ADRExportOptions {
|
|
6
|
+
outputDir?: string;
|
|
7
|
+
format?: 'madr' | 'nygard' | 'simple';
|
|
8
|
+
includeIndex?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ADRExporter {
|
|
12
|
+
private projectPath: string;
|
|
13
|
+
|
|
14
|
+
constructor(projectPath: string) {
|
|
15
|
+
this.projectPath = projectPath;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Export a single decision to ADR file
|
|
19
|
+
exportDecision(decision: Decision, options: ADRExportOptions = {}): string {
|
|
20
|
+
const outputDir = options.outputDir || join(this.projectPath, 'docs', 'decisions');
|
|
21
|
+
const format = options.format || 'madr';
|
|
22
|
+
|
|
23
|
+
// Ensure output directory exists
|
|
24
|
+
if (!existsSync(outputDir)) {
|
|
25
|
+
mkdirSync(outputDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Generate filename
|
|
29
|
+
const existingFiles = this.getExistingADRFiles(outputDir);
|
|
30
|
+
const nextNumber = this.getNextADRNumber(existingFiles);
|
|
31
|
+
const slug = this.slugify(decision.title);
|
|
32
|
+
const filename = `${String(nextNumber).padStart(4, '0')}-${slug}.md`;
|
|
33
|
+
const filePath = join(outputDir, filename);
|
|
34
|
+
|
|
35
|
+
// Generate content based on format
|
|
36
|
+
let content: string;
|
|
37
|
+
switch (format) {
|
|
38
|
+
case 'madr':
|
|
39
|
+
content = this.formatMADR(decision, nextNumber);
|
|
40
|
+
break;
|
|
41
|
+
case 'nygard':
|
|
42
|
+
content = this.formatNygard(decision, nextNumber);
|
|
43
|
+
break;
|
|
44
|
+
case 'simple':
|
|
45
|
+
default:
|
|
46
|
+
content = this.formatSimple(decision, nextNumber);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Write file
|
|
51
|
+
writeFileSync(filePath, content);
|
|
52
|
+
|
|
53
|
+
return filePath;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Export all decisions
|
|
57
|
+
exportAllDecisions(decisions: Decision[], options: ADRExportOptions = {}): string[] {
|
|
58
|
+
const exportedFiles: string[] = [];
|
|
59
|
+
const outputDir = options.outputDir || join(this.projectPath, 'docs', 'decisions');
|
|
60
|
+
|
|
61
|
+
// Sort by creation date
|
|
62
|
+
const sorted = [...decisions].sort((a, b) =>
|
|
63
|
+
a.createdAt.getTime() - b.createdAt.getTime()
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
for (const decision of sorted) {
|
|
67
|
+
const filePath = this.exportDecision(decision, { ...options, outputDir });
|
|
68
|
+
exportedFiles.push(filePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Generate index if requested
|
|
72
|
+
if (options.includeIndex !== false) {
|
|
73
|
+
const indexPath = this.generateIndex(decisions, outputDir);
|
|
74
|
+
exportedFiles.push(indexPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return exportedFiles;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Generate index file
|
|
81
|
+
private generateIndex(decisions: Decision[], outputDir: string): string {
|
|
82
|
+
const indexPath = join(outputDir, 'README.md');
|
|
83
|
+
|
|
84
|
+
const lines: string[] = [
|
|
85
|
+
'# Architecture Decision Records',
|
|
86
|
+
'',
|
|
87
|
+
'This directory contains Architecture Decision Records (ADRs) for this project.',
|
|
88
|
+
'',
|
|
89
|
+
'## Decisions',
|
|
90
|
+
'',
|
|
91
|
+
'| # | Title | Status | Date |',
|
|
92
|
+
'|---|-------|--------|------|'
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const sorted = [...decisions].sort((a, b) =>
|
|
96
|
+
a.createdAt.getTime() - b.createdAt.getTime()
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
sorted.forEach((d, i) => {
|
|
100
|
+
const num = String(i + 1).padStart(4, '0');
|
|
101
|
+
const slug = this.slugify(d.title);
|
|
102
|
+
const status = d.status || 'accepted';
|
|
103
|
+
const date = d.createdAt.toISOString().split('T')[0];
|
|
104
|
+
lines.push(`| ${num} | [${d.title}](./${num}-${slug}.md) | ${status} | ${date} |`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
lines.push('');
|
|
108
|
+
lines.push('---');
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push('*Generated by MemoryLayer*');
|
|
111
|
+
|
|
112
|
+
writeFileSync(indexPath, lines.join('\n'));
|
|
113
|
+
return indexPath;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// MADR format (Markdown Any Decision Records)
|
|
117
|
+
private formatMADR(decision: Decision, number: number): string {
|
|
118
|
+
const date = decision.createdAt.toISOString().split('T')[0];
|
|
119
|
+
const status = decision.status || 'accepted';
|
|
120
|
+
|
|
121
|
+
return `# ${decision.title}
|
|
122
|
+
|
|
123
|
+
* Status: ${status}
|
|
124
|
+
* Date: ${date}
|
|
125
|
+
${decision.author ? `* Author: ${decision.author}` : ''}
|
|
126
|
+
${decision.tags.length > 0 ? `* Tags: ${decision.tags.join(', ')}` : ''}
|
|
127
|
+
|
|
128
|
+
## Context and Problem Statement
|
|
129
|
+
|
|
130
|
+
${decision.description}
|
|
131
|
+
|
|
132
|
+
## Decision Drivers
|
|
133
|
+
|
|
134
|
+
* [List the main factors that influenced this decision]
|
|
135
|
+
|
|
136
|
+
## Considered Options
|
|
137
|
+
|
|
138
|
+
* [Option 1]
|
|
139
|
+
* [Option 2]
|
|
140
|
+
* [Option 3]
|
|
141
|
+
|
|
142
|
+
## Decision Outcome
|
|
143
|
+
|
|
144
|
+
Chosen option: "${decision.title}"
|
|
145
|
+
|
|
146
|
+
### Consequences
|
|
147
|
+
|
|
148
|
+
* Good, because [positive consequence]
|
|
149
|
+
* Bad, because [negative consequence]
|
|
150
|
+
|
|
151
|
+
${decision.files.length > 0 ? `## Related Files
|
|
152
|
+
|
|
153
|
+
${decision.files.map(f => `* \`${f}\``).join('\n')}
|
|
154
|
+
` : ''}
|
|
155
|
+
${decision.supersededBy ? `## Superseded By
|
|
156
|
+
|
|
157
|
+
This decision has been superseded by [ADR ${decision.supersededBy}](./${decision.supersededBy}.md).
|
|
158
|
+
` : ''}
|
|
159
|
+
---
|
|
160
|
+
*Exported from MemoryLayer*
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Nygard format (original ADR format)
|
|
165
|
+
private formatNygard(decision: Decision, number: number): string {
|
|
166
|
+
const date = decision.createdAt.toISOString().split('T')[0];
|
|
167
|
+
const status = decision.status || 'Accepted';
|
|
168
|
+
|
|
169
|
+
return `# ${number}. ${decision.title}
|
|
170
|
+
|
|
171
|
+
Date: ${date}
|
|
172
|
+
|
|
173
|
+
## Status
|
|
174
|
+
|
|
175
|
+
${status.charAt(0).toUpperCase() + status.slice(1)}
|
|
176
|
+
${decision.supersededBy ? `\nSuperseded by [ADR ${decision.supersededBy}](./${decision.supersededBy}.md)` : ''}
|
|
177
|
+
|
|
178
|
+
## Context
|
|
179
|
+
|
|
180
|
+
${decision.description}
|
|
181
|
+
|
|
182
|
+
## Decision
|
|
183
|
+
|
|
184
|
+
We will ${decision.title.toLowerCase()}.
|
|
185
|
+
|
|
186
|
+
## Consequences
|
|
187
|
+
|
|
188
|
+
[Describe the consequences of this decision]
|
|
189
|
+
|
|
190
|
+
${decision.files.length > 0 ? `## Related Files
|
|
191
|
+
|
|
192
|
+
${decision.files.map(f => `- ${f}`).join('\n')}
|
|
193
|
+
` : ''}
|
|
194
|
+
---
|
|
195
|
+
*Exported from MemoryLayer*
|
|
196
|
+
`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Simple format
|
|
200
|
+
private formatSimple(decision: Decision, number: number): string {
|
|
201
|
+
const date = decision.createdAt.toISOString().split('T')[0];
|
|
202
|
+
|
|
203
|
+
return `# ${decision.title}
|
|
204
|
+
|
|
205
|
+
**Date:** ${date}
|
|
206
|
+
${decision.author ? `**Author:** ${decision.author}` : ''}
|
|
207
|
+
**Status:** ${decision.status || 'accepted'}
|
|
208
|
+
${decision.tags.length > 0 ? `**Tags:** ${decision.tags.join(', ')}` : ''}
|
|
209
|
+
|
|
210
|
+
## Description
|
|
211
|
+
|
|
212
|
+
${decision.description}
|
|
213
|
+
|
|
214
|
+
${decision.files.length > 0 ? `## Related Files
|
|
215
|
+
|
|
216
|
+
${decision.files.map(f => `- \`${f}\``).join('\n')}
|
|
217
|
+
` : ''}
|
|
218
|
+
---
|
|
219
|
+
*Exported from MemoryLayer*
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private getExistingADRFiles(dir: string): string[] {
|
|
224
|
+
if (!existsSync(dir)) return [];
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const { readdirSync } = require('fs');
|
|
228
|
+
return readdirSync(dir)
|
|
229
|
+
.filter((f: string) => /^\d{4}-.*\.md$/.test(f))
|
|
230
|
+
.sort();
|
|
231
|
+
} catch {
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private getNextADRNumber(existingFiles: string[]): number {
|
|
237
|
+
if (existingFiles.length === 0) return 1;
|
|
238
|
+
|
|
239
|
+
const numbers = existingFiles
|
|
240
|
+
.map(f => parseInt(f.split('-')[0] || '0', 10))
|
|
241
|
+
.filter(n => !isNaN(n));
|
|
242
|
+
|
|
243
|
+
return Math.max(...numbers, 0) + 1;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private slugify(text: string): string {
|
|
247
|
+
return text
|
|
248
|
+
.toLowerCase()
|
|
249
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
250
|
+
.replace(/^-|-$/g, '')
|
|
251
|
+
.slice(0, 50);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
import type { Tier2Storage } from '../../storage/tier2.js';
|
|
3
|
+
import type { EmbeddingGenerator } from '../../indexing/embeddings.js';
|
|
4
|
+
import type {
|
|
5
|
+
Pattern,
|
|
6
|
+
PatternCategory,
|
|
7
|
+
PatternValidationResult,
|
|
8
|
+
ExistingFunction,
|
|
9
|
+
CodeExample,
|
|
10
|
+
PatternRule
|
|
11
|
+
} from '../../types/documentation.js';
|
|
12
|
+
import { PatternLibrary } from './pattern-library.js';
|
|
13
|
+
import { PatternLearner } from './pattern-learner.js';
|
|
14
|
+
import { PatternValidator } from './pattern-validator.js';
|
|
15
|
+
import { DuplicateDetector } from './duplicate-detector.js';
|
|
16
|
+
|
|
17
|
+
export class ArchitectureEnforcement {
|
|
18
|
+
private patternLibrary: PatternLibrary;
|
|
19
|
+
private patternLearner: PatternLearner;
|
|
20
|
+
private patternValidator: PatternValidator;
|
|
21
|
+
private duplicateDetector: DuplicateDetector;
|
|
22
|
+
private initialized = false;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
db: Database.Database,
|
|
26
|
+
tier2: Tier2Storage,
|
|
27
|
+
embeddingGenerator: EmbeddingGenerator
|
|
28
|
+
) {
|
|
29
|
+
this.patternLibrary = new PatternLibrary(db);
|
|
30
|
+
this.duplicateDetector = new DuplicateDetector(tier2, embeddingGenerator);
|
|
31
|
+
this.patternLearner = new PatternLearner(tier2, this.patternLibrary);
|
|
32
|
+
this.patternValidator = new PatternValidator(this.patternLibrary, this.duplicateDetector, tier2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Initialize by learning patterns from codebase
|
|
36
|
+
initialize(): { patternsLearned: number; examplesAdded: number } {
|
|
37
|
+
if (this.initialized) return { patternsLearned: 0, examplesAdded: 0 };
|
|
38
|
+
|
|
39
|
+
const result = this.patternLearner.learnFromCodebase();
|
|
40
|
+
this.initialized = true;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
patternsLearned: result.patternsLearned,
|
|
44
|
+
examplesAdded: result.examplesAdded
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate code against patterns
|
|
49
|
+
validatePattern(code: string, type?: PatternCategory | 'auto'): PatternValidationResult {
|
|
50
|
+
return this.patternValidator.validate(code, type);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Suggest existing functions for an intent
|
|
54
|
+
suggestExisting(intent: string, limit?: number): ExistingFunction[] {
|
|
55
|
+
return this.duplicateDetector.suggestExisting(intent, limit);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Learn a new pattern
|
|
59
|
+
learnPattern(
|
|
60
|
+
code: string,
|
|
61
|
+
name: string,
|
|
62
|
+
description?: string,
|
|
63
|
+
category?: PatternCategory
|
|
64
|
+
): { success: boolean; patternId?: string; message: string } {
|
|
65
|
+
return this.patternLearner.learnPattern(code, name, description, category);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// List all patterns
|
|
69
|
+
listPatterns(category?: PatternCategory): Pattern[] {
|
|
70
|
+
if (category) {
|
|
71
|
+
return this.patternLibrary.getPatternsByCategory(category);
|
|
72
|
+
}
|
|
73
|
+
return this.patternLibrary.getAllPatterns();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Get a specific pattern
|
|
77
|
+
getPattern(id: string): Pattern | null {
|
|
78
|
+
return this.patternLibrary.getPattern(id);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Add example to existing pattern
|
|
82
|
+
addExample(patternId: string, code: string, explanation: string, isAntiPattern: boolean = false): boolean {
|
|
83
|
+
const example: CodeExample = { code, explanation };
|
|
84
|
+
return this.patternLibrary.addExample(patternId, example, isAntiPattern);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Add rule to existing pattern
|
|
88
|
+
addRule(patternId: string, rule: string, severity: 'info' | 'warning' | 'critical'): boolean {
|
|
89
|
+
const patternRule: PatternRule = { rule, severity };
|
|
90
|
+
return this.patternLibrary.addRule(patternId, patternRule);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Search patterns
|
|
94
|
+
searchPatterns(query: string): Pattern[] {
|
|
95
|
+
return this.patternLibrary.searchPatterns(query);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Delete a pattern
|
|
99
|
+
deletePattern(id: string): boolean {
|
|
100
|
+
return this.patternLibrary.deletePattern(id);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Refresh duplicate detector index
|
|
104
|
+
refreshIndex(): void {
|
|
105
|
+
this.duplicateDetector.refresh();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get statistics
|
|
109
|
+
getStats(): {
|
|
110
|
+
patterns: {
|
|
111
|
+
total: number;
|
|
112
|
+
byCategory: Record<string, number>;
|
|
113
|
+
topPatterns: Array<{ name: string; usageCount: number }>;
|
|
114
|
+
};
|
|
115
|
+
functions: {
|
|
116
|
+
total: number;
|
|
117
|
+
exported: number;
|
|
118
|
+
byPurpose: Record<string, number>;
|
|
119
|
+
};
|
|
120
|
+
} {
|
|
121
|
+
return {
|
|
122
|
+
patterns: this.patternLearner.getStats(),
|
|
123
|
+
functions: this.duplicateDetector.getStats()
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Format validation result for display
|
|
128
|
+
static formatValidationResult(result: PatternValidationResult): string {
|
|
129
|
+
const lines: string[] = [];
|
|
130
|
+
|
|
131
|
+
const scoreIcon = result.score >= 80 ? '\u{1F7E2}' :
|
|
132
|
+
result.score >= 50 ? '\u{1F7E1}' : '\u{1F534}';
|
|
133
|
+
|
|
134
|
+
lines.push(`\u{1F50D} Pattern Validation\n`);
|
|
135
|
+
lines.push(`${scoreIcon} Score: ${result.score}/100`);
|
|
136
|
+
|
|
137
|
+
if (result.matchedPattern) {
|
|
138
|
+
lines.push(`Matched Pattern: ${result.matchedPattern}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (result.violations.length > 0) {
|
|
142
|
+
lines.push('\n\u274C Violations:');
|
|
143
|
+
for (const v of result.violations) {
|
|
144
|
+
const icon = v.severity === 'critical' ? '\u{1F534}' :
|
|
145
|
+
v.severity === 'warning' ? '\u{1F7E1}' : '\u{2139}\u{FE0F}';
|
|
146
|
+
lines.push(` ${icon} ${v.message}`);
|
|
147
|
+
if (v.suggestion) {
|
|
148
|
+
lines.push(` \u2192 ${v.suggestion}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (result.existingAlternatives.length > 0) {
|
|
154
|
+
lines.push('\n\u{1F4A1} Existing Alternatives:');
|
|
155
|
+
for (const alt of result.existingAlternatives) {
|
|
156
|
+
lines.push(` - ${alt.name}() in ${alt.file} (${alt.similarity}% similar)`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (result.suggestions.length > 0) {
|
|
161
|
+
lines.push('\n\u{1F4DD} Suggestions:');
|
|
162
|
+
for (const s of result.suggestions) {
|
|
163
|
+
const icon = s.priority === 'high' ? '\u{1F534}' :
|
|
164
|
+
s.priority === 'medium' ? '\u{1F7E1}' : '\u{1F7E2}';
|
|
165
|
+
lines.push(` ${icon} ${s.description}`);
|
|
166
|
+
if (s.code) {
|
|
167
|
+
lines.push(` ${s.code}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return lines.join('\n');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Format pattern list for display
|
|
176
|
+
static formatPatternList(patterns: Pattern[]): string {
|
|
177
|
+
if (patterns.length === 0) {
|
|
178
|
+
return 'No patterns found.';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const lines: string[] = [];
|
|
182
|
+
lines.push(`\u{1F4DA} Patterns (${patterns.length})\n`);
|
|
183
|
+
|
|
184
|
+
const byCategory: Record<string, Pattern[]> = {};
|
|
185
|
+
for (const p of patterns) {
|
|
186
|
+
if (!byCategory[p.category]) {
|
|
187
|
+
byCategory[p.category] = [];
|
|
188
|
+
}
|
|
189
|
+
byCategory[p.category].push(p);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
for (const [category, categoryPatterns] of Object.entries(byCategory)) {
|
|
193
|
+
lines.push(`\n${category.toUpperCase()}:`);
|
|
194
|
+
for (const p of categoryPatterns) {
|
|
195
|
+
lines.push(` \u251C\u2500 ${p.name} (${p.usageCount} uses)`);
|
|
196
|
+
if (p.description) {
|
|
197
|
+
lines.push(` \u2502 ${p.description.slice(0, 50)}...`);
|
|
198
|
+
}
|
|
199
|
+
lines.push(` \u2502 Rules: ${p.rules.length}, Examples: ${p.examples.length}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return lines.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Format existing function suggestions for display
|
|
207
|
+
static formatSuggestions(suggestions: ExistingFunction[]): string {
|
|
208
|
+
if (suggestions.length === 0) {
|
|
209
|
+
return 'No existing functions found for this intent.';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const lines: string[] = [];
|
|
213
|
+
lines.push(`\u{1F4A1} Existing Functions\n`);
|
|
214
|
+
|
|
215
|
+
for (const s of suggestions) {
|
|
216
|
+
lines.push(`\u251C\u2500 ${s.name}()`);
|
|
217
|
+
lines.push(`\u2502 File: ${s.file}:${s.line}`);
|
|
218
|
+
lines.push(`\u2502 Signature: ${s.signature}`);
|
|
219
|
+
lines.push(`\u2502 Used: ${s.usageCount} times, Relevance: ${s.similarity}%`);
|
|
220
|
+
if (s.description) {
|
|
221
|
+
lines.push(`\u2502 ${s.description}`);
|
|
222
|
+
}
|
|
223
|
+
lines.push('');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return lines.join('\n');
|
|
227
|
+
}
|
|
228
|
+
}
|