@bugsbunnycodes1998/cartographer-core 0.1.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/dist/__tests__/pipeline.test.d.ts +2 -0
- package/dist/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/__tests__/pipeline.test.js +58 -0
- package/dist/__tests__/pipeline.test.js.map +1 -0
- package/dist/analysis/call-graph.d.ts +13 -0
- package/dist/analysis/call-graph.d.ts.map +1 -0
- package/dist/analysis/call-graph.js +133 -0
- package/dist/analysis/call-graph.js.map +1 -0
- package/dist/analysis/dependency-graph.d.ts +8 -0
- package/dist/analysis/dependency-graph.d.ts.map +1 -0
- package/dist/analysis/dependency-graph.js +101 -0
- package/dist/analysis/dependency-graph.js.map +1 -0
- package/dist/analysis/git/archaeology.d.ts +20 -0
- package/dist/analysis/git/archaeology.d.ts.map +1 -0
- package/dist/analysis/git/archaeology.js +61 -0
- package/dist/analysis/git/archaeology.js.map +1 -0
- package/dist/analysis/git/bus-factor.d.ts +17 -0
- package/dist/analysis/git/bus-factor.d.ts.map +1 -0
- package/dist/analysis/git/bus-factor.js +90 -0
- package/dist/analysis/git/bus-factor.js.map +1 -0
- package/dist/analysis/git/churn.d.ts +26 -0
- package/dist/analysis/git/churn.d.ts.map +1 -0
- package/dist/analysis/git/churn.js +127 -0
- package/dist/analysis/git/churn.js.map +1 -0
- package/dist/analysis/git/contributors.d.ts +14 -0
- package/dist/analysis/git/contributors.d.ts.map +1 -0
- package/dist/analysis/git/contributors.js +60 -0
- package/dist/analysis/git/contributors.js.map +1 -0
- package/dist/analysis/git/evolution.d.ts +10 -0
- package/dist/analysis/git/evolution.d.ts.map +1 -0
- package/dist/analysis/git/evolution.js +127 -0
- package/dist/analysis/git/evolution.js.map +1 -0
- package/dist/analysis/git/index.d.ts +6 -0
- package/dist/analysis/git/index.d.ts.map +1 -0
- package/dist/analysis/git/index.js +6 -0
- package/dist/analysis/git/index.js.map +1 -0
- package/dist/analysis/go-visitor.d.ts +4 -0
- package/dist/analysis/go-visitor.d.ts.map +1 -0
- package/dist/analysis/go-visitor.js +295 -0
- package/dist/analysis/go-visitor.js.map +1 -0
- package/dist/analysis/health/indicators.d.ts +25 -0
- package/dist/analysis/health/indicators.d.ts.map +1 -0
- package/dist/analysis/health/indicators.js +53 -0
- package/dist/analysis/health/indicators.js.map +1 -0
- package/dist/analysis/health/scorer.d.ts +26 -0
- package/dist/analysis/health/scorer.d.ts.map +1 -0
- package/dist/analysis/health/scorer.js +97 -0
- package/dist/analysis/health/scorer.js.map +1 -0
- package/dist/analysis/health/test-coverage.d.ts +19 -0
- package/dist/analysis/health/test-coverage.d.ts.map +1 -0
- package/dist/analysis/health/test-coverage.js +67 -0
- package/dist/analysis/health/test-coverage.js.map +1 -0
- package/dist/analysis/import-resolver.d.ts +10 -0
- package/dist/analysis/import-resolver.d.ts.map +1 -0
- package/dist/analysis/import-resolver.js +353 -0
- package/dist/analysis/import-resolver.js.map +1 -0
- package/dist/analysis/index.d.ts +22 -0
- package/dist/analysis/index.d.ts.map +1 -0
- package/dist/analysis/index.js +24 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/java-visitor.d.ts +4 -0
- package/dist/analysis/java-visitor.d.ts.map +1 -0
- package/dist/analysis/java-visitor.js +257 -0
- package/dist/analysis/java-visitor.js.map +1 -0
- package/dist/analysis/metrics.d.ts +19 -0
- package/dist/analysis/metrics.d.ts.map +1 -0
- package/dist/analysis/metrics.js +70 -0
- package/dist/analysis/metrics.js.map +1 -0
- package/dist/analysis/module-detector.d.ts +3 -0
- package/dist/analysis/module-detector.d.ts.map +1 -0
- package/dist/analysis/module-detector.js +257 -0
- package/dist/analysis/module-detector.js.map +1 -0
- package/dist/analysis/parser.d.ts +13 -0
- package/dist/analysis/parser.d.ts.map +1 -0
- package/dist/analysis/parser.js +92 -0
- package/dist/analysis/parser.js.map +1 -0
- package/dist/analysis/python-visitor.d.ts +4 -0
- package/dist/analysis/python-visitor.d.ts.map +1 -0
- package/dist/analysis/python-visitor.js +306 -0
- package/dist/analysis/python-visitor.js.map +1 -0
- package/dist/analysis/rust-visitor.d.ts +4 -0
- package/dist/analysis/rust-visitor.d.ts.map +1 -0
- package/dist/analysis/rust-visitor.js +318 -0
- package/dist/analysis/rust-visitor.js.map +1 -0
- package/dist/analysis/semantic/cache.d.ts +16 -0
- package/dist/analysis/semantic/cache.d.ts.map +1 -0
- package/dist/analysis/semantic/cache.js +26 -0
- package/dist/analysis/semantic/cache.js.map +1 -0
- package/dist/analysis/semantic/openai-client.d.ts +21 -0
- package/dist/analysis/semantic/openai-client.d.ts.map +1 -0
- package/dist/analysis/semantic/openai-client.js +73 -0
- package/dist/analysis/semantic/openai-client.js.map +1 -0
- package/dist/analysis/semantic/prompts.d.ts +32 -0
- package/dist/analysis/semantic/prompts.d.ts.map +1 -0
- package/dist/analysis/semantic/prompts.js +134 -0
- package/dist/analysis/semantic/prompts.js.map +1 -0
- package/dist/analysis/semantic/summarizer.d.ts +36 -0
- package/dist/analysis/semantic/summarizer.d.ts.map +1 -0
- package/dist/analysis/semantic/summarizer.js +229 -0
- package/dist/analysis/semantic/summarizer.js.map +1 -0
- package/dist/analysis/ts-js-visitor.d.ts +4 -0
- package/dist/analysis/ts-js-visitor.d.ts.map +1 -0
- package/dist/analysis/ts-js-visitor.js +390 -0
- package/dist/analysis/ts-js-visitor.js.map +1 -0
- package/dist/analysis/visitor-registry.d.ts +5 -0
- package/dist/analysis/visitor-registry.d.ts.map +1 -0
- package/dist/analysis/visitor-registry.js +17 -0
- package/dist/analysis/visitor-registry.js.map +1 -0
- package/dist/analysis/visitor-utils.d.ts +38 -0
- package/dist/analysis/visitor-utils.d.ts.map +1 -0
- package/dist/analysis/visitor-utils.js +110 -0
- package/dist/analysis/visitor-utils.js.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/expeditions/custom.d.ts +11 -0
- package/dist/expeditions/custom.d.ts.map +1 -0
- package/dist/expeditions/custom.js +61 -0
- package/dist/expeditions/custom.js.map +1 -0
- package/dist/expeditions/danger-zones.d.ts +9 -0
- package/dist/expeditions/danger-zones.d.ts.map +1 -0
- package/dist/expeditions/danger-zones.js +42 -0
- package/dist/expeditions/danger-zones.js.map +1 -0
- package/dist/expeditions/generator.d.ts +21 -0
- package/dist/expeditions/generator.d.ts.map +1 -0
- package/dist/expeditions/generator.js +78 -0
- package/dist/expeditions/generator.js.map +1 -0
- package/dist/expeditions/grand-tour.d.ts +10 -0
- package/dist/expeditions/grand-tour.d.ts.map +1 -0
- package/dist/expeditions/grand-tour.js +96 -0
- package/dist/expeditions/grand-tour.js.map +1 -0
- package/dist/expeditions/request-flow.d.ts +11 -0
- package/dist/expeditions/request-flow.d.ts.map +1 -0
- package/dist/expeditions/request-flow.js +138 -0
- package/dist/expeditions/request-flow.js.map +1 -0
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-java.wasm +0 -0
- package/dist/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/grammars/tree-sitter-python.wasm +0 -0
- package/dist/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/graph/builder.d.ts +23 -0
- package/dist/graph/builder.d.ts.map +1 -0
- package/dist/graph/builder.js +131 -0
- package/dist/graph/builder.js.map +1 -0
- package/dist/graph/exporter.d.ts +5 -0
- package/dist/graph/exporter.d.ts.map +1 -0
- package/dist/graph/exporter.js +20 -0
- package/dist/graph/exporter.js.map +1 -0
- package/dist/graph/layout.d.ts +3 -0
- package/dist/graph/layout.d.ts.map +1 -0
- package/dist/graph/layout.js +257 -0
- package/dist/graph/layout.js.map +1 -0
- package/dist/graph/store.d.ts +13 -0
- package/dist/graph/store.d.ts.map +1 -0
- package/dist/graph/store.js +145 -0
- package/dist/graph/store.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/ingestion/cloner.d.ts +18 -0
- package/dist/ingestion/cloner.d.ts.map +1 -0
- package/dist/ingestion/cloner.js +62 -0
- package/dist/ingestion/cloner.js.map +1 -0
- package/dist/ingestion/file-tree.d.ts +3 -0
- package/dist/ingestion/file-tree.d.ts.map +1 -0
- package/dist/ingestion/file-tree.js +85 -0
- package/dist/ingestion/file-tree.js.map +1 -0
- package/dist/ingestion/index.d.ts +5 -0
- package/dist/ingestion/index.d.ts.map +1 -0
- package/dist/ingestion/index.js +5 -0
- package/dist/ingestion/index.js.map +1 -0
- package/dist/ingestion/language-detector.d.ts +8 -0
- package/dist/ingestion/language-detector.d.ts.map +1 -0
- package/dist/ingestion/language-detector.js +37 -0
- package/dist/ingestion/language-detector.js.map +1 -0
- package/dist/ingestion/path-resolver.d.ts +7 -0
- package/dist/ingestion/path-resolver.d.ts.map +1 -0
- package/dist/ingestion/path-resolver.js +30 -0
- package/dist/ingestion/path-resolver.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +11 -0
- package/dist/logger.js.map +1 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +223 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/types/analysis.d.ts +51 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +2 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/config.d.ts +19 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +2 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/edges.d.ts +8 -0
- package/dist/types/edges.d.ts.map +1 -0
- package/dist/types/edges.js +2 -0
- package/dist/types/edges.js.map +1 -0
- package/dist/types/expeditions.d.ts +19 -0
- package/dist/types/expeditions.d.ts.map +1 -0
- package/dist/types/expeditions.js +2 -0
- package/dist/types/expeditions.js.map +1 -0
- package/dist/types/graph.d.ts +29 -0
- package/dist/types/graph.d.ts.map +1 -0
- package/dist/types/graph.js +2 -0
- package/dist/types/graph.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/nodes.d.ts +35 -0
- package/dist/types/nodes.d.ts.map +1 -0
- package/dist/types/nodes.js +2 -0
- package/dist/types/nodes.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pipeline.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import { existsSync, rmSync } from "node:fs";
|
|
4
|
+
import { analyze } from "../pipeline.js";
|
|
5
|
+
const FIXTURE_DIR = resolve(import.meta.dirname, "..", "..", "..", "..", "test", "fixtures", "tiny-ts");
|
|
6
|
+
const OUTPUT_DIR = join(FIXTURE_DIR, ".cartographer-test");
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
if (existsSync(OUTPUT_DIR)) {
|
|
9
|
+
rmSync(OUTPUT_DIR, { recursive: true, force: true });
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
describe("analyze pipeline", () => {
|
|
13
|
+
it("analyzes the tiny-ts fixture and produces a valid graph", async () => {
|
|
14
|
+
const graph = await analyze({
|
|
15
|
+
rootDir: FIXTURE_DIR,
|
|
16
|
+
outputDir: OUTPUT_DIR,
|
|
17
|
+
});
|
|
18
|
+
// Check meta
|
|
19
|
+
expect(graph.meta.languages).toContain("typescript");
|
|
20
|
+
expect(graph.meta.totalFiles).toBeGreaterThan(0);
|
|
21
|
+
expect(graph.meta.totalModules).toBeGreaterThan(0);
|
|
22
|
+
// Check nodes exist
|
|
23
|
+
const modules = graph.nodes.filter((n) => n.kind === "module");
|
|
24
|
+
const files = graph.nodes.filter((n) => n.kind === "file");
|
|
25
|
+
expect(modules.length).toBeGreaterThanOrEqual(2); // at least auth, utils
|
|
26
|
+
expect(files.length).toBeGreaterThanOrEqual(5);
|
|
27
|
+
// Check module names
|
|
28
|
+
const moduleNames = modules.map((m) => m.name);
|
|
29
|
+
expect(moduleNames).toContain("auth");
|
|
30
|
+
expect(moduleNames).toContain("utils");
|
|
31
|
+
// Check files have LOC > 0
|
|
32
|
+
for (const file of files) {
|
|
33
|
+
expect(file.loc).toBeGreaterThan(0);
|
|
34
|
+
}
|
|
35
|
+
// Check file-to-module relationships
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
expect(file.parentId).toBeTruthy();
|
|
38
|
+
expect(modules.some((m) => m.id === file.parentId)).toBe(true);
|
|
39
|
+
}
|
|
40
|
+
// auth -> utils dependency (login.ts imports hash.ts)
|
|
41
|
+
expect(graph.edges.length).toBeGreaterThan(0);
|
|
42
|
+
const authToUtils = graph.edges.find((e) => e.sourceId === "mod:auth" && e.targetId === "mod:utils");
|
|
43
|
+
expect(authToUtils).toBeDefined();
|
|
44
|
+
// Check layout: modules have non-zero positions
|
|
45
|
+
const modulesWithPositions = modules.filter((n) => n.position.x !== 0 || n.position.z !== 0);
|
|
46
|
+
expect(modulesWithPositions.length).toBeGreaterThan(0);
|
|
47
|
+
// Check file buildings have dimensions
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
expect(file.dimensions.width).toBeGreaterThan(0);
|
|
50
|
+
expect(file.dimensions.height).toBeGreaterThan(0);
|
|
51
|
+
expect(file.dimensions.depth).toBeGreaterThan(0);
|
|
52
|
+
}
|
|
53
|
+
// Check output files were created
|
|
54
|
+
expect(existsSync(join(OUTPUT_DIR, "cartographer.db"))).toBe(true);
|
|
55
|
+
expect(existsSync(join(OUTPUT_DIR, "graph.json"))).toBe(true);
|
|
56
|
+
}, 30000);
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=pipeline.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.test.js","sourceRoot":"","sources":["../../src/__tests__/pipeline.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GraphNode, GraphEdge, FileAnalysis, ModuleInfo } from "../types/index.js";
|
|
2
|
+
export interface CallGraphResult {
|
|
3
|
+
functionNodes: GraphNode[];
|
|
4
|
+
callEdges: GraphEdge[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Build function-level nodes and call edges from file analyses.
|
|
8
|
+
*
|
|
9
|
+
* Creates a `GraphNode` for each function declaration and a `GraphEdge`
|
|
10
|
+
* for each resolved function-to-function call (same-file and cross-file).
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildCallGraph(analyses: FileAnalysis[], modules: ModuleInfo[], rootDir: string): CallGraphResult;
|
|
13
|
+
//# sourceMappingURL=call-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-graph.d.ts","sourceRoot":"","sources":["../../src/analysis/call-graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAOxF,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,SAAS,EAAE,CAAC;IAC3B,SAAS,EAAE,SAAS,EAAE,CAAC;CACxB;AA2BD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,MAAM,GACd,eAAe,CA+GjB"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
function normalizePath(p) {
|
|
4
|
+
return p.replace(/\\/g, "/");
|
|
5
|
+
}
|
|
6
|
+
function makeDefaultNode(id, kind, name) {
|
|
7
|
+
return {
|
|
8
|
+
id,
|
|
9
|
+
kind,
|
|
10
|
+
name,
|
|
11
|
+
path: null,
|
|
12
|
+
parentId: null,
|
|
13
|
+
loc: 0,
|
|
14
|
+
complexity: 0,
|
|
15
|
+
healthScore: 50,
|
|
16
|
+
createdAt: null,
|
|
17
|
+
lastModified: null,
|
|
18
|
+
totalCommits: 0,
|
|
19
|
+
busFactor: 0,
|
|
20
|
+
primaryOwner: null,
|
|
21
|
+
churnScore: 0,
|
|
22
|
+
summary: null,
|
|
23
|
+
explanation: null,
|
|
24
|
+
layer: null,
|
|
25
|
+
tags: [],
|
|
26
|
+
position: { x: 0, y: 0, z: 0 },
|
|
27
|
+
dimensions: { width: 1, height: 1, depth: 1 },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build function-level nodes and call edges from file analyses.
|
|
32
|
+
*
|
|
33
|
+
* Creates a `GraphNode` for each function declaration and a `GraphEdge`
|
|
34
|
+
* for each resolved function-to-function call (same-file and cross-file).
|
|
35
|
+
*/
|
|
36
|
+
export function buildCallGraph(analyses, modules, rootDir) {
|
|
37
|
+
const functionNodes = [];
|
|
38
|
+
const callEdgeMap = new Map();
|
|
39
|
+
// Maps for resolution
|
|
40
|
+
// relPath -> function name[]
|
|
41
|
+
const functionsByFile = new Map();
|
|
42
|
+
// relPath -> Map<importedName, sourceRelPath>
|
|
43
|
+
const importedNamesByFile = new Map();
|
|
44
|
+
// Set of all function node IDs for validation
|
|
45
|
+
const functionNodeIds = new Set();
|
|
46
|
+
// Pass 1: Create function nodes and build lookup maps
|
|
47
|
+
for (const analysis of analyses) {
|
|
48
|
+
const relPath = normalizePath(relative(rootDir, analysis.filePath));
|
|
49
|
+
const fileId = `file:${relPath}`;
|
|
50
|
+
// Find parent module for the file
|
|
51
|
+
let parentModuleId = null;
|
|
52
|
+
for (const mod of modules) {
|
|
53
|
+
if (mod.files.includes(relPath)) {
|
|
54
|
+
parentModuleId = mod.id;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Track functions declared in this file
|
|
59
|
+
const fnNames = new Set();
|
|
60
|
+
for (const fn of analysis.functions) {
|
|
61
|
+
const fnId = `fn:${relPath}:${fn.name}`;
|
|
62
|
+
const node = makeDefaultNode(fnId, "function", fn.name);
|
|
63
|
+
node.path = relPath;
|
|
64
|
+
node.parentId = fileId;
|
|
65
|
+
node.loc = fn.endLine - fn.startLine + 1;
|
|
66
|
+
node.complexity = fn.complexity;
|
|
67
|
+
// Inherit module's layer if no direct semantic info
|
|
68
|
+
// (Semantic data would be attached in a later enrichment pass)
|
|
69
|
+
functionNodes.push(node);
|
|
70
|
+
fnNames.add(fn.name);
|
|
71
|
+
functionNodeIds.add(fnId);
|
|
72
|
+
}
|
|
73
|
+
functionsByFile.set(relPath, fnNames);
|
|
74
|
+
// Build imported name -> source file mapping
|
|
75
|
+
const nameToSource = new Map();
|
|
76
|
+
for (const imp of analysis.imports) {
|
|
77
|
+
if (!imp.resolvedPath)
|
|
78
|
+
continue;
|
|
79
|
+
const targetRelPath = normalizePath(relative(rootDir, imp.resolvedPath));
|
|
80
|
+
for (const name of imp.names) {
|
|
81
|
+
nameToSource.set(name, targetRelPath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
importedNamesByFile.set(relPath, nameToSource);
|
|
85
|
+
}
|
|
86
|
+
// Pass 2: Resolve calls to edges
|
|
87
|
+
for (const analysis of analyses) {
|
|
88
|
+
const relPath = normalizePath(relative(rootDir, analysis.filePath));
|
|
89
|
+
const localFns = functionsByFile.get(relPath) ?? new Set();
|
|
90
|
+
const importedNames = importedNamesByFile.get(relPath) ?? new Map();
|
|
91
|
+
for (const fn of analysis.functions) {
|
|
92
|
+
const callerId = `fn:${relPath}:${fn.name}`;
|
|
93
|
+
for (const call of fn.calls) {
|
|
94
|
+
let calleeId = null;
|
|
95
|
+
// Case 1: callee is a local function in the same file
|
|
96
|
+
if (localFns.has(call.calleeName)) {
|
|
97
|
+
calleeId = `fn:${relPath}:${call.calleeName}`;
|
|
98
|
+
}
|
|
99
|
+
// Case 2: callee matches an imported name
|
|
100
|
+
else if (importedNames.has(call.calleeName)) {
|
|
101
|
+
const sourceRelPath = importedNames.get(call.calleeName);
|
|
102
|
+
const sourceFns = functionsByFile.get(sourceRelPath);
|
|
103
|
+
if (sourceFns?.has(call.calleeName)) {
|
|
104
|
+
calleeId = `fn:${sourceRelPath}:${call.calleeName}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (!calleeId)
|
|
108
|
+
continue;
|
|
109
|
+
if (calleeId === callerId)
|
|
110
|
+
continue; // Skip self-calls (recursion edge not useful here)
|
|
111
|
+
if (!functionNodeIds.has(calleeId))
|
|
112
|
+
continue; // Validate target exists
|
|
113
|
+
const edgeKey = `${callerId}|${calleeId}`;
|
|
114
|
+
const existing = callEdgeMap.get(edgeKey);
|
|
115
|
+
if (existing) {
|
|
116
|
+
existing.weight += 1;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
callEdgeMap.set(edgeKey, {
|
|
120
|
+
sourceId: callerId,
|
|
121
|
+
targetId: calleeId,
|
|
122
|
+
kind: "calls",
|
|
123
|
+
weight: 1,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const callEdges = Array.from(callEdgeMap.values());
|
|
130
|
+
logger.debug(`Call graph: ${functionNodes.length} function nodes, ${callEdges.length} call edges`);
|
|
131
|
+
return { functionNodes, callEdges };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=call-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-graph.js","sourceRoot":"","sources":["../../src/analysis/call-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FileAnalysis, ModuleInfo, GraphEdge } from "../types/index.js";
|
|
2
|
+
export interface DependencyGraphResult {
|
|
3
|
+
fileEdges: GraphEdge[];
|
|
4
|
+
moduleEdges: GraphEdge[];
|
|
5
|
+
circularDeps: string[][];
|
|
6
|
+
}
|
|
7
|
+
export declare function buildDependencyGraph(analyses: FileAnalysis[], modules: ModuleInfo[], rootDir: string): DependencyGraphResult;
|
|
8
|
+
//# sourceMappingURL=dependency-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../../src/analysis/dependency-graph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAiB7E,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;CAC1B;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,YAAY,EAAE,EACxB,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,MAAM,GACd,qBAAqB,CAwDvB"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
import { logger } from "../logger.js";
|
|
3
|
+
function normalizePath(p) {
|
|
4
|
+
return p.replace(/\\/g, "/");
|
|
5
|
+
}
|
|
6
|
+
function fileToModule(filePath, modules) {
|
|
7
|
+
const relPath = normalizePath(filePath);
|
|
8
|
+
for (const mod of modules) {
|
|
9
|
+
if (mod.files.includes(relPath)) {
|
|
10
|
+
return mod.id;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
export function buildDependencyGraph(analyses, modules, rootDir) {
|
|
16
|
+
const fileEdges = [];
|
|
17
|
+
const moduleEdgeMap = new Map();
|
|
18
|
+
// Build file-to-file edges from resolved imports
|
|
19
|
+
for (const analysis of analyses) {
|
|
20
|
+
const sourceRelPath = normalizePath(relative(rootDir, analysis.filePath));
|
|
21
|
+
const sourceFileId = `file:${sourceRelPath}`;
|
|
22
|
+
for (const imp of analysis.imports) {
|
|
23
|
+
if (!imp.resolvedPath)
|
|
24
|
+
continue;
|
|
25
|
+
if (imp.isTypeOnly)
|
|
26
|
+
continue; // Skip type-only imports for dependency edges
|
|
27
|
+
const targetRelPath = normalizePath(relative(rootDir, imp.resolvedPath));
|
|
28
|
+
const targetFileId = `file:${targetRelPath}`;
|
|
29
|
+
if (sourceFileId === targetFileId)
|
|
30
|
+
continue;
|
|
31
|
+
fileEdges.push({
|
|
32
|
+
sourceId: sourceFileId,
|
|
33
|
+
targetId: targetFileId,
|
|
34
|
+
kind: "imports",
|
|
35
|
+
weight: imp.names.length || 1,
|
|
36
|
+
});
|
|
37
|
+
// Aggregate to module-level edges
|
|
38
|
+
const sourceModule = fileToModule(sourceRelPath, modules);
|
|
39
|
+
const targetModule = fileToModule(targetRelPath, modules);
|
|
40
|
+
if (sourceModule && targetModule && sourceModule !== targetModule) {
|
|
41
|
+
const edgeKey = `${sourceModule}→${targetModule}`;
|
|
42
|
+
moduleEdgeMap.set(edgeKey, (moduleEdgeMap.get(edgeKey) ?? 0) + 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Convert module edge map to GraphEdge[]
|
|
47
|
+
const moduleEdges = [];
|
|
48
|
+
for (const [key, weight] of moduleEdgeMap) {
|
|
49
|
+
const [sourceId, targetId] = key.split("→");
|
|
50
|
+
moduleEdges.push({
|
|
51
|
+
sourceId,
|
|
52
|
+
targetId,
|
|
53
|
+
kind: "imports",
|
|
54
|
+
weight,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Detect circular dependencies at module level
|
|
58
|
+
const circularDeps = detectCycles(moduleEdges);
|
|
59
|
+
if (circularDeps.length > 0) {
|
|
60
|
+
logger.warn(`Circular dependencies detected: ${circularDeps.map((c) => c.join(" → ")).join("; ")}`);
|
|
61
|
+
}
|
|
62
|
+
logger.debug(`Dependency graph: ${fileEdges.length} file edges, ${moduleEdges.length} module edges`);
|
|
63
|
+
return { fileEdges, moduleEdges, circularDeps };
|
|
64
|
+
}
|
|
65
|
+
function detectCycles(edges) {
|
|
66
|
+
const adj = new Map();
|
|
67
|
+
for (const edge of edges) {
|
|
68
|
+
const neighbors = adj.get(edge.sourceId) ?? [];
|
|
69
|
+
neighbors.push(edge.targetId);
|
|
70
|
+
adj.set(edge.sourceId, neighbors);
|
|
71
|
+
}
|
|
72
|
+
const cycles = [];
|
|
73
|
+
const visited = new Set();
|
|
74
|
+
const inStack = new Set();
|
|
75
|
+
const path = [];
|
|
76
|
+
function dfs(node) {
|
|
77
|
+
if (inStack.has(node)) {
|
|
78
|
+
// Found a cycle
|
|
79
|
+
const cycleStart = path.indexOf(node);
|
|
80
|
+
if (cycleStart !== -1) {
|
|
81
|
+
cycles.push([...path.slice(cycleStart), node]);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (visited.has(node))
|
|
86
|
+
return;
|
|
87
|
+
visited.add(node);
|
|
88
|
+
inStack.add(node);
|
|
89
|
+
path.push(node);
|
|
90
|
+
for (const neighbor of adj.get(node) ?? []) {
|
|
91
|
+
dfs(neighbor);
|
|
92
|
+
}
|
|
93
|
+
path.pop();
|
|
94
|
+
inStack.delete(node);
|
|
95
|
+
}
|
|
96
|
+
for (const node of adj.keys()) {
|
|
97
|
+
dfs(node);
|
|
98
|
+
}
|
|
99
|
+
return cycles;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=dependency-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-graph.js","sourceRoot":"","sources":["../../src/analysis/dependency-graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ModuleInfo, EvolutionSnapshot } from "../../types/index.js";
|
|
2
|
+
import { type ChurnData } from "./churn.js";
|
|
3
|
+
import { type BusFactorData } from "./bus-factor.js";
|
|
4
|
+
import { type ContributorInfo } from "./contributors.js";
|
|
5
|
+
export interface GitArchaeologyResult {
|
|
6
|
+
churnMap: Map<string, ChurnData>;
|
|
7
|
+
busFactorMap: Map<string, BusFactorData>;
|
|
8
|
+
contributors: ContributorInfo[];
|
|
9
|
+
evolution: EvolutionSnapshot[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Run all git archaeology analyses on a repository.
|
|
13
|
+
*
|
|
14
|
+
* Orchestrates churn computation, bus factor analysis, contributor
|
|
15
|
+
* extraction, and evolution timeline building. Each sub-module handles
|
|
16
|
+
* its own errors gracefully — partial results are returned if some
|
|
17
|
+
* analyses fail.
|
|
18
|
+
*/
|
|
19
|
+
export declare function analyzeGitHistory(gitDir: string, filePaths: string[], modules: ModuleInfo[]): Promise<GitArchaeologyResult>;
|
|
20
|
+
//# sourceMappingURL=archaeology.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archaeology.d.ts","sourceRoot":"","sources":["../../../src/analysis/git/archaeology.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE1E,OAAO,EAAgB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAG1E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACjC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACzC,YAAY,EAAE,eAAe,EAAE,CAAC;IAChC,SAAS,EAAE,iBAAiB,EAAE,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC,oBAAoB,CAAC,CA+B/B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { logger } from "../../logger.js";
|
|
2
|
+
import { computeChurn } from "./churn.js";
|
|
3
|
+
import { computeBusFactors } from "./bus-factor.js";
|
|
4
|
+
import { getContributors } from "./contributors.js";
|
|
5
|
+
import { buildEvolutionTimeline } from "./evolution.js";
|
|
6
|
+
/**
|
|
7
|
+
* Run all git archaeology analyses on a repository.
|
|
8
|
+
*
|
|
9
|
+
* Orchestrates churn computation, bus factor analysis, contributor
|
|
10
|
+
* extraction, and evolution timeline building. Each sub-module handles
|
|
11
|
+
* its own errors gracefully — partial results are returned if some
|
|
12
|
+
* analyses fail.
|
|
13
|
+
*/
|
|
14
|
+
export async function analyzeGitHistory(gitDir, filePaths, modules) {
|
|
15
|
+
logger.info(`Git archaeology: analyzing ${filePaths.length} files in ${modules.length} modules`);
|
|
16
|
+
// Run churn and contributors in parallel (they don't depend on each other)
|
|
17
|
+
const [churnMap, contributors] = await Promise.all([
|
|
18
|
+
safeRunMap("churn", () => computeChurn(gitDir, filePaths)),
|
|
19
|
+
safeRunArray("contributors", () => getContributors(gitDir)),
|
|
20
|
+
]);
|
|
21
|
+
// Bus factor depends on modules
|
|
22
|
+
const busFactorMap = await safeRunMap("bus-factor", () => computeBusFactors(gitDir, modules));
|
|
23
|
+
// Evolution timeline
|
|
24
|
+
const evolution = await safeRunArray("evolution", () => buildEvolutionTimeline(gitDir, filePaths));
|
|
25
|
+
logger.info(`Git archaeology complete: ${churnMap.size} churn entries, ` +
|
|
26
|
+
`${busFactorMap.size} bus factors, ${contributors.length} contributors, ` +
|
|
27
|
+
`${evolution.length} evolution snapshots`);
|
|
28
|
+
return {
|
|
29
|
+
churnMap,
|
|
30
|
+
busFactorMap,
|
|
31
|
+
contributors,
|
|
32
|
+
evolution,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run an async function that returns a Map, with error handling.
|
|
37
|
+
* Returns an empty Map on failure.
|
|
38
|
+
*/
|
|
39
|
+
async function safeRunMap(name, fn) {
|
|
40
|
+
try {
|
|
41
|
+
return await fn();
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
logger.warn(`Git archaeology: ${name} failed — ${err instanceof Error ? err.message : err}`);
|
|
45
|
+
return new Map();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run an async function that returns an array, with error handling.
|
|
50
|
+
* Returns an empty array on failure.
|
|
51
|
+
*/
|
|
52
|
+
async function safeRunArray(name, fn) {
|
|
53
|
+
try {
|
|
54
|
+
return await fn();
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
logger.warn(`Git archaeology: ${name} failed — ${err instanceof Error ? err.message : err}`);
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=archaeology.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archaeology.js","sourceRoot":"","sources":["../../../src/analysis/git/archaeology.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAkB,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAsB,MAAM,iBAAiB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAwB,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AASxD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAAc,EACd,SAAmB,EACnB,OAAqB;IAErB,MAAM,CAAC,IAAI,CAAC,8BAA8B,SAAS,CAAC,MAAM,aAAa,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;IAEjG,2EAA2E;IAC3E,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACjD,UAAU,CAAoB,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAC7E,YAAY,CAAkB,cAAc,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;KAC7E,CAAC,CAAC;IAEH,gCAAgC;IAChC,MAAM,YAAY,GAAG,MAAM,UAAU,CAAwB,YAAY,EAAE,GAAG,EAAE,CAC9E,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CACnC,CAAC;IAEF,qBAAqB;IACrB,MAAM,SAAS,GAAG,MAAM,YAAY,CAAoB,WAAW,EAAE,GAAG,EAAE,CACxE,sBAAsB,CAAC,MAAM,EAAE,SAAS,CAAC,CAC1C,CAAC;IAEF,MAAM,CAAC,IAAI,CACT,6BAA6B,QAAQ,CAAC,IAAI,kBAAkB;QAC5D,GAAG,YAAY,CAAC,IAAI,iBAAiB,YAAY,CAAC,MAAM,iBAAiB;QACzE,GAAG,SAAS,CAAC,MAAM,sBAAsB,CAC1C,CAAC;IAEF,OAAO;QACL,QAAQ;QACR,YAAY;QACZ,YAAY;QACZ,SAAS;KACV,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,UAAU,CACvB,IAAY,EACZ,EAA4B;IAE5B,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CACT,oBAAoB,IAAI,aAAa,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAChF,CAAC;QACF,OAAO,IAAI,GAAG,EAAQ,CAAC;IACzB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,YAAY,CACzB,IAAY,EACZ,EAAsB;IAEtB,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CACT,oBAAoB,IAAI,aAAa,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,CAChF,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ModuleInfo } from "../../types/index.js";
|
|
2
|
+
export interface BusFactorData {
|
|
3
|
+
busFactor: number;
|
|
4
|
+
primaryOwner: string;
|
|
5
|
+
ownershipShare: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Compute the bus factor for each module.
|
|
9
|
+
*
|
|
10
|
+
* Since isomorphic-git has no blame command, we approximate:
|
|
11
|
+
* for each commit that touches files in a module, attribute all changes
|
|
12
|
+
* to the commit author. Then count author commits as a proxy for contribution.
|
|
13
|
+
*
|
|
14
|
+
* Bus factor = minimum N such that sum(top N knowledge shares) > 0.70
|
|
15
|
+
*/
|
|
16
|
+
export declare function computeBusFactors(gitDir: string, modules: ModuleInfo[]): Promise<Map<string, BusFactorData>>;
|
|
17
|
+
//# sourceMappingURL=bus-factor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus-factor.d.ts","sourceRoot":"","sources":["../../../src/analysis/git/bus-factor.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAyFrC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import git from "isomorphic-git";
|
|
3
|
+
import { logger } from "../../logger.js";
|
|
4
|
+
/**
|
|
5
|
+
* Compute the bus factor for each module.
|
|
6
|
+
*
|
|
7
|
+
* Since isomorphic-git has no blame command, we approximate:
|
|
8
|
+
* for each commit that touches files in a module, attribute all changes
|
|
9
|
+
* to the commit author. Then count author commits as a proxy for contribution.
|
|
10
|
+
*
|
|
11
|
+
* Bus factor = minimum N such that sum(top N knowledge shares) > 0.70
|
|
12
|
+
*/
|
|
13
|
+
export async function computeBusFactors(gitDir, modules) {
|
|
14
|
+
const result = new Map();
|
|
15
|
+
// First, get full log (limited for perf)
|
|
16
|
+
let allCommits;
|
|
17
|
+
try {
|
|
18
|
+
const logs = await git.log({ fs, dir: gitDir, depth: 500 });
|
|
19
|
+
allCommits = logs.map((entry) => ({
|
|
20
|
+
oid: entry.oid,
|
|
21
|
+
author: entry.commit.author.name,
|
|
22
|
+
timestamp: entry.commit.author.timestamp,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
logger.warn(`Bus factor: failed to read git log — ${err instanceof Error ? err.message : err}`);
|
|
27
|
+
// Return default bus factor for all modules
|
|
28
|
+
for (const mod of modules) {
|
|
29
|
+
result.set(mod.id, { busFactor: 0, primaryOwner: "unknown", ownershipShare: 0 });
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
// For each module, gather commits that touch its files
|
|
34
|
+
for (const mod of modules) {
|
|
35
|
+
try {
|
|
36
|
+
const authorCommitCounts = new Map();
|
|
37
|
+
let totalModuleCommits = 0;
|
|
38
|
+
// Get commits for each file in the module
|
|
39
|
+
for (const filePath of mod.files) {
|
|
40
|
+
try {
|
|
41
|
+
const fileCommits = await git.log({
|
|
42
|
+
fs,
|
|
43
|
+
dir: gitDir,
|
|
44
|
+
filepath: filePath,
|
|
45
|
+
depth: 500,
|
|
46
|
+
});
|
|
47
|
+
for (const entry of fileCommits) {
|
|
48
|
+
const author = entry.commit.author.name;
|
|
49
|
+
authorCommitCounts.set(author, (authorCommitCounts.get(author) ?? 0) + 1);
|
|
50
|
+
totalModuleCommits++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// File may not exist in git (untracked); skip silently
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (totalModuleCommits === 0 || authorCommitCounts.size === 0) {
|
|
58
|
+
result.set(mod.id, { busFactor: 0, primaryOwner: "unknown", ownershipShare: 0 });
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Sort authors by their share (descending)
|
|
62
|
+
const shares = [];
|
|
63
|
+
for (const [author, count] of authorCommitCounts) {
|
|
64
|
+
shares.push({ author, share: count / totalModuleCommits });
|
|
65
|
+
}
|
|
66
|
+
shares.sort((a, b) => b.share - a.share);
|
|
67
|
+
// Bus factor = min N where sum(top N shares) > 0.70
|
|
68
|
+
let cumulative = 0;
|
|
69
|
+
let busFactor = 0;
|
|
70
|
+
for (const entry of shares) {
|
|
71
|
+
cumulative += entry.share;
|
|
72
|
+
busFactor++;
|
|
73
|
+
if (cumulative > 0.70)
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
const primary = shares[0];
|
|
77
|
+
result.set(mod.id, {
|
|
78
|
+
busFactor,
|
|
79
|
+
primaryOwner: primary.author,
|
|
80
|
+
ownershipShare: primary.share,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
logger.debug(`Bus factor: error for module ${mod.id} — ${err instanceof Error ? err.message : err}`);
|
|
85
|
+
result.set(mod.id, { busFactor: 0, primaryOwner: "unknown", ownershipShare: 0 });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=bus-factor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus-factor.js","sourceRoot":"","sources":["../../../src/analysis/git/bus-factor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface ChurnData {
|
|
2
|
+
distinctAuthors: number;
|
|
3
|
+
totalCommits: number;
|
|
4
|
+
commitsLast90d: number;
|
|
5
|
+
linesChangedLast90d: number;
|
|
6
|
+
churnScore: number;
|
|
7
|
+
createdAt: string | null;
|
|
8
|
+
lastModified: string | null;
|
|
9
|
+
primaryAuthor: string | null;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Compute churn data for all files in the repository.
|
|
13
|
+
*
|
|
14
|
+
* Uses isomorphic-git.log() with filepath to get per-file commit history.
|
|
15
|
+
* Lines changed is approximated using commit count as a proxy (actual line
|
|
16
|
+
* diffs are expensive with isomorphic-git).
|
|
17
|
+
*
|
|
18
|
+
* Formula:
|
|
19
|
+
* rawChurn = (distinctAuthors/maxAuthors * 0.3)
|
|
20
|
+
* + (commitsLast90d/max(totalCommits,1) * 0.3)
|
|
21
|
+
* + (commitsLast90d/max(totalCommits,1) * 0.4) // simplified proxy
|
|
22
|
+
*
|
|
23
|
+
* Result is then normalized across all files to 0-1 range.
|
|
24
|
+
*/
|
|
25
|
+
export declare function computeChurn(gitDir: string, filePaths: string[]): Promise<Map<string, ChurnData>>;
|
|
26
|
+
//# sourceMappingURL=churn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"churn.d.ts","sourceRoot":"","sources":["../../../src/analysis/git/churn.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAQD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAmEjC"}
|