@chappibunny/repolens 0.7.0 → 0.9.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.
@@ -0,0 +1,509 @@
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 [
10
+ "# GraphQL Schema",
11
+ "",
12
+ "> No GraphQL schema was detected in this repository.",
13
+ "",
14
+ "RepoLens scans for `.graphql` and `.gql` schema files, inline SDL via tagged template literals (e.g. `gql\\`...\\``), resolver patterns, and popular GraphQL libraries including Apollo, Yoga, Nexus, Pothos, Mercurius, type-graphql, Relay, and urql.",
15
+ "",
16
+ "If your project uses GraphQL, ensure the relevant source directories are included in your `scan.include` configuration.",
17
+ ""
18
+ ].join("\n");
19
+ }
20
+
21
+ const lines = [];
22
+ lines.push("# GraphQL Schema");
23
+ lines.push("");
24
+ lines.push(`> ${graphqlResult.summary}`);
25
+ lines.push("");
26
+
27
+ // Libraries
28
+ if (graphqlResult.libraries.length > 0) {
29
+ lines.push("## Libraries and Frameworks");
30
+ lines.push("");
31
+ lines.push("The following GraphQL libraries and frameworks were detected in the project dependencies:");
32
+ lines.push("");
33
+ lines.push("| Library | Status |");
34
+ lines.push("|---------|--------|");
35
+ for (const lib of graphqlResult.libraries) {
36
+ lines.push(`| ${lib} | Detected |`);
37
+ }
38
+ lines.push("");
39
+ }
40
+
41
+ // Schema files
42
+ if (graphqlResult.schemaFiles.length > 0) {
43
+ lines.push("## Schema Files");
44
+ lines.push("");
45
+ lines.push(`${graphqlResult.schemaFiles.length} schema file${graphqlResult.schemaFiles.length === 1 ? " was" : "s were"} found containing GraphQL type definitions:`);
46
+ lines.push("");
47
+ for (const file of graphqlResult.schemaFiles) {
48
+ lines.push(`- \`${file}\``);
49
+ }
50
+ lines.push("");
51
+ }
52
+
53
+ // Queries
54
+ if (graphqlResult.queries.length > 0) {
55
+ lines.push("## Queries");
56
+ lines.push("");
57
+ lines.push(`The schema defines **${graphqlResult.queries.length}** read operation${graphqlResult.queries.length === 1 ? "" : "s"} for fetching data:`);
58
+ lines.push("");
59
+ lines.push("| Query | Return Type | Source |");
60
+ lines.push("|-------|-------------|--------|");
61
+ for (const q of graphqlResult.queries) {
62
+ lines.push(`| \`${q.name}\` | \`${q.type}\` | \`${q.source}\` |`);
63
+ }
64
+ lines.push("");
65
+ }
66
+
67
+ // Mutations
68
+ if (graphqlResult.mutations.length > 0) {
69
+ lines.push("## Mutations");
70
+ lines.push("");
71
+ lines.push(`The schema defines **${graphqlResult.mutations.length}** write operation${graphqlResult.mutations.length === 1 ? "" : "s"} for modifying data:`);
72
+ lines.push("");
73
+ lines.push("| Mutation | Return Type | Source |");
74
+ lines.push("|----------|-------------|--------|");
75
+ for (const m of graphqlResult.mutations) {
76
+ lines.push(`| \`${m.name}\` | \`${m.type}\` | \`${m.source}\` |`);
77
+ }
78
+ lines.push("");
79
+ }
80
+
81
+ // Subscriptions
82
+ if (graphqlResult.subscriptions.length > 0) {
83
+ lines.push("## Subscriptions");
84
+ lines.push("");
85
+ lines.push(`**${graphqlResult.subscriptions.length}** real-time subscription${graphqlResult.subscriptions.length === 1 ? "" : "s"} for streaming data:`);
86
+ lines.push("");
87
+ lines.push("| Subscription | Return Type | Source |");
88
+ lines.push("|--------------|-------------|--------|");
89
+ for (const s of graphqlResult.subscriptions) {
90
+ lines.push(`| \`${s.name}\` | \`${s.type}\` | \`${s.source}\` |`);
91
+ }
92
+ lines.push("");
93
+ }
94
+
95
+ // Object Types
96
+ if (graphqlResult.types.length > 0) {
97
+ lines.push("## Object Types");
98
+ lines.push("");
99
+ lines.push(`The schema defines **${graphqlResult.types.length}** object type${graphqlResult.types.length === 1 ? "" : "s"}, each representing a structured data entity:`);
100
+ lines.push("");
101
+ for (const t of graphqlResult.types) {
102
+ const impl = t.implements?.length ? ` (implements ${t.implements.join(", ")})` : "";
103
+ lines.push(`### \`${t.name}\`${impl}`);
104
+ lines.push("");
105
+ if (t.fields.length > 0) {
106
+ lines.push("| Field | Type |");
107
+ lines.push("|-------|------|");
108
+ for (const f of t.fields) {
109
+ lines.push(`| \`${f.name}\` | \`${f.type}\` |`);
110
+ }
111
+ }
112
+ lines.push(`\n*Source: \`${t.source}\`*\n`);
113
+ }
114
+ }
115
+
116
+ // Enums
117
+ if (graphqlResult.enums.length > 0) {
118
+ lines.push("## Enums");
119
+ lines.push("");
120
+ lines.push("Enumeration types constrain a field to a predefined set of values:");
121
+ lines.push("");
122
+ lines.push("| Enum | Values |");
123
+ lines.push("|------|--------|");
124
+ for (const e of graphqlResult.enums) {
125
+ lines.push(`| \`${e.name}\` | ${e.values.map(v => `\`${v}\``).join(", ")} |`);
126
+ }
127
+ lines.push("");
128
+ }
129
+
130
+ // Input Types
131
+ if (graphqlResult.inputs.length > 0) {
132
+ lines.push("## Input Types");
133
+ lines.push("");
134
+ lines.push("Input types define the shape of arguments passed to mutations and queries:");
135
+ lines.push("");
136
+ for (const i of graphqlResult.inputs) {
137
+ lines.push(`### \`${i.name}\``);
138
+ lines.push("");
139
+ if (i.fields.length > 0) {
140
+ lines.push("| Field | Type |");
141
+ lines.push("|-------|------|");
142
+ for (const f of i.fields) {
143
+ lines.push(`| \`${f.name}\` | \`${f.type}\` |`);
144
+ }
145
+ }
146
+ lines.push("");
147
+ }
148
+ }
149
+
150
+ // Interfaces
151
+ if (graphqlResult.interfaces.length > 0) {
152
+ lines.push("## Interfaces");
153
+ lines.push("");
154
+ lines.push("Interfaces define shared field contracts that object types must implement:");
155
+ lines.push("");
156
+ for (const iface of graphqlResult.interfaces) {
157
+ lines.push(`### \`${iface.name}\``);
158
+ lines.push("");
159
+ if (iface.fields.length > 0) {
160
+ lines.push("| Field | Type |");
161
+ lines.push("|-------|------|");
162
+ for (const f of iface.fields) {
163
+ lines.push(`| \`${f.name}\` | \`${f.type}\` |`);
164
+ }
165
+ }
166
+ lines.push("");
167
+ }
168
+ }
169
+
170
+ // Unions
171
+ if (graphqlResult.unions.length > 0) {
172
+ lines.push("## Union Types");
173
+ lines.push("");
174
+ lines.push("Union types represent values that could be one of several object types:");
175
+ lines.push("");
176
+ lines.push("| Union | Possible Types |");
177
+ lines.push("|-------|---------------|");
178
+ for (const u of graphqlResult.unions) {
179
+ lines.push(`| \`${u.name}\` | ${u.members.map(m => `\`${m}\``).join(", ")} |`);
180
+ }
181
+ lines.push("");
182
+ }
183
+
184
+ // Resolver Files
185
+ if (graphqlResult.resolverFiles.length > 0) {
186
+ lines.push("## Resolver Files");
187
+ lines.push("");
188
+ lines.push("These files contain resolver implementations that connect schema operations to data sources:");
189
+ lines.push("");
190
+ for (const file of graphqlResult.resolverFiles) {
191
+ lines.push(`- \`${file}\``);
192
+ }
193
+ lines.push("");
194
+ }
195
+
196
+ lines.push("---");
197
+ lines.push("");
198
+ lines.push("*Generated by RepoLens extended analysis. Schema detection is based on static analysis of source files and may not capture runtime-only definitions.*");
199
+ lines.push("");
200
+
201
+ return lines.join("\n");
202
+ }
203
+
204
+ export function renderTypeGraph(tsResult) {
205
+ if (!tsResult?.detected) {
206
+ return [
207
+ "# TypeScript Type Graph",
208
+ "",
209
+ "> No TypeScript type declarations were detected in this repository.",
210
+ "",
211
+ "RepoLens analyzes `.ts` and `.tsx` files for interface declarations, type aliases, classes, and enums. Ensure your TypeScript source directories are included in the `scan.include` configuration.",
212
+ ""
213
+ ].join("\n");
214
+ }
215
+
216
+ const lines = [];
217
+ lines.push("# TypeScript Type Graph");
218
+ lines.push("");
219
+ lines.push(`> ${tsResult.summary}`);
220
+ lines.push("");
221
+ lines.push("This document maps the type system of the project, showing how interfaces, classes, and type aliases relate to one another. Understanding these relationships helps navigate the codebase and identify coupling between modules.");
222
+ lines.push("");
223
+
224
+ // Interfaces
225
+ if (tsResult.interfaces.length > 0) {
226
+ lines.push("## Interfaces");
227
+ lines.push("");
228
+ lines.push(`**${tsResult.interfaces.length}** interface${tsResult.interfaces.length === 1 ? "" : "s"} define the data contracts used across the application:`);
229
+ lines.push("");
230
+ lines.push("| Interface | Extends | Source |");
231
+ lines.push("|-----------|---------|--------|");
232
+ for (const iface of tsResult.interfaces) {
233
+ const ext = iface.extends.length > 0 ? iface.extends.join(", ") : "—";
234
+ lines.push(`| \`${iface.name}\` | ${ext} | \`${iface.source}\` |`);
235
+ }
236
+ lines.push("");
237
+ }
238
+
239
+ // Classes
240
+ if (tsResult.classes.length > 0) {
241
+ lines.push("## Classes");
242
+ lines.push("");
243
+ lines.push(`**${tsResult.classes.length}** class${tsResult.classes.length === 1 ? "" : "es"} provide concrete implementations:`);
244
+ lines.push("");
245
+ lines.push("| Class | Extends | Implements | Source |");
246
+ lines.push("|-------|---------|------------|--------|");
247
+ for (const cls of tsResult.classes) {
248
+ const ext = cls.extends || "—";
249
+ const impl = cls.implements.length > 0 ? cls.implements.join(", ") : "—";
250
+ lines.push(`| \`${cls.name}\` | ${ext} | ${impl} | \`${cls.source}\` |`);
251
+ }
252
+ lines.push("");
253
+ }
254
+
255
+ // Type Aliases
256
+ if (tsResult.typeAliases.length > 0) {
257
+ lines.push("## Type Aliases");
258
+ lines.push("");
259
+ lines.push(`**${tsResult.typeAliases.length}** type alias${tsResult.typeAliases.length === 1 ? "" : "es"} define computed or composite types:`);
260
+ lines.push("");
261
+ lines.push("| Type | References | Source |");
262
+ lines.push("|------|------------|--------|");
263
+ for (const t of tsResult.typeAliases) {
264
+ const refs = t.refs.length > 0 ? t.refs.join(", ") : "—";
265
+ lines.push(`| \`${t.name}\` | ${refs} | \`${t.source}\` |`);
266
+ }
267
+ lines.push("");
268
+ }
269
+
270
+ // Enums
271
+ if (tsResult.enums.length > 0) {
272
+ lines.push("## Enums");
273
+ lines.push("");
274
+ lines.push(`**${tsResult.enums.length}** enum${tsResult.enums.length === 1 ? "" : "s"} define named constant sets:`);
275
+ lines.push("");
276
+ lines.push("| Enum | Source |");
277
+ lines.push("|------|--------|");
278
+ for (const e of tsResult.enums) {
279
+ lines.push(`| \`${e.name}\` | \`${e.source}\` |`);
280
+ }
281
+ lines.push("");
282
+ }
283
+
284
+ // Relationship Graph (Unicode)
285
+ if (tsResult.relationships.length > 0) {
286
+ lines.push("## Type Relationships");
287
+ lines.push("");
288
+ lines.push("The following diagram shows inheritance, implementation, and reference relationships between types:");
289
+ lines.push("");
290
+ lines.push("```");
291
+ const byFrom = new Map();
292
+ for (const rel of tsResult.relationships) {
293
+ if (!byFrom.has(rel.from)) byFrom.set(rel.from, []);
294
+ byFrom.get(rel.from).push(rel);
295
+ }
296
+ for (const [from, rels] of byFrom) {
297
+ lines.push(`${from}`);
298
+ for (let i = 0; i < rels.length; i++) {
299
+ const connector = i === rels.length - 1 ? "└──" : "├──";
300
+ const arrow = rels[i].type === "extends" ? "extends" :
301
+ rels[i].type === "implements" ? "implements" :
302
+ rels[i].type === "references" ? "uses" : rels[i].type;
303
+ lines.push(` ${connector} ${arrow} → ${rels[i].to}`);
304
+ }
305
+ }
306
+ lines.push("```");
307
+ lines.push("");
308
+ }
309
+
310
+ lines.push("---");
311
+ lines.push("");
312
+ lines.push("*Generated by RepoLens static analysis. Type detection uses regex-based parsing and may not capture all advanced TypeScript patterns.*");
313
+ lines.push("");
314
+
315
+ return lines.join("\n");
316
+ }
317
+
318
+ export function renderDependencyGraph(depResult) {
319
+ if (!depResult?.nodes?.length) {
320
+ return [
321
+ "# Dependency Graph",
322
+ "",
323
+ "> No source files were found to analyze for import dependencies.",
324
+ "",
325
+ "Ensure your `scan.include` patterns cover the relevant source directories.",
326
+ ""
327
+ ].join("\n");
328
+ }
329
+
330
+ const lines = [];
331
+ lines.push("# Dependency Graph");
332
+ lines.push("");
333
+ lines.push(`> ${depResult.summary}`);
334
+ lines.push("");
335
+ lines.push("This document maps every import relationship in the codebase, identifies the most-connected modules, and flags circular dependencies that may complicate refactoring or increase build times.");
336
+ lines.push("");
337
+
338
+ // Stats overview
339
+ const s = depResult.stats;
340
+ lines.push("## Overview");
341
+ lines.push("");
342
+ lines.push(`| Metric | Value |`);
343
+ lines.push(`|--------|-------|`);
344
+ lines.push(`| Source files | ${s.totalFiles} |`);
345
+ lines.push(`| Import edges | ${s.totalEdges} |`);
346
+ lines.push(`| External packages | ${s.externalDeps} |`);
347
+ lines.push(`| Circular dependencies | ${s.cycles} |`);
348
+ lines.push(`| Orphan files (no imports or importers) | ${s.orphanFiles} |`);
349
+ lines.push("");
350
+
351
+ // Hub modules (most imported)
352
+ if (s.hubs.length > 0) {
353
+ lines.push("## Hub Modules");
354
+ lines.push("");
355
+ lines.push("These are the most-imported modules in the codebase. Changes to hub modules have the widest blast radius and should be reviewed carefully:");
356
+ lines.push("");
357
+ lines.push("| Module | Imported By |");
358
+ lines.push("|--------|-------------|");
359
+ for (const hub of s.hubs) {
360
+ lines.push(`| \`${hub.key}\` | ${hub.importedBy} files |`);
361
+ }
362
+ lines.push("");
363
+ }
364
+
365
+ // Circular dependencies
366
+ if (depResult.cycles.length > 0) {
367
+ lines.push("## Circular Dependencies");
368
+ lines.push("");
369
+ lines.push(`**${depResult.cycles.length}** circular dependency chain${depResult.cycles.length === 1 ? " was" : "s were"} detected. Circular imports can cause initialization errors, increase bundle sizes, and make modules harder to test in isolation. Consider refactoring shared logic into a separate module.`);
370
+ lines.push("");
371
+ for (let i = 0; i < Math.min(depResult.cycles.length, 20); i++) {
372
+ const cycle = depResult.cycles[i];
373
+ lines.push(`${i + 1}. \`${cycle.join("` → `")}\``);
374
+ }
375
+ if (depResult.cycles.length > 20) {
376
+ lines.push(`\n*...and ${depResult.cycles.length - 20} more cycles*`);
377
+ }
378
+ lines.push("");
379
+ }
380
+
381
+ // External dependencies
382
+ if (depResult.externalDeps.length > 0) {
383
+ lines.push("## External Dependencies");
384
+ lines.push("");
385
+ lines.push(`The codebase imports **${depResult.externalDeps.length}** external package${depResult.externalDeps.length === 1 ? "" : "s"}. These are third-party modules resolved from \`node_modules\`:`);
386
+ lines.push("");
387
+ // Render as a compact table for cleaner output
388
+ const sorted = [...depResult.externalDeps].sort();
389
+ lines.push("| Package |");
390
+ lines.push("|---------|");
391
+ for (const dep of sorted) {
392
+ lines.push(`| \`${dep}\` |`);
393
+ }
394
+ lines.push("");
395
+ }
396
+
397
+ lines.push("---");
398
+ lines.push("");
399
+ lines.push("*Generated by RepoLens import analysis. Dependency detection covers ES module imports, dynamic imports, CommonJS require, and re-exports.*");
400
+ lines.push("");
401
+
402
+ return lines.join("\n");
403
+ }
404
+
405
+ export function renderArchitectureDrift(driftResult) {
406
+ const lines = [];
407
+ lines.push("# Architecture Drift Report");
408
+ lines.push("");
409
+
410
+ if (!driftResult.hasBaseline) {
411
+ lines.push(`> ${driftResult.summary}`);
412
+ lines.push("");
413
+ lines.push("A baseline snapshot of the current architecture has been saved. On subsequent runs, this report will track structural changes across the following dimensions:");
414
+ lines.push("");
415
+ lines.push("| Dimension | What is Tracked |");
416
+ lines.push("|-----------|----------------|");
417
+ lines.push("| Modules | New, removed, or significantly resized modules |");
418
+ lines.push("| API Endpoints | Added or removed backend routes |");
419
+ lines.push("| Dependencies | Changes to external package imports |");
420
+ lines.push("| Frameworks | Technology stack additions or removals |");
421
+ lines.push("| Circular Dependencies | Increases in dependency cycles |");
422
+ lines.push("| GraphQL Schema | Type additions or removals |");
423
+ lines.push("| Codebase Scale | Overall file count changes |");
424
+ lines.push("");
425
+ return lines.join("\n");
426
+ }
427
+
428
+ lines.push(`> Compared against baseline from **${driftResult.baselineTimestamp}**`);
429
+ lines.push("");
430
+ lines.push(`**${driftResult.summary}**`);
431
+ lines.push("");
432
+
433
+ if (driftResult.drifts.length === 0) {
434
+ lines.push("No architecture drift detected. The codebase structure matches the stored baseline across all tracked dimensions.");
435
+ lines.push("");
436
+ return lines.join("\n");
437
+ }
438
+
439
+ // Group drifts by severity
440
+ const critical = driftResult.drifts.filter(d => d.severity === "critical");
441
+ const warnings = driftResult.drifts.filter(d => d.severity === "warning");
442
+ const infos = driftResult.drifts.filter(d => d.severity === "info");
443
+
444
+ if (critical.length > 0) {
445
+ lines.push("## Critical Changes");
446
+ lines.push("");
447
+ lines.push("These changes may indicate significant architectural shifts that warrant team discussion:");
448
+ lines.push("");
449
+ for (const drift of critical) {
450
+ lines.push(`### ${formatCategoryLabel(drift.category)} — ${drift.type}`);
451
+ lines.push("");
452
+ for (const item of drift.items) {
453
+ lines.push(`- ${item}`);
454
+ }
455
+ lines.push("");
456
+ }
457
+ }
458
+
459
+ if (warnings.length > 0) {
460
+ lines.push("## Warnings");
461
+ lines.push("");
462
+ lines.push("These changes are notable and should be reviewed to ensure they are intentional:");
463
+ lines.push("");
464
+ for (const drift of warnings) {
465
+ lines.push(`### ${formatCategoryLabel(drift.category)} — ${drift.type}`);
466
+ lines.push("");
467
+ for (const item of drift.items) {
468
+ lines.push(`- ${item}`);
469
+ }
470
+ lines.push("");
471
+ }
472
+ }
473
+
474
+ if (infos.length > 0) {
475
+ lines.push("## Informational");
476
+ lines.push("");
477
+ lines.push("Routine changes that reflect normal development activity:");
478
+ lines.push("");
479
+ for (const drift of infos) {
480
+ lines.push(`### ${formatCategoryLabel(drift.category)} — ${drift.type}`);
481
+ lines.push("");
482
+ for (const item of drift.items) {
483
+ lines.push(`- ${item}`);
484
+ }
485
+ lines.push("");
486
+ }
487
+ }
488
+
489
+ lines.push("---");
490
+ lines.push("");
491
+ lines.push("*Generated by RepoLens drift detection. The baseline is updated automatically after each publish.*");
492
+ lines.push("");
493
+
494
+ return lines.join("\n");
495
+ }
496
+
497
+ function formatCategoryLabel(category) {
498
+ const labels = {
499
+ modules: "Modules",
500
+ api: "API Endpoints",
501
+ pages: "Pages",
502
+ dependencies: "Dependencies",
503
+ frameworks: "Frameworks",
504
+ cycles: "Circular Dependencies",
505
+ graphql: "GraphQL Schema",
506
+ scale: "Codebase Scale",
507
+ };
508
+ return labels[category] || category;
509
+ }
@@ -21,6 +21,10 @@ const ALL_DOCUMENT_KEYS = [
21
21
  "data_flows",
22
22
  "change_impact",
23
23
  "developer_onboarding",
24
+ "graphql_schema",
25
+ "type_graph",
26
+ "dependency_graph",
27
+ "architecture_drift",
24
28
  ];
25
29
 
26
30
  /**