@merittdev/horus 0.21.7 → 0.21.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.
Binary file
package/dist/index.cjs CHANGED
@@ -3057,7 +3057,7 @@ var init_version = __esm({
3057
3057
  "../../packages/core/src/version.ts"() {
3058
3058
  "use strict";
3059
3059
  init_cjs_shims();
3060
- HORUS_VERSION = true ? "0.21.7" : "dev";
3060
+ HORUS_VERSION = true ? "0.21.8" : "dev";
3061
3061
  PINNED_SOURCE_VERSION = HORUS_VERSION;
3062
3062
  SOURCE_PIN_ENFORCED = HORUS_VERSION !== "dev";
3063
3063
  EXPECTED_INDEX_CAPABILITY = 2;
@@ -29518,7 +29518,7 @@ var require_lib = __commonJS({
29518
29518
  }
29519
29519
  return new ErrorType(`${options.context ? options.context : "Value"} ${message}.`);
29520
29520
  }
29521
- function toNumber2(value, options) {
29521
+ function toNumber3(value, options) {
29522
29522
  if (typeof value === "bigint") {
29523
29523
  throw makeException(TypeError, "is a BigInt which cannot be converted to a number", options);
29524
29524
  }
@@ -29561,7 +29561,7 @@ var require_lib = __commonJS({
29561
29561
  const twoToTheBitLength = 2 ** bitLength;
29562
29562
  const twoToOneLessThanTheBitLength = 2 ** (bitLength - 1);
29563
29563
  return (value, options = {}) => {
29564
- let x4 = toNumber2(value, options);
29564
+ let x4 = toNumber3(value, options);
29565
29565
  x4 = censorNegativeZero(x4);
29566
29566
  if (options.enforceRange) {
29567
29567
  if (!Number.isFinite(x4)) {
@@ -29601,7 +29601,7 @@ var require_lib = __commonJS({
29601
29601
  const lowerBound = unsigned ? 0 : Number.MIN_SAFE_INTEGER;
29602
29602
  const asBigIntN = unsigned ? BigInt.asUintN : BigInt.asIntN;
29603
29603
  return (value, options = {}) => {
29604
- let x4 = toNumber2(value, options);
29604
+ let x4 = toNumber3(value, options);
29605
29605
  x4 = censorNegativeZero(x4);
29606
29606
  if (options.enforceRange) {
29607
29607
  if (!Number.isFinite(x4)) {
@@ -29648,18 +29648,18 @@ var require_lib = __commonJS({
29648
29648
  exports2["long long"] = createLongLongConversion(64, { unsigned: false });
29649
29649
  exports2["unsigned long long"] = createLongLongConversion(64, { unsigned: true });
29650
29650
  exports2.double = (value, options = {}) => {
29651
- const x4 = toNumber2(value, options);
29651
+ const x4 = toNumber3(value, options);
29652
29652
  if (!Number.isFinite(x4)) {
29653
29653
  throw makeException(TypeError, "is not a finite floating-point value", options);
29654
29654
  }
29655
29655
  return x4;
29656
29656
  };
29657
29657
  exports2["unrestricted double"] = (value, options = {}) => {
29658
- const x4 = toNumber2(value, options);
29658
+ const x4 = toNumber3(value, options);
29659
29659
  return x4;
29660
29660
  };
29661
29661
  exports2.float = (value, options = {}) => {
29662
- const x4 = toNumber2(value, options);
29662
+ const x4 = toNumber3(value, options);
29663
29663
  if (!Number.isFinite(x4)) {
29664
29664
  throw makeException(TypeError, "is not a finite floating-point value", options);
29665
29665
  }
@@ -29673,7 +29673,7 @@ var require_lib = __commonJS({
29673
29673
  return y5;
29674
29674
  };
29675
29675
  exports2["unrestricted float"] = (value, options = {}) => {
29676
- const x4 = toNumber2(value, options);
29676
+ const x4 = toNumber3(value, options);
29677
29677
  if (isNaN(x4)) {
29678
29678
  return x4;
29679
29679
  }
@@ -75376,6 +75376,501 @@ function shortTs2(iso) {
75376
75376
  return `${iso.slice(5, 10)} ${iso.slice(11, 16)}`;
75377
75377
  }
75378
75378
 
75379
+ // ../../packages/connectors/src/lens/client.ts
75380
+ init_cjs_shims();
75381
+ init_src();
75382
+ var LensCloudClient = class {
75383
+ apiBaseUrl;
75384
+ token;
75385
+ workspaceId;
75386
+ http;
75387
+ constructor(opts) {
75388
+ this.apiBaseUrl = opts.apiBaseUrl.replace(/\/$/, "");
75389
+ this.token = opts.token;
75390
+ this.workspaceId = opts.workspaceId;
75391
+ this.http = opts.http ?? {};
75392
+ }
75393
+ async request(path) {
75394
+ const url = `${this.apiBaseUrl}${path}`;
75395
+ const res = await fetchWithRetry(
75396
+ url,
75397
+ {
75398
+ method: "GET",
75399
+ headers: {
75400
+ Authorization: `Bearer ${this.token}`,
75401
+ "Content-Type": "application/json"
75402
+ }
75403
+ },
75404
+ this.http
75405
+ );
75406
+ if (!res.ok) {
75407
+ const text2 = await res.text().catch(() => "");
75408
+ throw new Error(`Lens GET ${path} -> ${res.status}: ${redactUpstreamBody(text2)}`);
75409
+ }
75410
+ return res.json();
75411
+ }
75412
+ get base() {
75413
+ return `/v1/workspaces/${encodeURIComponent(this.workspaceId)}/lens`;
75414
+ }
75415
+ /**
75416
+ * List the Lens sites the workspace owns. Reports are site-scoped, so this is the
75417
+ * discovery step before `listReports`. Transport/auth failures PROPAGATE (like Sentry's
75418
+ * `listIssues`) so an outage reads as a gap, not as "no sites".
75419
+ */
75420
+ async listSites() {
75421
+ const raw = await this.request(`${this.base}/sites`);
75422
+ const sites = Array.isArray(raw) ? raw : raw?.["sites"];
75423
+ if (!Array.isArray(sites)) return [];
75424
+ return sites.map((s5) => ({
75425
+ id: typeof s5["id"] === "string" ? s5["id"] : "",
75426
+ name: typeof s5["name"] === "string" && s5["name"] !== "" ? s5["name"] : typeof s5["id"] === "string" ? s5["id"] : ""
75427
+ })).filter((s5) => s5.id !== "");
75428
+ }
75429
+ /** Build the site-reports query path with the incident-window + filter params. */
75430
+ reportsPath(siteId, opts = {}) {
75431
+ const params = new URLSearchParams();
75432
+ if (opts.from !== void 0) params.set("from", opts.from);
75433
+ if (opts.to !== void 0) params.set("to", opts.to);
75434
+ if (opts.q !== void 0 && opts.q !== "") params.set("q", opts.q);
75435
+ if (opts.status !== void 0) params.set("status", opts.status);
75436
+ params.set("limit", String(Math.max(1, Math.min(opts.limit ?? 25, 200))));
75437
+ return `${this.base}/sites/${encodeURIComponent(siteId)}/reports?${params.toString()}`;
75438
+ }
75439
+ /**
75440
+ * List reports for a site within an optional incident window. `from`/`to` are inclusive
75441
+ * `createdAt` ISO-8601 bounds; `q` is a single case-insensitive substring over the
75442
+ * comment + env URL (NOT hint fan-out — the provider relevance-ranks client-side).
75443
+ * Returns the first page; transport/auth failures PROPAGATE.
75444
+ */
75445
+ async listReports(siteId, opts = {}) {
75446
+ const raw = await this.request(this.reportsPath(siteId, opts));
75447
+ const reports = raw?.["reports"];
75448
+ if (!Array.isArray(reports)) return [];
75449
+ return reports.map((r) => parseSummary(r, siteId));
75450
+ }
75451
+ /**
75452
+ * Fetch one full report (with metadata) by id, scoped to its site. Transport/auth
75453
+ * failures PROPAGATE; the provider wraps each call so one bad report never aborts a
75454
+ * collection.
75455
+ */
75456
+ async getReport(siteId, reportId) {
75457
+ const raw = await this.request(
75458
+ `${this.base}/sites/${encodeURIComponent(siteId)}/reports/${encodeURIComponent(reportId)}`
75459
+ );
75460
+ return parseReport(raw);
75461
+ }
75462
+ /** Cheap reachability probe: `listSites` succeeds → ok. Never throws. */
75463
+ async health() {
75464
+ try {
75465
+ const sites = await this.listSites();
75466
+ return { ok: true, detail: `lens reachable (workspace ${this.workspaceId}, ${sites.length} site(s))` };
75467
+ } catch (err2) {
75468
+ return { ok: false, detail: redactErrorMessage(err2) };
75469
+ }
75470
+ }
75471
+ };
75472
+ function toNumber2(v4) {
75473
+ if (typeof v4 === "number") return Number.isFinite(v4) ? v4 : 0;
75474
+ if (typeof v4 === "string") {
75475
+ const n4 = Number(v4);
75476
+ return Number.isFinite(n4) ? n4 : 0;
75477
+ }
75478
+ return 0;
75479
+ }
75480
+ function parseSummary(raw, siteId) {
75481
+ const summary = {
75482
+ id: String(raw["id"] ?? ""),
75483
+ lensSiteId: typeof raw["lensSiteId"] === "string" ? raw["lensSiteId"] : siteId,
75484
+ status: typeof raw["status"] === "string" ? raw["status"] : "",
75485
+ createdAt: typeof raw["createdAt"] === "string" ? raw["createdAt"] : "",
75486
+ submittedAt: typeof raw["submittedAt"] === "string" ? raw["submittedAt"] : null,
75487
+ externalIssue: raw["externalIssue"] ?? null,
75488
+ lensIssueId: typeof raw["lensIssueId"] === "string" ? raw["lensIssueId"] : null,
75489
+ issueOccurrenceCount: typeof raw["issueOccurrenceCount"] === "number" ? raw["issueOccurrenceCount"] : null,
75490
+ errorCount: toNumber2(raw["errorCount"]),
75491
+ hasReplay: raw["hasReplay"] === true
75492
+ };
75493
+ if (typeof raw["comment"] === "string") summary.comment = raw["comment"];
75494
+ if (typeof raw["url"] === "string") summary.url = raw["url"];
75495
+ return summary;
75496
+ }
75497
+ function parseReport(raw) {
75498
+ const o5 = raw ?? {};
75499
+ return {
75500
+ id: String(o5["id"] ?? ""),
75501
+ status: typeof o5["status"] === "string" ? o5["status"] : "",
75502
+ createdAt: typeof o5["createdAt"] === "string" ? o5["createdAt"] : "",
75503
+ submittedAt: typeof o5["submittedAt"] === "string" ? o5["submittedAt"] : null,
75504
+ metadata: parseMetadata(o5["metadata"])
75505
+ };
75506
+ }
75507
+ function parseMetadata(raw) {
75508
+ if (raw === null || raw === void 0 || typeof raw !== "object") return null;
75509
+ const m5 = raw;
75510
+ const meta = {
75511
+ errors: parseErrors(m5["errors"]),
75512
+ networkTail: parseNetworkTail(m5["networkTail"])
75513
+ };
75514
+ if (typeof m5["comment"] === "string") meta.comment = m5["comment"];
75515
+ if (typeof m5["trigger"] === "string") meta.trigger = m5["trigger"];
75516
+ const target = m5["target"];
75517
+ if (target && typeof target === "object") {
75518
+ const t2 = target;
75519
+ const parsed = {};
75520
+ if (typeof t2["domPath"] === "string") parsed.domPath = t2["domPath"];
75521
+ if (typeof t2["tagName"] === "string") parsed.tagName = t2["tagName"];
75522
+ if (typeof t2["text"] === "string") parsed.text = t2["text"];
75523
+ if (Array.isArray(t2["reactComponents"])) {
75524
+ parsed.reactComponents = t2["reactComponents"].filter(
75525
+ (c4) => typeof c4 === "string"
75526
+ );
75527
+ }
75528
+ meta.target = parsed;
75529
+ }
75530
+ const env = m5["env"];
75531
+ if (env && typeof env === "object") {
75532
+ const e = env;
75533
+ const parsed = { url: typeof e["url"] === "string" ? e["url"] : "" };
75534
+ if (typeof e["route"] === "string") parsed.route = e["route"];
75535
+ if (typeof e["userAgent"] === "string") parsed.userAgent = e["userAgent"];
75536
+ if (typeof e["browser"] === "string") parsed.browser = e["browser"];
75537
+ if (typeof e["os"] === "string") parsed.os = e["os"];
75538
+ meta.env = parsed;
75539
+ }
75540
+ const app = m5["app"];
75541
+ if (app && typeof app === "object") {
75542
+ const a2 = app;
75543
+ const parsed = {};
75544
+ if (typeof a2["release"] === "string") parsed.release = a2["release"];
75545
+ if (typeof a2["gitSha"] === "string") parsed.gitSha = a2["gitSha"];
75546
+ if (typeof a2["environment"] === "string") parsed.environment = a2["environment"];
75547
+ if (a2["featureFlags"] && typeof a2["featureFlags"] === "object") {
75548
+ parsed.featureFlags = a2["featureFlags"];
75549
+ }
75550
+ meta.app = parsed;
75551
+ }
75552
+ return meta;
75553
+ }
75554
+ function parseErrors(raw) {
75555
+ if (!Array.isArray(raw)) return [];
75556
+ return raw.filter((e) => typeof e["message"] === "string").map((e) => {
75557
+ const err2 = {
75558
+ message: e["message"],
75559
+ source: typeof e["source"] === "string" ? e["source"] : "",
75560
+ timestamp: toNumber2(e["timestamp"])
75561
+ };
75562
+ if (typeof e["stack"] === "string") err2.stack = e["stack"];
75563
+ return err2;
75564
+ });
75565
+ }
75566
+ function parseNetworkTail(raw) {
75567
+ if (!Array.isArray(raw)) return [];
75568
+ return raw.map((n4) => {
75569
+ const entry = {
75570
+ method: typeof n4["method"] === "string" ? n4["method"] : "",
75571
+ url: typeof n4["url"] === "string" ? n4["url"] : "",
75572
+ status: typeof n4["status"] === "number" ? n4["status"] : null,
75573
+ ok: typeof n4["ok"] === "boolean" ? n4["ok"] : null,
75574
+ durationMs: typeof n4["durationMs"] === "number" ? n4["durationMs"] : null,
75575
+ requestType: typeof n4["requestType"] === "string" ? n4["requestType"] : "",
75576
+ startedAt: toNumber2(n4["startedAt"])
75577
+ };
75578
+ if (typeof n4["error"] === "string") entry.error = n4["error"];
75579
+ return entry;
75580
+ });
75581
+ }
75582
+
75583
+ // ../../packages/connectors/src/lens/provider.ts
75584
+ init_cjs_shims();
75585
+ init_src();
75586
+ var LensProvider = class {
75587
+ constructor(client, opts = {}) {
75588
+ this.client = client;
75589
+ this.opts = opts;
75590
+ }
75591
+ client;
75592
+ opts;
75593
+ id = "lens";
75594
+ kind = "logs";
75595
+ sitesCache = null;
75596
+ get limit() {
75597
+ return this.opts.limit ?? 25;
75598
+ }
75599
+ get statusFilter() {
75600
+ return this.opts.statusFilter ?? "submitted";
75601
+ }
75602
+ /**
75603
+ * Collect recent reports across every site within the window and resolve each one's
75604
+ * top frame + failing requests. Reports are low-volume, so we fetch the full metadata
75605
+ * for up to `limit` (relevance-ranked) reports. A failing site LIST or reports LIST
75606
+ * PROPAGATES so the engine records a gap; per-report metadata fetches are best-effort
75607
+ * (a report still becomes a signal from its summary alone). Use queryEvidence() for the
75608
+ * degrade-to-[] contract.
75609
+ */
75610
+ async collect(opts = {}) {
75611
+ const siteIds = await this.sites();
75612
+ const hintTerms = opts.hintTerms ?? [];
75613
+ const summaries = [];
75614
+ for (const siteId of siteIds) {
75615
+ const page = await this.client.listReports(siteId, {
75616
+ ...opts.from !== void 0 ? { from: opts.from } : {},
75617
+ ...opts.to !== void 0 ? { to: opts.to } : {},
75618
+ status: this.statusFilter,
75619
+ limit: this.limit
75620
+ });
75621
+ summaries.push(...page);
75622
+ }
75623
+ const windowed = summaries.filter((s5) => inWindow(s5.createdAt, opts.from, opts.to));
75624
+ const ranked = rankSummaries(windowed, hintTerms).slice(0, this.limit);
75625
+ const reports = await Promise.all(
75626
+ ranked.map(
75627
+ (s5) => this.client.getReport(s5.lensSiteId, s5.id).catch(() => null)
75628
+ )
75629
+ );
75630
+ return ranked.map((summary, i3) => buildSignal(summary, reports[i3] ?? null));
75631
+ }
75632
+ /**
75633
+ * Synthesize Evidence from Lens reports. One `kind: 'log'` Evidence per report, with the
75634
+ * comment as the human signature, the captured error + failing requests + app context in
75635
+ * the payload, and the top frame surfaced as `filePath`/`symbolName`/`lineStart` (payload)
75636
+ * and `file`/`line` (links) so the engine can seed directly on it.
75637
+ */
75638
+ toEvidence(signals, hintTerms, collectedAt) {
75639
+ const query = `lens reports (status:${this.statusFilter}, up to ${this.limit})`;
75640
+ return signals.map((sig, i3) => signalToEvidence(sig, hintTerms, query, collectedAt, i3));
75641
+ }
75642
+ /**
75643
+ * One-shot evidence query: collect reports, convert to redacted Evidence. Preferred entry
75644
+ * point for the investigation engine. Degrades to [] on any failure so a flaky Cloud never
75645
+ * aborts an investigation.
75646
+ */
75647
+ async queryEvidence(opts = {}) {
75648
+ try {
75649
+ const hintTerms = opts.hintTerms ?? [];
75650
+ const signals = await this.collect({
75651
+ ...opts.from !== void 0 ? { from: opts.from } : {},
75652
+ ...opts.to !== void 0 ? { to: opts.to } : {},
75653
+ hintTerms
75654
+ });
75655
+ return this.toEvidence(signals, hintTerms, opts.collectedAt ?? (/* @__PURE__ */ new Date()).toISOString());
75656
+ } catch {
75657
+ return [];
75658
+ }
75659
+ }
75660
+ async health() {
75661
+ return this.client.health();
75662
+ }
75663
+ /** In-instance site-id cache — reports are site-scoped and the site list is stable. */
75664
+ async sites() {
75665
+ if (this.sitesCache !== null) return this.sitesCache;
75666
+ const sites = await this.client.listSites();
75667
+ this.sitesCache = sites.map((s5) => s5.id);
75668
+ return this.sitesCache;
75669
+ }
75670
+ };
75671
+ function buildSignal(summary, report) {
75672
+ const meta = report?.metadata ?? null;
75673
+ const errors2 = meta?.errors ?? [];
75674
+ const { frame, message } = topFrameFromErrors(errors2);
75675
+ const signal = {
75676
+ id: summary.id,
75677
+ lensSiteId: summary.lensSiteId,
75678
+ status: summary.status,
75679
+ createdAt: summary.createdAt,
75680
+ submittedAt: summary.submittedAt,
75681
+ issueOccurrenceCount: summary.issueOccurrenceCount,
75682
+ errorCount: summary.errorCount || errors2.length,
75683
+ hasReplay: summary.hasReplay,
75684
+ topFrame: frame,
75685
+ failingRequests: failingRequestsFrom(meta?.networkTail ?? [])
75686
+ };
75687
+ const comment = meta?.comment ?? summary.comment;
75688
+ if (comment !== void 0) signal.comment = comment;
75689
+ const route2 = meta?.env?.route;
75690
+ if (route2 !== void 0) signal.route = route2;
75691
+ const url = meta?.env?.url && meta.env.url !== "" ? meta.env.url : summary.url;
75692
+ if (url !== void 0) signal.url = url;
75693
+ if (meta?.app?.release !== void 0) signal.release = meta.app.release;
75694
+ if (meta?.app?.gitSha !== void 0) signal.gitSha = meta.app.gitSha;
75695
+ if (meta?.app?.environment !== void 0) signal.environment = meta.app.environment;
75696
+ if (meta?.app?.featureFlags !== void 0) signal.featureFlags = meta.app.featureFlags;
75697
+ if (meta?.trigger !== void 0) signal.trigger = meta.trigger;
75698
+ if (message !== void 0) signal.topErrorMessage = message;
75699
+ const react = meta?.target?.reactComponents;
75700
+ if (react !== void 0 && react.length > 0) signal.reactComponents = react;
75701
+ return signal;
75702
+ }
75703
+ function failingRequestsFrom(tail) {
75704
+ return tail.filter((n4) => n4.ok === false || typeof n4.status === "number" && n4.status >= 500).slice(0, 5).map((n4) => ({ method: n4.method, url: n4.url, status: n4.status }));
75705
+ }
75706
+ function topFrameFromErrors(errors2) {
75707
+ let firstMessage;
75708
+ for (const err2 of errors2) {
75709
+ if (firstMessage === void 0 && err2.message) firstMessage = err2.message;
75710
+ if (!err2.stack) continue;
75711
+ const frame = parseStackTopFrame(err2.stack);
75712
+ if (frame) return { frame, message: err2.message || firstMessage };
75713
+ }
75714
+ return firstMessage !== void 0 ? { frame: null, message: firstMessage } : { frame: null };
75715
+ }
75716
+ function isSkippableLocation(location2) {
75717
+ if (location2 === "") return true;
75718
+ if (location2.includes("node_modules")) return true;
75719
+ if (/^[a-z-]+-extension:\/\//i.test(location2)) return true;
75720
+ if (location2.includes("[native code]") || location2.includes("<anonymous>")) return true;
75721
+ return false;
75722
+ }
75723
+ function parseStackTopFrame(stack) {
75724
+ const lines = stack.split("\n");
75725
+ for (const raw of lines) {
75726
+ const line2 = raw.trim();
75727
+ if (line2 === "") continue;
75728
+ let fnName;
75729
+ let location2;
75730
+ if (line2.startsWith("at ")) {
75731
+ const body2 = line2.slice(3).trim();
75732
+ const parenMatch = /^(.*?)\s*\((.+)\)\s*$/.exec(body2);
75733
+ if (parenMatch) {
75734
+ fnName = parenMatch[1]?.trim() || void 0;
75735
+ location2 = parenMatch[2].trim();
75736
+ } else {
75737
+ location2 = body2;
75738
+ }
75739
+ } else if (line2.includes("@")) {
75740
+ const at4 = line2.lastIndexOf("@");
75741
+ fnName = line2.slice(0, at4).trim() || void 0;
75742
+ location2 = line2.slice(at4 + 1).trim();
75743
+ } else {
75744
+ continue;
75745
+ }
75746
+ if (location2 === void 0 || isSkippableLocation(location2)) continue;
75747
+ const parsed = parseLocation(location2);
75748
+ if (!parsed) continue;
75749
+ if (isSkippableLocation(parsed.filename)) continue;
75750
+ const frame = { filename: parsed.filename };
75751
+ if (fnName !== void 0 && fnName !== "") frame.function = fnName;
75752
+ if (parsed.lineno !== void 0) frame.lineno = parsed.lineno;
75753
+ return frame;
75754
+ }
75755
+ return null;
75756
+ }
75757
+ function parseLocation(location2) {
75758
+ const m5 = /^(.*?):(\d+)(?::(\d+))?$/.exec(location2);
75759
+ const rawUrl = m5 ? m5[1] : location2;
75760
+ const lineno = m5 ? Number(m5[2]) : void 0;
75761
+ let filename = rawUrl;
75762
+ try {
75763
+ if (/^[a-z]+:\/\//i.test(rawUrl)) filename = new URL(rawUrl).pathname;
75764
+ } catch {
75765
+ filename = rawUrl;
75766
+ }
75767
+ if (filename === "") return null;
75768
+ return lineno !== void 0 && Number.isFinite(lineno) ? { filename, lineno } : { filename };
75769
+ }
75770
+ function signalToEvidence(sig, hintTerms, query, collectedAt, index2) {
75771
+ const frame = sig.topFrame;
75772
+ const title = buildTitle2(sig);
75773
+ const relevance = computeRelevance2(sig, hintTerms);
75774
+ const payload = {
75775
+ source: "lens",
75776
+ reportId: sig.id,
75777
+ ...sig.comment !== void 0 ? { comment: redactSecrets(sig.comment) } : {},
75778
+ ...sig.route !== void 0 ? { route: sig.route } : {},
75779
+ ...sig.url !== void 0 ? { url: sig.url } : {},
75780
+ ...sig.release !== void 0 ? { release: sig.release } : {},
75781
+ ...sig.gitSha !== void 0 ? { gitSha: sig.gitSha } : {},
75782
+ ...sig.trigger !== void 0 ? { trigger: sig.trigger } : {},
75783
+ errorCount: sig.errorCount,
75784
+ ...sig.topErrorMessage !== void 0 ? { topError: redactSecrets(sig.topErrorMessage) } : {},
75785
+ ...sig.failingRequests.length > 0 ? { failingRequests: sig.failingRequests } : {},
75786
+ ...sig.reactComponents !== void 0 ? { reactComponents: sig.reactComponents } : {},
75787
+ ...sig.featureFlags !== void 0 ? { featureFlags: sig.featureFlags } : {}
75788
+ };
75789
+ if (frame?.filename !== void 0) payload["filePath"] = frame.filename;
75790
+ if (frame?.function !== void 0) payload["symbolName"] = frame.function;
75791
+ if (frame?.lineno !== void 0) payload["lineStart"] = frame.lineno;
75792
+ const links = {};
75793
+ if (frame?.filename !== void 0) links.file = frame.filename;
75794
+ if (frame?.lineno !== void 0) links.line = frame.lineno;
75795
+ const ev = {
75796
+ id: `ev_lens_${index2}`,
75797
+ source: "logs",
75798
+ kind: "log",
75799
+ title,
75800
+ relevance,
75801
+ payload,
75802
+ links,
75803
+ provenance: { query, collectedAt }
75804
+ };
75805
+ const ts = sig.submittedAt ?? sig.createdAt;
75806
+ if (ts) ev.timestamp = ts;
75807
+ return ev;
75808
+ }
75809
+ function buildTitle2(sig) {
75810
+ const rawComment = sig.comment ? redactSecrets(sig.comment) : "";
75811
+ const comment = rawComment !== "" ? rawComment.slice(0, 80) : "(no comment)";
75812
+ const errs = `${sig.errorCount} error(s)`;
75813
+ const where = sig.route ?? sig.url ?? "";
75814
+ const whereStr = where !== "" ? ` \xB7 ${where}` : "";
75815
+ const when = sig.createdAt ? ` \xB7 ${shortTs3(sig.createdAt)}` : "";
75816
+ return `Lens report: "${comment}" \xB7 ${errs}${whereStr}${when}`.slice(0, 220);
75817
+ }
75818
+ function computeRelevance2(sig, hintTerms, now = Date.now()) {
75819
+ let score = 0.6;
75820
+ const hay = [
75821
+ sig.comment ?? "",
75822
+ sig.route ?? "",
75823
+ sig.url ?? "",
75824
+ sig.topErrorMessage ?? "",
75825
+ sig.topFrame?.filename ?? "",
75826
+ sig.topFrame?.function ?? ""
75827
+ ].join(" ").toLowerCase();
75828
+ const domainTerms = hintTerms.filter((t2) => t2.length > 2);
75829
+ if (domainTerms.some((t2) => hay.includes(t2.toLowerCase()))) score += 0.2;
75830
+ const ts = sig.submittedAt ?? sig.createdAt;
75831
+ if (ts) {
75832
+ const ageMs = now - Date.parse(ts);
75833
+ if (Number.isFinite(ageMs)) {
75834
+ if (ageMs <= 864e5) score += 0.1;
75835
+ else if (ageMs <= 7 * 864e5) score += 0.05;
75836
+ }
75837
+ }
75838
+ if ((sig.issueOccurrenceCount ?? 0) >= 3) score += 0.05;
75839
+ return Math.min(0.95, Math.max(0.5, score));
75840
+ }
75841
+ function rankSummaries(summaries, hintTerms, now = Date.now()) {
75842
+ const domainTerms = hintTerms.filter((t2) => t2.length > 2);
75843
+ const score = (s5) => {
75844
+ let v4 = 0;
75845
+ const hay = `${s5.comment ?? ""} ${s5.url ?? ""}`.toLowerCase();
75846
+ if (domainTerms.some((t2) => hay.includes(t2.toLowerCase()))) v4 += 2;
75847
+ if (s5.createdAt) {
75848
+ const ageMs = now - Date.parse(s5.createdAt);
75849
+ if (Number.isFinite(ageMs) && ageMs <= 864e5) v4 += 1;
75850
+ }
75851
+ if ((s5.issueOccurrenceCount ?? 0) >= 3) v4 += 0.5;
75852
+ return v4;
75853
+ };
75854
+ return [...summaries].sort((a2, b5) => {
75855
+ const d3 = score(b5) - score(a2);
75856
+ if (d3 !== 0) return d3;
75857
+ return (Date.parse(b5.createdAt) || 0) - (Date.parse(a2.createdAt) || 0);
75858
+ });
75859
+ }
75860
+ function inWindow(createdAt, from, to) {
75861
+ const t2 = Date.parse(createdAt);
75862
+ if (!Number.isFinite(t2)) return true;
75863
+ const lo = from !== void 0 ? Date.parse(from) : NaN;
75864
+ const hi = to !== void 0 ? Date.parse(to) : NaN;
75865
+ if (Number.isFinite(lo) && t2 < lo) return false;
75866
+ if (Number.isFinite(hi) && t2 > hi) return false;
75867
+ return true;
75868
+ }
75869
+ function shortTs3(iso) {
75870
+ if (!iso || iso.length < 16) return iso || "\u2014";
75871
+ return `${iso.slice(5, 10)} ${iso.slice(11, 16)}`;
75872
+ }
75873
+
75379
75874
  // ../../packages/connectors/src/axiom/client.ts
75380
75875
  init_cjs_shims();
75381
75876
  init_src();
@@ -75629,8 +76124,8 @@ var AxiomProvider = class {
75629
76124
  return this.client.health();
75630
76125
  }
75631
76126
  recordToEvidence(rec, hintTerms, query, collectedAt, index2) {
75632
- const title = buildTitle2(rec, this.opts.dataset);
75633
- const relevance = computeRelevance2(rec, hintTerms);
76127
+ const title = buildTitle3(rec, this.opts.dataset);
76128
+ const relevance = computeRelevance3(rec, hintTerms);
75634
76129
  const payload = {
75635
76130
  source: "axiom",
75636
76131
  dataset: this.opts.dataset,
@@ -75660,7 +76155,7 @@ function pickField2(fields, candidates) {
75660
76155
  }
75661
76156
  return void 0;
75662
76157
  }
75663
- function buildTitle2(rec, dataset) {
76158
+ function buildTitle3(rec, dataset) {
75664
76159
  const msg = pickField2(rec.fields, MESSAGE_FIELDS);
75665
76160
  const level = pickField2(rec.fields, LEVEL_FIELDS);
75666
76161
  const lvl = level ? `[${level}] ` : "";
@@ -75668,13 +76163,13 @@ function buildTitle2(rec, dataset) {
75668
76163
  const rawCount = rec.fields["count"];
75669
76164
  const count = typeof rawCount === "number" && Number.isFinite(rawCount) && rawCount > 1 ? rawCount : void 0;
75670
76165
  if (count !== void 0) {
75671
- const latest = rec.timestamp ? ` (latest ${shortTs3(rec.timestamp)})` : "";
76166
+ const latest = rec.timestamp ? ` (latest ${shortTs4(rec.timestamp)})` : "";
75672
76167
  return `Axiom ${dataset}: ${lvl}${body2} \xD7${count.toLocaleString("en-US")}${latest}`.slice(0, 220);
75673
76168
  }
75674
- const ts = rec.timestamp ? ` \xB7 ${shortTs3(rec.timestamp)}` : "";
76169
+ const ts = rec.timestamp ? ` \xB7 ${shortTs4(rec.timestamp)}` : "";
75675
76170
  return `Axiom ${dataset}: ${lvl}${body2}${ts}`.slice(0, 220);
75676
76171
  }
75677
- function computeRelevance2(rec, hintTerms, now = Date.now()) {
76172
+ function computeRelevance3(rec, hintTerms, now = Date.now()) {
75678
76173
  let score = 0.6;
75679
76174
  const hay = JSON.stringify(rec.fields).toLowerCase();
75680
76175
  const domainTerms = hintTerms.filter((t2) => t2.length > 2);
@@ -75693,7 +76188,7 @@ function computeRelevance2(rec, hintTerms, now = Date.now()) {
75693
76188
  if (typeof count === "number" && count >= 10) score += 0.1;
75694
76189
  return Math.min(0.95, Math.max(0.5, score));
75695
76190
  }
75696
- function shortTs3(iso) {
76191
+ function shortTs4(iso) {
75697
76192
  if (!iso || iso.length < 16) return iso || "\u2014";
75698
76193
  return `${iso.slice(5, 10)} ${iso.slice(11, 16)}`;
75699
76194
  }
@@ -75942,8 +76437,8 @@ var ShopifyProvider = class {
75942
76437
  id: `ev_shopify_${index2}`,
75943
76438
  source: KIND_TO_SOURCE[rec.kind],
75944
76439
  kind: rec.kind,
75945
- title: buildTitle3(rec, this.opts.store),
75946
- relevance: computeRelevance3(rec, hintTerms),
76440
+ title: buildTitle4(rec, this.opts.store),
76441
+ relevance: computeRelevance4(rec, hintTerms),
75947
76442
  payload,
75948
76443
  links: {},
75949
76444
  provenance: { query: `shopify ${this.opts.store} ${rec.name}`, collectedAt }
@@ -75960,8 +76455,8 @@ function bindWindowVars(q2, from, to) {
75960
76455
  if (to !== void 0) window2["to"] = to;
75961
76456
  return { ...base2, ...window2 };
75962
76457
  }
75963
- function buildTitle3(rec, store) {
75964
- const ts = rec.timestamp ? ` \xB7 ${shortTs4(rec.timestamp)}` : "";
76458
+ function buildTitle4(rec, store) {
76459
+ const ts = rec.timestamp ? ` \xB7 ${shortTs5(rec.timestamp)}` : "";
75965
76460
  return redactSecrets(`Shopify ${store} ${rec.name}: ${summarize2(rec)}${ts}`).slice(0, 220);
75966
76461
  }
75967
76462
  function summarize2(rec) {
@@ -75988,14 +76483,14 @@ function countResults(data) {
75988
76483
  }
75989
76484
  return found ? total : null;
75990
76485
  }
75991
- function computeRelevance3(rec, hintTerms) {
76486
+ function computeRelevance4(rec, hintTerms) {
75992
76487
  let score = rec.relevance ?? 0.6;
75993
76488
  const hay = JSON.stringify(rec.data ?? {}).toLowerCase();
75994
76489
  const domainTerms = hintTerms.filter((t2) => t2.length > 2);
75995
76490
  if (domainTerms.some((t2) => hay.includes(t2.toLowerCase()))) score += 0.2;
75996
76491
  return Math.min(0.95, Math.max(0.5, score));
75997
76492
  }
75998
- function shortTs4(iso) {
76493
+ function shortTs5(iso) {
75999
76494
  if (!iso || iso.length < 16) return iso || "\u2014";
76000
76495
  return `${iso.slice(5, 10)} ${iso.slice(11, 16)}`;
76001
76496
  }
@@ -76714,6 +77209,15 @@ function shopifyForEnv(renv) {
76714
77209
  }
76715
77210
  );
76716
77211
  }
77212
+ function lensForCloud(opts) {
77213
+ return new LensProvider(
77214
+ new LensCloudClient({
77215
+ apiBaseUrl: opts.apiBaseUrl,
77216
+ token: opts.token,
77217
+ workspaceId: opts.workspaceId
77218
+ })
77219
+ );
77220
+ }
76717
77221
  function codeForRepo(config2, repoName) {
76718
77222
  const renv = resolveEnvironment(config2, { project: repoName });
76719
77223
  const c4 = codeForEnv(renv);
@@ -77096,6 +77600,9 @@ async function redisServerStatus(renv) {
77096
77600
  // ../../packages/connectors/src/shopify/index.ts
77097
77601
  init_cjs_shims();
77098
77602
 
77603
+ // ../../packages/connectors/src/lens/index.ts
77604
+ init_cjs_shims();
77605
+
77099
77606
  // ../../packages/cli/src/lib/bundled-backend.ts
77100
77607
  init_cjs_shims();
77101
77608
  var import_node_fs5 = require("fs");
@@ -94233,6 +94740,7 @@ init_cjs_shims();
94233
94740
 
94234
94741
  // ../../packages/engine/src/engine.ts
94235
94742
  init_cjs_shims();
94743
+ init_src();
94236
94744
 
94237
94745
  // ../../packages/engine/src/render.ts
94238
94746
  init_cjs_shims();
@@ -97328,7 +97836,7 @@ async function captureInvestigationMemory(store, investigationId, report, report
97328
97836
  init_cjs_shims();
97329
97837
  function hasAnyRuntimeConnector(c4) {
97330
97838
  return Boolean(
97331
- c4.elasticsearch || c4.sentry || c4.axiom || c4.grafana || c4.mongodb || c4.postgres || c4.redis || c4.queue || c4.shopify
97839
+ c4.elasticsearch || c4.sentry || c4.lens || c4.axiom || c4.grafana || c4.mongodb || c4.postgres || c4.redis || c4.queue || c4.shopify
97332
97840
  );
97333
97841
  }
97334
97842
  function isUnavailable(c4, kind) {
@@ -100055,6 +100563,88 @@ async function investigate(input, deps) {
100055
100563
  sentryFailureReason = connectorFailureReason(sentryErr);
100056
100564
  }
100057
100565
  }
100566
+ let lensCollected = false;
100567
+ let lensFailureReason;
100568
+ let lensReportCount = 0;
100569
+ if (deps.lens) {
100570
+ try {
100571
+ const from = logWindowFrom(input.logsSince ?? input.since);
100572
+ const to = (/* @__PURE__ */ new Date()).toISOString();
100573
+ const lensSeedBase = top ? top.filePath.split("/").pop() ?? "" : "";
100574
+ const lensTerms = top ? [.../* @__PURE__ */ new Set([...tokenize(hint), ...tokenize(top.name), ...tokenize(lensSeedBase)])] : [...new Set(tokenize(hint))];
100575
+ const seedFileBase = top ? (top.filePath.split("/").pop() ?? "").toLowerCase() : "";
100576
+ const signals = await deps.lens.collect({ from, to, hintTerms: lensTerms });
100577
+ lensCollected = true;
100578
+ lensReportCount = signals.length;
100579
+ for (const sig of signals.slice(0, 10)) {
100580
+ const frame = sig.topFrame;
100581
+ const sigKey = [sig.comment ?? "", sig.topErrorMessage ?? "", frame?.function ?? "", frame?.filename ?? ""].filter(Boolean).join(" ");
100582
+ const frameFileBase = (frame?.filename ?? "").split("/").pop()?.toLowerCase() ?? "";
100583
+ const frameMatchesSeed = seedFileBase.length > 0 && frameFileBase.length > 0 && (frameFileBase === seedFileBase || frameFileBase.includes(seedFileBase) || seedFileBase.includes(frameFileBase));
100584
+ const { relevanceClass: classified, relevanceReason } = classifyLogRelevance(
100585
+ sigKey,
100586
+ frame?.filename ? [frame.filename] : [],
100587
+ lensTerms,
100588
+ input.service
100589
+ );
100590
+ const relevanceClass = frameMatchesSeed ? "direct" : classified;
100591
+ const reason = frameMatchesSeed ? `Lens report frame at seed file ${frame?.filename ?? ""}` : relevanceReason;
100592
+ const commentText = sig.comment ? redactSecrets(sig.comment).slice(0, 80) : "(no comment)";
100593
+ const where = sig.route ?? sig.url ?? "";
100594
+ const whereStr = where !== "" ? ` \xB7 ${where}` : "";
100595
+ const frameLoc = frame?.filename ? ` @ ${frame.filename}${frame.lineno !== void 0 ? `:${frame.lineno}` : ""}` : "";
100596
+ const tag = relevanceClass === "ambient" ? " [ambient]" : "";
100597
+ const title = `Lens report: "${commentText}" \xB7 ${sig.errorCount} error(s)${whereStr} (${shortTs(sig.createdAt)})${frameLoc}${tag}`.slice(
100598
+ 0,
100599
+ 220
100600
+ );
100601
+ const links = {};
100602
+ if (frame?.filename !== void 0) links.file = frame.filename;
100603
+ if (frame?.lineno !== void 0) links.line = frame.lineno;
100604
+ const ev = mkEv(
100605
+ "log",
100606
+ title,
100607
+ {
100608
+ source: "lens",
100609
+ reportId: sig.id,
100610
+ comment: sig.comment ? redactSecrets(sig.comment) : null,
100611
+ route: sig.route ?? null,
100612
+ url: sig.url ?? null,
100613
+ release: sig.release ?? null,
100614
+ gitSha: sig.gitSha ?? null,
100615
+ trigger: sig.trigger ?? null,
100616
+ errorCount: sig.errorCount,
100617
+ topError: sig.topErrorMessage ? redactSecrets(sig.topErrorMessage) : null,
100618
+ // Direct code seed — same fields the engine reads off a code symbol.
100619
+ filePath: frame?.filename ?? null,
100620
+ symbolName: frame?.function ?? null,
100621
+ lineStart: frame?.lineno ?? null,
100622
+ failingRequests: sig.failingRequests,
100623
+ reactComponents: sig.reactComponents ?? null,
100624
+ featureFlags: sig.featureFlags ?? null,
100625
+ relevanceClass,
100626
+ relevanceReason: reason
100627
+ },
100628
+ links,
100629
+ sig.submittedAt ?? sig.createdAt ?? void 0,
100630
+ // Direct: full report weight, boosted when the raise-site frame is resolved.
100631
+ // Ambient: demoted so unrelated reports don't inflate confidence.
100632
+ relevanceClass === "direct" ? frame?.filename ? 0.95 : 0.9 : 0.45
100633
+ );
100634
+ logEvIds.push(ev.id);
100635
+ if (relevanceClass === "direct") {
100636
+ directLogEvIds.push(ev.id);
100637
+ const sigLabel = sig.comment && sig.comment.trim() !== "" ? redactSecrets(sig.comment).slice(0, 120) : sig.topErrorMessage ? redactSecrets(sig.topErrorMessage) : `Lens report ${sig.id}`;
100638
+ directSignatures.push({ key: sigLabel, count: Math.max(1, sig.errorCount), message: sigLabel });
100639
+ } else {
100640
+ ambientLogEvIds.push(ev.id);
100641
+ }
100642
+ }
100643
+ } catch (lensErr) {
100644
+ lensCollected = false;
100645
+ lensFailureReason = connectorFailureReason(lensErr);
100646
+ }
100647
+ }
100058
100648
  let axiomCollected = false;
100059
100649
  let axiomFailureReason;
100060
100650
  if (deps.axiom) {
@@ -101080,6 +101670,7 @@ async function investigate(input, deps) {
101080
101670
  ["mongodb", deps.connectors.mongodb, deps.mongo],
101081
101671
  ["postgres", deps.connectors.postgres, deps.postgres],
101082
101672
  ["sentry", deps.connectors.sentry, deps.sentry],
101673
+ ["lens", deps.connectors.lens, deps.lens],
101083
101674
  ["axiom", deps.connectors.axiom, deps.axiom],
101084
101675
  ["shopify", deps.connectors.shopify, deps.shopify],
101085
101676
  ["redis", deps.connectors.redis, deps.redisState]
@@ -101095,6 +101686,8 @@ async function investigate(input, deps) {
101095
101686
  // `unavailable` instead of silently flipping to "not configured".
101096
101687
  sentryCollected,
101097
101688
  sentryFailureReason,
101689
+ lensCollected,
101690
+ lensFailureReason,
101098
101691
  axiomCollected,
101099
101692
  axiomFailureReason,
101100
101693
  shopifyCollected,
@@ -101117,6 +101710,9 @@ async function investigate(input, deps) {
101117
101710
  sentry: deps.sentry != null,
101118
101711
  sentryCollected,
101119
101712
  sentryFailureReason,
101713
+ lens: deps.lens != null,
101714
+ lensCollected,
101715
+ lensFailureReason,
101120
101716
  axiom: deps.axiom != null,
101121
101717
  axiomCollected,
101122
101718
  axiomFailureReason,
@@ -104451,8 +105047,8 @@ function buildLowerPriority(report, seedLowConfidence, effectiveTopLower) {
104451
105047
  });
104452
105048
  const noEvidence = !c4.implicated && !hasRuntimeEvidence;
104453
105049
  if (!noEvidence) continue;
104454
- const inWindow = [...c4.files].some((f5) => changedFiles.has(f5));
104455
- if (inWindow) continue;
105050
+ const inWindow2 = [...c4.files].some((f5) => changedFiles.has(f5));
105051
+ if (inWindow2) continue;
104456
105052
  out2.push({
104457
105053
  area: c4.area,
104458
105054
  reasons: [
@@ -120039,6 +120635,9 @@ async function buildInvestigationContext(renv, opts) {
120039
120635
  const mongo = mongoForEnv(renv);
120040
120636
  const postgres2 = postgresForEnv(renv);
120041
120637
  const sentry = sentryForEnv(renv);
120638
+ const auth = readAuth();
120639
+ const cloudCfg = readCloudConfig(renv.path);
120640
+ const lens = auth && cloudCfg?.workspace?.id ? lensForCloud({ apiBaseUrl: auth.apiBaseUrl, token: auth.token, workspaceId: cloudCfg.workspace.id }) : null;
120042
120641
  const axiom = axiomForEnv(renv);
120043
120642
  const shopify = shopifyForEnv(renv);
120044
120643
  const queue = queueForEnv(renv);
@@ -120057,6 +120656,7 @@ async function buildInvestigationContext(renv, opts) {
120057
120656
  mongo,
120058
120657
  postgres: postgres2,
120059
120658
  sentry,
120659
+ lens,
120060
120660
  axiom,
120061
120661
  shopify,
120062
120662
  queue,
@@ -120109,6 +120709,7 @@ async function runOneInvestigation(input, ctx, opts = {}) {
120109
120709
  mongo: ctx.mongo,
120110
120710
  postgres: ctx.postgres,
120111
120711
  sentry: ctx.sentry,
120712
+ lens: ctx.lens,
120112
120713
  axiom: ctx.axiom,
120113
120714
  shopify: ctx.shopify,
120114
120715
  queue: ctx.queue,
@@ -120125,6 +120726,9 @@ async function runOneInvestigation(input, ctx, opts = {}) {
120125
120726
  mongodb: !!renv.connectors.mongodb,
120126
120727
  postgres: !!renv.connectors.postgres,
120127
120728
  sentry: !!renv.connectors.sentry,
120729
+ // Lens is NATIVE (no config stanza) — "configured" == provider present, mirroring how
120730
+ // `queue` is derived from `ctx.queue`. Absent when not logged in / repo not cloud-linked.
120731
+ lens: ctx.lens != null,
120128
120732
  axiom: !!renv.connectors.axiom,
120129
120733
  shopify: !!renv.connectors.shopify,
120130
120734
  redis: !!renv.connectors.redis,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.21.7",
3
+ "version": "0.21.8",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {