depgraph-core 1.5.2 → 1.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/.vscode/depgraph-output.json +7197 -2405
- package/README.md +44 -2
- package/depgraph-mcp.js +24282 -0
- package/depgraph.js +2 -2
- package/package.json +10 -3
- package/docs/README.md +0 -57
- package/docs/architecture.md +0 -89
- package/docs/data-types.md +0 -218
- package/docs/language-registry.md +0 -206
- package/docs/parser-conventions.md +0 -194
- package/docs/stage-collector.md +0 -109
- package/docs/stage-graph.md +0 -157
- package/docs/stage-impact.md +0 -154
- package/docs/stage-metrics.md +0 -113
- package/docs/stage-output.md +0 -144
- package/docs/stage-parser.md +0 -144
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# Language Parser Conventions
|
|
2
|
-
|
|
3
|
-
> **Standardized architecture, directory conventions, and coding guidelines for DepGraph language parsers.**
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
DepGraph relies on language plugins to extract code entities, imports, and exports from source files across various languages without heavy AST dependencies. As support for languages grows in sophistication—especially when incorporating deep graph extractors (e.g. Graphify engines) or rich syntax parsing—parsers must adhere to clear structural conventions to remain readable, maintainable, and testable.
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## The Two-Tier Architecture
|
|
14
|
-
|
|
15
|
-
To balance simplicity for straightforward languages and scalability for feature-rich languages, DepGraph uses a two-tier organization model:
|
|
16
|
-
|
|
17
|
-
| Tier | Format | When to Use | Typical File Count | Target Size |
|
|
18
|
-
|---|---|---|---|---|
|
|
19
|
-
| **Tier 1: Single-File Plugin** | `src/languages/<lang>.ts` | Simple languages or basic regex extractors | 1 file | < 300 lines |
|
|
20
|
-
| **Tier 2: Modular Directory Module** | `src/languages/<lang>/` | Complex languages, >300 lines, or dual-purpose extractors (DepGraph + Graphify) | 4–6 modular files | < 250 lines per file |
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Tier 1: Single-File Plugin Convention
|
|
25
|
-
|
|
26
|
-
Used by languages like Python, Go, Java, Kotlin, PHP, Ruby, Swift, C#, and JavaScript.
|
|
27
|
-
|
|
28
|
-
### Structure
|
|
29
|
-
|
|
30
|
-
A single file: `src/languages/<lang>.ts`
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
src/languages/
|
|
34
|
-
├── go.ts
|
|
35
|
-
├── python.ts
|
|
36
|
-
└── java.ts
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Standard Section Order
|
|
40
|
-
|
|
41
|
-
Every single-file parser must organize code into well-demarcated sections in the following order:
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
// 1. Imports & External Types
|
|
45
|
-
import { RawEntity, RawImport } from '../types';
|
|
46
|
-
import { EntityPattern, LanguageParser, registerParser } from './registry';
|
|
47
|
-
import { COMPLEXITY_THRESHOLDS } from '../constants';
|
|
48
|
-
|
|
49
|
-
// 2. Helpers & Complexity Estimation
|
|
50
|
-
function estimateComplexity(code: string, name: string): string { ... }
|
|
51
|
-
|
|
52
|
-
// 3. Entity Patterns (Exported for gitdiff reuse)
|
|
53
|
-
export const langEntityPatterns: EntityPattern[] = [ ... ];
|
|
54
|
-
|
|
55
|
-
// 4. Entity Extractor
|
|
56
|
-
function extractEntities(code: string, filePath: string): RawEntity[] { ... }
|
|
57
|
-
|
|
58
|
-
// 5. Import Extractor
|
|
59
|
-
function extractImports(code: string): RawImport[] { ... }
|
|
60
|
-
|
|
61
|
-
// 6. Export Extractor
|
|
62
|
-
function extractExports(code: string): string[] { ... }
|
|
63
|
-
|
|
64
|
-
// 7. Parser Registration
|
|
65
|
-
export const LangParser: LanguageParser = {
|
|
66
|
-
lang: 'lang',
|
|
67
|
-
extensions: ['.ext'],
|
|
68
|
-
extractEntities,
|
|
69
|
-
extractImports,
|
|
70
|
-
extractExports,
|
|
71
|
-
entityPatterns: langEntityPatterns,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
registerParser(LangParser);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## Tier 2: Modular Directory Module Convention
|
|
80
|
-
|
|
81
|
-
Used when a language parser exceeds ~300 lines or integrates secondary engines (such as standalone Graphify graph extractors). Languages using this tier include **Dart** and **Rust**.
|
|
82
|
-
|
|
83
|
-
### Directory Layout
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
src/languages/<lang>/
|
|
87
|
-
├── index.ts # Public entrypoint, LanguageParser definition, side-effect registration
|
|
88
|
-
├── types.ts # Specialized TypeScript interfaces (e.g., Graphify node/edge definitions)
|
|
89
|
-
├── patterns.ts # EntityPattern definitions, keyword sets, and token constants
|
|
90
|
-
├── helpers.ts # Lexing utilities, comment cleaners, brace depth counters, complexity
|
|
91
|
-
├── extractor.ts # Core DepGraph contract: extractEntities, extractImports, extractExports
|
|
92
|
-
└── graphify.ts # Standalone deep AST/regex graph extraction engine (if applicable)
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### File Responsibilities & Contract
|
|
96
|
-
|
|
97
|
-
#### 1. `index.ts` (Entry Point & Barrel)
|
|
98
|
-
- Constructs the `LanguageParser` object.
|
|
99
|
-
- Invokes `registerParser(...)` at module evaluation time.
|
|
100
|
-
- Re-exports public helpers and secondary functions (e.g., `cleanDartComments`, `estimateComplexity`, `extractDart`) to ensure seamless backward compatibility.
|
|
101
|
-
- Transparently resolves `import './languages/dart'` without altering caller syntax.
|
|
102
|
-
|
|
103
|
-
```ts
|
|
104
|
-
import { LanguageParser, registerParser } from '../registry';
|
|
105
|
-
import { langEntityPatterns } from './patterns';
|
|
106
|
-
import { extractEntities, extractImports, extractExports } from './extractor';
|
|
107
|
-
|
|
108
|
-
export * from './types';
|
|
109
|
-
export * from './patterns';
|
|
110
|
-
export * from './helpers';
|
|
111
|
-
export * from './graphify';
|
|
112
|
-
|
|
113
|
-
export const LangParser: LanguageParser = {
|
|
114
|
-
lang: 'lang',
|
|
115
|
-
extensions: ['.ext'],
|
|
116
|
-
extractEntities,
|
|
117
|
-
extractImports,
|
|
118
|
-
extractExports,
|
|
119
|
-
entityPatterns: langEntityPatterns,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
registerParser(LangParser);
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
#### 2. `types.ts` (Domain Types)
|
|
126
|
-
- Houses language-specific data contracts that do not belong in global `src/types.ts`.
|
|
127
|
-
- Example: Graphify node representations, edge payloads, and extractor result contracts (`DartGraphNode`, `DartGraphEdge`, `DartGraphResult`).
|
|
128
|
-
|
|
129
|
-
#### 3. `patterns.ts` (Regex & Token Sets)
|
|
130
|
-
- Contains `EntityPattern[]` regex rules with named or indexed capture groups (Group 1 must always be the entity name).
|
|
131
|
-
- Houses language keywords and primitive type sets (e.g., `DART_KEYWORDS`, `RUST_PRIMITIVES`).
|
|
132
|
-
|
|
133
|
-
#### 4. `helpers.ts` (Lexical & Analysis Utilities)
|
|
134
|
-
- String manipulations, string literal escaping (`escapeRegex`).
|
|
135
|
-
- Comment cleaning functions that preserve line counts (`clean<Lang>Comments`).
|
|
136
|
-
- Balanced bracket/brace scanners (`_findMatchingBrace`, `_splitBalanced`).
|
|
137
|
-
- Cyclomatic complexity estimation (`estimateComplexity`).
|
|
138
|
-
|
|
139
|
-
#### 5. `extractor.ts` (DepGraph Stage 2 Implementation)
|
|
140
|
-
- Implements the 3 required methods of `LanguageParser`:
|
|
141
|
-
- `extractEntities(code: string, filePath: string): RawEntity[]`
|
|
142
|
-
- `extractImports(code: string): RawImport[]`
|
|
143
|
-
- `extractExports(code: string): string[]`
|
|
144
|
-
- Operates strictly on pre-cleaned code (single-line comments already stripped by the stage caller).
|
|
145
|
-
|
|
146
|
-
#### 6. `graphify.ts` (Deep Graph Engine)
|
|
147
|
-
- Isolated from core stage pipeline execution.
|
|
148
|
-
- Contains the self-contained graph extraction function (`extractDart`, `extractRust`).
|
|
149
|
-
- Allows comprehensive standalone graph analysis without bloating the pipeline parsing logic.
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Core Implementation Rules
|
|
154
|
-
|
|
155
|
-
Every parser, whether Tier 1 or Tier 2, must respect the following rules:
|
|
156
|
-
|
|
157
|
-
### 1. Zero Heavy AST Dependencies
|
|
158
|
-
DepGraph is intentionally designed to be lightweight, ultrafast, and dependency-free.
|
|
159
|
-
- **Do not** introduce heavy AST parsers (such as Babel, tree-sitter, or Roslyn).
|
|
160
|
-
- Rely on regular expressions, state machines, and balanced delimiter traversal.
|
|
161
|
-
|
|
162
|
-
### 2. Preserve Line Number Offsets During Comment Cleaning
|
|
163
|
-
When stripping comments or preprocessing files:
|
|
164
|
-
- Replace stripped blocks with the identical number of `\n` newline characters they spanned.
|
|
165
|
-
- This guarantees that entity line numbers reported in `RawEntity.line` accurately match original file source lines.
|
|
166
|
-
|
|
167
|
-
### 3. Regex State Hygiene
|
|
168
|
-
Global regular expressions (`/pattern/g`) retain state in `regex.lastIndex`.
|
|
169
|
-
- Reset `regex.lastIndex = 0` prior to any `exec` loop or instantiate regular expressions within the function scope.
|
|
170
|
-
- Never let `lastIndex` leak across calls.
|
|
171
|
-
|
|
172
|
-
### 4. Group 1 Entity Identifier Contract
|
|
173
|
-
Any pattern in `entityPatterns` must capture the entity identifier name in **Group 1**:
|
|
174
|
-
```ts
|
|
175
|
-
{
|
|
176
|
-
regex: /^[ \t]*fn\s+([a-z_]\w*)/gm,
|
|
177
|
-
type: 'function',
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
This enables `src/stages/gitdiff.ts` to deduce modified entity names directly from git hunk headers.
|
|
181
|
-
|
|
182
|
-
### 5. Local vs. External Import Flagging
|
|
183
|
-
- Mark `isLocal: true` only for relative or internal project paths (e.g., `./utils`, `../services`).
|
|
184
|
-
- Mark `isLocal: false` for standard library packages or third-party package dependencies (e.g., `fmt`, `dart:async`, `std::collections`).
|
|
185
|
-
- Only local imports become edges in DepGraph's dependency graph.
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
## When to Migrate from Tier 1 to Tier 2
|
|
190
|
-
|
|
191
|
-
Migrate a language parser from `src/languages/<lang>.ts` to `src/languages/<lang>/` when:
|
|
192
|
-
1. The single file exceeds **300 lines of code**.
|
|
193
|
-
2. The language incorporates custom type systems or deep extractors (e.g., Graphify bindings).
|
|
194
|
-
3. The language requires extensive grammar assistance (e.g., custom bracket matching, token splitting, or stateful tokenization).
|
package/docs/stage-collector.md
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# Stage 1 — Collector
|
|
2
|
-
|
|
3
|
-
> **File**: `src/stages/collector.ts`
|
|
4
|
-
> **Exports**: `collectFiles(dir: string): string[]`
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## What It Does
|
|
9
|
-
|
|
10
|
-
The collector is the very first thing that runs. Its job is simple: **walk a directory tree and return a flat list of file paths** that the rest of the pipeline should process.
|
|
11
|
-
|
|
12
|
-
It does **not** read file contents — it only looks at names, extensions, and file sizes.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## How It Works
|
|
17
|
-
|
|
18
|
-
### 1. Recursive directory walk
|
|
19
|
-
|
|
20
|
-
`collectFiles` calls an internal `walk()` helper which:
|
|
21
|
-
|
|
22
|
-
1. Reads the entries of the current directory with `fs.readdirSync`.
|
|
23
|
-
2. For each entry:
|
|
24
|
-
- If it's a **directory**, check it against `IGNORE_DIRS`. If not ignored, recurse into it.
|
|
25
|
-
- If it's a **file**, check its extension against `SUPPORTED_EXTS` and its size against `MAX_FILE_SIZE`.
|
|
26
|
-
|
|
27
|
-
```ts
|
|
28
|
-
if (stat.isDirectory()) {
|
|
29
|
-
if (!IGNORE_DIRS.has(entry)) walk(fullPath);
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const ext = path.extname(entry);
|
|
34
|
-
if (SUPPORTED_EXTS.has(ext) && stat.size < MAX_FILE_SIZE) {
|
|
35
|
-
results.push(fullPath);
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 2. Ignored directories
|
|
40
|
-
|
|
41
|
-
Defined in `src/constants.ts`:
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
export const IGNORE_DIRS = new Set([
|
|
45
|
-
'node_modules', '.git', 'dist', 'build',
|
|
46
|
-
'.next', '__pycache__', 'vendor', 'venv',
|
|
47
|
-
'target', 'out', 'coverage', '.cache'
|
|
48
|
-
]);
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
These are skipped entirely — the walker never descends into them.
|
|
52
|
-
|
|
53
|
-
### 3. Supported extensions
|
|
54
|
-
|
|
55
|
-
Also from constants:
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
export const SUPPORTED_EXTS = new Set([
|
|
59
|
-
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
60
|
-
'.py', '.go', '.java', '.cs', '.rb',
|
|
61
|
-
'.php', '.swift', '.kt', '.vue', '.svelte'
|
|
62
|
-
]);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
> **Note**: Collecting a file with `.java` doesn't mean it will be *parsed* successfully. The collector just gathers eligible paths. If no language parser is registered for that extension, the parser stage silently drops it.
|
|
66
|
-
|
|
67
|
-
### 4. Size guard
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
export const MAX_FILE_SIZE = 300_000; // 300 KB
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Files over 300 KB are skipped. This prevents the scanner from choking on minified bundles, lock files accidentally using a supported extension, or generated code.
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## Error Handling
|
|
78
|
-
|
|
79
|
-
The collector is fault-tolerant at the entry level:
|
|
80
|
-
- If a directory can't be read (permissions, broken symlink), it logs a warning and continues.
|
|
81
|
-
- If `fs.statSync` fails on an entry, it skips that entry and continues.
|
|
82
|
-
|
|
83
|
-
It **never throws** — it always returns whatever it managed to collect.
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Output
|
|
88
|
-
|
|
89
|
-
A flat `string[]` of absolute file paths, for example:
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
[
|
|
93
|
-
"/project/src/main.ts",
|
|
94
|
-
"/project/src/utils/auth.ts",
|
|
95
|
-
"/project/src/routes/users.ts"
|
|
96
|
-
]
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Configuration Quick Reference
|
|
102
|
-
|
|
103
|
-
| Constant | Default | Description |
|
|
104
|
-
|---|---|---|
|
|
105
|
-
| `IGNORE_DIRS` | See above | Directory names to skip entirely |
|
|
106
|
-
| `SUPPORTED_EXTS` | See above | File extensions that are eligible |
|
|
107
|
-
| `MAX_FILE_SIZE` | `300_000` (300 KB) | Maximum file size in bytes |
|
|
108
|
-
|
|
109
|
-
All constants live in [`src/constants.ts`](../src/constants.ts).
|
package/docs/stage-graph.md
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
# Stage 3 — Graph Builder
|
|
2
|
-
|
|
3
|
-
> **File**: `src/stages/graph.ts`
|
|
4
|
-
> **Exports**: `buildGraph(parsedFiles: ParsedFile[]): DepGraph`
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## What It Does
|
|
9
|
-
|
|
10
|
-
The graph builder takes the flat list of `ParsedFile` objects and turns it into a **directed dependency graph**: a collection of **nodes** (entities) connected by **edges** (import relationships).
|
|
11
|
-
|
|
12
|
-
After this stage, you know exactly which entity depends on which other entity, across the entire codebase.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## The Three-Step Build Process
|
|
17
|
-
|
|
18
|
-
### Step 1 — Create a node for every entity
|
|
19
|
-
|
|
20
|
-
Every `RawEntity` found in every file becomes a `DepNode` in the graph:
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
for (const file of parsedFiles) {
|
|
24
|
-
const fileBase = path.basename(file.filePath, path.extname(file.filePath));
|
|
25
|
-
|
|
26
|
-
for (const entity of file.entities) {
|
|
27
|
-
const id = makeId(entity.name, fileBase); // e.g. "getUserById__userService"
|
|
28
|
-
|
|
29
|
-
if (nodes.has(id)) continue; // skip duplicates
|
|
30
|
-
|
|
31
|
-
nodes.set(id, {
|
|
32
|
-
id,
|
|
33
|
-
name: entity.name,
|
|
34
|
-
type: entity.type,
|
|
35
|
-
file: file.filePath,
|
|
36
|
-
line: entity.line,
|
|
37
|
-
lang: file.lang,
|
|
38
|
-
complexity: entity.complexity,
|
|
39
|
-
inDegree: 0,
|
|
40
|
-
outDegree: 0,
|
|
41
|
-
centralityScore: 0,
|
|
42
|
-
connections: [],
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**Node IDs** are composite strings: `entityName__fileBasename`.
|
|
49
|
-
For example, the function `getUserById` in `src/services/userService.ts` gets ID `getUserById__userService`.
|
|
50
|
-
|
|
51
|
-
This scheme prevents collisions when two files have a function with the same name.
|
|
52
|
-
|
|
53
|
-
### Step 2 — Build a file path lookup map
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
const fileMap = new Map<string, ParsedFile>();
|
|
57
|
-
for (const file of parsedFiles) {
|
|
58
|
-
fileMap.set(file.filePath, file);
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
This allows O(1) lookup from a resolved file path → its `ParsedFile` data.
|
|
63
|
-
|
|
64
|
-
### Step 3 — Connect nodes through imports
|
|
65
|
-
|
|
66
|
-
This is where the real graph-building happens. For every file:
|
|
67
|
-
|
|
68
|
-
1. Iterate over its `imports`.
|
|
69
|
-
2. Skip **external** imports (packages like `express`, `lodash`) — only local imports matter.
|
|
70
|
-
3. **Resolve** the import path to an actual file on disk (see path resolution below).
|
|
71
|
-
4. For each imported name, find the matching entity in the target file.
|
|
72
|
-
5. Create an edge from **every entity in the importing file** to the **matching target entity**.
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
edges.push({
|
|
76
|
-
from: fromId,
|
|
77
|
-
to: toId,
|
|
78
|
-
type: 'imports',
|
|
79
|
-
description: `${fromEntity.name} imports ${importedName} from ${path.basename(resolvedPath)}`,
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Duplicate edges are guarded against before insertion.
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
## Path Resolution
|
|
88
|
-
|
|
89
|
-
Import paths like `"./userService"` don't include an extension. The `resolvePath` helper tries these candidates in priority order:
|
|
90
|
-
|
|
91
|
-
```
|
|
92
|
-
./userService
|
|
93
|
-
./userService.ts
|
|
94
|
-
./userService.tsx
|
|
95
|
-
./userService.js
|
|
96
|
-
./userService.jsx
|
|
97
|
-
./userService/index.ts
|
|
98
|
-
./userService/index.js
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
It finds the first candidate that matches a known file in `parsedFiles` and returns that path. If nothing matches, the import is silently skipped.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Output Shape — `DepGraph`
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
interface DepGraph {
|
|
109
|
-
nodes: Map<string, DepNode>;
|
|
110
|
-
edges: DepEdge[];
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
At this point, all `inDegree`, `outDegree`, and `centralityScore` fields on each node are still `0`. The **Metrics stage** fills these in (see [stage-metrics.md](./stage-metrics.md)).
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Edge Direction Convention
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
FROM → TO
|
|
122
|
-
(importer) (imported)
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
If `routes/users.ts` imports `getUserById` from `services/userService.ts`, the edge is:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
GET /api/users__users → getUserById__userService
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
This forward-direction convention means: **following edges = following dependencies**.
|
|
132
|
-
For impact analysis (who depends on me?), edges are traversed **in reverse** (see [stage-impact.md](./stage-impact.md)).
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
## The `makeId` Helper
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
function makeId(name: string, fileBase: string): string {
|
|
140
|
-
const cleanName = name.replace(/[^a-zA-Z0-9]/g, '_');
|
|
141
|
-
const cleanBase = fileBase.replace(/[^a-zA-Z0-9]/g, '_');
|
|
142
|
-
return `${cleanName}__${cleanBase}`;
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Non-alphanumeric characters (e.g. spaces, slashes in route paths like `GET /api/users`) are replaced with underscores. This keeps IDs safe for use as JSON keys and stable across runs.
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Common Gotchas
|
|
151
|
-
|
|
152
|
-
| Situation | Behaviour |
|
|
153
|
-
|---|---|
|
|
154
|
-
| Two files export the same function name | First one wins; second is skipped (duplicate `id` guard) |
|
|
155
|
-
| Import resolves to a file with no entities | The import is dropped — nothing to link to |
|
|
156
|
-
| Circular imports (A imports B, B imports A) | Both edges are created; no infinite loop — the graph is built in a single linear pass |
|
|
157
|
-
| External package imports | Skipped via the `isLocal` flag on `RawImport` |
|
package/docs/stage-impact.md
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
# Stage 5 — Impact Simulator
|
|
2
|
-
|
|
3
|
-
> **File**: `src/stages/impact.ts`
|
|
4
|
-
> **Exports**: `simulateImpact(graph, targetName, changeDescription): ImpactReport`
|
|
5
|
-
> **Triggered by**: `--impact <name> <description>` CLI flag
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## What It Does
|
|
10
|
-
|
|
11
|
-
The impact simulator answers the question: **"If I change this entity, what else breaks?"**
|
|
12
|
-
|
|
13
|
-
It performs a **reverse Breadth-First Search (BFS)** starting from the target node, walking *against* the edge direction to find every node that (directly or transitively) depends on the target. It then scores each affected node by impact severity and produces a risk report with testing recommendations.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## The Four-Step Algorithm
|
|
18
|
-
|
|
19
|
-
### Step 1 — Find the target node
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
const targetNode = [...graph.nodes.values()]
|
|
23
|
-
.find(n => n.name === targetName);
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Searches by `name` (not `id`). If the target is not found, an empty report is returned immediately.
|
|
27
|
-
|
|
28
|
-
### Step 2 — Reverse BFS
|
|
29
|
-
|
|
30
|
-
A standard BFS, but instead of following edges *forward* (A→B means A depends on B), we follow edges *backward* (find all edges where `edge.to === nodeId`):
|
|
31
|
-
|
|
32
|
-
```ts
|
|
33
|
-
function getDirectDependents(graph, nodeId) {
|
|
34
|
-
return graph.edges
|
|
35
|
-
.filter(e => e.to === nodeId)
|
|
36
|
-
.map(e => e.from);
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
The BFS seeds the queue with **direct dependents** (depth 1) and expands outward, level by level, until:
|
|
41
|
-
- A node has already been visited (`visited` Set), or
|
|
42
|
-
- The depth exceeds `MAX_BFS_DEPTH` (default: `10`)
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
target node
|
|
46
|
-
▲
|
|
47
|
-
│ (direct dependents — depth 1)
|
|
48
|
-
[A, B, C]
|
|
49
|
-
▲
|
|
50
|
-
│ (dependents of A, B, C — depth 2)
|
|
51
|
-
[D, E, F, G]
|
|
52
|
-
▲ ...and so on up to depth 10
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Step 3 — Score each affected node
|
|
56
|
-
|
|
57
|
-
Impact level is determined by **BFS depth**:
|
|
58
|
-
|
|
59
|
-
| Depth | Impact Level | Breaking Change? |
|
|
60
|
-
|---|---|---|
|
|
61
|
-
| 1 | `critical` | Yes |
|
|
62
|
-
| 2 | `high` | Yes |
|
|
63
|
-
| 3–4 | `medium` | No |
|
|
64
|
-
| 5–10 | `low` | No |
|
|
65
|
-
|
|
66
|
-
Nodes at depth ≤ 2 are flagged as `breakingChange: true` because they directly consume the target's interface.
|
|
67
|
-
|
|
68
|
-
### Step 4 — Compute the risk score
|
|
69
|
-
|
|
70
|
-
```ts
|
|
71
|
-
const score = C * 30 + H * 15 + M * 7 + L * 2 + inDegree * 3;
|
|
72
|
-
return Math.min(100, score);
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Where `C`, `H`, `M`, `L` are the counts of critical/high/medium/low affected nodes, and `inDegree` is the target node's own in-degree.
|
|
76
|
-
|
|
77
|
-
The score is capped at 100.
|
|
78
|
-
|
|
79
|
-
**Risk level thresholds:**
|
|
80
|
-
|
|
81
|
-
| Score | Risk Level |
|
|
82
|
-
|---|---|
|
|
83
|
-
| ≥ 75 | `CRITICAL` |
|
|
84
|
-
| ≥ 50 | `HIGH` |
|
|
85
|
-
| ≥ 25 | `MEDIUM` |
|
|
86
|
-
| < 25 | `LOW` |
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Output Shape — `ImpactReport`
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
interface ImpactReport {
|
|
94
|
-
targetNode: string; // the ID of the changed node
|
|
95
|
-
changeDescription: string; // from CLI --impact flag
|
|
96
|
-
riskScore: number; // 0–100
|
|
97
|
-
riskLevel: string; // LOW / MEDIUM / HIGH / CRITICAL
|
|
98
|
-
affectedNodes: AffectedNode[];
|
|
99
|
-
breakingChanges: AffectedNode[]; // subset of affectedNodes where breakingChange=true
|
|
100
|
-
testingPlan: string[];
|
|
101
|
-
recommendations: string[];
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### `AffectedNode`
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
interface AffectedNode {
|
|
109
|
-
nodeId: string;
|
|
110
|
-
name: string;
|
|
111
|
-
file: string;
|
|
112
|
-
depth: number; // BFS distance from target
|
|
113
|
-
impact: string; // critical / high / medium / low
|
|
114
|
-
reason: string; // human-readable why
|
|
115
|
-
changeRequired: string; // what action is needed
|
|
116
|
-
breakingChange: boolean;
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Recommendations Logic
|
|
123
|
-
|
|
124
|
-
Recommendations are generated automatically based on the risk score:
|
|
125
|
-
|
|
126
|
-
| Risk Score | Recommendations |
|
|
127
|
-
|---|---|
|
|
128
|
-
| ≥ 75 | Full team review, phased rollout, full regression suite |
|
|
129
|
-
| ≥ 50 | Tech lead review, feature flag the change |
|
|
130
|
-
| ≥ 25 | Code review, test all affected modules |
|
|
131
|
-
| < 25 | Standard PR process sufficient |
|
|
132
|
-
|
|
133
|
-
If any breaking changes exist, an additional note is always appended.
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## Usage Example
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
node depgraph.js ./src --impact "getUserById" "removing userId parameter"
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
This runs the full pipeline and then simulates changing `getUserById`, returning a detailed report of everything that would be affected.
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Edge Cases
|
|
148
|
-
|
|
149
|
-
| Situation | Behaviour |
|
|
150
|
-
|---|---|
|
|
151
|
-
| Target not found | Returns an empty report with `riskScore: 0` and an explanatory testing plan entry |
|
|
152
|
-
| Target has no dependents | Returns an empty `affectedNodes` array and `LOW` risk |
|
|
153
|
-
| Circular dependencies | The `visited` Set prevents infinite loops |
|
|
154
|
-
| Extremely deep dependency chains | `MAX_BFS_DEPTH = 10` hard-caps traversal |
|