@merittdev/horus 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.cjs +97 -7
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -65880,7 +65880,7 @@ init_cjs_shims();
65880
65880
 
65881
65881
  // ../../packages/core/src/version.ts
65882
65882
  init_cjs_shims();
65883
- var HORUS_VERSION = true ? "0.16.0" : "dev";
65883
+ var HORUS_VERSION = true ? "0.17.0" : "dev";
65884
65884
  var PINNED_SOURCE_VERSION = "2.0.2";
65885
65885
 
65886
65886
  // ../../packages/core/src/evidence.ts
@@ -70670,10 +70670,23 @@ var connectorsSchema = external_exports.object({
70670
70670
  ).optional()
70671
70671
  }).optional()
70672
70672
  }).default({});
70673
+ var notifySchema = external_exports.object({
70674
+ /** Only dispatch when an auto-investigation's confidence is >= this (0..1). Default 0.6. */
70675
+ minConfidence: external_exports.number().min(0).max(1).default(0.6),
70676
+ /** Generic outbound webhook (Slack-compatible JSON, HMAC-signed when `secret` is set). */
70677
+ webhook: external_exports.object({
70678
+ url: external_exports.string().url(),
70679
+ /** HMAC-SHA256 signing secret; when set, payloads carry an `X-Horus-Signature` header. */
70680
+ secret: external_exports.string().min(1).optional()
70681
+ }).optional(),
70682
+ /** Also push the headline to Horus Cloud (uses the existing cloud session, if signed in). */
70683
+ cloud: external_exports.boolean().default(false)
70684
+ }).optional();
70673
70685
  var environmentSchema = external_exports.object({
70674
70686
  name: external_exports.string().min(1),
70675
70687
  readOnly: external_exports.boolean().default(true),
70676
- connectors: connectorsSchema
70688
+ connectors: connectorsSchema,
70689
+ notify: notifySchema
70677
70690
  });
70678
70691
  var projectSchema = external_exports.object({
70679
70692
  name: external_exports.string().min(1),
@@ -70919,7 +70932,8 @@ function resolveEnvironment(config2, opts) {
70919
70932
  readOnly: environment.readOnly,
70920
70933
  repositories: repositories2,
70921
70934
  path: primary?.path ?? "",
70922
- connectors: resolved
70935
+ connectors: resolved,
70936
+ ...environment.notify !== void 0 ? { notify: environment.notify } : {}
70923
70937
  };
70924
70938
  }
70925
70939
  var CONFIG_EXAMPLES = {
@@ -98175,7 +98189,9 @@ async function investigate(input, deps) {
98175
98189
  const seedFile = top?.filePath;
98176
98190
  const seedInChanges = recentChangeRelevant;
98177
98191
  const citation = formatRegressionCitation(recentChanges, changes, seedFile);
98178
- const regressionTitle = top && seedInChanges ? `Recent change${citation.commitClause} to ${top.name}${citation.symbolClause} in ${changeRangeLabel} may have introduced the regression` : top ? `Changes shipped in ${changeRangeLabel} but none touched ${top.name} \u2014 a regression here is unlikely to be a code change to the seed itself (check upstream deps, data, or config)` : `Recent change${citation.commitClause}${citation.symbolClause} in ${changeRangeLabel} may have introduced the regression`;
98192
+ const seedFocus = seedInChanges ? seedTouchingCommitFocus(recentChanges, top?.filePath) : null;
98193
+ const isBroadChange = seedFocus !== null && seedFocus > 14;
98194
+ const regressionTitle = top && seedInChanges && !isBroadChange ? `Recent change${citation.commitClause} to ${top.name}${citation.symbolClause} in ${changeRangeLabel} may have introduced the regression` : top && seedInChanges && isBroadChange ? `No specific cause identified from the available evidence \u2014 a broad recent change (${seedFocus} files) in ${changeRangeLabel} touched ${top.name}'s file but isn't clearly linked to the symptom; connect runtime evidence (logs/metrics) for a code-aware cause` : top ? `Changes shipped in ${changeRangeLabel} but none touched ${top.name} \u2014 a regression here is unlikely to be a code change to the seed itself (check upstream deps, data, or config)` : `Recent change${citation.commitClause}${citation.symbolClause} in ${changeRangeLabel} may have introduced the regression`;
98179
98195
  causeInputs.push({
98180
98196
  id: "cause:deployment-regression",
98181
98197
  title: regressionTitle,
@@ -98185,7 +98201,7 @@ async function investigate(input, deps) {
98185
98201
  // HOR-451: scale the seed-touched prior by commit focus — a broad/diffuse commit that merely
98186
98202
  // touched the seed no longer outranks an evidence-backed runtime cause.
98187
98203
  baseScore: clamp015(
98188
- (seedInChanges ? regressionSeedTouchedScore(seedTouchingCommitFocus(recentChanges, top?.filePath)) : 0.18) + (queueHits.length > 0 ? 0.05 : 0)
98204
+ (seedInChanges ? regressionSeedTouchedScore(seedFocus) : 0.18) + (queueHits.length > 0 ? 0.05 : 0)
98189
98205
  ),
98190
98206
  metadata: { blastRadius }
98191
98207
  });
@@ -118233,6 +118249,70 @@ async function runPacket(hintOrId, opts) {
118233
118249
  // ../../packages/cli/src/commands/watch.ts
118234
118250
  init_cjs_shims();
118235
118251
  var import_picocolors19 = __toESM(require_picocolors(), 1);
118252
+
118253
+ // ../../packages/cli/src/lib/notify-sink.ts
118254
+ init_cjs_shims();
118255
+ var import_node_crypto11 = require("crypto");
118256
+ function buildWebhookPayload(h3) {
118257
+ const pct2 = `${Math.round(h3.confidence * 100)}%`;
118258
+ return {
118259
+ text: `\u{1F52D} Horus: ${h3.hint} \u2192 ${h3.cause} (${pct2})`,
118260
+ investigationId: h3.investigationId,
118261
+ hint: h3.hint,
118262
+ cause: h3.cause,
118263
+ confidence: h3.confidence
118264
+ };
118265
+ }
118266
+ function signPayload(body2, secret) {
118267
+ return `sha256=${(0, import_node_crypto11.createHmac)("sha256", secret).update(body2).digest("hex")}`;
118268
+ }
118269
+ function shouldNotify(confidence, notify) {
118270
+ if (notify === void 0) return false;
118271
+ if (notify.webhook === void 0 && notify.cloud !== true) return false;
118272
+ return confidence >= notify.minConfidence;
118273
+ }
118274
+ async function postWebhook(url, secret, h3, timeoutMs) {
118275
+ const body2 = JSON.stringify(buildWebhookPayload(h3));
118276
+ const headers = { "content-type": "application/json" };
118277
+ if (secret !== void 0) headers["x-horus-signature"] = signPayload(body2, secret);
118278
+ const controller = new AbortController();
118279
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
118280
+ try {
118281
+ const res = await fetch(url, { method: "POST", headers, body: body2, signal: controller.signal });
118282
+ if (!res.ok) return { target: "webhook", ok: false, detail: `HTTP ${res.status}` };
118283
+ return { target: "webhook", ok: true, detail: `HTTP ${res.status}` };
118284
+ } catch (err2) {
118285
+ return { target: "webhook", ok: false, detail: err2.message };
118286
+ } finally {
118287
+ clearTimeout(timer2);
118288
+ }
118289
+ }
118290
+ async function dispatchNotify(report, notify, opts = {}) {
118291
+ if (notify === void 0) return [];
118292
+ const h3 = {
118293
+ investigationId: report.id,
118294
+ hint: report.hint,
118295
+ cause: report.cause,
118296
+ confidence: report.confidence
118297
+ };
118298
+ if (!shouldNotify(report.confidence, notify)) return [];
118299
+ const timeoutMs = opts.timeoutMs ?? 5e3;
118300
+ const results = [];
118301
+ if (notify.webhook !== void 0) {
118302
+ results.push(await postWebhook(notify.webhook.url, notify.webhook.secret, h3, timeoutMs));
118303
+ }
118304
+ if (notify.cloud === true && opts.cloudPush !== void 0) {
118305
+ try {
118306
+ await opts.cloudPush();
118307
+ results.push({ target: "cloud", ok: true, detail: "pushed" });
118308
+ } catch (err2) {
118309
+ results.push({ target: "cloud", ok: false, detail: err2.message });
118310
+ }
118311
+ }
118312
+ return results;
118313
+ }
118314
+
118315
+ // ../../packages/cli/src/commands/watch.ts
118236
118316
  function detectNewSentryIncidents(issues, seen) {
118237
118317
  const fresh = [];
118238
118318
  for (const issue2 of issues) {
@@ -118362,6 +118442,16 @@ async function runWatch(opts) {
118362
118442
  console.log(
118363
118443
  `${import_picocolors19.default.bold("\u25CF")} ${incident.hint} ${import_picocolors19.default.dim("\u2192")} ${cause} ${import_picocolors19.default.dim(`(${(confidence * 100).toFixed(0)}%)`)} ${import_picocolors19.default.dim(`id: ${report.id}`)}${persisted}`
118364
118444
  );
118445
+ if (shouldNotify(confidence, renv.notify)) {
118446
+ const results = await dispatchNotify(
118447
+ { id: report.id, confidence, hint: incident.hint, cause },
118448
+ renv.notify
118449
+ );
118450
+ for (const r of results) {
118451
+ if (r.ok) console.error(import_picocolors19.default.dim(`[watch] notified ${r.target} (${r.detail})`));
118452
+ else console.error(import_picocolors19.default.yellow(`[watch] notify ${r.target} failed \u2014 ${r.detail}`));
118453
+ }
118454
+ }
118365
118455
  } catch (err2) {
118366
118456
  console.error(
118367
118457
  import_picocolors19.default.yellow(`[watch] investigation failed for "${incident.ref}" \u2014 ${err2.message}`)
@@ -125231,7 +125321,7 @@ async function createProjectFromRepo(client, ctx, remote, yes) {
125231
125321
  // ../../packages/cli/src/commands/telemetry.ts
125232
125322
  init_cjs_shims();
125233
125323
  var import_picocolors56 = __toESM(require_picocolors(), 1);
125234
- var import_node_crypto11 = require("crypto");
125324
+ var import_node_crypto12 = require("crypto");
125235
125325
 
125236
125326
  // ../../packages/cli/src/lib/telemetry/transport.ts
125237
125327
  init_cjs_shims();
@@ -125423,7 +125513,7 @@ async function runTelemetryDisableContent() {
125423
125513
  return 0;
125424
125514
  }
125425
125515
  async function runTelemetryResetId() {
125426
- const next = (0, import_node_crypto11.randomUUID)();
125516
+ const next = (0, import_node_crypto12.randomUUID)();
125427
125517
  updateTelemetryState((s5) => {
125428
125518
  s5.installId = next;
125429
125519
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {