projscan 0.10.0 → 0.12.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/README.md +56 -19
- package/dist/analyzers/unusedDependencyCheck.js +69 -17
- package/dist/analyzers/unusedDependencyCheck.js.map +1 -1
- package/dist/cli/_shared.d.ts +16 -0
- package/dist/cli/_shared.js +210 -0
- package/dist/cli/_shared.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +1 -0
- package/dist/cli/commands/analyze.js +87 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/audit.d.ts +1 -0
- package/dist/cli/commands/audit.js +47 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/badge.d.ts +1 -0
- package/dist/cli/commands/badge.js +45 -0
- package/dist/cli/commands/badge.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +1 -0
- package/dist/cli/commands/ci.js +57 -0
- package/dist/cli/commands/ci.js.map +1 -0
- package/dist/cli/commands/coupling.d.ts +1 -0
- package/dist/cli/commands/coupling.js +83 -0
- package/dist/cli/commands/coupling.js.map +1 -0
- package/dist/cli/commands/coverage.d.ts +1 -0
- package/dist/cli/commands/coverage.js +63 -0
- package/dist/cli/commands/coverage.js.map +1 -0
- package/dist/cli/commands/dependencies.d.ts +1 -0
- package/dist/cli/commands/dependencies.js +45 -0
- package/dist/cli/commands/dependencies.js.map +1 -0
- package/dist/cli/commands/diagram.d.ts +1 -0
- package/dist/cli/commands/diagram.js +45 -0
- package/dist/cli/commands/diagram.js.map +1 -0
- package/dist/cli/commands/diff.d.ts +1 -0
- package/dist/cli/commands/diff.js +70 -0
- package/dist/cli/commands/diff.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +1 -0
- package/dist/cli/commands/doctor.js +62 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +1 -0
- package/dist/cli/commands/explain.js +42 -0
- package/dist/cli/commands/explain.js.map +1 -0
- package/dist/cli/commands/file.d.ts +1 -0
- package/dist/cli/commands/file.js +45 -0
- package/dist/cli/commands/file.js.map +1 -0
- package/dist/cli/commands/fix.d.ts +1 -0
- package/dist/cli/commands/fix.js +70 -0
- package/dist/cli/commands/fix.js.map +1 -0
- package/dist/cli/commands/help.d.ts +1 -0
- package/dist/cli/commands/help.js +11 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/hotspots.d.ts +1 -0
- package/dist/cli/commands/hotspots.js +74 -0
- package/dist/cli/commands/hotspots.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +21 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/outdated.d.ts +1 -0
- package/dist/cli/commands/outdated.js +51 -0
- package/dist/cli/commands/outdated.js.map +1 -0
- package/dist/cli/commands/prDiff.d.ts +1 -0
- package/dist/cli/commands/prDiff.js +59 -0
- package/dist/cli/commands/prDiff.js.map +1 -0
- package/dist/cli/commands/search.d.ts +1 -0
- package/dist/cli/commands/search.js +233 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/structure.d.ts +1 -0
- package/dist/cli/commands/structure.js +58 -0
- package/dist/cli/commands/structure.js.map +1 -0
- package/dist/cli/commands/upgrade.d.ts +1 -0
- package/dist/cli/commands/upgrade.js +44 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/commands/workspaces.d.ts +1 -0
- package/dist/cli/commands/workspaces.js +35 -0
- package/dist/cli/commands/workspaces.js.map +1 -0
- package/dist/cli/index.js +45 -1132
- package/dist/cli/index.js.map +1 -1
- package/dist/core/ast.d.ts +2 -0
- package/dist/core/ast.js +35 -2
- package/dist/core/ast.js.map +1 -1
- package/dist/core/codeGraph.d.ts +2 -0
- package/dist/core/codeGraph.js +2 -0
- package/dist/core/codeGraph.js.map +1 -1
- package/dist/core/couplingAnalyzer.d.ts +18 -0
- package/dist/core/couplingAnalyzer.js +174 -0
- package/dist/core/couplingAnalyzer.js.map +1 -0
- package/dist/core/fileInspector.d.ts +1 -1
- package/dist/core/fileInspector.js +31 -1
- package/dist/core/fileInspector.js.map +1 -1
- package/dist/core/hotspotAnalyzer.d.ts +13 -0
- package/dist/core/hotspotAnalyzer.js +29 -6
- package/dist/core/hotspotAnalyzer.js.map +1 -1
- package/dist/core/indexCache.js +6 -3
- package/dist/core/indexCache.js.map +1 -1
- package/dist/core/languages/LanguageAdapter.d.ts +1 -1
- package/dist/core/languages/goAdapter.d.ts +2 -0
- package/dist/core/languages/goAdapter.js +138 -0
- package/dist/core/languages/goAdapter.js.map +1 -0
- package/dist/core/languages/goCallSites.d.ts +20 -0
- package/dist/core/languages/goCallSites.js +42 -0
- package/dist/core/languages/goCallSites.js.map +1 -0
- package/dist/core/languages/goCyclomatic.d.ts +21 -0
- package/dist/core/languages/goCyclomatic.js +55 -0
- package/dist/core/languages/goCyclomatic.js.map +1 -0
- package/dist/core/languages/goExports.d.ts +26 -0
- package/dist/core/languages/goExports.js +89 -0
- package/dist/core/languages/goExports.js.map +1 -0
- package/dist/core/languages/goImports.d.ts +26 -0
- package/dist/core/languages/goImports.js +64 -0
- package/dist/core/languages/goImports.js.map +1 -0
- package/dist/core/languages/goManifests.d.ts +19 -0
- package/dist/core/languages/goManifests.js +56 -0
- package/dist/core/languages/goManifests.js.map +1 -0
- package/dist/core/languages/javaAdapter.d.ts +2 -0
- package/dist/core/languages/javaAdapter.js +148 -0
- package/dist/core/languages/javaAdapter.js.map +1 -0
- package/dist/core/languages/javaCallSites.d.ts +16 -0
- package/dist/core/languages/javaCallSites.js +45 -0
- package/dist/core/languages/javaCallSites.js.map +1 -0
- package/dist/core/languages/javaCyclomatic.d.ts +21 -0
- package/dist/core/languages/javaCyclomatic.js +49 -0
- package/dist/core/languages/javaCyclomatic.js.map +1 -0
- package/dist/core/languages/javaExports.d.ts +25 -0
- package/dist/core/languages/javaExports.js +80 -0
- package/dist/core/languages/javaExports.js.map +1 -0
- package/dist/core/languages/javaImports.d.ts +25 -0
- package/dist/core/languages/javaImports.js +49 -0
- package/dist/core/languages/javaImports.js.map +1 -0
- package/dist/core/languages/javaManifests.d.ts +25 -0
- package/dist/core/languages/javaManifests.js +86 -0
- package/dist/core/languages/javaManifests.js.map +1 -0
- package/dist/core/languages/pythonAdapter.js +8 -1
- package/dist/core/languages/pythonAdapter.js.map +1 -1
- package/dist/core/languages/pythonCallSites.d.ts +19 -0
- package/dist/core/languages/pythonCallSites.js +40 -0
- package/dist/core/languages/pythonCallSites.js.map +1 -0
- package/dist/core/languages/pythonCyclomatic.d.ts +18 -0
- package/dist/core/languages/pythonCyclomatic.js +45 -0
- package/dist/core/languages/pythonCyclomatic.js.map +1 -0
- package/dist/core/languages/registry.js +4 -1
- package/dist/core/languages/registry.js.map +1 -1
- package/dist/core/languages/rubyAdapter.d.ts +2 -0
- package/dist/core/languages/rubyAdapter.js +131 -0
- package/dist/core/languages/rubyAdapter.js.map +1 -0
- package/dist/core/languages/rubyCallSites.d.ts +16 -0
- package/dist/core/languages/rubyCallSites.js +34 -0
- package/dist/core/languages/rubyCallSites.js.map +1 -0
- package/dist/core/languages/rubyCyclomatic.d.ts +19 -0
- package/dist/core/languages/rubyCyclomatic.js +47 -0
- package/dist/core/languages/rubyCyclomatic.js.map +1 -0
- package/dist/core/languages/rubyExports.d.ts +24 -0
- package/dist/core/languages/rubyExports.js +53 -0
- package/dist/core/languages/rubyExports.js.map +1 -0
- package/dist/core/languages/rubyImports.d.ts +12 -0
- package/dist/core/languages/rubyImports.js +75 -0
- package/dist/core/languages/rubyImports.js.map +1 -0
- package/dist/core/languages/rubyManifests.d.ts +20 -0
- package/dist/core/languages/rubyManifests.js +55 -0
- package/dist/core/languages/rubyManifests.js.map +1 -0
- package/dist/core/languages/treeSitterLoader.js +4 -1
- package/dist/core/languages/treeSitterLoader.js.map +1 -1
- package/dist/core/monorepo.d.ts +20 -0
- package/dist/core/monorepo.js +270 -0
- package/dist/core/monorepo.js.map +1 -0
- package/dist/core/outdatedDetector.d.ts +13 -2
- package/dist/core/outdatedDetector.js +86 -16
- package/dist/core/outdatedDetector.js.map +1 -1
- package/dist/core/prDiff.d.ts +43 -0
- package/dist/core/prDiff.js +298 -0
- package/dist/core/prDiff.js.map +1 -0
- package/dist/core/telemetry.d.ts +90 -0
- package/dist/core/telemetry.js +199 -0
- package/dist/core/telemetry.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-ruby.wasm +0 -0
- package/dist/mcp/tools/_shared.d.ts +24 -0
- package/dist/mcp/tools/_shared.js +82 -0
- package/dist/mcp/tools/_shared.js.map +1 -0
- package/dist/mcp/tools/analyze.d.ts +2 -0
- package/dist/mcp/tools/analyze.js +55 -0
- package/dist/mcp/tools/analyze.js.map +1 -0
- package/dist/mcp/tools/audit.d.ts +2 -0
- package/dist/mcp/tools/audit.js +32 -0
- package/dist/mcp/tools/audit.js.map +1 -0
- package/dist/mcp/tools/coupling.d.ts +2 -0
- package/dist/mcp/tools/coupling.js +67 -0
- package/dist/mcp/tools/coupling.js.map +1 -0
- package/dist/mcp/tools/coverage.d.ts +2 -0
- package/dist/mcp/tools/coverage.js +53 -0
- package/dist/mcp/tools/coverage.js.map +1 -0
- package/dist/mcp/tools/dependencies.d.ts +2 -0
- package/dist/mcp/tools/dependencies.js +16 -0
- package/dist/mcp/tools/dependencies.js.map +1 -0
- package/dist/mcp/tools/doctor.d.ts +2 -0
- package/dist/mcp/tools/doctor.js +30 -0
- package/dist/mcp/tools/doctor.js.map +1 -0
- package/dist/mcp/tools/explain.d.ts +2 -0
- package/dist/mcp/tools/explain.js +30 -0
- package/dist/mcp/tools/explain.js.map +1 -0
- package/dist/mcp/tools/file.d.ts +2 -0
- package/dist/mcp/tools/file.js +22 -0
- package/dist/mcp/tools/file.js.map +1 -0
- package/dist/mcp/tools/graph.d.ts +2 -0
- package/dist/mcp/tools/graph.js +69 -0
- package/dist/mcp/tools/graph.js.map +1 -0
- package/dist/mcp/tools/hotspots.d.ts +2 -0
- package/dist/mcp/tools/hotspots.js +64 -0
- package/dist/mcp/tools/hotspots.js.map +1 -0
- package/dist/mcp/tools/outdated.d.ts +2 -0
- package/dist/mcp/tools/outdated.js +36 -0
- package/dist/mcp/tools/outdated.js.map +1 -0
- package/dist/mcp/tools/prDiff.d.ts +2 -0
- package/dist/mcp/tools/prDiff.js +38 -0
- package/dist/mcp/tools/prDiff.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +2 -0
- package/dist/mcp/tools/search.js +167 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/structure.d.ts +2 -0
- package/dist/mcp/tools/structure.js +34 -0
- package/dist/mcp/tools/structure.js.map +1 -0
- package/dist/mcp/tools/upgrade.d.ts +2 -0
- package/dist/mcp/tools/upgrade.js +38 -0
- package/dist/mcp/tools/upgrade.js.map +1 -0
- package/dist/mcp/tools/workspaces.d.ts +2 -0
- package/dist/mcp/tools/workspaces.js +13 -0
- package/dist/mcp/tools/workspaces.js.map +1 -0
- package/dist/mcp/tools.d.ts +12 -6
- package/dist/mcp/tools.js +40 -605
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reporters/consoleReporter.d.ts +4 -1
- package/dist/reporters/consoleReporter.js +113 -0
- package/dist/reporters/consoleReporter.js.map +1 -1
- package/dist/reporters/jsonReporter.d.ts +4 -1
- package/dist/reporters/jsonReporter.js +9 -0
- package/dist/reporters/jsonReporter.js.map +1 -1
- package/dist/reporters/markdownReporter.d.ts +4 -1
- package/dist/reporters/markdownReporter.js +103 -3
- package/dist/reporters/markdownReporter.js.map +1 -1
- package/dist/tool-manifest.json +358 -0
- package/dist/types.d.ts +115 -0
- package/dist/utils/cache.d.ts +3 -0
- package/dist/utils/cache.js +51 -0
- package/dist/utils/cache.js.map +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { scanRepository } from './repositoryScanner.js';
|
|
6
|
+
import { buildCodeGraph } from './codeGraph.js';
|
|
7
|
+
/**
|
|
8
|
+
* Compute a structural diff between two refs by building a CodeGraph for each
|
|
9
|
+
* side and comparing exports / imports / call sites / cyclomatic complexity /
|
|
10
|
+
* fan-in.
|
|
11
|
+
*
|
|
12
|
+
* Strategy: stand up a throwaway git worktree at the base ref, scan it like
|
|
13
|
+
* any other project, build its graph, then diff against the head graph
|
|
14
|
+
* (caller passes head graph in or the function builds one from `rootPath`).
|
|
15
|
+
*
|
|
16
|
+
* This is "structural diff", not text diff: agents reviewing PRs care about
|
|
17
|
+
* "what's the new export surface" and "which call sites disappeared", not
|
|
18
|
+
* "what whitespace changed on line 42."
|
|
19
|
+
*/
|
|
20
|
+
export async function computePrDiff(rootPath, options = {}) {
|
|
21
|
+
const isRepo = await isGitRepository(rootPath);
|
|
22
|
+
if (!isRepo) {
|
|
23
|
+
return {
|
|
24
|
+
available: false,
|
|
25
|
+
reason: 'Not a git repository - PR diff requires git history.',
|
|
26
|
+
base: { ref: '', resolvedSha: null },
|
|
27
|
+
head: { ref: '', resolvedSha: null },
|
|
28
|
+
filesAdded: [],
|
|
29
|
+
filesRemoved: [],
|
|
30
|
+
filesModified: [],
|
|
31
|
+
totalFilesChanged: 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const headRef = options.head ?? 'HEAD';
|
|
35
|
+
const baseRef = options.base ?? (await pickDefaultBase(rootPath));
|
|
36
|
+
const headSha = await resolveSha(rootPath, headRef);
|
|
37
|
+
const baseSha = await resolveSha(rootPath, baseRef);
|
|
38
|
+
if (!baseSha) {
|
|
39
|
+
return {
|
|
40
|
+
available: false,
|
|
41
|
+
reason: `Could not resolve base ref "${baseRef}".`,
|
|
42
|
+
base: { ref: baseRef, resolvedSha: null },
|
|
43
|
+
head: { ref: headRef, resolvedSha: headSha },
|
|
44
|
+
filesAdded: [],
|
|
45
|
+
filesRemoved: [],
|
|
46
|
+
filesModified: [],
|
|
47
|
+
totalFilesChanged: 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Build the head graph from rootPath as-is (the working tree).
|
|
51
|
+
const headScan = await scanRepository(rootPath);
|
|
52
|
+
const headGraph = await buildCodeGraph(rootPath, headScan.files);
|
|
53
|
+
// Stand up a worktree at the base ref and scan it.
|
|
54
|
+
const worktreeDir = await mkTempWorktreeDir();
|
|
55
|
+
let baseGraph;
|
|
56
|
+
try {
|
|
57
|
+
await runGit(rootPath, ['worktree', 'add', '--detach', worktreeDir, baseSha]);
|
|
58
|
+
const baseScan = await scanRepository(worktreeDir);
|
|
59
|
+
baseGraph = await buildCodeGraph(worktreeDir, baseScan.files);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
// Best-effort tear-down. `git worktree remove --force` handles the case
|
|
63
|
+
// where the worktree dir is somehow non-empty.
|
|
64
|
+
await runGit(rootPath, ['worktree', 'remove', '--force', worktreeDir]).catch(() => { });
|
|
65
|
+
await fs.rm(worktreeDir, { recursive: true, force: true }).catch(() => { });
|
|
66
|
+
}
|
|
67
|
+
return diffGraphs(baseRef, baseSha, headRef, headSha, baseGraph, headGraph);
|
|
68
|
+
}
|
|
69
|
+
/** Pure function - exported for unit testing without a real git repo. */
|
|
70
|
+
export function diffGraphs(baseRef, baseSha, headRef, headSha, baseGraph, headGraph) {
|
|
71
|
+
const filesAdded = [];
|
|
72
|
+
const filesRemoved = [];
|
|
73
|
+
const filesModified = [];
|
|
74
|
+
const allFiles = new Set([...baseGraph.files.keys(), ...headGraph.files.keys()]);
|
|
75
|
+
for (const file of allFiles) {
|
|
76
|
+
const baseEntry = baseGraph.files.get(file);
|
|
77
|
+
const headEntry = headGraph.files.get(file);
|
|
78
|
+
if (!baseEntry && headEntry) {
|
|
79
|
+
filesAdded.push(file);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (baseEntry && !headEntry) {
|
|
83
|
+
filesRemoved.push(file);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (!baseEntry || !headEntry)
|
|
87
|
+
continue; // unreachable, satisfies TS
|
|
88
|
+
// Modified files: structural compare.
|
|
89
|
+
const exportsBase = new Set(baseEntry.exports.map((e) => e.name));
|
|
90
|
+
const exportsHead = new Set(headEntry.exports.map((e) => e.name));
|
|
91
|
+
const importsBase = new Set(baseEntry.imports.map((i) => i.source));
|
|
92
|
+
const importsHead = new Set(headEntry.imports.map((i) => i.source));
|
|
93
|
+
const callsBase = new Set(baseEntry.callSites);
|
|
94
|
+
const callsHead = new Set(headEntry.callSites);
|
|
95
|
+
const rawExportsAdded = [...exportsHead].filter((x) => !exportsBase.has(x));
|
|
96
|
+
const rawExportsRemoved = [...exportsBase].filter((x) => !exportsHead.has(x));
|
|
97
|
+
// Pull out renames first; whatever's left stays as +/-.
|
|
98
|
+
const { renames, addedAfter, removedAfter } = detectRenames(rawExportsRemoved, rawExportsAdded);
|
|
99
|
+
const exportsAdded = addedAfter;
|
|
100
|
+
const exportsRemoved = removedAfter;
|
|
101
|
+
const exportsRenamed = renames;
|
|
102
|
+
const importsAdded = [...importsHead].filter((x) => !importsBase.has(x));
|
|
103
|
+
const importsRemoved = [...importsBase].filter((x) => !importsHead.has(x));
|
|
104
|
+
const callsAdded = [...callsHead].filter((x) => !callsBase.has(x));
|
|
105
|
+
const callsRemoved = [...callsBase].filter((x) => !callsHead.has(x));
|
|
106
|
+
const ccDelta = baseEntry.parseOk && headEntry.parseOk
|
|
107
|
+
? headEntry.cyclomaticComplexity - baseEntry.cyclomaticComplexity
|
|
108
|
+
: null;
|
|
109
|
+
const fanInBase = baseGraph.localImporters.get(file)?.size ?? 0;
|
|
110
|
+
const fanInHead = headGraph.localImporters.get(file)?.size ?? 0;
|
|
111
|
+
const fanInDelta = fanInHead - fanInBase;
|
|
112
|
+
const hasChange = exportsAdded.length +
|
|
113
|
+
exportsRemoved.length +
|
|
114
|
+
exportsRenamed.length +
|
|
115
|
+
importsAdded.length +
|
|
116
|
+
importsRemoved.length +
|
|
117
|
+
callsAdded.length +
|
|
118
|
+
callsRemoved.length >
|
|
119
|
+
0 ||
|
|
120
|
+
(ccDelta !== null && ccDelta !== 0) ||
|
|
121
|
+
fanInDelta !== 0;
|
|
122
|
+
if (!hasChange)
|
|
123
|
+
continue;
|
|
124
|
+
filesModified.push({
|
|
125
|
+
relativePath: file,
|
|
126
|
+
status: 'modified',
|
|
127
|
+
exportsAdded: exportsAdded.sort(),
|
|
128
|
+
exportsRemoved: exportsRemoved.sort(),
|
|
129
|
+
exportsRenamed,
|
|
130
|
+
importsAdded: importsAdded.sort(),
|
|
131
|
+
importsRemoved: importsRemoved.sort(),
|
|
132
|
+
callsAdded: callsAdded.sort(),
|
|
133
|
+
callsRemoved: callsRemoved.sort(),
|
|
134
|
+
cyclomaticDelta: ccDelta,
|
|
135
|
+
fanInDelta,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
filesAdded.sort();
|
|
139
|
+
filesRemoved.sort();
|
|
140
|
+
filesModified.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
141
|
+
return {
|
|
142
|
+
available: true,
|
|
143
|
+
base: { ref: baseRef, resolvedSha: baseSha },
|
|
144
|
+
head: { ref: headRef, resolvedSha: headSha },
|
|
145
|
+
filesAdded,
|
|
146
|
+
filesRemoved,
|
|
147
|
+
filesModified,
|
|
148
|
+
totalFilesChanged: filesAdded.length + filesRemoved.length + filesModified.length,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export function detectRenames(removed, added) {
|
|
152
|
+
if (removed.length === 0 || added.length === 0) {
|
|
153
|
+
return { renames: [], removedAfter: removed.sort(), addedAfter: added.sort() };
|
|
154
|
+
}
|
|
155
|
+
const candidates = [];
|
|
156
|
+
for (const from of removed) {
|
|
157
|
+
for (const to of added) {
|
|
158
|
+
const score = similarity(from, to);
|
|
159
|
+
if (score >= 0.5)
|
|
160
|
+
candidates.push({ from, to, score });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Greedy: pick highest scores first; each name can only be paired once.
|
|
164
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
165
|
+
const usedFrom = new Set();
|
|
166
|
+
const usedTo = new Set();
|
|
167
|
+
const renames = [];
|
|
168
|
+
for (const c of candidates) {
|
|
169
|
+
if (usedFrom.has(c.from) || usedTo.has(c.to))
|
|
170
|
+
continue;
|
|
171
|
+
usedFrom.add(c.from);
|
|
172
|
+
usedTo.add(c.to);
|
|
173
|
+
renames.push({ from: c.from, to: c.to });
|
|
174
|
+
}
|
|
175
|
+
renames.sort((a, b) => a.from.localeCompare(b.from));
|
|
176
|
+
return {
|
|
177
|
+
renames,
|
|
178
|
+
removedAfter: removed.filter((n) => !usedFrom.has(n)).sort(),
|
|
179
|
+
addedAfter: added.filter((n) => !usedTo.has(n)).sort(),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function similarity(a, b) {
|
|
183
|
+
if (a === b)
|
|
184
|
+
return 1;
|
|
185
|
+
const maxLen = Math.max(a.length, b.length);
|
|
186
|
+
if (maxLen === 0)
|
|
187
|
+
return 1;
|
|
188
|
+
const dist = levenshtein(a, b);
|
|
189
|
+
const editScore = 1 - dist / maxLen;
|
|
190
|
+
const prefix = sharedPrefix(a, b);
|
|
191
|
+
const suffix = sharedSuffix(a, b);
|
|
192
|
+
const affixScore = Math.max(prefix, suffix) / maxLen;
|
|
193
|
+
// Take the stronger signal. Either evidence alone is enough to suspect a
|
|
194
|
+
// rename: a name that's mostly the same characters (high edit score) OR a
|
|
195
|
+
// name that shares a long prefix/suffix (e.g. `fetch` -> `fetchUser`).
|
|
196
|
+
return Math.max(editScore, affixScore);
|
|
197
|
+
}
|
|
198
|
+
function levenshtein(a, b) {
|
|
199
|
+
if (a === b)
|
|
200
|
+
return 0;
|
|
201
|
+
const m = a.length;
|
|
202
|
+
const n = b.length;
|
|
203
|
+
if (m === 0)
|
|
204
|
+
return n;
|
|
205
|
+
if (n === 0)
|
|
206
|
+
return m;
|
|
207
|
+
// Two-row DP - small allocations keep this cheap on the realistic name sizes.
|
|
208
|
+
let prev = new Array(n + 1);
|
|
209
|
+
let curr = new Array(n + 1);
|
|
210
|
+
for (let j = 0; j <= n; j++)
|
|
211
|
+
prev[j] = j;
|
|
212
|
+
for (let i = 1; i <= m; i++) {
|
|
213
|
+
curr[0] = i;
|
|
214
|
+
for (let j = 1; j <= n; j++) {
|
|
215
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
216
|
+
curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
217
|
+
}
|
|
218
|
+
[prev, curr] = [curr, prev];
|
|
219
|
+
}
|
|
220
|
+
return prev[n];
|
|
221
|
+
}
|
|
222
|
+
function sharedPrefix(a, b) {
|
|
223
|
+
const cap = Math.min(a.length, b.length);
|
|
224
|
+
let i = 0;
|
|
225
|
+
while (i < cap && a.charCodeAt(i) === b.charCodeAt(i))
|
|
226
|
+
i++;
|
|
227
|
+
return i;
|
|
228
|
+
}
|
|
229
|
+
function sharedSuffix(a, b) {
|
|
230
|
+
const cap = Math.min(a.length, b.length);
|
|
231
|
+
let i = 0;
|
|
232
|
+
while (i < cap && a.charCodeAt(a.length - 1 - i) === b.charCodeAt(b.length - 1 - i))
|
|
233
|
+
i++;
|
|
234
|
+
return i;
|
|
235
|
+
}
|
|
236
|
+
// ── git helpers ───────────────────────────────────────────
|
|
237
|
+
async function isGitRepository(rootPath) {
|
|
238
|
+
const { code } = await runGit(rootPath, ['rev-parse', '--is-inside-work-tree']).catch(() => ({
|
|
239
|
+
code: 1,
|
|
240
|
+
stdout: '',
|
|
241
|
+
stderr: '',
|
|
242
|
+
}));
|
|
243
|
+
return code === 0;
|
|
244
|
+
}
|
|
245
|
+
async function resolveSha(rootPath, ref) {
|
|
246
|
+
const { code, stdout } = await runGit(rootPath, ['rev-parse', '--verify', `${ref}^{commit}`]).catch(() => ({ code: 1, stdout: '', stderr: '' }));
|
|
247
|
+
if (code !== 0)
|
|
248
|
+
return null;
|
|
249
|
+
const sha = stdout.trim();
|
|
250
|
+
return sha || null;
|
|
251
|
+
}
|
|
252
|
+
/** origin/main if it exists, else main, else master. Mirrors what most tooling does. */
|
|
253
|
+
async function pickDefaultBase(rootPath) {
|
|
254
|
+
for (const candidate of ['origin/main', 'main', 'origin/master', 'master']) {
|
|
255
|
+
if (await resolveSha(rootPath, candidate))
|
|
256
|
+
return candidate;
|
|
257
|
+
}
|
|
258
|
+
return 'HEAD~1';
|
|
259
|
+
}
|
|
260
|
+
async function mkTempWorktreeDir() {
|
|
261
|
+
return fs.mkdtemp(path.join(os.tmpdir(), 'projscan-pr-diff-'));
|
|
262
|
+
}
|
|
263
|
+
function runGit(cwd, args, opts = {}) {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
const child = spawn('git', args, { cwd, env: process.env });
|
|
266
|
+
let stdout = '';
|
|
267
|
+
let stderr = '';
|
|
268
|
+
let settled = false;
|
|
269
|
+
const timeout = opts.timeoutMs
|
|
270
|
+
? setTimeout(() => {
|
|
271
|
+
if (settled)
|
|
272
|
+
return;
|
|
273
|
+
settled = true;
|
|
274
|
+
child.kill('SIGKILL');
|
|
275
|
+
reject(new Error('git command timed out'));
|
|
276
|
+
}, opts.timeoutMs)
|
|
277
|
+
: null;
|
|
278
|
+
child.stdout.on('data', (d) => (stdout += d.toString()));
|
|
279
|
+
child.stderr.on('data', (d) => (stderr += d.toString()));
|
|
280
|
+
child.on('error', (err) => {
|
|
281
|
+
if (settled)
|
|
282
|
+
return;
|
|
283
|
+
settled = true;
|
|
284
|
+
if (timeout)
|
|
285
|
+
clearTimeout(timeout);
|
|
286
|
+
reject(err);
|
|
287
|
+
});
|
|
288
|
+
child.on('close', (code) => {
|
|
289
|
+
if (settled)
|
|
290
|
+
return;
|
|
291
|
+
settled = true;
|
|
292
|
+
if (timeout)
|
|
293
|
+
clearTimeout(timeout);
|
|
294
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=prDiff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prDiff.js","sourceRoot":"","sources":["../../src/core/prDiff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAkB,MAAM,gBAAgB,CAAC;AAUhE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,UAAyB,EAAE;IAE3B,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,sDAAsD;YAC9D,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;YACpC,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE;YACpC,UAAU,EAAE,EAAE;YACd,YAAY,EAAE,EAAE;YAChB,aAAa,EAAE,EAAE;YACjB,iBAAiB,EAAE,CAAC;SACrB,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAAC;IACvC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,+BAA+B,OAAO,IAAI;YAClD,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE;YACzC,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE;YAC5C,UAAU,EAAE,EAAE;YACd,YAAY,EAAE,EAAE;YAChB,aAAa,EAAE,EAAE;YACjB,iBAAiB,EAAE,CAAC;SACrB,CAAC;IACJ,CAAC;IAED,+DAA+D;IAC/D,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEjE,mDAAmD;IACnD,MAAM,WAAW,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAC9C,IAAI,SAAoB,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QAC9E,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;QACnD,SAAS,GAAG,MAAM,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC;YAAS,CAAC;QACT,wEAAwE;QACxE,+CAA+C;QAC/C,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACvF,MAAM,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AAC9E,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,OAAsB,EACtB,OAAe,EACf,OAAsB,EACtB,SAAoB,EACpB,SAAoB;IAEpB,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,aAAa,GAAkB,EAAE,CAAC;IAExC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzF,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAE5C,IAAI,CAAC,SAAS,IAAI,SAAS,EAAE,CAAC;YAC5B,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IAAI,SAAS,IAAI,CAAC,SAAS,EAAE,CAAC;YAC5B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxB,SAAS;QACX,CAAC;QACD,IAAI,CAAC,SAAS,IAAI,CAAC,SAAS;YAAE,SAAS,CAAC,4BAA4B;QAEpE,sCAAsC;QACtC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QAE/C,MAAM,eAAe,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,MAAM,iBAAiB,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9E,wDAAwD;QACxD,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,aAAa,CACzD,iBAAiB,EACjB,eAAe,CAChB,CAAC;QACF,MAAM,YAAY,GAAG,UAAU,CAAC;QAChC,MAAM,cAAc,GAAG,YAAY,CAAC;QACpC,MAAM,cAAc,GAAG,OAAO,CAAC;QAC/B,MAAM,YAAY,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACzE,MAAM,cAAc,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E,MAAM,UAAU,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,MAAM,YAAY,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAErE,MAAM,OAAO,GACX,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,OAAO;YACpC,CAAC,CAAC,SAAS,CAAC,oBAAoB,GAAG,SAAS,CAAC,oBAAoB;YACjE,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,SAAS,GAAG,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC;QAChE,MAAM,SAAS,GAAG,SAAS,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC;QAChE,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;QAEzC,MAAM,SAAS,GACb,YAAY,CAAC,MAAM;YACjB,cAAc,CAAC,MAAM;YACrB,cAAc,CAAC,MAAM;YACrB,YAAY,CAAC,MAAM;YACnB,cAAc,CAAC,MAAM;YACrB,UAAU,CAAC,MAAM;YACjB,YAAY,CAAC,MAAM;YACnB,CAAC;YACH,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,CAAC,CAAC;YACnC,UAAU,KAAK,CAAC,CAAC;QAEnB,IAAI,CAAC,SAAS;YAAE,SAAS;QAEzB,aAAa,CAAC,IAAI,CAAC;YACjB,YAAY,EAAE,IAAI;YAClB,MAAM,EAAE,UAAU;YAClB,YAAY,EAAE,YAAY,CAAC,IAAI,EAAE;YACjC,cAAc,EAAE,cAAc,CAAC,IAAI,EAAE;YACrC,cAAc;YACd,YAAY,EAAE,YAAY,CAAC,IAAI,EAAE;YACjC,cAAc,EAAE,cAAc,CAAC,IAAI,EAAE;YACrC,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE;YAC7B,YAAY,EAAE,YAAY,CAAC,IAAI,EAAE;YACjC,eAAe,EAAE,OAAO;YACxB,UAAU;SACX,CAAC,CAAC;IACL,CAAC;IAED,UAAU,CAAC,IAAI,EAAE,CAAC;IAClB,YAAY,CAAC,IAAI,EAAE,CAAC;IACpB,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;IAE3E,OAAO;QACL,SAAS,EAAE,IAAI;QACf,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE;QAC5C,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE;QAC5C,UAAU;QACV,YAAY;QACZ,aAAa;QACb,iBAAiB,EAAE,UAAU,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,GAAG,aAAa,CAAC,MAAM;KAClF,CAAC;AACJ,CAAC;AAuBD,MAAM,UAAU,aAAa,CAAC,OAAiB,EAAE,KAAe;IAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/C,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;IACjF,CAAC;IAGD,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;YACvB,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI,KAAK,IAAI,GAAG;gBAAE,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IACD,wEAAwE;IACxE,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,SAAS;QACvD,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAErD,OAAO;QACL,OAAO;QACP,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;QAC5D,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;KACvD,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,CAAS,EAAE,CAAS;IACtC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC;IACpC,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC;IACrD,yEAAyE;IACzE,0EAA0E;IAC1E,uEAAuE;IACvE,OAAO,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS;IACvC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnB,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,8EAA8E;IAC9E,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACpC,IAAI,IAAI,GAAG,IAAI,KAAK,CAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;QAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjE,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QACvE,CAAC;QACD,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,CAAC,EAAE,CAAC;IAC3D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,YAAY,CAAC,CAAS,EAAE,CAAS;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;QAAE,CAAC,EAAE,CAAC;IACzF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,6DAA6D;AAE7D,KAAK,UAAU,eAAe,CAAC,QAAgB;IAC7C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC3F,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,EAAE;KACX,CAAC,CAAC,CAAC;IACJ,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB,EAAE,GAAW;IACrD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,CACjG,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAC5C,CAAC;IACF,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC1B,OAAO,GAAG,IAAI,IAAI,CAAC;AACrB,CAAC;AAED,wFAAwF;AACxF,KAAK,UAAU,eAAe,CAAC,QAAgB;IAC7C,KAAK,MAAM,SAAS,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC3E,IAAI,MAAM,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9D,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,KAAK,UAAU,iBAAiB;IAC9B,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC;AACjE,CAAC;AAQD,SAAS,MAAM,CACb,GAAW,EACX,IAAc,EACd,OAA+B,EAAE;IAEjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5D,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS;YAC5B,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACtB,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;YAC7C,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;YACpB,CAAC,CAAC,IAAI,CAAC;QACT,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACzD,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACzD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,OAAO;gBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,OAAO;gBAAE,YAAY,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in, privacy-preserving telemetry (0.14).
|
|
3
|
+
*
|
|
4
|
+
* What it records: tool name, duration, success/failure, projscan version,
|
|
5
|
+
* iso timestamp. That's it. NO source content, NO file paths, NO arguments,
|
|
6
|
+
* NO repository identifiers, NO machine identifiers.
|
|
7
|
+
*
|
|
8
|
+
* What it does NOT do: send anything off the machine. The MVP sink is a
|
|
9
|
+
* local JSONL file the user controls. A remote sink may follow in a later
|
|
10
|
+
* release once we have a server worth pointing at.
|
|
11
|
+
*
|
|
12
|
+
* Discoverability: off by default. Enable via .projscanrc telemetry block,
|
|
13
|
+
* or PROJSCAN_TELEMETRY=1 env var (env wins). PROJSCAN_TELEMETRY=0 disables
|
|
14
|
+
* even when the config opts in (per-machine kill switch).
|
|
15
|
+
*/
|
|
16
|
+
export interface TelemetryConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Where to write events. Default: `~/.projscan/telemetry.jsonl`.
|
|
20
|
+
* Either a path or the literal string "stderr" (test/diagnostic use).
|
|
21
|
+
*/
|
|
22
|
+
sink: string;
|
|
23
|
+
}
|
|
24
|
+
export interface TelemetryEvent {
|
|
25
|
+
ts: string;
|
|
26
|
+
version: string;
|
|
27
|
+
tool: string;
|
|
28
|
+
durationMs: number;
|
|
29
|
+
ok: boolean;
|
|
30
|
+
errorCode?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve effective config. Env var takes precedence; .projscanrc supplies
|
|
34
|
+
* the sink and acts as the default. Non-throwing — bad config silently
|
|
35
|
+
* disables.
|
|
36
|
+
*/
|
|
37
|
+
export declare function resolveTelemetryConfig(rcConfig?: {
|
|
38
|
+
enabled?: boolean;
|
|
39
|
+
sink?: string;
|
|
40
|
+
}): TelemetryConfig;
|
|
41
|
+
/** Test-only: drop the cached config so tests can mutate env between cases. */
|
|
42
|
+
export declare function _resetTelemetryConfigForTests(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Fire-and-forget event recorder. Catches every error path so an unwritable
|
|
45
|
+
* sink can never affect the calling tool. Does nothing when disabled.
|
|
46
|
+
*/
|
|
47
|
+
export declare function recordToolCall(tool: string, durationMs: number, ok: boolean, errorCode?: string, rcConfig?: {
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
sink?: string;
|
|
50
|
+
}): Promise<void>;
|
|
51
|
+
/** Returns the JSON used by `projscan_telemetry` to surface state to the user. */
|
|
52
|
+
export declare function describeTelemetryConfig(rcConfig?: {
|
|
53
|
+
enabled?: boolean;
|
|
54
|
+
sink?: string;
|
|
55
|
+
}): TelemetryConfig & {
|
|
56
|
+
defaultSink: string;
|
|
57
|
+
envOverride: string | null;
|
|
58
|
+
};
|
|
59
|
+
export interface ToolHistogram {
|
|
60
|
+
tool: string;
|
|
61
|
+
count: number;
|
|
62
|
+
errorCount: number;
|
|
63
|
+
errorRate: number;
|
|
64
|
+
/** Latency percentiles (ms). null when count is 0 (shouldn't happen post-filter). */
|
|
65
|
+
p50Ms: number | null;
|
|
66
|
+
p95Ms: number | null;
|
|
67
|
+
p99Ms: number | null;
|
|
68
|
+
meanMs: number;
|
|
69
|
+
minMs: number;
|
|
70
|
+
maxMs: number;
|
|
71
|
+
}
|
|
72
|
+
export interface TelemetryAggregate {
|
|
73
|
+
available: boolean;
|
|
74
|
+
reason?: string;
|
|
75
|
+
sink: string;
|
|
76
|
+
totalEvents: number;
|
|
77
|
+
windowFrom: string | null;
|
|
78
|
+
windowTo: string | null;
|
|
79
|
+
byTool: ToolHistogram[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Read the JSONL sink and bucket events by tool, returning latency
|
|
83
|
+
* percentiles per tool. Returns available:false (with a reason) when the
|
|
84
|
+
* sink doesn't exist yet — typical case for users who haven't enabled
|
|
85
|
+
* telemetry or haven't run anything yet.
|
|
86
|
+
*/
|
|
87
|
+
export declare function aggregateTelemetry(rcConfig?: {
|
|
88
|
+
enabled?: boolean;
|
|
89
|
+
sink?: string;
|
|
90
|
+
}): Promise<TelemetryAggregate>;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
const DEFAULT_SINK_DIR = path.join(os.homedir(), '.projscan');
|
|
7
|
+
const DEFAULT_SINK_FILE = path.join(DEFAULT_SINK_DIR, 'telemetry.jsonl');
|
|
8
|
+
let cachedConfig;
|
|
9
|
+
let cachedVersion;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve effective config. Env var takes precedence; .projscanrc supplies
|
|
12
|
+
* the sink and acts as the default. Non-throwing — bad config silently
|
|
13
|
+
* disables.
|
|
14
|
+
*/
|
|
15
|
+
export function resolveTelemetryConfig(rcConfig) {
|
|
16
|
+
if (cachedConfig)
|
|
17
|
+
return cachedConfig;
|
|
18
|
+
const envFlag = process.env.PROJSCAN_TELEMETRY;
|
|
19
|
+
let enabled = !!rcConfig?.enabled;
|
|
20
|
+
if (envFlag === '1' || envFlag?.toLowerCase() === 'true')
|
|
21
|
+
enabled = true;
|
|
22
|
+
if (envFlag === '0' || envFlag?.toLowerCase() === 'false')
|
|
23
|
+
enabled = false;
|
|
24
|
+
const sink = rcConfig?.sink || DEFAULT_SINK_FILE;
|
|
25
|
+
cachedConfig = { enabled, sink };
|
|
26
|
+
return cachedConfig;
|
|
27
|
+
}
|
|
28
|
+
/** Test-only: drop the cached config so tests can mutate env between cases. */
|
|
29
|
+
export function _resetTelemetryConfigForTests() {
|
|
30
|
+
cachedConfig = undefined;
|
|
31
|
+
cachedVersion = undefined;
|
|
32
|
+
}
|
|
33
|
+
function readVersion() {
|
|
34
|
+
if (cachedVersion)
|
|
35
|
+
return cachedVersion;
|
|
36
|
+
try {
|
|
37
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
const pkg = JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));
|
|
39
|
+
cachedVersion = String(pkg.version ?? '0.0.0');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
cachedVersion = '0.0.0';
|
|
43
|
+
}
|
|
44
|
+
return cachedVersion;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fire-and-forget event recorder. Catches every error path so an unwritable
|
|
48
|
+
* sink can never affect the calling tool. Does nothing when disabled.
|
|
49
|
+
*/
|
|
50
|
+
export async function recordToolCall(tool, durationMs, ok, errorCode, rcConfig) {
|
|
51
|
+
const config = resolveTelemetryConfig(rcConfig);
|
|
52
|
+
if (!config.enabled)
|
|
53
|
+
return;
|
|
54
|
+
const event = {
|
|
55
|
+
ts: new Date().toISOString(),
|
|
56
|
+
version: readVersion(),
|
|
57
|
+
tool,
|
|
58
|
+
durationMs: Math.round(durationMs),
|
|
59
|
+
ok,
|
|
60
|
+
};
|
|
61
|
+
if (errorCode)
|
|
62
|
+
event.errorCode = errorCode;
|
|
63
|
+
try {
|
|
64
|
+
if (config.sink === 'stderr') {
|
|
65
|
+
process.stderr.write(`[telemetry] ${JSON.stringify(event)}\n`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
await fs.mkdir(path.dirname(config.sink), { recursive: true });
|
|
69
|
+
await fs.appendFile(config.sink, JSON.stringify(event) + '\n', 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Sink failures are silent by design. Telemetry must never break a tool.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Returns the JSON used by `projscan_telemetry` to surface state to the user. */
|
|
76
|
+
export function describeTelemetryConfig(rcConfig) {
|
|
77
|
+
const config = resolveTelemetryConfig(rcConfig);
|
|
78
|
+
const env = process.env.PROJSCAN_TELEMETRY ?? null;
|
|
79
|
+
return {
|
|
80
|
+
...config,
|
|
81
|
+
defaultSink: DEFAULT_SINK_FILE,
|
|
82
|
+
envOverride: env,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Read the JSONL sink and bucket events by tool, returning latency
|
|
87
|
+
* percentiles per tool. Returns available:false (with a reason) when the
|
|
88
|
+
* sink doesn't exist yet — typical case for users who haven't enabled
|
|
89
|
+
* telemetry or haven't run anything yet.
|
|
90
|
+
*/
|
|
91
|
+
export async function aggregateTelemetry(rcConfig) {
|
|
92
|
+
const config = resolveTelemetryConfig(rcConfig);
|
|
93
|
+
const sinkPath = config.sink === 'stderr' ? '' : config.sink;
|
|
94
|
+
if (!sinkPath) {
|
|
95
|
+
return {
|
|
96
|
+
available: false,
|
|
97
|
+
reason: 'Sink is "stderr"; nothing to aggregate from disk.',
|
|
98
|
+
sink: config.sink,
|
|
99
|
+
totalEvents: 0,
|
|
100
|
+
windowFrom: null,
|
|
101
|
+
windowTo: null,
|
|
102
|
+
byTool: [],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
let raw;
|
|
106
|
+
try {
|
|
107
|
+
raw = await fs.readFile(sinkPath, 'utf-8');
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return {
|
|
111
|
+
available: false,
|
|
112
|
+
reason: `No telemetry sink found at ${sinkPath}. Enable telemetry and run a few tools first.`,
|
|
113
|
+
sink: sinkPath,
|
|
114
|
+
totalEvents: 0,
|
|
115
|
+
windowFrom: null,
|
|
116
|
+
windowTo: null,
|
|
117
|
+
byTool: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const events = [];
|
|
121
|
+
for (const line of raw.split('\n')) {
|
|
122
|
+
const trimmed = line.trim();
|
|
123
|
+
if (!trimmed)
|
|
124
|
+
continue;
|
|
125
|
+
try {
|
|
126
|
+
const obj = JSON.parse(trimmed);
|
|
127
|
+
if (typeof obj?.tool === 'string' && typeof obj?.durationMs === 'number') {
|
|
128
|
+
events.push(obj);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Skip malformed lines silently — JSONL files can be partially written
|
|
133
|
+
// when a process is killed mid-append.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (events.length === 0) {
|
|
137
|
+
return {
|
|
138
|
+
available: true,
|
|
139
|
+
sink: sinkPath,
|
|
140
|
+
totalEvents: 0,
|
|
141
|
+
windowFrom: null,
|
|
142
|
+
windowTo: null,
|
|
143
|
+
byTool: [],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const byToolMap = new Map();
|
|
147
|
+
let windowFrom = events[0].ts;
|
|
148
|
+
let windowTo = events[0].ts;
|
|
149
|
+
for (const e of events) {
|
|
150
|
+
if (e.ts < windowFrom)
|
|
151
|
+
windowFrom = e.ts;
|
|
152
|
+
if (e.ts > windowTo)
|
|
153
|
+
windowTo = e.ts;
|
|
154
|
+
if (!byToolMap.has(e.tool))
|
|
155
|
+
byToolMap.set(e.tool, []);
|
|
156
|
+
byToolMap.get(e.tool).push(e);
|
|
157
|
+
}
|
|
158
|
+
const byTool = [];
|
|
159
|
+
for (const [tool, toolEvents] of byToolMap) {
|
|
160
|
+
const durations = toolEvents.map((e) => e.durationMs).sort((a, b) => a - b);
|
|
161
|
+
const errorCount = toolEvents.filter((e) => e.ok === false).length;
|
|
162
|
+
byTool.push({
|
|
163
|
+
tool,
|
|
164
|
+
count: toolEvents.length,
|
|
165
|
+
errorCount,
|
|
166
|
+
errorRate: Math.round((errorCount / toolEvents.length) * 1000) / 1000,
|
|
167
|
+
p50Ms: percentile(durations, 50),
|
|
168
|
+
p95Ms: percentile(durations, 95),
|
|
169
|
+
p99Ms: percentile(durations, 99),
|
|
170
|
+
meanMs: Math.round(durations.reduce((s, n) => s + n, 0) / durations.length),
|
|
171
|
+
minMs: durations[0],
|
|
172
|
+
maxMs: durations[durations.length - 1],
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
byTool.sort((a, b) => b.count - a.count);
|
|
176
|
+
return {
|
|
177
|
+
available: true,
|
|
178
|
+
sink: sinkPath,
|
|
179
|
+
totalEvents: events.length,
|
|
180
|
+
windowFrom,
|
|
181
|
+
windowTo,
|
|
182
|
+
byTool,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** Linear-interpolation percentile over a pre-sorted ascending array. */
|
|
186
|
+
function percentile(sorted, p) {
|
|
187
|
+
if (sorted.length === 0)
|
|
188
|
+
return null;
|
|
189
|
+
if (sorted.length === 1)
|
|
190
|
+
return sorted[0];
|
|
191
|
+
const rank = (p / 100) * (sorted.length - 1);
|
|
192
|
+
const lo = Math.floor(rank);
|
|
193
|
+
const hi = Math.ceil(rank);
|
|
194
|
+
if (lo === hi)
|
|
195
|
+
return sorted[lo];
|
|
196
|
+
const frac = rank - lo;
|
|
197
|
+
return Math.round(sorted[lo] * (1 - frac) + sorted[hi] * frac);
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/core/telemetry.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FileEntry, FileExplanation, McpToolDefinition, DirectoryNode } from '../../types.js';
|
|
2
|
+
export interface McpToolHandler {
|
|
3
|
+
(args: Record<string, unknown>, rootPath: string): Promise<unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface McpTool extends McpToolDefinition {
|
|
6
|
+
handler: McpToolHandler;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A repo is "Python-dominated" if it has a pyproject.toml OR setup.py AND
|
|
10
|
+
* either no node_modules directory or no package.json.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isPythonDominated(rootPath: string, files: FileEntry[]): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the `package` arg to a (file -> boolean) filter, or null when
|
|
15
|
+
* scoping wasn't requested.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolvePackageFilter(rootPath: string, args: Record<string, unknown>): Promise<((file: string) => boolean) | null>;
|
|
18
|
+
export declare const PACKAGE_ARG_SCHEMA: {
|
|
19
|
+
readonly type: "string";
|
|
20
|
+
readonly description: "Optional. Workspace package name (from projscan_workspaces) to scope results to one package only.";
|
|
21
|
+
};
|
|
22
|
+
/** Walk a DirectoryNode tree to find the node whose `path` equals targetPath. */
|
|
23
|
+
export declare function sliceTree(node: DirectoryNode, targetPath: string): DirectoryNode | null;
|
|
24
|
+
export declare function explainFile(absolutePath: string, content: string, rootPath: string): FileExplanation;
|