@appchy/jarvis 0.1.41 → 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 +144 -23
- package/dist/bin.js.map +1 -1
- package/package.json +4 -4
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
|
|
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)
|
|
2687
|
-
else if (exitCode === 0)
|
|
2688
|
-
else
|
|
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:
|
|
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:
|
|
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 = "
|
|
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,
|
|
10714
|
+
function tightened(baseline, report2) {
|
|
10697
10715
|
const allowed = {};
|
|
10698
|
-
for (const rule of /* @__PURE__ */ new Set([...Object.keys(baseline.allowed), ...Object.keys(
|
|
10699
|
-
allowed[rule] = Math.min(baseline.allowed[rule] ?? Number.MAX_SAFE_INTEGER,
|
|
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(
|
|
10726
|
-
const held = Object.entries(
|
|
10727
|
-
if (
|
|
10728
|
-
return `governance baseline recorded: ${held} finding(s) across ${Object.keys(
|
|
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 (
|
|
10731
|
-
return
|
|
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
|
|
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
|
-
`${
|
|
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
|
|
10823
|
-
process.stdout.write(`${describe(
|
|
10840
|
+
const report2 = ratchet(readBaseline(config2.repoRoot), [...diagnostics, ...findings]);
|
|
10841
|
+
process.stdout.write(`${describe(report2, config2.repoRoot)}
|
|
10824
10842
|
`);
|
|
10825
|
-
if (
|
|
10826
|
-
if (!
|
|
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
|
|
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
|
|