@chappibunny/repolens 0.6.4 โ 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 +22 -0
- package/README.md +44 -28
- 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/cli.js +65 -20
- package/src/core/scan.js +2 -1
- package/src/docs/generate-doc-set.js +51 -3
- package/src/init.js +210 -6
- package/src/publishers/confluence.js +2 -5
- package/src/publishers/notion.js +2 -1
- package/src/renderers/renderAnalysis.js +408 -0
- package/src/utils/errors.js +132 -0
- package/src/utils/metrics.js +69 -12
- package/src/utils/telemetry.js +41 -20
- package/src/watch.js +92 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.8.0
|
|
6
|
+
|
|
7
|
+
### โจ New Features
|
|
8
|
+
- **GraphQL Schema Detection** (`src/analyzers/graphql-analyzer.js`): Detects `.graphql`/`.gql` schema files, inline SDL via gql tagged templates, 11 library patterns (Apollo, Yoga, Mercurius, Nexus, Pothos, type-graphql, Relay, urql, etc.), and resolver patterns. Parses queries, mutations, subscriptions, object types, enums, inputs, interfaces, unions, scalars, and directives.
|
|
9
|
+
- **TypeScript Type Graph Analysis** (`src/analyzers/typescript-analyzer.js`): Parses interfaces (with extends), type aliases (with reference extraction), classes (extends + implements), enums, and generic constraints. Builds relationship graph with deduplication, filtering to project-declared types only.
|
|
10
|
+
- **Dependency Graph with Cycle Detection** (`src/analyzers/dependency-graph.js`): Parses ES imports, dynamic imports, CommonJS require, and re-exports. Builds directed adjacency graph with cycle detection via iterative DFS (3-color marking). Tracks hub modules, orphan files, and external dependencies.
|
|
11
|
+
- **Architecture Drift Detection** (`src/analyzers/drift-detector.js`): Compares current architecture against stored baseline snapshots. Detects changes across 8 categories (modules, APIs, pages, dependencies, frameworks, circular deps, GraphQL types, file count). Categorizes drifts by severity: ๐ด critical, ๐ก warning, ๐ข info. Baselines auto-saved to `.repolens/architecture-baseline.json`.
|
|
12
|
+
- **Extended Analysis Renderers** (`src/renderers/renderAnalysis.js`): Markdown renderers for all 4 new document types with tables, Unicode relationship trees, and severity-grouped reports.
|
|
13
|
+
- **4 New Document Types**: `graphql_schema`, `type_graph`, `dependency_graph`, `architecture_drift` โ bringing total to 15 audience-aware documents.
|
|
14
|
+
|
|
15
|
+
### ๐งช Tests
|
|
16
|
+
- Added 31 new tests for extended analysis features (121 tests across 12 files)
|
|
17
|
+
|
|
18
|
+
## 0.7.0
|
|
19
|
+
|
|
20
|
+
### โจ New Features
|
|
21
|
+
- **Interactive Init Wizard**: `repolens init --interactive` โ step-by-step configuration wizard with scan presets (Next.js, Express, generic), publisher selection, AI provider setup, and branch filtering
|
|
22
|
+
- **Watch Mode**: `repolens watch` โ watches source directories for changes and regenerates Markdown docs with 500ms debounce (no API calls)
|
|
23
|
+
- **Enhanced Error Messages**: Centralized error catalog with actionable guidance โ every error now shows what went wrong, why, and how to fix it
|
|
24
|
+
- **Performance Monitoring**: Scan, render, and publish timing summary printed after every `publish` run
|
|
25
|
+
- **Coverage Scoring Improvements**: New section completeness metric (12 document types tracked), updated health score weights, and `metrics.json` snapshots saved to `.repolens/`
|
|
26
|
+
|
|
5
27
|
## 0.6.4
|
|
6
28
|
|
|
7
29
|
### ๐ง Maintenance
|
package/README.md
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img src="Avatar.png" alt="RepoLens" width="120" />
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
1
|
```
|
|
6
2
|
โโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโ โโโ โโโโโโโโโโโโ โโโโโโโโโโโ
|
|
7
3
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโ โโโโโโโโโโโ
|
|
@@ -9,7 +5,7 @@
|
|
|
9
5
|
โโโโโโโโโโโโโโ โโโโโโโ โโโ โโโโโโ โโโโโโ โโโโโโโโโโโโโโโโโโ
|
|
10
6
|
โโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโ
|
|
11
7
|
โโโ โโโโโโโโโโโโโโ โโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโ
|
|
12
|
-
|
|
8
|
+
Repository Intelligence CLI
|
|
13
9
|
```
|
|
14
10
|
|
|
15
11
|
[](https://github.com/CHAPIBUNNY/repolens/actions)
|
|
@@ -20,7 +16,7 @@
|
|
|
20
16
|
|
|
21
17
|
AI-assisted documentation intelligence system that generates architecture docs for engineers AND readable system docs for stakeholders
|
|
22
18
|
|
|
23
|
-
**Current Status**: v0.
|
|
19
|
+
**Current Status**: v0.8.0 โ Extended Analysis
|
|
24
20
|
|
|
25
21
|
RepoLens automatically generates and maintains living architecture documentation by analyzing your repository structure, extracting meaningful insights from your package.json, and creating visual dependency graphs. Run it once, or let it auto-update on every push.
|
|
26
22
|
|
|
@@ -132,7 +128,15 @@ RepoLens automatically detects:
|
|
|
132
128
|
โ
**Branch-Aware** - Prevent doc conflicts across branches
|
|
133
129
|
โ
**GitHub Actions** - Autonomous operation on every push
|
|
134
130
|
โ
**Team Notifications** - Discord integration with rich embeds (NEW in v0.6.0)
|
|
135
|
-
โ
**Health Score Tracking** - Monitor documentation quality over time
|
|
131
|
+
โ
**Health Score Tracking** - Monitor documentation quality over time
|
|
132
|
+
โ
**Watch Mode** - Auto-regenerate docs on file changes (NEW in v0.7.0)
|
|
133
|
+
โ
**Interactive Setup** - Step-by-step configuration wizard (NEW in v0.7.0)
|
|
134
|
+
โ
**Performance Metrics** - Timing breakdown for scan/render/publish (NEW in v0.7.0)
|
|
135
|
+
โ
**Actionable Errors** - Enhanced error messages with fix guidance (NEW in v0.7.0)
|
|
136
|
+
โ
**GraphQL Schema Detection** - Discover types, queries, mutations, and resolvers (NEW in v0.8.0)
|
|
137
|
+
โ
**TypeScript Type Graph** - Map interfaces, classes, and type relationships (NEW in v0.8.0)
|
|
138
|
+
โ
**Dependency Graph** - Import analysis with circular dependency detection (NEW in v0.8.0)
|
|
139
|
+
โ
**Architecture Drift** - Track structural changes against a baseline (NEW in v0.8.0)
|
|
136
140
|
|
|
137
141
|
---
|
|
138
142
|
|
|
@@ -224,7 +228,7 @@ npm link
|
|
|
224
228
|
Install from a specific version:
|
|
225
229
|
|
|
226
230
|
```bash
|
|
227
|
-
npm install https://github.com/CHAPIBUNNY/repolens/releases/download/v0.
|
|
231
|
+
npm install https://github.com/CHAPIBUNNY/repolens/releases/download/v0.8.0/chappibunny-repolens-0.8.0.tgz
|
|
228
232
|
```
|
|
229
233
|
</details>
|
|
230
234
|
|
|
@@ -1077,7 +1081,7 @@ Simulates the full user installation experience:
|
|
|
1077
1081
|
npm pack
|
|
1078
1082
|
|
|
1079
1083
|
# Install globally from tarball
|
|
1080
|
-
npm install -g chappibunny-repolens-0.
|
|
1084
|
+
npm install -g chappibunny-repolens-0.8.0.tgz
|
|
1081
1085
|
|
|
1082
1086
|
# Verify
|
|
1083
1087
|
repolens --version
|
|
@@ -1091,18 +1095,23 @@ repolens/
|
|
|
1091
1095
|
โ โโโ repolens.js # CLI executable wrapper
|
|
1092
1096
|
โโโ src/
|
|
1093
1097
|
โ โโโ cli.js # Command orchestration + banner
|
|
1094
|
-
โ โโโ init.js # Scaffolding command
|
|
1098
|
+
โ โโโ init.js # Scaffolding command (+ interactive wizard)
|
|
1095
1099
|
โ โโโ doctor.js # Validation command
|
|
1096
1100
|
โ โโโ migrate.js # Workflow migration (legacy โ current)
|
|
1101
|
+
โ โโโ watch.js # Watch mode for local development
|
|
1097
1102
|
โ โโโ core/
|
|
1098
1103
|
โ โ โโโ config.js # Config loading + validation
|
|
1099
1104
|
โ โ โโโ config-schema.js # Schema version tracking
|
|
1100
1105
|
โ โ โโโ diff.js # Git diff operations
|
|
1101
1106
|
โ โ โโโ scan.js # Repository scanning + metadata extraction
|
|
1102
1107
|
โ โโโ analyzers/
|
|
1103
|
-
โ โ โโโ domain-inference.js
|
|
1104
|
-
โ โ โโโ context-builder.js
|
|
1105
|
-
โ โ
|
|
1108
|
+
โ โ โโโ domain-inference.js # Business domain mapping
|
|
1109
|
+
โ โ โโโ context-builder.js # Structured AI context assembly
|
|
1110
|
+
โ โ โโโ flow-inference.js # Data flow detection
|
|
1111
|
+
โ โ โโโ graphql-analyzer.js # GraphQL schema detection
|
|
1112
|
+
โ โ โโโ typescript-analyzer.js # TypeScript type graph analysis
|
|
1113
|
+
โ โ โโโ dependency-graph.js # Import graph with cycle detection
|
|
1114
|
+
โ โ โโโ drift-detector.js # Architecture drift detection
|
|
1106
1115
|
โ โโโ ai/
|
|
1107
1116
|
โ โ โโโ provider.js # Provider-agnostic AI generation
|
|
1108
1117
|
โ โ โโโ prompts.js # Strict prompt templates
|
|
@@ -1112,9 +1121,10 @@ repolens/
|
|
|
1112
1121
|
โ โ โโโ generate-doc-set.js # Document generation orchestration
|
|
1113
1122
|
โ โ โโโ write-doc-set.js # Write docs to disk
|
|
1114
1123
|
โ โโโ renderers/
|
|
1115
|
-
โ โ โโโ render.js
|
|
1116
|
-
โ โ โโโ renderDiff.js
|
|
1117
|
-
โ โ
|
|
1124
|
+
โ โ โโโ render.js # System overview, catalog, API, routes
|
|
1125
|
+
โ โ โโโ renderDiff.js # Architecture diff rendering
|
|
1126
|
+
โ โ โโโ renderMap.js # Unicode dependency diagrams
|
|
1127
|
+
โ โ โโโ renderAnalysis.js # Extended analysis renderers
|
|
1118
1128
|
โ โโโ publishers/
|
|
1119
1129
|
โ โ โโโ index.js # Publisher orchestration + branch filtering
|
|
1120
1130
|
โ โ โโโ publish.js # Publishing pipeline
|
|
@@ -1133,9 +1143,10 @@ repolens/
|
|
|
1133
1143
|
โ โโโ metrics.js # Documentation coverage & health scoring
|
|
1134
1144
|
โ โโโ rate-limit.js # Token bucket rate limiter for APIs
|
|
1135
1145
|
โ โโโ secrets.js # Secret detection & sanitization
|
|
1136
|
-
โ โโโ telemetry.js # Opt-in error tracking
|
|
1146
|
+
โ โโโ telemetry.js # Opt-in error tracking + performance timers
|
|
1147
|
+
โ โโโ errors.js # Enhanced error messages with guidance
|
|
1137
1148
|
โ โโโ update-check.js # Version update notifications
|
|
1138
|
-
โโโ tests/ # Vitest test suite (
|
|
1149
|
+
โโโ tests/ # Vitest test suite (121 tests across 12 files)
|
|
1139
1150
|
โโโ .repolens.yml # Dogfooding config
|
|
1140
1151
|
โโโ package.json
|
|
1141
1152
|
โโโ CHANGELOG.md
|
|
@@ -1152,13 +1163,13 @@ RepoLens uses automated GitHub Actions releases.
|
|
|
1152
1163
|
### Creating a Release
|
|
1153
1164
|
|
|
1154
1165
|
```bash
|
|
1155
|
-
# Patch version (0.
|
|
1166
|
+
# Patch version (0.8.0 โ 0.8.1) - Bug fixes
|
|
1156
1167
|
npm run release:patch
|
|
1157
1168
|
|
|
1158
|
-
# Minor version (0.
|
|
1169
|
+
# Minor version (0.8.0 โ 0.9.0) - New features
|
|
1159
1170
|
npm run release:minor
|
|
1160
1171
|
|
|
1161
|
-
# Major version (0.
|
|
1172
|
+
# Major version (0.8.0 โ 1.0.0) - Breaking changes
|
|
1162
1173
|
npm run release:major
|
|
1163
1174
|
|
|
1164
1175
|
# Push the tag to trigger workflow
|
|
@@ -1190,11 +1201,11 @@ RepoLens is currently in early access. v1.0 will open for community contribution
|
|
|
1190
1201
|
|
|
1191
1202
|
## ๐บ๏ธ Roadmap to v1.0
|
|
1192
1203
|
|
|
1193
|
-
**Current Status:** v0.
|
|
1204
|
+
**Current Status:** v0.8.0 โ Extended Analysis
|
|
1194
1205
|
|
|
1195
1206
|
### Completed โ
|
|
1196
1207
|
|
|
1197
|
-
- [x] CLI commands: `init`, `doctor`, `publish`, `migrate`, `feedback`, `version`, `help`
|
|
1208
|
+
- [x] CLI commands: `init`, `doctor`, `publish`, `migrate`, `watch`, `feedback`, `version`, `help`
|
|
1198
1209
|
- [x] Config schema v1 with validation
|
|
1199
1210
|
- [x] Auto-discovery of `.repolens.yml`
|
|
1200
1211
|
- [x] Publishers: Notion + Confluence + Markdown
|
|
@@ -1206,7 +1217,7 @@ RepoLens is currently in early access. v1.0 will open for community contribution
|
|
|
1206
1217
|
- [x] GitHub Actions automation (publish + release)
|
|
1207
1218
|
- [x] PR architecture diff comments
|
|
1208
1219
|
- [x] Performance guardrails (10k warning, 50k limit)
|
|
1209
|
-
- [x] Comprehensive test suite (
|
|
1220
|
+
- [x] Comprehensive test suite (121 tests across 12 files)
|
|
1210
1221
|
- [x] Security hardening (secret detection, injection prevention, fuzzing)
|
|
1211
1222
|
- [x] Discord notifications with rich embeds
|
|
1212
1223
|
- [x] Documentation coverage & health scoring
|
|
@@ -1214,14 +1225,19 @@ RepoLens is currently in early access. v1.0 will open for community contribution
|
|
|
1214
1225
|
- [x] npm registry publication (`@chappibunny/repolens`)
|
|
1215
1226
|
- [x] Automated npm releases via GitHub Actions
|
|
1216
1227
|
- [x] Workflow migration command (`repolens migrate`)
|
|
1228
|
+
- [x] Interactive configuration wizard (`repolens init --interactive`)
|
|
1229
|
+
- [x] Watch mode for local development (`repolens watch`)
|
|
1230
|
+
- [x] Enhanced error messages with actionable guidance
|
|
1231
|
+
- [x] Performance monitoring (scan/render/publish timing)
|
|
1232
|
+
- [x] Improved documentation coverage scoring
|
|
1233
|
+
- [x] GraphQL schema detection (queries, mutations, types, resolvers)
|
|
1234
|
+
- [x] TypeScript type graph analysis (interfaces, classes, relationships)
|
|
1235
|
+
- [x] Dependency graph with circular dependency detection
|
|
1236
|
+
- [x] Architecture drift detection (baseline comparison)
|
|
1217
1237
|
|
|
1218
1238
|
### Planned for v1.0 ๐ฏ
|
|
1219
1239
|
|
|
1220
1240
|
- [ ] Plugin system for custom renderers
|
|
1221
|
-
- [ ] GraphQL schema detection
|
|
1222
|
-
- [ ] TypeScript type graph analysis
|
|
1223
|
-
- [ ] Interactive configuration wizard
|
|
1224
|
-
- [ ] Watch mode for local development
|
|
1225
1241
|
- [ ] Additional publishers (GitHub Wiki, Obsidian)
|
|
1226
1242
|
|
|
1227
1243
|
See [ROADMAP.md](./ROADMAP.md) for detailed planning.
|
package/package.json
CHANGED
package/src/ai/document-plan.js
CHANGED
|
@@ -88,6 +88,38 @@ export const DOCUMENT_PLAN = [
|
|
|
88
88
|
audience: "technical",
|
|
89
89
|
ai: true,
|
|
90
90
|
description: "Quick start guide for new developers"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
key: "graphql_schema",
|
|
94
|
+
filename: "11-graphql-schema.md",
|
|
95
|
+
title: "GraphQL Schema",
|
|
96
|
+
audience: "technical",
|
|
97
|
+
ai: false,
|
|
98
|
+
description: "GraphQL types, queries, mutations, and resolver map"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: "type_graph",
|
|
102
|
+
filename: "12-type-graph.md",
|
|
103
|
+
title: "TypeScript Type Graph",
|
|
104
|
+
audience: "technical",
|
|
105
|
+
ai: false,
|
|
106
|
+
description: "TypeScript interfaces, types, classes, and relationships"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "dependency_graph",
|
|
110
|
+
filename: "13-dependency-graph.md",
|
|
111
|
+
title: "Dependency Graph",
|
|
112
|
+
audience: "technical",
|
|
113
|
+
ai: false,
|
|
114
|
+
description: "Module dependency analysis with cycle detection"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
key: "architecture_drift",
|
|
118
|
+
filename: "14-architecture-drift.md",
|
|
119
|
+
title: "Architecture Drift",
|
|
120
|
+
audience: "mixed",
|
|
121
|
+
ai: false,
|
|
122
|
+
description: "Structural changes compared to baseline snapshot"
|
|
91
123
|
}
|
|
92
124
|
];
|
|
93
125
|
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Dependency graph analysis with cycle detection
|
|
2
|
+
// Parses import/require statements, builds a directed graph, detects cycles via DFS
|
|
3
|
+
|
|
4
|
+
import fs from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { info, warn } from "../utils/logger.js";
|
|
7
|
+
|
|
8
|
+
const CODE_EXTENSIONS = new Set([".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"]);
|
|
9
|
+
|
|
10
|
+
// Patterns for extracting imports
|
|
11
|
+
const IMPORT_PATTERNS = [
|
|
12
|
+
// ES module: import ... from "..."
|
|
13
|
+
/import\s+(?:[\w*{},\s]+\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
14
|
+
// Dynamic import: import("...")
|
|
15
|
+
/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
16
|
+
// CommonJS: require("...")
|
|
17
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
18
|
+
// Re-export: export ... from "..."
|
|
19
|
+
/export\s+(?:[\w*{},\s]+\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
async function readFileSafe(filePath) {
|
|
23
|
+
try {
|
|
24
|
+
return await fs.readFile(filePath, "utf8");
|
|
25
|
+
} catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isRelativeImport(specifier) {
|
|
31
|
+
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveImportPath(importerFile, specifier) {
|
|
35
|
+
// Only resolve relative imports โ skip node_modules / bare specifiers
|
|
36
|
+
if (!isRelativeImport(specifier)) return null;
|
|
37
|
+
|
|
38
|
+
const importerDir = path.dirname(importerFile);
|
|
39
|
+
let resolved = path.posix.join(importerDir, specifier);
|
|
40
|
+
|
|
41
|
+
// Normalize path separators
|
|
42
|
+
resolved = resolved.replace(/\\/g, "/");
|
|
43
|
+
|
|
44
|
+
// Strip known extensions for dedup โ we normalize to extensionless keys
|
|
45
|
+
resolved = resolved.replace(/\.(js|ts|jsx|tsx|mjs|cjs)$/, "");
|
|
46
|
+
|
|
47
|
+
// Strip /index suffix
|
|
48
|
+
resolved = resolved.replace(/\/index$/, "");
|
|
49
|
+
|
|
50
|
+
return resolved;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeFileKey(file) {
|
|
54
|
+
// Normalize a source file to a key that can match resolved imports
|
|
55
|
+
let key = file.replace(/\\/g, "/");
|
|
56
|
+
key = key.replace(/\.(js|ts|jsx|tsx|mjs|cjs)$/, "");
|
|
57
|
+
key = key.replace(/\/index$/, "");
|
|
58
|
+
return key;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function analyzeDependencyGraph(files, repoRoot) {
|
|
62
|
+
const result = {
|
|
63
|
+
nodes: [], // { key, file, imports: string[], importedBy: string[] }
|
|
64
|
+
edges: [], // { from, to }
|
|
65
|
+
cycles: [], // [ [a, b, c, a], ... ]
|
|
66
|
+
externalDeps: [],// sorted unique bare-specifier imports
|
|
67
|
+
stats: null,
|
|
68
|
+
summary: null,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const codeFiles = files.filter(f => {
|
|
72
|
+
const ext = path.extname(f);
|
|
73
|
+
return CODE_EXTENSIONS.has(ext);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (codeFiles.length === 0) return result;
|
|
77
|
+
|
|
78
|
+
// Build adjacency: fileKey โ Set<fileKey>
|
|
79
|
+
const adjacency = new Map();
|
|
80
|
+
const fileKeyToFile = new Map();
|
|
81
|
+
const allFileKeys = new Set();
|
|
82
|
+
const externalSet = new Set();
|
|
83
|
+
|
|
84
|
+
// Register all known file keys
|
|
85
|
+
for (const file of codeFiles) {
|
|
86
|
+
const key = normalizeFileKey(file);
|
|
87
|
+
allFileKeys.add(key);
|
|
88
|
+
fileKeyToFile.set(key, file);
|
|
89
|
+
if (!adjacency.has(key)) adjacency.set(key, new Set());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Parse imports and build edges
|
|
93
|
+
for (const file of codeFiles) {
|
|
94
|
+
const content = await readFileSafe(path.join(repoRoot, file));
|
|
95
|
+
if (!content) continue;
|
|
96
|
+
|
|
97
|
+
const fromKey = normalizeFileKey(file);
|
|
98
|
+
|
|
99
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
100
|
+
// Reset lastIndex since we reuse regex objects
|
|
101
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
102
|
+
let match;
|
|
103
|
+
while ((match = regex.exec(content)) !== null) {
|
|
104
|
+
const specifier = match[1];
|
|
105
|
+
|
|
106
|
+
if (isRelativeImport(specifier)) {
|
|
107
|
+
const resolved = resolveImportPath(file, specifier);
|
|
108
|
+
if (resolved && allFileKeys.has(resolved)) {
|
|
109
|
+
adjacency.get(fromKey).add(resolved);
|
|
110
|
+
}
|
|
111
|
+
} else if (!specifier.startsWith("node:")) {
|
|
112
|
+
// Bare specifier โ external dependency
|
|
113
|
+
// Normalize scoped packages: @scope/pkg/path โ @scope/pkg
|
|
114
|
+
const parts = specifier.startsWith("@") ? specifier.split("/").slice(0, 2) : specifier.split("/").slice(0, 1);
|
|
115
|
+
externalSet.add(parts.join("/"));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Build nodes and edges
|
|
122
|
+
const importedByMap = new Map();
|
|
123
|
+
for (const key of allFileKeys) {
|
|
124
|
+
importedByMap.set(key, []);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const [fromKey, deps] of adjacency) {
|
|
128
|
+
for (const toKey of deps) {
|
|
129
|
+
result.edges.push({ from: fromKey, to: toKey });
|
|
130
|
+
if (importedByMap.has(toKey)) {
|
|
131
|
+
importedByMap.get(toKey).push(fromKey);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const key of allFileKeys) {
|
|
137
|
+
result.nodes.push({
|
|
138
|
+
key,
|
|
139
|
+
file: fileKeyToFile.get(key) || key,
|
|
140
|
+
imports: [...(adjacency.get(key) || [])],
|
|
141
|
+
importedBy: importedByMap.get(key) || [],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
result.externalDeps = [...externalSet].sort();
|
|
146
|
+
|
|
147
|
+
// Detect cycles using iterative DFS (Tarjan-lite / coloring)
|
|
148
|
+
result.cycles = detectCycles(adjacency);
|
|
149
|
+
|
|
150
|
+
// Stats
|
|
151
|
+
const totalImports = result.edges.length;
|
|
152
|
+
const orphans = result.nodes.filter(n => n.imports.length === 0 && n.importedBy.length === 0);
|
|
153
|
+
const hubThreshold = Math.max(5, Math.floor(result.nodes.length * 0.1));
|
|
154
|
+
const hubs = result.nodes
|
|
155
|
+
.filter(n => n.importedBy.length >= hubThreshold)
|
|
156
|
+
.sort((a, b) => b.importedBy.length - a.importedBy.length)
|
|
157
|
+
.slice(0, 10)
|
|
158
|
+
.map(n => ({ key: n.key, importedBy: n.importedBy.length }));
|
|
159
|
+
|
|
160
|
+
result.stats = {
|
|
161
|
+
totalFiles: codeFiles.length,
|
|
162
|
+
totalEdges: totalImports,
|
|
163
|
+
externalDeps: result.externalDeps.length,
|
|
164
|
+
cycles: result.cycles.length,
|
|
165
|
+
orphanFiles: orphans.length,
|
|
166
|
+
hubs,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
result.summary = buildSummary(result);
|
|
170
|
+
|
|
171
|
+
if (result.cycles.length > 0) {
|
|
172
|
+
warn(`Dependency graph: ${result.cycles.length} circular dependency cycle(s) detected`);
|
|
173
|
+
} else {
|
|
174
|
+
info(`Dependency graph: ${totalImports} edges across ${codeFiles.length} files, no cycles`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Detect all cycles in a directed graph using DFS with 3-color marking.
|
|
182
|
+
* Returns an array of cycle paths (each path ends where it started).
|
|
183
|
+
*/
|
|
184
|
+
function detectCycles(adjacency) {
|
|
185
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
186
|
+
const color = new Map();
|
|
187
|
+
const parent = new Map();
|
|
188
|
+
const cycles = [];
|
|
189
|
+
|
|
190
|
+
for (const node of adjacency.keys()) {
|
|
191
|
+
color.set(node, WHITE);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
for (const startNode of adjacency.keys()) {
|
|
195
|
+
if (color.get(startNode) !== WHITE) continue;
|
|
196
|
+
|
|
197
|
+
// Iterative DFS
|
|
198
|
+
const stack = [{ node: startNode, neighbors: [...(adjacency.get(startNode) || [])], idx: 0 }];
|
|
199
|
+
color.set(startNode, GRAY);
|
|
200
|
+
parent.set(startNode, null);
|
|
201
|
+
|
|
202
|
+
while (stack.length > 0) {
|
|
203
|
+
const frame = stack[stack.length - 1];
|
|
204
|
+
|
|
205
|
+
if (frame.idx >= frame.neighbors.length) {
|
|
206
|
+
// All neighbors processed โ mark black and backtrack
|
|
207
|
+
color.set(frame.node, BLACK);
|
|
208
|
+
stack.pop();
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const neighbor = frame.neighbors[frame.idx++];
|
|
213
|
+
|
|
214
|
+
if (color.get(neighbor) === GRAY) {
|
|
215
|
+
// Back edge โ cycle found. Reconstruct the cycle path.
|
|
216
|
+
const cyclePath = [neighbor];
|
|
217
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
218
|
+
cyclePath.push(stack[i].node);
|
|
219
|
+
if (stack[i].node === neighbor) break;
|
|
220
|
+
}
|
|
221
|
+
cyclePath.reverse();
|
|
222
|
+
// Only keep reasonably short cycles to avoid noise
|
|
223
|
+
if (cyclePath.length <= 20) {
|
|
224
|
+
cycles.push(cyclePath);
|
|
225
|
+
}
|
|
226
|
+
// Cap cycles to prevent runaway in huge graphs
|
|
227
|
+
if (cycles.length >= 50) return cycles;
|
|
228
|
+
} else if (color.get(neighbor) === WHITE) {
|
|
229
|
+
color.set(neighbor, GRAY);
|
|
230
|
+
parent.set(neighbor, frame.node);
|
|
231
|
+
stack.push({ node: neighbor, neighbors: [...(adjacency.get(neighbor) || [])], idx: 0 });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return cycles;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function buildSummary(result) {
|
|
240
|
+
const parts = [
|
|
241
|
+
`${result.stats.totalFiles} files`,
|
|
242
|
+
`${result.stats.totalEdges} imports`,
|
|
243
|
+
`${result.stats.externalDeps} external packages`,
|
|
244
|
+
];
|
|
245
|
+
if (result.stats.cycles > 0) {
|
|
246
|
+
parts.push(`โ ๏ธ ${result.stats.cycles} circular dependency cycle(s)`);
|
|
247
|
+
} else {
|
|
248
|
+
parts.push("โ no circular dependencies");
|
|
249
|
+
}
|
|
250
|
+
if (result.stats.orphanFiles > 0) {
|
|
251
|
+
parts.push(`${result.stats.orphanFiles} orphan file(s)`);
|
|
252
|
+
}
|
|
253
|
+
return parts.join(" ยท ");
|
|
254
|
+
}
|