@merittdev/horus 0.21.6 → 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.
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.6" : "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");
@@ -77378,6 +77885,35 @@ var CloudClient = class {
77378
77885
  `/v1/projects/${projectId}/memory-items${qs ? `?${qs}` : ""}`
77379
77886
  );
77380
77887
  }
77888
+ // ── Team memory (HOR-464 / M4) ────────────────────────────────────────────
77889
+ /**
77890
+ * One-way PROMOTE local items into the workspace's shared team-memory store. Server-wins &
77891
+ * idempotent (`ON CONFLICT (workspace_id, origin_client_id) DO NOTHING`); it clamps/skips
77892
+ * confirmed-outcome (privacy). The body is a positive allowlist — vectors NEVER cross the wire.
77893
+ */
77894
+ promoteTeamMemory(projectId, body2) {
77895
+ return this.request(
77896
+ "POST",
77897
+ `/v1/projects/${projectId}/team-memory/promote`,
77898
+ body2
77899
+ );
77900
+ }
77901
+ /**
77902
+ * Read the workspace's team-memory items with `seq > since`, ORDER BY seq ASC. The v1 client pulls
77903
+ * `since:'0'` and paginates on `nextCursor` until `hasMore` is false, then reconciles the local
77904
+ * cache. `includeDeleted` surfaces tombstones (deletedAt) so the cache can drop removed rows.
77905
+ */
77906
+ listTeamMemorySince(projectId, q2) {
77907
+ const params = new URLSearchParams();
77908
+ params.set("since", q2.since);
77909
+ if (q2.limit !== void 0) params.set("limit", String(q2.limit));
77910
+ if (q2.includeDeleted) params.set("includeDeleted", "true");
77911
+ if (q2.search) params.set("search", q2.search);
77912
+ return this.request(
77913
+ "GET",
77914
+ `/v1/projects/${projectId}/team-memory?${params.toString()}`
77915
+ );
77916
+ }
77381
77917
  };
77382
77918
 
77383
77919
  // ../../packages/cli/src/lib/cloud/auth-store.ts
@@ -84445,6 +84981,21 @@ var memoryItem = pgTable(
84445
84981
  userId: text("user_id"),
84446
84982
  visibility: text("visibility").notNull().default("private"),
84447
84983
  // private|team
84984
+ // HOR-464 (M4): team-memory cache provenance. `origin` distinguishes a locally-authored row
84985
+ // (`local`) from a disposable read-cache row pulled from the cloud team store (`cloud`). The
84986
+ // cloud-owned copy is server-authoritative — `origin='cloud'` rows are readonly locally (any
84987
+ // local mutation throws `MemoryCacheReadonlyError`; re-promote to change) and are NEVER re-pushed
84988
+ // by `memory sync` (which pushes `origin='local'` only). `cloudId` is the server team-memory id,
84989
+ // `authorName` the promoting teammate (DISPLAY-ONLY — never feeds rank), `pulledAt` the last
84990
+ // refresh time (staleness).
84991
+ origin: text("origin").notNull().default("local"),
84992
+ // local|cloud
84993
+ cloudId: text("cloud_id"),
84994
+ // server team_memory id (provenance/join)
84995
+ authorName: text("author_name"),
84996
+ // promoting teammate's display name (attribution, display-only)
84997
+ pulledAt: timestamp("pulled_at", { withTimezone: true }),
84998
+ // last cache-refresh time
84448
84999
  payload: jsonb("payload"),
84449
85000
  // forward-compat, zero-migration extension
84450
85001
  // Incident-family recall keys, populated at WRITE for incident-derived kinds
@@ -84461,7 +85012,9 @@ var memoryItem = pgTable(
84461
85012
  index("memory_item_scope_idx").on(t2.scope),
84462
85013
  index("memory_item_status_idx").on(t2.status),
84463
85014
  index("memory_item_tenancy_idx").on(t2.orgId, t2.workspaceId, t2.repo, t2.userId),
84464
- index("memory_item_signature_idx").on(t2.signature)
85015
+ index("memory_item_signature_idx").on(t2.signature),
85016
+ // HOR-464: fast repo-scoped split of local-authored vs cloud-cached rows (pull reconcile + sync filter).
85017
+ index("memory_item_origin_idx").on(t2.repo, t2.origin)
84465
85018
  ]
84466
85019
  );
84467
85020
  var memoryLink = pgTable(
@@ -92771,6 +93324,16 @@ WHERE im.investigation_id IS NOT NULL AND im.project IS NULL;`,
92771
93324
  WHERE "project" IS NULL AND incident_input->>'repo' IS NOT NULL;`,
92772
93325
  'CREATE INDEX "investigations_project_created_idx" ON "investigations" ("project","created_at");'
92773
93326
  ]
93327
+ },
93328
+ {
93329
+ "tag": "0012_memory_item_origin",
93330
+ "statements": [
93331
+ `ALTER TABLE "memory_item" ADD COLUMN "origin" text DEFAULT 'local' NOT NULL;`,
93332
+ 'ALTER TABLE "memory_item" ADD COLUMN "cloud_id" text;',
93333
+ 'ALTER TABLE "memory_item" ADD COLUMN "author_name" text;',
93334
+ 'ALTER TABLE "memory_item" ADD COLUMN "pulled_at" timestamp with time zone;',
93335
+ 'CREATE INDEX IF NOT EXISTS "memory_item_origin_idx" ON "memory_item" ("repo","origin");'
93336
+ ]
92774
93337
  }
92775
93338
  ];
92776
93339
 
@@ -94177,6 +94740,7 @@ init_cjs_shims();
94177
94740
 
94178
94741
  // ../../packages/engine/src/engine.ts
94179
94742
  init_cjs_shims();
94743
+ init_src();
94180
94744
 
94181
94745
  // ../../packages/engine/src/render.ts
94182
94746
  init_cjs_shims();
@@ -96403,6 +96967,16 @@ var MemorySecretError = class extends Error {
96403
96967
  this.kind = kind;
96404
96968
  }
96405
96969
  };
96970
+ var MemoryCacheReadonlyError = class extends Error {
96971
+ id;
96972
+ constructor(id) {
96973
+ super(
96974
+ `refusing to mutate a cloud-owned cached memory item (${id}); the team store owns it \u2014 re-promote to change it`
96975
+ );
96976
+ this.name = "MemoryCacheReadonlyError";
96977
+ this.id = id;
96978
+ }
96979
+ };
96406
96980
  var CLAIM_SECRET_PATTERNS = [
96407
96981
  { label: "private-key", re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----/ },
96408
96982
  { label: "jwt", re: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/ },
@@ -96562,6 +97136,7 @@ function createLocalMemoryStore(db) {
96562
97136
  async update(id, patch, opts) {
96563
97137
  const current = await getById(id);
96564
97138
  if (current === null) throw new Error(`memory item not found: ${id}`);
97139
+ if (current.origin === "cloud") throw new MemoryCacheReadonlyError(id);
96565
97140
  const set = {};
96566
97141
  if (patch.claim !== void 0) {
96567
97142
  assertClaimClean(patch.claim);
@@ -96594,6 +97169,7 @@ function createLocalMemoryStore(db) {
96594
97169
  if (repo === "") return [];
96595
97170
  const conds = [eq(memoryItem.repo, repo)];
96596
97171
  if (q2.scope !== void 0) conds.push(eq(memoryItem.scope, q2.scope));
97172
+ if (q2.origin !== void 0) conds.push(eq(memoryItem.origin, q2.origin));
96597
97173
  if (q2.visibility !== void 0) conds.push(eq(memoryItem.visibility, q2.visibility));
96598
97174
  if (q2.orgId !== void 0) conds.push(eq(memoryItem.orgId, q2.orgId));
96599
97175
  if (q2.workspaceId !== void 0) conds.push(eq(memoryItem.workspaceId, q2.workspaceId));
@@ -96614,6 +97190,7 @@ function createLocalMemoryStore(db) {
96614
97190
  async setStatus(id, status, audit) {
96615
97191
  const current = await getById(id);
96616
97192
  if (current === null) throw new Error(`memory item not found: ${id}`);
97193
+ if (current.origin === "cloud") throw new MemoryCacheReadonlyError(id);
96617
97194
  await db.update(memoryItem).set({ status }).where(eq(memoryItem.id, id));
96618
97195
  await appendAudit({
96619
97196
  memoryId: id,
@@ -96627,6 +97204,7 @@ function createLocalMemoryStore(db) {
96627
97204
  async setVisibility(id, v4, audit) {
96628
97205
  const current = await getById(id);
96629
97206
  if (current === null) throw new Error(`memory item not found: ${id}`);
97207
+ if (current.origin === "cloud") throw new MemoryCacheReadonlyError(id);
96630
97208
  await db.update(memoryItem).set({ visibility: v4 }).where(eq(memoryItem.id, id));
96631
97209
  await appendAudit({
96632
97210
  memoryId: id,
@@ -96771,6 +97349,96 @@ function createLocalMemoryStore(db) {
96771
97349
  },
96772
97350
  async history(id) {
96773
97351
  return db.select().from(memoryAudit).where(eq(memoryAudit.memoryId, id)).orderBy(desc(memoryAudit.at), desc(memoryAudit.id));
97352
+ },
97353
+ // ---- HOR-464 / M4: disposable team-memory read-cache + one-way promote ----
97354
+ async upsertCached(item, audit) {
97355
+ assertClaimClean(item.claim);
97356
+ const repo = (item.repo ?? "").trim();
97357
+ if (repo === "") throw new Error("memory_item requires a repo (HOR-46 fail-closed)");
97358
+ const id = (item.id ?? "").trim();
97359
+ if (id === "") throw new Error("upsertCached requires a stable item id (the origin client id)");
97360
+ const status = item.status ?? "fresh";
97361
+ const now = /* @__PURE__ */ new Date();
97362
+ const values2 = {
97363
+ ...item,
97364
+ id,
97365
+ repo,
97366
+ status,
97367
+ origin: "cloud",
97368
+ pulledAt: now,
97369
+ signature: null,
97370
+ tags: null
97371
+ };
97372
+ await db.insert(memoryItem).values(values2).onConflictDoUpdate({
97373
+ target: memoryItem.id,
97374
+ set: {
97375
+ kind: values2.kind,
97376
+ claim: values2.claim,
97377
+ scope: values2.scope,
97378
+ source: values2.source,
97379
+ status,
97380
+ confidence: values2.confidence,
97381
+ visibility: values2.visibility ?? "team",
97382
+ evidence: values2.evidence,
97383
+ cloudId: values2.cloudId ?? null,
97384
+ authorName: values2.authorName ?? null,
97385
+ lastVerifiedAt: values2.lastVerifiedAt ?? null,
97386
+ lastVerifiedHash: values2.lastVerifiedHash ?? null,
97387
+ origin: "cloud",
97388
+ // Re-scope to the repo we're pulling into. The cache is disposable and
97389
+ // project-isolated (HOR-46): a team item's id is the promoter's local id and is
97390
+ // stable across a workspace, so pulling it from a different repo on the same store
97391
+ // must move the cache row to the current repo — otherwise "pull here" silently
97392
+ // leaves it invisible here, stuck in whichever repo first pulled it.
97393
+ repo: values2.repo,
97394
+ pulledAt: now
97395
+ }
97396
+ });
97397
+ const row = await getById(id);
97398
+ if (row === null) throw new Error("memory_item upsertCached returned no row");
97399
+ await appendAudit({
97400
+ memoryId: id,
97401
+ action: "pulled",
97402
+ actor: audit.actor,
97403
+ fromStatus: null,
97404
+ toStatus: status,
97405
+ note: audit.note ?? null,
97406
+ detail: { origin: "cloud", ...values2.cloudId ? { cloudId: values2.cloudId } : {} }
97407
+ });
97408
+ return row;
97409
+ },
97410
+ async reconcileCache(repo, keepIds) {
97411
+ const r = repo.trim();
97412
+ if (r === "") return 0;
97413
+ const conds = keepIds.length > 0 ? and(
97414
+ eq(memoryItem.repo, r),
97415
+ eq(memoryItem.origin, "cloud"),
97416
+ notInArray(memoryItem.id, keepIds)
97417
+ ) : and(eq(memoryItem.repo, r), eq(memoryItem.origin, "cloud"));
97418
+ const deleted = await db.delete(memoryItem).where(conds).returning();
97419
+ return deleted.length;
97420
+ },
97421
+ async markPromoted(id, provenance, audit) {
97422
+ const current = await getById(id);
97423
+ if (current === null) throw new Error(`memory item not found: ${id}`);
97424
+ await db.update(memoryItem).set({
97425
+ visibility: "team",
97426
+ origin: "cloud",
97427
+ cloudId: provenance.cloudId,
97428
+ authorName: provenance.authorName ?? current.authorName ?? null
97429
+ }).where(eq(memoryItem.id, id));
97430
+ await appendAudit({
97431
+ memoryId: id,
97432
+ action: "promote",
97433
+ actor: audit.actor,
97434
+ fromStatus: null,
97435
+ toStatus: null,
97436
+ note: audit.note ?? `promoted to team (cloud id ${provenance.cloudId})`,
97437
+ detail: { origin: "cloud", visibility: "team", cloudId: provenance.cloudId }
97438
+ });
97439
+ const updated = await getById(id);
97440
+ if (updated === null) throw new Error(`memory item vanished during promote: ${id}`);
97441
+ return updated;
96774
97442
  }
96775
97443
  };
96776
97444
  }
@@ -97168,7 +97836,7 @@ async function captureInvestigationMemory(store, investigationId, report, report
97168
97836
  init_cjs_shims();
97169
97837
  function hasAnyRuntimeConnector(c4) {
97170
97838
  return Boolean(
97171
- 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
97172
97840
  );
97173
97841
  }
97174
97842
  function isUnavailable(c4, kind) {
@@ -99895,6 +100563,88 @@ async function investigate(input, deps) {
99895
100563
  sentryFailureReason = connectorFailureReason(sentryErr);
99896
100564
  }
99897
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
+ }
99898
100648
  let axiomCollected = false;
99899
100649
  let axiomFailureReason;
99900
100650
  if (deps.axiom) {
@@ -100920,6 +101670,7 @@ async function investigate(input, deps) {
100920
101670
  ["mongodb", deps.connectors.mongodb, deps.mongo],
100921
101671
  ["postgres", deps.connectors.postgres, deps.postgres],
100922
101672
  ["sentry", deps.connectors.sentry, deps.sentry],
101673
+ ["lens", deps.connectors.lens, deps.lens],
100923
101674
  ["axiom", deps.connectors.axiom, deps.axiom],
100924
101675
  ["shopify", deps.connectors.shopify, deps.shopify],
100925
101676
  ["redis", deps.connectors.redis, deps.redisState]
@@ -100935,6 +101686,8 @@ async function investigate(input, deps) {
100935
101686
  // `unavailable` instead of silently flipping to "not configured".
100936
101687
  sentryCollected,
100937
101688
  sentryFailureReason,
101689
+ lensCollected,
101690
+ lensFailureReason,
100938
101691
  axiomCollected,
100939
101692
  axiomFailureReason,
100940
101693
  shopifyCollected,
@@ -100957,6 +101710,9 @@ async function investigate(input, deps) {
100957
101710
  sentry: deps.sentry != null,
100958
101711
  sentryCollected,
100959
101712
  sentryFailureReason,
101713
+ lens: deps.lens != null,
101714
+ lensCollected,
101715
+ lensFailureReason,
100960
101716
  axiom: deps.axiom != null,
100961
101717
  axiomCollected,
100962
101718
  axiomFailureReason,
@@ -104291,8 +105047,8 @@ function buildLowerPriority(report, seedLowConfidence, effectiveTopLower) {
104291
105047
  });
104292
105048
  const noEvidence = !c4.implicated && !hasRuntimeEvidence;
104293
105049
  if (!noEvidence) continue;
104294
- const inWindow = [...c4.files].some((f5) => changedFiles.has(f5));
104295
- if (inWindow) continue;
105050
+ const inWindow2 = [...c4.files].some((f5) => changedFiles.has(f5));
105051
+ if (inWindow2) continue;
104296
105052
  out2.push({
104297
105053
  area: c4.area,
104298
105054
  reasons: [
@@ -119879,6 +120635,9 @@ async function buildInvestigationContext(renv, opts) {
119879
120635
  const mongo = mongoForEnv(renv);
119880
120636
  const postgres2 = postgresForEnv(renv);
119881
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;
119882
120641
  const axiom = axiomForEnv(renv);
119883
120642
  const shopify = shopifyForEnv(renv);
119884
120643
  const queue = queueForEnv(renv);
@@ -119897,6 +120656,7 @@ async function buildInvestigationContext(renv, opts) {
119897
120656
  mongo,
119898
120657
  postgres: postgres2,
119899
120658
  sentry,
120659
+ lens,
119900
120660
  axiom,
119901
120661
  shopify,
119902
120662
  queue,
@@ -119949,6 +120709,7 @@ async function runOneInvestigation(input, ctx, opts = {}) {
119949
120709
  mongo: ctx.mongo,
119950
120710
  postgres: ctx.postgres,
119951
120711
  sentry: ctx.sentry,
120712
+ lens: ctx.lens,
119952
120713
  axiom: ctx.axiom,
119953
120714
  shopify: ctx.shopify,
119954
120715
  queue: ctx.queue,
@@ -119965,6 +120726,9 @@ async function runOneInvestigation(input, ctx, opts = {}) {
119965
120726
  mongodb: !!renv.connectors.mongodb,
119966
120727
  postgres: !!renv.connectors.postgres,
119967
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,
119968
120732
  axiom: !!renv.connectors.axiom,
119969
120733
  shopify: !!renv.connectors.shopify,
119970
120734
  redis: !!renv.connectors.redis,
@@ -122314,6 +123078,12 @@ function fromWire(r, repo) {
122314
123078
  repo,
122315
123079
  userId: r.createdByUserId ?? null,
122316
123080
  visibility: r.visibility,
123081
+ // This is the write-only private mirror path (dualWrite reads local): a round-tripped record is
123082
+ // the device's OWN authored item, so it carries no team-cache provenance (HOR-464).
123083
+ origin: "local",
123084
+ cloudId: null,
123085
+ authorName: null,
123086
+ pulledAt: null,
122317
123087
  payload: null,
122318
123088
  // Incident-family recall keys are LOCAL-ONLY context (like vectors): they never enter the
122319
123089
  // cloud-sync path, so a cloud-read row carries neither.
@@ -122375,6 +123145,10 @@ function createCloudMemoryStore(client, cfg) {
122375
123145
  repo: item.repo ?? repoFallback,
122376
123146
  userId: item.userId ?? null,
122377
123147
  visibility: input.visibility ?? "private",
123148
+ origin: "local",
123149
+ cloudId: null,
123150
+ authorName: null,
123151
+ pulledAt: null,
122378
123152
  payload: null,
122379
123153
  // Incident-family recall keys stay LOCAL-ONLY — never synced, so the cloud echo carries neither.
122380
123154
  signature: null,
@@ -122409,6 +123183,10 @@ function createCloudMemoryStore(client, cfg) {
122409
123183
  repo: repoFallback,
122410
123184
  userId: null,
122411
123185
  visibility: "private",
123186
+ origin: "local",
123187
+ cloudId: null,
123188
+ authorName: null,
123189
+ pulledAt: null,
122412
123190
  payload: null,
122413
123191
  signature: null,
122414
123192
  tags: null
@@ -122667,6 +123445,10 @@ function recalledToJSON(r) {
122667
123445
  effectiveStatus: r.freshness.status,
122668
123446
  confidence: r.item.confidence,
122669
123447
  visibility: r.item.visibility,
123448
+ // HOR-464: provenance + teammate attribution. `origin='cloud'` marks a pulled team item; the
123449
+ // `authorName` is DISPLAY-ONLY (it is never read into rank/order — the honesty invariant).
123450
+ origin: r.item.origin,
123451
+ authorName: r.item.authorName ?? null,
122670
123452
  evidence: r.item.evidence,
122671
123453
  freshness: {
122672
123454
  label: r.freshness.label,
@@ -122693,6 +123475,10 @@ function renderStoredItems(items) {
122693
123475
  lines.push(
122694
123476
  ` - ${r.item.kind} \xB7 scope: ${r.item.scope} \xB7 ${r.freshness.label}${drift} \xB7 confidence: ${conf}`
122695
123477
  );
123478
+ if (r.item.origin === "cloud") {
123479
+ const who = r.item.authorName ? `teammate ${r.item.authorName}` : "a teammate";
123480
+ lines.push(` - ${import_picocolors36.default.dim(`shared by ${who} (team)`)}`);
123481
+ }
122696
123482
  }
122697
123483
  return lines.join("\n");
122698
123484
  }
@@ -123171,6 +123957,7 @@ async function runMemorySync(opts) {
123171
123957
  const store = createLocalMemoryStore(db);
123172
123958
  items = await store.query({
123173
123959
  repo: project,
123960
+ origin: "local",
123174
123961
  status: SYNCABLE_STATUSES,
123175
123962
  limit: opts.limit ?? 5e3
123176
123963
  });
@@ -123305,6 +124092,215 @@ async function runMemorySync(opts) {
123305
124092
  }
123306
124093
  return failed > 0 ? 1 : 0;
123307
124094
  }
124095
+ function toPromoteInput(item) {
124096
+ return {
124097
+ originClientId: item.id,
124098
+ kind: item.kind,
124099
+ claim: item.claim,
124100
+ scope: item.scope,
124101
+ source: item.source,
124102
+ status: item.status,
124103
+ confidence: item.confidence,
124104
+ contentHash: item.lastVerifiedHash ?? null,
124105
+ provenance: {}
124106
+ };
124107
+ }
124108
+ async function runMemoryPromote(id, opts) {
124109
+ const json2 = opts.json === true;
124110
+ const fail = (msg) => {
124111
+ if (json2) console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
124112
+ else console.error(import_picocolors36.default.red(msg));
124113
+ return 1;
124114
+ };
124115
+ const memId = (id ?? "").trim();
124116
+ if (memId === "") return fail("A memory item id is required.");
124117
+ const root = repoRootOrCwd(opts.cwd);
124118
+ const cfg = readCloudConfig(root);
124119
+ if (!isCloudActive(cfg) || !cfg.project) {
124120
+ return fail("This repo isn't linked to a cloud project. Run `horus cloud link` first.");
124121
+ }
124122
+ const session = authedClient();
124123
+ if (!session) return fail("Not logged in. Run `horus login` first.");
124124
+ let config2;
124125
+ try {
124126
+ config2 = await loadConfig(opts.config);
124127
+ } catch (err2) {
124128
+ return fail(err2.message);
124129
+ }
124130
+ const project = resolveProject(config2, opts.repo);
124131
+ if (!project) return 1;
124132
+ const { db, sql: sql2 } = await openDb(config2.database.url);
124133
+ try {
124134
+ const store = createLocalMemoryStore(db);
124135
+ const row = await store.get(memId);
124136
+ if (row === null) return fail(`Memory item not found: ${memId}`);
124137
+ if (row.kind === "confirmed-outcome" || row.source === "confirmed-outcome") {
124138
+ return fail("Refusing to promote a confirmed-outcome item \u2014 it stays private (spec \xA75.2).");
124139
+ }
124140
+ if (row.origin === "cloud") {
124141
+ if (json2) console.log(JSON.stringify({ ok: true, id: memId, alreadyTeam: true }, null, 2));
124142
+ else console.log(import_picocolors36.default.dim(`${memId} is already a team item \u2014 nothing to promote.`));
124143
+ return 0;
124144
+ }
124145
+ const res = await session.client.promoteTeamMemory(cfg.project.id, {
124146
+ items: [toPromoteInput(row)]
124147
+ });
124148
+ const promoted = res.items.find((it2) => it2.originClientId === memId);
124149
+ if (!promoted) {
124150
+ return fail(
124151
+ `The server did not promote ${memId} (skipped ${res.skipped}/${res.total}). Local state unchanged.`
124152
+ );
124153
+ }
124154
+ const updated = await store.markPromoted(
124155
+ memId,
124156
+ { cloudId: promoted.id, authorName: promoted.authorName ?? null },
124157
+ { actor: { kind: "user" }, note: `promoted to team ${cfg.project.slug}` }
124158
+ );
124159
+ if (json2) {
124160
+ console.log(
124161
+ JSON.stringify(
124162
+ {
124163
+ ok: true,
124164
+ id: memId,
124165
+ cloudId: promoted.id,
124166
+ visibility: updated.visibility,
124167
+ origin: updated.origin
124168
+ },
124169
+ null,
124170
+ 2
124171
+ )
124172
+ );
124173
+ } else {
124174
+ console.log(
124175
+ import_picocolors36.default.green(`Promoted ${memId} to team memory (${cfg.project.slug}) \u2014 now visible to teammates.`)
124176
+ );
124177
+ }
124178
+ return 0;
124179
+ } catch (err2) {
124180
+ return fail(err2.message);
124181
+ } finally {
124182
+ await sql2.end();
124183
+ }
124184
+ }
124185
+ var TEAM_PULL_PAGE_LIMIT = 200;
124186
+ var TEAM_PULL_MAX_PAGES = 1e4;
124187
+ function formatAge3(ms) {
124188
+ const mins = Math.floor(ms / 6e4);
124189
+ if (mins < 1) return "just now";
124190
+ if (mins < 60) return `${mins}m ago`;
124191
+ const hrs = Math.floor(mins / 60);
124192
+ if (hrs < 24) return `${hrs}h ago`;
124193
+ return `${Math.floor(hrs / 24)}d ago`;
124194
+ }
124195
+ async function runMemoryPull(opts) {
124196
+ const json2 = opts.json === true;
124197
+ const fail = (msg) => {
124198
+ if (json2) console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
124199
+ else console.error(import_picocolors36.default.red(msg));
124200
+ return 1;
124201
+ };
124202
+ const root = repoRootOrCwd(opts.cwd);
124203
+ const cfg = readCloudConfig(root);
124204
+ if (!isCloudActive(cfg) || !cfg.project) {
124205
+ return fail("This repo isn't linked to a cloud project. Run `horus cloud link` first.");
124206
+ }
124207
+ const session = authedClient();
124208
+ if (!session) return fail("Not logged in. Run `horus login` first.");
124209
+ let config2;
124210
+ try {
124211
+ config2 = await loadConfig(opts.config);
124212
+ } catch (err2) {
124213
+ return fail(err2.message);
124214
+ }
124215
+ const project = resolveProject(config2, opts.repo);
124216
+ if (!project) return 1;
124217
+ const { db, sql: sql2 } = await openDb(config2.database.url);
124218
+ try {
124219
+ const store = createLocalMemoryStore(db);
124220
+ const vectorIndex = buildVectorIndex(config2, project);
124221
+ const items = [];
124222
+ let since = "0";
124223
+ try {
124224
+ for (let page = 0; page < TEAM_PULL_MAX_PAGES; page++) {
124225
+ const res = await session.client.listTeamMemorySince(cfg.project.id, {
124226
+ since,
124227
+ limit: TEAM_PULL_PAGE_LIMIT,
124228
+ includeDeleted: true
124229
+ });
124230
+ items.push(...res.items);
124231
+ if (!res.hasMore || !res.nextCursor) break;
124232
+ since = res.nextCursor;
124233
+ }
124234
+ } catch (err2) {
124235
+ if (err2 instanceof CloudOfflineError) {
124236
+ const cached2 = await store.query({ repo: project, origin: "cloud" });
124237
+ const newest = cached2.map((c4) => c4.pulledAt ? new Date(c4.pulledAt).getTime() : 0).reduce((a2, b5) => Math.max(a2, b5), 0);
124238
+ const note = newest > 0 ? `stale \u2014 last pulled ${formatAge3(Date.now() - newest)}` : "stale \u2014 never pulled";
124239
+ if (json2) {
124240
+ console.log(
124241
+ JSON.stringify({ ok: true, offline: true, stale: true, cached: cached2.length, note }, null, 2)
124242
+ );
124243
+ } else {
124244
+ console.log(import_picocolors36.default.yellow(`Offline \u2014 serving ${cached2.length} cached team item(s) (${note}).`));
124245
+ }
124246
+ return 0;
124247
+ }
124248
+ throw err2;
124249
+ }
124250
+ const keepIds = [];
124251
+ let upserted = 0;
124252
+ let removed = 0;
124253
+ for (const it2 of items) {
124254
+ if (it2.deletedAt) {
124255
+ await bestEffortRemove(vectorIndex, it2.originClientId);
124256
+ continue;
124257
+ }
124258
+ const cacheRow = {
124259
+ id: it2.originClientId,
124260
+ // stable cross-device id (the promoting device's local id)
124261
+ kind: it2.kind,
124262
+ claim: it2.claim,
124263
+ scope: it2.scope,
124264
+ source: it2.source,
124265
+ status: it2.status ?? "fresh",
124266
+ // A null server confidence stores as 0 — honest (unknown confidence ranks low, never inflated).
124267
+ confidence: it2.confidence ?? 0,
124268
+ evidence: [],
124269
+ repo: project,
124270
+ visibility: "team",
124271
+ cloudId: it2.id,
124272
+ authorName: it2.authorName
124273
+ };
124274
+ await store.upsertCached(cacheRow, { actor: { kind: "system" }, note: "memory pull" });
124275
+ await bestEffortUpsert(vectorIndex, {
124276
+ memoryId: it2.originClientId,
124277
+ claim: it2.claim,
124278
+ repo: project,
124279
+ scope: it2.scope
124280
+ });
124281
+ keepIds.push(it2.originClientId);
124282
+ upserted += 1;
124283
+ }
124284
+ removed = await store.reconcileCache(project, keepIds);
124285
+ if (json2) {
124286
+ console.log(
124287
+ JSON.stringify({ ok: true, project, pulled: upserted, removed, total: items.length }, null, 2)
124288
+ );
124289
+ } else {
124290
+ console.log(
124291
+ import_picocolors36.default.green(
124292
+ `Pulled ${upserted} team memory item(s)` + (removed ? `, dropped ${removed} stale` : "") + "."
124293
+ )
124294
+ );
124295
+ console.log(import_picocolors36.default.dim("Team items are read-only locally \u2014 re-promote to change one. Vectors stay local."));
124296
+ }
124297
+ return 0;
124298
+ } catch (err2) {
124299
+ return fail(err2.message);
124300
+ } finally {
124301
+ await sql2.end();
124302
+ }
124303
+ }
123308
124304
  function outcomeLabelToJSON(l2) {
123309
124305
  return {
123310
124306
  id: l2.id,
@@ -129302,6 +130298,12 @@ Examples:
129302
130298
  process.exitCode = await runMemorySync(opts);
129303
130299
  }
129304
130300
  );
130301
+ memory.command("promote <id>").description("Promote a local memory item into the shared team memory (one-way; refuses confirmed-outcome)").option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository/project WITHIN the loaded config (default: inferred from cwd) \u2014 cross-repo targeting is --config or cd").option("--json", "output JSON").action(async (id, opts) => {
130302
+ process.exitCode = await runMemoryPromote(id, opts);
130303
+ });
130304
+ memory.command("pull").description("Refresh the local cache of shared team memory (full pull; offline serves stale cache)").option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository/project WITHIN the loaded config (default: inferred from cwd) \u2014 cross-repo targeting is --config or cd").option("--force", "force a full refresh even if the cache looks current").option("--json", "output JSON").action(async (opts) => {
130305
+ process.exitCode = await runMemoryPull(opts);
130306
+ });
129305
130307
  const evalCmd = program2.command("eval").description("Read-only accuracy harness over Horus's outcome-label eval set (never writes labels)");
129306
130308
  evalCmd.command("build").description("Emit a byte-stable corpus-<version>.jsonl + manifest from the eval set (read-only)").option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository/project WITHIN the loaded config (default: inferred from cwd) \u2014 cross-repo targeting is --config or cd").option("--source <source>", "filter to one signal source: feedback | confirm").option("--days <n>", "only include labels from the last N days", (v4) => Number(v4)).option("--limit <n>", "max labels to scan", (v4) => Number(v4)).option("--out <dir>", "output directory for the corpus + manifest (default: ./.horus/eval)").option("--version <v>", "corpus schema version tag (default: v1)").option("--json", "output JSON").action(
129307
130309
  async (opts) => {