projscan 0.10.0 → 0.11.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 +25 -7
- package/dist/cli/index.js +294 -10
- 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 +136 -0
- package/dist/core/languages/goAdapter.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/pythonAdapter.js +5 -0
- package/dist/core/languages/pythonAdapter.js.map +1 -1
- 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 +2 -1
- package/dist/core/languages/registry.js.map +1 -1
- package/dist/core/languages/treeSitterLoader.js +2 -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/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/mcp/server.js +22 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools.js +269 -20
- 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/types.d.ts +113 -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/dist/utils/config.js +10 -0
- package/dist/utils/config.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fg from 'fast-glob';
|
|
4
|
+
/**
|
|
5
|
+
* Detect monorepo workspaces. Covers the three setups that account for the
|
|
6
|
+
* vast majority of JS/TS monorepos: npm/yarn workspaces (package.json
|
|
7
|
+
* `workspaces`), pnpm (pnpm-workspace.yaml), and the Nx/Turbo/Lerna fallback
|
|
8
|
+
* (their files exist but they typically piggy-back on package.json
|
|
9
|
+
* workspaces or rely on a `packages/` convention).
|
|
10
|
+
*
|
|
11
|
+
* Returns kind 'none' for non-monorepos. Always includes the workspace root
|
|
12
|
+
* package itself if it has its own package.json with a name.
|
|
13
|
+
*/
|
|
14
|
+
export async function detectWorkspaces(rootPath) {
|
|
15
|
+
// Read root package.json (may not exist for Python-only repos).
|
|
16
|
+
const rootPkg = await readPackageJson(path.join(rootPath, 'package.json'));
|
|
17
|
+
// 1) pnpm has its own manifest. Prefer it when present.
|
|
18
|
+
const pnpmManifest = path.join(rootPath, 'pnpm-workspace.yaml');
|
|
19
|
+
if (await fileExists(pnpmManifest)) {
|
|
20
|
+
const patterns = await readPnpmPackages(pnpmManifest);
|
|
21
|
+
const packages = await collectPackages(rootPath, patterns, rootPkg);
|
|
22
|
+
return { kind: 'pnpm', packages, source: 'pnpm-workspace.yaml' };
|
|
23
|
+
}
|
|
24
|
+
// 2) npm / yarn workspaces in package.json.
|
|
25
|
+
if (rootPkg) {
|
|
26
|
+
const patterns = readNpmYarnPatterns(rootPkg);
|
|
27
|
+
if (patterns && patterns.length > 0) {
|
|
28
|
+
const packages = await collectPackages(rootPath, patterns, rootPkg);
|
|
29
|
+
// Distinguish yarn from npm if a yarn.lock is present — affects nothing
|
|
30
|
+
// about discovery, just labelling.
|
|
31
|
+
const kind = (await fileExists(path.join(rootPath, 'yarn.lock')))
|
|
32
|
+
? 'yarn'
|
|
33
|
+
: 'npm';
|
|
34
|
+
return { kind, packages, source: 'package.json#workspaces' };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// 3) Lerna — its config has an explicit `packages` field with globs.
|
|
38
|
+
const lernaPath = path.join(rootPath, 'lerna.json');
|
|
39
|
+
if (await fileExists(lernaPath)) {
|
|
40
|
+
const patterns = (await readJsonField(lernaPath, 'packages')) ?? ['packages/*'];
|
|
41
|
+
const packages = await collectPackages(rootPath, patterns, rootPkg);
|
|
42
|
+
if (packages.length > 0) {
|
|
43
|
+
return { kind: 'lerna', packages, source: 'lerna.json#packages' };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// 4) Nx — workspaceLayout supplies appsDir/libsDir; project.json files
|
|
47
|
+
// anywhere identify packages. Older Nx variants used workspace.json (a
|
|
48
|
+
// single file enumerating projects); we read its `projects` map too.
|
|
49
|
+
const nxJsonPath = path.join(rootPath, 'nx.json');
|
|
50
|
+
if (await fileExists(nxJsonPath)) {
|
|
51
|
+
const nxPackages = await collectNxPackages(rootPath, nxJsonPath);
|
|
52
|
+
if (nxPackages.length > 0 || rootPkg) {
|
|
53
|
+
const merged = rootPkg ? [rootPackageOnly(rootPkg), ...nxPackages] : nxPackages;
|
|
54
|
+
merged.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
55
|
+
return { kind: 'nx', packages: merged, source: 'nx.json + project.json scan' };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 5) Turbo — turbo.json defines task pipelines, NOT workspace layout.
|
|
59
|
+
// Turbo always rides on top of npm/yarn/pnpm workspaces (handled above).
|
|
60
|
+
// If we get here with a turbo.json but no workspace declaration, treat it
|
|
61
|
+
// as a marker and fall back to the packages/* convention so we still
|
|
62
|
+
// surface something useful.
|
|
63
|
+
if (await fileExists(path.join(rootPath, 'turbo.json'))) {
|
|
64
|
+
const packages = await collectPackages(rootPath, ['packages/*', 'apps/*'], rootPkg);
|
|
65
|
+
if (packages.length > 0) {
|
|
66
|
+
return { kind: 'turbo', packages, source: 'turbo.json (fallback glob)' };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Single-package repo (or non-JS).
|
|
70
|
+
return { kind: 'none', packages: rootPkg ? [rootPackageOnly(rootPkg)] : [] };
|
|
71
|
+
}
|
|
72
|
+
async function readPackageJson(absPath) {
|
|
73
|
+
try {
|
|
74
|
+
const raw = await fs.readFile(absPath, 'utf-8');
|
|
75
|
+
return JSON.parse(raw);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function readNpmYarnPatterns(pkg) {
|
|
82
|
+
if (!pkg.workspaces)
|
|
83
|
+
return null;
|
|
84
|
+
if (Array.isArray(pkg.workspaces))
|
|
85
|
+
return pkg.workspaces;
|
|
86
|
+
if (Array.isArray(pkg.workspaces.packages))
|
|
87
|
+
return pkg.workspaces.packages;
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
async function readPnpmPackages(manifestPath) {
|
|
91
|
+
// Tiny YAML subset reader: pnpm-workspace.yaml is just `packages:` followed
|
|
92
|
+
// by a YAML list. Avoiding a full YAML dep keeps the install size small.
|
|
93
|
+
let content;
|
|
94
|
+
try {
|
|
95
|
+
content = await fs.readFile(manifestPath, 'utf-8');
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const patterns = [];
|
|
101
|
+
const lines = content.split('\n');
|
|
102
|
+
let inPackages = false;
|
|
103
|
+
for (const raw of lines) {
|
|
104
|
+
const line = raw.trimEnd();
|
|
105
|
+
if (line.startsWith('packages:')) {
|
|
106
|
+
inPackages = true;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (inPackages) {
|
|
110
|
+
// List item: ` - 'pattern'` or ` - "pattern"` or ` - pattern`.
|
|
111
|
+
const m = /^\s*-\s*['"]?([^'"\n]+?)['"]?\s*$/.exec(line);
|
|
112
|
+
if (m) {
|
|
113
|
+
patterns.push(m[1]);
|
|
114
|
+
}
|
|
115
|
+
else if (line.length > 0 && !line.startsWith(' ') && !line.startsWith('\t')) {
|
|
116
|
+
// New top-level key — packages section ended.
|
|
117
|
+
inPackages = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return patterns;
|
|
122
|
+
}
|
|
123
|
+
async function collectPackages(rootPath, patterns, rootPkg) {
|
|
124
|
+
// Each pattern is typically `packages/*`. Resolve to package.json files.
|
|
125
|
+
const pkgManifests = patterns.map((p) => `${trimTrailingSlash(p)}/package.json`);
|
|
126
|
+
const matches = await fg(pkgManifests, {
|
|
127
|
+
cwd: rootPath,
|
|
128
|
+
onlyFiles: true,
|
|
129
|
+
ignore: ['**/node_modules/**'],
|
|
130
|
+
});
|
|
131
|
+
const packages = [];
|
|
132
|
+
for (const rel of matches) {
|
|
133
|
+
const abs = path.join(rootPath, rel);
|
|
134
|
+
const pkg = await readPackageJson(abs);
|
|
135
|
+
if (!pkg)
|
|
136
|
+
continue;
|
|
137
|
+
const dir = path.posix.dirname(rel.split(path.sep).join('/'));
|
|
138
|
+
packages.push({
|
|
139
|
+
name: pkg.name ?? path.posix.basename(dir),
|
|
140
|
+
relativePath: dir,
|
|
141
|
+
version: pkg.version,
|
|
142
|
+
isRoot: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (rootPkg && rootPkg.name) {
|
|
146
|
+
packages.unshift(rootPackageOnly(rootPkg));
|
|
147
|
+
}
|
|
148
|
+
packages.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
149
|
+
return packages;
|
|
150
|
+
}
|
|
151
|
+
function rootPackageOnly(rootPkg) {
|
|
152
|
+
return {
|
|
153
|
+
name: rootPkg.name ?? '<root>',
|
|
154
|
+
relativePath: '',
|
|
155
|
+
version: rootPkg.version,
|
|
156
|
+
isRoot: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function trimTrailingSlash(p) {
|
|
160
|
+
return p.endsWith('/') ? p.slice(0, -1) : p;
|
|
161
|
+
}
|
|
162
|
+
async function fileExists(absPath) {
|
|
163
|
+
try {
|
|
164
|
+
await fs.access(absPath);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Read a JSON file and return one named field (or undefined). Best-effort. */
|
|
172
|
+
async function readJsonField(absPath, field) {
|
|
173
|
+
try {
|
|
174
|
+
const raw = await fs.readFile(absPath, 'utf-8');
|
|
175
|
+
const parsed = JSON.parse(raw);
|
|
176
|
+
const v = parsed[field];
|
|
177
|
+
return v;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Discover Nx workspace packages.
|
|
185
|
+
*
|
|
186
|
+
* Modern Nx (16+) puts a `project.json` next to every project. Older Nx
|
|
187
|
+
* (<= 15) maintains a single `workspace.json` listing them. We try both:
|
|
188
|
+
* scan for project.json files first (covers anything modern), then read
|
|
189
|
+
* workspace.json's `projects` map and treat its directories as packages.
|
|
190
|
+
*
|
|
191
|
+
* `nx.json#workspaceLayout` provides default `appsDir` / `libsDir` hints
|
|
192
|
+
* that we use to scope the project.json scan. When unspecified Nx defaults
|
|
193
|
+
* to "apps" and "libs"; we honor that.
|
|
194
|
+
*/
|
|
195
|
+
async function collectNxPackages(rootPath, nxJsonPath) {
|
|
196
|
+
const layout = (await readJsonField(nxJsonPath, 'workspaceLayout')) ?? {};
|
|
197
|
+
const appsDir = (layout.appsDir ?? 'apps').replace(/\/+$/, '');
|
|
198
|
+
const libsDir = (layout.libsDir ?? 'libs').replace(/\/+$/, '');
|
|
199
|
+
const found = new Map();
|
|
200
|
+
// 1) Scan for project.json files. Look in the layout dirs first, then in
|
|
201
|
+
// a wider `**/project.json` search bounded by reasonable globs.
|
|
202
|
+
const patterns = Array.from(new Set([
|
|
203
|
+
`${appsDir}/**/project.json`,
|
|
204
|
+
`${libsDir}/**/project.json`,
|
|
205
|
+
'packages/**/project.json',
|
|
206
|
+
]));
|
|
207
|
+
const matches = await fg(patterns, {
|
|
208
|
+
cwd: rootPath,
|
|
209
|
+
onlyFiles: true,
|
|
210
|
+
ignore: ['**/node_modules/**'],
|
|
211
|
+
deep: 4,
|
|
212
|
+
});
|
|
213
|
+
for (const rel of matches) {
|
|
214
|
+
const projectJsonPath = path.join(rootPath, rel);
|
|
215
|
+
const name = (await readJsonField(projectJsonPath, 'name')) ?? path.posix.basename(path.posix.dirname(rel.split(path.sep).join('/')));
|
|
216
|
+
const dir = path.posix.dirname(rel.split(path.sep).join('/'));
|
|
217
|
+
if (!found.has(dir)) {
|
|
218
|
+
found.set(dir, { name, relativePath: dir, isRoot: false });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// 2) Older Nx: read workspace.json projects map.
|
|
222
|
+
const workspaceJsonPath = path.join(rootPath, 'workspace.json');
|
|
223
|
+
if (await fileExists(workspaceJsonPath)) {
|
|
224
|
+
const projects = (await readJsonField(workspaceJsonPath, 'projects')) ?? {};
|
|
225
|
+
for (const [name, val] of Object.entries(projects)) {
|
|
226
|
+
const root = typeof val === 'string' ? val : val.root;
|
|
227
|
+
if (!root)
|
|
228
|
+
continue;
|
|
229
|
+
const dir = root.replace(/\/+$/, '');
|
|
230
|
+
if (!found.has(dir)) {
|
|
231
|
+
found.set(dir, { name, relativePath: dir, isRoot: false });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return [...found.values()];
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Given a file's project-relative path, return the workspace package whose
|
|
239
|
+
* `relativePath` is its longest matching prefix. Used to filter hotspots /
|
|
240
|
+
* coupling rows by --package.
|
|
241
|
+
*/
|
|
242
|
+
export function findPackageForFile(workspaces, filePath) {
|
|
243
|
+
let best = null;
|
|
244
|
+
for (const pkg of workspaces.packages) {
|
|
245
|
+
if (pkg.isRoot)
|
|
246
|
+
continue;
|
|
247
|
+
const prefix = pkg.relativePath ? pkg.relativePath + '/' : '';
|
|
248
|
+
if (filePath === pkg.relativePath || filePath.startsWith(prefix)) {
|
|
249
|
+
if (!best || pkg.relativePath.length > best.relativePath.length)
|
|
250
|
+
best = pkg;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Fall back to root package if nothing more specific matched.
|
|
254
|
+
if (!best) {
|
|
255
|
+
const root = workspaces.packages.find((p) => p.isRoot);
|
|
256
|
+
return root ?? null;
|
|
257
|
+
}
|
|
258
|
+
return best;
|
|
259
|
+
}
|
|
260
|
+
/** Filter helper for callers passing --package <name>. */
|
|
261
|
+
export function filterFilesByPackage(workspaces, packageName, files) {
|
|
262
|
+
const pkg = workspaces.packages.find((p) => p.name === packageName);
|
|
263
|
+
if (!pkg)
|
|
264
|
+
return [];
|
|
265
|
+
if (pkg.isRoot)
|
|
266
|
+
return files;
|
|
267
|
+
const prefix = pkg.relativePath + '/';
|
|
268
|
+
return files.filter((f) => f === pkg.relativePath || f.startsWith(prefix));
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=monorepo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monorepo.js","sourceRoot":"","sources":["../../src/core/monorepo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type CodeGraph } from './codeGraph.js';
|
|
2
|
+
import type { PrDiffReport } from '../types.js';
|
|
3
|
+
export interface PrDiffOptions {
|
|
4
|
+
/** Base ref (branch, tag, sha). Default: origin/main, falling back to main. */
|
|
5
|
+
base?: string;
|
|
6
|
+
/** Head ref. Default: HEAD. */
|
|
7
|
+
head?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compute a structural diff between two refs by building a CodeGraph for each
|
|
11
|
+
* side and comparing exports / imports / call sites / cyclomatic complexity /
|
|
12
|
+
* fan-in.
|
|
13
|
+
*
|
|
14
|
+
* Strategy: stand up a throwaway git worktree at the base ref, scan it like
|
|
15
|
+
* any other project, build its graph, then diff against the head graph
|
|
16
|
+
* (caller passes head graph in or the function builds one from `rootPath`).
|
|
17
|
+
*
|
|
18
|
+
* This is "structural diff", not text diff: agents reviewing PRs care about
|
|
19
|
+
* "what's the new export surface" and "which call sites disappeared", not
|
|
20
|
+
* "what whitespace changed on line 42."
|
|
21
|
+
*/
|
|
22
|
+
export declare function computePrDiff(rootPath: string, options?: PrDiffOptions): Promise<PrDiffReport>;
|
|
23
|
+
/** Pure function — exported for unit testing without a real git repo. */
|
|
24
|
+
export declare function diffGraphs(baseRef: string, baseSha: string | null, headRef: string, headSha: string | null, baseGraph: CodeGraph, headGraph: CodeGraph): PrDiffReport;
|
|
25
|
+
/**
|
|
26
|
+
* Pair removed/added export names that look like renames. A pair counts as a
|
|
27
|
+
* rename when it's the best-scoring match on both sides AND the similarity
|
|
28
|
+
* exceeds a threshold. Each name participates in at most one pair (greedy by
|
|
29
|
+
* descending score). Whatever isn't paired stays in the +/- lists.
|
|
30
|
+
*
|
|
31
|
+
* Similarity blends two signals (each in [0,1]): normalized Levenshtein
|
|
32
|
+
* distance and longest-common-affix fraction. Threshold of 0.6 chosen to
|
|
33
|
+
* pair "fooBar" → "fooBaz" and "Widget" → "WidgetThing" without pairing
|
|
34
|
+
* unrelated names like "save" → "load".
|
|
35
|
+
*/
|
|
36
|
+
import type { ExportRename } from '../types.js';
|
|
37
|
+
interface RenameSplit {
|
|
38
|
+
renames: ExportRename[];
|
|
39
|
+
removedAfter: string[];
|
|
40
|
+
addedAfter: string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function detectRenames(removed: string[], added: string[]): RenameSplit;
|
|
43
|
+
export {};
|
|
@@ -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"}
|