@mneme-ai/core 0.8.4 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entities/go-parser.d.ts +47 -0
- package/dist/entities/go-parser.d.ts.map +1 -0
- package/dist/entities/go-parser.js +315 -0
- package/dist/entities/go-parser.js.map +1 -0
- package/dist/entities/go-parser.test.d.ts +2 -0
- package/dist/entities/go-parser.test.d.ts.map +1 -0
- package/dist/entities/go-parser.test.js +147 -0
- package/dist/entities/go-parser.test.js.map +1 -0
- package/dist/entities/index.d.ts +1 -0
- package/dist/entities/index.d.ts.map +1 -1
- package/dist/entities/index.js +1 -0
- package/dist/entities/index.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer/indexer.d.ts +12 -0
- package/dist/indexer/indexer.d.ts.map +1 -1
- package/dist/indexer/indexer.js +28 -1
- package/dist/indexer/indexer.js.map +1 -1
- package/dist/insights/bus-factor.d.ts +58 -0
- package/dist/insights/bus-factor.d.ts.map +1 -0
- package/dist/insights/bus-factor.js +117 -0
- package/dist/insights/bus-factor.js.map +1 -0
- package/dist/insights/bus-factor.test.d.ts +2 -0
- package/dist/insights/bus-factor.test.d.ts.map +1 -0
- package/dist/insights/bus-factor.test.js +149 -0
- package/dist/insights/bus-factor.test.js.map +1 -0
- package/dist/insights/commit-coach.d.ts +80 -0
- package/dist/insights/commit-coach.d.ts.map +1 -0
- package/dist/insights/commit-coach.js +230 -0
- package/dist/insights/commit-coach.js.map +1 -0
- package/dist/insights/commit-coach.test.d.ts +2 -0
- package/dist/insights/commit-coach.test.d.ts.map +1 -0
- package/dist/insights/commit-coach.test.js +163 -0
- package/dist/insights/commit-coach.test.js.map +1 -0
- package/dist/insights/crystal-ball.d.ts +76 -0
- package/dist/insights/crystal-ball.d.ts.map +1 -0
- package/dist/insights/crystal-ball.js +219 -0
- package/dist/insights/crystal-ball.js.map +1 -0
- package/dist/insights/crystal-ball.test.d.ts +2 -0
- package/dist/insights/crystal-ball.test.d.ts.map +1 -0
- package/dist/insights/crystal-ball.test.js +157 -0
- package/dist/insights/crystal-ball.test.js.map +1 -0
- package/dist/insights/decisions.d.ts +38 -0
- package/dist/insights/decisions.d.ts.map +1 -0
- package/dist/insights/decisions.js +125 -0
- package/dist/insights/decisions.js.map +1 -0
- package/dist/insights/decisions.test.d.ts +2 -0
- package/dist/insights/decisions.test.d.ts.map +1 -0
- package/dist/insights/decisions.test.js +141 -0
- package/dist/insights/decisions.test.js.map +1 -0
- package/dist/insights/dream.d.ts +71 -0
- package/dist/insights/dream.d.ts.map +1 -0
- package/dist/insights/dream.js +235 -0
- package/dist/insights/dream.js.map +1 -0
- package/dist/insights/dream.test.d.ts +2 -0
- package/dist/insights/dream.test.d.ts.map +1 -0
- package/dist/insights/dream.test.js +127 -0
- package/dist/insights/dream.test.js.map +1 -0
- package/dist/insights/index.d.ts +21 -0
- package/dist/insights/index.d.ts.map +1 -0
- package/dist/insights/index.js +21 -0
- package/dist/insights/index.js.map +1 -0
- package/dist/insights/obsidian.d.ts +42 -0
- package/dist/insights/obsidian.d.ts.map +1 -0
- package/dist/insights/obsidian.js +263 -0
- package/dist/insights/obsidian.js.map +1 -0
- package/dist/insights/obsidian.test.d.ts +2 -0
- package/dist/insights/obsidian.test.d.ts.map +1 -0
- package/dist/insights/obsidian.test.js +241 -0
- package/dist/insights/obsidian.test.js.map +1 -0
- package/dist/insights/paradox.d.ts +36 -0
- package/dist/insights/paradox.d.ts.map +1 -0
- package/dist/insights/paradox.js +201 -0
- package/dist/insights/paradox.js.map +1 -0
- package/dist/insights/paradox.test.d.ts +2 -0
- package/dist/insights/paradox.test.d.ts.map +1 -0
- package/dist/insights/paradox.test.js +88 -0
- package/dist/insights/paradox.test.js.map +1 -0
- package/dist/insights/regret.d.ts +57 -0
- package/dist/insights/regret.d.ts.map +1 -0
- package/dist/insights/regret.js +137 -0
- package/dist/insights/regret.js.map +1 -0
- package/dist/insights/regret.test.d.ts +2 -0
- package/dist/insights/regret.test.d.ts.map +1 -0
- package/dist/insights/regret.test.js +153 -0
- package/dist/insights/regret.test.js.map +1 -0
- package/dist/insights/stack-trace.d.ts +40 -0
- package/dist/insights/stack-trace.d.ts.map +1 -0
- package/dist/insights/stack-trace.js +127 -0
- package/dist/insights/stack-trace.js.map +1 -0
- package/dist/insights/stack-trace.test.d.ts +2 -0
- package/dist/insights/stack-trace.test.d.ts.map +1 -0
- package/dist/insights/stack-trace.test.js +103 -0
- package/dist/insights/stack-trace.test.js.map +1 -0
- package/dist/insights/story.d.ts +34 -0
- package/dist/insights/story.d.ts.map +1 -0
- package/dist/insights/story.js +100 -0
- package/dist/insights/story.js.map +1 -0
- package/dist/insights/story.test.d.ts +2 -0
- package/dist/insights/story.test.d.ts.map +1 -0
- package/dist/insights/story.test.js +99 -0
- package/dist/insights/story.test.js.map +1 -0
- package/dist/insights/suggest.d.ts +29 -0
- package/dist/insights/suggest.d.ts.map +1 -0
- package/dist/insights/suggest.js +93 -0
- package/dist/insights/suggest.js.map +1 -0
- package/dist/insights/suggest.test.d.ts +2 -0
- package/dist/insights/suggest.test.d.ts.map +1 -0
- package/dist/insights/suggest.test.js +71 -0
- package/dist/insights/suggest.test.js.map +1 -0
- package/dist/insights/who-knows.d.ts +66 -0
- package/dist/insights/who-knows.d.ts.map +1 -0
- package/dist/insights/who-knows.js +125 -0
- package/dist/insights/who-knows.js.map +1 -0
- package/dist/insights/who-knows.test.d.ts +2 -0
- package/dist/insights/who-knows.test.d.ts.map +1 -0
- package/dist/insights/who-knows.test.js +109 -0
- package/dist/insights/who-knows.test.js.map +1 -0
- package/dist/quant/alpha.d.ts +87 -0
- package/dist/quant/alpha.d.ts.map +1 -0
- package/dist/quant/alpha.js +103 -0
- package/dist/quant/alpha.js.map +1 -0
- package/dist/quant/alpha.test.d.ts +2 -0
- package/dist/quant/alpha.test.d.ts.map +1 -0
- package/dist/quant/alpha.test.js +147 -0
- package/dist/quant/alpha.test.js.map +1 -0
- package/dist/quant/backtest.d.ts +57 -0
- package/dist/quant/backtest.d.ts.map +1 -0
- package/dist/quant/backtest.js +90 -0
- package/dist/quant/backtest.js.map +1 -0
- package/dist/quant/backtest.test.d.ts +2 -0
- package/dist/quant/backtest.test.d.ts.map +1 -0
- package/dist/quant/backtest.test.js +133 -0
- package/dist/quant/backtest.test.js.map +1 -0
- package/dist/quant/black-swan.d.ts +45 -0
- package/dist/quant/black-swan.d.ts.map +1 -0
- package/dist/quant/black-swan.js +112 -0
- package/dist/quant/black-swan.js.map +1 -0
- package/dist/quant/black-swan.test.d.ts +2 -0
- package/dist/quant/black-swan.test.d.ts.map +1 -0
- package/dist/quant/black-swan.test.js +131 -0
- package/dist/quant/black-swan.test.js.map +1 -0
- package/dist/quant/correlation-matrix.d.ts +54 -0
- package/dist/quant/correlation-matrix.d.ts.map +1 -0
- package/dist/quant/correlation-matrix.js +103 -0
- package/dist/quant/correlation-matrix.js.map +1 -0
- package/dist/quant/correlation-matrix.test.d.ts +2 -0
- package/dist/quant/correlation-matrix.test.d.ts.map +1 -0
- package/dist/quant/correlation-matrix.test.js +118 -0
- package/dist/quant/correlation-matrix.test.js.map +1 -0
- package/dist/quant/drawdown.d.ts +51 -0
- package/dist/quant/drawdown.d.ts.map +1 -0
- package/dist/quant/drawdown.js +96 -0
- package/dist/quant/drawdown.js.map +1 -0
- package/dist/quant/drawdown.test.d.ts +2 -0
- package/dist/quant/drawdown.test.d.ts.map +1 -0
- package/dist/quant/drawdown.test.js +166 -0
- package/dist/quant/drawdown.test.js.map +1 -0
- package/dist/quant/greek.d.ts +55 -0
- package/dist/quant/greek.d.ts.map +1 -0
- package/dist/quant/greek.js +157 -0
- package/dist/quant/greek.js.map +1 -0
- package/dist/quant/greek.test.d.ts +2 -0
- package/dist/quant/greek.test.d.ts.map +1 -0
- package/dist/quant/greek.test.js +138 -0
- package/dist/quant/greek.test.js.map +1 -0
- package/dist/quant/implied-volatility.d.ts +65 -0
- package/dist/quant/implied-volatility.d.ts.map +1 -0
- package/dist/quant/implied-volatility.js +149 -0
- package/dist/quant/implied-volatility.js.map +1 -0
- package/dist/quant/implied-volatility.test.d.ts +2 -0
- package/dist/quant/implied-volatility.test.d.ts.map +1 -0
- package/dist/quant/implied-volatility.test.js +127 -0
- package/dist/quant/implied-volatility.test.js.map +1 -0
- package/dist/quant/index.d.ts +28 -0
- package/dist/quant/index.d.ts.map +1 -0
- package/dist/quant/index.js +28 -0
- package/dist/quant/index.js.map +1 -0
- package/dist/quant/insider-trading.d.ts +56 -0
- package/dist/quant/insider-trading.d.ts.map +1 -0
- package/dist/quant/insider-trading.js +129 -0
- package/dist/quant/insider-trading.js.map +1 -0
- package/dist/quant/insider-trading.test.d.ts +2 -0
- package/dist/quant/insider-trading.test.d.ts.map +1 -0
- package/dist/quant/insider-trading.test.js +130 -0
- package/dist/quant/insider-trading.test.js.map +1 -0
- package/dist/quant/moneyball.d.ts +48 -0
- package/dist/quant/moneyball.d.ts.map +1 -0
- package/dist/quant/moneyball.js +110 -0
- package/dist/quant/moneyball.js.map +1 -0
- package/dist/quant/moneyball.test.d.ts +2 -0
- package/dist/quant/moneyball.test.d.ts.map +1 -0
- package/dist/quant/moneyball.test.js +137 -0
- package/dist/quant/moneyball.test.js.map +1 -0
- package/dist/quant/tax-loss-harvest.d.ts +59 -0
- package/dist/quant/tax-loss-harvest.d.ts.map +1 -0
- package/dist/quant/tax-loss-harvest.js +126 -0
- package/dist/quant/tax-loss-harvest.js.map +1 -0
- package/dist/quant/tax-loss-harvest.test.d.ts +2 -0
- package/dist/quant/tax-loss-harvest.test.d.ts.map +1 -0
- package/dist/quant/tax-loss-harvest.test.js +126 -0
- package/dist/quant/tax-loss-harvest.test.js.map +1 -0
- package/dist/retrieve/index.d.ts +2 -0
- package/dist/retrieve/index.d.ts.map +1 -1
- package/dist/retrieve/index.js +2 -0
- package/dist/retrieve/index.js.map +1 -1
- package/dist/retrieve/intent.d.ts +32 -0
- package/dist/retrieve/intent.d.ts.map +1 -0
- package/dist/retrieve/intent.js +104 -0
- package/dist/retrieve/intent.js.map +1 -0
- package/dist/retrieve/intent.test.d.ts +2 -0
- package/dist/retrieve/intent.test.d.ts.map +1 -0
- package/dist/retrieve/intent.test.js +106 -0
- package/dist/retrieve/intent.test.js.map +1 -0
- package/dist/retrieve/search.d.ts +30 -0
- package/dist/retrieve/search.d.ts.map +1 -1
- package/dist/retrieve/search.js +48 -0
- package/dist/retrieve/search.js.map +1 -1
- package/dist/retrieve/search.test.js +84 -1
- package/dist/retrieve/search.test.js.map +1 -1
- package/dist/retrieve/synthesize.d.ts +57 -0
- package/dist/retrieve/synthesize.d.ts.map +1 -0
- package/dist/retrieve/synthesize.js +191 -0
- package/dist/retrieve/synthesize.js.map +1 -0
- package/dist/retrieve/synthesize.test.d.ts +2 -0
- package/dist/retrieve/synthesize.test.d.ts.map +1 -0
- package/dist/retrieve/synthesize.test.js +127 -0
- package/dist/retrieve/synthesize.test.js.map +1 -0
- package/dist/store/schema.d.ts +2 -2
- package/dist/store/schema.d.ts.map +1 -1
- package/dist/store/schema.js +60 -2
- package/dist/store/schema.js.map +1 -1
- package/dist/store/sqlite.d.ts +2 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +24 -0
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/sqlite.test.js +1 -1
- package/dist/util/index.d.ts +2 -0
- package/dist/util/index.d.ts.map +1 -1
- package/dist/util/index.js +2 -1
- package/dist/util/index.js.map +1 -1
- package/dist/util/redact.d.ts +58 -0
- package/dist/util/redact.d.ts.map +1 -0
- package/dist/util/redact.js +129 -0
- package/dist/util/redact.js.map +1 -0
- package/dist/util/redact.test.d.ts +2 -0
- package/dist/util/redact.test.d.ts.map +1 -0
- package/dist/util/redact.test.js +148 -0
- package/dist/util/redact.test.js.map +1 -0
- package/dist/wisdom/calibrator.d.ts +43 -0
- package/dist/wisdom/calibrator.d.ts.map +1 -0
- package/dist/wisdom/calibrator.js +120 -0
- package/dist/wisdom/calibrator.js.map +1 -0
- package/dist/wisdom/feedback.d.ts +45 -0
- package/dist/wisdom/feedback.d.ts.map +1 -0
- package/dist/wisdom/feedback.js +116 -0
- package/dist/wisdom/feedback.js.map +1 -0
- package/dist/wisdom/index.d.ts +15 -0
- package/dist/wisdom/index.d.ts.map +1 -0
- package/dist/wisdom/index.js +15 -0
- package/dist/wisdom/index.js.map +1 -0
- package/dist/wisdom/types.d.ts +67 -0
- package/dist/wisdom/types.d.ts.map +1 -0
- package/dist/wisdom/types.js +20 -0
- package/dist/wisdom/types.js.map +1 -0
- package/dist/wisdom/wisdom.test.d.ts +2 -0
- package/dist/wisdom/wisdom.test.d.ts.map +1 -0
- package/dist/wisdom/wisdom.test.js +144 -0
- package/dist/wisdom/wisdom.test.js.map +1 -0
- package/package.json +1 -1
package/dist/indexer/indexer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { readCommits, readFileChanges } from "../git/log.js";
|
|
3
|
+
import { redact, mergeHits } from "../util/redact.js";
|
|
3
4
|
export class Indexer {
|
|
4
5
|
opts;
|
|
5
6
|
constructor(opts) {
|
|
@@ -34,6 +35,28 @@ export class Indexer {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
const chunks = buildChunks(commits);
|
|
38
|
+
// Redaction runs BEFORE embedding so secrets never reach a remote provider.
|
|
39
|
+
// Default ON; opt out only on trusted internal repos with no secret history.
|
|
40
|
+
let redactionHits = {};
|
|
41
|
+
if (this.opts.redact !== false) {
|
|
42
|
+
const ropts = typeof this.opts.redact === "object" ? this.opts.redact : {};
|
|
43
|
+
for (const chunk of chunks) {
|
|
44
|
+
const r = redact(chunk.text, ropts);
|
|
45
|
+
if (Object.keys(r.hits).length > 0) {
|
|
46
|
+
chunk.text = r.text;
|
|
47
|
+
redactionHits = mergeHits(redactionHits, r.hits);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const totalHits = Object.values(redactionHits).reduce((a, b) => a + b, 0);
|
|
51
|
+
if (totalHits > 0) {
|
|
52
|
+
report({
|
|
53
|
+
phase: "writing",
|
|
54
|
+
current: totalHits,
|
|
55
|
+
total: chunks.length,
|
|
56
|
+
message: `redacted ${totalHits} secret(s) across ${Object.keys(redactionHits).length} rule(s)`,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
37
60
|
if (this.opts.embedder) {
|
|
38
61
|
const model = this.opts.embedder.name;
|
|
39
62
|
const batchSize = this.opts.embedBatchSize ?? 32;
|
|
@@ -55,7 +78,11 @@ export class Indexer {
|
|
|
55
78
|
this.opts.store.upsertChunks(chunks);
|
|
56
79
|
}
|
|
57
80
|
report({ phase: "done", current: chunks.length, total: chunks.length });
|
|
58
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
commits: commits.length,
|
|
83
|
+
chunks: chunks.length,
|
|
84
|
+
redactionHits,
|
|
85
|
+
};
|
|
59
86
|
}
|
|
60
87
|
}
|
|
61
88
|
export function buildChunks(commits) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../src/indexer/indexer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAOzC,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"indexer.js","sourceRoot":"","sources":["../../src/indexer/indexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme bus-factor` — surface single-source-of-truth knowledge holders.
|
|
3
|
+
*
|
|
4
|
+
* For each indexed file: compute author distribution. Files where one
|
|
5
|
+
* author owns >75% of commits AND the file is touched frequently are
|
|
6
|
+
* "fragile knowledge zones" — if that person leaves, the knowledge dies.
|
|
7
|
+
*
|
|
8
|
+
* This is different from `who-knows`:
|
|
9
|
+
* - `who-knows` → "who knows about X?" (answer + backup)
|
|
10
|
+
* - `bus-factor` → "what knowledge is fragile?" (per-file ownership risk)
|
|
11
|
+
*
|
|
12
|
+
* Output ranks by criticality = ownerShare × log(touchCount). High-touch
|
|
13
|
+
* solo-owned files are the highest risk. Concrete pairing recommendations
|
|
14
|
+
* are attached when a backup author exists.
|
|
15
|
+
*/
|
|
16
|
+
import type { MnemeStore } from "../store/sqlite.js";
|
|
17
|
+
export interface FileRisk {
|
|
18
|
+
filePath: string;
|
|
19
|
+
/** Total commits that touched this file. */
|
|
20
|
+
totalTouches: number;
|
|
21
|
+
/** Top owner — name + email + share. */
|
|
22
|
+
topOwner: {
|
|
23
|
+
name: string;
|
|
24
|
+
email: string;
|
|
25
|
+
touches: number;
|
|
26
|
+
sharePct: number;
|
|
27
|
+
lastTouch: string;
|
|
28
|
+
};
|
|
29
|
+
/** Closest second-place author (for pair recommendation). Undefined if solo-only. */
|
|
30
|
+
backup?: {
|
|
31
|
+
name: string;
|
|
32
|
+
email: string;
|
|
33
|
+
touches: number;
|
|
34
|
+
lastTouch: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Risk tier from criticality score:
|
|
38
|
+
* critical: ownerShare ≥ 0.85 AND touches ≥ 10
|
|
39
|
+
* high: ownerShare ≥ 0.75 AND touches ≥ 5
|
|
40
|
+
* medium: ownerShare ≥ 0.6 AND touches ≥ 3
|
|
41
|
+
* low: everything else (still surfaced when solo-touched)
|
|
42
|
+
*/
|
|
43
|
+
tier: "critical" | "high" | "medium" | "low";
|
|
44
|
+
/** Numeric criticality — for sorting. */
|
|
45
|
+
score: number;
|
|
46
|
+
/** A 1-line lesson/recommendation. */
|
|
47
|
+
recommendation: string;
|
|
48
|
+
}
|
|
49
|
+
export interface BusFactorOptions {
|
|
50
|
+
/** Skip files that match these glob-like fragments. Defaults to lockfiles, generated dirs. */
|
|
51
|
+
excludePatterns?: string[];
|
|
52
|
+
/** Minimum touches for a file to be considered. Below this we don't have enough signal. */
|
|
53
|
+
minTouches?: number;
|
|
54
|
+
/** Cap on results returned. */
|
|
55
|
+
topN?: number;
|
|
56
|
+
}
|
|
57
|
+
export declare function busFactor(store: MnemeStore, opts?: BusFactorOptions): FileRisk[];
|
|
58
|
+
//# sourceMappingURL=bus-factor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus-factor.d.ts","sourceRoot":"","sources":["../../src/insights/bus-factor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,qFAAqF;IACrF,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7E;;;;;;OAMG;IACH,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IAC7C,yCAAyC;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,8FAA8F;IAC9F,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAaD,wBAAgB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,GAAE,gBAAqB,GAAG,QAAQ,EAAE,CA8EpF"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme bus-factor` — surface single-source-of-truth knowledge holders.
|
|
3
|
+
*
|
|
4
|
+
* For each indexed file: compute author distribution. Files where one
|
|
5
|
+
* author owns >75% of commits AND the file is touched frequently are
|
|
6
|
+
* "fragile knowledge zones" — if that person leaves, the knowledge dies.
|
|
7
|
+
*
|
|
8
|
+
* This is different from `who-knows`:
|
|
9
|
+
* - `who-knows` → "who knows about X?" (answer + backup)
|
|
10
|
+
* - `bus-factor` → "what knowledge is fragile?" (per-file ownership risk)
|
|
11
|
+
*
|
|
12
|
+
* Output ranks by criticality = ownerShare × log(touchCount). High-touch
|
|
13
|
+
* solo-owned files are the highest risk. Concrete pairing recommendations
|
|
14
|
+
* are attached when a backup author exists.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_EXCLUDES = [
|
|
17
|
+
"package-lock.json",
|
|
18
|
+
"yarn.lock",
|
|
19
|
+
"pnpm-lock.yaml",
|
|
20
|
+
"node_modules/",
|
|
21
|
+
"dist/",
|
|
22
|
+
".min.js",
|
|
23
|
+
".min.css",
|
|
24
|
+
"/generated/",
|
|
25
|
+
];
|
|
26
|
+
export function busFactor(store, opts = {}) {
|
|
27
|
+
const minTouches = opts.minTouches ?? 3;
|
|
28
|
+
const excludes = opts.excludePatterns ?? DEFAULT_EXCLUDES;
|
|
29
|
+
const topN = opts.topN ?? 20;
|
|
30
|
+
// Aggregate per-file author counts via JOIN of file_changes + commits.
|
|
31
|
+
const rows = store.db
|
|
32
|
+
.prepare(`SELECT
|
|
33
|
+
fc.path AS path,
|
|
34
|
+
c.author_name AS author_name,
|
|
35
|
+
c.author_email AS author_email,
|
|
36
|
+
COUNT(*) AS touches,
|
|
37
|
+
MAX(c.author_date) AS last_touch
|
|
38
|
+
FROM file_changes fc
|
|
39
|
+
JOIN commits c ON c.hash = fc.commit_hash
|
|
40
|
+
GROUP BY fc.path, c.author_name, c.author_email
|
|
41
|
+
ORDER BY fc.path, touches DESC`)
|
|
42
|
+
.all();
|
|
43
|
+
const byFile = new Map();
|
|
44
|
+
for (const r of rows) {
|
|
45
|
+
if (excludes.some((p) => r.path.includes(p)))
|
|
46
|
+
continue;
|
|
47
|
+
if (!byFile.has(r.path))
|
|
48
|
+
byFile.set(r.path, { filePath: r.path, authors: [], totalTouches: 0 });
|
|
49
|
+
const b = byFile.get(r.path);
|
|
50
|
+
b.authors.push({ name: r.author_name, email: r.author_email, touches: r.touches, lastTouch: r.last_touch });
|
|
51
|
+
b.totalTouches += r.touches;
|
|
52
|
+
}
|
|
53
|
+
const risks = [];
|
|
54
|
+
for (const b of byFile.values()) {
|
|
55
|
+
if (b.totalTouches < minTouches)
|
|
56
|
+
continue;
|
|
57
|
+
b.authors.sort((a, c) => c.touches - a.touches);
|
|
58
|
+
const top = b.authors[0];
|
|
59
|
+
const sharePct = Math.round((top.touches / b.totalTouches) * 100);
|
|
60
|
+
const share = top.touches / b.totalTouches;
|
|
61
|
+
const score = share * Math.log2(b.totalTouches + 1);
|
|
62
|
+
const { tier, recommendation } = classifyRisk(share, b.totalTouches, b.authors);
|
|
63
|
+
risks.push({
|
|
64
|
+
filePath: b.filePath,
|
|
65
|
+
totalTouches: b.totalTouches,
|
|
66
|
+
topOwner: {
|
|
67
|
+
name: top.name,
|
|
68
|
+
email: top.email,
|
|
69
|
+
touches: top.touches,
|
|
70
|
+
sharePct,
|
|
71
|
+
lastTouch: top.lastTouch,
|
|
72
|
+
},
|
|
73
|
+
backup: b.authors[1]
|
|
74
|
+
? {
|
|
75
|
+
name: b.authors[1].name,
|
|
76
|
+
email: b.authors[1].email,
|
|
77
|
+
touches: b.authors[1].touches,
|
|
78
|
+
lastTouch: b.authors[1].lastTouch,
|
|
79
|
+
}
|
|
80
|
+
: undefined,
|
|
81
|
+
tier,
|
|
82
|
+
score,
|
|
83
|
+
recommendation,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
risks.sort((a, b) => b.score - a.score);
|
|
87
|
+
return risks.slice(0, topN);
|
|
88
|
+
}
|
|
89
|
+
function classifyRisk(share, touches, authors) {
|
|
90
|
+
if (share >= 0.85 && touches >= 10) {
|
|
91
|
+
return {
|
|
92
|
+
tier: "critical",
|
|
93
|
+
recommendation: authors.length > 1
|
|
94
|
+
? `Pair with ${authors[1].name} this sprint — they are the only backup signal in history.`
|
|
95
|
+
: "Find a pairing partner before this person leaves. No backup currently exists.",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (share >= 0.75 && touches >= 5) {
|
|
99
|
+
return {
|
|
100
|
+
tier: "high",
|
|
101
|
+
recommendation: authors.length > 1
|
|
102
|
+
? `Encourage ${authors[1].name} to pick up small changes here to spread the knowledge.`
|
|
103
|
+
: "Solo-owned. Pair-program the next change to spread context.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (share >= 0.6 && touches >= 3) {
|
|
107
|
+
return {
|
|
108
|
+
tier: "medium",
|
|
109
|
+
recommendation: "Knowledge is concentrated but not critical. Watch for owner departure.",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
tier: "low",
|
|
114
|
+
recommendation: "Knowledge is reasonably distributed.",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=bus-factor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus-factor.js","sourceRoot":"","sources":["../../src/insights/bus-factor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus-factor.test.d.ts","sourceRoot":"","sources":["../../src/insights/bus-factor.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { MnemeStore } from "../store/sqlite.js";
|
|
6
|
+
import { busFactor } from "./bus-factor.js";
|
|
7
|
+
let tmpDir;
|
|
8
|
+
let store;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmpDir = mkdtempSync(join(tmpdir(), "mneme-busfactor-test-"));
|
|
11
|
+
store = new MnemeStore(join(tmpDir, "mneme.db"));
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
store.close();
|
|
15
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
const cmt = (hash, author, date, files) => ({
|
|
18
|
+
hash,
|
|
19
|
+
shortHash: hash.slice(0, 7),
|
|
20
|
+
authorName: author,
|
|
21
|
+
authorEmail: `${author}@x`,
|
|
22
|
+
authorDate: `${date}T00:00:00Z`,
|
|
23
|
+
committerDate: `${date}T00:00:00Z`,
|
|
24
|
+
subject: `commit by ${author}`,
|
|
25
|
+
body: "",
|
|
26
|
+
parents: [],
|
|
27
|
+
files,
|
|
28
|
+
});
|
|
29
|
+
function seed(commits) {
|
|
30
|
+
store.upsertCommits(commits);
|
|
31
|
+
for (const c of commits) {
|
|
32
|
+
const changes = c.files.map((f) => ({
|
|
33
|
+
commitHash: c.hash,
|
|
34
|
+
path: f,
|
|
35
|
+
changeKind: "M",
|
|
36
|
+
insertions: 1,
|
|
37
|
+
deletions: 0,
|
|
38
|
+
}));
|
|
39
|
+
store.upsertFileChanges(changes);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
describe("busFactor — risk tier classification", () => {
|
|
43
|
+
it("flags CRITICAL when one author owns ≥85% AND ≥10 touches", () => {
|
|
44
|
+
const commits = [];
|
|
45
|
+
for (let i = 0; i < 9; i++)
|
|
46
|
+
commits.push(cmt(`a${i}1234567`, "alice", `2024-08-0${(i % 9) + 1}`, ["src/payment.ts"]));
|
|
47
|
+
commits.push(cmt("b0123456", "bob", "2024-09-01", ["src/payment.ts"]));
|
|
48
|
+
seed(commits);
|
|
49
|
+
const risks = busFactor(store, { minTouches: 1 });
|
|
50
|
+
const payment = risks.find((r) => r.filePath === "src/payment.ts");
|
|
51
|
+
expect(payment.tier).toBe("critical");
|
|
52
|
+
expect(payment.topOwner.name).toBe("alice");
|
|
53
|
+
expect(payment.topOwner.sharePct).toBe(90);
|
|
54
|
+
expect(payment.recommendation.toLowerCase()).toContain("bob");
|
|
55
|
+
});
|
|
56
|
+
it("flags HIGH when one author owns 75-85% with 5+ touches", () => {
|
|
57
|
+
const commits = [
|
|
58
|
+
cmt("a1", "alice", "2024-08-01", ["src/auth.ts"]),
|
|
59
|
+
cmt("a2", "alice", "2024-08-02", ["src/auth.ts"]),
|
|
60
|
+
cmt("a3", "alice", "2024-08-03", ["src/auth.ts"]),
|
|
61
|
+
cmt("a4", "alice", "2024-08-04", ["src/auth.ts"]),
|
|
62
|
+
cmt("a5", "alice", "2024-08-05", ["src/auth.ts"]),
|
|
63
|
+
cmt("a6", "alice", "2024-08-06", ["src/auth.ts"]),
|
|
64
|
+
cmt("b1", "bob", "2024-08-07", ["src/auth.ts"]),
|
|
65
|
+
cmt("b2", "bob", "2024-08-08", ["src/auth.ts"]),
|
|
66
|
+
];
|
|
67
|
+
seed(commits);
|
|
68
|
+
const risks = busFactor(store, { minTouches: 1 });
|
|
69
|
+
const auth = risks.find((r) => r.filePath === "src/auth.ts");
|
|
70
|
+
expect(auth.tier).toBe("high");
|
|
71
|
+
expect(auth.topOwner.sharePct).toBe(75);
|
|
72
|
+
});
|
|
73
|
+
it("flags MEDIUM when share is 60-75%", () => {
|
|
74
|
+
const commits = [
|
|
75
|
+
cmt("a1", "alice", "2024-08-01", ["src/x.ts"]),
|
|
76
|
+
cmt("a2", "alice", "2024-08-02", ["src/x.ts"]),
|
|
77
|
+
cmt("a3", "alice", "2024-08-03", ["src/x.ts"]),
|
|
78
|
+
cmt("b1", "bob", "2024-08-04", ["src/x.ts"]),
|
|
79
|
+
cmt("b2", "bob", "2024-08-05", ["src/x.ts"]),
|
|
80
|
+
];
|
|
81
|
+
seed(commits);
|
|
82
|
+
const risks = busFactor(store, { minTouches: 1 });
|
|
83
|
+
const x = risks.find((r) => r.filePath === "src/x.ts");
|
|
84
|
+
expect(x.tier).toBe("medium");
|
|
85
|
+
expect(x.topOwner.sharePct).toBe(60);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe("busFactor — backup recommendation", () => {
|
|
89
|
+
it("includes the second-place author as the backup", () => {
|
|
90
|
+
const commits = [
|
|
91
|
+
...Array.from({ length: 9 }, (_, i) => cmt(`a${i}`, "alice", `2024-08-0${(i % 9) + 1}`, ["x.ts"])),
|
|
92
|
+
cmt("b1", "bob", "2024-09-01", ["x.ts"]),
|
|
93
|
+
cmt("c1", "carol", "2024-09-02", ["x.ts"]),
|
|
94
|
+
];
|
|
95
|
+
seed(commits);
|
|
96
|
+
const risks = busFactor(store, { minTouches: 1 });
|
|
97
|
+
const x = risks.find((r) => r.filePath === "x.ts");
|
|
98
|
+
expect(x.backup?.name).toBeDefined();
|
|
99
|
+
expect(["bob", "carol"]).toContain(x.backup?.name);
|
|
100
|
+
});
|
|
101
|
+
it("backup undefined when only one author has ever touched the file", () => {
|
|
102
|
+
const commits = Array.from({ length: 5 }, (_, i) => cmt(`a${i}`, "alice", `2024-08-0${i + 1}`, ["solo.ts"]));
|
|
103
|
+
seed(commits);
|
|
104
|
+
const risks = busFactor(store, { minTouches: 1 });
|
|
105
|
+
const solo = risks.find((r) => r.filePath === "solo.ts");
|
|
106
|
+
expect(solo.backup).toBeUndefined();
|
|
107
|
+
expect(solo.recommendation.toLowerCase()).toMatch(/no backup|spread/);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe("busFactor — filtering and sorting", () => {
|
|
111
|
+
it("excludes lockfiles + generated dirs by default", () => {
|
|
112
|
+
const commits = [
|
|
113
|
+
cmt("a1", "alice", "2024-08-01", ["package-lock.json", "src/x.ts"]),
|
|
114
|
+
cmt("a2", "alice", "2024-08-02", ["package-lock.json", "src/x.ts"]),
|
|
115
|
+
cmt("a3", "alice", "2024-08-03", ["package-lock.json", "src/x.ts"]),
|
|
116
|
+
];
|
|
117
|
+
seed(commits);
|
|
118
|
+
const risks = busFactor(store, { minTouches: 1 });
|
|
119
|
+
expect(risks.find((r) => r.filePath === "package-lock.json")).toBeUndefined();
|
|
120
|
+
expect(risks.find((r) => r.filePath === "src/x.ts")).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
it("respects minTouches threshold", () => {
|
|
123
|
+
const commits = [cmt("a1", "alice", "2024-08-01", ["lonely.ts"])];
|
|
124
|
+
seed(commits);
|
|
125
|
+
expect(busFactor(store, { minTouches: 2 })).toEqual([]);
|
|
126
|
+
expect(busFactor(store, { minTouches: 1 })).toHaveLength(1);
|
|
127
|
+
});
|
|
128
|
+
it("sorts by criticality score (high-touch solo > low-touch solo)", () => {
|
|
129
|
+
const commits = [
|
|
130
|
+
...Array.from({ length: 12 }, (_, i) => cmt(`a${i}`, "alice", `2024-08-${String(i + 1).padStart(2, "0")}`, ["hot.ts"])),
|
|
131
|
+
...Array.from({ length: 3 }, (_, i) => cmt(`b${i}`, "alice", `2024-09-0${i + 1}`, ["cold.ts"])),
|
|
132
|
+
];
|
|
133
|
+
seed(commits);
|
|
134
|
+
const risks = busFactor(store, { minTouches: 1 });
|
|
135
|
+
expect(risks[0].filePath).toBe("hot.ts");
|
|
136
|
+
expect(risks[0].score).toBeGreaterThan(risks[1].score);
|
|
137
|
+
});
|
|
138
|
+
it("respects topN limit", () => {
|
|
139
|
+
const commits = [];
|
|
140
|
+
for (let f = 0; f < 30; f++) {
|
|
141
|
+
for (let i = 0; i < 3; i++) {
|
|
142
|
+
commits.push(cmt(`f${f}c${i}`.padEnd(7, "x"), "alice", `2024-08-${String((i % 28) + 1).padStart(2, "0")}`, [`f${f}.ts`]));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
seed(commits);
|
|
146
|
+
expect(busFactor(store, { minTouches: 1, topN: 5 })).toHaveLength(5);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=bus-factor.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bus-factor.test.js","sourceRoot":"","sources":["../../src/insights/bus-factor.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mneme commit-coach` — pre-commit assistant.
|
|
3
|
+
*
|
|
4
|
+
* Reads a unified diff (from `git diff --staged` or stdin) and produces
|
|
5
|
+
* a structured advice payload covering:
|
|
6
|
+
* 1. Suggested commit message (style-matched to repo's history).
|
|
7
|
+
* 2. Recommended reviewers (top experts on touched files).
|
|
8
|
+
* 3. Scope warning (touching too many modules → suggest splitting).
|
|
9
|
+
* 4. Past-regret check (this pattern caused INC-X before).
|
|
10
|
+
*
|
|
11
|
+
* Pure data analysis. The CLI optionally calls an LLM to polish the
|
|
12
|
+
* commit message; everything else is heuristic + store queries.
|
|
13
|
+
*/
|
|
14
|
+
import type { MnemeStore } from "../store/sqlite.js";
|
|
15
|
+
export interface ParsedDiff {
|
|
16
|
+
files: string[];
|
|
17
|
+
/** Unique top-2-segment modules (e.g. "src/payment", "src/auth"). */
|
|
18
|
+
modules: string[];
|
|
19
|
+
/** Total lines added across all hunks. */
|
|
20
|
+
added: number;
|
|
21
|
+
/** Total lines removed across all hunks. */
|
|
22
|
+
removed: number;
|
|
23
|
+
/** Type guess based on file paths + line counts. */
|
|
24
|
+
shape: "feat" | "fix" | "refactor" | "test" | "docs" | "chore" | "perf";
|
|
25
|
+
}
|
|
26
|
+
export interface Reviewer {
|
|
27
|
+
name: string;
|
|
28
|
+
email: string;
|
|
29
|
+
/** % of commits on the touched files attributable to this person. */
|
|
30
|
+
ownership: number;
|
|
31
|
+
/** Files this reviewer is the primary owner of (within touched set). */
|
|
32
|
+
ownedFiles: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface RegretWarning {
|
|
35
|
+
pattern: string;
|
|
36
|
+
pastCommitHash: string;
|
|
37
|
+
pastDate: string;
|
|
38
|
+
outcome: string;
|
|
39
|
+
}
|
|
40
|
+
export interface CoachAdvice {
|
|
41
|
+
/** What the diff looks like. */
|
|
42
|
+
diff: ParsedDiff;
|
|
43
|
+
/** Suggested commit message subject (style-matched). */
|
|
44
|
+
suggestedSubject: string;
|
|
45
|
+
/** Top 3 recommended reviewers. */
|
|
46
|
+
reviewers: Reviewer[];
|
|
47
|
+
/** Scope diagnosis — module count + verdict. */
|
|
48
|
+
scopeOK: boolean;
|
|
49
|
+
scopeMessage: string;
|
|
50
|
+
/** Past-regret warnings if similar past changes regretted. */
|
|
51
|
+
warnings: RegretWarning[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse a unified diff string. Best-effort — handles standard `git diff`
|
|
55
|
+
* output. Skips binary diffs and file-mode changes.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseDiff(diffText: string): ParsedDiff;
|
|
58
|
+
/**
|
|
59
|
+
* Look up the top reviewer per touched file (the file's largest contributor).
|
|
60
|
+
* Aggregates across files to find the people most worth requesting review from.
|
|
61
|
+
*/
|
|
62
|
+
export declare function recommendReviewers(store: MnemeStore, files: string[], topN?: number): Reviewer[];
|
|
63
|
+
/**
|
|
64
|
+
* Suggest a commit message based on the diff shape + style of recent
|
|
65
|
+
* commits in the repo. Returns a Conventional-Commits-style subject line.
|
|
66
|
+
*/
|
|
67
|
+
export declare function suggestSubject(diff: ParsedDiff, recentSubjects: string[]): string;
|
|
68
|
+
/** Diagnose scope creep. Touching > 3 modules is usually too much for one commit. */
|
|
69
|
+
export declare function checkScope(diff: ParsedDiff): {
|
|
70
|
+
scopeOK: boolean;
|
|
71
|
+
message: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Check past regrets: any commit touching the same files where the
|
|
75
|
+
* follow-up was a fix/revert? If so, surface as a warning.
|
|
76
|
+
*/
|
|
77
|
+
export declare function checkPastRegrets(store: MnemeStore, files: string[]): RegretWarning[];
|
|
78
|
+
/** End-to-end coach: parse → recommend → diagnose. Pure-ish (reads the store). */
|
|
79
|
+
export declare function coach(store: MnemeStore, diffText: string): CoachAdvice;
|
|
80
|
+
//# sourceMappingURL=commit-coach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-coach.d.ts","sourceRoot":"","sources":["../../src/insights/commit-coach.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIrD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,KAAK,EAAE,MAAM,GAAG,KAAK,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;CACzE;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,IAAI,EAAE,UAAU,CAAC;IACjB,wDAAwD;IACxD,gBAAgB,EAAE,MAAM,CAAC;IACzB,mCAAmC;IACnC,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAmCtD;AAkBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,SAAI,GAAG,QAAQ,EAAE,CAyD3F;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,CAiBjF;AAED,qFAAqF;AACrF,wBAAgB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAYlF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAmDpF;AAED,kFAAkF;AAClF,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAqBtE"}
|