@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 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 path10 from "path";
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(path22) {
1249
+ function isExecutable(path23) {
1250
1250
  try {
1251
- accessSync(path22, constants.X_OK);
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 path7 from "path";
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(path7.dirname(file), { recursive: true });
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 path8 from "path";
1463
+ import path9 from "path";
1417
1464
  import os4 from "os";
1418
1465
  function serviceSuffix() {
1419
1466
  const dir = getConfigDir();
1420
- const base = path8.join(os4.homedir(), ".jarvis");
1467
+ const base = path9.join(os4.homedir(), ".jarvis");
1421
1468
  if (dir === base) return "";
1422
- const name = dir.startsWith(base + path8.sep) ? dir.slice(base.length + 1) : path8.basename(dir);
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 = path8.join(os4.homedir(), "Library", "LaunchAgents");
1434
- var PLIST_PATH = path8.join(PLIST_DIR, `${PLIST_LABEL}.plist`);
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
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(path8.dirname(logFile), { recursive: true });
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 = path8.join(tmpDir, `jarvis-task-${Date.now()}.xml`);
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 = path8.join(os4.homedir(), ".config", "systemd", "user");
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 = path8.join(SYSTEMD_DIR, UNIT_NAME);
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(path8.dirname(logFile), { recursive: true });
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 = path8.join(this.baseDir, "agent.pid");
1848
- logFile = path8.join(this.baseDir, "agent.log");
1849
- markerFile = path8.join(this.baseDir, "service-installed");
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 path9 from "path";
1927
- var SETTINGS_DIR = path9.join(os5.homedir(), ".claude");
1928
- var SETTINGS_FILE = path9.join(SETTINGS_DIR, "settings.json");
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 = path9.basename(command);
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 path9.resolve(path9.dirname(entry), "..");
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: path9.join(cliRoot, "bin", h.binBasename)
2012
+ binAbsPath: path10.join(cliRoot, "bin", h.binBasename)
1966
2013
  }));
1967
2014
  }
1968
2015
  function ownsClaudeCode() {
1969
- return getConfigDir() === path9.join(os5.homedir(), ".jarvis");
2016
+ return getConfigDir() === path10.join(os5.homedir(), ".jarvis");
1970
2017
  }
1971
2018
  function readSettings() {
1972
- if (!existsSync(SETTINGS_FILE)) return {};
2019
+ if (!existsSync2(SETTINGS_FILE)) return {};
1973
2020
  try {
1974
- const raw = readFileSync(SETTINGS_FILE, "utf-8");
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
- mkdirSync(SETTINGS_DIR, { recursive: true });
1986
- writeFileSync(SETTINGS_FILE, JSON.stringify(s, null, 2) + "\n");
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: existsSync(hook.binAbsPath),
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: existsSync(h.binAbsPath)
2100
+ exists: existsSync2(h.binAbsPath)
2054
2101
  }))
2055
2102
  });
2056
- const missing = resolved.filter((h) => !existsSync(h.binAbsPath));
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 (!existsSync(hook.binAbsPath)) {
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
- ${path10.join(state2.strayDir, "graphify")}, and your shell cannot see it.
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 = path10.join(home, name);
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 = path10.resolve(p.trim());
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(path10.join(getConfigDir(), "env.json"), "utf-8");
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 existsSync2, readdirSync } from "fs";
2701
+ import { existsSync as existsSync3, readdirSync } from "fs";
2648
2702
  import os7 from "os";
2649
- import path11 from "path";
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
- path11.join(home, ".local", "bin", "claude"),
2657
- path11.join(home, ".claude", "local", "claude"),
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 = path11.join(home, ".vscode", "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) => path11.join(extensions, name, "resources", "native-binary", "claude"));
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 (existsSync2(candidate)) return candidate;
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 path12 from "path";
2814
+ import path13 from "path";
2761
2815
  function readConfig() {
2762
2816
  try {
2763
2817
  return JSON.parse(
2764
- fs11.readFileSync(path12.join(getConfigDir(), "env.json"), "utf-8")
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 = path12.resolve(rawPath);
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 = path12.resolve(rawPath);
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 path13 from "path";
2863
+ import path14 from "path";
2810
2864
  function readLocalConfig() {
2811
2865
  try {
2812
2866
  return JSON.parse(
2813
- fs12.readFileSync(path13.join(getConfigDir(), "env.json"), "utf-8")
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(path22) {
2992
+ function loadEnvFile(path23) {
2939
2993
  try {
2940
- process.loadEnvFile(path22);
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 existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync } from "fs";
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 (!existsSync3(helper)) return;
3060
+ if (!existsSync4(helper)) return;
3007
3061
  const mode = statSync(helper).mode;
3008
3062
  if (mode & 73) return;
3009
- chmodSync(helper, mode | 493);
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 (existsSync3(entry)) {
3070
+ if (existsSync4(entry)) {
3017
3071
  try {
3018
- const { sessionId } = JSON.parse(readFileSync2(entry, "utf8"));
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 (existsSync3(candidate)) return candidate;
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 readFileSync3 } from "fs";
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(readFileSync3(join3(LIVE, name), "utf-8"));
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 path15 from "path";
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 path14 from "path";
3495
+ import path15 from "path";
3442
3496
  function getCacheRoot() {
3443
- return path14.join(getJarvisDir(), "cache");
3497
+ return path15.join(getJarvisDir(), "cache");
3444
3498
  }
3445
3499
  function getNamespaceDir(req) {
3446
- return path14.join(getCacheRoot(), req.namespace);
3500
+ return path15.join(getCacheRoot(), req.namespace);
3447
3501
  }
3448
3502
  function getEntryPath(req) {
3449
- return path14.join(getNamespaceDir({ namespace: req.namespace }), `${req.key}.json`);
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 = path14.join(dir, name);
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: path15.join(home, ".claude", "projects") }
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 = path15.resolve(req.path);
3647
+ const abs = path16.resolve(req.path);
3594
3648
  for (const root of getSessionRoots()) {
3595
- const rel = path15.relative(root.dir, abs);
3596
- if (!rel.startsWith("..") && !path15.isAbsolute(rel)) return root.provider;
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 = path15.join(req.root.dir, entry);
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 = path15.join(projectDir, name);
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) => path15.basename(f.filePath, path15.extname(f.filePath)));
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: path15.dirname(req.path),
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 = path15.basename(file.filePath, path15.extname(file.filePath));
3747
- const folderCwd = decodeProjectFolder({ folder: path15.basename(file.projectDir) });
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 = path15.basename(file.filePath, path15.extname(file.filePath));
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) => path15.basename(p).startsWith("."),
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 = path15.basename(p, path15.extname(p));
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 = path15.basename(p, path15.extname(p));
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 = path15.basename(p, path15.extname(p));
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) => path15.basename(p).startsWith(".")
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 = path15.basename(filePath, ".jsonl");
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) => path15.basename(candidate.filePath, path15.extname(candidate.filePath)) === req.id
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 = path15.basename(file.filePath, path15.extname(file.filePath));
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 path22 = pathIn(work, file);
4737
- if (path22.split("/").at(-2) === req.id) return readItem(work, file);
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 path22 = pathIn(work, file);
4887
- if (LEFT_THE_BOARD.some((home) => path22.startsWith(`versions/${home}/`))) continue;
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 existsSync4 } from "fs";
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 (existsSync4(join5(dir, ENTRY))) return join5(dir, ENTRY);
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 path22 = (issue.path ?? []).map((step) => String(typeof step === "object" ? step.key : step)).join(".");
5874
- return path22 ? `${path22}: ${issue.message}` : issue.message;
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 existsSync5, readFileSync as readFileSync4 } from "fs";
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 path22 = pathOf(ctx, key);
6163
- await mkdir(dirname4(path22), { recursive: true });
6164
- await writeFile(path22, bytes, "utf8");
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 path22 = resolve4(dir, name);
6183
- if (existsSync5(path22)) {
6184
- const config2 = await importConfig(path22);
6185
- validateConfig(config2, path22);
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(path22) {
6255
+ async function importConfig(path23) {
6202
6256
  const jiti = createJiti(import.meta.url, { moduleCache: false, alias: selfAlias() });
6203
- const mod = await jiti.import(path22);
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 (existsSync5(resolve4(dir, "package.json"))) return dir;
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(readFileSync4(resolve4(root, "package.json"), "utf8"));
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, path22) {
6310
+ function validateConfig(value, path23) {
6257
6311
  const fail = (msg) => {
6258
- throw new Error(`${path22}: ${msg}`);
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 path22 = await git3(root, ["rev-parse", "--git-path", "FETCH_HEAD"]);
6996
- if (!path22) return void 0;
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, path22))).mtime.toISOString();
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
- if (behindOrigin)
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
- reason2 = `cannot reach ${origin?.ref}, so I cannot tell whether this is current${origin?.lastSyncedIso ? ` \u2014 last synced at ${origin.lastSyncedIso}` : " and this clone has never fetched"}.`;
7057
- else if (behindHead)
7058
- reason2 = `${graphRel} is older than HEAD \u2014 the code graph is stale; 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.`;
7059
- else if (rebuiltOnDisk)
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 path22 = ctx.config.persistence?.locate?.({ repoRoot: ctx.config.repoRoot }, "snapshot");
7076
- if (!path22) return void 0;
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(path22)).mtimeMs;
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 path22 of freshPaths) {
7582
+ for (const path23 of freshPaths) {
7501
7583
  let mtimeMs;
7502
7584
  try {
7503
- mtimeMs = statSync2(resolve6(repoRoot, path22)).mtimeMs;
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 ? " \u2014 GRAPH IS STALE, run `jarvis build graph` (incremental) before trusting details" : ""}${staleOrigins.length ? ` \u2014 ${staleOrigins.map((o) => o.origin).join(", ")} behind their own HEAD (re-run \`jarvis build graph\` there, then re-merge)` : ""}.`,
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, path22) {
8157
- const value = metaValue(metadata, path22);
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, path22) {
8253
+ function metaValue(metadata, path23) {
8161
8254
  if (!metadata) return void 0;
8162
- if (Object.prototype.hasOwnProperty.call(metadata, path22)) return metadata[path22];
8255
+ if (Object.prototype.hasOwnProperty.call(metadata, path23)) return metadata[path23];
8163
8256
  let current = metadata;
8164
- for (const part of path22.split(".")) {
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, path22) {
8276
- let dir = path22.includes("/") ? path22.slice(0, path22.lastIndexOf("/")) : "";
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 path22 of unresolved) {
8873
- const dirGov = resolveDirectoryGovernance(ctx, path22).filter((n) => !isWorkItem(n)).map((g) => g.id).filter((id) => !cites.has(id)).slice(0, 3);
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: path22,
8880
- 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."}`,
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
- p.name,
9357
- { title: p.title, description: p.description },
9358
- async () => ({
9359
- messages: [
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 ? { _meta: { data: { stale: true, ...f.reason ? { reason: f.reason } : {} } } } : {}
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 path16 from "path";
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: path16.basename(d),
9672
+ name: path17.basename(d),
9575
9673
  type: "directory"
9576
9674
  })),
9577
- ...allFiles.map((f) => ({ path: f, name: path16.basename(f), type: "file" }))
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 = path16.relative(workspacePath2, path16.join(dir, entry.name));
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(path16.join(dir, entry.name), depth + 1);
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 = path16.join(absPath, entry.name);
9707
- const rel = path16.relative(rootPath, childAbs);
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 = path16.resolve(workdir, relPath);
9719
- const resolvedRoot = path16.resolve(workdir);
9720
- if (!absPath.startsWith(resolvedRoot + path16.sep) && absPath !== 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(path22) {
9737
- workspacePath = path22;
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 path17 from "path";
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 = "20f7aad";
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: path17.join(getConfigDir(), "env.json") });
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 path18 from "path";
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(path18.join(root, "skills"));
11046
- removeDir(path18.join(root, "prompts"));
11047
- removeDir(path18.join(root, "cache"));
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(path22, method, body) {
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(path22, instanceUrl()), {
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} ${path22} failed: ${resp.status} ${await resp.text()}`);
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 mkdirSync2, readFileSync as readFileSync6, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
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 readFileSync5, writeFileSync as writeFileSync2 } from "fs";
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(readFileSync5(join8(repo, BASELINE), "utf-8"));
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
- writeFileSync2(join8(repo, BASELINE), `${JSON.stringify({ allowed: ordered }, null, 2)}
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
- mkdirSync2(dirname6(lockPath), { recursive: true });
11364
- const stamp = () => writeFileSync3(lockPath, `${process.pid}
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 (readFileSync6(lockPath, "utf8").trim() !== String(process.pid)) return;
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 path20 from "path";
11512
+ import path21 from "path";
11415
11513
 
11416
11514
  // src/dev.ts
11417
11515
  import fs18 from "fs";
11418
- import path19 from "path";
11516
+ import path20 from "path";
11419
11517
  function redirectFile() {
11420
- return path19.join(getConfigDir(), "dev.json");
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 = path19.relative(realPath(repo), cwd);
11456
- return rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel);
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 = path20.resolve(opts.root ?? current?.root ?? process.cwd());
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 readFileSync7 } from "fs";
12145
+ import { readFileSync as readFileSync8 } from "fs";
12048
12146
  import os10 from "os";
12049
- import path21 from "path";
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(path21.join(os10.homedir(), ".claude.json"));
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(path21.join(cwd, ".mcp.json"));
12164
+ const declared = readJson2(path22.join(cwd, ".mcp.json"));
12067
12165
  if (!declared?.mcpServers) return [];
12068
- const settings = readJson2(path21.join(cwd, ".claude", "settings.local.json"));
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(readFileSync7(file, "utf-8"));
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 path22 = join9(assets, wanted);
12270
- if (!path22.startsWith(assets)) {
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(path22);
12276
- const body = extname(path22) === ".html" ? Buffer.from(named(raw.toString("utf-8"), repo)) : raw;
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(path22)] ?? "application/octet-stream",
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 existsSync6, readFileSync as readFileSync8, watch } from "fs";
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(readFileSync8(manifest, "utf-8"));
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((path22) => existsSync6(path22)) && tool(root, "tsup") && tool(root, "tsx");
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((path22) => existsSync6(path22)) ?? null;
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 path22 = await import("path");
12698
+ const path23 = await import("path");
12601
12699
  let dir = process.cwd();
12602
- while (dir !== path22.dirname(dir)) {
12603
- const envPath = path22.join(dir, ".env");
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 = path22.dirname(dir);
12706
+ dir = path23.dirname(dir);
12609
12707
  }
12610
12708
  }
12611
12709
  createCli().parse();