@limo-labs/limo-cli 0.1.0-alpha.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/README.md +238 -0
- package/dist/agents/analyst.d.ts +24 -0
- package/dist/agents/analyst.js +128 -0
- package/dist/agents/editor.d.ts +26 -0
- package/dist/agents/editor.js +157 -0
- package/dist/agents/planner-validator.d.ts +7 -0
- package/dist/agents/planner-validator.js +125 -0
- package/dist/agents/planner.d.ts +56 -0
- package/dist/agents/planner.js +186 -0
- package/dist/agents/writer.d.ts +25 -0
- package/dist/agents/writer.js +164 -0
- package/dist/commands/analyze.d.ts +14 -0
- package/dist/commands/analyze.js +562 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +41 -0
- package/dist/report/diagrams.d.ts +27 -0
- package/dist/report/diagrams.js +74 -0
- package/dist/report/graphCompiler.d.ts +37 -0
- package/dist/report/graphCompiler.js +277 -0
- package/dist/report/markdownGenerator.d.ts +71 -0
- package/dist/report/markdownGenerator.js +148 -0
- package/dist/tools/additional.d.ts +116 -0
- package/dist/tools/additional.js +349 -0
- package/dist/tools/extended.d.ts +101 -0
- package/dist/tools/extended.js +586 -0
- package/dist/tools/index.d.ts +86 -0
- package/dist/tools/index.js +362 -0
- package/dist/types/agents.types.d.ts +139 -0
- package/dist/types/agents.types.js +6 -0
- package/dist/types/graphSemantics.d.ts +99 -0
- package/dist/types/graphSemantics.js +104 -0
- package/dist/utils/debug.d.ts +28 -0
- package/dist/utils/debug.js +125 -0
- package/dist/utils/limoConfigParser.d.ts +21 -0
- package/dist/utils/limoConfigParser.js +274 -0
- package/dist/utils/reviewMonitor.d.ts +20 -0
- package/dist/utils/reviewMonitor.js +121 -0
- package/package.json +62 -0
- package/prompts/analyst.md +343 -0
- package/prompts/editor.md +196 -0
- package/prompts/planner.md +388 -0
- package/prompts/writer.md +218 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagram rendering utilities for CLI reports
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Embeds a diagram in Markdown format
|
|
6
|
+
*
|
|
7
|
+
* For SVG diagrams, embeds as base64 data URI image for universal compatibility.
|
|
8
|
+
* For DOT/Mermaid format, embeds as code blocks for external rendering.
|
|
9
|
+
*/
|
|
10
|
+
export function embedDiagramInMarkdown(diagram) {
|
|
11
|
+
let markdown = '';
|
|
12
|
+
// Add title and description
|
|
13
|
+
if (diagram.title) {
|
|
14
|
+
markdown += `### ${diagram.title}\n\n`;
|
|
15
|
+
}
|
|
16
|
+
if (diagram.description) {
|
|
17
|
+
markdown += `${diagram.description}\n\n`;
|
|
18
|
+
}
|
|
19
|
+
// Embed diagram based on format
|
|
20
|
+
if (diagram.format === 'svg') {
|
|
21
|
+
// Convert SVG to base64 data URI and embed as image
|
|
22
|
+
const base64Svg = Buffer.from(diagram.source, 'utf-8').toString('base64');
|
|
23
|
+
const dataUri = `data:image/svg+xml;base64,${base64Svg}`;
|
|
24
|
+
const altText = diagram.title || 'Architecture Diagram';
|
|
25
|
+
markdown += `\n\n`;
|
|
26
|
+
}
|
|
27
|
+
else if (diagram.format === 'dot') {
|
|
28
|
+
// DOT as code block (requires external renderer)
|
|
29
|
+
markdown += `\`\`\`dot\n${diagram.source}\n\`\`\`\n\n`;
|
|
30
|
+
}
|
|
31
|
+
else if (diagram.format === 'mermaid') {
|
|
32
|
+
// Mermaid as code block
|
|
33
|
+
markdown += `\`\`\`mermaid\n${diagram.source}\n\`\`\`\n\n`;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Fallback for unknown formats
|
|
37
|
+
markdown += `\`\`\`${diagram.format}\n${diagram.source}\n\`\`\`\n\n`;
|
|
38
|
+
}
|
|
39
|
+
// Add metadata if present
|
|
40
|
+
if (diagram.metadata) {
|
|
41
|
+
if (diagram.metadata.nodeCount !== undefined || diagram.metadata.edgeCount !== undefined) {
|
|
42
|
+
markdown += `*Complexity: `;
|
|
43
|
+
const parts = [];
|
|
44
|
+
if (diagram.metadata.nodeCount !== undefined) {
|
|
45
|
+
parts.push(`${diagram.metadata.nodeCount} nodes`);
|
|
46
|
+
}
|
|
47
|
+
if (diagram.metadata.edgeCount !== undefined) {
|
|
48
|
+
parts.push(`${diagram.metadata.edgeCount} edges`);
|
|
49
|
+
}
|
|
50
|
+
markdown += parts.join(', ') + '*\n\n';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Add related files if present
|
|
54
|
+
if (diagram.relatedFiles && diagram.relatedFiles.length > 0) {
|
|
55
|
+
markdown += `**Related files:**\n`;
|
|
56
|
+
for (const file of diagram.relatedFiles) {
|
|
57
|
+
// Use proper file path encoding
|
|
58
|
+
const encodedPath = encodeFilePathForMarkdown(file);
|
|
59
|
+
markdown += `- [\`${file}\`](${encodedPath})\n`;
|
|
60
|
+
}
|
|
61
|
+
markdown += '\n';
|
|
62
|
+
}
|
|
63
|
+
return markdown;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Encode file path for use in markdown links
|
|
67
|
+
* Preserves forward slashes for VS Code compatibility
|
|
68
|
+
*/
|
|
69
|
+
function encodeFilePathForMarkdown(filePath) {
|
|
70
|
+
return filePath
|
|
71
|
+
.split('/')
|
|
72
|
+
.map(segment => encodeURIComponent(segment))
|
|
73
|
+
.join('/');
|
|
74
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph Semantics to SVG Compiler
|
|
3
|
+
*
|
|
4
|
+
* Converts graph semantic structure (IR) to SVG directly via DOT.
|
|
5
|
+
* This compiler uses ONLY explicit semantic fields from the IR.
|
|
6
|
+
* NO natural language inference, NO layout guessing.
|
|
7
|
+
*
|
|
8
|
+
* CRITICAL: LLM never sees this code. This is a pure code-side responsibility.
|
|
9
|
+
*/
|
|
10
|
+
import type { GraphSemantics } from '../types/graphSemantics.js';
|
|
11
|
+
/**
|
|
12
|
+
* Compile graph semantics to DOT language
|
|
13
|
+
*/
|
|
14
|
+
export declare function compileGraphToDot(semantics: GraphSemantics): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate DOT with validation
|
|
17
|
+
*/
|
|
18
|
+
export declare function compileGraphToDotSafe(semantics: GraphSemantics): {
|
|
19
|
+
success: boolean;
|
|
20
|
+
dot?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Compile graph semantics directly to SVG
|
|
25
|
+
*
|
|
26
|
+
* This is the primary function for diagram generation.
|
|
27
|
+
* It compiles semantic IR to DOT, then uses Viz.js to render SVG.
|
|
28
|
+
*/
|
|
29
|
+
export declare function compileGraphToSvg(semantics: GraphSemantics): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Compile graph semantics to SVG with validation
|
|
32
|
+
*/
|
|
33
|
+
export declare function compileGraphToSvgSafe(semantics: GraphSemantics): Promise<{
|
|
34
|
+
success: boolean;
|
|
35
|
+
svg?: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph Semantics to SVG Compiler
|
|
3
|
+
*
|
|
4
|
+
* Converts graph semantic structure (IR) to SVG directly via DOT.
|
|
5
|
+
* This compiler uses ONLY explicit semantic fields from the IR.
|
|
6
|
+
* NO natural language inference, NO layout guessing.
|
|
7
|
+
*
|
|
8
|
+
* CRITICAL: LLM never sees this code. This is a pure code-side responsibility.
|
|
9
|
+
*/
|
|
10
|
+
import { instance } from '@viz-js/viz';
|
|
11
|
+
/**
|
|
12
|
+
* Cached Viz.js instance for performance
|
|
13
|
+
*/
|
|
14
|
+
let vizInstance = null;
|
|
15
|
+
/**
|
|
16
|
+
* Get or initialize Viz.js instance
|
|
17
|
+
*/
|
|
18
|
+
async function getVizInstance() {
|
|
19
|
+
if (!vizInstance) {
|
|
20
|
+
vizInstance = await instance();
|
|
21
|
+
}
|
|
22
|
+
return vizInstance;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Node shape mapping based on semantic type
|
|
26
|
+
*/
|
|
27
|
+
const NODE_SHAPES = {
|
|
28
|
+
'component': 'box',
|
|
29
|
+
'layer': 'folder',
|
|
30
|
+
'system': 'hexagon',
|
|
31
|
+
'data': 'cylinder',
|
|
32
|
+
'process': 'ellipse',
|
|
33
|
+
'actor': 'house'
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Node color mapping based on semantic type
|
|
37
|
+
*/
|
|
38
|
+
const NODE_COLORS = {
|
|
39
|
+
'component': { fillcolor: '#E3F2FD', fontcolor: '#0D47A1' },
|
|
40
|
+
'layer': { fillcolor: '#F3E5F5', fontcolor: '#4A148C' },
|
|
41
|
+
'system': { fillcolor: '#FFF9C4', fontcolor: '#F57F17' },
|
|
42
|
+
'data': { fillcolor: '#E0F2F1', fontcolor: '#004D40' },
|
|
43
|
+
'process': { fillcolor: '#FCE4EC', fontcolor: '#880E4F' },
|
|
44
|
+
'actor': { fillcolor: '#E8F5E9', fontcolor: '#1B5E20' }
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Edge style mapping based on semantic type
|
|
48
|
+
*/
|
|
49
|
+
const EDGE_STYLES = {
|
|
50
|
+
'depends_on': { style: 'solid', arrowhead: 'normal', color: '#424242' },
|
|
51
|
+
'calls': { style: 'solid', arrowhead: 'vee', color: '#1976D2' },
|
|
52
|
+
'uses': { style: 'dashed', arrowhead: 'open', color: '#424242' },
|
|
53
|
+
'contains': { style: 'solid', arrowhead: 'diamond', color: '#4CAF50' },
|
|
54
|
+
'inherits': { style: 'solid', arrowhead: 'empty', color: '#9C27B0' },
|
|
55
|
+
'implements': { style: 'dashed', arrowhead: 'empty', color: '#9C27B0' },
|
|
56
|
+
'data_flow': { style: 'bold', arrowhead: 'normal', color: '#FF9800' },
|
|
57
|
+
'control_flow': { style: 'bold', arrowhead: 'normal', color: '#F44336' }
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Compile graph semantics to DOT language
|
|
61
|
+
*/
|
|
62
|
+
export function compileGraphToDot(semantics) {
|
|
63
|
+
const lines = [];
|
|
64
|
+
const indent = ' ';
|
|
65
|
+
// Start digraph
|
|
66
|
+
lines.push(`digraph ${quote(sanitizeId(semantics.id))} {`);
|
|
67
|
+
// Graph attributes
|
|
68
|
+
const direction = semantics.metadata?.direction || 'TB';
|
|
69
|
+
lines.push(`${indent}rankdir="${direction}";`);
|
|
70
|
+
lines.push(`${indent}bgcolor="transparent";`);
|
|
71
|
+
lines.push(`${indent}fontname="Arial";`);
|
|
72
|
+
lines.push(`${indent}fontsize=12;`);
|
|
73
|
+
lines.push(`${indent}nodesep=0.5;`);
|
|
74
|
+
lines.push(`${indent}ranksep=0.75;`);
|
|
75
|
+
lines.push('');
|
|
76
|
+
// Default node attributes
|
|
77
|
+
lines.push(`${indent}node [style=filled, fontname="Arial", fontsize=11, margin="0.2,0.1"];`);
|
|
78
|
+
lines.push('');
|
|
79
|
+
// Default edge attributes
|
|
80
|
+
lines.push(`${indent}edge [fontname="Arial", fontsize=9];`);
|
|
81
|
+
lines.push('');
|
|
82
|
+
// Build group structure
|
|
83
|
+
const groups = semantics.groups || [];
|
|
84
|
+
const groupsByParent = organizeGroupsByParent(groups);
|
|
85
|
+
const nodesInGroups = new Set();
|
|
86
|
+
// Add groups and their nodes
|
|
87
|
+
if (groups.length > 0) {
|
|
88
|
+
addGroupsRecursive(lines, indent, groups, groupsByParent, semantics.nodes, nodesInGroups, semantics.metadata?.focusNodes);
|
|
89
|
+
}
|
|
90
|
+
// Add ungrouped nodes
|
|
91
|
+
for (const node of semantics.nodes) {
|
|
92
|
+
if (!node.group || !nodesInGroups.has(node.id)) {
|
|
93
|
+
addNodeDef(lines, indent, node, semantics.metadata?.focusNodes);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Add edges
|
|
97
|
+
if (semantics.edges.length > 0) {
|
|
98
|
+
lines.push('');
|
|
99
|
+
for (const edge of semantics.edges) {
|
|
100
|
+
addEdgeDef(lines, indent, edge);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Close digraph
|
|
104
|
+
lines.push('}');
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Organize groups by parent
|
|
109
|
+
*/
|
|
110
|
+
function organizeGroupsByParent(groups) {
|
|
111
|
+
const map = new Map();
|
|
112
|
+
for (const group of groups) {
|
|
113
|
+
const parent = group.parent;
|
|
114
|
+
if (!map.has(parent)) {
|
|
115
|
+
map.set(parent, []);
|
|
116
|
+
}
|
|
117
|
+
map.get(parent).push(group);
|
|
118
|
+
}
|
|
119
|
+
return map;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Add groups recursively with their nodes
|
|
123
|
+
*/
|
|
124
|
+
function addGroupsRecursive(lines, baseIndent, allGroups, groupsByParent, nodes, nodesInGroups, focusNodes, parentId, currentDepth = 0) {
|
|
125
|
+
const children = groupsByParent.get(parentId) || [];
|
|
126
|
+
const indent = baseIndent.repeat(currentDepth + 1);
|
|
127
|
+
for (const group of children) {
|
|
128
|
+
// Start subgraph
|
|
129
|
+
lines.push('');
|
|
130
|
+
lines.push(`${indent}subgraph cluster_${sanitizeId(group.id)} {`);
|
|
131
|
+
lines.push(`${indent} label=${quote(group.label)};`);
|
|
132
|
+
lines.push(`${indent} style="rounded,filled";`);
|
|
133
|
+
lines.push(`${indent} fillcolor="#F5F5F5";`);
|
|
134
|
+
lines.push(`${indent} color="#BDBDBD";`);
|
|
135
|
+
lines.push(`${indent} fontsize=12;`);
|
|
136
|
+
lines.push(`${indent} fontname="Arial Bold";`);
|
|
137
|
+
if (group.description) {
|
|
138
|
+
lines.push(`${indent} tooltip=${quote(group.description)};`);
|
|
139
|
+
}
|
|
140
|
+
// Add nodes in this group
|
|
141
|
+
for (const node of nodes) {
|
|
142
|
+
if (node.group === group.id) {
|
|
143
|
+
addNodeDef(lines, indent + ' ', node, focusNodes);
|
|
144
|
+
nodesInGroups.add(node.id);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Recursively add child groups
|
|
148
|
+
addGroupsRecursive(lines, baseIndent, allGroups, groupsByParent, nodes, nodesInGroups, focusNodes, group.id, currentDepth + 1);
|
|
149
|
+
// End subgraph
|
|
150
|
+
lines.push(`${indent}}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Add node definition
|
|
155
|
+
*/
|
|
156
|
+
function addNodeDef(lines, indent, node, focusNodes) {
|
|
157
|
+
const shape = NODE_SHAPES[node.type] || 'box';
|
|
158
|
+
const colors = NODE_COLORS[node.type] || { fillcolor: '#FFFFFF', fontcolor: '#000000' };
|
|
159
|
+
const isFocused = focusNodes?.includes(node.id);
|
|
160
|
+
const attrs = [
|
|
161
|
+
`label=${quote(node.label)}`,
|
|
162
|
+
`shape=${shape}`,
|
|
163
|
+
`fillcolor="${colors.fillcolor}"`,
|
|
164
|
+
`fontcolor="${colors.fontcolor}"`,
|
|
165
|
+
`style="${isFocused ? 'filled,bold' : 'filled'}"`,
|
|
166
|
+
`penwidth=${isFocused ? '2' : '1'}`,
|
|
167
|
+
`tooltip=${quote(node.description || node.label)}`
|
|
168
|
+
];
|
|
169
|
+
lines.push(`${indent}${sanitizeId(node.id)} [${attrs.join(', ')}];`);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Add edge definition
|
|
173
|
+
*/
|
|
174
|
+
function addEdgeDef(lines, indent, edge) {
|
|
175
|
+
const style = EDGE_STYLES[edge.type] || { style: 'solid', arrowhead: 'normal', color: '#424242' };
|
|
176
|
+
const attrs = [
|
|
177
|
+
`style=${style.style}`,
|
|
178
|
+
`arrowhead=${style.arrowhead}`,
|
|
179
|
+
`color="${style.color}"`
|
|
180
|
+
];
|
|
181
|
+
if (edge.label) {
|
|
182
|
+
attrs.push(`label=${quote(edge.label)}`);
|
|
183
|
+
}
|
|
184
|
+
if (edge.metadata?.weight) {
|
|
185
|
+
const penwidth = Math.max(1, Math.min(5, edge.metadata.weight / 2));
|
|
186
|
+
attrs.push(`penwidth=${penwidth}`);
|
|
187
|
+
}
|
|
188
|
+
if (edge.bidirectional) {
|
|
189
|
+
attrs.push('dir=both');
|
|
190
|
+
}
|
|
191
|
+
const arrow = edge.bidirectional ? '--' : '->';
|
|
192
|
+
lines.push(`${indent}${sanitizeId(edge.from)} ${arrow} ${sanitizeId(edge.to)} [${attrs.join(', ')}];`);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* DOT reserved keywords that cannot be used as node IDs
|
|
196
|
+
*/
|
|
197
|
+
const DOT_KEYWORDS = new Set([
|
|
198
|
+
'node', 'edge', 'graph', 'digraph', 'subgraph', 'strict',
|
|
199
|
+
// Also include common DOT attribute names to avoid confusion
|
|
200
|
+
'label', 'shape', 'color', 'style', 'rank'
|
|
201
|
+
]);
|
|
202
|
+
/**
|
|
203
|
+
* Sanitize ID to be valid DOT identifier
|
|
204
|
+
*
|
|
205
|
+
* Handles:
|
|
206
|
+
* - Special characters (replaced with underscores)
|
|
207
|
+
* - IDs starting with digits (prefixed with 'n_')
|
|
208
|
+
* - DOT reserved keywords (prefixed with 'n_')
|
|
209
|
+
* - Empty IDs (replaced with random ID)
|
|
210
|
+
*/
|
|
211
|
+
function sanitizeId(id) {
|
|
212
|
+
// 1. Replace invalid characters with underscores
|
|
213
|
+
let cleaned = id.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
214
|
+
// 2. Handle IDs starting with digits (DOT doesn't allow this)
|
|
215
|
+
if (/^[0-9]/.test(cleaned)) {
|
|
216
|
+
cleaned = 'n_' + cleaned;
|
|
217
|
+
}
|
|
218
|
+
// 3. Handle DOT reserved keywords (case-insensitive)
|
|
219
|
+
if (DOT_KEYWORDS.has(cleaned.toLowerCase())) {
|
|
220
|
+
cleaned = 'n_' + cleaned;
|
|
221
|
+
}
|
|
222
|
+
// 4. Ensure non-empty ID
|
|
223
|
+
if (cleaned === '' || cleaned === '_') {
|
|
224
|
+
cleaned = 'node_' + Math.random().toString(36).substring(7);
|
|
225
|
+
}
|
|
226
|
+
return cleaned;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Quote string for DOT
|
|
230
|
+
*/
|
|
231
|
+
function quote(str) {
|
|
232
|
+
return '"' + str.replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"';
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Generate DOT with validation
|
|
236
|
+
*/
|
|
237
|
+
export function compileGraphToDotSafe(semantics) {
|
|
238
|
+
try {
|
|
239
|
+
const dot = compileGraphToDot(semantics);
|
|
240
|
+
return { success: true, dot };
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
error: `Failed to compile graph to DOT: ${error instanceof Error ? error.message : String(error)}`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Compile graph semantics directly to SVG
|
|
251
|
+
*
|
|
252
|
+
* This is the primary function for diagram generation.
|
|
253
|
+
* It compiles semantic IR to DOT, then uses Viz.js to render SVG.
|
|
254
|
+
*/
|
|
255
|
+
export async function compileGraphToSvg(semantics) {
|
|
256
|
+
// Step 1: Compile semantic structure to DOT
|
|
257
|
+
const dot = compileGraphToDot(semantics);
|
|
258
|
+
// Step 2: Render DOT to SVG using Viz.js
|
|
259
|
+
const viz = await getVizInstance();
|
|
260
|
+
const svg = viz.renderString(dot, { format: 'svg', engine: 'dot' });
|
|
261
|
+
return svg;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Compile graph semantics to SVG with validation
|
|
265
|
+
*/
|
|
266
|
+
export async function compileGraphToSvgSafe(semantics) {
|
|
267
|
+
try {
|
|
268
|
+
const svg = await compileGraphToSvg(semantics);
|
|
269
|
+
return { success: true, svg };
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
error: `Failed to compile graph to SVG: ${error instanceof Error ? error.message : String(error)}`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Report Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates professional markdown reports with embedded diagrams,
|
|
5
|
+
* rich metadata, and statistics - matching VSCode report quality.
|
|
6
|
+
*/
|
|
7
|
+
import type { DiagramDefinition } from './diagrams.js';
|
|
8
|
+
export interface ReportMetadata {
|
|
9
|
+
id: string;
|
|
10
|
+
title: string;
|
|
11
|
+
generatedAt: Date;
|
|
12
|
+
projectPath: string;
|
|
13
|
+
aiModel?: string;
|
|
14
|
+
modules: string[];
|
|
15
|
+
statistics?: {
|
|
16
|
+
totalSections?: number;
|
|
17
|
+
totalDiagrams?: number;
|
|
18
|
+
totalFindings?: number;
|
|
19
|
+
filesAnalyzed?: number;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export interface ReportSection {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
content: string;
|
|
26
|
+
diagrams?: DiagramDefinition[];
|
|
27
|
+
}
|
|
28
|
+
export interface Report {
|
|
29
|
+
metadata: ReportMetadata;
|
|
30
|
+
executiveSummary?: string;
|
|
31
|
+
sections: ReportSection[];
|
|
32
|
+
}
|
|
33
|
+
export declare class MarkdownGenerator {
|
|
34
|
+
/**
|
|
35
|
+
* Generate a complete markdown report
|
|
36
|
+
*/
|
|
37
|
+
generate(report: Report): string;
|
|
38
|
+
/**
|
|
39
|
+
* Generate report header with rich metadata
|
|
40
|
+
*/
|
|
41
|
+
private generateHeader;
|
|
42
|
+
/**
|
|
43
|
+
* Generate executive summary section
|
|
44
|
+
*/
|
|
45
|
+
private generateExecutiveSummary;
|
|
46
|
+
/**
|
|
47
|
+
* Generate a single section with embedded diagrams
|
|
48
|
+
*/
|
|
49
|
+
private generateSection;
|
|
50
|
+
/**
|
|
51
|
+
* Embed a single diagram
|
|
52
|
+
*/
|
|
53
|
+
private embedDiagram;
|
|
54
|
+
/**
|
|
55
|
+
* Format date in a human-readable way
|
|
56
|
+
*/
|
|
57
|
+
private formatDate;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Helper function to extract title from markdown content
|
|
61
|
+
*/
|
|
62
|
+
export declare function extractTitle(content: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Helper function to count findings in content
|
|
65
|
+
* Counts bullet points, numbered items, and sections
|
|
66
|
+
*/
|
|
67
|
+
export declare function countFindings(content: string): number;
|
|
68
|
+
/**
|
|
69
|
+
* Helper function to count findings across all sections
|
|
70
|
+
*/
|
|
71
|
+
export declare function countAllFindings(sections: ReportSection[]): number;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown Report Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates professional markdown reports with embedded diagrams,
|
|
5
|
+
* rich metadata, and statistics - matching VSCode report quality.
|
|
6
|
+
*/
|
|
7
|
+
import { embedDiagramInMarkdown } from './diagrams.js';
|
|
8
|
+
export class MarkdownGenerator {
|
|
9
|
+
/**
|
|
10
|
+
* Generate a complete markdown report
|
|
11
|
+
*/
|
|
12
|
+
generate(report) {
|
|
13
|
+
const parts = [];
|
|
14
|
+
// 1. Header with metadata
|
|
15
|
+
parts.push(this.generateHeader(report.metadata));
|
|
16
|
+
// 2. Executive Summary (if available)
|
|
17
|
+
if (report.executiveSummary) {
|
|
18
|
+
parts.push(this.generateExecutiveSummary(report.executiveSummary));
|
|
19
|
+
}
|
|
20
|
+
// 3. Sections with embedded diagrams
|
|
21
|
+
for (const section of report.sections) {
|
|
22
|
+
parts.push(this.generateSection(section));
|
|
23
|
+
}
|
|
24
|
+
return parts.join('\n\n');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate report header with rich metadata
|
|
28
|
+
*/
|
|
29
|
+
generateHeader(metadata) {
|
|
30
|
+
let header = `# ${metadata.title}\n\n`;
|
|
31
|
+
// Basic metadata
|
|
32
|
+
header += `**Generated**: ${this.formatDate(metadata.generatedAt)}\n`;
|
|
33
|
+
header += `**Project**: ${metadata.projectPath}\n`;
|
|
34
|
+
// AI Model (if available)
|
|
35
|
+
if (metadata.aiModel) {
|
|
36
|
+
header += `**AI Model**: ${metadata.aiModel}\n`;
|
|
37
|
+
}
|
|
38
|
+
// Analysis Modules
|
|
39
|
+
if (metadata.modules.length > 0) {
|
|
40
|
+
header += `**Analysis Modules**: ${metadata.modules.join(', ')}\n`;
|
|
41
|
+
}
|
|
42
|
+
// Statistics (if available)
|
|
43
|
+
if (metadata.statistics) {
|
|
44
|
+
header += '\n**Statistics**:\n';
|
|
45
|
+
const stats = metadata.statistics;
|
|
46
|
+
if (stats.totalSections !== undefined) {
|
|
47
|
+
header += `- Report Sections: ${stats.totalSections}\n`;
|
|
48
|
+
}
|
|
49
|
+
if (stats.totalDiagrams !== undefined) {
|
|
50
|
+
header += `- Diagrams: ${stats.totalDiagrams}\n`;
|
|
51
|
+
}
|
|
52
|
+
if (stats.totalFindings !== undefined) {
|
|
53
|
+
header += `- Key Findings: ${stats.totalFindings}\n`;
|
|
54
|
+
}
|
|
55
|
+
if (stats.filesAnalyzed !== undefined) {
|
|
56
|
+
header += `- Files Analyzed: ${stats.filesAnalyzed}\n`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
header += '\n---';
|
|
60
|
+
return header;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate executive summary section
|
|
64
|
+
*/
|
|
65
|
+
generateExecutiveSummary(summary) {
|
|
66
|
+
// Check if summary already has markdown formatting
|
|
67
|
+
// If it already starts with "##", use it as-is
|
|
68
|
+
if (summary.trim().startsWith('##')) {
|
|
69
|
+
return `${summary}\n\n---`;
|
|
70
|
+
}
|
|
71
|
+
// Otherwise, wrap it with a heading
|
|
72
|
+
return `## 📊 Executive Summary\n\n${summary}\n\n---`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generate a single section with embedded diagrams
|
|
76
|
+
*/
|
|
77
|
+
generateSection(section) {
|
|
78
|
+
let markdown = section.content;
|
|
79
|
+
// Embed diagrams within the section (not at the end!)
|
|
80
|
+
if (section.diagrams && section.diagrams.length > 0) {
|
|
81
|
+
// Add diagrams at the end of the section content
|
|
82
|
+
markdown += '\n\n';
|
|
83
|
+
for (const diagram of section.diagrams) {
|
|
84
|
+
markdown += this.embedDiagram(diagram) + '\n\n';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return markdown.trim();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Embed a single diagram
|
|
91
|
+
*/
|
|
92
|
+
embedDiagram(diagram) {
|
|
93
|
+
return embedDiagramInMarkdown(diagram);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Format date in a human-readable way
|
|
97
|
+
*/
|
|
98
|
+
formatDate(date) {
|
|
99
|
+
return date.toLocaleString('en-US', {
|
|
100
|
+
year: 'numeric',
|
|
101
|
+
month: 'long',
|
|
102
|
+
day: 'numeric',
|
|
103
|
+
hour: '2-digit',
|
|
104
|
+
minute: '2-digit',
|
|
105
|
+
second: '2-digit',
|
|
106
|
+
hour12: false
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Helper function to extract title from markdown content
|
|
112
|
+
*/
|
|
113
|
+
export function extractTitle(content) {
|
|
114
|
+
// Find first ## heading
|
|
115
|
+
const match = content.match(/^##\s+(.+)$/m);
|
|
116
|
+
if (match) {
|
|
117
|
+
return match[1].trim();
|
|
118
|
+
}
|
|
119
|
+
// Fallback: use first non-empty line
|
|
120
|
+
const firstLine = content.split('\n').find(line => line.trim().length > 0);
|
|
121
|
+
return firstLine?.trim() || 'Untitled Section';
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Helper function to count findings in content
|
|
125
|
+
* Counts bullet points, numbered items, and sections
|
|
126
|
+
*/
|
|
127
|
+
export function countFindings(content) {
|
|
128
|
+
let count = 0;
|
|
129
|
+
// Count bullet points (- or *)
|
|
130
|
+
const bulletPoints = content.match(/^[\s]*[-*]\s+/gm);
|
|
131
|
+
if (bulletPoints) {
|
|
132
|
+
count += bulletPoints.length;
|
|
133
|
+
}
|
|
134
|
+
// Count numbered items (1., 2., etc.)
|
|
135
|
+
const numberedItems = content.match(/^[\s]*\d+\.\s+/gm);
|
|
136
|
+
if (numberedItems) {
|
|
137
|
+
count += numberedItems.length;
|
|
138
|
+
}
|
|
139
|
+
return count;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Helper function to count findings across all sections
|
|
143
|
+
*/
|
|
144
|
+
export function countAllFindings(sections) {
|
|
145
|
+
return sections.reduce((total, section) => {
|
|
146
|
+
return total + countFindings(section.content);
|
|
147
|
+
}, 0);
|
|
148
|
+
}
|