@appchy/jarvis 0.1.95 → 0.1.97
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 +297 -199
- package/dist/bin.js.map +1 -1
- package/dist/data/backends.mjs +92 -22
- package/dist/data/mcp.mjs +62 -18
- package/harness/harness/epic.py +69 -1
- package/harness/harness/version.py +6 -1
- package/harness/test_work.py +75 -0
- package/package.json +3 -3
package/dist/bin.js
CHANGED
|
@@ -129,7 +129,7 @@ import { execSync as execSync4 } from "child_process";
|
|
|
129
129
|
import { createInterface as createInterface3 } from "readline";
|
|
130
130
|
import fs10 from "fs";
|
|
131
131
|
import os6 from "os";
|
|
132
|
-
import
|
|
132
|
+
import path11 from "path";
|
|
133
133
|
|
|
134
134
|
// src/env.ts
|
|
135
135
|
import fs7 from "fs";
|
|
@@ -1246,9 +1246,9 @@ function runnable(bin, spawn4) {
|
|
|
1246
1246
|
const probe = spawn4(bin, ["--help"], { stdio: "ignore" });
|
|
1247
1247
|
return probe.error?.code !== "ENOENT";
|
|
1248
1248
|
}
|
|
1249
|
-
function isExecutable(
|
|
1249
|
+
function isExecutable(path23) {
|
|
1250
1250
|
try {
|
|
1251
|
-
accessSync(
|
|
1251
|
+
accessSync(path23, constants.X_OK);
|
|
1252
1252
|
return true;
|
|
1253
1253
|
} catch {
|
|
1254
1254
|
return false;
|
|
@@ -1387,10 +1387,57 @@ async function ensureGraphify(options = {}) {
|
|
|
1387
1387
|
);
|
|
1388
1388
|
}
|
|
1389
1389
|
|
|
1390
|
+
// src/graph/hook.ts
|
|
1391
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
|
|
1392
|
+
import path7 from "path";
|
|
1393
|
+
var SIGNATURE = "# managed by jarvis";
|
|
1394
|
+
var SCRIPT = `#!/bin/sh
|
|
1395
|
+
${SIGNATURE} \u2014 keeps this repo's map current, so what an agent plans against is what is here.
|
|
1396
|
+
# Detached and silenced: git waits for a hook's output streams to close, so a build left on stdout
|
|
1397
|
+
# would hold up every commit by the length of a build. Never fails a commit.
|
|
1398
|
+
command -v jarvis >/dev/null 2>&1 || exit 0
|
|
1399
|
+
(jarvis build graph >/dev/null 2>&1 &) >/dev/null 2>&1
|
|
1400
|
+
exit 0
|
|
1401
|
+
`;
|
|
1402
|
+
function installCommitHook(repoRoot) {
|
|
1403
|
+
const dir = path7.join(repoRoot, ".git", "hooks");
|
|
1404
|
+
const file = path7.join(dir, "post-commit");
|
|
1405
|
+
if (!existsSync(path7.join(repoRoot, ".git")) || !existsSync(path7.join(repoRoot, ".git", "HEAD"))) {
|
|
1406
|
+
return { installed: false, reason: "not-a-repo" };
|
|
1407
|
+
}
|
|
1408
|
+
if (existsSync(file)) {
|
|
1409
|
+
try {
|
|
1410
|
+
if (!readFileSync(file, "utf8").includes(SIGNATURE))
|
|
1411
|
+
return { installed: false, reason: "foreign-hook", path: file };
|
|
1412
|
+
} catch {
|
|
1413
|
+
return { installed: false, reason: "unwritable", path: file };
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
try {
|
|
1417
|
+
mkdirSync(dir, { recursive: true });
|
|
1418
|
+
writeFileSync(file, SCRIPT, "utf8");
|
|
1419
|
+
chmodSync(file, 493);
|
|
1420
|
+
return { installed: true, path: file };
|
|
1421
|
+
} catch {
|
|
1422
|
+
return { installed: false, reason: "unwritable", path: file };
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
function describeCommitHook(outcome, repoRoot) {
|
|
1426
|
+
if (outcome.installed)
|
|
1427
|
+
return ` the map rebuilds itself on every commit here \u2713 (${path7.relative(repoRoot, outcome.path) || outcome.path})`;
|
|
1428
|
+
if (outcome.reason === "not-a-repo")
|
|
1429
|
+
return ` not a git repo, so there is no commit to rebuild on.
|
|
1430
|
+
Run 'jarvis init' inside one, or rebuild by hand with 'jarvis build graph'.`;
|
|
1431
|
+
if (outcome.reason === "foreign-hook")
|
|
1432
|
+
return ` a post-commit hook is already here and it is not ours \u2014 left untouched.
|
|
1433
|
+
Add this line to it to keep the map current: jarvis build graph >/dev/null 2>&1 &`;
|
|
1434
|
+
return ` could not write the hook \u2014 the map still builds, by hand, with 'jarvis build graph'.`;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1390
1437
|
// src/machine.ts
|
|
1391
1438
|
import crypto2 from "crypto";
|
|
1392
1439
|
import fs8 from "fs";
|
|
1393
|
-
import
|
|
1440
|
+
import path8 from "path";
|
|
1394
1441
|
var cached = null;
|
|
1395
1442
|
function getMachine() {
|
|
1396
1443
|
if (cached) return cached;
|
|
@@ -1405,7 +1452,7 @@ function getMachine() {
|
|
|
1405
1452
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1406
1453
|
schemaVersion: 1
|
|
1407
1454
|
};
|
|
1408
|
-
fs8.mkdirSync(
|
|
1455
|
+
fs8.mkdirSync(path8.dirname(file), { recursive: true });
|
|
1409
1456
|
fs8.writeFileSync(file, JSON.stringify(fresh, null, 2));
|
|
1410
1457
|
return cached = fresh;
|
|
1411
1458
|
}
|
|
@@ -1413,13 +1460,13 @@ function getMachine() {
|
|
|
1413
1460
|
// src/service.ts
|
|
1414
1461
|
import { execSync as execSync3, spawn } from "child_process";
|
|
1415
1462
|
import fs9 from "fs";
|
|
1416
|
-
import
|
|
1463
|
+
import path9 from "path";
|
|
1417
1464
|
import os4 from "os";
|
|
1418
1465
|
function serviceSuffix() {
|
|
1419
1466
|
const dir = getConfigDir();
|
|
1420
|
-
const base =
|
|
1467
|
+
const base = path9.join(os4.homedir(), ".jarvis");
|
|
1421
1468
|
if (dir === base) return "";
|
|
1422
|
-
const name = dir.startsWith(base +
|
|
1469
|
+
const name = dir.startsWith(base + path9.sep) ? dir.slice(base.length + 1) : path9.basename(dir);
|
|
1423
1470
|
return name.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1424
1471
|
}
|
|
1425
1472
|
var SERVICE_SUFFIX = serviceSuffix();
|
|
@@ -1430,8 +1477,8 @@ function createServiceManager() {
|
|
|
1430
1477
|
return new FallbackService();
|
|
1431
1478
|
}
|
|
1432
1479
|
var PLIST_LABEL = `com.appchy.jarvis${SERVICE_SUFFIX ? `.${SERVICE_SUFFIX}` : ""}`;
|
|
1433
|
-
var PLIST_DIR =
|
|
1434
|
-
var PLIST_PATH =
|
|
1480
|
+
var PLIST_DIR = path9.join(os4.homedir(), "Library", "LaunchAgents");
|
|
1481
|
+
var PLIST_PATH = path9.join(PLIST_DIR, `${PLIST_LABEL}.plist`);
|
|
1435
1482
|
function escapeXml(s) {
|
|
1436
1483
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1437
1484
|
}
|
|
@@ -1585,7 +1632,7 @@ ${Object.entries(opts.env ?? {}).map(([k, v]) => ` <key>${escapeXml(k)}</key>
|
|
|
1585
1632
|
</plist>
|
|
1586
1633
|
`;
|
|
1587
1634
|
fs9.mkdirSync(PLIST_DIR, { recursive: true });
|
|
1588
|
-
fs9.mkdirSync(
|
|
1635
|
+
fs9.mkdirSync(path9.dirname(logFile), { recursive: true });
|
|
1589
1636
|
unloadAgent();
|
|
1590
1637
|
fs9.writeFileSync(PLIST_PATH, plist);
|
|
1591
1638
|
loadAgent();
|
|
@@ -1687,7 +1734,7 @@ var WindowsService = class {
|
|
|
1687
1734
|
</Task>
|
|
1688
1735
|
`;
|
|
1689
1736
|
const tmpDir = os4.tmpdir();
|
|
1690
|
-
const tmpFile =
|
|
1737
|
+
const tmpFile = path9.join(tmpDir, `jarvis-task-${Date.now()}.xml`);
|
|
1691
1738
|
fs9.writeFileSync(tmpFile, xml, { encoding: "utf-16le" });
|
|
1692
1739
|
try {
|
|
1693
1740
|
execSync3(`schtasks /Create /TN "${TASK_NAME}" /XML "${tmpFile}" /F`, {
|
|
@@ -1733,9 +1780,9 @@ var WindowsService = class {
|
|
|
1733
1780
|
}
|
|
1734
1781
|
}
|
|
1735
1782
|
};
|
|
1736
|
-
var SYSTEMD_DIR =
|
|
1783
|
+
var SYSTEMD_DIR = path9.join(os4.homedir(), ".config", "systemd", "user");
|
|
1737
1784
|
var UNIT_NAME = `jarvis${SERVICE_SUFFIX ? `-${SERVICE_SUFFIX}` : ""}.service`;
|
|
1738
|
-
var UNIT_PATH =
|
|
1785
|
+
var UNIT_PATH = path9.join(SYSTEMD_DIR, UNIT_NAME);
|
|
1739
1786
|
var LinuxService = class {
|
|
1740
1787
|
install(opts) {
|
|
1741
1788
|
const args = [opts.entryPath, "start", "--foreground", "--workspace", opts.workspacePath];
|
|
@@ -1764,7 +1811,7 @@ StandardError=append:${logFile}
|
|
|
1764
1811
|
WantedBy=default.target
|
|
1765
1812
|
`;
|
|
1766
1813
|
fs9.mkdirSync(SYSTEMD_DIR, { recursive: true });
|
|
1767
|
-
fs9.mkdirSync(
|
|
1814
|
+
fs9.mkdirSync(path9.dirname(logFile), { recursive: true });
|
|
1768
1815
|
if (this.isInstalled()) {
|
|
1769
1816
|
try {
|
|
1770
1817
|
execSync3(`systemctl --user stop ${UNIT_NAME}`, { stdio: "ignore" });
|
|
@@ -1844,9 +1891,9 @@ WantedBy=default.target
|
|
|
1844
1891
|
};
|
|
1845
1892
|
var FallbackService = class {
|
|
1846
1893
|
baseDir = getJarvisDir();
|
|
1847
|
-
pidFile =
|
|
1848
|
-
logFile =
|
|
1849
|
-
markerFile =
|
|
1894
|
+
pidFile = path9.join(this.baseDir, "agent.pid");
|
|
1895
|
+
logFile = path9.join(this.baseDir, "agent.log");
|
|
1896
|
+
markerFile = path9.join(this.baseDir, "service-installed");
|
|
1850
1897
|
install(opts) {
|
|
1851
1898
|
fs9.mkdirSync(this.baseDir, { recursive: true });
|
|
1852
1899
|
this.stop();
|
|
@@ -1921,11 +1968,11 @@ var FallbackService = class {
|
|
|
1921
1968
|
};
|
|
1922
1969
|
|
|
1923
1970
|
// src/hooks/install.ts
|
|
1924
|
-
import { existsSync, mkdirSync, readFileSync, realpathSync, writeFileSync } from "fs";
|
|
1971
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1925
1972
|
import os5 from "os";
|
|
1926
|
-
import
|
|
1927
|
-
var SETTINGS_DIR =
|
|
1928
|
-
var SETTINGS_FILE =
|
|
1973
|
+
import path10 from "path";
|
|
1974
|
+
var SETTINGS_DIR = path10.join(os5.homedir(), ".claude");
|
|
1975
|
+
var SETTINGS_FILE = path10.join(SETTINGS_DIR, "settings.json");
|
|
1929
1976
|
var HOOK_STEMS = [
|
|
1930
1977
|
"pre-tool-use",
|
|
1931
1978
|
"session-start",
|
|
@@ -1943,7 +1990,7 @@ function jarvisHooks() {
|
|
|
1943
1990
|
];
|
|
1944
1991
|
}
|
|
1945
1992
|
function isJarvisCommand(command) {
|
|
1946
|
-
const name =
|
|
1993
|
+
const name = path10.basename(command);
|
|
1947
1994
|
return HOOK_STEMS.some((stem) => name === `${stem}.mjs` || name === `${stem}.dev.mjs`);
|
|
1948
1995
|
}
|
|
1949
1996
|
function getCliRoot() {
|
|
@@ -1956,22 +2003,22 @@ function getCliRoot() {
|
|
|
1956
2003
|
entry = realpathSync(binPath);
|
|
1957
2004
|
} catch {
|
|
1958
2005
|
}
|
|
1959
|
-
return
|
|
2006
|
+
return path10.resolve(path10.dirname(entry), "..");
|
|
1960
2007
|
}
|
|
1961
2008
|
function resolveHooks() {
|
|
1962
2009
|
const cliRoot = getCliRoot();
|
|
1963
2010
|
return jarvisHooks().map((h) => ({
|
|
1964
2011
|
...h,
|
|
1965
|
-
binAbsPath:
|
|
2012
|
+
binAbsPath: path10.join(cliRoot, "bin", h.binBasename)
|
|
1966
2013
|
}));
|
|
1967
2014
|
}
|
|
1968
2015
|
function ownsClaudeCode() {
|
|
1969
|
-
return getConfigDir() ===
|
|
2016
|
+
return getConfigDir() === path10.join(os5.homedir(), ".jarvis");
|
|
1970
2017
|
}
|
|
1971
2018
|
function readSettings() {
|
|
1972
|
-
if (!
|
|
2019
|
+
if (!existsSync2(SETTINGS_FILE)) return {};
|
|
1973
2020
|
try {
|
|
1974
|
-
const raw =
|
|
2021
|
+
const raw = readFileSync2(SETTINGS_FILE, "utf-8");
|
|
1975
2022
|
return raw.trim() ? JSON.parse(raw) : {};
|
|
1976
2023
|
} catch (err) {
|
|
1977
2024
|
logger.sys.warn("[hooks.install] settings.json unreadable", {
|
|
@@ -1982,8 +2029,8 @@ function readSettings() {
|
|
|
1982
2029
|
}
|
|
1983
2030
|
function writeSettings(s) {
|
|
1984
2031
|
try {
|
|
1985
|
-
|
|
1986
|
-
|
|
2032
|
+
mkdirSync2(SETTINGS_DIR, { recursive: true });
|
|
2033
|
+
writeFileSync2(SETTINGS_FILE, JSON.stringify(s, null, 2) + "\n");
|
|
1987
2034
|
return true;
|
|
1988
2035
|
} catch (err) {
|
|
1989
2036
|
logger.sys.warn("[hooks.install] settings.json unwritable", {
|
|
@@ -2028,7 +2075,7 @@ function claudeCodeHooksState() {
|
|
|
2028
2075
|
return {
|
|
2029
2076
|
type: hook.type,
|
|
2030
2077
|
bin: hook.binAbsPath,
|
|
2031
|
-
exists:
|
|
2078
|
+
exists: existsSync2(hook.binAbsPath),
|
|
2032
2079
|
...command ? { command } : {}
|
|
2033
2080
|
};
|
|
2034
2081
|
});
|
|
@@ -2050,10 +2097,10 @@ function installClaudeCodeHooks() {
|
|
|
2050
2097
|
expected: resolved.map((h) => ({
|
|
2051
2098
|
type: h.type,
|
|
2052
2099
|
bin: h.binAbsPath,
|
|
2053
|
-
exists:
|
|
2100
|
+
exists: existsSync2(h.binAbsPath)
|
|
2054
2101
|
}))
|
|
2055
2102
|
});
|
|
2056
|
-
const missing = resolved.filter((h) => !
|
|
2103
|
+
const missing = resolved.filter((h) => !existsSync2(h.binAbsPath));
|
|
2057
2104
|
if (missing.length === resolved.length) {
|
|
2058
2105
|
logger.sys.warn("[hooks.install] no bin shims found, skipping", {
|
|
2059
2106
|
configDir: configDir2,
|
|
@@ -2064,7 +2111,7 @@ function installClaudeCodeHooks() {
|
|
|
2064
2111
|
let settings = readSettings();
|
|
2065
2112
|
const installed = [];
|
|
2066
2113
|
for (const hook of resolved) {
|
|
2067
|
-
if (!
|
|
2114
|
+
if (!existsSync2(hook.binAbsPath)) {
|
|
2068
2115
|
logger.sys.warn("[hooks.install] bin shim missing, skipping hook", {
|
|
2069
2116
|
configDir: configDir2,
|
|
2070
2117
|
type: hook.type,
|
|
@@ -2318,6 +2365,7 @@ async function setUpThisMachine(prompts) {
|
|
|
2318
2365
|
console.log(" \u25B8 Wiring this machine's Claude Code to jarvis");
|
|
2319
2366
|
installHooks();
|
|
2320
2367
|
console.log();
|
|
2368
|
+
setUpMapRebuild();
|
|
2321
2369
|
await setUpMapTool(prompts);
|
|
2322
2370
|
console.log(" \u25B8 The model key the map embeds with");
|
|
2323
2371
|
const key = loadConfig()?.openaiApiKey;
|
|
@@ -2328,6 +2376,12 @@ async function setUpThisMachine(prompts) {
|
|
|
2328
2376
|
`
|
|
2329
2377
|
);
|
|
2330
2378
|
}
|
|
2379
|
+
function setUpMapRebuild() {
|
|
2380
|
+
const repoRoot = process.cwd();
|
|
2381
|
+
console.log(" \u25B8 Keeping this repo's map current");
|
|
2382
|
+
console.log(`${describeCommitHook(installCommitHook(repoRoot), repoRoot)}
|
|
2383
|
+
`);
|
|
2384
|
+
}
|
|
2331
2385
|
async function setUpMapTool(prompts) {
|
|
2332
2386
|
console.log(" \u25B8 The tool the map's code half is built from");
|
|
2333
2387
|
const state2 = resolveGraphify();
|
|
@@ -2338,7 +2392,7 @@ async function setUpMapTool(prompts) {
|
|
|
2338
2392
|
if (state2.strayDir) {
|
|
2339
2393
|
console.log(
|
|
2340
2394
|
` graphify is INSTALLED but not on PATH \u2014 it is at
|
|
2341
|
-
${
|
|
2395
|
+
${path11.join(state2.strayDir, "graphify")}, and your shell cannot see it.
|
|
2342
2396
|
'jarvis build graph' finds it anyway; nothing else will.
|
|
2343
2397
|
` + putItOnPath(state2.strayDir)
|
|
2344
2398
|
);
|
|
@@ -2401,7 +2455,7 @@ function discoverCandidateRoots() {
|
|
|
2401
2455
|
const names = ["Code", "code", "work", "src", "Projects", "projects", "dev", "Developer"];
|
|
2402
2456
|
const out = [];
|
|
2403
2457
|
for (const name of names) {
|
|
2404
|
-
const p =
|
|
2458
|
+
const p = path11.join(home, name);
|
|
2405
2459
|
let exists = false;
|
|
2406
2460
|
try {
|
|
2407
2461
|
exists = fs10.statSync(p).isDirectory();
|
|
@@ -2518,7 +2572,7 @@ async function readlineQuestion2(question) {
|
|
|
2518
2572
|
});
|
|
2519
2573
|
}
|
|
2520
2574
|
function normalizePath(p) {
|
|
2521
|
-
const resolved =
|
|
2575
|
+
const resolved = path11.resolve(p.trim());
|
|
2522
2576
|
try {
|
|
2523
2577
|
return fs10.realpathSync.native(resolved);
|
|
2524
2578
|
} catch {
|
|
@@ -2569,7 +2623,7 @@ function mergeRootCandidates(saved, discovered) {
|
|
|
2569
2623
|
}
|
|
2570
2624
|
function readLocalRoots() {
|
|
2571
2625
|
try {
|
|
2572
|
-
const raw = fs10.readFileSync(
|
|
2626
|
+
const raw = fs10.readFileSync(path11.join(getConfigDir(), "env.json"), "utf-8");
|
|
2573
2627
|
const parsed = JSON.parse(raw);
|
|
2574
2628
|
return Array.isArray(parsed.roots) ? parsed.roots : [];
|
|
2575
2629
|
} catch {
|
|
@@ -2644,26 +2698,26 @@ import { createInterface as createInterface4 } from "readline";
|
|
|
2644
2698
|
|
|
2645
2699
|
// src/mcp.ts
|
|
2646
2700
|
import { execFile } from "child_process";
|
|
2647
|
-
import { existsSync as
|
|
2701
|
+
import { existsSync as existsSync3, readdirSync } from "fs";
|
|
2648
2702
|
import os7 from "os";
|
|
2649
|
-
import
|
|
2703
|
+
import path12 from "path";
|
|
2650
2704
|
import { promisify } from "util";
|
|
2651
2705
|
var run = promisify(execFile);
|
|
2652
2706
|
var SERVER_NAME = "jarvis";
|
|
2653
2707
|
function candidatePaths() {
|
|
2654
2708
|
const home = os7.homedir();
|
|
2655
2709
|
return [
|
|
2656
|
-
|
|
2657
|
-
|
|
2710
|
+
path12.join(home, ".local", "bin", "claude"),
|
|
2711
|
+
path12.join(home, ".claude", "local", "claude"),
|
|
2658
2712
|
"/opt/homebrew/bin/claude",
|
|
2659
2713
|
"/usr/local/bin/claude",
|
|
2660
2714
|
...bundledWithEditor(home)
|
|
2661
2715
|
];
|
|
2662
2716
|
}
|
|
2663
2717
|
function bundledWithEditor(home) {
|
|
2664
|
-
const extensions =
|
|
2718
|
+
const extensions = path12.join(home, ".vscode", "extensions");
|
|
2665
2719
|
try {
|
|
2666
|
-
return readdirSync(extensions).filter((name) => name.startsWith("anthropic.claude-code-")).sort().reverse().map((name) =>
|
|
2720
|
+
return readdirSync(extensions).filter((name) => name.startsWith("anthropic.claude-code-")).sort().reverse().map((name) => path12.join(extensions, name, "resources", "native-binary", "claude"));
|
|
2667
2721
|
} catch {
|
|
2668
2722
|
return [];
|
|
2669
2723
|
}
|
|
@@ -2676,7 +2730,7 @@ async function findClaude() {
|
|
|
2676
2730
|
} catch {
|
|
2677
2731
|
}
|
|
2678
2732
|
for (const candidate of candidatePaths()) {
|
|
2679
|
-
if (
|
|
2733
|
+
if (existsSync3(candidate)) return candidate;
|
|
2680
2734
|
}
|
|
2681
2735
|
return null;
|
|
2682
2736
|
}
|
|
@@ -2757,11 +2811,11 @@ async function confirm(question) {
|
|
|
2757
2811
|
|
|
2758
2812
|
// src/commands/roots.ts
|
|
2759
2813
|
import fs11 from "fs";
|
|
2760
|
-
import
|
|
2814
|
+
import path13 from "path";
|
|
2761
2815
|
function readConfig() {
|
|
2762
2816
|
try {
|
|
2763
2817
|
return JSON.parse(
|
|
2764
|
-
fs11.readFileSync(
|
|
2818
|
+
fs11.readFileSync(path13.join(getConfigDir(), "env.json"), "utf-8")
|
|
2765
2819
|
);
|
|
2766
2820
|
} catch {
|
|
2767
2821
|
return {};
|
|
@@ -2782,7 +2836,7 @@ function register6(program) {
|
|
|
2782
2836
|
for (const r of roots) console.log(` ${r}`);
|
|
2783
2837
|
});
|
|
2784
2838
|
cmd.command("add <path>").description("Add a root directory").action(async (rawPath) => {
|
|
2785
|
-
const abs =
|
|
2839
|
+
const abs = path13.resolve(rawPath);
|
|
2786
2840
|
const roots = getRoots();
|
|
2787
2841
|
if (roots.includes(abs)) {
|
|
2788
2842
|
console.log(`Already added: ${abs}`);
|
|
@@ -2792,7 +2846,7 @@ function register6(program) {
|
|
|
2792
2846
|
console.log(`Added: ${abs}`);
|
|
2793
2847
|
});
|
|
2794
2848
|
cmd.command("remove <path>").description("Remove a root directory").action(async (rawPath) => {
|
|
2795
|
-
const abs =
|
|
2849
|
+
const abs = path13.resolve(rawPath);
|
|
2796
2850
|
const roots = getRoots();
|
|
2797
2851
|
const next = roots.filter((r) => r !== abs);
|
|
2798
2852
|
if (next.length === roots.length) {
|
|
@@ -2806,11 +2860,11 @@ function register6(program) {
|
|
|
2806
2860
|
|
|
2807
2861
|
// src/commands/settings.ts
|
|
2808
2862
|
import fs12 from "fs";
|
|
2809
|
-
import
|
|
2863
|
+
import path14 from "path";
|
|
2810
2864
|
function readLocalConfig() {
|
|
2811
2865
|
try {
|
|
2812
2866
|
return JSON.parse(
|
|
2813
|
-
fs12.readFileSync(
|
|
2867
|
+
fs12.readFileSync(path14.join(getConfigDir(), "env.json"), "utf-8")
|
|
2814
2868
|
);
|
|
2815
2869
|
} catch {
|
|
2816
2870
|
return {};
|
|
@@ -2935,16 +2989,16 @@ function resolveRepo(repo) {
|
|
|
2935
2989
|
loadEnvFile(resolve(homedir2(), ".jarvis", "data.env"));
|
|
2936
2990
|
return cwd;
|
|
2937
2991
|
}
|
|
2938
|
-
function loadEnvFile(
|
|
2992
|
+
function loadEnvFile(path23) {
|
|
2939
2993
|
try {
|
|
2940
|
-
process.loadEnvFile(
|
|
2994
|
+
process.loadEnvFile(path23);
|
|
2941
2995
|
} catch {
|
|
2942
2996
|
}
|
|
2943
2997
|
}
|
|
2944
2998
|
|
|
2945
2999
|
// ../../providers/anthropic/src/anthropic.agent.provider.ts
|
|
2946
3000
|
import { execFile as execFile2 } from "child_process";
|
|
2947
|
-
import { chmodSync, existsSync as
|
|
3001
|
+
import { chmodSync as chmodSync2, existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
|
|
2948
3002
|
import { createRequire } from "module";
|
|
2949
3003
|
import { randomUUID } from "crypto";
|
|
2950
3004
|
import { homedir as homedir3 } from "os";
|
|
@@ -3003,19 +3057,19 @@ function ensureSpawnHelperIsExecutable() {
|
|
|
3003
3057
|
`${process.platform}-${process.arch}`,
|
|
3004
3058
|
"spawn-helper"
|
|
3005
3059
|
);
|
|
3006
|
-
if (!
|
|
3060
|
+
if (!existsSync4(helper)) return;
|
|
3007
3061
|
const mode = statSync(helper).mode;
|
|
3008
3062
|
if (mode & 73) return;
|
|
3009
|
-
|
|
3063
|
+
chmodSync2(helper, mode | 493);
|
|
3010
3064
|
} catch {
|
|
3011
3065
|
}
|
|
3012
3066
|
}
|
|
3013
3067
|
async function idOf(term) {
|
|
3014
3068
|
const entry = join2(homedir3(), ".claude", "sessions", `${term.pid}.json`);
|
|
3015
3069
|
for (let attempt = 0; attempt < 60; attempt++) {
|
|
3016
|
-
if (
|
|
3070
|
+
if (existsSync4(entry)) {
|
|
3017
3071
|
try {
|
|
3018
|
-
const { sessionId } = JSON.parse(
|
|
3072
|
+
const { sessionId } = JSON.parse(readFileSync3(entry, "utf8"));
|
|
3019
3073
|
if (sessionId) return sessionId;
|
|
3020
3074
|
} catch {
|
|
3021
3075
|
}
|
|
@@ -3050,7 +3104,7 @@ async function findExecutable() {
|
|
|
3050
3104
|
} catch {
|
|
3051
3105
|
}
|
|
3052
3106
|
for (const candidate of candidatePaths2()) {
|
|
3053
|
-
if (
|
|
3107
|
+
if (existsSync4(candidate)) return candidate;
|
|
3054
3108
|
}
|
|
3055
3109
|
return null;
|
|
3056
3110
|
}
|
|
@@ -3106,7 +3160,7 @@ function endedAs(exitCode, signal) {
|
|
|
3106
3160
|
|
|
3107
3161
|
// ../../providers/anthropic/src/anthropic.session.provider.ts
|
|
3108
3162
|
import { stat as fsStat } from "fs/promises";
|
|
3109
|
-
import { createReadStream, readdirSync as readdirSync3, readFileSync as
|
|
3163
|
+
import { createReadStream, readdirSync as readdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
3110
3164
|
import { homedir as homedir4 } from "os";
|
|
3111
3165
|
import { join as join3 } from "path";
|
|
3112
3166
|
import readline from "readline";
|
|
@@ -3121,7 +3175,7 @@ function listLiveSessions() {
|
|
|
3121
3175
|
for (const name of readdirSync3(LIVE)) {
|
|
3122
3176
|
if (!name.endsWith(".json")) continue;
|
|
3123
3177
|
try {
|
|
3124
|
-
const record = JSON.parse(
|
|
3178
|
+
const record = JSON.parse(readFileSync4(join3(LIVE, name), "utf-8"));
|
|
3125
3179
|
if (typeof record.sessionId === "string") ids.add(record.sessionId);
|
|
3126
3180
|
} catch {
|
|
3127
3181
|
}
|
|
@@ -3431,22 +3485,22 @@ import chokidar from "chokidar";
|
|
|
3431
3485
|
import fs14 from "fs/promises";
|
|
3432
3486
|
import { execFile as execFile3 } from "child_process";
|
|
3433
3487
|
import os8 from "os";
|
|
3434
|
-
import
|
|
3488
|
+
import path16 from "path";
|
|
3435
3489
|
import { promisify as promisify3 } from "util";
|
|
3436
3490
|
|
|
3437
3491
|
// src/cache.ts
|
|
3438
3492
|
import crypto3 from "crypto";
|
|
3439
3493
|
import fs13 from "fs/promises";
|
|
3440
3494
|
import { constants as fsConstants } from "fs/promises";
|
|
3441
|
-
import
|
|
3495
|
+
import path15 from "path";
|
|
3442
3496
|
function getCacheRoot() {
|
|
3443
|
-
return
|
|
3497
|
+
return path15.join(getJarvisDir(), "cache");
|
|
3444
3498
|
}
|
|
3445
3499
|
function getNamespaceDir(req) {
|
|
3446
|
-
return
|
|
3500
|
+
return path15.join(getCacheRoot(), req.namespace);
|
|
3447
3501
|
}
|
|
3448
3502
|
function getEntryPath(req) {
|
|
3449
|
-
return
|
|
3503
|
+
return path15.join(getNamespaceDir({ namespace: req.namespace }), `${req.key}.json`);
|
|
3450
3504
|
}
|
|
3451
3505
|
async function ensureNamespaceDir(req) {
|
|
3452
3506
|
await fs13.mkdir(getNamespaceDir({ namespace: req.namespace }), { recursive: true });
|
|
@@ -3539,7 +3593,7 @@ async function sweepExpiredCacheEntries(req) {
|
|
|
3539
3593
|
}
|
|
3540
3594
|
for (const name of names) {
|
|
3541
3595
|
if (!name.endsWith(".json") || name.endsWith(".tmp")) continue;
|
|
3542
|
-
const file =
|
|
3596
|
+
const file = path15.join(dir, name);
|
|
3543
3597
|
let raw;
|
|
3544
3598
|
try {
|
|
3545
3599
|
raw = await fs13.readFile(file, "utf-8");
|
|
@@ -3584,16 +3638,16 @@ var execFileAsync = promisify3(execFile3);
|
|
|
3584
3638
|
function getSessionRoots() {
|
|
3585
3639
|
const home = process.env.JARVIS_CC_PROJECTS_HOME ?? os8.homedir();
|
|
3586
3640
|
return [
|
|
3587
|
-
{ provider: "claude-code", dir:
|
|
3641
|
+
{ provider: "claude-code", dir: path16.join(home, ".claude", "projects") }
|
|
3588
3642
|
// { provider: "codex", dir: path.join(home, ".codex", "sessions") },
|
|
3589
3643
|
// { provider: "cursor", dir: path.join(home, ".cursor", "sessions") },
|
|
3590
3644
|
];
|
|
3591
3645
|
}
|
|
3592
3646
|
function getSessionProvider(req) {
|
|
3593
|
-
const abs =
|
|
3647
|
+
const abs = path16.resolve(req.path);
|
|
3594
3648
|
for (const root of getSessionRoots()) {
|
|
3595
|
-
const rel =
|
|
3596
|
-
if (!rel.startsWith("..") && !
|
|
3649
|
+
const rel = path16.relative(root.dir, abs);
|
|
3650
|
+
if (!rel.startsWith("..") && !path16.isAbsolute(rel)) return root.provider;
|
|
3597
3651
|
}
|
|
3598
3652
|
return null;
|
|
3599
3653
|
}
|
|
@@ -3607,7 +3661,7 @@ async function walkRoot(req) {
|
|
|
3607
3661
|
}
|
|
3608
3662
|
for (const entry of entries) {
|
|
3609
3663
|
if (entry.startsWith(".")) continue;
|
|
3610
|
-
const projectDir =
|
|
3664
|
+
const projectDir = path16.join(req.root.dir, entry);
|
|
3611
3665
|
let stat4;
|
|
3612
3666
|
try {
|
|
3613
3667
|
stat4 = await fs14.stat(projectDir);
|
|
@@ -3624,7 +3678,7 @@ async function walkRoot(req) {
|
|
|
3624
3678
|
for (const name of files2) {
|
|
3625
3679
|
if (name.startsWith(".")) continue;
|
|
3626
3680
|
if (!name.endsWith(".jsonl") && !name.endsWith(".json")) continue;
|
|
3627
|
-
const filePath =
|
|
3681
|
+
const filePath = path16.join(projectDir, name);
|
|
3628
3682
|
let fstat;
|
|
3629
3683
|
try {
|
|
3630
3684
|
fstat = await fs14.stat(filePath);
|
|
@@ -3650,7 +3704,7 @@ async function walkAllSessions(req = {}) {
|
|
|
3650
3704
|
}
|
|
3651
3705
|
async function listLocalSessionIds(req = {}) {
|
|
3652
3706
|
const files2 = await walkAllSessions(req);
|
|
3653
|
-
return files2.map((f) =>
|
|
3707
|
+
return files2.map((f) => path16.basename(f.filePath, path16.extname(f.filePath)));
|
|
3654
3708
|
}
|
|
3655
3709
|
async function getSessionFile(req) {
|
|
3656
3710
|
const provider2 = getSessionProvider({ path: req.path });
|
|
@@ -3665,7 +3719,7 @@ async function getSessionFile(req) {
|
|
|
3665
3719
|
return {
|
|
3666
3720
|
provider: provider2,
|
|
3667
3721
|
filePath: req.path,
|
|
3668
|
-
projectDir:
|
|
3722
|
+
projectDir: path16.dirname(req.path),
|
|
3669
3723
|
mtimeMs: stat4.mtimeMs
|
|
3670
3724
|
};
|
|
3671
3725
|
}
|
|
@@ -3743,8 +3797,8 @@ function toStatus(req) {
|
|
|
3743
3797
|
}
|
|
3744
3798
|
async function buildSession(req) {
|
|
3745
3799
|
const { file, rollup } = req;
|
|
3746
|
-
const sessionId =
|
|
3747
|
-
const folderCwd = decodeProjectFolder({ folder:
|
|
3800
|
+
const sessionId = path16.basename(file.filePath, path16.extname(file.filePath));
|
|
3801
|
+
const folderCwd = decodeProjectFolder({ folder: path16.basename(file.projectDir) });
|
|
3748
3802
|
const cwd = rollup.cwd ?? folderCwd ?? null;
|
|
3749
3803
|
const repo = cwd ? await getRepo({ cwd, cache: req.repoCache }).catch(() => null) : null;
|
|
3750
3804
|
const status2 = toStatus({
|
|
@@ -3979,7 +4033,7 @@ async function reconcileSessions(req, options) {
|
|
|
3979
4033
|
for (const file of onDisk) {
|
|
3980
4034
|
if (seen.has(file.filePath)) continue;
|
|
3981
4035
|
try {
|
|
3982
|
-
const newcomerSessionId =
|
|
4036
|
+
const newcomerSessionId = path16.basename(file.filePath, path16.extname(file.filePath));
|
|
3983
4037
|
const { entry: stub } = await readSessionCacheEntry({ sessionId: newcomerSessionId });
|
|
3984
4038
|
const { entry, task } = await parseSession(
|
|
3985
4039
|
{ file },
|
|
@@ -4002,7 +4056,7 @@ function watchSessions(ctx, req, options) {
|
|
|
4002
4056
|
roots.map((r) => r.dir),
|
|
4003
4057
|
{
|
|
4004
4058
|
ignoreInitial: true,
|
|
4005
|
-
ignored: (p) =>
|
|
4059
|
+
ignored: (p) => path16.basename(p).startsWith("."),
|
|
4006
4060
|
depth: 2,
|
|
4007
4061
|
// Coalesce a burst of writes (CC streams line-by-line) into one event.
|
|
4008
4062
|
awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 }
|
|
@@ -4027,7 +4081,7 @@ function watchSessions(ctx, req, options) {
|
|
|
4027
4081
|
if (!isSessionFile(p)) return;
|
|
4028
4082
|
const file = await getSessionFile({ path: p });
|
|
4029
4083
|
if (!file) return;
|
|
4030
|
-
const sessionId =
|
|
4084
|
+
const sessionId = path16.basename(p, path16.extname(p));
|
|
4031
4085
|
await withSessionLock(sessionId, async () => {
|
|
4032
4086
|
try {
|
|
4033
4087
|
const { entry: priorEntry } = await readSessionCacheEntry({ sessionId });
|
|
@@ -4050,7 +4104,7 @@ function watchSessions(ctx, req, options) {
|
|
|
4050
4104
|
if (!isSessionFile(p)) return;
|
|
4051
4105
|
const file = await getSessionFile({ path: p });
|
|
4052
4106
|
if (!file) return;
|
|
4053
|
-
const sessionId =
|
|
4107
|
+
const sessionId = path16.basename(p, path16.extname(p));
|
|
4054
4108
|
await withSessionLock(sessionId, async () => {
|
|
4055
4109
|
try {
|
|
4056
4110
|
const { entry: priorEntry } = await readSessionCacheEntry({ sessionId });
|
|
@@ -4077,7 +4131,7 @@ function watchSessions(ctx, req, options) {
|
|
|
4077
4131
|
}
|
|
4078
4132
|
async function handleUnlink(p) {
|
|
4079
4133
|
if (!isSessionFile(p)) return;
|
|
4080
|
-
const sessionId =
|
|
4134
|
+
const sessionId = path16.basename(p, path16.extname(p));
|
|
4081
4135
|
await withSessionLock(sessionId, async () => {
|
|
4082
4136
|
await clearSessionCacheEntry({ sessionId });
|
|
4083
4137
|
await options.onSessionDeleted({ taskId: sessionId, reason: "removed" });
|
|
@@ -4089,11 +4143,11 @@ function watchSessions(ctx, req, options) {
|
|
|
4089
4143
|
const hooksDir = getHooksDir();
|
|
4090
4144
|
const hooksWatcher = chokidar.watch(`${hooksDir}/*.jsonl`, {
|
|
4091
4145
|
ignoreInitial: false,
|
|
4092
|
-
ignored: (p) =>
|
|
4146
|
+
ignored: (p) => path16.basename(p).startsWith(".")
|
|
4093
4147
|
});
|
|
4094
4148
|
async function handleHookFile(filePath) {
|
|
4095
4149
|
if (!filePath.endsWith(".jsonl")) return;
|
|
4096
|
-
const sessionId =
|
|
4150
|
+
const sessionId = path16.basename(filePath, ".jsonl");
|
|
4097
4151
|
if (!sessionId) return;
|
|
4098
4152
|
await withSessionLock(sessionId, async () => {
|
|
4099
4153
|
try {
|
|
@@ -4352,12 +4406,12 @@ async function listSessions(req) {
|
|
|
4352
4406
|
async function getSession(req) {
|
|
4353
4407
|
const all = await walkAllSessions();
|
|
4354
4408
|
const file = all.find(
|
|
4355
|
-
(candidate) =>
|
|
4409
|
+
(candidate) => path16.basename(candidate.filePath, path16.extname(candidate.filePath)) === req.id
|
|
4356
4410
|
);
|
|
4357
4411
|
return file ? toSession(file) : null;
|
|
4358
4412
|
}
|
|
4359
4413
|
async function toSession(file) {
|
|
4360
|
-
const sessionId =
|
|
4414
|
+
const sessionId = path16.basename(file.filePath, path16.extname(file.filePath));
|
|
4361
4415
|
const [{ rollup }, { entry }] = await Promise.all([
|
|
4362
4416
|
projectSession2({ file }).catch(() => ({ rollup: createEmptyRollup() })),
|
|
4363
4417
|
readSessionCacheEntry({ sessionId })
|
|
@@ -4733,8 +4787,8 @@ async function listWorkItems(_ctx, req, options) {
|
|
|
4733
4787
|
async function findWorkItem(_ctx, req, options) {
|
|
4734
4788
|
const work = join4(options.root, "work");
|
|
4735
4789
|
for (const file of await walk(join4(work, "versions"))) {
|
|
4736
|
-
const
|
|
4737
|
-
if (
|
|
4790
|
+
const path23 = pathIn(work, file);
|
|
4791
|
+
if (path23.split("/").at(-2) === req.id) return readItem(work, file);
|
|
4738
4792
|
}
|
|
4739
4793
|
return null;
|
|
4740
4794
|
}
|
|
@@ -4883,8 +4937,8 @@ async function readTree(root) {
|
|
|
4883
4937
|
const work = join4(root, "work");
|
|
4884
4938
|
const items = [];
|
|
4885
4939
|
for (const file of await walk(join4(work, "versions"))) {
|
|
4886
|
-
const
|
|
4887
|
-
if (LEFT_THE_BOARD.some((home) =>
|
|
4940
|
+
const path23 = pathIn(work, file);
|
|
4941
|
+
if (LEFT_THE_BOARD.some((home) => path23.startsWith(`versions/${home}/`))) continue;
|
|
4888
4942
|
const item2 = await readItem(work, file);
|
|
4889
4943
|
if (item2) items.push(item2);
|
|
4890
4944
|
}
|
|
@@ -5025,7 +5079,7 @@ async function createWorkItem2(ctx, req, options) {
|
|
|
5025
5079
|
|
|
5026
5080
|
// src/harness.ts
|
|
5027
5081
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
5028
|
-
import { existsSync as
|
|
5082
|
+
import { existsSync as existsSync5 } from "fs";
|
|
5029
5083
|
import { dirname as dirname3, join as join5 } from "path";
|
|
5030
5084
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5031
5085
|
var ENTRY = join5("harness", "work.py");
|
|
@@ -5035,7 +5089,7 @@ var INSTALLED = ["jarvis", "work"];
|
|
|
5035
5089
|
function payload() {
|
|
5036
5090
|
let dir = dirname3(fileURLToPath2(import.meta.url));
|
|
5037
5091
|
for (let up = 0; up < 6; up++) {
|
|
5038
|
-
if (
|
|
5092
|
+
if (existsSync5(join5(dir, ENTRY))) return join5(dir, ENTRY);
|
|
5039
5093
|
const parent = dirname3(dir);
|
|
5040
5094
|
if (parent === dir) break;
|
|
5041
5095
|
dir = parent;
|
|
@@ -5870,8 +5924,8 @@ function reason(err) {
|
|
|
5870
5924
|
}
|
|
5871
5925
|
function issueText(issues) {
|
|
5872
5926
|
return issues.slice(0, 3).map((issue) => {
|
|
5873
|
-
const
|
|
5874
|
-
return
|
|
5927
|
+
const path23 = (issue.path ?? []).map((step) => String(typeof step === "object" ? step.key : step)).join(".");
|
|
5928
|
+
return path23 ? `${path23}: ${issue.message}` : issue.message;
|
|
5875
5929
|
}).join("; ");
|
|
5876
5930
|
}
|
|
5877
5931
|
function keyText(data) {
|
|
@@ -6126,7 +6180,7 @@ function edgeMatchesConfidence(edge, backendName, confidence) {
|
|
|
6126
6180
|
}
|
|
6127
6181
|
|
|
6128
6182
|
// ../../packages/data/src/config.ts
|
|
6129
|
-
import { existsSync as
|
|
6183
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
6130
6184
|
import { readFile as readFile4 } from "fs/promises";
|
|
6131
6185
|
import { dirname as dirname5, resolve as resolve4 } from "path";
|
|
6132
6186
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -6159,9 +6213,9 @@ function localFiles(options = {}) {
|
|
|
6159
6213
|
}
|
|
6160
6214
|
},
|
|
6161
6215
|
save: async (ctx, key, bytes) => {
|
|
6162
|
-
const
|
|
6163
|
-
await mkdir(dirname4(
|
|
6164
|
-
await writeFile(
|
|
6216
|
+
const path23 = pathOf(ctx, key);
|
|
6217
|
+
await mkdir(dirname4(path23), { recursive: true });
|
|
6218
|
+
await writeFile(path23, bytes, "utf8");
|
|
6165
6219
|
},
|
|
6166
6220
|
locate: (ctx, key) => pathOf(ctx, key)
|
|
6167
6221
|
});
|
|
@@ -6179,10 +6233,10 @@ async function loadConfig2(cwd) {
|
|
|
6179
6233
|
let dir = resolve4(cwd);
|
|
6180
6234
|
for (; ; ) {
|
|
6181
6235
|
for (const name of CONFIG_NAMES) {
|
|
6182
|
-
const
|
|
6183
|
-
if (
|
|
6184
|
-
const config2 = await importConfig(
|
|
6185
|
-
validateConfig(config2,
|
|
6236
|
+
const path23 = resolve4(dir, name);
|
|
6237
|
+
if (existsSync6(path23)) {
|
|
6238
|
+
const config2 = await importConfig(path23);
|
|
6239
|
+
validateConfig(config2, path23);
|
|
6186
6240
|
return {
|
|
6187
6241
|
...config2,
|
|
6188
6242
|
persistence: config2.persistence ?? localFiles(),
|
|
@@ -6198,9 +6252,9 @@ async function loadConfig2(cwd) {
|
|
|
6198
6252
|
`No jarvis.config.{ts,js,mjs} found from ${cwd}. A repo maps its world in one, at its root.`
|
|
6199
6253
|
);
|
|
6200
6254
|
}
|
|
6201
|
-
async function importConfig(
|
|
6255
|
+
async function importConfig(path23) {
|
|
6202
6256
|
const jiti = createJiti(import.meta.url, { moduleCache: false, alias: selfAlias() });
|
|
6203
|
-
const mod = await jiti.import(
|
|
6257
|
+
const mod = await jiti.import(path23);
|
|
6204
6258
|
const config2 = mod.default ?? mod;
|
|
6205
6259
|
return config2;
|
|
6206
6260
|
}
|
|
@@ -6208,7 +6262,7 @@ var PACKAGE_NAME = "@jarvis/data";
|
|
|
6208
6262
|
function packageRoot() {
|
|
6209
6263
|
let dir = dirname5(fileURLToPath3(import.meta.url));
|
|
6210
6264
|
for (; ; ) {
|
|
6211
|
-
if (
|
|
6265
|
+
if (existsSync6(resolve4(dir, "package.json"))) return dir;
|
|
6212
6266
|
const parent = dirname5(dir);
|
|
6213
6267
|
if (parent === dir) return dir;
|
|
6214
6268
|
dir = parent;
|
|
@@ -6216,7 +6270,7 @@ function packageRoot() {
|
|
|
6216
6270
|
}
|
|
6217
6271
|
function selfAlias() {
|
|
6218
6272
|
const root = packageRoot();
|
|
6219
|
-
const pkg = JSON.parse(
|
|
6273
|
+
const pkg = JSON.parse(readFileSync5(resolve4(root, "package.json"), "utf8"));
|
|
6220
6274
|
if (pkg.name !== PACKAGE_NAME) {
|
|
6221
6275
|
const vendored = process.env[VENDOR_ENV];
|
|
6222
6276
|
if (!vendored) {
|
|
@@ -6253,9 +6307,9 @@ function toVendorAlias(dir) {
|
|
|
6253
6307
|
function toSpecifier(subpath) {
|
|
6254
6308
|
return subpath === "." ? PACKAGE_NAME : `${PACKAGE_NAME}/${subpath.replace(/^\.\//, "")}`;
|
|
6255
6309
|
}
|
|
6256
|
-
function validateConfig(value,
|
|
6310
|
+
function validateConfig(value, path23) {
|
|
6257
6311
|
const fail = (msg) => {
|
|
6258
|
-
throw new Error(`${
|
|
6312
|
+
throw new Error(`${path23}: ${msg}`);
|
|
6259
6313
|
};
|
|
6260
6314
|
if (typeof value !== "object" || value === null) fail("config must export an object");
|
|
6261
6315
|
const c = value;
|
|
@@ -6992,10 +7046,10 @@ async function git3(root, argv, timeout = 5e3) {
|
|
|
6992
7046
|
}
|
|
6993
7047
|
}
|
|
6994
7048
|
async function fetchHeadIso(root) {
|
|
6995
|
-
const
|
|
6996
|
-
if (!
|
|
7049
|
+
const path23 = await git3(root, ["rev-parse", "--git-path", "FETCH_HEAD"]);
|
|
7050
|
+
if (!path23) return void 0;
|
|
6997
7051
|
try {
|
|
6998
|
-
return (await stat2(resolve5(root,
|
|
7052
|
+
return (await stat2(resolve5(root, path23))).mtime.toISOString();
|
|
6999
7053
|
} catch {
|
|
7000
7054
|
return void 0;
|
|
7001
7055
|
}
|
|
@@ -7042,6 +7096,14 @@ async function freshness(ctx) {
|
|
|
7042
7096
|
const builtMs = Date.parse(ctx.builtAt);
|
|
7043
7097
|
let behindHead;
|
|
7044
7098
|
if (head && mtimeMs) behindHead = mtimeMs < head.committedMs;
|
|
7099
|
+
let behindHeadBy;
|
|
7100
|
+
let behindHeadForMs;
|
|
7101
|
+
if (behindHead && head && mtimeMs) {
|
|
7102
|
+
behindHeadForMs = head.committedMs - mtimeMs;
|
|
7103
|
+
const since = new Date(mtimeMs).toISOString();
|
|
7104
|
+
const count2 = Number(await git3(root, ["rev-list", "--count", `--since=${since}`, "HEAD"]));
|
|
7105
|
+
if (Number.isFinite(count2)) behindHeadBy = count2;
|
|
7106
|
+
}
|
|
7045
7107
|
const replacedMs = Math.max(mtimeMs ?? 0, await snapshotMtimeMs(ctx) ?? 0);
|
|
7046
7108
|
const rebuiltOnDisk = Boolean(
|
|
7047
7109
|
replacedMs && Number.isFinite(builtMs) && replacedMs > builtMs + 1e3
|
|
@@ -7050,32 +7112,52 @@ async function freshness(ctx) {
|
|
|
7050
7112
|
const behindOrigin = Boolean(origin?.reachable && (origin.behindBy ?? 0) > 0);
|
|
7051
7113
|
const originUnknown = Boolean(origin && !origin.reachable);
|
|
7052
7114
|
let reason2;
|
|
7053
|
-
|
|
7115
|
+
let verdict;
|
|
7116
|
+
if (behindOrigin) {
|
|
7117
|
+
verdict = "stale";
|
|
7054
7118
|
reason2 = `this checkout is ${origin?.behindBy} commit(s) behind ${origin?.ref} \u2014 the map and the board both describe an older world than the origin's. Pull, then \`jarvis build graph\`.`;
|
|
7055
|
-
else if (originUnknown)
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7119
|
+
} else if (originUnknown) {
|
|
7120
|
+
verdict = "unknown";
|
|
7121
|
+
reason2 = `cannot reach ${origin?.ref}, so whether this is current is UNKNOWN rather than bad \u2014 rebuilding cannot answer it${origin?.lastSyncedIso ? `. Last heard from a remote at ${origin.lastSyncedIso}` : ", and this clone has never fetched"}.`;
|
|
7122
|
+
} else if (behindHead) {
|
|
7123
|
+
verdict = "stale";
|
|
7124
|
+
reason2 = `${graphRel} is ${describeBehind(behindHeadBy, behindHeadForMs)} \u2014 run \`jarvis build graph\` (the incremental update \u2014 it re-extracts in place), then traces and impact are trustworthy again. \`--rebuild\` re-extracts from scratch and relabels; it is for a graph still wrong after a plain build, not for ordinary drift.`;
|
|
7125
|
+
} else if (rebuiltOnDisk) {
|
|
7126
|
+
verdict = "stale";
|
|
7060
7127
|
reason2 = `${graphRel} was rebuilt after this MCP server loaded it \u2014 reloading automatically.`;
|
|
7128
|
+
}
|
|
7061
7129
|
return {
|
|
7062
7130
|
graphPath: graphRel,
|
|
7063
7131
|
...mtimeIso ? { mtimeIso } : {},
|
|
7064
7132
|
...head ? { head: { commit: head.commit, dirty: head.dirty } } : {},
|
|
7065
7133
|
...behindHead !== void 0 ? { behindHead } : {},
|
|
7134
|
+
...behindHeadBy !== void 0 ? { behindHeadBy } : {},
|
|
7135
|
+
...behindHeadForMs !== void 0 ? { behindHeadForMs } : {},
|
|
7066
7136
|
...rebuiltOnDisk ? { rebuiltOnDisk } : {},
|
|
7067
7137
|
...origin ? { origin } : {},
|
|
7068
7138
|
...behindOrigin ? { behindOrigin } : {},
|
|
7069
7139
|
...originUnknown ? { originUnknown } : {},
|
|
7070
7140
|
stale: Boolean(behindHead) || rebuiltOnDisk || behindOrigin || originUnknown,
|
|
7141
|
+
...verdict ? { verdict } : {},
|
|
7071
7142
|
...reason2 ? { reason: reason2 } : {}
|
|
7072
7143
|
};
|
|
7073
7144
|
}
|
|
7145
|
+
function describeBehind(commits, forMs) {
|
|
7146
|
+
const age = forMs !== void 0 && forMs >= 60 * 60 * 1e3 ? describeAge2(forMs) : void 0;
|
|
7147
|
+
if (commits === void 0) return `older than HEAD${age ? ` by ${age}` : ""}`;
|
|
7148
|
+
const plural = commits === 1 ? "commit" : "commits";
|
|
7149
|
+
return `${commits} ${plural} behind HEAD${age ? `, built ${age} ago` : ""}`;
|
|
7150
|
+
}
|
|
7151
|
+
function describeAge2(ms) {
|
|
7152
|
+
const hours = Math.round(ms / (60 * 60 * 1e3));
|
|
7153
|
+
if (hours < 48) return `${hours} hour(s)`;
|
|
7154
|
+
return `${Math.round(hours / 24)} day(s)`;
|
|
7155
|
+
}
|
|
7074
7156
|
async function snapshotMtimeMs(ctx) {
|
|
7075
|
-
const
|
|
7076
|
-
if (!
|
|
7157
|
+
const path23 = ctx.config.persistence?.locate?.({ repoRoot: ctx.config.repoRoot }, "snapshot");
|
|
7158
|
+
if (!path23) return void 0;
|
|
7077
7159
|
try {
|
|
7078
|
-
return (await stat2(
|
|
7160
|
+
return (await stat2(path23)).mtimeMs;
|
|
7079
7161
|
} catch {
|
|
7080
7162
|
return void 0;
|
|
7081
7163
|
}
|
|
@@ -7497,10 +7579,10 @@ async function cachedEnforcerDiags(ctx, freshPaths) {
|
|
|
7497
7579
|
}
|
|
7498
7580
|
function isFresh(snapshot, repoRoot, freshPaths) {
|
|
7499
7581
|
if (!freshPaths || freshPaths.length === 0) return true;
|
|
7500
|
-
for (const
|
|
7582
|
+
for (const path23 of freshPaths) {
|
|
7501
7583
|
let mtimeMs;
|
|
7502
7584
|
try {
|
|
7503
|
-
mtimeMs = statSync2(resolve6(repoRoot,
|
|
7585
|
+
mtimeMs = statSync2(resolve6(repoRoot, path23)).mtimeMs;
|
|
7504
7586
|
} catch {
|
|
7505
7587
|
return false;
|
|
7506
7588
|
}
|
|
@@ -7701,11 +7783,13 @@ async function brief(ctx, req) {
|
|
|
7701
7783
|
const areas = [...communityCounts.entries()].map(([name, size]) => ({ name, size })).sort((a, b) => b.size - a.size).slice(0, listCap);
|
|
7702
7784
|
const inProgressCount = byCategory["in-progress"] ?? 0;
|
|
7703
7785
|
return {
|
|
7704
|
-
summary: `${leaves.length} work item(s) \u2014 ${inProgressCount} in flight, ${byCategory["open"] ?? 0} open, ${byCategory["done"] ?? 0} done. ${artifacts} knowledge artifact(s); health: ${health.counts.error} error(s), ${health.counts.warn} warning(s)${f.stale ?
|
|
7786
|
+
summary: `${leaves.length} work item(s) \u2014 ${inProgressCount} in flight, ${byCategory["open"] ?? 0} open, ${byCategory["done"] ?? 0} done. ${artifacts} knowledge artifact(s); health: ${health.counts.error} error(s), ${health.counts.warn} warning(s)${f.stale ? ` \u2014 ${describeFreshness(f)}` : ""}${staleOrigins.length ? ` \u2014 ${staleOrigins.map((o) => o.origin).join(", ")} behind their own HEAD (re-run \`jarvis build graph\` there, then re-merge)` : ""}.`,
|
|
7705
7787
|
freshness: {
|
|
7706
7788
|
builtAt: ctx.builtAt,
|
|
7707
7789
|
...f.head ? { head: { commit: f.head.commit, dirty: f.head.dirty } } : {},
|
|
7708
7790
|
...f.stale !== void 0 ? { stale: f.stale } : {},
|
|
7791
|
+
...f.verdict ? { verdict: f.verdict } : {},
|
|
7792
|
+
...f.behindHeadBy !== void 0 ? { behindHeadBy: f.behindHeadBy } : {},
|
|
7709
7793
|
...originsFresh.length ? { federated: originsFresh } : {}
|
|
7710
7794
|
},
|
|
7711
7795
|
graph: { nodes: ctx.graph.nodes().length, artifacts, edges: ctx.graph.edges().length },
|
|
@@ -7747,6 +7831,15 @@ function renderWorkItem(ctx, item2, home) {
|
|
|
7747
7831
|
...home !== void 0 ? { belongsTo: home } : {}
|
|
7748
7832
|
};
|
|
7749
7833
|
}
|
|
7834
|
+
function describeFreshness(f) {
|
|
7835
|
+
if (f.verdict === "unknown")
|
|
7836
|
+
return "FRESHNESS UNKNOWN \u2014 cannot reach the origin, so nothing below is confirmed current; rebuilding cannot answer it";
|
|
7837
|
+
if (f.behindOrigin)
|
|
7838
|
+
return `THIS CHECKOUT IS BEHIND ITS ORIGIN by ${f.origin?.behindBy} commit(s) \u2014 pull, then run \`jarvis build graph\``;
|
|
7839
|
+
if (f.behindHead)
|
|
7840
|
+
return `GRAPH IS STALE by ${f.behindHeadBy ?? "an unknown number of"} commit(s), run \`jarvis build graph\` (incremental) before trusting details`;
|
|
7841
|
+
return "GRAPH IS STALE, run `jarvis build graph` (incremental) before trusting details";
|
|
7842
|
+
}
|
|
7750
7843
|
|
|
7751
7844
|
// ../../packages/data/src/resolve.ts
|
|
7752
7845
|
function resolveTarget(ctx, input) {
|
|
@@ -8153,15 +8246,15 @@ import { readFile as readFile5 } from "fs/promises";
|
|
|
8153
8246
|
import { resolve as resolve7 } from "path";
|
|
8154
8247
|
|
|
8155
8248
|
// ../../packages/data/src/meta.ts
|
|
8156
|
-
function stringMeta(metadata,
|
|
8157
|
-
const value = metaValue(metadata,
|
|
8249
|
+
function stringMeta(metadata, path23) {
|
|
8250
|
+
const value = metaValue(metadata, path23);
|
|
8158
8251
|
return typeof value === "string" ? value : void 0;
|
|
8159
8252
|
}
|
|
8160
|
-
function metaValue(metadata,
|
|
8253
|
+
function metaValue(metadata, path23) {
|
|
8161
8254
|
if (!metadata) return void 0;
|
|
8162
|
-
if (Object.prototype.hasOwnProperty.call(metadata,
|
|
8255
|
+
if (Object.prototype.hasOwnProperty.call(metadata, path23)) return metadata[path23];
|
|
8163
8256
|
let current = metadata;
|
|
8164
|
-
for (const part of
|
|
8257
|
+
for (const part of path23.split(".")) {
|
|
8165
8258
|
if (!current || typeof current !== "object" || !Object.prototype.hasOwnProperty.call(current, part))
|
|
8166
8259
|
return void 0;
|
|
8167
8260
|
current = current[part];
|
|
@@ -8272,8 +8365,8 @@ function resolveScopeGovernance(ctx, members, codeNodes) {
|
|
|
8272
8365
|
}
|
|
8273
8366
|
return [...out.values()];
|
|
8274
8367
|
}
|
|
8275
|
-
function resolveDirectoryGovernance(ctx,
|
|
8276
|
-
let dir =
|
|
8368
|
+
function resolveDirectoryGovernance(ctx, path23) {
|
|
8369
|
+
let dir = path23.includes("/") ? path23.slice(0, path23.lastIndexOf("/")) : "";
|
|
8277
8370
|
for (; ; ) {
|
|
8278
8371
|
const siblings = codeNodesInDir(ctx, dir);
|
|
8279
8372
|
if (siblings.length > 0) return resolveScopeGovernance(ctx, [], siblings);
|
|
@@ -8869,15 +8962,15 @@ async function review(ctx, req) {
|
|
|
8869
8962
|
cites: []
|
|
8870
8963
|
});
|
|
8871
8964
|
}
|
|
8872
|
-
for (const
|
|
8873
|
-
const dirGov = resolveDirectoryGovernance(ctx,
|
|
8965
|
+
for (const path23 of unresolved) {
|
|
8966
|
+
const dirGov = resolveDirectoryGovernance(ctx, path23).filter((n) => !isWorkItem(n)).map((g) => g.id).filter((id) => !cites.has(id)).slice(0, 3);
|
|
8874
8967
|
const hint = dirGov.length ? ` Files in its directory are governed by ${dirGov.join(", ")} \u2014 cite if it applies.` : "";
|
|
8875
8968
|
findings.push({
|
|
8876
8969
|
code: "unresolved-edit",
|
|
8877
8970
|
severity: "info",
|
|
8878
8971
|
confidence: "precise",
|
|
8879
|
-
subject:
|
|
8880
|
-
message: `${
|
|
8972
|
+
subject: path23,
|
|
8973
|
+
message: `${path23} isn't in the graph yet (a new file, or a path that doesn't match).${hint || " No governed siblings to assess it by."}`,
|
|
8881
8974
|
cites: dirGov
|
|
8882
8975
|
});
|
|
8883
8976
|
}
|
|
@@ -9352,15 +9445,11 @@ function createServer(ctx, hosted = [], prompts = []) {
|
|
|
9352
9445
|
);
|
|
9353
9446
|
}
|
|
9354
9447
|
for (const p of prompts) {
|
|
9355
|
-
server.registerPrompt(
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
{ role: "user", content: { type: "text", text: await p.read() } }
|
|
9361
|
-
]
|
|
9362
|
-
})
|
|
9363
|
-
);
|
|
9448
|
+
server.registerPrompt(p.name, { title: p.title, description: p.description }, async () => ({
|
|
9449
|
+
messages: [
|
|
9450
|
+
{ role: "user", content: { type: "text", text: await p.read() } }
|
|
9451
|
+
]
|
|
9452
|
+
}));
|
|
9364
9453
|
}
|
|
9365
9454
|
return server;
|
|
9366
9455
|
}
|
|
@@ -9391,14 +9480,23 @@ function buildInstructions(req = {}) {
|
|
|
9391
9480
|
}
|
|
9392
9481
|
async function result(ctx, structured) {
|
|
9393
9482
|
const f = await freshness(ctx);
|
|
9394
|
-
const summary = f.stale && !/stale/i.test(structured.summary) ? `${STALE_PREFIX}${structured.summary}` : structured.summary;
|
|
9483
|
+
const summary = f.stale && !/stale/i.test(structured.summary) ? `${f.verdict === "unknown" ? UNKNOWN_PREFIX : STALE_PREFIX}${structured.summary}` : structured.summary;
|
|
9395
9484
|
return {
|
|
9396
9485
|
content: [{ type: "text", text: summary }],
|
|
9397
9486
|
structuredContent: { ...structured, summary },
|
|
9398
|
-
...f.stale ? {
|
|
9487
|
+
...f.stale ? {
|
|
9488
|
+
_meta: {
|
|
9489
|
+
data: {
|
|
9490
|
+
stale: true,
|
|
9491
|
+
...f.verdict ? { verdict: f.verdict } : {},
|
|
9492
|
+
...f.reason ? { reason: f.reason } : {}
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
} : {}
|
|
9399
9496
|
};
|
|
9400
9497
|
}
|
|
9401
9498
|
var STALE_PREFIX = "\u26A0 STALE GRAPH \u2014 results may be wrong; run `jarvis build graph` (the incremental update \u2014 not --rebuild). ";
|
|
9499
|
+
var UNKNOWN_PREFIX = "\u26A0 FRESHNESS UNKNOWN \u2014 this clone cannot reach its origin, so it cannot tell whether these results are current. ";
|
|
9402
9500
|
|
|
9403
9501
|
// ../../packages/data/src/mcp/snapshot.ts
|
|
9404
9502
|
async function loadSnapshot(config2) {
|
|
@@ -9551,7 +9649,7 @@ router.method("env.updateConfig", parseUpdateConfig, updateConfig);
|
|
|
9551
9649
|
import { execFile as execFile8 } from "child_process";
|
|
9552
9650
|
import crypto4 from "crypto";
|
|
9553
9651
|
import { promisify as promisify8 } from "util";
|
|
9554
|
-
import
|
|
9652
|
+
import path17 from "path";
|
|
9555
9653
|
import fs15 from "fs/promises";
|
|
9556
9654
|
var execFileAsync3 = promisify8(execFile8);
|
|
9557
9655
|
var MAX_SEARCH_RESULTS = 50;
|
|
@@ -9571,10 +9669,10 @@ async function searchFiles(workspacePath2, query4) {
|
|
|
9571
9669
|
const allEntries = [
|
|
9572
9670
|
...[...dirSet].map((d) => ({
|
|
9573
9671
|
path: d,
|
|
9574
|
-
name:
|
|
9672
|
+
name: path17.basename(d),
|
|
9575
9673
|
type: "directory"
|
|
9576
9674
|
})),
|
|
9577
|
-
...allFiles.map((f) => ({ path: f, name:
|
|
9675
|
+
...allFiles.map((f) => ({ path: f, name: path17.basename(f), type: "file" }))
|
|
9578
9676
|
];
|
|
9579
9677
|
if (!query4.trim()) {
|
|
9580
9678
|
const sorted = [...allEntries].sort((a, b) => {
|
|
@@ -9608,7 +9706,7 @@ async function fallbackSearch(workspacePath2, query4) {
|
|
|
9608
9706
|
for (const entry of entries) {
|
|
9609
9707
|
if (results.length >= MAX_SEARCH_RESULTS) break;
|
|
9610
9708
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
9611
|
-
const rel =
|
|
9709
|
+
const rel = path17.relative(workspacePath2, path17.join(dir, entry.name));
|
|
9612
9710
|
if (rel.toLowerCase().includes(q)) {
|
|
9613
9711
|
results.push({
|
|
9614
9712
|
path: rel,
|
|
@@ -9616,7 +9714,7 @@ async function fallbackSearch(workspacePath2, query4) {
|
|
|
9616
9714
|
type: entry.isDirectory() ? "directory" : "file"
|
|
9617
9715
|
});
|
|
9618
9716
|
}
|
|
9619
|
-
if (entry.isDirectory()) await walk2(
|
|
9717
|
+
if (entry.isDirectory()) await walk2(path17.join(dir, entry.name), depth + 1);
|
|
9620
9718
|
}
|
|
9621
9719
|
} catch {
|
|
9622
9720
|
}
|
|
@@ -9703,8 +9801,8 @@ async function walkTree(req) {
|
|
|
9703
9801
|
});
|
|
9704
9802
|
const nodes = [];
|
|
9705
9803
|
for (const entry of entries) {
|
|
9706
|
-
const childAbs =
|
|
9707
|
-
const rel =
|
|
9804
|
+
const childAbs = path17.join(absPath, entry.name);
|
|
9805
|
+
const rel = path17.relative(rootPath, childAbs);
|
|
9708
9806
|
if (entry.isDirectory) {
|
|
9709
9807
|
const children = await walkTree({ absPath: childAbs, rootPath, depth: depth + 1 });
|
|
9710
9808
|
nodes.push({ name: entry.name, path: rel, type: "directory", children });
|
|
@@ -9715,9 +9813,9 @@ async function walkTree(req) {
|
|
|
9715
9813
|
return nodes;
|
|
9716
9814
|
}
|
|
9717
9815
|
async function readFileContent(workdir, relPath) {
|
|
9718
|
-
const absPath =
|
|
9719
|
-
const resolvedRoot =
|
|
9720
|
-
if (!absPath.startsWith(resolvedRoot +
|
|
9816
|
+
const absPath = path17.resolve(workdir, relPath);
|
|
9817
|
+
const resolvedRoot = path17.resolve(workdir);
|
|
9818
|
+
if (!absPath.startsWith(resolvedRoot + path17.sep) && absPath !== resolvedRoot) {
|
|
9721
9819
|
throw new FileReadError("path_outside_workdir", "path_outside_workdir");
|
|
9722
9820
|
}
|
|
9723
9821
|
const stat4 = await fs15.stat(absPath);
|
|
@@ -9733,8 +9831,8 @@ async function readFileContent(workdir, relPath) {
|
|
|
9733
9831
|
|
|
9734
9832
|
// src/workspace.ts
|
|
9735
9833
|
var workspacePath = null;
|
|
9736
|
-
function setWorkspacePath(
|
|
9737
|
-
workspacePath =
|
|
9834
|
+
function setWorkspacePath(path23) {
|
|
9835
|
+
workspacePath = path23;
|
|
9738
9836
|
}
|
|
9739
9837
|
function getWorkspacePath() {
|
|
9740
9838
|
if (!workspacePath) {
|
|
@@ -10144,7 +10242,7 @@ async function scanHandler(_ctx, req) {
|
|
|
10144
10242
|
router.method("workdir.scan", parseScan2, scanHandler);
|
|
10145
10243
|
|
|
10146
10244
|
// src/start.ts
|
|
10147
|
-
import
|
|
10245
|
+
import path18 from "path";
|
|
10148
10246
|
|
|
10149
10247
|
// src/upstream.ts
|
|
10150
10248
|
import WebSocket from "ws";
|
|
@@ -10154,7 +10252,7 @@ import { createRequire as createRequire2 } from "module";
|
|
|
10154
10252
|
var _require = createRequire2(import.meta.url);
|
|
10155
10253
|
var VERSION2 = _require("../package.json").version ?? "0.0.0";
|
|
10156
10254
|
var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
|
|
10157
|
-
var SHA = "
|
|
10255
|
+
var SHA = "8686d75";
|
|
10158
10256
|
var BUILT = "2026-09-11";
|
|
10159
10257
|
var BUILD = SHA ?? "source";
|
|
10160
10258
|
var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
|
|
@@ -10522,7 +10620,7 @@ async function startAgent(options) {
|
|
|
10522
10620
|
}, 3e4);
|
|
10523
10621
|
void (async () => {
|
|
10524
10622
|
const cloudRoots = await fetchRoots(options.upstream.hubUrl, envId, options.token);
|
|
10525
|
-
env = startEnv({ configPath:
|
|
10623
|
+
env = startEnv({ configPath: path18.join(getConfigDir(), "env.json") });
|
|
10526
10624
|
if (env.getConfig().roots.length === 0 && cloudRoots.length > 0) {
|
|
10527
10625
|
env.updateConfig({ roots: cloudRoots });
|
|
10528
10626
|
}
|
|
@@ -11007,7 +11105,7 @@ function register13(program) {
|
|
|
11007
11105
|
|
|
11008
11106
|
// src/commands/uninstall.ts
|
|
11009
11107
|
import fs17 from "fs";
|
|
11010
|
-
import
|
|
11108
|
+
import path19 from "path";
|
|
11011
11109
|
import { createInterface as createInterface5 } from "readline";
|
|
11012
11110
|
function register14(program) {
|
|
11013
11111
|
program.command("uninstall").description("Stop the agent, remove the OS service, and wipe local config + identity").option("--keep-data", "Skip the data-wipe prompt; preserve skills/prompts/cache/settings").option("--purge", "Skip the prompt and wipe ALL local data (skills, prompts, cache, settings)").action(async (opts) => {
|
|
@@ -11042,9 +11140,9 @@ async function runUninstall(opts) {
|
|
|
11042
11140
|
wipeData = await confirm2("Also remove local data (skills, prompts, cache, settings)? [y/N] ");
|
|
11043
11141
|
if (wipeData) {
|
|
11044
11142
|
const root = getJarvisDir();
|
|
11045
|
-
removeDir(
|
|
11046
|
-
removeDir(
|
|
11047
|
-
removeDir(
|
|
11143
|
+
removeDir(path19.join(root, "skills"));
|
|
11144
|
+
removeDir(path19.join(root, "prompts"));
|
|
11145
|
+
removeDir(path19.join(root, "cache"));
|
|
11048
11146
|
removeFile({ path: getSettingsFile() });
|
|
11049
11147
|
console.log("Uninstalled. Config, identity, logs, OS service, and local data removed.");
|
|
11050
11148
|
} else {
|
|
@@ -11084,12 +11182,12 @@ function instanceUrl() {
|
|
|
11084
11182
|
if (!url) throw new Error("not connected \u2014 run `jarvis connect` first");
|
|
11085
11183
|
return url;
|
|
11086
11184
|
}
|
|
11087
|
-
async function call(
|
|
11185
|
+
async function call(path23, method, body) {
|
|
11088
11186
|
const config2 = loadConfig();
|
|
11089
11187
|
if (!config2?.token) {
|
|
11090
11188
|
throw new Error("not connected \u2014 run `jarvis connect` first");
|
|
11091
11189
|
}
|
|
11092
|
-
const resp = await fetch(new URL(
|
|
11190
|
+
const resp = await fetch(new URL(path23, instanceUrl()), {
|
|
11093
11191
|
method,
|
|
11094
11192
|
headers: {
|
|
11095
11193
|
authorization: `Bearer ${config2.token}`,
|
|
@@ -11098,7 +11196,7 @@ async function call(path22, method, body) {
|
|
|
11098
11196
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
11099
11197
|
});
|
|
11100
11198
|
if (!resp.ok) {
|
|
11101
|
-
throw new Error(`${method} ${
|
|
11199
|
+
throw new Error(`${method} ${path23} failed: ${resp.status} ${await resp.text()}`);
|
|
11102
11200
|
}
|
|
11103
11201
|
return await resp.json();
|
|
11104
11202
|
}
|
|
@@ -11183,12 +11281,12 @@ function register17(program) {
|
|
|
11183
11281
|
}
|
|
11184
11282
|
|
|
11185
11283
|
// src/graph/build.ts
|
|
11186
|
-
import { mkdirSync as
|
|
11284
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, statSync as statSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
11187
11285
|
import { dirname as dirname6, resolve as resolve8 } from "path";
|
|
11188
11286
|
|
|
11189
11287
|
// src/graph/ratchet.ts
|
|
11190
11288
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
11191
|
-
import { readFileSync as
|
|
11289
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
11192
11290
|
import { join as join8 } from "path";
|
|
11193
11291
|
var BASELINE = join8(".claude", "governance.baseline.json");
|
|
11194
11292
|
var GOVERNS = (finding) => finding.severity !== "error" && finding.code !== "PLUGIN_ERROR";
|
|
@@ -11201,7 +11299,7 @@ function countByRule(findings) {
|
|
|
11201
11299
|
}
|
|
11202
11300
|
function readBaseline(repo) {
|
|
11203
11301
|
try {
|
|
11204
|
-
const parsed = JSON.parse(
|
|
11302
|
+
const parsed = JSON.parse(readFileSync6(join8(repo, BASELINE), "utf-8"));
|
|
11205
11303
|
const allowed = parsed?.allowed;
|
|
11206
11304
|
return { allowed: allowed && typeof allowed === "object" ? allowed : {}, adopted: true };
|
|
11207
11305
|
} catch {
|
|
@@ -11238,7 +11336,7 @@ function writeBaseline(repo, baseline) {
|
|
|
11238
11336
|
const ordered = Object.fromEntries(
|
|
11239
11337
|
Object.entries(baseline.allowed).filter(([, count2]) => count2 > 0).sort(([left], [right]) => left.localeCompare(right))
|
|
11240
11338
|
);
|
|
11241
|
-
|
|
11339
|
+
writeFileSync3(join8(repo, BASELINE), `${JSON.stringify({ allowed: ordered }, null, 2)}
|
|
11242
11340
|
`);
|
|
11243
11341
|
}
|
|
11244
11342
|
var NAMED = 5;
|
|
@@ -11360,8 +11458,8 @@ var DEFAULT_STALE_MS = 10 * 60 * 1e3;
|
|
|
11360
11458
|
function acquireBuildLock(repoRoot, options = {}) {
|
|
11361
11459
|
const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
|
|
11362
11460
|
const lockPath = options.lockPath ?? resolve8(repoRoot, ".data", "build.lock");
|
|
11363
|
-
|
|
11364
|
-
const stamp = () =>
|
|
11461
|
+
mkdirSync3(dirname6(lockPath), { recursive: true });
|
|
11462
|
+
const stamp = () => writeFileSync4(lockPath, `${process.pid}
|
|
11365
11463
|
`, { flag: "wx" });
|
|
11366
11464
|
try {
|
|
11367
11465
|
stamp();
|
|
@@ -11381,7 +11479,7 @@ function acquireBuildLock(repoRoot, options = {}) {
|
|
|
11381
11479
|
if (released) return;
|
|
11382
11480
|
released = true;
|
|
11383
11481
|
try {
|
|
11384
|
-
if (
|
|
11482
|
+
if (readFileSync7(lockPath, "utf8").trim() !== String(process.pid)) return;
|
|
11385
11483
|
} catch {
|
|
11386
11484
|
return;
|
|
11387
11485
|
}
|
|
@@ -11411,13 +11509,13 @@ function register18(program) {
|
|
|
11411
11509
|
}
|
|
11412
11510
|
|
|
11413
11511
|
// src/commands/dev.ts
|
|
11414
|
-
import
|
|
11512
|
+
import path21 from "path";
|
|
11415
11513
|
|
|
11416
11514
|
// src/dev.ts
|
|
11417
11515
|
import fs18 from "fs";
|
|
11418
|
-
import
|
|
11516
|
+
import path20 from "path";
|
|
11419
11517
|
function redirectFile() {
|
|
11420
|
-
return
|
|
11518
|
+
return path20.join(getConfigDir(), "dev.json");
|
|
11421
11519
|
}
|
|
11422
11520
|
function readDevRedirect() {
|
|
11423
11521
|
try {
|
|
@@ -11452,8 +11550,8 @@ function isRedirected(req) {
|
|
|
11452
11550
|
if (req.redirect.all) return true;
|
|
11453
11551
|
const cwd = realPath(req.cwd);
|
|
11454
11552
|
return req.redirect.repos.some((repo) => {
|
|
11455
|
-
const rel =
|
|
11456
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
11553
|
+
const rel = path20.relative(realPath(repo), cwd);
|
|
11554
|
+
return rel === "" || !rel.startsWith("..") && !path20.isAbsolute(rel);
|
|
11457
11555
|
});
|
|
11458
11556
|
}
|
|
11459
11557
|
|
|
@@ -11462,7 +11560,7 @@ function register19(program) {
|
|
|
11462
11560
|
const dev = program.command("dev").description("Run a development build in a repo, without replacing the installed jarvis");
|
|
11463
11561
|
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) => {
|
|
11464
11562
|
const current = readDevRedirect();
|
|
11465
|
-
const root =
|
|
11563
|
+
const root = path21.resolve(opts.root ?? current?.root ?? process.cwd());
|
|
11466
11564
|
if (where !== "here" && where !== "all") {
|
|
11467
11565
|
console.error(`Unknown target '${where}' \u2014 say 'here' or 'all'.`);
|
|
11468
11566
|
process.exitCode = 1;
|
|
@@ -12044,9 +12142,9 @@ function describeStart(response) {
|
|
|
12044
12142
|
}
|
|
12045
12143
|
|
|
12046
12144
|
// src/serve/approvals.ts
|
|
12047
|
-
import { readFileSync as
|
|
12145
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
12048
12146
|
import os10 from "os";
|
|
12049
|
-
import
|
|
12147
|
+
import path22 from "path";
|
|
12050
12148
|
function pendingApproval(cwd) {
|
|
12051
12149
|
const unapproved = unapprovedServers(cwd);
|
|
12052
12150
|
const reasons = [
|
|
@@ -12057,15 +12155,15 @@ function pendingApproval(cwd) {
|
|
|
12057
12155
|
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.`;
|
|
12058
12156
|
}
|
|
12059
12157
|
function untrustedFolder(cwd) {
|
|
12060
|
-
const state2 = readJson2(
|
|
12158
|
+
const state2 = readJson2(path22.join(os10.homedir(), ".claude.json"));
|
|
12061
12159
|
const project = state2?.projects?.[cwd];
|
|
12062
12160
|
if (!state2) return false;
|
|
12063
12161
|
return project?.hasTrustDialogAccepted !== true;
|
|
12064
12162
|
}
|
|
12065
12163
|
function unapprovedServers(cwd) {
|
|
12066
|
-
const declared = readJson2(
|
|
12164
|
+
const declared = readJson2(path22.join(cwd, ".mcp.json"));
|
|
12067
12165
|
if (!declared?.mcpServers) return [];
|
|
12068
|
-
const settings = readJson2(
|
|
12166
|
+
const settings = readJson2(path22.join(cwd, ".claude", "settings.local.json"));
|
|
12069
12167
|
if (settings?.enableAllProjectMcpServers === true) return [];
|
|
12070
12168
|
const answered = /* @__PURE__ */ new Set([
|
|
12071
12169
|
...settings?.enabledMcpjsonServers ?? [],
|
|
@@ -12075,7 +12173,7 @@ function unapprovedServers(cwd) {
|
|
|
12075
12173
|
}
|
|
12076
12174
|
function readJson2(file) {
|
|
12077
12175
|
try {
|
|
12078
|
-
return JSON.parse(
|
|
12176
|
+
return JSON.parse(readFileSync8(file, "utf-8"));
|
|
12079
12177
|
} catch {
|
|
12080
12178
|
return null;
|
|
12081
12179
|
}
|
|
@@ -12266,16 +12364,16 @@ async function item(repo, id, res) {
|
|
|
12266
12364
|
}
|
|
12267
12365
|
async function asset(repo, assets, url, res) {
|
|
12268
12366
|
const wanted = url.pathname === "/" || !extname(url.pathname) ? "/index.html" : url.pathname;
|
|
12269
|
-
const
|
|
12270
|
-
if (!
|
|
12367
|
+
const path23 = join9(assets, wanted);
|
|
12368
|
+
if (!path23.startsWith(assets)) {
|
|
12271
12369
|
res.writeHead(403).end("no");
|
|
12272
12370
|
return;
|
|
12273
12371
|
}
|
|
12274
12372
|
try {
|
|
12275
|
-
const raw = await readFile7(
|
|
12276
|
-
const body = extname(
|
|
12373
|
+
const raw = await readFile7(path23);
|
|
12374
|
+
const body = extname(path23) === ".html" ? Buffer.from(named(raw.toString("utf-8"), repo)) : raw;
|
|
12277
12375
|
res.writeHead(200, {
|
|
12278
|
-
"content-type": TYPES[extname(
|
|
12376
|
+
"content-type": TYPES[extname(path23)] ?? "application/octet-stream",
|
|
12279
12377
|
"content-length": body.byteLength,
|
|
12280
12378
|
// The bundle's name never changes, so a cached copy is indistinguishable from the current
|
|
12281
12379
|
// one and a browser will happily keep running the build you just replaced. That is the
|
|
@@ -12352,7 +12450,7 @@ function serveUi(repo, port, options = {}) {
|
|
|
12352
12450
|
|
|
12353
12451
|
// src/ui/source.ts
|
|
12354
12452
|
import { spawn as spawn3 } from "child_process";
|
|
12355
|
-
import { existsSync as
|
|
12453
|
+
import { existsSync as existsSync7, readFileSync as readFileSync9, watch } from "fs";
|
|
12356
12454
|
import { stat as stat3 } from "fs/promises";
|
|
12357
12455
|
import { dirname as dirname8, join as join10, resolve as resolve10 } from "path";
|
|
12358
12456
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
@@ -12372,7 +12470,7 @@ function sourceCheckout() {
|
|
|
12372
12470
|
function builtFrom() {
|
|
12373
12471
|
try {
|
|
12374
12472
|
const manifest = join10(dirname8(fileURLToPath5(import.meta.url)), "..", "package.json");
|
|
12375
|
-
const read3 = JSON.parse(
|
|
12473
|
+
const read3 = JSON.parse(readFileSync9(manifest, "utf-8"));
|
|
12376
12474
|
return typeof read3.jarvis?.source === "string" ? read3.jarvis.source : null;
|
|
12377
12475
|
} catch {
|
|
12378
12476
|
return null;
|
|
@@ -12383,7 +12481,7 @@ function usable(root) {
|
|
|
12383
12481
|
join10(root, "apps", "cli", "src", "ui", "browser", "App.tsx"),
|
|
12384
12482
|
join10(root, "apps", "cli", "tsup.ui.config.ts")
|
|
12385
12483
|
];
|
|
12386
|
-
const present = files2.every((
|
|
12484
|
+
const present = files2.every((path23) => existsSync7(path23)) && tool(root, "tsup") && tool(root, "tsx");
|
|
12387
12485
|
return present ? root : null;
|
|
12388
12486
|
}
|
|
12389
12487
|
function tool(root, name) {
|
|
@@ -12391,7 +12489,7 @@ function tool(root, name) {
|
|
|
12391
12489
|
join10(root, "apps", "cli", "node_modules", ".bin", name),
|
|
12392
12490
|
join10(root, "node_modules", ".bin", name)
|
|
12393
12491
|
];
|
|
12394
|
-
return places.find((
|
|
12492
|
+
return places.find((path23) => existsSync7(path23)) ?? null;
|
|
12395
12493
|
}
|
|
12396
12494
|
function runFromCheckout(req) {
|
|
12397
12495
|
const child = spawn3(
|
|
@@ -12597,15 +12695,15 @@ function createCli() {
|
|
|
12597
12695
|
if (isRunningFromSource()) {
|
|
12598
12696
|
const dotenv = await import("dotenv");
|
|
12599
12697
|
const fs19 = await import("fs");
|
|
12600
|
-
const
|
|
12698
|
+
const path23 = await import("path");
|
|
12601
12699
|
let dir = process.cwd();
|
|
12602
|
-
while (dir !==
|
|
12603
|
-
const envPath =
|
|
12700
|
+
while (dir !== path23.dirname(dir)) {
|
|
12701
|
+
const envPath = path23.join(dir, ".env");
|
|
12604
12702
|
if (fs19.existsSync(envPath)) {
|
|
12605
12703
|
dotenv.config({ path: envPath });
|
|
12606
12704
|
break;
|
|
12607
12705
|
}
|
|
12608
|
-
dir =
|
|
12706
|
+
dir = path23.dirname(dir);
|
|
12609
12707
|
}
|
|
12610
12708
|
}
|
|
12611
12709
|
createCli().parse();
|