@merittdev/horus 0.8.10 → 0.8.12

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 +150 -14
  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.8.10" : "dev";
65883
+ var HORUS_VERSION = true ? "0.8.12" : "dev";
65884
65884
  var PINNED_SOURCE_VERSION = "1.5.1";
65885
65885
 
65886
65886
  // ../../packages/core/src/evidence.ts
@@ -71571,6 +71571,18 @@ function buildLevelFilter(m5, minLevel) {
71571
71571
  );
71572
71572
  return { terms: { [m5.levelField]: stringLevels } };
71573
71573
  }
71574
+ function buildWhereFilters(where) {
71575
+ if (where === void 0 || where.length === 0) return [];
71576
+ return where.map((w4) => ({
71577
+ bool: {
71578
+ should: [
71579
+ { term: { [w4.field]: w4.value } },
71580
+ { term: { [`${w4.field}.keyword`]: w4.value } }
71581
+ ],
71582
+ minimum_should_match: 1
71583
+ }
71584
+ }));
71585
+ }
71574
71586
  function buildTextMust(q2, mapping = MERITT_FIELD_MAPPING) {
71575
71587
  if (q2.text === void 0) return [{ match_all: {} }];
71576
71588
  if (q2.broadText === true) {
@@ -71595,6 +71607,7 @@ function buildSearchBody(q2, mapping = MERITT_FIELD_MAPPING) {
71595
71607
  if (q2.service !== void 0) {
71596
71608
  filters.push({ term: { [serviceTermField(mapping)]: q2.service } });
71597
71609
  }
71610
+ filters.push(...buildWhereFilters(q2.where));
71598
71611
  const mustClause = buildTextMust(q2, mapping);
71599
71612
  return {
71600
71613
  query: {
@@ -71623,6 +71636,7 @@ function buildErrorAggBody(q2, field, mapping = MERITT_FIELD_MAPPING) {
71623
71636
  if (q2.eventCode !== void 0) {
71624
71637
  filters.push({ term: { [signatureTermField(mapping)]: q2.eventCode } });
71625
71638
  }
71639
+ filters.push(...buildWhereFilters(q2.where));
71626
71640
  const aggField = field === mapping.eventCodeField ? signatureTermField(mapping) : `${field}.keyword`;
71627
71641
  return {
71628
71642
  size: 0,
@@ -71857,6 +71871,7 @@ function buildErrorAnalysisBody(q2, field = "event_code", mapping = MERITT_FIELD
71857
71871
  if (q2.eventCode !== void 0) {
71858
71872
  filters.push({ term: { [sigTerm]: q2.eventCode } });
71859
71873
  }
71874
+ filters.push(...buildWhereFilters(q2.where));
71860
71875
  const mustClause = buildTextMust(q2, mapping);
71861
71876
  return {
71862
71877
  size: 0,
@@ -108557,6 +108572,14 @@ var CloudClient = class {
108557
108572
  `/v1/projects/${projectId}/knowledge-snapshots/latest`
108558
108573
  );
108559
108574
  }
108575
+ /** Push a `horus what-changed` report to the linked cloud project. */
108576
+ createChangeReport(projectId, body2) {
108577
+ return this.request(
108578
+ "POST",
108579
+ `/v1/projects/${projectId}/changes`,
108580
+ body2
108581
+ );
108582
+ }
108560
108583
  };
108561
108584
 
108562
108585
  // ../../packages/cli/src/lib/cloud/auth-store.ts
@@ -110030,6 +110053,7 @@ async function uploadInvestigationToCloud(client, cfg, report) {
110030
110053
  await client.createAgentRun(projectId, investigation.id, {
110031
110054
  repositoryId: cfg.repository?.id,
110032
110055
  status: "completed",
110056
+ agent: "Horus CLI",
110033
110057
  cliVersion: HORUS_VERSION,
110034
110058
  summary: report.summary,
110035
110059
  idempotencyKey: idempotencyKey(report.id, "run")
@@ -111157,12 +111181,74 @@ async function runWhatChanged(service, opts) {
111157
111181
  console.error(import_picocolors14.default.yellow(`[ai] ${result.warning}`));
111158
111182
  }
111159
111183
  }
111184
+ if (opts.push) {
111185
+ await pushChangeReport(r, opts.repo);
111186
+ }
111160
111187
  return 0;
111161
111188
  } catch (err2) {
111162
111189
  console.error(import_picocolors14.default.red(err2.message));
111163
111190
  return 1;
111164
111191
  }
111165
111192
  }
111193
+ async function pushChangeReport(r, repoCwd) {
111194
+ const session = authedClient();
111195
+ if (!session) {
111196
+ console.error(import_picocolors14.default.yellow("[cloud] not logged in \u2014 run `horus login` to push reports."));
111197
+ return;
111198
+ }
111199
+ const cfg = readCloudConfig(repoRootOrCwd(repoCwd));
111200
+ if (!isCloudActive(cfg)) {
111201
+ console.error(import_picocolors14.default.yellow("[cloud] repo not linked \u2014 run `horus cloud link` to push reports."));
111202
+ return;
111203
+ }
111204
+ const projectId = cfg.project?.id;
111205
+ if (!projectId) return;
111206
+ const symName = (s5) => {
111207
+ if (s5 && typeof s5 === "object") {
111208
+ const o5 = s5;
111209
+ return String(o5.name ?? o5.fqn ?? o5.id ?? o5.symbol ?? "");
111210
+ }
111211
+ return String(s5);
111212
+ };
111213
+ const ci = r.changeImpact;
111214
+ const payload = {
111215
+ summary: r.summary,
111216
+ note: r.note,
111217
+ window: r.window,
111218
+ commitCount: r.commitCount,
111219
+ topCommits: r.topCommits.map((c4) => ({
111220
+ hash: c4.shortSha,
111221
+ message: c4.subject,
111222
+ author: c4.author,
111223
+ files: c4.files.length
111224
+ })),
111225
+ changeImpact: ci ? {
111226
+ added: ci.added.map(symName),
111227
+ modified: ci.modified.map((m5) => symName(m5.after)),
111228
+ removed: ci.removed.map(symName)
111229
+ } : null,
111230
+ contributors: r.contributors,
111231
+ queueTopology: r.queueTopology
111232
+ };
111233
+ try {
111234
+ await session.client.createChangeReport(projectId, {
111235
+ service: r.window.service ?? void 0,
111236
+ since: r.window.since ?? void 0,
111237
+ until: r.window.until ?? void 0,
111238
+ summary: r.summary,
111239
+ commitCount: r.commitCount,
111240
+ contributorCount: r.contributors.length,
111241
+ symbolsAdded: ci?.added.length ?? 0,
111242
+ symbolsModified: ci?.modified.length ?? 0,
111243
+ symbolsRemoved: ci?.removed.length ?? 0,
111244
+ queueTopologyTouched: r.queueTopology.touched,
111245
+ payload
111246
+ });
111247
+ console.error(import_picocolors14.default.dim("[cloud] pushed change report."));
111248
+ } catch (err2) {
111249
+ console.error(import_picocolors14.default.yellow(`[cloud] push failed: ${err2.message}`));
111250
+ }
111251
+ }
111166
111252
 
111167
111253
  // ../../packages/cli/src/commands/architecture.ts
111168
111254
  init_cjs_shims();
@@ -112037,6 +112123,19 @@ function resolveRawLevel(opts) {
112037
112123
  if (opts.allLevels) return void 0;
112038
112124
  return opts.level ?? "error";
112039
112125
  }
112126
+ function parseWhere(raw) {
112127
+ if (raw === void 0) return [];
112128
+ return raw.map((entry2) => {
112129
+ const eq2 = entry2.indexOf("=");
112130
+ if (eq2 <= 0) {
112131
+ throw new Error(
112132
+ `Invalid --where "${entry2}" \u2014 expected field=value, e.g. --where context.brand_id=42`
112133
+ );
112134
+ }
112135
+ return { field: entry2.slice(0, eq2).trim(), value: entry2.slice(eq2 + 1).trim() };
112136
+ });
112137
+ }
112138
+ var whereLabel = (where) => where.map((w4) => `${w4.field}=${w4.value}`).join(", ");
112040
112139
  function levelColor(level, text2) {
112041
112140
  if (level === "error" || level === "fatal") return import_picocolors27.default.red(text2);
112042
112141
  if (level === "warn") return import_picocolors27.default.yellow(text2);
@@ -112052,6 +112151,15 @@ function matchServiceLabel(requested, candidates) {
112052
112151
  }
112053
112152
  async function runLogs(service, opts) {
112054
112153
  try {
112154
+ let where;
112155
+ try {
112156
+ where = parseWhere(opts.where);
112157
+ } catch (err2) {
112158
+ console.error(import_picocolors27.default.red(err2.message));
112159
+ return 1;
112160
+ }
112161
+ const hasWhere = where.length > 0;
112162
+ const whereArg = hasWhere ? where : void 0;
112055
112163
  const config2 = await loadConfig(opts.config, { name: opts.name });
112056
112164
  let renv;
112057
112165
  try {
@@ -112103,8 +112211,9 @@ async function runLogs(service, opts) {
112103
112211
  const from = sinceToIso(opts.since) ?? new Date(Date.now() - 7 * 864e5).toISOString();
112104
112212
  const fromDisplay = from.slice(0, 16).replace("T", " ");
112105
112213
  if (opts.raw === true) {
112106
- const rawLevel = resolveRawLevel(opts);
112107
- const limit = opts.limit !== void 0 ? Math.min(Number(opts.limit), 1e3) : 20;
112214
+ const rawLevel = hasWhere && opts.level === void 0 ? void 0 : resolveRawLevel(opts);
112215
+ const defaultLimit = hasWhere ? 200 : 20;
112216
+ const limit = opts.limit !== void 0 ? Math.min(Number(opts.limit), 1e3) : defaultLimit;
112108
112217
  const records = await logs.searchLogs({
112109
112218
  service: resolvedService,
112110
112219
  from,
@@ -112113,8 +112222,10 @@ async function runLogs(service, opts) {
112113
112222
  // grep searches message + detail + context.* so error strings buried
112114
112223
  // outside the message field are still found (HOR-216).
112115
112224
  broadText: opts.grep !== void 0,
112225
+ where: whereArg,
112116
112226
  limit
112117
112227
  });
112228
+ const truncated = records.length === limit;
112118
112229
  if (opts.json) {
112119
112230
  console.log(
112120
112231
  JSON.stringify(
@@ -112126,7 +112237,9 @@ async function runLogs(service, opts) {
112126
112237
  index: indexPattern,
112127
112238
  from,
112128
112239
  level: rawLevel ?? "all",
112129
- grep: opts.grep ?? null
112240
+ grep: opts.grep ?? null,
112241
+ where: whereArg ?? null,
112242
+ truncated
112130
112243
  },
112131
112244
  records: records.map((r) => ({
112132
112245
  timestamp: r.timestamp,
@@ -112148,6 +112261,11 @@ async function runLogs(service, opts) {
112148
112261
  );
112149
112262
  return 0;
112150
112263
  }
112264
+ console.error(
112265
+ import_picocolors27.default.dim(
112266
+ `logs: ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + ` \xB7 from ${fromDisplay} UTC \xB7 level ${rawLevel ?? "all"}` + (hasWhere ? ` \xB7 where ${whereLabel(where)}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "") + (rawLevel === "error" && !hasWhere ? " (error+ only \u2014 use --all-levels for info/debug)" : "")
112267
+ )
112268
+ );
112151
112269
  if (records.length === 0) {
112152
112270
  console.log(import_picocolors27.default.dim("No logs matched."));
112153
112271
  return 0;
@@ -112162,6 +112280,13 @@ async function runLogs(service, opts) {
112162
112280
  console.log(import_picocolors27.default.dim(` ${key}: ${value.slice(0, 200)}`));
112163
112281
  }
112164
112282
  }
112283
+ if (truncated) {
112284
+ console.error(
112285
+ import_picocolors27.default.yellow(
112286
+ `[warn] showing the first ${limit} record(s) \u2014 results may be truncated. Narrow with --where/--since/--level or raise --limit (max 1000).`
112287
+ )
112288
+ );
112289
+ }
112165
112290
  return 0;
112166
112291
  }
112167
112292
  if (opts.groupBy !== void 0) {
@@ -112171,7 +112296,8 @@ async function runLogs(service, opts) {
112171
112296
  service: resolvedService,
112172
112297
  from,
112173
112298
  text: opts.grep,
112174
- broadText: opts.grep !== void 0
112299
+ broadText: opts.grep !== void 0,
112300
+ where: whereArg
112175
112301
  },
112176
112302
  field
112177
112303
  );
@@ -112187,6 +112313,7 @@ async function runLogs(service, opts) {
112187
112313
  from,
112188
112314
  severity: "error+",
112189
112315
  grep: opts.grep ?? null,
112316
+ where: whereArg ?? null,
112190
112317
  groupBy: field
112191
112318
  },
112192
112319
  buckets
@@ -112199,7 +112326,7 @@ async function runLogs(service, opts) {
112199
112326
  }
112200
112327
  console.log(
112201
112328
  import_picocolors27.default.bold(`Top ${field}`) + import_picocolors27.default.dim(
112202
- ` \u2014 ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
112329
+ ` \u2014 ${renv.project}/${renv.env}` + (resolvedService ? ` \xB7 service ${resolvedService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "") + (hasWhere ? ` \xB7 where ${whereLabel(where)}` : "")
112203
112330
  )
112204
112331
  );
112205
112332
  console.log(import_picocolors27.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
@@ -112220,12 +112347,13 @@ async function runLogs(service, opts) {
112220
112347
  service: resolvedService,
112221
112348
  from,
112222
112349
  text: opts.grep,
112223
- broadText: opts.grep !== void 0
112350
+ broadText: opts.grep !== void 0,
112351
+ where: whereArg
112224
112352
  });
112225
112353
  let scopeService = resolvedService;
112226
112354
  let broadeningNote;
112227
112355
  if (analysis.signatures.length === 0 && resolvedService !== void 0) {
112228
- const broader = await logs.analyzeErrors({ from, text: opts.grep, broadText: opts.grep !== void 0 });
112356
+ const broader = await logs.analyzeErrors({ from, text: opts.grep, broadText: opts.grep !== void 0, where: whereArg });
112229
112357
  if (broader.signatures.length > 0) {
112230
112358
  const match = matchServiceLabel(resolvedService, broader.affectedServices);
112231
112359
  if (match.kind === "unique" && match.matched !== void 0 && match.matched !== resolvedService) {
@@ -112233,7 +112361,8 @@ async function runLogs(service, opts) {
112233
112361
  service: match.matched,
112234
112362
  from,
112235
112363
  text: opts.grep,
112236
- broadText: opts.grep !== void 0
112364
+ broadText: opts.grep !== void 0,
112365
+ where: whereArg
112237
112366
  });
112238
112367
  if (scoped.signatures.length > 0) {
112239
112368
  analysis = scoped;
@@ -112266,7 +112395,8 @@ async function runLogs(service, opts) {
112266
112395
  index: indexPattern,
112267
112396
  from,
112268
112397
  severity: "error+",
112269
- grep: opts.grep ?? null
112398
+ grep: opts.grep ?? null,
112399
+ where: whereArg ?? null
112270
112400
  },
112271
112401
  totalErrors: analysis.totalErrors,
112272
112402
  signatures: analysis.signatures,
@@ -112281,7 +112411,7 @@ async function runLogs(service, opts) {
112281
112411
  }
112282
112412
  console.log(
112283
112413
  import_picocolors27.default.bold(`Error analysis`) + import_picocolors27.default.dim(
112284
- ` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
112414
+ ` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "") + (hasWhere ? ` \xB7 where ${whereLabel(where)}` : "")
112285
112415
  )
112286
112416
  );
112287
112417
  console.log(import_picocolors27.default.dim(` index: ${indexPattern} \xB7 from: ${fromDisplay} UTC \xB7 severity: error+`));
@@ -116457,7 +116587,7 @@ Examples:
116457
116587
  );
116458
116588
  program2.command("what-changed [service]").description(
116459
116589
  "Concise, evidence-backed summary of what changed for a service in a time window"
116460
- ).option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository name from config").option("--since <when>", 'git --since (default "7 days ago")').option("--until <when>", "git --until").option("--json", "output JSON").option("--ai", "append AI interpretation of the changes").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
116590
+ ).option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository name from config").option("--since <when>", 'git --since (default "7 days ago")').option("--until <when>", "git --until").option("--json", "output JSON").option("--ai", "append AI interpretation of the changes").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").option("--push", "push the report to the linked Horus Cloud project").action(
116461
116591
  async (service, opts) => {
116462
116592
  process.exitCode = await runWhatChanged(service, {
116463
116593
  config: opts.config,
@@ -116466,7 +116596,8 @@ Examples:
116466
116596
  until: opts.until,
116467
116597
  json: opts.json,
116468
116598
  ai: opts.ai,
116469
- aiModel: opts.aiModel
116599
+ aiModel: opts.aiModel,
116600
+ push: opts.push
116470
116601
  });
116471
116602
  }
116472
116603
  );
@@ -116580,7 +116711,12 @@ Examples:
116580
116711
  });
116581
116712
  program2.command("logs [service]").description(
116582
116713
  "Synthesize error evidence from logs (signatures, first/last, affected services); --raw for lines"
116583
- ).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence (error+ by default)").option("--all-levels", "with --raw: show all severity levels, not just error+").option("--group-by <field>", "aggregate error counts by a context field, e.g. context.brand_id").option("--limit <n>", "max records (with --raw)").option("--json", "output JSON").option("--ai", "append AI narration of error signatures (default mode only, not --raw or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
116714
+ ).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence (error+ by default)").option("--all-levels", "with --raw: show all severity levels, not just error+").option("--group-by <field>", "aggregate error counts by a context field, e.g. context.brand_id").option(
116715
+ "--where <field=value>",
116716
+ "filter by a structured context field (repeatable, AND-combined), e.g. --where context.brand_id=42",
116717
+ (val, acc) => [...acc, val],
116718
+ []
116719
+ ).option("--limit <n>", "max records (with --raw)").option("--json", "output JSON").option("--ai", "append AI narration of error signatures (default mode only, not --raw or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
116584
116720
  async (service, opts) => {
116585
116721
  process.exitCode = await runLogs(service, opts);
116586
116722
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.8.10",
3
+ "version": "0.8.12",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {