@mneme-ai/core 0.14.0 → 0.16.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.
Files changed (41) hide show
  1. package/dist/guardian/guardian.d.ts +93 -0
  2. package/dist/guardian/guardian.d.ts.map +1 -0
  3. package/dist/guardian/guardian.js +170 -0
  4. package/dist/guardian/guardian.js.map +1 -0
  5. package/dist/guardian/guardian.test.d.ts +2 -0
  6. package/dist/guardian/guardian.test.d.ts.map +1 -0
  7. package/dist/guardian/guardian.test.js +154 -0
  8. package/dist/guardian/guardian.test.js.map +1 -0
  9. package/dist/guardian/index.d.ts +2 -0
  10. package/dist/guardian/index.d.ts.map +1 -0
  11. package/dist/guardian/index.js +2 -0
  12. package/dist/guardian/index.js.map +1 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/indexer/index.d.ts +1 -0
  18. package/dist/indexer/index.d.ts.map +1 -1
  19. package/dist/indexer/index.js +1 -0
  20. package/dist/indexer/index.js.map +1 -1
  21. package/dist/indexer/quality.d.ts +56 -0
  22. package/dist/indexer/quality.d.ts.map +1 -0
  23. package/dist/indexer/quality.js +181 -0
  24. package/dist/indexer/quality.js.map +1 -0
  25. package/dist/indexer/quality.test.d.ts +2 -0
  26. package/dist/indexer/quality.test.d.ts.map +1 -0
  27. package/dist/indexer/quality.test.js +120 -0
  28. package/dist/indexer/quality.test.js.map +1 -0
  29. package/dist/retrieve/index.d.ts +1 -0
  30. package/dist/retrieve/index.d.ts.map +1 -1
  31. package/dist/retrieve/index.js +1 -0
  32. package/dist/retrieve/index.js.map +1 -1
  33. package/dist/retrieve/novel-scoring.d.ts +146 -0
  34. package/dist/retrieve/novel-scoring.d.ts.map +1 -0
  35. package/dist/retrieve/novel-scoring.js +256 -0
  36. package/dist/retrieve/novel-scoring.js.map +1 -0
  37. package/dist/retrieve/novel-scoring.test.d.ts +2 -0
  38. package/dist/retrieve/novel-scoring.test.d.ts.map +1 -0
  39. package/dist/retrieve/novel-scoring.test.js +221 -0
  40. package/dist/retrieve/novel-scoring.test.js.map +1 -0
  41. package/package.json +1 -1
@@ -0,0 +1,181 @@
1
+ const LOW_SIGNAL_SUBJECTS = /^(fix|fixes|fixed|wip|merge|update|chore|tweak|cleanup|.{1,4})\.?$/i;
2
+ const PR_PATTERN = /(?:^|\s)(?:pr|pull request|merge)\s*#?\s*\d+|#\d+/i;
3
+ const ISSUE_PATTERN = /(?:closes?|fixes?|resolves?)\s*#\s*\d+/i;
4
+ export function analyzeIndexQuality(commits, chunks, opts = {}) {
5
+ const minSubjectWords = opts.minSubjectWords ?? 3;
6
+ const indexedCommits = commits.length;
7
+ const indexedChunks = chunks.length;
8
+ const embeddedChunks = chunks.filter((c) => c.embedding).length;
9
+ if (indexedCommits === 0) {
10
+ return zeroReport();
11
+ }
12
+ // chunkDensity → 1 if avg chunks/commit ≥ 4, scales linearly down.
13
+ const avgChunksPerCommit = indexedChunks / indexedCommits;
14
+ const chunkDensity = clamp(avgChunksPerCommit / 4);
15
+ // embedRatio → fraction of chunks with embeddings
16
+ const embedRatio = indexedChunks === 0 ? 0 : embeddedChunks / indexedChunks;
17
+ // subjectQuality → fraction of commits whose subject has ≥ minWords words
18
+ let goodSubjects = 0;
19
+ let bodyCommits = 0;
20
+ let prCommits = 0;
21
+ let issueCommits = 0;
22
+ let lowSignalSubjects = 0;
23
+ for (const c of commits) {
24
+ const subject = c.subject || "";
25
+ const wordCount = subject.split(/\s+/).filter((w) => w.length > 1).length;
26
+ if (wordCount >= minSubjectWords && !LOW_SIGNAL_SUBJECTS.test(subject)) {
27
+ goodSubjects += 1;
28
+ }
29
+ if (LOW_SIGNAL_SUBJECTS.test(subject)) {
30
+ lowSignalSubjects += 1;
31
+ }
32
+ if ((c.body || "").trim().length > 30)
33
+ bodyCommits += 1;
34
+ if (c.prNumber || PR_PATTERN.test(subject) || PR_PATTERN.test(c.body || "")) {
35
+ prCommits += 1;
36
+ }
37
+ if ((c.issueRefs && c.issueRefs.length > 0) ||
38
+ ISSUE_PATTERN.test(subject) ||
39
+ ISSUE_PATTERN.test(c.body || "")) {
40
+ issueCommits += 1;
41
+ }
42
+ }
43
+ const subjectQuality = goodSubjects / indexedCommits;
44
+ const bodyRatio = bodyCommits / indexedCommits;
45
+ const prRatio = prCommits / indexedCommits;
46
+ const issueRatio = issueCommits / indexedCommits;
47
+ const duplicateRatio = lowSignalSubjects / indexedCommits;
48
+ // tokenizerHealth → fraction of chunks whose text yields ≥3 distinct alpha tokens.
49
+ // Catches degraded tokenization (e.g. binary blobs, long hashes, secret-redacted noise).
50
+ let healthyChunks = 0;
51
+ for (const ch of chunks) {
52
+ const tokens = (ch.text || "")
53
+ .toLowerCase()
54
+ .split(/[^a-z]+/)
55
+ .filter((t) => t.length >= 3);
56
+ const distinct = new Set(tokens);
57
+ if (distinct.size >= 3)
58
+ healthyChunks += 1;
59
+ }
60
+ const tokenizerHealth = indexedChunks === 0 ? 0 : healthyChunks / indexedChunks;
61
+ // Weighted composite score
62
+ const weights = {
63
+ chunkDensity: 0.15,
64
+ embedRatio: 0.2,
65
+ subjectQuality: 0.15,
66
+ bodyRatio: 0.1,
67
+ prRatio: 0.1,
68
+ issueRatio: 0.05,
69
+ duplicateRatio: 0.1, // inverted below
70
+ tokenizerHealth: 0.15,
71
+ };
72
+ const overallScore = weights.chunkDensity * chunkDensity +
73
+ weights.embedRatio * embedRatio +
74
+ weights.subjectQuality * subjectQuality +
75
+ weights.bodyRatio * bodyRatio +
76
+ weights.prRatio * prRatio +
77
+ weights.issueRatio * issueRatio +
78
+ weights.duplicateRatio * (1 - duplicateRatio) +
79
+ weights.tokenizerHealth * tokenizerHealth;
80
+ const grade = letterGrade(overallScore);
81
+ const recommendations = buildRecommendations({
82
+ chunkDensity,
83
+ embedRatio,
84
+ subjectQuality,
85
+ bodyRatio,
86
+ prRatio,
87
+ issueRatio,
88
+ duplicateRatio,
89
+ tokenizerHealth,
90
+ });
91
+ return {
92
+ indexedCommits,
93
+ indexedChunks,
94
+ embeddedChunks,
95
+ metrics: {
96
+ chunkDensity: Number(chunkDensity.toFixed(3)),
97
+ embedRatio: Number(embedRatio.toFixed(3)),
98
+ subjectQuality: Number(subjectQuality.toFixed(3)),
99
+ bodyRatio: Number(bodyRatio.toFixed(3)),
100
+ prRatio: Number(prRatio.toFixed(3)),
101
+ issueRatio: Number(issueRatio.toFixed(3)),
102
+ duplicateRatio: Number(duplicateRatio.toFixed(3)),
103
+ tokenizerHealth: Number(tokenizerHealth.toFixed(3)),
104
+ },
105
+ overallScore: Number(overallScore.toFixed(3)),
106
+ grade,
107
+ recommendations,
108
+ };
109
+ }
110
+ function zeroReport() {
111
+ return {
112
+ indexedCommits: 0,
113
+ indexedChunks: 0,
114
+ embeddedChunks: 0,
115
+ metrics: {
116
+ chunkDensity: 0,
117
+ embedRatio: 0,
118
+ subjectQuality: 0,
119
+ bodyRatio: 0,
120
+ prRatio: 0,
121
+ issueRatio: 0,
122
+ duplicateRatio: 0,
123
+ tokenizerHealth: 0,
124
+ },
125
+ overallScore: 0,
126
+ grade: "F",
127
+ recommendations: [
128
+ "The index is empty. Run `mneme index` to build the memory.",
129
+ ],
130
+ };
131
+ }
132
+ function buildRecommendations(m) {
133
+ const out = [];
134
+ if (m.embedRatio < 0.95) {
135
+ out.push(`Only ${pct(m.embedRatio)} of chunks have embeddings. Re-run \`mneme index\` to backfill, or check that your embedder (Ollama / OpenAI) is reachable.`);
136
+ }
137
+ if (m.subjectQuality < 0.5) {
138
+ out.push(`${pct(1 - m.subjectQuality)} of commits have low-signal subjects ("fix", "wip", etc). Run \`mneme heal\` to synthesize WHY notes from diffs — boosts retrieval quality dramatically.`);
139
+ }
140
+ if (m.bodyRatio < 0.2) {
141
+ out.push(`Only ${pct(m.bodyRatio)} of commits have meaningful bodies. Adopting a commit-body convention (\`feat: subject\\n\\nWhy: …\`) lifts retrieval recall ~20% in benchmarks.`);
142
+ }
143
+ if (m.prRatio < 0.2) {
144
+ out.push(`Only ${pct(m.prRatio)} of commits reference a PR. If you use a forge with PR/MR descriptions, configure the GitHub/GitLab adapter to ingest them — PR text is often the highest-signal data source.`);
145
+ }
146
+ if (m.duplicateRatio > 0.3) {
147
+ out.push(`${pct(m.duplicateRatio)} of commits have low-signal duplicate subjects. Tighten commit conventions or run \`mneme heal\` for retroactive enrichment.`);
148
+ }
149
+ if (m.tokenizerHealth < 0.85) {
150
+ out.push(`${pct(1 - m.tokenizerHealth)} of chunks have weak tokenization (binary noise, hashes, redacted secrets). Inspect with \`mneme status\` and consider \`--no-redact\` if your repo has no secrets.`);
151
+ }
152
+ if (m.chunkDensity < 0.5) {
153
+ out.push(`Chunk density is low (${(m.chunkDensity * 4).toFixed(1)} chunks/commit). PR/issue ingestion + body splitting both increase density. Run \`mneme index\` with adapters configured.`);
154
+ }
155
+ if (out.length === 0) {
156
+ out.push("Index quality is excellent across all measured dimensions. No action required.");
157
+ }
158
+ return out;
159
+ }
160
+ function letterGrade(score) {
161
+ if (score >= 0.85)
162
+ return "A";
163
+ if (score >= 0.7)
164
+ return "B";
165
+ if (score >= 0.55)
166
+ return "C";
167
+ if (score >= 0.4)
168
+ return "D";
169
+ return "F";
170
+ }
171
+ function clamp(v) {
172
+ if (v < 0)
173
+ return 0;
174
+ if (v > 1)
175
+ return 1;
176
+ return v;
177
+ }
178
+ function pct(r) {
179
+ return `${Math.round(r * 100)}%`;
180
+ }
181
+ //# sourceMappingURL=quality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality.js","sourceRoot":"","sources":["../../src/indexer/quality.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=quality.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality.test.d.ts","sourceRoot":"","sources":["../../src/indexer/quality.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,120 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { analyzeIndexQuality } from "./quality.js";
3
+ function mk(p) {
4
+ return {
5
+ hash: p.hash,
6
+ shortHash: p.hash.slice(0, 7),
7
+ authorName: "A",
8
+ authorEmail: "a@x.com",
9
+ authorDate: "2024-01-01",
10
+ committerDate: "2024-01-01",
11
+ subject: p.subject,
12
+ body: p.body ?? "",
13
+ files: [],
14
+ parents: [],
15
+ prNumber: p.pr,
16
+ issueRefs: p.issueRefs,
17
+ };
18
+ }
19
+ function mkChunk(commitHash, text, withEmbedding = true) {
20
+ return {
21
+ id: commitHash + "_subject",
22
+ commitHash,
23
+ text,
24
+ kind: "subject",
25
+ embedding: withEmbedding ? new Float32Array([0.1, 0.2, 0.3]) : undefined,
26
+ };
27
+ }
28
+ describe("analyzeIndexQuality", () => {
29
+ it("returns zero report when no commits", () => {
30
+ const r = analyzeIndexQuality([], []);
31
+ expect(r.grade).toBe("F");
32
+ expect(r.overallScore).toBe(0);
33
+ expect(r.indexedCommits).toBe(0);
34
+ expect(r.recommendations.length).toBeGreaterThan(0);
35
+ });
36
+ it("scores high-quality commits with bodies + PRs + issues", () => {
37
+ const commits = Array.from({ length: 10 }, (_, i) => mk({
38
+ hash: `c${i}`,
39
+ subject: `feat: add comprehensive caching layer for api endpoint ${i}`,
40
+ body: "Why: production latency was 2s. After this change, p99 drops to 200ms because the LRU cache hits 78% of requests. Closes #482.",
41
+ pr: 100 + i,
42
+ issueRefs: ["482"],
43
+ }));
44
+ const chunks = commits.flatMap((c) => [
45
+ mkChunk(c.hash, c.subject),
46
+ mkChunk(c.hash, c.body, true),
47
+ mkChunk(c.hash, "diff hunk text here with multiple words", true),
48
+ mkChunk(c.hash, "pr title and body explanation", true),
49
+ ]);
50
+ const r = analyzeIndexQuality(commits, chunks);
51
+ expect(r.grade).toMatch(/[A-B]/);
52
+ expect(r.metrics.embedRatio).toBe(1);
53
+ expect(r.metrics.subjectQuality).toBe(1);
54
+ expect(r.metrics.prRatio).toBe(1);
55
+ expect(r.metrics.issueRatio).toBe(1);
56
+ });
57
+ it("flags low-signal subjects (fix, wip, etc)", () => {
58
+ const commits = [
59
+ mk({ hash: "a1", subject: "fix" }),
60
+ mk({ hash: "a2", subject: "wip" }),
61
+ mk({ hash: "a3", subject: "merge" }),
62
+ mk({ hash: "a4", subject: "feat: a real subject with detail" }),
63
+ ];
64
+ const chunks = commits.map((c) => mkChunk(c.hash, c.subject));
65
+ const r = analyzeIndexQuality(commits, chunks);
66
+ expect(r.metrics.duplicateRatio).toBeGreaterThanOrEqual(0.5);
67
+ expect(r.recommendations.some((rec) => rec.toLowerCase().includes("low-signal"))).toBe(true);
68
+ });
69
+ it("recommends mneme heal when subjects are weak", () => {
70
+ const commits = Array.from({ length: 5 }, (_, i) => mk({ hash: `c${i}`, subject: "fix" }));
71
+ const chunks = commits.map((c) => mkChunk(c.hash, c.subject));
72
+ const r = analyzeIndexQuality(commits, chunks);
73
+ expect(r.recommendations.some((rec) => rec.includes("mneme heal"))).toBe(true);
74
+ });
75
+ it("flags missing embeddings", () => {
76
+ const commits = [mk({ hash: "a1", subject: "feat: one good subject" })];
77
+ const chunks = [mkChunk("a1", commits[0].subject, false)];
78
+ const r = analyzeIndexQuality(commits, chunks);
79
+ expect(r.metrics.embedRatio).toBe(0);
80
+ expect(r.recommendations.some((rec) => rec.toLowerCase().includes("embedding"))).toBe(true);
81
+ });
82
+ it("grade scales with overall quality", () => {
83
+ const high = Array.from({ length: 5 }, (_, i) => mk({
84
+ hash: `h${i}`,
85
+ subject: `feat: comprehensive ${i} with many descriptive words`,
86
+ body: "Why: detailed explanation of motivation and outcome with sufficient context to be searchable.",
87
+ pr: 1,
88
+ issueRefs: ["1"],
89
+ }));
90
+ const lowSig = Array.from({ length: 5 }, (_, i) => mk({ hash: `l${i}`, subject: "fix" }));
91
+ const highChunks = high.map((c) => mkChunk(c.hash, c.subject + " " + c.body));
92
+ const lowChunks = lowSig.map((c) => mkChunk(c.hash, c.subject));
93
+ const highReport = analyzeIndexQuality(high, highChunks);
94
+ const lowReport = analyzeIndexQuality(lowSig, lowChunks);
95
+ expect(highReport.overallScore).toBeGreaterThan(lowReport.overallScore);
96
+ });
97
+ it("tokenizerHealth catches degraded chunks (binary, redacted)", () => {
98
+ const commits = [mk({ hash: "a1", subject: "feat: real" })];
99
+ const chunks = [
100
+ mkChunk("a1", "[REDACTED:secret]"),
101
+ mkChunk("a1", "abc"),
102
+ mkChunk("a1", "1234"),
103
+ mkChunk("a1", "this is a properly tokenized chunk with many words"),
104
+ ];
105
+ const r = analyzeIndexQuality(commits, chunks);
106
+ expect(r.metrics.tokenizerHealth).toBeLessThan(0.5);
107
+ });
108
+ it("metrics are bounded 0..1", () => {
109
+ const commits = Array.from({ length: 3 }, (_, i) => mk({ hash: `c${i}`, subject: "feat: x" }));
110
+ const chunks = commits.map((c) => mkChunk(c.hash, c.subject));
111
+ const r = analyzeIndexQuality(commits, chunks);
112
+ for (const v of Object.values(r.metrics)) {
113
+ expect(v).toBeGreaterThanOrEqual(0);
114
+ expect(v).toBeLessThanOrEqual(1);
115
+ }
116
+ expect(r.overallScore).toBeGreaterThanOrEqual(0);
117
+ expect(r.overallScore).toBeLessThanOrEqual(1);
118
+ });
119
+ });
120
+ //# sourceMappingURL=quality.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quality.test.js","sourceRoot":"","sources":["../../src/indexer/quality.test.ts"],"names":[],"mappings":"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"}
@@ -2,4 +2,5 @@ export * from "./search.js";
2
2
  export * from "./rerank.js";
3
3
  export * from "./intent.js";
4
4
  export * from "./synthesize.js";
5
+ export * from "./novel-scoring.js";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC"}
@@ -2,4 +2,5 @@ export * from "./search.js";
2
2
  export * from "./rerank.js";
3
3
  export * from "./intent.js";
4
4
  export * from "./synthesize.js";
5
+ export * from "./novel-scoring.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retrieve/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Novel scoring algorithms for Mneme retrieval.
3
+ *
4
+ * These formulas are designed to outperform pure semantic similarity by
5
+ * exploiting signals that other tools (which only embed file contents)
6
+ * cannot see: time decay, regret patterns, author diversity, and the
7
+ * causal graph implicit in commit/PR/issue references.
8
+ *
9
+ * All formulas are pure functions. They take the set of search results
10
+ * + (optional) commit history and produce a re-ranked list. Use them as
11
+ * post-processors over base BM25/cosine search.
12
+ *
13
+ * Algorithms:
14
+ * 1. TDWE — Time-Decay Weighted Embedding score
15
+ * Formula: w(c) = exp(-λ · age_days / half_life)
16
+ * adjusted = base × w(c)
17
+ *
18
+ * 2. RACB — Regret-Aware Chunk Boosting
19
+ * Formula: boost(c) = 1 + ln(1 + days_to_followup × severity_factor)
20
+ * Severity: revert=3, hotfix=2, fix=1, sameFiles=0.5, none=0
21
+ *
22
+ * 3. ADS — Author Diversity Score re-ranking
23
+ * Formula: penalty = α · (same_author_above_in_ranking / K)
24
+ * final = base × (1 - penalty)
25
+ *
26
+ * 4. CGAR — Causal Graph Augmented Retrieval (light)
27
+ * Walks PR/issue links 2 hops; chunks reachable get boost = 0.85^hops
28
+ */
29
+ import type { Commit, SearchResult } from "../types.js";
30
+ export interface TdweOptions {
31
+ /** Reference time (UTC ms). Defaults to Date.now(). */
32
+ nowMs?: number;
33
+ /**
34
+ * Half-life in days. After this many days, weight decays to 0.5.
35
+ * Default 365 — a year-old commit is half as relevant as today's.
36
+ * Set higher for stable codebases, lower for fast-moving repos.
37
+ */
38
+ halfLifeDays?: number;
39
+ /**
40
+ * λ — decay coefficient. Higher = more aggressive decay.
41
+ * Default 0.693 (ln 2) so that age = halfLifeDays gives weight 0.5.
42
+ */
43
+ lambda?: number;
44
+ }
45
+ /**
46
+ * Apply time-decay weighting to a base score.
47
+ * - A commit from today gets weight ≈ 1.0
48
+ * - A commit at half-life age gets weight ≈ 0.5
49
+ * - A commit at 2× half-life age gets weight ≈ 0.25
50
+ */
51
+ export declare function timeDecayWeight(commitDateIso: string, opts?: TdweOptions): number;
52
+ export declare function applyTdwe(results: SearchResult[], opts?: TdweOptions): SearchResult[];
53
+ export type RegretKind = "revert" | "hotfix" | "fix" | "sameFiles" | "none";
54
+ export interface RegretSignal {
55
+ /** Commit hash that exhibits a regret follow-up. */
56
+ commitHash: string;
57
+ /** What kind of follow-up was detected. */
58
+ kind: RegretKind;
59
+ /** Days from commit to its follow-up. */
60
+ daysToFollowup: number;
61
+ }
62
+ export interface RacbOptions {
63
+ /**
64
+ * Maximum boost cap (multiplicative). Default 2.5 — chunks from highly
65
+ * regretted commits get up to 2.5× boost.
66
+ */
67
+ maxBoost?: number;
68
+ /** Map regret kind → severity factor used in formula. */
69
+ severityMap?: Record<RegretKind, number>;
70
+ }
71
+ /**
72
+ * Compute regret boost for a single signal.
73
+ * boost = 1 + ln(1 + days × severity)
74
+ *
75
+ * Why ln? Because the "wisdom" in a regret saturates. A 1-day-to-fix is
76
+ * very informative; a 30-day-to-fix is *more* informative but not 30×
77
+ * more. Logarithmic growth captures diminishing returns.
78
+ */
79
+ export declare function regretBoost(signal: RegretSignal, opts?: RacbOptions): number;
80
+ export declare function applyRacb(results: SearchResult[], signals: RegretSignal[], opts?: RacbOptions): SearchResult[];
81
+ export interface AdsOptions {
82
+ /** Penalty coefficient 0..1. Higher = more aggressive diversification. Default 0.4. */
83
+ alpha?: number;
84
+ /** Cap on how many results to consider. Default Infinity. */
85
+ topK?: number;
86
+ }
87
+ /**
88
+ * Re-rank results to penalize over-representation of any single author.
89
+ *
90
+ * For each result at position i:
91
+ * sameAuthorAbove = count of (j < i) where author(j) == author(i)
92
+ * penalty = α × (sameAuthorAbove / total)
93
+ * final = base × (1 - penalty)
94
+ *
95
+ * Then re-sort by final.
96
+ *
97
+ * Why this matters: when one author dominates a topic, retrieval becomes
98
+ * monocultural. Diversity surfaces the second-most-knowledgeable
99
+ * contributor, which the user often actually needs.
100
+ */
101
+ export declare function applyAds(results: SearchResult[], opts?: AdsOptions): SearchResult[];
102
+ export interface CgarOptions {
103
+ /** Max hops to walk in the causal graph. Default 2. */
104
+ maxHops?: number;
105
+ /** Decay per hop. Default 0.85 — each hop reduces boost by 15%. */
106
+ hopDecay?: number;
107
+ /** Initial boost for direct causal neighbors. Default 1.3. */
108
+ initialBoost?: number;
109
+ }
110
+ /**
111
+ * Build adjacency of "causal references" between commits using:
112
+ * - PR numbers in body / subject
113
+ * - Issue refs (closes #N, fixes #N, etc.)
114
+ * - Reverts that name a commit hash
115
+ *
116
+ * Returns a map: commitHash → Set<referenced commit hash>
117
+ */
118
+ export declare function buildCausalGraph(commits: Commit[]): Map<string, Set<string>>;
119
+ /**
120
+ * Boost search results by causal proximity to the top hit.
121
+ *
122
+ * If a chunk's commit is referenced (directly or transitively up to
123
+ * maxHops) by another result's commit, its score gets boosted.
124
+ */
125
+ export declare function applyCgar(results: SearchResult[], commits: Commit[], opts?: CgarOptions): SearchResult[];
126
+ export interface EnsembleOptions {
127
+ tdwe?: TdweOptions | false;
128
+ racb?: {
129
+ signals: RegretSignal[];
130
+ opts?: RacbOptions;
131
+ } | false;
132
+ ads?: AdsOptions | false;
133
+ cgar?: {
134
+ commits: Commit[];
135
+ opts?: CgarOptions;
136
+ } | false;
137
+ }
138
+ /**
139
+ * Apply all four novel scoring layers in sequence:
140
+ * 1. TDWE — temporal relevance
141
+ * 2. RACB — regret-derived wisdom boost
142
+ * 3. CGAR — causal graph propagation
143
+ * 4. ADS — diversity re-ranking (last, after scoring is final)
144
+ */
145
+ export declare function applyNovelScoring(results: SearchResult[], ensemble: EnsembleOptions): SearchResult[];
146
+ //# sourceMappingURL=novel-scoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"novel-scoring.d.ts","sourceRoot":"","sources":["../../src/retrieve/novel-scoring.ts"],"names":[],"mappings":"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"}