@appchy/jarvis 0.1.40 → 0.1.42

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/bin.js CHANGED
@@ -2178,6 +2178,20 @@ function removeHook(settings, hook) {
2178
2178
  if (Object.keys(next.hooks).length === 0) delete next.hooks;
2179
2179
  return { next, changed: true };
2180
2180
  }
2181
+ function claudeCodeHooksState() {
2182
+ const settings = readSettings();
2183
+ return resolveHooks().map((hook) => {
2184
+ const ours = (settings.hooks?.[hook.type] ?? []).filter(ownsEntry).flatMap((entry) => entry.hooks ?? []).map((h) => h.command).filter((cmd) => typeof cmd === "string");
2185
+ const command = ours.find((each) => each === hook.binAbsPath) ?? ours[0];
2186
+ return {
2187
+ type: hook.type,
2188
+ bin: hook.binAbsPath,
2189
+ exists: existsSync(hook.binAbsPath),
2190
+ ...command ? { command } : {}
2191
+ };
2192
+ });
2193
+ }
2194
+ var claudeSettingsFile = SETTINGS_FILE;
2181
2195
  function installClaudeCodeHooks() {
2182
2196
  const configDir2 = getConfigDir();
2183
2197
  if (!ownsClaudeCode()) {
@@ -2659,7 +2673,7 @@ async function live(req) {
2659
2673
  const { term, options } = req;
2660
2674
  let sessionId = req.sessionId;
2661
2675
  if (!sessionId) sessionId = await idOf(term).catch(() => null);
2662
- const report = (status2, reason2) => {
2676
+ const report2 = (status2, reason2) => {
2663
2677
  if (!sessionId) return;
2664
2678
  const state2 = { sessionId, status: status2, ...reason2 ? { reason: reason2 } : {} };
2665
2679
  options.onState?.(state2);
@@ -2683,9 +2697,9 @@ async function live(req) {
2683
2697
  await new Promise((resolve9) => {
2684
2698
  term.onExit(({ exitCode, signal }) => {
2685
2699
  if (idle) clearTimeout(idle);
2686
- if (closedForIdle) report("stopped", "closed after sitting idle");
2687
- else if (exitCode === 0) report("done");
2688
- else report("failed", endedAs(exitCode, signal));
2700
+ if (closedForIdle) report2("stopped", "closed after sitting idle");
2701
+ else if (exitCode === 0) report2("done");
2702
+ else report2("failed", endedAs(exitCode, signal));
2689
2703
  resolve9();
2690
2704
  });
2691
2705
  });
@@ -4368,12 +4382,16 @@ async function recordSession(ctx, req, options) {
4368
4382
  function flag(name, value) {
4369
4383
  return value === void 0 || value === "" ? [] : [name, value];
4370
4384
  }
4385
+ function harnessEnv(options, env) {
4386
+ const { WORK_DIR: _inherited, ...ambient } = process.env;
4387
+ return { ...ambient, CLAUDE_PROJECT_DIR: options.root, ...env };
4388
+ }
4371
4389
  async function harnessSaid(args, options, env = {}) {
4372
4390
  const [command, ...prefix] = options.harness ?? DEFAULT_HARNESS;
4373
4391
  try {
4374
4392
  const { stdout } = await run4(command, [...prefix, ...args], {
4375
4393
  cwd: options.root,
4376
- env: { ...process.env, ...env },
4394
+ env: harnessEnv(options, env),
4377
4395
  maxBuffer: 8 * 1024 * 1024
4378
4396
  });
4379
4397
  return stdout;
@@ -4388,7 +4406,7 @@ async function harness(args, options, env = {}) {
4388
4406
  try {
4389
4407
  const { stdout } = await run4(command, [...prefix, ...args], {
4390
4408
  cwd: options.root,
4391
- env: { ...process.env, ...env },
4409
+ env: harnessEnv(options, env),
4392
4410
  maxBuffer: 8 * 1024 * 1024
4393
4411
  });
4394
4412
  return stdout;
@@ -9594,7 +9612,7 @@ import WebSocket from "ws";
9594
9612
  import { createRequire as createRequire2 } from "module";
9595
9613
  var _require = createRequire2(import.meta.url);
9596
9614
  var VERSION2 = _require("../package.json").version ?? "0.0.0";
9597
- var SHA = "8651453";
9615
+ var SHA = "9d8714f";
9598
9616
  var BUILT = "2026-09-07";
9599
9617
  var BUILD = SHA ?? "source";
9600
9618
  var BUILD_LABEL = SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`;
@@ -10693,10 +10711,10 @@ function ratchet(baseline, findings) {
10693
10711
  }
10694
10712
  return { raised, lowered, counts, passed: raised.length === 0, adopting: false };
10695
10713
  }
10696
- function tightened(baseline, report) {
10714
+ function tightened(baseline, report2) {
10697
10715
  const allowed = {};
10698
- for (const rule of /* @__PURE__ */ new Set([...Object.keys(baseline.allowed), ...Object.keys(report.counts)])) {
10699
- allowed[rule] = Math.min(baseline.allowed[rule] ?? Number.MAX_SAFE_INTEGER, report.counts[rule] ?? 0);
10716
+ for (const rule of /* @__PURE__ */ new Set([...Object.keys(baseline.allowed), ...Object.keys(report2.counts)])) {
10717
+ allowed[rule] = Math.min(baseline.allowed[rule] ?? Number.MAX_SAFE_INTEGER, report2.counts[rule] ?? 0);
10700
10718
  }
10701
10719
  return { allowed, adopted: true };
10702
10720
  }
@@ -10722,17 +10740,17 @@ function touched(repo) {
10722
10740
  return /* @__PURE__ */ new Set();
10723
10741
  }
10724
10742
  }
10725
- function describe(report, repo = "") {
10726
- const held = Object.entries(report.counts).reduce((sum, [, n]) => sum + n, 0);
10727
- if (report.adopting) {
10728
- return `governance baseline recorded: ${held} finding(s) across ${Object.keys(report.counts).length} class(es), written to ${BASELINE}. From here a class may fall and may not rise.`;
10743
+ function describe(report2, repo = "") {
10744
+ const held = Object.entries(report2.counts).reduce((sum, [, n]) => sum + n, 0);
10745
+ if (report2.adopting) {
10746
+ return `governance baseline recorded: ${held} finding(s) across ${Object.keys(report2.counts).length} class(es), written to ${BASELINE}. From here a class may fall and may not rise.`;
10729
10747
  }
10730
- if (report.passed) {
10731
- return report.lowered.length > 0 ? `governance held: ${report.lowered.map((each) => `${each.rule} ${each.was}\u2192${each.now}`).join(", ")} \u2014 baseline tightened. ${held} finding(s) carried.` : `governance held: ${held} finding(s) carried, none added.`;
10748
+ if (report2.passed) {
10749
+ return report2.lowered.length > 0 ? `governance held: ${report2.lowered.map((each) => `${each.rule} ${each.was}\u2192${each.now}`).join(", ")} \u2014 baseline tightened. ${held} finding(s) carried.` : `governance held: ${held} finding(s) carried, none added.`;
10732
10750
  }
10733
10751
  const lines = [];
10734
10752
  const dirty = repo ? touched(repo) : /* @__PURE__ */ new Set();
10735
- for (const each of report.raised) {
10753
+ for (const each of report2.raised) {
10736
10754
  lines.push(` ${each.rule}: ${each.was} \u2192 ${each.now} (+${each.now - each.was})`);
10737
10755
  const hits = [...each.hits].sort(
10738
10756
  (left, right) => Number(dirty.has(right.file ?? "")) - Number(dirty.has(left.file ?? ""))
@@ -10744,7 +10762,7 @@ function describe(report, repo = "") {
10744
10762
  if (hits.length > NAMED) lines.push(` \u2026and ${hits.length - NAMED} more`);
10745
10763
  }
10746
10764
  return [
10747
- `${report.raised.length} governance class(es) went up. This repo already carried the`,
10765
+ `${report2.raised.length} governance class(es) went up. This repo already carried the`,
10748
10766
  `rest; what fails here is adding to them.`,
10749
10767
  ...lines,
10750
10768
  ``,
@@ -10819,11 +10837,11 @@ ${formatLedger(ledger)}
10819
10837
  `
10820
10838
  );
10821
10839
  if (gate) {
10822
- const report = ratchet(readBaseline(config2.repoRoot), [...diagnostics, ...findings]);
10823
- process.stdout.write(`${describe(report, config2.repoRoot)}
10840
+ const report2 = ratchet(readBaseline(config2.repoRoot), [...diagnostics, ...findings]);
10841
+ process.stdout.write(`${describe(report2, config2.repoRoot)}
10824
10842
  `);
10825
- if (report.passed) writeBaseline(config2.repoRoot, tightened(readBaseline(config2.repoRoot), report));
10826
- if (!report.passed) return 1;
10843
+ if (report2.passed) writeBaseline(config2.repoRoot, tightened(readBaseline(config2.repoRoot), report2));
10844
+ if (!report2.passed) return 1;
10827
10845
  }
10828
10846
  return errors > 0 ? 1 : 0;
10829
10847
  }
@@ -11588,9 +11606,111 @@ function register17(program) {
11588
11606
  });
11589
11607
  }
11590
11608
 
11609
+ // src/commands/hooks.ts
11610
+ var GREEN2 = (text) => `\x1B[32m${text}\x1B[0m`;
11611
+ var RED2 = (text) => `\x1B[31m${text}\x1B[0m`;
11612
+ var YELLOW = (text) => `\x1B[33m${text}\x1B[0m`;
11613
+ function quietInstallerNarration() {
11614
+ const drop = () => {
11615
+ };
11616
+ const provider2 = {
11617
+ debug: drop,
11618
+ info: drop,
11619
+ warn: (_ctx, message) => console.warn(` ${message}`),
11620
+ error: (_ctx, message) => console.error(` ${message}`),
11621
+ audit: drop
11622
+ };
11623
+ logger.init({ provider: provider2 });
11624
+ }
11625
+ function isOurs(hook) {
11626
+ return hook.command === hook.bin;
11627
+ }
11628
+ function isElsewhere(hook) {
11629
+ return hook.command !== void 0 && hook.command !== hook.bin;
11630
+ }
11631
+ function report(state2) {
11632
+ console.log(` Settings: ${claudeSettingsFile}`);
11633
+ for (const hook of state2) {
11634
+ const mark = isOurs(hook) ? GREEN2("installed") : isElsewhere(hook) ? YELLOW("another install") : RED2("missing");
11635
+ const gone = hook.exists ? "" : RED2(" \u2014 the shim it names is not there");
11636
+ console.log(` ${hook.type.padEnd(17)} ${mark}${gone}`);
11637
+ console.log(` ${"".padEnd(17)} ${hook.command ?? hook.bin}`);
11638
+ }
11639
+ if (state2.some(isElsewhere)) {
11640
+ console.log(
11641
+ `
11642
+ Claude Code is running hooks from a different copy of the CLI than this one.
11643
+ Both work, and the one you are reading is not the one running. Install from
11644
+ whichever copy you mean to keep, or remove the other.`
11645
+ );
11646
+ }
11647
+ }
11648
+ function refuseNamedConfigDir() {
11649
+ if (ownsClaudeCode()) return false;
11650
+ console.error(
11651
+ `This config dir does not own this machine's Claude Code, so nothing was changed.
11652
+ Config dir: ${getConfigDir()}
11653
+ There is one Claude Code per machine and the default dir owns it. Run this
11654
+ without JARVIS_CONFIG_DIR set.`
11655
+ );
11656
+ return true;
11657
+ }
11658
+ function register18(program) {
11659
+ const hooks = program.command("hooks").description("Wire this machine's Claude Code to jarvis \u2014 no pairing, no daemon");
11660
+ hooks.command("install").description("Install the Claude Code hooks (the session block and the rules on edit)").action(() => {
11661
+ if (refuseNamedConfigDir()) process.exit(1);
11662
+ quietInstallerNarration();
11663
+ installClaudeCodeHooks();
11664
+ const state2 = claudeCodeHooksState();
11665
+ const installed = state2.filter(isOurs).length;
11666
+ if (installed === 0) {
11667
+ console.error("No hooks were installed.");
11668
+ report(state2);
11669
+ console.error(
11670
+ `
11671
+ Every shim above is missing, which means this is not a complete install.
11672
+ Reinstall the CLI: npm i -g @appchy/jarvis`
11673
+ );
11674
+ process.exit(1);
11675
+ }
11676
+ console.log(`Claude Code hooks installed (${installed} of ${state2.length}):`);
11677
+ report(state2);
11678
+ console.log(`
11679
+ Open Claude Code in a repo that has a work/ tree to see them.`);
11680
+ });
11681
+ hooks.command("status").description("What Claude Code will actually run").action(() => {
11682
+ const state2 = claudeCodeHooksState();
11683
+ const installed = state2.filter(isOurs).length;
11684
+ console.log(
11685
+ installed === state2.length ? `All ${state2.length} Claude Code hooks are installed:` : `${installed} of ${state2.length} Claude Code hooks are installed:`
11686
+ );
11687
+ report(state2);
11688
+ if (!ownsClaudeCode()) {
11689
+ console.log(
11690
+ `
11691
+ Note: this config dir (${getConfigDir()}) does not own this machine's
11692
+ Claude Code, so it neither installs these nor removes them.`
11693
+ );
11694
+ }
11695
+ });
11696
+ hooks.command("uninstall").description("Remove the Claude Code hooks this machine installed").action(() => {
11697
+ if (refuseNamedConfigDir()) process.exit(1);
11698
+ quietInstallerNarration();
11699
+ uninstallClaudeCodeHooks();
11700
+ const left = claudeCodeHooksState().filter(isOurs);
11701
+ if (left.length === 0) {
11702
+ console.log("Claude Code hooks removed.");
11703
+ return;
11704
+ }
11705
+ console.error(`${left.length} hook(s) could not be removed:`);
11706
+ report(left);
11707
+ process.exit(1);
11708
+ });
11709
+ }
11710
+
11591
11711
  // src/commands/work.ts
11592
11712
  import { spawnSync as spawnSync3 } from "child_process";
11593
- function register18(program) {
11713
+ function register19(program) {
11594
11714
  program.command("work").description("The board \u2014 take work, prove it, finish it").argument("[args...]", "Passed to the harness untouched").allowUnknownOption().passThroughOptions().helpOption(false).action((passed) => {
11595
11715
  const problem = harnessProblem();
11596
11716
  if (problem) {
@@ -11631,6 +11751,7 @@ function createCli() {
11631
11751
  register16(program);
11632
11752
  register17(program);
11633
11753
  register18(program);
11754
+ register19(program);
11634
11755
  return program;
11635
11756
  }
11636
11757