openlore 2.0.7 → 2.0.8
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 +122 -20
- package/dist/cli/commands/mcp.d.ts +681 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +384 -200
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -1
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
- package/dist/core/analyzer/artifact-generator.js +59 -4
- package/dist/core/analyzer/artifact-generator.js.map +1 -1
- package/dist/core/analyzer/call-graph.d.ts +19 -1
- package/dist/core/analyzer/call-graph.d.ts.map +1 -1
- package/dist/core/analyzer/call-graph.js +128 -28
- package/dist/core/analyzer/call-graph.js.map +1 -1
- package/dist/core/architecture/check.d.ts +63 -0
- package/dist/core/architecture/check.d.ts.map +1 -0
- package/dist/core/architecture/check.js +192 -0
- package/dist/core/architecture/check.js.map +1 -0
- package/dist/core/architecture/rules.d.ts +73 -0
- package/dist/core/architecture/rules.d.ts.map +1 -0
- package/dist/core/architecture/rules.js +201 -0
- package/dist/core/architecture/rules.js.map +1 -0
- package/dist/core/decisions/project.d.ts +59 -0
- package/dist/core/decisions/project.d.ts.map +1 -0
- package/dist/core/decisions/project.js +68 -0
- package/dist/core/decisions/project.js.map +1 -0
- package/dist/core/decisions/verifier.d.ts +10 -0
- package/dist/core/decisions/verifier.d.ts.map +1 -1
- package/dist/core/decisions/verifier.js +48 -5
- package/dist/core/decisions/verifier.js.map +1 -1
- package/dist/core/provenance/change-coupling.d.ts +68 -0
- package/dist/core/provenance/change-coupling.d.ts.map +1 -0
- package/dist/core/provenance/change-coupling.js +134 -0
- package/dist/core/provenance/change-coupling.js.map +1 -0
- package/dist/core/provenance/git-provenance.d.ts +67 -0
- package/dist/core/provenance/git-provenance.d.ts.map +1 -0
- package/dist/core/provenance/git-provenance.js +177 -0
- package/dist/core/provenance/git-provenance.js.map +1 -0
- package/dist/core/provenance/project.d.ts +37 -0
- package/dist/core/provenance/project.d.ts.map +1 -0
- package/dist/core/provenance/project.js +46 -0
- package/dist/core/provenance/project.js.map +1 -0
- package/dist/core/services/edge-store.d.ts +41 -0
- package/dist/core/services/edge-store.d.ts.map +1 -1
- package/dist/core/services/edge-store.js +251 -3
- package/dist/core/services/edge-store.js.map +1 -1
- package/dist/core/services/llm-service.d.ts +9 -0
- package/dist/core/services/llm-service.d.ts.map +1 -1
- package/dist/core/services/llm-service.js +15 -4
- package/dist/core/services/llm-service.js.map +1 -1
- package/dist/core/services/mcp-handlers/architecture.d.ts +19 -0
- package/dist/core/services/mcp-handlers/architecture.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/architecture.js +104 -0
- package/dist/core/services/mcp-handlers/architecture.js.map +1 -0
- package/dist/core/services/mcp-handlers/change-coupling.d.ts +16 -0
- package/dist/core/services/mcp-handlers/change-coupling.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/change-coupling.js +57 -0
- package/dist/core/services/mcp-handlers/change-coupling.js.map +1 -0
- package/dist/core/services/mcp-handlers/graph.d.ts +27 -0
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/graph.js +98 -16
- package/dist/core/services/mcp-handlers/graph.js.map +1 -1
- package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/orient.js +122 -2
- package/dist/core/services/mcp-handlers/orient.js.map +1 -1
- package/dist/core/services/mcp-handlers/reachability.d.ts +30 -0
- package/dist/core/services/mcp-handlers/reachability.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/reachability.js +222 -0
- package/dist/core/services/mcp-handlers/reachability.js.map +1 -0
- package/dist/core/services/mcp-handlers/structural-diff.d.ts +31 -0
- package/dist/core/services/mcp-handlers/structural-diff.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/structural-diff.js +268 -0
- package/dist/core/services/mcp-handlers/structural-diff.js.map +1 -0
- package/dist/core/services/mcp-handlers/test-impact.d.ts +34 -0
- package/dist/core/services/mcp-handlers/test-impact.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/test-impact.js +221 -0
- package/dist/core/services/mcp-handlers/test-impact.js.map +1 -0
- package/dist/core/services/mcp-handlers/tool-guard.d.ts +45 -0
- package/dist/core/services/mcp-handlers/tool-guard.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/tool-guard.js +81 -0
- package/dist/core/services/mcp-handlers/tool-guard.js.map +1 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/utils.js +15 -1
- package/dist/core/services/mcp-handlers/utils.js.map +1 -1
- package/dist/core/services/mcp-watcher.d.ts.map +1 -1
- package/dist/core/services/mcp-watcher.js +9 -0
- package/dist/core/services/mcp-watcher.js.map +1 -1
- package/package.json +8 -8
|
@@ -72,6 +72,41 @@ function buildTargetedDiff(decision, diffByFile, fallbackDiff) {
|
|
|
72
72
|
// No matching files — pass a slice of the global diff so the LLM can still check
|
|
73
73
|
return fallbackDiff.slice(0, 4_000);
|
|
74
74
|
}
|
|
75
|
+
/** A changed hunk is "substantive" once it carries at least this many +/- lines. */
|
|
76
|
+
const SUBSTANTIVE_MIN_CHANGED_LINES = 2;
|
|
77
|
+
/** Count real added/removed lines in a diff hunk (excluding the +++/--- file markers). */
|
|
78
|
+
function countChangedLines(hunk) {
|
|
79
|
+
let n = 0;
|
|
80
|
+
for (const line of hunk.split('\n')) {
|
|
81
|
+
if ((line.startsWith('+') && !line.startsWith('+++')) ||
|
|
82
|
+
(line.startsWith('-') && !line.startsWith('---'))) {
|
|
83
|
+
n++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return n;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Deterministic verification evidence: a decision is grounded when EVERY one of its
|
|
90
|
+
* affectedFiles appears in the diff with a substantive hunk. Returns the evidence
|
|
91
|
+
* file (the first affected file) when grounded, else null.
|
|
92
|
+
*
|
|
93
|
+
* This is the HF-1 fallback: the LLM `verify` step over-marks legitimate
|
|
94
|
+
* tool-addition decisions as `phantom`, stalling the dogfood gate. When the code
|
|
95
|
+
* is demonstrably in the diff, trust the diff over the LLM's "no evidence" call.
|
|
96
|
+
*/
|
|
97
|
+
export function substantiveEvidence(decision, diffByFile) {
|
|
98
|
+
if (decision.affectedFiles.length === 0)
|
|
99
|
+
return null;
|
|
100
|
+
let totalChanged = 0;
|
|
101
|
+
for (const file of decision.affectedFiles) {
|
|
102
|
+
const normalised = file.replace(/^[ab]\//, '');
|
|
103
|
+
const hunk = diffByFile.get(normalised);
|
|
104
|
+
if (!hunk)
|
|
105
|
+
return null; // require ALL affected files present
|
|
106
|
+
totalChanged += countChangedLines(hunk);
|
|
107
|
+
}
|
|
108
|
+
return totalChanged >= SUBSTANTIVE_MIN_CHANGED_LINES ? decision.affectedFiles[0] : null;
|
|
109
|
+
}
|
|
75
110
|
export async function verifyDecisions(decisions, diff, llm, commitMessages) {
|
|
76
111
|
if (decisions.length === 0) {
|
|
77
112
|
return { verified: [], phantom: [], missing: [] };
|
|
@@ -103,13 +138,21 @@ export async function verifyDecisions(decisions, diff, llm, commitMessages) {
|
|
|
103
138
|
return [];
|
|
104
139
|
return [{ ...d, status: 'verified', confidence: v.confidence, evidenceFile: v.evidenceFile, verifiedAt: now }];
|
|
105
140
|
});
|
|
106
|
-
|
|
107
|
-
|
|
141
|
+
// HF-1: rescue any LLM-marked phantom whose affected files are all present in the
|
|
142
|
+
// diff with substantive hunks — trust the diff over the LLM's "no evidence" call.
|
|
143
|
+
const phantom = [];
|
|
144
|
+
for (const p of result.phantom) {
|
|
108
145
|
const d = byId.get(p.id);
|
|
109
146
|
if (!d)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
147
|
+
continue;
|
|
148
|
+
const evidenceFile = substantiveEvidence(d, diffByFile);
|
|
149
|
+
if (evidenceFile) {
|
|
150
|
+
verified.push({ ...d, status: 'verified', confidence: 'low', evidenceFile, verifiedAt: now });
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
phantom.push({ ...d, status: 'phantom', confidence: 'low', verifiedAt: now });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
113
156
|
return { verified, phantom, missing: result.missing };
|
|
114
157
|
}
|
|
115
158
|
//# sourceMappingURL=verifier.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../../src/core/decisions/verifier.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"verifier.js","sourceRoot":"","sources":["../../../src/core/decisions/verifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change-Coupling & Volatility Analysis (spec-22) — mined from local git history.
|
|
3
|
+
*
|
|
4
|
+
* Two facts the call graph structurally cannot see:
|
|
5
|
+
*
|
|
6
|
+
* 1. Change coupling — "these files almost always change together." The
|
|
7
|
+
* *invisible* coupling with no import/call edge: the config and the parser
|
|
8
|
+
* that move in lockstep, the handler and its migration. An agent editing one
|
|
9
|
+
* is warned about the sibling it would otherwise miss.
|
|
10
|
+
* 2. Volatility / churn — "this file changed 23 times." A caution flag: high-churn
|
|
11
|
+
* code is where edits are riskiest.
|
|
12
|
+
*
|
|
13
|
+
* Prior art is logical/change coupling (CodeScene), whose own framing is decisive:
|
|
14
|
+
* change coupling "isn't possible to calculate from code alone — it is mined from
|
|
15
|
+
* git." Local, deterministic, no network (builds on Spec 18's git ingestion).
|
|
16
|
+
*
|
|
17
|
+
* Honest limits: co-change is CORRELATION, not causation; it is statistical and
|
|
18
|
+
* needs sufficient history; bulk commits (formatting sweeps, mass renames, vendored
|
|
19
|
+
* drops) manufacture false coupling. So: support/confidence thresholds, a documented
|
|
20
|
+
* bulk-commit size filter, and presentation as a SIGNAL, never a rule.
|
|
21
|
+
*/
|
|
22
|
+
export declare const COUPLING_MAX_COMMITS = 1000;
|
|
23
|
+
export declare const COUPLING_BULK_THRESHOLD = 25;
|
|
24
|
+
export declare const COUPLING_MIN_SUPPORT = 3;
|
|
25
|
+
export declare const COUPLING_MIN_CONFIDENCE = 0.3;
|
|
26
|
+
export declare const COUPLING_TOP_PAIRS = 5;
|
|
27
|
+
export declare const VOLATILITY_HIGH = 12;
|
|
28
|
+
export declare const VOLATILITY_MEDIUM = 5;
|
|
29
|
+
export interface CoupledFile {
|
|
30
|
+
file: string;
|
|
31
|
+
/** Number of commits that changed both files. */
|
|
32
|
+
support: number;
|
|
33
|
+
/** support / churn(thisFile) — P(B changes | A changes), 0..1. */
|
|
34
|
+
confidence: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ChangeCouplingResult {
|
|
37
|
+
/** file → number of (non-bulk) commits that touched it. */
|
|
38
|
+
churn: Map<string, number>;
|
|
39
|
+
/** file → its most-coupled files (above thresholds, capped, sorted). */
|
|
40
|
+
coupling: Map<string, CoupledFile[]>;
|
|
41
|
+
stats: {
|
|
42
|
+
commitsScanned: number;
|
|
43
|
+
bulkCommitsFiltered: number;
|
|
44
|
+
filesTracked: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Per-file change-coupling record as persisted/queried (spec-22). */
|
|
48
|
+
export interface FileChangeCoupling {
|
|
49
|
+
filePath: string;
|
|
50
|
+
churn: number;
|
|
51
|
+
coupledWith: CoupledFile[];
|
|
52
|
+
}
|
|
53
|
+
export interface ChangeCouplingOptions {
|
|
54
|
+
maxCommits?: number;
|
|
55
|
+
bulkThreshold?: number;
|
|
56
|
+
minSupport?: number;
|
|
57
|
+
minConfidence?: number;
|
|
58
|
+
topPairs?: number;
|
|
59
|
+
}
|
|
60
|
+
/** Map a churn count to a volatility level (absolute thresholds over the window). */
|
|
61
|
+
export declare function volatilityLevel(churn: number): 'high' | 'medium' | 'low';
|
|
62
|
+
/**
|
|
63
|
+
* Compute co-change coupling + churn from the local git log. Returns empty maps
|
|
64
|
+
* for a non-git / empty repo — never throws, never blocks analyze. Deterministic
|
|
65
|
+
* for a fixed git state.
|
|
66
|
+
*/
|
|
67
|
+
export declare function analyzeChangeCoupling(rootPath: string, opts?: ChangeCouplingOptions): Promise<ChangeCouplingResult>;
|
|
68
|
+
//# sourceMappingURL=change-coupling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-coupling.d.ts","sourceRoot":"","sources":["../../../src/core/provenance/change-coupling.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AASH,eAAO,MAAM,oBAAoB,OAAO,CAAC;AACzC,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AACtC,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAInC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,wEAAwE;IACxE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IACrC,KAAK,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CACtF;AAED,sEAAsE;AACtE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,WAAW,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAIxE;AAOD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,qBAA0B,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAkF/B"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change-Coupling & Volatility Analysis (spec-22) — mined from local git history.
|
|
3
|
+
*
|
|
4
|
+
* Two facts the call graph structurally cannot see:
|
|
5
|
+
*
|
|
6
|
+
* 1. Change coupling — "these files almost always change together." The
|
|
7
|
+
* *invisible* coupling with no import/call edge: the config and the parser
|
|
8
|
+
* that move in lockstep, the handler and its migration. An agent editing one
|
|
9
|
+
* is warned about the sibling it would otherwise miss.
|
|
10
|
+
* 2. Volatility / churn — "this file changed 23 times." A caution flag: high-churn
|
|
11
|
+
* code is where edits are riskiest.
|
|
12
|
+
*
|
|
13
|
+
* Prior art is logical/change coupling (CodeScene), whose own framing is decisive:
|
|
14
|
+
* change coupling "isn't possible to calculate from code alone — it is mined from
|
|
15
|
+
* git." Local, deterministic, no network (builds on Spec 18's git ingestion).
|
|
16
|
+
*
|
|
17
|
+
* Honest limits: co-change is CORRELATION, not causation; it is statistical and
|
|
18
|
+
* needs sufficient history; bulk commits (formatting sweeps, mass renames, vendored
|
|
19
|
+
* drops) manufacture false coupling. So: support/confidence thresholds, a documented
|
|
20
|
+
* bulk-commit size filter, and presentation as a SIGNAL, never a rule.
|
|
21
|
+
*/
|
|
22
|
+
import { execFile } from 'node:child_process';
|
|
23
|
+
import { promisify } from 'node:util';
|
|
24
|
+
import { isGitRepository } from '../drift/git-diff.js';
|
|
25
|
+
const execFileAsync = promisify(execFile);
|
|
26
|
+
// Documented bounds & thresholds.
|
|
27
|
+
export const COUPLING_MAX_COMMITS = 1000; // history window scanned
|
|
28
|
+
export const COUPLING_BULK_THRESHOLD = 25; // commits touching > this many files are filtered (manufacture coupling)
|
|
29
|
+
export const COUPLING_MIN_SUPPORT = 3; // min co-changes for a pair to count
|
|
30
|
+
export const COUPLING_MIN_CONFIDENCE = 0.3; // min co-changes / churn(A)
|
|
31
|
+
export const COUPLING_TOP_PAIRS = 5; // coupled files kept per file
|
|
32
|
+
// Absolute churn thresholds over the scanned window → volatility level.
|
|
33
|
+
export const VOLATILITY_HIGH = 12;
|
|
34
|
+
export const VOLATILITY_MEDIUM = 5;
|
|
35
|
+
const RS = '\x1e';
|
|
36
|
+
/** Map a churn count to a volatility level (absolute thresholds over the window). */
|
|
37
|
+
export function volatilityLevel(churn) {
|
|
38
|
+
if (churn >= VOLATILITY_HIGH)
|
|
39
|
+
return 'high';
|
|
40
|
+
if (churn >= VOLATILITY_MEDIUM)
|
|
41
|
+
return 'medium';
|
|
42
|
+
return 'low';
|
|
43
|
+
}
|
|
44
|
+
/** Round a confidence to 2 decimals deterministically. */
|
|
45
|
+
function round2(n) {
|
|
46
|
+
return Math.round(n * 100) / 100;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Compute co-change coupling + churn from the local git log. Returns empty maps
|
|
50
|
+
* for a non-git / empty repo — never throws, never blocks analyze. Deterministic
|
|
51
|
+
* for a fixed git state.
|
|
52
|
+
*/
|
|
53
|
+
export async function analyzeChangeCoupling(rootPath, opts = {}) {
|
|
54
|
+
const empty = {
|
|
55
|
+
churn: new Map(), coupling: new Map(),
|
|
56
|
+
stats: { commitsScanned: 0, bulkCommitsFiltered: 0, filesTracked: 0 },
|
|
57
|
+
};
|
|
58
|
+
if (!(await isGitRepository(rootPath)))
|
|
59
|
+
return empty;
|
|
60
|
+
const maxCommits = opts.maxCommits ?? COUPLING_MAX_COMMITS;
|
|
61
|
+
const bulkThreshold = opts.bulkThreshold ?? COUPLING_BULK_THRESHOLD;
|
|
62
|
+
const minSupport = opts.minSupport ?? COUPLING_MIN_SUPPORT;
|
|
63
|
+
const minConfidence = opts.minConfidence ?? COUPLING_MIN_CONFIDENCE;
|
|
64
|
+
const topPairs = opts.topPairs ?? COUPLING_TOP_PAIRS;
|
|
65
|
+
let stdout;
|
|
66
|
+
try {
|
|
67
|
+
({ stdout } = await execFileAsync('git', ['log', `--max-count=${maxCommits}`, '--no-merges', `--format=${RS}%h`, '--name-only'], { cwd: rootPath, maxBuffer: 128 * 1024 * 1024 }));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return empty;
|
|
71
|
+
}
|
|
72
|
+
// Parse into per-commit file sets.
|
|
73
|
+
const commitFileSets = [];
|
|
74
|
+
for (const seg of stdout.split(RS)) {
|
|
75
|
+
if (!seg.trim())
|
|
76
|
+
continue;
|
|
77
|
+
const lines = seg.split('\n').map(s => s.trim()).filter(Boolean);
|
|
78
|
+
// First line is the short SHA (from the format); the rest are file paths.
|
|
79
|
+
const files = lines.slice(1);
|
|
80
|
+
if (files.length > 0)
|
|
81
|
+
commitFileSets.push(files);
|
|
82
|
+
}
|
|
83
|
+
const churn = new Map();
|
|
84
|
+
// coOccur: "A\x00B" (A < B sorted) → count.
|
|
85
|
+
const coOccur = new Map();
|
|
86
|
+
let bulkFiltered = 0;
|
|
87
|
+
let scanned = 0;
|
|
88
|
+
for (const files of commitFileSets) {
|
|
89
|
+
if (files.length > bulkThreshold) {
|
|
90
|
+
bulkFiltered++;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
scanned++;
|
|
94
|
+
const uniq = [...new Set(files)].sort();
|
|
95
|
+
for (const f of uniq)
|
|
96
|
+
churn.set(f, (churn.get(f) ?? 0) + 1);
|
|
97
|
+
for (let i = 0; i < uniq.length; i++) {
|
|
98
|
+
for (let j = i + 1; j < uniq.length; j++) {
|
|
99
|
+
const key = `${uniq[i]}\x00${uniq[j]}`;
|
|
100
|
+
coOccur.set(key, (coOccur.get(key) ?? 0) + 1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Build directed coupling per file from the symmetric co-occurrence counts.
|
|
105
|
+
const couplingTmp = new Map();
|
|
106
|
+
const addPair = (a, b, support) => {
|
|
107
|
+
const churnA = churn.get(a) ?? 0;
|
|
108
|
+
if (churnA === 0)
|
|
109
|
+
return;
|
|
110
|
+
const confidence = support / churnA;
|
|
111
|
+
if (support < minSupport || confidence < minConfidence)
|
|
112
|
+
return;
|
|
113
|
+
const arr = couplingTmp.get(a) ?? [];
|
|
114
|
+
arr.push({ file: b, support, confidence: round2(confidence) });
|
|
115
|
+
couplingTmp.set(a, arr);
|
|
116
|
+
};
|
|
117
|
+
for (const [key, support] of coOccur) {
|
|
118
|
+
const [a, b] = key.split('\x00');
|
|
119
|
+
addPair(a, b, support);
|
|
120
|
+
addPair(b, a, support);
|
|
121
|
+
}
|
|
122
|
+
// Sort + cap each file's coupled list deterministically.
|
|
123
|
+
const coupling = new Map();
|
|
124
|
+
for (const [file, arr] of couplingTmp) {
|
|
125
|
+
arr.sort((x, y) => y.confidence - x.confidence || y.support - x.support || x.file.localeCompare(y.file));
|
|
126
|
+
coupling.set(file, arr.slice(0, topPairs));
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
churn,
|
|
130
|
+
coupling,
|
|
131
|
+
stats: { commitsScanned: scanned, bulkCommitsFiltered: bulkFiltered, filesTracked: churn.size },
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=change-coupling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-coupling.js","sourceRoot":"","sources":["../../../src/core/provenance/change-coupling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local provenance extractor (spec-18) — "who last changed this, in which PR".
|
|
3
|
+
*
|
|
4
|
+
* Reads the local `.git` history (and, only if present and authenticated, the
|
|
5
|
+
* local `gh` CLI) to produce per-file provenance. The deliberate constraint:
|
|
6
|
+
* **everything is local, nothing is uploaded.** This is the no-OAuth alternative
|
|
7
|
+
* to cloud connectors — the git-only path needs no network at all; `gh` is an
|
|
8
|
+
* optional enrichment that degrades gracefully when absent.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the parser→projector split used by IaC and decisions: this module is
|
|
11
|
+
* the parser (raw git → normalized records); project.ts maps records to typed
|
|
12
|
+
* `authored_by` / `changed_in_pr` graph edges.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PROVENANCE_MAX_COMMITS = 1000;
|
|
15
|
+
export declare const PROVENANCE_TOP_AUTHORS = 5;
|
|
16
|
+
export declare const PROVENANCE_MAX_PRS = 5;
|
|
17
|
+
export interface Author {
|
|
18
|
+
name: string;
|
|
19
|
+
email: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PullRequest {
|
|
22
|
+
number: number;
|
|
23
|
+
/** Title — only populated when `gh` enrichment succeeds. */
|
|
24
|
+
title?: string;
|
|
25
|
+
/** open | closed | merged — only populated when `gh` enrichment succeeds. */
|
|
26
|
+
state?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Per-file provenance — derived, regenerable, repo-relative POSIX path. */
|
|
29
|
+
export interface FileProvenance {
|
|
30
|
+
filePath: string;
|
|
31
|
+
lastAuthor: Author;
|
|
32
|
+
lastDate: string;
|
|
33
|
+
lastCommit: string;
|
|
34
|
+
lastSubject: string;
|
|
35
|
+
/** Distinct authors, most-recent-first, capped — includes lastAuthor. */
|
|
36
|
+
recentAuthors: Author[];
|
|
37
|
+
/** Distinct PRs that touched the file, most-recent-first, capped. */
|
|
38
|
+
prs: PullRequest[];
|
|
39
|
+
}
|
|
40
|
+
export interface ProvenanceOptions {
|
|
41
|
+
maxCommits?: number;
|
|
42
|
+
topAuthors?: number;
|
|
43
|
+
maxPrs?: number;
|
|
44
|
+
/** Attempt `gh` enrichment for PR titles/state. Auto-skips if gh is absent. */
|
|
45
|
+
useGh?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Extract the PR number a commit subject references (squash "(#123)" or merge). */
|
|
48
|
+
export declare function parsePrNumber(subject: string): number | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Extract per-file provenance for `files` (repo-relative POSIX paths).
|
|
51
|
+
*
|
|
52
|
+
* - Authors come from non-merge commits (real contributors, not the merger).
|
|
53
|
+
* - PR numbers come from squash subjects `(#N)` and merge commits
|
|
54
|
+
* (`--first-parent`, which attributes a PR's full diff to its files).
|
|
55
|
+
* - Returns `[]` for a non-git or empty repo — never throws, never blocks analyze.
|
|
56
|
+
*/
|
|
57
|
+
export declare function extractProvenance(rootPath: string, files: string[], opts?: ProvenanceOptions): Promise<FileProvenance[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Enrich PR numbers with title/state via the local `gh` CLI — one bounded call.
|
|
60
|
+
* Returns an empty map when `gh` is absent, unauthenticated, or the repo has no
|
|
61
|
+
* GitHub remote. Never throws; the git-only path is unaffected.
|
|
62
|
+
*/
|
|
63
|
+
export declare function enrichWithGh(rootPath: string): Promise<Map<number, {
|
|
64
|
+
title: string;
|
|
65
|
+
state: string;
|
|
66
|
+
}>>;
|
|
67
|
+
//# sourceMappingURL=git-provenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-provenance.d.ts","sourceRoot":"","sources":["../../../src/core/provenance/git-provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAC3C,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAMpC,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4EAA4E;AAC5E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,qEAAqE;IACrE,GAAG,EAAE,WAAW,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMjE;AAqCD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,GAAE,iBAAsB,GAC3B,OAAO,CAAC,cAAc,EAAE,CAAC,CAkF3B;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAcxD"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local provenance extractor (spec-18) — "who last changed this, in which PR".
|
|
3
|
+
*
|
|
4
|
+
* Reads the local `.git` history (and, only if present and authenticated, the
|
|
5
|
+
* local `gh` CLI) to produce per-file provenance. The deliberate constraint:
|
|
6
|
+
* **everything is local, nothing is uploaded.** This is the no-OAuth alternative
|
|
7
|
+
* to cloud connectors — the git-only path needs no network at all; `gh` is an
|
|
8
|
+
* optional enrichment that degrades gracefully when absent.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the parser→projector split used by IaC and decisions: this module is
|
|
11
|
+
* the parser (raw git → normalized records); project.ts maps records to typed
|
|
12
|
+
* `authored_by` / `changed_in_pr` graph edges.
|
|
13
|
+
*/
|
|
14
|
+
import { execFile } from 'node:child_process';
|
|
15
|
+
import { promisify } from 'node:util';
|
|
16
|
+
import { logger } from '../../utils/logger.js';
|
|
17
|
+
import { isGitRepository } from '../drift/git-diff.js';
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
// Bounds (documented caps — never import unbounded history; never bloat the graph).
|
|
20
|
+
export const PROVENANCE_MAX_COMMITS = 1000; // history depth scanned per pass
|
|
21
|
+
export const PROVENANCE_TOP_AUTHORS = 5; // recent distinct authors kept per file
|
|
22
|
+
export const PROVENANCE_MAX_PRS = 5; // distinct PRs kept per file
|
|
23
|
+
const GH_PR_LIMIT = 200; // single bounded `gh pr list` enrichment
|
|
24
|
+
const RS = '\x1e'; // record separator between commits
|
|
25
|
+
const FS = '\x1f'; // field separator within a commit header
|
|
26
|
+
/** Extract the PR number a commit subject references (squash "(#123)" or merge). */
|
|
27
|
+
export function parsePrNumber(subject) {
|
|
28
|
+
const merge = subject.match(/Merge pull request #(\d+)/);
|
|
29
|
+
if (merge)
|
|
30
|
+
return parseInt(merge[1], 10);
|
|
31
|
+
const squash = subject.match(/\(#(\d+)\)\s*$/) ?? subject.match(/\(#(\d+)\)/);
|
|
32
|
+
if (squash)
|
|
33
|
+
return parseInt(squash[1], 10);
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/** Run one `git log` pass and parse commits + their changed files. */
|
|
37
|
+
async function gitLog(rootPath, extraArgs, maxCommits) {
|
|
38
|
+
const format = `${RS}%h${FS}%an${FS}%ae${FS}%aI${FS}%s`;
|
|
39
|
+
let stdout;
|
|
40
|
+
try {
|
|
41
|
+
({ stdout } = await execFileAsync('git', ['log', `--max-count=${maxCommits}`, `--format=${format}`, '--name-only', ...extraArgs], { cwd: rootPath, maxBuffer: 64 * 1024 * 1024 }));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return []; // shallow/empty history, bad ref, etc. — never throw
|
|
45
|
+
}
|
|
46
|
+
const commits = [];
|
|
47
|
+
for (const seg of stdout.split(RS)) {
|
|
48
|
+
if (!seg.trim())
|
|
49
|
+
continue;
|
|
50
|
+
const nl = seg.indexOf('\n');
|
|
51
|
+
const header = nl === -1 ? seg : seg.slice(0, nl);
|
|
52
|
+
const [sha, name, email, date, subject] = header.split(FS);
|
|
53
|
+
if (!sha)
|
|
54
|
+
continue;
|
|
55
|
+
const files = nl === -1 ? [] : seg.slice(nl + 1).split('\n').map(s => s.trim()).filter(Boolean);
|
|
56
|
+
commits.push({ sha, author: { name: name ?? '', email: email ?? '' }, date: date ?? '', subject: subject ?? '', files });
|
|
57
|
+
}
|
|
58
|
+
return commits;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Extract per-file provenance for `files` (repo-relative POSIX paths).
|
|
62
|
+
*
|
|
63
|
+
* - Authors come from non-merge commits (real contributors, not the merger).
|
|
64
|
+
* - PR numbers come from squash subjects `(#N)` and merge commits
|
|
65
|
+
* (`--first-parent`, which attributes a PR's full diff to its files).
|
|
66
|
+
* - Returns `[]` for a non-git or empty repo — never throws, never blocks analyze.
|
|
67
|
+
*/
|
|
68
|
+
export async function extractProvenance(rootPath, files, opts = {}) {
|
|
69
|
+
if (files.length === 0)
|
|
70
|
+
return [];
|
|
71
|
+
if (!(await isGitRepository(rootPath)))
|
|
72
|
+
return [];
|
|
73
|
+
const maxCommits = opts.maxCommits ?? PROVENANCE_MAX_COMMITS;
|
|
74
|
+
const topAuthors = opts.topAuthors ?? PROVENANCE_TOP_AUTHORS;
|
|
75
|
+
const maxPrs = opts.maxPrs ?? PROVENANCE_MAX_PRS;
|
|
76
|
+
const wanted = new Set(files);
|
|
77
|
+
// Pass A — authorship + squash PRs (real authors, no merge commits).
|
|
78
|
+
const authorCommits = await gitLog(rootPath, ['--no-merges'], maxCommits);
|
|
79
|
+
// Pass B — merge-workflow PRs (merge commits with their full diff).
|
|
80
|
+
const mergeCommits = await gitLog(rootPath, ['--merges', '--first-parent'], maxCommits);
|
|
81
|
+
if (authorCommits.length === 0 && mergeCommits.length === 0)
|
|
82
|
+
return [];
|
|
83
|
+
const byFile = new Map();
|
|
84
|
+
const acc = (f) => {
|
|
85
|
+
let a = byFile.get(f);
|
|
86
|
+
if (!a) {
|
|
87
|
+
a = { authors: [], authorKeys: new Set(), prNumbers: [], prSeen: new Set() };
|
|
88
|
+
byFile.set(f, a);
|
|
89
|
+
}
|
|
90
|
+
return a;
|
|
91
|
+
};
|
|
92
|
+
// git log is newest-first; the first commit touching a file is its last-touch.
|
|
93
|
+
for (const c of authorCommits) {
|
|
94
|
+
const pr = parsePrNumber(c.subject);
|
|
95
|
+
for (const f of c.files) {
|
|
96
|
+
if (!wanted.has(f))
|
|
97
|
+
continue;
|
|
98
|
+
const a = acc(f);
|
|
99
|
+
if (!a.lastAuthor) {
|
|
100
|
+
a.lastAuthor = c.author;
|
|
101
|
+
a.lastDate = c.date;
|
|
102
|
+
a.lastCommit = c.sha;
|
|
103
|
+
a.lastSubject = c.subject;
|
|
104
|
+
}
|
|
105
|
+
const key = c.author.email || c.author.name;
|
|
106
|
+
if (key && !a.authorKeys.has(key) && a.authors.length < topAuthors) {
|
|
107
|
+
a.authorKeys.add(key);
|
|
108
|
+
a.authors.push(c.author);
|
|
109
|
+
}
|
|
110
|
+
if (pr !== undefined && !a.prSeen.has(pr) && a.prNumbers.length < maxPrs) {
|
|
111
|
+
a.prSeen.add(pr);
|
|
112
|
+
a.prNumbers.push(pr);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
for (const c of mergeCommits) {
|
|
117
|
+
const pr = parsePrNumber(c.subject);
|
|
118
|
+
if (pr === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
for (const f of c.files) {
|
|
121
|
+
if (!wanted.has(f))
|
|
122
|
+
continue;
|
|
123
|
+
const a = acc(f);
|
|
124
|
+
if (!a.prSeen.has(pr) && a.prNumbers.length < maxPrs) {
|
|
125
|
+
a.prSeen.add(pr);
|
|
126
|
+
a.prNumbers.push(pr);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Optional gh enrichment — one bounded call, best-effort, never required.
|
|
131
|
+
const allPrs = new Set();
|
|
132
|
+
for (const a of byFile.values())
|
|
133
|
+
for (const n of a.prNumbers)
|
|
134
|
+
allPrs.add(n);
|
|
135
|
+
const prMeta = (opts.useGh ?? true) && allPrs.size > 0
|
|
136
|
+
? await enrichWithGh(rootPath)
|
|
137
|
+
: new Map();
|
|
138
|
+
const result = [];
|
|
139
|
+
for (const [filePath, a] of byFile) {
|
|
140
|
+
if (!a.lastAuthor)
|
|
141
|
+
continue; // only PRs from merge pass, no authorship → skip (no last-touch)
|
|
142
|
+
result.push({
|
|
143
|
+
filePath,
|
|
144
|
+
lastAuthor: a.lastAuthor,
|
|
145
|
+
lastDate: a.lastDate,
|
|
146
|
+
lastCommit: a.lastCommit,
|
|
147
|
+
lastSubject: a.lastSubject,
|
|
148
|
+
recentAuthors: a.authors,
|
|
149
|
+
prs: a.prNumbers.map(number => {
|
|
150
|
+
const meta = prMeta.get(number);
|
|
151
|
+
return meta ? { number, title: meta.title, state: meta.state } : { number };
|
|
152
|
+
}),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// Deterministic order.
|
|
156
|
+
result.sort((x, y) => x.filePath.localeCompare(y.filePath));
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Enrich PR numbers with title/state via the local `gh` CLI — one bounded call.
|
|
161
|
+
* Returns an empty map when `gh` is absent, unauthenticated, or the repo has no
|
|
162
|
+
* GitHub remote. Never throws; the git-only path is unaffected.
|
|
163
|
+
*/
|
|
164
|
+
export async function enrichWithGh(rootPath) {
|
|
165
|
+
const out = new Map();
|
|
166
|
+
try {
|
|
167
|
+
const { stdout } = await execFileAsync('gh', ['pr', 'list', '--state', 'all', '--limit', String(GH_PR_LIMIT), '--json', 'number,title,state'], { cwd: rootPath, maxBuffer: 16 * 1024 * 1024 });
|
|
168
|
+
const prs = JSON.parse(stdout);
|
|
169
|
+
for (const p of prs)
|
|
170
|
+
out.set(p.number, { title: p.title, state: (p.state ?? '').toLowerCase() });
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
logger.debug?.('provenance: gh enrichment unavailable — using git-only provenance');
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=git-provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-provenance.js","sourceRoot":"","sources":["../../../src/core/provenance/git-provenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Projection: per-file provenance → typed `authored_by` / `changed_in_pr` graph
|
|
3
|
+
* edges (spec-18). The decisions/IaC analogue: git history is the authored source;
|
|
4
|
+
* this projection is derived and regenerable. Edges hang off existing file nodes —
|
|
5
|
+
* no person/PR nodes are added to the call graph, keeping it un-bloated (the cap is
|
|
6
|
+
* applied upstream in the extractor).
|
|
7
|
+
*
|
|
8
|
+
* Edge direction is code → context: a file is `authored_by` a person and
|
|
9
|
+
* `changed_in_pr` a pull request.
|
|
10
|
+
*/
|
|
11
|
+
import type { FileProvenance } from './git-provenance.js';
|
|
12
|
+
/** file → person. `role: 'last'` marks the last-touch author. */
|
|
13
|
+
export interface AuthoredByEdge {
|
|
14
|
+
kind: 'authored_by';
|
|
15
|
+
filePath: string;
|
|
16
|
+
name: string;
|
|
17
|
+
email: string;
|
|
18
|
+
/** Author date of the last-touch commit (only meaningful for role 'last'). */
|
|
19
|
+
lastDate?: string;
|
|
20
|
+
role: 'last' | 'recent';
|
|
21
|
+
}
|
|
22
|
+
/** file → pull request. `title`/`state` present only when `gh` enrichment succeeded. */
|
|
23
|
+
export interface ChangedInPrEdge {
|
|
24
|
+
kind: 'changed_in_pr';
|
|
25
|
+
filePath: string;
|
|
26
|
+
pr: number;
|
|
27
|
+
title?: string;
|
|
28
|
+
state?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ProjectedProvenance {
|
|
31
|
+
authoredBy: AuthoredByEdge[];
|
|
32
|
+
changedInPr: ChangedInPrEdge[];
|
|
33
|
+
}
|
|
34
|
+
/** "Name <email>" display key for a person (stable identity). */
|
|
35
|
+
export declare function personKey(name: string, email: string): string;
|
|
36
|
+
export declare function projectProvenance(records: FileProvenance[]): ProjectedProvenance;
|
|
37
|
+
//# sourceMappingURL=project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../../src/core/provenance/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;CACzB;AAED,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,eAAe,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC;AAED,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,mBAAmB,CAgChF"}
|