@appchy/jarvis 0.1.43 → 0.1.45
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/bin/jarvis.mjs +78 -12
- package/dist/bin.js +197 -78
- package/dist/bin.js.map +1 -1
- package/dist/data/backends.mjs +2 -2
- package/dist/data/{chunk-AKQQC5IT.mjs → chunk-AA75N6NR.mjs} +3 -3
- package/dist/data/{chunk-7REP35VA.mjs → chunk-LYW75USL.mjs} +9 -0
- package/dist/data/embedders.mjs +10 -49
- package/dist/data/index.mjs +2 -2
- package/dist/data/labelers.mjs +107 -0
- package/dist/data/linkers.mjs +1 -1
- package/dist/data/mcp.mjs +2 -2
- package/dist/data/stores.mjs +2 -2
- package/dist/hooks/config-change.js.map +1 -1
- package/dist/hooks/pre-tool-use.js.map +1 -1
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop.js.map +1 -1
- package/dist/hooks/user-prompt-submit.js.map +1 -1
- package/harness/test_work.py +11 -0
- package/package.json +6 -6
- package/dist/data/rerankers.mjs +0 -52
package/bin/jarvis.mjs
CHANGED
|
@@ -8,25 +8,91 @@
|
|
|
8
8
|
* pairs, so one binary serves our cloud, a self-hosted instance and a laptop
|
|
9
9
|
* alike. For source-tree iteration with hot reload, run
|
|
10
10
|
* `pnpm -C apps/cli dev` — that runs `tsx src/bin.ts` directly.
|
|
11
|
+
*
|
|
12
|
+
* It does decide WHICH BUILD, for the two commands a repo reaches jarvis with.
|
|
13
|
+
* A repo names jarvis by `command: "jarvis"` in a checked-in `.mcp.json`, so the
|
|
14
|
+
* only place a machine-specific answer can live is here.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
import { fileURLToPath } from "url";
|
|
14
|
-
import { dirname, join } from "path";
|
|
15
|
-
import { existsSync } from "fs";
|
|
18
|
+
import { dirname, isAbsolute, join, relative } from "path";
|
|
19
|
+
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
20
|
+
import { homedir } from "os";
|
|
16
21
|
|
|
17
22
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
23
|
const distEntry = join(__dirname, "..", "dist", "bin.js");
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
/** The commands a repo reaches jarvis with. Machine lifecycle — `start`, `init`, `pair` —
|
|
26
|
+
* is deliberately absent: a redirect meant for an MCP server must never move the daemon,
|
|
27
|
+
* which owns this machine's Claude Code and is identified by its config dir, not its build. */
|
|
28
|
+
const REDIRECTED = new Set(["serve", "work"]);
|
|
29
|
+
|
|
30
|
+
/** The development build to run here, or null for the one this launcher ships with.
|
|
31
|
+
* `src/dev.ts` answers the same question for `jarvis dev status`; it cannot be imported,
|
|
32
|
+
* because this has to decide before any bundle is loaded. Keep the two answers identical. */
|
|
33
|
+
function devBuild() {
|
|
34
|
+
const command = process.argv[2];
|
|
35
|
+
if (!REDIRECTED.has(command)) return null;
|
|
36
|
+
|
|
37
|
+
const configDir = process.env.JARVIS_CONFIG_DIR ?? join(homedir(), ".jarvis");
|
|
38
|
+
let redirect;
|
|
39
|
+
try {
|
|
40
|
+
redirect = JSON.parse(readFileSync(join(configDir, "dev.json"), "utf-8"));
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
28
43
|
}
|
|
29
|
-
|
|
44
|
+
if (typeof redirect?.root !== "string") return null;
|
|
45
|
+
|
|
46
|
+
// Every symlink resolved on both sides: an unresolved comparison fails wherever a home
|
|
47
|
+
// or temp directory is a link, and that failure is indistinguishable from being switched off.
|
|
48
|
+
const real = (target) => {
|
|
49
|
+
try {
|
|
50
|
+
return realpathSync(target);
|
|
51
|
+
} catch {
|
|
52
|
+
return target;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const cwd = real(process.cwd());
|
|
56
|
+
const here =
|
|
57
|
+
redirect.all === true ||
|
|
58
|
+
(Array.isArray(redirect.repos) &&
|
|
59
|
+
redirect.repos.some((repo) => {
|
|
60
|
+
if (typeof repo !== "string") return false;
|
|
61
|
+
const rel = relative(real(repo), cwd);
|
|
62
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
63
|
+
}));
|
|
64
|
+
if (!here) return null;
|
|
65
|
+
|
|
66
|
+
const entry = join(redirect.root, "apps", "cli", "dist", "bin.js");
|
|
67
|
+
if (!existsSync(entry)) {
|
|
68
|
+
// Falling back silently would serve the installed build under the name of the one
|
|
69
|
+
// being tested, which is the shape of this bug nobody catches: the half you look at
|
|
70
|
+
// is right. stderr, because stdout is the MCP transport.
|
|
71
|
+
process.stderr.write(
|
|
72
|
+
`jarvis: ${redirect.root} has no build to run — using the installed one.\n` +
|
|
73
|
+
` Build it with \`pnpm --filter @appchy/jarvis build\`.\n`,
|
|
74
|
+
);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return entry;
|
|
30
78
|
}
|
|
31
79
|
|
|
32
|
-
|
|
80
|
+
const dev = devBuild();
|
|
81
|
+
if (dev) {
|
|
82
|
+
process.stderr.write(`jarvis: development build, from ${dev}\n`);
|
|
83
|
+
await import(dev);
|
|
84
|
+
} else {
|
|
85
|
+
if (!existsSync(distEntry)) {
|
|
86
|
+
const isSourceCheckout = existsSync(join(__dirname, "..", "src", "bin.ts"));
|
|
87
|
+
console.error("Error: dist/bin.js not found.");
|
|
88
|
+
if (isSourceCheckout) {
|
|
89
|
+
console.error(" This is a source checkout. Run `pnpm -C apps/cli dev` for dev work,");
|
|
90
|
+
console.error(" or `pnpm -C apps/cli build` to produce a bundle.");
|
|
91
|
+
} else {
|
|
92
|
+
console.error(" The package may be corrupted. Reinstall with `pnpm i -g @appchy/jarvis`.");
|
|
93
|
+
}
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await import(distEntry);
|
|
98
|
+
}
|
package/dist/bin.js
CHANGED
|
@@ -4433,8 +4433,8 @@ async function scanTree(root) {
|
|
|
4433
4433
|
for (const file of await walk(join3(work, base))) {
|
|
4434
4434
|
const text = await readFile(file, "utf-8");
|
|
4435
4435
|
const changedAt = (await stat(file)).mtime.toISOString().slice(0, 10);
|
|
4436
|
-
const
|
|
4437
|
-
const item = toWorkItem({ path:
|
|
4436
|
+
const path22 = relative(work, file).split(sep).join("/");
|
|
4437
|
+
const item = toWorkItem({ path: path22, text, changedAt });
|
|
4438
4438
|
if (item) items.push(item);
|
|
4439
4439
|
}
|
|
4440
4440
|
}
|
|
@@ -5400,8 +5400,8 @@ function reason(err) {
|
|
|
5400
5400
|
}
|
|
5401
5401
|
function issueText(issues) {
|
|
5402
5402
|
return issues.slice(0, 3).map((issue) => {
|
|
5403
|
-
const
|
|
5404
|
-
return
|
|
5403
|
+
const path22 = (issue.path ?? []).map((step) => String(typeof step === "object" ? step.key : step)).join(".");
|
|
5404
|
+
return path22 ? `${path22}: ${issue.message}` : issue.message;
|
|
5405
5405
|
}).join("; ");
|
|
5406
5406
|
}
|
|
5407
5407
|
function keyText(data) {
|
|
@@ -5689,9 +5689,9 @@ function localFiles(options = {}) {
|
|
|
5689
5689
|
}
|
|
5690
5690
|
},
|
|
5691
5691
|
save: async (ctx, key, bytes) => {
|
|
5692
|
-
const
|
|
5693
|
-
await mkdir(dirname3(
|
|
5694
|
-
await writeFile(
|
|
5692
|
+
const path22 = pathOf(ctx, key);
|
|
5693
|
+
await mkdir(dirname3(path22), { recursive: true });
|
|
5694
|
+
await writeFile(path22, bytes, "utf8");
|
|
5695
5695
|
},
|
|
5696
5696
|
locate: (ctx, key) => pathOf(ctx, key)
|
|
5697
5697
|
});
|
|
@@ -5709,10 +5709,10 @@ async function loadConfig2(cwd) {
|
|
|
5709
5709
|
let dir = resolve3(cwd);
|
|
5710
5710
|
for (; ; ) {
|
|
5711
5711
|
for (const name of CONFIG_NAMES) {
|
|
5712
|
-
const
|
|
5713
|
-
if (existsSync5(
|
|
5714
|
-
const config2 = await importConfig(
|
|
5715
|
-
validateConfig(config2,
|
|
5712
|
+
const path22 = resolve3(dir, name);
|
|
5713
|
+
if (existsSync5(path22)) {
|
|
5714
|
+
const config2 = await importConfig(path22);
|
|
5715
|
+
validateConfig(config2, path22);
|
|
5716
5716
|
return {
|
|
5717
5717
|
...config2,
|
|
5718
5718
|
persistence: config2.persistence ?? localFiles(),
|
|
@@ -5728,9 +5728,9 @@ async function loadConfig2(cwd) {
|
|
|
5728
5728
|
`No jarvis.config.{ts,js,mjs} found from ${cwd}. A repo maps its world in one, at its root.`
|
|
5729
5729
|
);
|
|
5730
5730
|
}
|
|
5731
|
-
async function importConfig(
|
|
5731
|
+
async function importConfig(path22) {
|
|
5732
5732
|
const jiti = createJiti(import.meta.url, { moduleCache: false, alias: selfAlias() });
|
|
5733
|
-
const mod = await jiti.import(
|
|
5733
|
+
const mod = await jiti.import(path22);
|
|
5734
5734
|
const config2 = mod.default ?? mod;
|
|
5735
5735
|
return config2;
|
|
5736
5736
|
}
|
|
@@ -5770,9 +5770,9 @@ var VENDOR_SUBPATHS = [
|
|
|
5770
5770
|
"backends",
|
|
5771
5771
|
"embedders",
|
|
5772
5772
|
"finders",
|
|
5773
|
+
"labelers",
|
|
5773
5774
|
"linkers",
|
|
5774
5775
|
"persistences",
|
|
5775
|
-
"rerankers",
|
|
5776
5776
|
"stores"
|
|
5777
5777
|
];
|
|
5778
5778
|
function toVendorAlias(dir) {
|
|
@@ -5783,9 +5783,9 @@ function toVendorAlias(dir) {
|
|
|
5783
5783
|
function toSpecifier(subpath) {
|
|
5784
5784
|
return subpath === "." ? PACKAGE_NAME : `${PACKAGE_NAME}/${subpath.replace(/^\.\//, "")}`;
|
|
5785
5785
|
}
|
|
5786
|
-
function validateConfig(value,
|
|
5786
|
+
function validateConfig(value, path22) {
|
|
5787
5787
|
const fail = (msg) => {
|
|
5788
|
-
throw new Error(`${
|
|
5788
|
+
throw new Error(`${path22}: ${msg}`);
|
|
5789
5789
|
};
|
|
5790
5790
|
if (typeof value !== "object" || value === null) fail("config must export an object");
|
|
5791
5791
|
const c = value;
|
|
@@ -5809,11 +5809,11 @@ function validateConfig(value, path20) {
|
|
|
5809
5809
|
const search3 = c.search;
|
|
5810
5810
|
if (search3 !== void 0) {
|
|
5811
5811
|
if (typeof search3.embedder?.embed !== "function")
|
|
5812
|
-
fail("`search.embedder` must be an embedder (e.g.
|
|
5812
|
+
fail("`search.embedder` must be an embedder (e.g. openaiEmbeddings())");
|
|
5813
5813
|
if (search3.store !== void 0 && typeof search3.store.query !== "function")
|
|
5814
5814
|
fail("`search.store` must be a vector store");
|
|
5815
5815
|
if (search3.reranker !== void 0 && typeof search3.reranker.rerank !== "function")
|
|
5816
|
-
fail("`search.reranker` must be a reranker
|
|
5816
|
+
fail("`search.reranker` must be a reranker");
|
|
5817
5817
|
}
|
|
5818
5818
|
}
|
|
5819
5819
|
|
|
@@ -5822,6 +5822,7 @@ async function buildEmbeddings(config2, graph2) {
|
|
|
5822
5822
|
const cfg = config2.search;
|
|
5823
5823
|
if (!cfg) return void 0;
|
|
5824
5824
|
const embedder2 = cfg.embedder.resolve ? await cfg.embedder.resolve() : cfg.embedder;
|
|
5825
|
+
if (!embedder2) return declined(cfg.embedder, "no semantic index was built");
|
|
5825
5826
|
const storeImpl = cfg.store;
|
|
5826
5827
|
const nodes = cfg.types ? cfg.types.flatMap((t) => graph2.nodes(t)) : graph2.nodes();
|
|
5827
5828
|
const ctx = { repoRoot: config2.repoRoot };
|
|
@@ -5880,6 +5881,7 @@ async function loadEmbeddings(config2, graph2) {
|
|
|
5880
5881
|
const cfg = config2.search;
|
|
5881
5882
|
if (!cfg) return void 0;
|
|
5882
5883
|
const embedder2 = cfg.embedder.resolve ? await cfg.embedder.resolve() : cfg.embedder;
|
|
5884
|
+
if (!embedder2) return declined(cfg.embedder, "search is lexical");
|
|
5883
5885
|
await probeEmbedder(embedder2);
|
|
5884
5886
|
const storeImpl = cfg.store;
|
|
5885
5887
|
const nodes = cfg.types ? cfg.types.flatMap((t) => graph2.nodes(t)) : graph2.nodes();
|
|
@@ -5911,6 +5913,13 @@ function toRuntime(cfg, embedder2, storeImpl, items) {
|
|
|
5911
5913
|
...cfg.confidence ? { confidence: cfg.confidence } : {}
|
|
5912
5914
|
};
|
|
5913
5915
|
}
|
|
5916
|
+
function declined(embedder2, consequence) {
|
|
5917
|
+
process.stderr.write(
|
|
5918
|
+
`[data:warn] embedder ${embedder2.name} is not available on this machine \u2014 ${consequence}. Token-overlap (BM25) search still applies.
|
|
5919
|
+
`
|
|
5920
|
+
);
|
|
5921
|
+
return void 0;
|
|
5922
|
+
}
|
|
5914
5923
|
async function probeEmbedder(embedder2) {
|
|
5915
5924
|
try {
|
|
5916
5925
|
await embedder2.embed(["probe"]);
|
|
@@ -6491,10 +6500,10 @@ async function git3(root, argv, timeout = 5e3) {
|
|
|
6491
6500
|
}
|
|
6492
6501
|
}
|
|
6493
6502
|
async function fetchHeadIso(root) {
|
|
6494
|
-
const
|
|
6495
|
-
if (!
|
|
6503
|
+
const path22 = await git3(root, ["rev-parse", "--git-path", "FETCH_HEAD"]);
|
|
6504
|
+
if (!path22) return void 0;
|
|
6496
6505
|
try {
|
|
6497
|
-
return (await stat2(resolve4(root,
|
|
6506
|
+
return (await stat2(resolve4(root, path22))).mtime.toISOString();
|
|
6498
6507
|
} catch {
|
|
6499
6508
|
return void 0;
|
|
6500
6509
|
}
|
|
@@ -6571,10 +6580,10 @@ async function freshness(ctx) {
|
|
|
6571
6580
|
};
|
|
6572
6581
|
}
|
|
6573
6582
|
async function snapshotMtimeMs(ctx) {
|
|
6574
|
-
const
|
|
6575
|
-
if (!
|
|
6583
|
+
const path22 = ctx.config.persistence?.locate?.({ repoRoot: ctx.config.repoRoot }, "snapshot");
|
|
6584
|
+
if (!path22) return void 0;
|
|
6576
6585
|
try {
|
|
6577
|
-
return (await stat2(
|
|
6586
|
+
return (await stat2(path22)).mtimeMs;
|
|
6578
6587
|
} catch {
|
|
6579
6588
|
return void 0;
|
|
6580
6589
|
}
|
|
@@ -6996,10 +7005,10 @@ async function cachedEnforcerDiags(ctx, freshPaths) {
|
|
|
6996
7005
|
}
|
|
6997
7006
|
function isFresh(snapshot, repoRoot, freshPaths) {
|
|
6998
7007
|
if (!freshPaths || freshPaths.length === 0) return true;
|
|
6999
|
-
for (const
|
|
7008
|
+
for (const path22 of freshPaths) {
|
|
7000
7009
|
let mtimeMs;
|
|
7001
7010
|
try {
|
|
7002
|
-
mtimeMs = statSync2(resolve5(repoRoot,
|
|
7011
|
+
mtimeMs = statSync2(resolve5(repoRoot, path22)).mtimeMs;
|
|
7003
7012
|
} catch {
|
|
7004
7013
|
return false;
|
|
7005
7014
|
}
|
|
@@ -7652,15 +7661,15 @@ import { readFile as readFile5 } from "fs/promises";
|
|
|
7652
7661
|
import { resolve as resolve6 } from "path";
|
|
7653
7662
|
|
|
7654
7663
|
// ../../packages/data/src/meta.ts
|
|
7655
|
-
function stringMeta(metadata,
|
|
7656
|
-
const value = metaValue(metadata,
|
|
7664
|
+
function stringMeta(metadata, path22) {
|
|
7665
|
+
const value = metaValue(metadata, path22);
|
|
7657
7666
|
return typeof value === "string" ? value : void 0;
|
|
7658
7667
|
}
|
|
7659
|
-
function metaValue(metadata,
|
|
7668
|
+
function metaValue(metadata, path22) {
|
|
7660
7669
|
if (!metadata) return void 0;
|
|
7661
|
-
if (Object.prototype.hasOwnProperty.call(metadata,
|
|
7670
|
+
if (Object.prototype.hasOwnProperty.call(metadata, path22)) return metadata[path22];
|
|
7662
7671
|
let current = metadata;
|
|
7663
|
-
for (const part of
|
|
7672
|
+
for (const part of path22.split(".")) {
|
|
7664
7673
|
if (!current || typeof current !== "object" || !Object.prototype.hasOwnProperty.call(current, part))
|
|
7665
7674
|
return void 0;
|
|
7666
7675
|
current = current[part];
|
|
@@ -7771,8 +7780,8 @@ function resolveScopeGovernance(ctx, members, codeNodes) {
|
|
|
7771
7780
|
}
|
|
7772
7781
|
return [...out.values()];
|
|
7773
7782
|
}
|
|
7774
|
-
function resolveDirectoryGovernance(ctx,
|
|
7775
|
-
let dir =
|
|
7783
|
+
function resolveDirectoryGovernance(ctx, path22) {
|
|
7784
|
+
let dir = path22.includes("/") ? path22.slice(0, path22.lastIndexOf("/")) : "";
|
|
7776
7785
|
for (; ; ) {
|
|
7777
7786
|
const siblings = codeNodesInDir(ctx, dir);
|
|
7778
7787
|
if (siblings.length > 0) return resolveScopeGovernance(ctx, [], siblings);
|
|
@@ -8333,15 +8342,15 @@ async function review(ctx, req) {
|
|
|
8333
8342
|
cites: []
|
|
8334
8343
|
});
|
|
8335
8344
|
}
|
|
8336
|
-
for (const
|
|
8337
|
-
const dirGov = resolveDirectoryGovernance(ctx,
|
|
8345
|
+
for (const path22 of unresolved) {
|
|
8346
|
+
const dirGov = resolveDirectoryGovernance(ctx, path22).filter((n) => !isWorkItem(n)).map((g) => g.id).filter((id) => !cites.has(id)).slice(0, 3);
|
|
8338
8347
|
const hint = dirGov.length ? ` Files in its directory are governed by ${dirGov.join(", ")} \u2014 cite if it applies.` : "";
|
|
8339
8348
|
findings.push({
|
|
8340
8349
|
code: "unresolved-edit",
|
|
8341
8350
|
severity: "info",
|
|
8342
8351
|
confidence: "precise",
|
|
8343
|
-
subject:
|
|
8344
|
-
message: `${
|
|
8352
|
+
subject: path22,
|
|
8353
|
+
message: `${path22} isn't in the graph yet (a new file, or a path that doesn't match).${hint || " No governed siblings to assess it by."}`,
|
|
8345
8354
|
cites: dirGov
|
|
8346
8355
|
});
|
|
8347
8356
|
}
|
|
@@ -9192,8 +9201,8 @@ async function readFileContent(workdir, relPath) {
|
|
|
9192
9201
|
|
|
9193
9202
|
// src/workspace.ts
|
|
9194
9203
|
var workspacePath = null;
|
|
9195
|
-
function setWorkspacePath(
|
|
9196
|
-
workspacePath =
|
|
9204
|
+
function setWorkspacePath(path22) {
|
|
9205
|
+
workspacePath = path22;
|
|
9197
9206
|
}
|
|
9198
9207
|
function getWorkspacePath() {
|
|
9199
9208
|
if (!workspacePath) {
|
|
@@ -9612,10 +9621,11 @@ import WebSocket from "ws";
|
|
|
9612
9621
|
import { createRequire as createRequire2 } from "module";
|
|
9613
9622
|
var _require = createRequire2(import.meta.url);
|
|
9614
9623
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
9615
|
-
var
|
|
9624
|
+
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
9625
|
+
var SHA = "86c8e54";
|
|
9616
9626
|
var BUILT = "2026-09-07";
|
|
9617
9627
|
var BUILD = SHA ?? "source";
|
|
9618
|
-
var BUILD_LABEL = SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`;
|
|
9628
|
+
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
9619
9629
|
|
|
9620
9630
|
// src/upstream.ts
|
|
9621
9631
|
function createUpstreamClient(config2) {
|
|
@@ -10542,12 +10552,12 @@ function register14(program) {
|
|
|
10542
10552
|
}
|
|
10543
10553
|
|
|
10544
10554
|
// src/commands/mcp.ts
|
|
10545
|
-
async function call(
|
|
10555
|
+
async function call(path22, method, body) {
|
|
10546
10556
|
const config2 = loadConfig();
|
|
10547
10557
|
if (!config2?.token) {
|
|
10548
10558
|
throw new Error("not connected \u2014 run `jarvis connect` first");
|
|
10549
10559
|
}
|
|
10550
|
-
const resp = await fetch(new URL(
|
|
10560
|
+
const resp = await fetch(new URL(path22, config2.url ?? getJarvisUrl()), {
|
|
10551
10561
|
method,
|
|
10552
10562
|
headers: {
|
|
10553
10563
|
authorization: `Bearer ${config2.token}`,
|
|
@@ -10556,7 +10566,7 @@ async function call(path20, method, body) {
|
|
|
10556
10566
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
10557
10567
|
});
|
|
10558
10568
|
if (!resp.ok) {
|
|
10559
|
-
throw new Error(`${method} ${
|
|
10569
|
+
throw new Error(`${method} ${path22} failed: ${resp.status} ${await resp.text()}`);
|
|
10560
10570
|
}
|
|
10561
10571
|
return await resp.json();
|
|
10562
10572
|
}
|
|
@@ -10613,6 +10623,33 @@ function register15(program) {
|
|
|
10613
10623
|
});
|
|
10614
10624
|
}
|
|
10615
10625
|
|
|
10626
|
+
// src/commands/model.ts
|
|
10627
|
+
function register16(program) {
|
|
10628
|
+
const model = program.command("model").description("Model access for the map's semantic search, on this machine");
|
|
10629
|
+
model.command("key").argument("[key]", "The OpenAI key to store. Omit it to see what is set.").option("--show", "Say whether a key is set, without printing it").option("--clear", "Forget the stored key").description("Set the OpenAI key the map embeds with").action((key, opts) => {
|
|
10630
|
+
const config2 = loadConfig();
|
|
10631
|
+
if (!config2) {
|
|
10632
|
+
console.error("This machine has no jarvis config yet \u2014 run `jarvis init` first.");
|
|
10633
|
+
process.exitCode = 1;
|
|
10634
|
+
return;
|
|
10635
|
+
}
|
|
10636
|
+
if (opts.clear) {
|
|
10637
|
+
saveConfig({ ...config2, openaiApiKey: void 0 });
|
|
10638
|
+
console.log("Forgotten. The map will build with no semantic index.");
|
|
10639
|
+
return;
|
|
10640
|
+
}
|
|
10641
|
+
if (!key || opts.show) {
|
|
10642
|
+
console.log(
|
|
10643
|
+
config2.openaiApiKey ? `A key is set (\u2026${config2.openaiApiKey.slice(-4)}). The map embeds with it.` : "No key set. The map builds with no semantic index and search stays lexical."
|
|
10644
|
+
);
|
|
10645
|
+
if (!key) console.log("Set one with `jarvis model key <key>`.");
|
|
10646
|
+
return;
|
|
10647
|
+
}
|
|
10648
|
+
saveConfig({ ...config2, openaiApiKey: key });
|
|
10649
|
+
console.log("Stored. Run `jarvis build graph` to build the semantic index.");
|
|
10650
|
+
});
|
|
10651
|
+
}
|
|
10652
|
+
|
|
10616
10653
|
// src/graph/build.ts
|
|
10617
10654
|
import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
10618
10655
|
import { dirname as dirname5, resolve as resolve7 } from "path";
|
|
@@ -10786,12 +10823,6 @@ async function runBuild(cwd, argv = []) {
|
|
|
10786
10823
|
if (argv.includes("--rebuild")) process.env.JARVIS_DATA_REBUILD = "1";
|
|
10787
10824
|
else process.env.JARVIS_DATA_REFRESH = "1";
|
|
10788
10825
|
if (argv.includes("--label")) process.env.JARVIS_DATA_LABEL = "1";
|
|
10789
|
-
const labelBackend = readFlag(argv, "--backend");
|
|
10790
|
-
if (labelBackend) process.env.JARVIS_DATA_BACKEND = labelBackend;
|
|
10791
|
-
const labelModel = readFlag(argv, "--model");
|
|
10792
|
-
if (labelModel) process.env.JARVIS_DATA_MODEL = labelModel;
|
|
10793
|
-
const labelTier = readFlag(argv, "--tier");
|
|
10794
|
-
if (labelTier) process.env.JARVIS_DATA_TIER = labelTier;
|
|
10795
10826
|
const lock = acquireBuildLock(cwd);
|
|
10796
10827
|
if (!lock) {
|
|
10797
10828
|
process.stderr.write(
|
|
@@ -10845,13 +10876,6 @@ ${formatLedger(ledger)}
|
|
|
10845
10876
|
}
|
|
10846
10877
|
return errors > 0 ? 1 : 0;
|
|
10847
10878
|
}
|
|
10848
|
-
function readFlag(argv, flag2) {
|
|
10849
|
-
const exact = argv.indexOf(flag2);
|
|
10850
|
-
if (exact >= 0) return argv[exact + 1];
|
|
10851
|
-
const prefix = `${flag2}=`;
|
|
10852
|
-
const inline = argv.find((arg) => arg.startsWith(prefix));
|
|
10853
|
-
return inline ? inline.slice(prefix.length) : void 0;
|
|
10854
|
-
}
|
|
10855
10879
|
var DEFAULT_STALE_MS = 10 * 60 * 1e3;
|
|
10856
10880
|
function acquireBuildLock(repoRoot, options = {}) {
|
|
10857
10881
|
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
@@ -10904,29 +10928,122 @@ function resolveRepo(repo) {
|
|
|
10904
10928
|
loadEnvFile(resolve8(homedir4(), ".jarvis", "data.env"));
|
|
10905
10929
|
return cwd;
|
|
10906
10930
|
}
|
|
10907
|
-
function loadEnvFile(
|
|
10931
|
+
function loadEnvFile(path22) {
|
|
10908
10932
|
try {
|
|
10909
|
-
process.loadEnvFile(
|
|
10933
|
+
process.loadEnvFile(path22);
|
|
10910
10934
|
} catch {
|
|
10911
10935
|
}
|
|
10912
10936
|
}
|
|
10913
10937
|
|
|
10914
10938
|
// src/commands/build.ts
|
|
10915
|
-
function
|
|
10939
|
+
function register17(program) {
|
|
10916
10940
|
const build2 = program.command("build").description("Build an artifact from this repo");
|
|
10917
|
-
build2.command("graph").description("Read the repo's jarvis.config.ts, build the graph, and say what is wrong with it").option("--repo <path>", "The repo to map. Default: the working directory").option("--rebuild", "LAST RESORT: throw the graph away and re-extract from scratch").option("--label", "Re-name the detected communities through the configured labeler").option("--gate", "Fail when a governance class has more findings than this repo carries").
|
|
10941
|
+
build2.command("graph").description("Read the repo's jarvis.config.ts, build the graph, and say what is wrong with it").option("--repo <path>", "The repo to map. Default: the working directory").option("--rebuild", "LAST RESORT: throw the graph away and re-extract from scratch").option("--label", "Re-name the detected communities through the configured labeler").option("--gate", "Fail when a governance class has more findings than this repo carries").action(async (opts) => {
|
|
10918
10942
|
const argv = [
|
|
10919
10943
|
...opts.rebuild ? ["--rebuild"] : [],
|
|
10920
10944
|
...opts.label ? ["--label"] : [],
|
|
10921
|
-
...opts.gate ? ["--gate"] : []
|
|
10922
|
-
...opts.backend ? ["--backend", opts.backend] : [],
|
|
10923
|
-
...opts.model ? ["--model", opts.model] : [],
|
|
10924
|
-
...opts.tier ? ["--tier", opts.tier] : []
|
|
10945
|
+
...opts.gate ? ["--gate"] : []
|
|
10925
10946
|
];
|
|
10926
10947
|
process.exitCode = await runBuild(resolveRepo(opts.repo), argv);
|
|
10927
10948
|
});
|
|
10928
10949
|
}
|
|
10929
10950
|
|
|
10951
|
+
// src/commands/dev.ts
|
|
10952
|
+
import path20 from "path";
|
|
10953
|
+
|
|
10954
|
+
// src/dev.ts
|
|
10955
|
+
import fs18 from "fs";
|
|
10956
|
+
import path19 from "path";
|
|
10957
|
+
function redirectFile() {
|
|
10958
|
+
return path19.join(getConfigDir(), "dev.json");
|
|
10959
|
+
}
|
|
10960
|
+
function readDevRedirect() {
|
|
10961
|
+
try {
|
|
10962
|
+
const raw = JSON.parse(fs18.readFileSync(redirectFile(), "utf-8"));
|
|
10963
|
+
if (typeof raw.root !== "string" || raw.root.length === 0) return null;
|
|
10964
|
+
return {
|
|
10965
|
+
root: raw.root,
|
|
10966
|
+
repos: Array.isArray(raw.repos) ? raw.repos.filter((r) => typeof r === "string") : [],
|
|
10967
|
+
all: raw.all === true
|
|
10968
|
+
};
|
|
10969
|
+
} catch {
|
|
10970
|
+
return null;
|
|
10971
|
+
}
|
|
10972
|
+
}
|
|
10973
|
+
function saveDevRedirect(req) {
|
|
10974
|
+
const dir = getConfigDir();
|
|
10975
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
10976
|
+
fs18.writeFileSync(redirectFile(), `${JSON.stringify(req, null, 2)}
|
|
10977
|
+
`);
|
|
10978
|
+
}
|
|
10979
|
+
function clearDevRedirect() {
|
|
10980
|
+
fs18.rmSync(redirectFile(), { force: true });
|
|
10981
|
+
}
|
|
10982
|
+
function realPath(target) {
|
|
10983
|
+
try {
|
|
10984
|
+
return fs18.realpathSync(target);
|
|
10985
|
+
} catch {
|
|
10986
|
+
return target;
|
|
10987
|
+
}
|
|
10988
|
+
}
|
|
10989
|
+
function isRedirected(req) {
|
|
10990
|
+
if (req.redirect.all) return true;
|
|
10991
|
+
const cwd = realPath(req.cwd);
|
|
10992
|
+
return req.redirect.repos.some((repo) => {
|
|
10993
|
+
const rel = path19.relative(realPath(repo), cwd);
|
|
10994
|
+
return rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel);
|
|
10995
|
+
});
|
|
10996
|
+
}
|
|
10997
|
+
|
|
10998
|
+
// src/commands/dev.ts
|
|
10999
|
+
function register18(program) {
|
|
11000
|
+
const dev = program.command("dev").description("Run a development build in a repo, without replacing the installed jarvis");
|
|
11001
|
+
dev.command("use").argument("[where]", "'here' for the working directory, 'all' for every repo", "here").option("--root <path>", "The checkout to run. Default: the one already recorded").description("Point a repo at the development build").action((where, opts) => {
|
|
11002
|
+
const current = readDevRedirect();
|
|
11003
|
+
const root = path20.resolve(opts.root ?? current?.root ?? process.cwd());
|
|
11004
|
+
if (where !== "here" && where !== "all") {
|
|
11005
|
+
console.error(`Unknown target '${where}' \u2014 say 'here' or 'all'.`);
|
|
11006
|
+
process.exitCode = 1;
|
|
11007
|
+
return;
|
|
11008
|
+
}
|
|
11009
|
+
const repos = new Set(current?.repos ?? []);
|
|
11010
|
+
if (where === "here") repos.add(realPath(process.cwd()));
|
|
11011
|
+
saveDevRedirect({ root, repos: [...repos], all: where === "all" || (current?.all ?? false) });
|
|
11012
|
+
console.log(
|
|
11013
|
+
where === "all" ? `Every repo on this machine now serves the development build in ${root}.` : `${process.cwd()} now serves the development build in ${root}.`
|
|
11014
|
+
);
|
|
11015
|
+
console.log("The installed jarvis is untouched. Undo with `jarvis dev off`.");
|
|
11016
|
+
});
|
|
11017
|
+
dev.command("off").argument("[where]", "'here' for the working directory, 'all' to stop everywhere", "all").description("Stop serving the development build").action((where) => {
|
|
11018
|
+
const current = readDevRedirect();
|
|
11019
|
+
if (!current) {
|
|
11020
|
+
console.log("No repo is on a development build.");
|
|
11021
|
+
return;
|
|
11022
|
+
}
|
|
11023
|
+
if (where === "here") {
|
|
11024
|
+
const repos = current.repos.filter((repo) => realPath(repo) !== realPath(process.cwd()));
|
|
11025
|
+
saveDevRedirect({ ...current, repos, all: false });
|
|
11026
|
+
console.log(`${process.cwd()} is back on the installed jarvis.`);
|
|
11027
|
+
return;
|
|
11028
|
+
}
|
|
11029
|
+
clearDevRedirect();
|
|
11030
|
+
console.log("Every repo is back on the installed jarvis.");
|
|
11031
|
+
});
|
|
11032
|
+
dev.command("status").description("Say which repos are on a development build").action(() => {
|
|
11033
|
+
const current = readDevRedirect();
|
|
11034
|
+
if (!current) {
|
|
11035
|
+
console.log("No repo is on a development build.");
|
|
11036
|
+
return;
|
|
11037
|
+
}
|
|
11038
|
+
console.log(`development build ${current.root}`);
|
|
11039
|
+
console.log(current.all ? " every repo on this machine" : "");
|
|
11040
|
+
for (const repo of current.repos) console.log(` ${repo}`);
|
|
11041
|
+
console.log(
|
|
11042
|
+
isRedirected({ cwd: process.cwd(), redirect: current }) ? "\nHere: the development build." : "\nHere: the installed jarvis."
|
|
11043
|
+
);
|
|
11044
|
+
});
|
|
11045
|
+
}
|
|
11046
|
+
|
|
10930
11047
|
// src/graph/mcp.ts
|
|
10931
11048
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10932
11049
|
async function runMcp(cwd, hosted = [], prompts = []) {
|
|
@@ -11464,7 +11581,7 @@ function describeStart(response) {
|
|
|
11464
11581
|
// src/serve/approvals.ts
|
|
11465
11582
|
import { readFileSync as readFileSync7 } from "fs";
|
|
11466
11583
|
import os10 from "os";
|
|
11467
|
-
import
|
|
11584
|
+
import path21 from "path";
|
|
11468
11585
|
function pendingApproval(cwd) {
|
|
11469
11586
|
const unapproved = unapprovedServers(cwd);
|
|
11470
11587
|
const reasons = [
|
|
@@ -11475,15 +11592,15 @@ function pendingApproval(cwd) {
|
|
|
11475
11592
|
return `it will HOLD at a prompt and take no turn, because ${reasons.join(" and ")}. Nothing will appear in Claude Code's history or in the app until somebody opens ${cwd} in Claude Code once and answers. This is a one-time gate per folder.`;
|
|
11476
11593
|
}
|
|
11477
11594
|
function untrustedFolder(cwd) {
|
|
11478
|
-
const state2 = readJson2(
|
|
11595
|
+
const state2 = readJson2(path21.join(os10.homedir(), ".claude.json"));
|
|
11479
11596
|
const project = state2?.projects?.[cwd];
|
|
11480
11597
|
if (!state2) return false;
|
|
11481
11598
|
return project?.hasTrustDialogAccepted !== true;
|
|
11482
11599
|
}
|
|
11483
11600
|
function unapprovedServers(cwd) {
|
|
11484
|
-
const declared = readJson2(
|
|
11601
|
+
const declared = readJson2(path21.join(cwd, ".mcp.json"));
|
|
11485
11602
|
if (!declared?.mcpServers) return [];
|
|
11486
|
-
const settings = readJson2(
|
|
11603
|
+
const settings = readJson2(path21.join(cwd, ".claude", "settings.local.json"));
|
|
11487
11604
|
if (settings?.enableAllProjectMcpServers === true) return [];
|
|
11488
11605
|
const answered = /* @__PURE__ */ new Set([
|
|
11489
11606
|
...settings?.enabledMcpjsonServers ?? [],
|
|
@@ -11593,7 +11710,7 @@ async function brief2(repo) {
|
|
|
11593
11710
|
}
|
|
11594
11711
|
|
|
11595
11712
|
// src/commands/serve.ts
|
|
11596
|
-
function
|
|
11713
|
+
function register19(program) {
|
|
11597
11714
|
program.command("serve").description("Serve this repo to an agent over stdio \u2014 what an .mcp.json launches").option("--repo <path>", "The repo to serve. Default: the working directory").action(async (opts) => {
|
|
11598
11715
|
const repo = resolveRepo(opts.repo);
|
|
11599
11716
|
const problem = harnessProblem();
|
|
@@ -11655,7 +11772,7 @@ function refuseNamedConfigDir() {
|
|
|
11655
11772
|
);
|
|
11656
11773
|
return true;
|
|
11657
11774
|
}
|
|
11658
|
-
function
|
|
11775
|
+
function register20(program) {
|
|
11659
11776
|
const hooks = program.command("hooks").description("Wire this machine's Claude Code to jarvis \u2014 no pairing, no daemon");
|
|
11660
11777
|
hooks.command("install").description("Install the Claude Code hooks (the session block and the rules on edit)").action(() => {
|
|
11661
11778
|
if (refuseNamedConfigDir()) process.exit(1);
|
|
@@ -11710,7 +11827,7 @@ Open Claude Code in a repo that has a work/ tree to see them.`);
|
|
|
11710
11827
|
|
|
11711
11828
|
// src/commands/work.ts
|
|
11712
11829
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
11713
|
-
function
|
|
11830
|
+
function register21(program) {
|
|
11714
11831
|
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) => {
|
|
11715
11832
|
const problem = harnessProblem();
|
|
11716
11833
|
if (problem) {
|
|
@@ -11752,22 +11869,24 @@ function createCli() {
|
|
|
11752
11869
|
register17(program);
|
|
11753
11870
|
register18(program);
|
|
11754
11871
|
register19(program);
|
|
11872
|
+
register20(program);
|
|
11873
|
+
register21(program);
|
|
11755
11874
|
return program;
|
|
11756
11875
|
}
|
|
11757
11876
|
|
|
11758
11877
|
// src/bin.ts
|
|
11759
11878
|
if (isRunningFromSource()) {
|
|
11760
11879
|
const dotenv = await import("dotenv");
|
|
11761
|
-
const
|
|
11762
|
-
const
|
|
11880
|
+
const fs19 = await import("fs");
|
|
11881
|
+
const path22 = await import("path");
|
|
11763
11882
|
let dir = process.cwd();
|
|
11764
|
-
while (dir !==
|
|
11765
|
-
const envPath =
|
|
11766
|
-
if (
|
|
11883
|
+
while (dir !== path22.dirname(dir)) {
|
|
11884
|
+
const envPath = path22.join(dir, ".env");
|
|
11885
|
+
if (fs19.existsSync(envPath)) {
|
|
11767
11886
|
dotenv.config({ path: envPath });
|
|
11768
11887
|
break;
|
|
11769
11888
|
}
|
|
11770
|
-
dir =
|
|
11889
|
+
dir = path22.dirname(dir);
|
|
11771
11890
|
}
|
|
11772
11891
|
}
|
|
11773
11892
|
createCli().parse();
|