@merittdev/horus 0.1.16 → 0.1.18

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 +373 -16
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -50314,7 +50314,7 @@ init_cjs_shims();
50314
50314
 
50315
50315
  // ../../packages/core/src/version.ts
50316
50316
  init_cjs_shims();
50317
- var HORUS_VERSION = true ? "0.1.16" : "dev";
50317
+ var HORUS_VERSION = true ? "0.1.18" : "dev";
50318
50318
  var PINNED_AXON_VERSION = "1.0.7";
50319
50319
  var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
50320
50320
 
@@ -59087,7 +59087,7 @@ function sql(strings, ...params) {
59087
59087
  return new SQL([new StringChunk(str)]);
59088
59088
  }
59089
59089
  sql2.raw = raw;
59090
- function join5(chunks, separator) {
59090
+ function join7(chunks, separator) {
59091
59091
  const result = [];
59092
59092
  for (const [i, chunk] of chunks.entries()) {
59093
59093
  if (i > 0 && separator !== void 0) {
@@ -59097,7 +59097,7 @@ function sql(strings, ...params) {
59097
59097
  }
59098
59098
  return new SQL(result);
59099
59099
  }
59100
- sql2.join = join5;
59100
+ sql2.join = join7;
59101
59101
  function identifier(value) {
59102
59102
  return new Name(value);
59103
59103
  }
@@ -62688,7 +62688,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
62688
62688
  return (table, on) => {
62689
62689
  const baseTableName = this.tableName;
62690
62690
  const tableName = getTableLikeName(table);
62691
- if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
62691
+ if (typeof tableName === "string" && this.config.joins?.some((join7) => join7.alias === tableName)) {
62692
62692
  throw new Error(`Alias "${tableName}" is already used in this query`);
62693
62693
  }
62694
62694
  if (!this.isPartialSelect) {
@@ -63700,7 +63700,7 @@ var PgUpdateBase = class extends QueryPromise {
63700
63700
  createJoin(joinType) {
63701
63701
  return (table, on) => {
63702
63702
  const tableName = getTableLikeName(table);
63703
- if (typeof tableName === "string" && this.config.joins.some((join5) => join5.alias === tableName)) {
63703
+ if (typeof tableName === "string" && this.config.joins.some((join7) => join7.alias === tableName)) {
63704
63704
  throw new Error(`Alias "${tableName}" is already used in this query`);
63705
63705
  }
63706
63706
  if (typeof on === "function") {
@@ -63796,10 +63796,10 @@ var PgUpdateBase = class extends QueryPromise {
63796
63796
  const fromFields = this.getTableLikeFields(this.config.from);
63797
63797
  fields[tableName] = fromFields;
63798
63798
  }
63799
- for (const join5 of this.config.joins) {
63800
- const tableName2 = getTableLikeName(join5.table);
63801
- if (typeof tableName2 === "string" && !is(join5.table, SQL)) {
63802
- const fromFields = this.getTableLikeFields(join5.table);
63799
+ for (const join7 of this.config.joins) {
63800
+ const tableName2 = getTableLikeName(join7.table);
63801
+ if (typeof tableName2 === "string" && !is(join7.table, SQL)) {
63802
+ const fromFields = this.getTableLikeFields(join7.table);
63803
63803
  fields[tableName2] = fromFields;
63804
63804
  }
63805
63805
  }
@@ -72938,22 +72938,22 @@ function extractQueueGraph(input) {
72938
72938
  }
72939
72939
  }
72940
72940
  const producers = [];
72941
- for (const pc35 of input.producerClasses) {
72942
- for (const m of pc35.content.matchAll(INJECT_QUEUE_RE)) {
72941
+ for (const pc37 of input.producerClasses) {
72942
+ for (const m of pc37.content.matchAll(INJECT_QUEUE_RE)) {
72943
72943
  const queue = m[1] ?? "";
72944
- if (queue) producers.push({ queue, symbol: pc35.name, file: pc35.filePath });
72944
+ if (queue) producers.push({ queue, symbol: pc37.name, file: pc37.filePath });
72945
72945
  }
72946
- for (const m of pc35.content.matchAll(NEW_BULL_RE)) {
72946
+ for (const m of pc37.content.matchAll(NEW_BULL_RE)) {
72947
72947
  if (m[2] !== "Queue") continue;
72948
72948
  const queue = resolveArg(m[3] ?? "", constMap);
72949
72949
  if (queue) {
72950
- producers.push({ queue, symbol: (m[1] ?? pc35.name) || baseName(pc35.filePath), file: pc35.filePath });
72950
+ producers.push({ queue, symbol: (m[1] ?? pc37.name) || baseName(pc37.filePath), file: pc37.filePath });
72951
72951
  }
72952
72952
  }
72953
- for (const m of pc35.content.matchAll(QUEUE_NAME_CONST_RE)) {
72953
+ for (const m of pc37.content.matchAll(QUEUE_NAME_CONST_RE)) {
72954
72954
  const ident = m[1] ?? "";
72955
72955
  const queue = m[2] ?? "";
72956
- if (queue) producers.push({ queue, symbol: ident || baseName(pc35.filePath), file: pc35.filePath });
72956
+ if (queue) producers.push({ queue, symbol: ident || baseName(pc37.filePath), file: pc37.filePath });
72957
72957
  }
72958
72958
  }
72959
72959
  const workers = [];
@@ -77899,6 +77899,325 @@ async function runReadiness(opts) {
77899
77899
  return blockingFails.length > 0 ? 1 : 0;
77900
77900
  }
77901
77901
 
77902
+ // ../../packages/cli/src/commands/skill.ts
77903
+ init_cjs_shims();
77904
+ var import_node_fs9 = require("fs");
77905
+ var import_node_path11 = require("path");
77906
+ var import_node_os2 = require("os");
77907
+ var import_picocolors35 = __toESM(require_picocolors(), 1);
77908
+ var SUPPORTED_TARGETS = ["claude", "codex", "gemini", "cursor", "generic"];
77909
+ function isValidTarget(target) {
77910
+ return SUPPORTED_TARGETS.includes(target);
77911
+ }
77912
+ function getSkillInstallPath(target, opts) {
77913
+ const base2 = opts.global ? (0, import_node_os2.homedir)() : opts.cwd ?? process.cwd();
77914
+ if (target === "claude") {
77915
+ return (0, import_node_path11.join)(base2, ".claude", "skills", "horus.md");
77916
+ }
77917
+ return (0, import_node_path11.join)(base2, ".horus", "skills", `horus-${target}.md`);
77918
+ }
77919
+ var BASE_SKILL = `# Horus Incident Investigation Skill
77920
+
77921
+ When investigating a production issue in a repo with Horus configured, use Horus before guessing.
77922
+
77923
+ ## When to use Horus
77924
+
77925
+ Use Horus when the user asks:
77926
+ - why something is broken, slow, or failing
77927
+ - about a production incident or outage
77928
+ - what changed recently
77929
+ - who owns a component or service
77930
+ - about blast radius or impact of a change
77931
+ - about queues, workers, Redis/BullMQ, logs, metrics, MongoDB state, or source paths
77932
+ - to verify a fix or check for regressions after a change
77933
+
77934
+ ## Getting started
77935
+
77936
+ \`\`\`bash
77937
+ horus status
77938
+ horus explain <symbol-or-hint>
77939
+ horus investigate "<incident hint>"
77940
+ \`\`\`
77941
+
77942
+ ## Runtime evidence
77943
+
77944
+ \`\`\`bash
77945
+ horus logs [service]
77946
+ horus state
77947
+ horus metrics "<hint>"
77948
+ horus queues --live
77949
+ \`\`\`
77950
+
77951
+ ## Source reasoning
77952
+
77953
+ \`\`\`bash
77954
+ horus blast-radius <symbol>
77955
+ horus owner <symbol>
77956
+ horus changes <base> [compare]
77957
+ horus timeline
77958
+ horus what-changed
77959
+ \`\`\`
77960
+
77961
+ ## Saved investigations
77962
+
77963
+ \`\`\`bash
77964
+ horus replay <id>
77965
+ horus ask <id> "what evidence is missing?"
77966
+ horus ask <id> "what evidence contradicts <hypothesis>?"
77967
+ horus postmortem <id>
77968
+ horus score <id>
77969
+ \`\`\`
77970
+
77971
+ ## Rules
77972
+
77973
+ - Treat Horus evidence as grounded but not infallible.
77974
+ - Mention when Horus evidence is missing, stale, ambient, or not structurally linked.
77975
+ - Do not invent runtime evidence Horus did not collect.
77976
+ - Prefer exact source locations from Horus over guesses.
77977
+ - If Horus output conflicts with filesystem or source inspection, report the discrepancy.
77978
+ - For write or fix tasks, use Horus to validate blast radius and rerun relevant checks after changes.
77979
+ - Horus is read-only evidence collection \u2014 never use Horus to mutate production systems.
77980
+ - Do not hallucinate evidence. If Horus returns nothing, say so explicitly.
77981
+ `;
77982
+ var CLAUDE_SUFFIX = `## Claude Code notes
77983
+
77984
+ This skill is loaded automatically when present at \`.claude/skills/horus.md\`.
77985
+
77986
+ Useful starting points:
77987
+ - \`horus investigations\` \u2014 list saved investigation IDs
77988
+ - \`horus doctor\` \u2014 check system health
77989
+ - \`horus onboard [area]\` \u2014 get oriented in a new codebase area
77990
+ `;
77991
+ function generateSkillContent(target) {
77992
+ if (target === "claude") {
77993
+ return BASE_SKILL + "\n" + CLAUDE_SUFFIX;
77994
+ }
77995
+ return BASE_SKILL;
77996
+ }
77997
+ async function runSkillInstall(target, opts) {
77998
+ const log = opts.write ?? ((line2) => console.log(line2));
77999
+ if (!isValidTarget(target)) {
78000
+ log(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
78001
+ log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
78002
+ return 1;
78003
+ }
78004
+ const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
78005
+ const content = generateSkillContent(target);
78006
+ if ((0, import_node_fs9.existsSync)(outPath) && !opts.force) {
78007
+ log(`${import_picocolors35.default.red("\u2717")} ${outPath} already exists`);
78008
+ log(import_picocolors35.default.dim(" pass --force to overwrite"));
78009
+ return 1;
78010
+ }
78011
+ try {
78012
+ (0, import_node_fs9.mkdirSync)((0, import_node_path11.dirname)(outPath), { recursive: true });
78013
+ (0, import_node_fs9.writeFileSync)(outPath, content, "utf8");
78014
+ } catch (err) {
78015
+ log(`${import_picocolors35.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
78016
+ return 1;
78017
+ }
78018
+ log(`${import_picocolors35.default.green("\u2713")} Horus skill installed \u2192 ${outPath}`);
78019
+ if (target === "claude") {
78020
+ log(import_picocolors35.default.dim(" Claude Code will load this skill automatically."));
78021
+ log(import_picocolors35.default.dim(" Invoke it with: /horus"));
78022
+ } else {
78023
+ log(import_picocolors35.default.dim(` Copy or reference this file in your ${target} agent instructions.`));
78024
+ }
78025
+ return 0;
78026
+ }
78027
+ async function runSkillPrint(target, opts) {
78028
+ const log = opts.write ?? ((line2) => console.log(line2));
78029
+ if (!isValidTarget(target)) {
78030
+ log(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
78031
+ log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
78032
+ return 1;
78033
+ }
78034
+ process.stdout.write(generateSkillContent(target));
78035
+ return 0;
78036
+ }
78037
+ async function runSkillPath(target, opts) {
78038
+ const log = opts.write ?? ((line2) => console.log(line2));
78039
+ if (!isValidTarget(target)) {
78040
+ log(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
78041
+ log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
78042
+ return 1;
78043
+ }
78044
+ const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
78045
+ log(outPath);
78046
+ return 0;
78047
+ }
78048
+
78049
+ // ../../packages/cli/src/commands/update.ts
78050
+ init_cjs_shims();
78051
+ var import_node_fs10 = require("fs");
78052
+ var import_node_path12 = require("path");
78053
+ var import_node_os3 = require("os");
78054
+ var import_node_crypto2 = require("crypto");
78055
+ var import_picocolors36 = __toESM(require_picocolors(), 1);
78056
+ var RELEASES_API = "https://api.github.com/repos/meritt-dev/horus/releases/latest";
78057
+ function parseVersion(v) {
78058
+ const m = v.replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
78059
+ if (!m) throw new Error(`Cannot parse version: ${v}`);
78060
+ return [+m[1], +m[2], +m[3]];
78061
+ }
78062
+ function isNewer(candidate, current) {
78063
+ const [cam, can, cap] = parseVersion(candidate);
78064
+ const [cum, cun, cup] = parseVersion(current);
78065
+ if (cam !== cum) return cam > cum;
78066
+ if (can !== cun) return can > cun;
78067
+ return cap > cup;
78068
+ }
78069
+ async function fetchLatestRelease(_fetch = fetch) {
78070
+ const res = await _fetch(RELEASES_API, {
78071
+ headers: {
78072
+ "User-Agent": `horus/${HORUS_VERSION}`,
78073
+ Accept: "application/vnd.github.v3+json"
78074
+ }
78075
+ });
78076
+ if (!res.ok) throw new Error(`GitHub API responded ${res.status}`);
78077
+ return res.json();
78078
+ }
78079
+ function sha256hex(buf) {
78080
+ return (0, import_node_crypto2.createHash)("sha256").update(buf).digest("hex");
78081
+ }
78082
+ function resolveBinaryPath() {
78083
+ const raw = process.argv[1];
78084
+ if (!raw) throw new Error("Cannot determine binary path from process.argv[1]");
78085
+ try {
78086
+ return (0, import_node_fs10.realpathSync)(raw);
78087
+ } catch {
78088
+ return raw;
78089
+ }
78090
+ }
78091
+ async function runUpdate(opts) {
78092
+ const write = opts.write ?? ((l) => console.log(l));
78093
+ const _fetch = opts._fetch ?? fetch;
78094
+ const currentVersion = opts._currentVersion ?? HORUS_VERSION;
78095
+ write(`
78096
+ ${import_picocolors36.default.bold("Horus update")}
78097
+ `);
78098
+ write(` Current version: ${import_picocolors36.default.cyan(currentVersion)}`);
78099
+ let release;
78100
+ try {
78101
+ release = await fetchLatestRelease(_fetch);
78102
+ } catch (e) {
78103
+ write(` ${import_picocolors36.default.red("\u2717")} Could not reach GitHub: ${e.message}`);
78104
+ return 1;
78105
+ }
78106
+ const latest = release.tag_name.replace(/^v/, "");
78107
+ write(` Latest version: ${import_picocolors36.default.cyan(latest)}`);
78108
+ const newer = isNewer(latest, currentVersion);
78109
+ if (opts.check) {
78110
+ if (newer) {
78111
+ write(`
78112
+ ${import_picocolors36.default.yellow("\u2192")} ${import_picocolors36.default.bold(latest)} is available.`);
78113
+ write(` Run ${import_picocolors36.default.bold("horus update")} to upgrade.`);
78114
+ } else {
78115
+ write(`
78116
+ ${import_picocolors36.default.green("\u2713")} Already on the latest version.`);
78117
+ }
78118
+ return 0;
78119
+ }
78120
+ if (!newer && !opts.force) {
78121
+ write(`
78122
+ ${import_picocolors36.default.green("\u2713")} Already on the latest version.`);
78123
+ return 0;
78124
+ }
78125
+ const tag = release.tag_name;
78126
+ const assetName = `horus-${tag}`;
78127
+ const checksumName = `${assetName}.sha256`;
78128
+ const binaryAsset = release.assets.find((a) => a.name === assetName);
78129
+ const checksumAsset = release.assets.find((a) => a.name === checksumName);
78130
+ if (!binaryAsset) {
78131
+ write(` ${import_picocolors36.default.red("\u2717")} Release ${tag} has no binary asset named '${assetName}'.`);
78132
+ write(` Fallback: ${import_picocolors36.default.dim("curl -fsSL https://horus.sh/install.sh | bash")}`);
78133
+ return 1;
78134
+ }
78135
+ write(`
78136
+ Downloading ${assetName}...`);
78137
+ const binRes = await _fetch(binaryAsset.browser_download_url, {
78138
+ headers: { "User-Agent": `horus/${HORUS_VERSION}` },
78139
+ redirect: "follow"
78140
+ });
78141
+ if (!binRes.ok || !binRes.body) {
78142
+ write(` ${import_picocolors36.default.red("\u2717")} Download failed: ${binRes.status}`);
78143
+ return 1;
78144
+ }
78145
+ const binBuf = Buffer.from(await binRes.arrayBuffer());
78146
+ if (checksumAsset) {
78147
+ const csRes = await _fetch(checksumAsset.browser_download_url, {
78148
+ headers: { "User-Agent": `horus/${HORUS_VERSION}` },
78149
+ redirect: "follow"
78150
+ });
78151
+ if (csRes.ok) {
78152
+ const csText = await csRes.text();
78153
+ const expectedHash = csText.trim().split(/\s+/)[0];
78154
+ const actualHash = sha256hex(binBuf);
78155
+ if (expectedHash !== actualHash) {
78156
+ write(` ${import_picocolors36.default.red("\u2717")} Checksum mismatch \u2014 download may be corrupt.`);
78157
+ write(` Expected: ${expectedHash}`);
78158
+ write(` Got: ${actualHash}`);
78159
+ return 1;
78160
+ }
78161
+ write(` ${import_picocolors36.default.green("\u2713")} Checksum verified.`);
78162
+ }
78163
+ }
78164
+ let binaryPath;
78165
+ try {
78166
+ binaryPath = resolveBinaryPath();
78167
+ } catch (e) {
78168
+ write(` ${import_picocolors36.default.red("\u2717")} ${e.message}`);
78169
+ return 1;
78170
+ }
78171
+ if (!(0, import_node_fs10.existsSync)(binaryPath)) {
78172
+ write(` ${import_picocolors36.default.red("\u2717")} Binary not found at ${binaryPath}`);
78173
+ return 1;
78174
+ }
78175
+ const tmpPath = (0, import_node_path12.join)((0, import_node_os3.tmpdir)(), `horus-update-${tag}-${process.pid}`);
78176
+ const backupPath = `${binaryPath}.bak`;
78177
+ try {
78178
+ (0, import_node_fs10.writeFileSync)(tmpPath, binBuf, { mode: 493 });
78179
+ } catch (e) {
78180
+ write(` ${import_picocolors36.default.red("\u2717")} Could not write to temp dir: ${e.message}`);
78181
+ return 1;
78182
+ }
78183
+ try {
78184
+ (0, import_node_fs10.renameSync)(binaryPath, backupPath);
78185
+ try {
78186
+ (0, import_node_fs10.renameSync)(tmpPath, binaryPath);
78187
+ (0, import_node_fs10.chmodSync)(binaryPath, 493);
78188
+ } catch (e) {
78189
+ try {
78190
+ (0, import_node_fs10.renameSync)(backupPath, binaryPath);
78191
+ } catch {
78192
+ }
78193
+ throw e;
78194
+ }
78195
+ try {
78196
+ (0, import_node_fs10.unlinkSync)(backupPath);
78197
+ } catch {
78198
+ }
78199
+ } catch (e) {
78200
+ try {
78201
+ (0, import_node_fs10.unlinkSync)(tmpPath);
78202
+ } catch {
78203
+ }
78204
+ const msg = e.message ?? String(e);
78205
+ const needsSudo = e.code === "EACCES" || e.code === "EPERM";
78206
+ write(` ${import_picocolors36.default.red("\u2717")} Could not replace binary at ${binaryPath}: ${msg}`);
78207
+ if (needsSudo) {
78208
+ write(`
78209
+ Try with sudo:`);
78210
+ write(
78211
+ ` ${import_picocolors36.default.dim(`sudo curl -fsSL https://github.com/meritt-dev/horus/releases/download/${tag}/${assetName} -o ${binaryPath} && sudo chmod +x ${binaryPath}`)}`
78212
+ );
78213
+ }
78214
+ return 1;
78215
+ }
78216
+ write(` ${import_picocolors36.default.green("\u2713")} Updated: ${import_picocolors36.default.bold(currentVersion)} \u2192 ${import_picocolors36.default.bold(latest)}`);
78217
+ write(` ${import_picocolors36.default.dim(binaryPath)}`);
78218
+ return 0;
78219
+ }
78220
+
77902
78221
  // ../../packages/cli/src/index.ts
77903
78222
  function buildProgram() {
77904
78223
  const program2 = new Command();
@@ -78211,6 +78530,44 @@ Examples:
78211
78530
  process.exitCode = await runMetrics(hint, opts);
78212
78531
  }
78213
78532
  );
78533
+ const skill = program2.command("skill").description("Manage the Horus agent skill \u2014 install playbooks that teach agents to use Horus as their evidence layer");
78534
+ skill.command("install <target>").description("Install the Horus skill for a coding agent (claude | codex | gemini | cursor | generic)").option("--force", "overwrite an existing skill file").option("--global", "install into the home directory instead of the current repo").action(async (target, opts) => {
78535
+ process.exitCode = await runSkillInstall(target, { force: opts.force, global: opts.global });
78536
+ }).addHelpText("after", `
78537
+ Targets:
78538
+ claude \u2192 .claude/skills/horus.md (Claude Code \u2014 loaded automatically)
78539
+ codex \u2192 .horus/skills/horus-codex.md
78540
+ gemini \u2192 .horus/skills/horus-gemini.md
78541
+ cursor \u2192 .horus/skills/horus-cursor.md
78542
+ generic \u2192 .horus/skills/horus-generic.md
78543
+
78544
+ Examples:
78545
+ horus skill install claude
78546
+ horus skill install generic --force
78547
+ horus skill install claude --global
78548
+ `);
78549
+ skill.command("print <target>").description("Print the Horus skill to stdout \u2014 copy it into any agent setup").action(async (target) => {
78550
+ process.exitCode = await runSkillPrint(target, {});
78551
+ }).addHelpText("after", `
78552
+ Examples:
78553
+ horus skill print generic
78554
+ horus skill print claude
78555
+ `);
78556
+ skill.command("path <target>").description("Print the install path for the given target").option("--global", "show the global (home directory) path").action(async (target, opts) => {
78557
+ process.exitCode = await runSkillPath(target, { global: opts.global });
78558
+ }).addHelpText("after", `
78559
+ Examples:
78560
+ horus skill path claude
78561
+ horus skill path generic --global
78562
+ `);
78563
+ program2.command("update").description("Update Horus to the latest release from GitHub").option("--check", "check for a newer version without downloading").option("--force", "re-download even if already on the latest version").action(async (opts) => {
78564
+ process.exitCode = await runUpdate({ check: opts.check, force: opts.force });
78565
+ }).addHelpText("after", `
78566
+ Examples:
78567
+ horus update # update to latest
78568
+ horus update --check # check without installing
78569
+ horus update --force # re-download current version
78570
+ `);
78214
78571
  return program2;
78215
78572
  }
78216
78573
  async function run(argv = process.argv) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {