@mneme-ai/correlator 0.8.3

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 (38) hide show
  1. package/README.md +42 -0
  2. package/dist/adapters/datadog.d.ts +41 -0
  3. package/dist/adapters/datadog.d.ts.map +1 -0
  4. package/dist/adapters/datadog.js +136 -0
  5. package/dist/adapters/datadog.js.map +1 -0
  6. package/dist/adapters/github-actions.d.ts +44 -0
  7. package/dist/adapters/github-actions.d.ts.map +1 -0
  8. package/dist/adapters/github-actions.js +114 -0
  9. package/dist/adapters/github-actions.js.map +1 -0
  10. package/dist/adapters/manual.d.ts +20 -0
  11. package/dist/adapters/manual.d.ts.map +1 -0
  12. package/dist/adapters/manual.js +47 -0
  13. package/dist/adapters/manual.js.map +1 -0
  14. package/dist/adapters/manual.test.d.ts +2 -0
  15. package/dist/adapters/manual.test.d.ts.map +1 -0
  16. package/dist/adapters/manual.test.js +54 -0
  17. package/dist/adapters/manual.test.js.map +1 -0
  18. package/dist/adapters/sentry.d.ts +51 -0
  19. package/dist/adapters/sentry.d.ts.map +1 -0
  20. package/dist/adapters/sentry.js +206 -0
  21. package/dist/adapters/sentry.js.map +1 -0
  22. package/dist/adapters/sentry.test.d.ts +2 -0
  23. package/dist/adapters/sentry.test.d.ts.map +1 -0
  24. package/dist/adapters/sentry.test.js +241 -0
  25. package/dist/adapters/sentry.test.js.map +1 -0
  26. package/dist/index.d.ts +6 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +6 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/temporal.d.ts +24 -0
  31. package/dist/temporal.d.ts.map +1 -0
  32. package/dist/temporal.js +121 -0
  33. package/dist/temporal.js.map +1 -0
  34. package/dist/temporal.test.d.ts +2 -0
  35. package/dist/temporal.test.d.ts.map +1 -0
  36. package/dist/temporal.test.js +132 -0
  37. package/dist/temporal.test.js.map +1 -0
  38. package/package.json +32 -0
@@ -0,0 +1,206 @@
1
+ const DEFAULT_BASE = "https://sentry.io";
2
+ export class SentryAdapter {
3
+ source = "sentry";
4
+ base;
5
+ token;
6
+ orgSlug;
7
+ projectSlug;
8
+ concurrency;
9
+ timeoutMs;
10
+ fetchStackFrames;
11
+ maxIssues;
12
+ constructor(opts) {
13
+ if (!opts.orgSlug)
14
+ throw new Error("SentryAdapter requires orgSlug");
15
+ if (!opts.projectSlug)
16
+ throw new Error("SentryAdapter requires projectSlug");
17
+ if (!opts.apiToken)
18
+ throw new Error("SentryAdapter requires apiToken");
19
+ this.base = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/$/, "");
20
+ this.token = opts.apiToken;
21
+ this.orgSlug = opts.orgSlug;
22
+ this.projectSlug = opts.projectSlug;
23
+ this.concurrency = Math.max(1, opts.concurrency ?? 4);
24
+ this.timeoutMs = opts.timeoutMs ?? 15_000;
25
+ this.fetchStackFrames = opts.fetchStackFrames ?? true;
26
+ this.maxIssues = Math.max(1, opts.maxIssues ?? 1000);
27
+ }
28
+ async fetch(opts) {
29
+ const issues = await this.listIssues(opts);
30
+ const capped = issues.slice(0, this.maxIssues);
31
+ if (!this.fetchStackFrames)
32
+ return capped.map((i) => this.mapIssue(i));
33
+ // Hydrate stack frames concurrently.
34
+ const out = [];
35
+ const queue = [...capped];
36
+ const workers = [];
37
+ for (let w = 0; w < this.concurrency; w++) {
38
+ workers.push((async () => {
39
+ while (queue.length) {
40
+ const issue = queue.shift();
41
+ if (!issue)
42
+ break;
43
+ const frames = await this.fetchLatestEventFrames(issue.id);
44
+ out.push(this.mapIssue(issue, frames));
45
+ }
46
+ })());
47
+ }
48
+ await Promise.all(workers);
49
+ // Restore deterministic order.
50
+ out.sort((a, b) => Date.parse(a.occurredAt) - Date.parse(b.occurredAt));
51
+ return out;
52
+ }
53
+ async listIssues(opts) {
54
+ const params = new URLSearchParams();
55
+ if (opts.since && opts.until) {
56
+ params.set("start", opts.since);
57
+ params.set("end", opts.until);
58
+ }
59
+ else if (opts.since) {
60
+ params.set("statsPeriod", computeStatsPeriod(opts.since));
61
+ }
62
+ else {
63
+ params.set("statsPeriod", "30d");
64
+ }
65
+ params.set("limit", "100");
66
+ params.set("query", "is:unresolved is:resolved is:ignored sort:created");
67
+ const out = [];
68
+ let url = `${this.base}/api/0/projects/${this.orgSlug}/${this.projectSlug}/issues/?${params.toString()}`;
69
+ while (url && out.length < this.maxIssues) {
70
+ const result = await this.requestWithLink(url);
71
+ if (Array.isArray(result.json))
72
+ out.push(...result.json);
73
+ url = result.nextUrl;
74
+ }
75
+ return out;
76
+ }
77
+ mapIssue = (i, stackFrames = []) => ({
78
+ id: `sentry:${i.id}`,
79
+ source: "sentry",
80
+ externalId: i.id,
81
+ title: i.title || i.metadata?.value || i.metadata?.type || `Sentry issue ${i.id}`,
82
+ occurredAt: i.firstSeen,
83
+ resolvedAt: i.status === "resolved" ? i.lastSeen : undefined,
84
+ severity: mapLevel(i.level),
85
+ affectedFiles: dedupe([
86
+ ...(i.culprit ? extractPathFromCulprit(i.culprit) : []),
87
+ ...stackFrames.map((f) => f.file).filter(Boolean),
88
+ ]),
89
+ stackFrames: stackFrames.length ? stackFrames : undefined,
90
+ url: i.permalink,
91
+ metadata: {
92
+ project: i.project?.slug,
93
+ events: i.count,
94
+ users: i.userCount,
95
+ ...(i.metadata ?? {}),
96
+ },
97
+ });
98
+ async fetchLatestEventFrames(issueId) {
99
+ try {
100
+ const { json } = await this.requestWithLink(`${this.base}/api/0/issues/${issueId}/events/latest/`);
101
+ if (!json)
102
+ return [];
103
+ const exception = (json.entries ?? []).find((e) => e.type === "exception");
104
+ const values = exception?.data?.values ?? [];
105
+ const frames = [];
106
+ for (const v of values) {
107
+ for (const f of v.stacktrace?.frames ?? []) {
108
+ if (!f.filename)
109
+ continue;
110
+ frames.push({
111
+ file: f.filename,
112
+ line: typeof f.lineno === "number" ? f.lineno : 0,
113
+ function: f.function,
114
+ module: f.module,
115
+ });
116
+ }
117
+ }
118
+ return frames.slice(-20); // most recent frames are most informative
119
+ }
120
+ catch {
121
+ return [];
122
+ }
123
+ }
124
+ async requestWithLink(url, attempt = 0) {
125
+ const headers = {
126
+ authorization: `Bearer ${this.token}`,
127
+ accept: "application/json",
128
+ "user-agent": "mneme/0.1",
129
+ };
130
+ const controller = new AbortController();
131
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
132
+ try {
133
+ const res = await fetch(url, { headers, signal: controller.signal });
134
+ if (res.status === 404)
135
+ return { json: null, nextUrl: null };
136
+ if (res.status === 429 || res.status >= 500) {
137
+ if (attempt >= 4) {
138
+ throw new Error(`Sentry API ${res.status} after ${attempt} retries: ${url}`);
139
+ }
140
+ const retryAfter = Number(res.headers.get("retry-after") ?? 0);
141
+ const backoffMs = retryAfter > 0 ? retryAfter * 1000 : 2 ** attempt * 500;
142
+ await sleep(backoffMs);
143
+ return this.requestWithLink(url, attempt + 1);
144
+ }
145
+ if (!res.ok) {
146
+ const body = await res.text();
147
+ throw new Error(`Sentry API ${res.status}: ${body.slice(0, 200)}`);
148
+ }
149
+ const json = (await res.json());
150
+ const nextUrl = parseNextLink(res.headers.get("link"));
151
+ return { json, nextUrl };
152
+ }
153
+ finally {
154
+ clearTimeout(timeout);
155
+ }
156
+ }
157
+ }
158
+ function mapLevel(level) {
159
+ switch ((level ?? "").toLowerCase()) {
160
+ case "fatal":
161
+ return "critical";
162
+ case "error":
163
+ return "error";
164
+ case "warning":
165
+ return "warning";
166
+ case "info":
167
+ case "debug":
168
+ return "info";
169
+ default:
170
+ return "error";
171
+ }
172
+ }
173
+ /**
174
+ * Sentry's `culprit` is usually `module in function` or a path-like string.
175
+ * Pull anything that looks like a filename out of it.
176
+ */
177
+ function extractPathFromCulprit(culprit) {
178
+ const matches = culprit.match(/[\w./\\-]+\.(?:ts|tsx|js|jsx|py|go|rs|java|kt|cs|rb|php|cpp|c|h)/g);
179
+ return matches ? Array.from(new Set(matches)) : [];
180
+ }
181
+ function dedupe(arr) {
182
+ return Array.from(new Set(arr));
183
+ }
184
+ /** Parse RFC 5988 Link header for cursor-based pagination (Sentry uses this). */
185
+ function parseNextLink(header) {
186
+ if (!header)
187
+ return null;
188
+ for (const part of header.split(",")) {
189
+ const m = part.match(/<([^>]+)>;\s*rel="next";\s*results="true"/);
190
+ if (m)
191
+ return m[1];
192
+ }
193
+ return null;
194
+ }
195
+ /** "2025-09-01T00:00:00Z" → "60d" (rounded up) for Sentry's statsPeriod. */
196
+ function computeStatsPeriod(sinceIso) {
197
+ const since = Date.parse(sinceIso);
198
+ if (!Number.isFinite(since))
199
+ return "30d";
200
+ const days = Math.max(1, Math.ceil((Date.now() - since) / (24 * 60 * 60 * 1000)));
201
+ return `${days}d`;
202
+ }
203
+ function sleep(ms) {
204
+ return new Promise((r) => setTimeout(r, ms));
205
+ }
206
+ //# sourceMappingURL=sentry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry.js","sourceRoot":"","sources":["../../src/adapters/sentry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sentry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry.test.d.ts","sourceRoot":"","sources":["../../src/adapters/sentry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,241 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { SentryAdapter } from "./sentry.js";
3
+ const headers = (link) => {
4
+ const h = { "content-type": "application/json" };
5
+ if (link)
6
+ h.link = link;
7
+ return h;
8
+ };
9
+ describe("SentryAdapter — construction", () => {
10
+ it("requires orgSlug, projectSlug, apiToken", () => {
11
+ expect(() => new SentryAdapter({ orgSlug: "", projectSlug: "p", apiToken: "t" })).toThrow();
12
+ expect(() => new SentryAdapter({ orgSlug: "o", projectSlug: "", apiToken: "t" })).toThrow();
13
+ expect(() => new SentryAdapter({ orgSlug: "o", projectSlug: "p", apiToken: "" })).toThrow();
14
+ });
15
+ it("normalizes baseUrl trailing slash", () => {
16
+ const a = new SentryAdapter({
17
+ orgSlug: "o",
18
+ projectSlug: "p",
19
+ apiToken: "t",
20
+ baseUrl: "https://sentry.internal/",
21
+ });
22
+ expect(a).toBeDefined();
23
+ });
24
+ });
25
+ describe("SentryAdapter.fetch", () => {
26
+ beforeEach(() => {
27
+ vi.spyOn(globalThis, "fetch");
28
+ });
29
+ afterEach(() => {
30
+ vi.restoreAllMocks();
31
+ });
32
+ it("maps issues to Mneme Incident shape", async () => {
33
+ globalThis.fetch.mockImplementation((url) => {
34
+ if (url.includes("/issues/")) {
35
+ return Promise.resolve(new Response(JSON.stringify([
36
+ {
37
+ id: "12345",
38
+ title: "Stripe webhook 500",
39
+ level: "error",
40
+ status: "unresolved",
41
+ firstSeen: "2025-09-01T10:00:00Z",
42
+ lastSeen: "2025-09-01T11:00:00Z",
43
+ count: 47,
44
+ userCount: 12,
45
+ permalink: "https://sentry.io/organizations/foo/issues/12345/",
46
+ culprit: "handleWebhook in src/payment.ts",
47
+ project: { slug: "web" },
48
+ metadata: { type: "TypeError", value: "Cannot read property" },
49
+ },
50
+ ]), { status: 200, headers: headers() }));
51
+ }
52
+ return Promise.resolve(new Response("[]", { status: 200, headers: headers() }));
53
+ });
54
+ const a = new SentryAdapter({
55
+ orgSlug: "foo",
56
+ projectSlug: "web",
57
+ apiToken: "tok",
58
+ fetchStackFrames: false,
59
+ });
60
+ const incidents = await a.fetch({});
61
+ expect(incidents).toHaveLength(1);
62
+ const inc = incidents[0];
63
+ expect(inc.id).toBe("sentry:12345");
64
+ expect(inc.source).toBe("sentry");
65
+ expect(inc.title).toBe("Stripe webhook 500");
66
+ expect(inc.severity).toBe("error");
67
+ expect(inc.occurredAt).toBe("2025-09-01T10:00:00Z");
68
+ expect(inc.affectedFiles).toContain("src/payment.ts");
69
+ expect(inc.url).toContain("sentry.io");
70
+ });
71
+ it("maps level → severity correctly", async () => {
72
+ const issues = ["fatal", "error", "warning", "info", "unknown"].map((level, i) => ({
73
+ id: String(i),
74
+ title: `t${i}`,
75
+ level,
76
+ firstSeen: "2025-01-01T00:00:00Z",
77
+ }));
78
+ globalThis.fetch.mockResolvedValue(new Response(JSON.stringify(issues), { status: 200, headers: headers() }));
79
+ const a = new SentryAdapter({
80
+ orgSlug: "o",
81
+ projectSlug: "p",
82
+ apiToken: "t",
83
+ fetchStackFrames: false,
84
+ });
85
+ const out = await a.fetch({});
86
+ const sevById = Object.fromEntries(out.map((i) => [i.id, i.severity]));
87
+ expect(sevById["sentry:0"]).toBe("critical");
88
+ expect(sevById["sentry:1"]).toBe("error");
89
+ expect(sevById["sentry:2"]).toBe("warning");
90
+ expect(sevById["sentry:3"]).toBe("info");
91
+ expect(sevById["sentry:4"]).toBe("error"); // default
92
+ });
93
+ it("hydrates stack frames from latest event when enabled", async () => {
94
+ let callCount = 0;
95
+ globalThis.fetch.mockImplementation((url) => {
96
+ callCount++;
97
+ if (url.includes("/projects/")) {
98
+ return Promise.resolve(new Response(JSON.stringify([
99
+ {
100
+ id: "1",
101
+ title: "Crash",
102
+ level: "error",
103
+ firstSeen: "2025-01-01T00:00:00Z",
104
+ },
105
+ ]), { status: 200, headers: headers() }));
106
+ }
107
+ if (url.includes("/events/latest/")) {
108
+ return Promise.resolve(new Response(JSON.stringify({
109
+ entries: [
110
+ {
111
+ type: "exception",
112
+ data: {
113
+ values: [
114
+ {
115
+ stacktrace: {
116
+ frames: [
117
+ { filename: "src/payment.ts", lineno: 42, function: "charge" },
118
+ { filename: "src/webhook.ts", lineno: 7, function: "handle" },
119
+ ],
120
+ },
121
+ },
122
+ ],
123
+ },
124
+ },
125
+ ],
126
+ }), { status: 200, headers: headers() }));
127
+ }
128
+ return Promise.resolve(new Response("{}", { status: 200, headers: headers() }));
129
+ });
130
+ const a = new SentryAdapter({
131
+ orgSlug: "o",
132
+ projectSlug: "p",
133
+ apiToken: "t",
134
+ fetchStackFrames: true,
135
+ });
136
+ const [inc] = await a.fetch({});
137
+ expect(inc).toBeDefined();
138
+ expect(inc.stackFrames).toHaveLength(2);
139
+ expect(inc.stackFrames[0].file).toBe("src/payment.ts");
140
+ expect(inc.affectedFiles).toContain("src/payment.ts");
141
+ expect(callCount).toBeGreaterThanOrEqual(2);
142
+ });
143
+ it("does not call /events/latest/ when fetchStackFrames=false", async () => {
144
+ globalThis.fetch.mockImplementation((url) => {
145
+ if (url.includes("/projects/")) {
146
+ return Promise.resolve(new Response(JSON.stringify([
147
+ { id: "1", title: "x", level: "error", firstSeen: "2025-01-01T00:00:00Z" },
148
+ ]), { status: 200, headers: headers() }));
149
+ }
150
+ throw new Error("should not be called");
151
+ });
152
+ const a = new SentryAdapter({
153
+ orgSlug: "o",
154
+ projectSlug: "p",
155
+ apiToken: "t",
156
+ fetchStackFrames: false,
157
+ });
158
+ await a.fetch({});
159
+ const calledUrls = globalThis.fetch.mock.calls.map((c) => c[0]);
160
+ expect(calledUrls.some((u) => u.includes("/events/latest/"))).toBe(false);
161
+ });
162
+ it("follows pagination via Link header rel=next", async () => {
163
+ let page = 0;
164
+ globalThis.fetch.mockImplementation((url) => {
165
+ page++;
166
+ if (page === 1) {
167
+ return Promise.resolve(new Response(JSON.stringify([{ id: "1", title: "a", firstSeen: "2025-01-01T00:00:00Z" }]), {
168
+ status: 200,
169
+ headers: headers('<https://sentry.io/api/0/projects/o/p/issues/?cursor=abc>; rel="next"; results="true"'),
170
+ }));
171
+ }
172
+ if (page === 2) {
173
+ return Promise.resolve(new Response(JSON.stringify([{ id: "2", title: "b", firstSeen: "2025-01-02T00:00:00Z" }]), { status: 200, headers: headers() }));
174
+ }
175
+ return Promise.resolve(new Response("[]", { status: 200, headers: headers() }));
176
+ });
177
+ const a = new SentryAdapter({
178
+ orgSlug: "o",
179
+ projectSlug: "p",
180
+ apiToken: "t",
181
+ fetchStackFrames: false,
182
+ });
183
+ const out = await a.fetch({});
184
+ expect(out.length).toBe(2);
185
+ expect(page).toBe(2);
186
+ });
187
+ it("respects since/until via start/end params", async () => {
188
+ globalThis.fetch.mockResolvedValue(new Response("[]", { status: 200, headers: headers() }));
189
+ const a = new SentryAdapter({
190
+ orgSlug: "o",
191
+ projectSlug: "p",
192
+ apiToken: "t",
193
+ fetchStackFrames: false,
194
+ });
195
+ await a.fetch({ since: "2025-01-01T00:00:00Z", until: "2025-02-01T00:00:00Z" });
196
+ const url = globalThis.fetch.mock.calls[0][0];
197
+ expect(url).toContain("start=");
198
+ expect(url).toContain("end=");
199
+ });
200
+ it("sends Authorization header", async () => {
201
+ globalThis.fetch.mockResolvedValue(new Response("[]", { status: 200, headers: headers() }));
202
+ const a = new SentryAdapter({
203
+ orgSlug: "o",
204
+ projectSlug: "p",
205
+ apiToken: "secret-token",
206
+ fetchStackFrames: false,
207
+ });
208
+ await a.fetch({});
209
+ const init = globalThis.fetch.mock.calls[0][1];
210
+ expect(init.headers.authorization).toBe("Bearer secret-token");
211
+ });
212
+ it("returns empty array on 404 (project not found)", async () => {
213
+ globalThis.fetch.mockResolvedValue(new Response("", { status: 404 }));
214
+ const a = new SentryAdapter({
215
+ orgSlug: "o",
216
+ projectSlug: "p",
217
+ apiToken: "t",
218
+ fetchStackFrames: false,
219
+ });
220
+ const out = await a.fetch({});
221
+ expect(out).toEqual([]);
222
+ });
223
+ it("caps to maxIssues", async () => {
224
+ const big = Array.from({ length: 50 }, (_, i) => ({
225
+ id: String(i),
226
+ title: `t${i}`,
227
+ firstSeen: "2025-01-01T00:00:00Z",
228
+ }));
229
+ globalThis.fetch.mockResolvedValue(new Response(JSON.stringify(big), { status: 200, headers: headers() }));
230
+ const a = new SentryAdapter({
231
+ orgSlug: "o",
232
+ projectSlug: "p",
233
+ apiToken: "t",
234
+ maxIssues: 5,
235
+ fetchStackFrames: false,
236
+ });
237
+ const out = await a.fetch({});
238
+ expect(out).toHaveLength(5);
239
+ });
240
+ });
241
+ //# sourceMappingURL=sentry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry.test.js","sourceRoot":"","sources":["../../src/adapters/sentry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from "./temporal.js";
2
+ export * from "./adapters/sentry.js";
3
+ export * from "./adapters/datadog.js";
4
+ export * from "./adapters/github-actions.js";
5
+ export * from "./adapters/manual.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export * from "./temporal.js";
2
+ export * from "./adapters/sentry.js";
3
+ export * from "./adapters/datadog.js";
4
+ export * from "./adapters/github-actions.js";
5
+ export * from "./adapters/manual.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import type { Correlation, correlate as CorrelateNS } from "@mneme-ai/core";
2
+ /**
3
+ * Temporal + structural correlation engine.
4
+ *
5
+ * For each (commit, incident) pair where the incident occurred within `windowMs`
6
+ * after the commit, score by:
7
+ * 1. temporal proximity (closer = stronger)
8
+ * 2. file overlap (commit's files vs incident.affectedFiles ∪ stack frames)
9
+ *
10
+ * Semantic correlation (commit message vs stack trace embeddings) is layered on
11
+ * top by future versions — it requires an embedder, which this engine does not.
12
+ */
13
+ export declare class TemporalCorrelationEngine implements CorrelateNS.CorrelationEngine {
14
+ private readonly defaults;
15
+ readonly name = "temporal-structural-v1";
16
+ constructor(defaults?: {
17
+ windowMs?: number;
18
+ minWeight?: number;
19
+ /** Fraction of weight assigned to file-overlap (rest goes to temporal). 0..1. */
20
+ fileOverlapWeight?: number;
21
+ });
22
+ correlate(input: CorrelateNS.CorrelateInput): Promise<Correlation[]>;
23
+ }
24
+ //# sourceMappingURL=temporal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../src/temporal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,WAAW,EAEX,SAAS,IAAI,WAAW,EACzB,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;GAUG;AACH,qBAAa,yBAA0B,YAAW,WAAW,CAAC,iBAAiB;IAI3E,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAH3B,QAAQ,CAAC,IAAI,4BAA4B;gBAGtB,QAAQ,GAAE;QACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,iFAAiF;QACjF,iBAAiB,CAAC,EAAE,MAAM,CAAC;KACvB;IAGF,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,cAAc,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAkD3E"}
@@ -0,0 +1,121 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Temporal + structural correlation engine.
4
+ *
5
+ * For each (commit, incident) pair where the incident occurred within `windowMs`
6
+ * after the commit, score by:
7
+ * 1. temporal proximity (closer = stronger)
8
+ * 2. file overlap (commit's files vs incident.affectedFiles ∪ stack frames)
9
+ *
10
+ * Semantic correlation (commit message vs stack trace embeddings) is layered on
11
+ * top by future versions — it requires an embedder, which this engine does not.
12
+ */
13
+ export class TemporalCorrelationEngine {
14
+ defaults;
15
+ name = "temporal-structural-v1";
16
+ constructor(defaults = {}) {
17
+ this.defaults = defaults;
18
+ }
19
+ async correlate(input) {
20
+ const windowMs = input.windowMs ??
21
+ this.defaults.windowMs ??
22
+ // 7 days
23
+ 7 * 24 * 60 * 60 * 1000;
24
+ const minWeight = this.defaults.minWeight ?? 0.15;
25
+ const overlapWeight = clamp01(this.defaults.fileOverlapWeight ?? 0.4);
26
+ const temporalWeight = 1 - overlapWeight;
27
+ const out = [];
28
+ const sortedCommits = [...input.commits].sort(byDate);
29
+ for (const inc of input.incidents) {
30
+ const incidentTime = parseTime(inc.occurredAt);
31
+ if (!Number.isFinite(incidentTime))
32
+ continue;
33
+ const windowStart = incidentTime - windowMs;
34
+ const incidentFiles = new Set([
35
+ ...(inc.affectedFiles ?? []),
36
+ ...(inc.stackFrames?.map((f) => normalizePath(f.file)) ?? []),
37
+ ]);
38
+ for (const c of sortedCommits) {
39
+ const ct = parseTime(c.authorDate);
40
+ if (!Number.isFinite(ct))
41
+ continue;
42
+ if (ct > incidentTime)
43
+ break;
44
+ if (ct < windowStart)
45
+ continue;
46
+ const temporal = 1 - (incidentTime - ct) / windowMs;
47
+ const overlap = computeFileOverlap(c.files, incidentFiles);
48
+ // Convex combination: weight ∈ [0, 1], no saturation, monotonic in both inputs.
49
+ const weight = clamp01(temporal * temporalWeight + overlap * overlapWeight);
50
+ if (weight < minWeight)
51
+ continue;
52
+ const reason = buildReason(temporal, overlap, c, inc);
53
+ out.push({
54
+ id: hashId([c.hash, inc.id]),
55
+ fromKind: "commit",
56
+ fromId: c.hash,
57
+ toKind: "incident",
58
+ toId: inc.id,
59
+ weight: round3(weight),
60
+ reason,
61
+ evidence: [c.hash, inc.id],
62
+ });
63
+ }
64
+ }
65
+ return out;
66
+ }
67
+ }
68
+ function buildReason(temporal, overlap, c, inc) {
69
+ const parts = [];
70
+ parts.push(`Incident "${inc.title}" occurred ${humanDelta(c.authorDate, inc.occurredAt)} after commit ${c.shortHash || c.hash.slice(0, 7)}.`);
71
+ if (overlap > 0) {
72
+ parts.push(`File overlap score: ${round2(overlap)}.`);
73
+ }
74
+ else {
75
+ parts.push("No direct file overlap; correlation is purely temporal.");
76
+ }
77
+ parts.push(`Temporal score: ${round2(temporal)}.`);
78
+ return parts.join(" ");
79
+ }
80
+ function computeFileOverlap(commitFiles, incidentFiles) {
81
+ if (!commitFiles.length || !incidentFiles.size)
82
+ return 0;
83
+ const cset = new Set(commitFiles.map(normalizePath));
84
+ let hits = 0;
85
+ for (const f of cset)
86
+ if (incidentFiles.has(f))
87
+ hits++;
88
+ return hits / Math.min(cset.size, incidentFiles.size);
89
+ }
90
+ function normalizePath(p) {
91
+ return p.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
92
+ }
93
+ function parseTime(iso) {
94
+ const t = Date.parse(iso);
95
+ return Number.isFinite(t) ? t : NaN;
96
+ }
97
+ function humanDelta(fromIso, toIso) {
98
+ const ms = Math.max(0, Date.parse(toIso) - Date.parse(fromIso));
99
+ const h = ms / 3_600_000;
100
+ if (h < 1)
101
+ return `${Math.round(ms / 60_000)}m`;
102
+ if (h < 48)
103
+ return `${h.toFixed(1)}h`;
104
+ return `${(h / 24).toFixed(1)}d`;
105
+ }
106
+ function clamp01(n) {
107
+ return Math.min(1, Math.max(0, n));
108
+ }
109
+ function round3(n) {
110
+ return Math.round(n * 1000) / 1000;
111
+ }
112
+ function round2(n) {
113
+ return Math.round(n * 100) / 100;
114
+ }
115
+ function hashId(parts) {
116
+ return createHash("sha1").update(parts.join("|")).digest("hex").slice(0, 16);
117
+ }
118
+ function byDate(a, b) {
119
+ return Date.parse(a.authorDate) - Date.parse(b.authorDate);
120
+ }
121
+ //# sourceMappingURL=temporal.js.map