@chappibunny/repolens 0.7.0 → 0.8.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/CHANGELOG.md +13 -0
- package/README.md +28 -17
- package/package.json +1 -1
- package/src/ai/document-plan.js +32 -0
- package/src/analyzers/dependency-graph.js +254 -0
- package/src/analyzers/drift-detector.js +226 -0
- package/src/analyzers/graphql-analyzer.js +261 -0
- package/src/analyzers/typescript-analyzer.js +171 -0
- package/src/core/scan.js +2 -1
- package/src/docs/generate-doc-set.js +51 -3
- package/src/renderers/renderAnalysis.js +408 -0
- package/src/utils/metrics.js +4 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// Renderers for v0.8.0 Extended Analysis documents:
|
|
2
|
+
// - GraphQL Schema
|
|
3
|
+
// - TypeScript Type Graph
|
|
4
|
+
// - Dependency Graph
|
|
5
|
+
// - Architecture Drift
|
|
6
|
+
|
|
7
|
+
export function renderGraphQLSchema(graphqlResult) {
|
|
8
|
+
if (!graphqlResult?.detected) {
|
|
9
|
+
return "# GraphQL Schema\n\nNo GraphQL schema detected in this repository.\n\n" +
|
|
10
|
+
"RepoLens looks for `.graphql`/`.gql` files, inline SDL (gql tagged templates), " +
|
|
11
|
+
"resolver patterns, and GraphQL libraries (Apollo, Yoga, Nexus, Pothos, etc.).\n";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const lines = [];
|
|
15
|
+
lines.push("# GraphQL Schema");
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push(`> ${graphqlResult.summary}`);
|
|
18
|
+
lines.push("");
|
|
19
|
+
|
|
20
|
+
// Libraries
|
|
21
|
+
if (graphqlResult.libraries.length > 0) {
|
|
22
|
+
lines.push("## Libraries & Frameworks");
|
|
23
|
+
lines.push("");
|
|
24
|
+
for (const lib of graphqlResult.libraries) {
|
|
25
|
+
lines.push(`- ${lib}`);
|
|
26
|
+
}
|
|
27
|
+
lines.push("");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Schema files
|
|
31
|
+
if (graphqlResult.schemaFiles.length > 0) {
|
|
32
|
+
lines.push("## Schema Files");
|
|
33
|
+
lines.push("");
|
|
34
|
+
for (const file of graphqlResult.schemaFiles) {
|
|
35
|
+
lines.push(`- \`${file}\``);
|
|
36
|
+
}
|
|
37
|
+
lines.push("");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Queries
|
|
41
|
+
if (graphqlResult.queries.length > 0) {
|
|
42
|
+
lines.push("## Queries");
|
|
43
|
+
lines.push("");
|
|
44
|
+
lines.push("| Query | Return Type | Source |");
|
|
45
|
+
lines.push("|-------|-------------|--------|");
|
|
46
|
+
for (const q of graphqlResult.queries) {
|
|
47
|
+
lines.push(`| \`${q.name}\` | \`${q.type}\` | \`${q.source}\` |`);
|
|
48
|
+
}
|
|
49
|
+
lines.push("");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Mutations
|
|
53
|
+
if (graphqlResult.mutations.length > 0) {
|
|
54
|
+
lines.push("## Mutations");
|
|
55
|
+
lines.push("");
|
|
56
|
+
lines.push("| Mutation | Return Type | Source |");
|
|
57
|
+
lines.push("|----------|-------------|--------|");
|
|
58
|
+
for (const m of graphqlResult.mutations) {
|
|
59
|
+
lines.push(`| \`${m.name}\` | \`${m.type}\` | \`${m.source}\` |`);
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Subscriptions
|
|
65
|
+
if (graphqlResult.subscriptions.length > 0) {
|
|
66
|
+
lines.push("## Subscriptions");
|
|
67
|
+
lines.push("");
|
|
68
|
+
lines.push("| Subscription | Return Type | Source |");
|
|
69
|
+
lines.push("|--------------|-------------|--------|");
|
|
70
|
+
for (const s of graphqlResult.subscriptions) {
|
|
71
|
+
lines.push(`| \`${s.name}\` | \`${s.type}\` | \`${s.source}\` |`);
|
|
72
|
+
}
|
|
73
|
+
lines.push("");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Object Types
|
|
77
|
+
if (graphqlResult.types.length > 0) {
|
|
78
|
+
lines.push("## Object Types");
|
|
79
|
+
lines.push("");
|
|
80
|
+
for (const t of graphqlResult.types) {
|
|
81
|
+
const impl = t.implements?.length ? ` (implements ${t.implements.join(", ")})` : "";
|
|
82
|
+
lines.push(`### \`${t.name}\`${impl}`);
|
|
83
|
+
lines.push("");
|
|
84
|
+
if (t.fields.length > 0) {
|
|
85
|
+
lines.push("| Field | Type |");
|
|
86
|
+
lines.push("|-------|------|");
|
|
87
|
+
for (const f of t.fields) {
|
|
88
|
+
lines.push(`| \`${f.name}\` | \`${f.type}\` |`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
lines.push(`\n*Source: \`${t.source}\`*\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Enums
|
|
96
|
+
if (graphqlResult.enums.length > 0) {
|
|
97
|
+
lines.push("## Enums");
|
|
98
|
+
lines.push("");
|
|
99
|
+
for (const e of graphqlResult.enums) {
|
|
100
|
+
lines.push(`- **${e.name}**: ${e.values.join(", ")}`);
|
|
101
|
+
}
|
|
102
|
+
lines.push("");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Input Types
|
|
106
|
+
if (graphqlResult.inputs.length > 0) {
|
|
107
|
+
lines.push("## Input Types");
|
|
108
|
+
lines.push("");
|
|
109
|
+
for (const i of graphqlResult.inputs) {
|
|
110
|
+
lines.push(`### \`${i.name}\``);
|
|
111
|
+
lines.push("");
|
|
112
|
+
if (i.fields.length > 0) {
|
|
113
|
+
lines.push("| Field | Type |");
|
|
114
|
+
lines.push("|-------|------|");
|
|
115
|
+
for (const f of i.fields) {
|
|
116
|
+
lines.push(`| \`${f.name}\` | \`${f.type}\` |`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
lines.push("");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Interfaces
|
|
124
|
+
if (graphqlResult.interfaces.length > 0) {
|
|
125
|
+
lines.push("## Interfaces");
|
|
126
|
+
lines.push("");
|
|
127
|
+
for (const iface of graphqlResult.interfaces) {
|
|
128
|
+
lines.push(`### \`${iface.name}\``);
|
|
129
|
+
lines.push("");
|
|
130
|
+
if (iface.fields.length > 0) {
|
|
131
|
+
lines.push("| Field | Type |");
|
|
132
|
+
lines.push("|-------|------|");
|
|
133
|
+
for (const f of iface.fields) {
|
|
134
|
+
lines.push(`| \`${f.name}\` | \`${f.type}\` |`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
lines.push("");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Unions
|
|
142
|
+
if (graphqlResult.unions.length > 0) {
|
|
143
|
+
lines.push("## Unions");
|
|
144
|
+
lines.push("");
|
|
145
|
+
for (const u of graphqlResult.unions) {
|
|
146
|
+
lines.push(`- **${u.name}** = ${u.members.join(" | ")}`);
|
|
147
|
+
}
|
|
148
|
+
lines.push("");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Resolver Files
|
|
152
|
+
if (graphqlResult.resolverFiles.length > 0) {
|
|
153
|
+
lines.push("## Resolver Files");
|
|
154
|
+
lines.push("");
|
|
155
|
+
for (const file of graphqlResult.resolverFiles) {
|
|
156
|
+
lines.push(`- \`${file}\``);
|
|
157
|
+
}
|
|
158
|
+
lines.push("");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function renderTypeGraph(tsResult) {
|
|
165
|
+
if (!tsResult?.detected) {
|
|
166
|
+
return "# TypeScript Type Graph\n\nNo TypeScript type declarations detected in this repository.\n\n" +
|
|
167
|
+
"RepoLens looks for `.ts`/`.tsx` files containing interfaces, type aliases, classes, and enums.\n";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const lines = [];
|
|
171
|
+
lines.push("# TypeScript Type Graph");
|
|
172
|
+
lines.push("");
|
|
173
|
+
lines.push(`> ${tsResult.summary}`);
|
|
174
|
+
lines.push("");
|
|
175
|
+
|
|
176
|
+
// Interfaces
|
|
177
|
+
if (tsResult.interfaces.length > 0) {
|
|
178
|
+
lines.push("## Interfaces");
|
|
179
|
+
lines.push("");
|
|
180
|
+
lines.push("| Interface | Extends | Source |");
|
|
181
|
+
lines.push("|-----------|---------|--------|");
|
|
182
|
+
for (const iface of tsResult.interfaces) {
|
|
183
|
+
const ext = iface.extends.length > 0 ? iface.extends.join(", ") : "—";
|
|
184
|
+
lines.push(`| \`${iface.name}\` | ${ext} | \`${iface.source}\` |`);
|
|
185
|
+
}
|
|
186
|
+
lines.push("");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Classes
|
|
190
|
+
if (tsResult.classes.length > 0) {
|
|
191
|
+
lines.push("## Classes");
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push("| Class | Extends | Implements | Source |");
|
|
194
|
+
lines.push("|-------|---------|------------|--------|");
|
|
195
|
+
for (const cls of tsResult.classes) {
|
|
196
|
+
const ext = cls.extends || "—";
|
|
197
|
+
const impl = cls.implements.length > 0 ? cls.implements.join(", ") : "—";
|
|
198
|
+
lines.push(`| \`${cls.name}\` | ${ext} | ${impl} | \`${cls.source}\` |`);
|
|
199
|
+
}
|
|
200
|
+
lines.push("");
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Type Aliases
|
|
204
|
+
if (tsResult.typeAliases.length > 0) {
|
|
205
|
+
lines.push("## Type Aliases");
|
|
206
|
+
lines.push("");
|
|
207
|
+
lines.push("| Type | References | Source |");
|
|
208
|
+
lines.push("|------|------------|--------|");
|
|
209
|
+
for (const t of tsResult.typeAliases) {
|
|
210
|
+
const refs = t.refs.length > 0 ? t.refs.join(", ") : "—";
|
|
211
|
+
lines.push(`| \`${t.name}\` | ${refs} | \`${t.source}\` |`);
|
|
212
|
+
}
|
|
213
|
+
lines.push("");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Enums
|
|
217
|
+
if (tsResult.enums.length > 0) {
|
|
218
|
+
lines.push("## Enums");
|
|
219
|
+
lines.push("");
|
|
220
|
+
lines.push("| Enum | Source |");
|
|
221
|
+
lines.push("|------|--------|");
|
|
222
|
+
for (const e of tsResult.enums) {
|
|
223
|
+
lines.push(`| \`${e.name}\` | \`${e.source}\` |`);
|
|
224
|
+
}
|
|
225
|
+
lines.push("");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Relationship Graph (Unicode)
|
|
229
|
+
if (tsResult.relationships.length > 0) {
|
|
230
|
+
lines.push("## Type Relationships");
|
|
231
|
+
lines.push("");
|
|
232
|
+
lines.push("```");
|
|
233
|
+
// Group by source type
|
|
234
|
+
const byFrom = new Map();
|
|
235
|
+
for (const rel of tsResult.relationships) {
|
|
236
|
+
if (!byFrom.has(rel.from)) byFrom.set(rel.from, []);
|
|
237
|
+
byFrom.get(rel.from).push(rel);
|
|
238
|
+
}
|
|
239
|
+
for (const [from, rels] of byFrom) {
|
|
240
|
+
lines.push(`${from}`);
|
|
241
|
+
for (let i = 0; i < rels.length; i++) {
|
|
242
|
+
const connector = i === rels.length - 1 ? "└──" : "├──";
|
|
243
|
+
const arrow = rels[i].type === "extends" ? "extends" :
|
|
244
|
+
rels[i].type === "implements" ? "implements" :
|
|
245
|
+
rels[i].type === "references" ? "uses" : rels[i].type;
|
|
246
|
+
lines.push(` ${connector} ${arrow} → ${rels[i].to}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
lines.push("```");
|
|
250
|
+
lines.push("");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return lines.join("\n");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function renderDependencyGraph(depResult) {
|
|
257
|
+
if (!depResult?.nodes?.length) {
|
|
258
|
+
return "# Dependency Graph\n\nNo code files found to analyze for dependencies.\n";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const lines = [];
|
|
262
|
+
lines.push("# Dependency Graph");
|
|
263
|
+
lines.push("");
|
|
264
|
+
lines.push(`> ${depResult.summary}`);
|
|
265
|
+
lines.push("");
|
|
266
|
+
|
|
267
|
+
// Stats overview
|
|
268
|
+
lines.push("## Overview");
|
|
269
|
+
lines.push("");
|
|
270
|
+
const s = depResult.stats;
|
|
271
|
+
lines.push(`| Metric | Value |`);
|
|
272
|
+
lines.push(`|--------|-------|`);
|
|
273
|
+
lines.push(`| Source files | ${s.totalFiles} |`);
|
|
274
|
+
lines.push(`| Import edges | ${s.totalEdges} |`);
|
|
275
|
+
lines.push(`| External packages | ${s.externalDeps} |`);
|
|
276
|
+
lines.push(`| Circular dependencies | ${s.cycles} |`);
|
|
277
|
+
lines.push(`| Orphan files | ${s.orphanFiles} |`);
|
|
278
|
+
lines.push("");
|
|
279
|
+
|
|
280
|
+
// Hub modules (most imported)
|
|
281
|
+
if (s.hubs.length > 0) {
|
|
282
|
+
lines.push("## Hub Modules (Most Imported)");
|
|
283
|
+
lines.push("");
|
|
284
|
+
lines.push("| Module | Imported By |");
|
|
285
|
+
lines.push("|--------|-------------|");
|
|
286
|
+
for (const hub of s.hubs) {
|
|
287
|
+
lines.push(`| \`${hub.key}\` | ${hub.importedBy} files |`);
|
|
288
|
+
}
|
|
289
|
+
lines.push("");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Circular dependencies
|
|
293
|
+
if (depResult.cycles.length > 0) {
|
|
294
|
+
lines.push("## ⚠️ Circular Dependencies");
|
|
295
|
+
lines.push("");
|
|
296
|
+
lines.push("The following circular dependency chains were detected:");
|
|
297
|
+
lines.push("");
|
|
298
|
+
for (let i = 0; i < Math.min(depResult.cycles.length, 20); i++) {
|
|
299
|
+
const cycle = depResult.cycles[i];
|
|
300
|
+
lines.push(`${i + 1}. \`${cycle.join("` → `")}\``);
|
|
301
|
+
}
|
|
302
|
+
if (depResult.cycles.length > 20) {
|
|
303
|
+
lines.push(`\n*...and ${depResult.cycles.length - 20} more cycles*`);
|
|
304
|
+
}
|
|
305
|
+
lines.push("");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// External dependencies
|
|
309
|
+
if (depResult.externalDeps.length > 0) {
|
|
310
|
+
lines.push("## External Dependencies");
|
|
311
|
+
lines.push("");
|
|
312
|
+
const cols = 3;
|
|
313
|
+
const perCol = Math.ceil(depResult.externalDeps.length / cols);
|
|
314
|
+
for (const dep of depResult.externalDeps) {
|
|
315
|
+
lines.push(`- \`${dep}\``);
|
|
316
|
+
}
|
|
317
|
+
lines.push("");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return lines.join("\n");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function renderArchitectureDrift(driftResult) {
|
|
324
|
+
const lines = [];
|
|
325
|
+
lines.push("# Architecture Drift Report");
|
|
326
|
+
lines.push("");
|
|
327
|
+
|
|
328
|
+
if (!driftResult.hasBaseline) {
|
|
329
|
+
lines.push("> " + driftResult.summary);
|
|
330
|
+
lines.push("");
|
|
331
|
+
lines.push("Once a baseline is established, this report will track structural changes including:");
|
|
332
|
+
lines.push("- New/removed modules and API endpoints");
|
|
333
|
+
lines.push("- Dependency shifts and circular dependency trends");
|
|
334
|
+
lines.push("- Framework and technology stack changes");
|
|
335
|
+
lines.push("- GraphQL schema evolution");
|
|
336
|
+
lines.push("- Overall codebase scale changes");
|
|
337
|
+
lines.push("");
|
|
338
|
+
return lines.join("\n");
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
lines.push(`> Compared against baseline from **${driftResult.baselineTimestamp}**`);
|
|
342
|
+
lines.push("");
|
|
343
|
+
lines.push(`**${driftResult.summary}**`);
|
|
344
|
+
lines.push("");
|
|
345
|
+
|
|
346
|
+
if (driftResult.drifts.length === 0) {
|
|
347
|
+
lines.push("✅ No architecture drift detected. The codebase structure matches the baseline.");
|
|
348
|
+
lines.push("");
|
|
349
|
+
return lines.join("\n");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Group drifts by severity
|
|
353
|
+
const critical = driftResult.drifts.filter(d => d.severity === "critical");
|
|
354
|
+
const warnings = driftResult.drifts.filter(d => d.severity === "warning");
|
|
355
|
+
const infos = driftResult.drifts.filter(d => d.severity === "info");
|
|
356
|
+
|
|
357
|
+
if (critical.length > 0) {
|
|
358
|
+
lines.push("## 🔴 Critical Changes");
|
|
359
|
+
lines.push("");
|
|
360
|
+
for (const drift of critical) {
|
|
361
|
+
lines.push(`### ${formatCategoryLabel(drift.category)} — ${drift.type}`);
|
|
362
|
+
for (const item of drift.items) {
|
|
363
|
+
lines.push(`- ${item}`);
|
|
364
|
+
}
|
|
365
|
+
lines.push("");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (warnings.length > 0) {
|
|
370
|
+
lines.push("## 🟡 Warnings");
|
|
371
|
+
lines.push("");
|
|
372
|
+
for (const drift of warnings) {
|
|
373
|
+
lines.push(`### ${formatCategoryLabel(drift.category)} — ${drift.type}`);
|
|
374
|
+
for (const item of drift.items) {
|
|
375
|
+
lines.push(`- ${item}`);
|
|
376
|
+
}
|
|
377
|
+
lines.push("");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (infos.length > 0) {
|
|
382
|
+
lines.push("## 🟢 Informational");
|
|
383
|
+
lines.push("");
|
|
384
|
+
for (const drift of infos) {
|
|
385
|
+
lines.push(`### ${formatCategoryLabel(drift.category)} — ${drift.type}`);
|
|
386
|
+
for (const item of drift.items) {
|
|
387
|
+
lines.push(`- ${item}`);
|
|
388
|
+
}
|
|
389
|
+
lines.push("");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return lines.join("\n");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function formatCategoryLabel(category) {
|
|
397
|
+
const labels = {
|
|
398
|
+
modules: "Modules",
|
|
399
|
+
api: "API Endpoints",
|
|
400
|
+
pages: "Pages",
|
|
401
|
+
dependencies: "Dependencies",
|
|
402
|
+
frameworks: "Frameworks",
|
|
403
|
+
cycles: "Circular Dependencies",
|
|
404
|
+
graphql: "GraphQL Schema",
|
|
405
|
+
scale: "Codebase Scale",
|
|
406
|
+
};
|
|
407
|
+
return labels[category] || category;
|
|
408
|
+
}
|