@appchy/jarvis 0.1.49 → 0.1.51

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
@@ -669,8 +669,8 @@ function rpcRouter() {
669
669
  return;
670
670
  }
671
671
  if (Array.isArray(parsed)) {
672
- for (const item of parsed) {
673
- await this.dispatch(ctx, item, send);
672
+ for (const item2 of parsed) {
673
+ await this.dispatch(ctx, item2, send);
674
674
  }
675
675
  return;
676
676
  }
@@ -748,7 +748,7 @@ function startIpcServer(socketPath, userId) {
748
748
  };
749
749
  }
750
750
  async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
751
- return new Promise((resolve9, reject) => {
751
+ return new Promise((resolve10, reject) => {
752
752
  const conn = net.connect(socketPath);
753
753
  let buf = "";
754
754
  let settled = false;
@@ -780,7 +780,7 @@ async function callDaemonIpc(socketPath, method, params, timeoutMs = 5e3) {
780
780
  if (parsed.error) {
781
781
  finish(() => reject(new Error(parsed.error?.message ?? "ipc error")));
782
782
  } else {
783
- finish(() => resolve9(parsed.result));
783
+ finish(() => resolve10(parsed.result));
784
784
  }
785
785
  } catch (err) {
786
786
  finish(() => reject(err instanceof Error ? err : new Error(String(err))));
@@ -1106,9 +1106,21 @@ function writeRootsDirect(roots) {
1106
1106
  }
1107
1107
 
1108
1108
  // src/exchange.ts
1109
- import { execSync as execSync2 } from "child_process";
1110
1109
  import os3 from "os";
1111
1110
  import { createInterface } from "readline";
1111
+
1112
+ // src/browser.ts
1113
+ import { execSync as execSync2 } from "child_process";
1114
+ function openBrowser(req) {
1115
+ try {
1116
+ if (process.platform === "darwin") execSync2(`open "${req.url}"`);
1117
+ else if (process.platform === "win32") execSync2(`start "" "${req.url}"`);
1118
+ else execSync2(`xdg-open "${req.url}"`);
1119
+ } catch {
1120
+ }
1121
+ }
1122
+
1123
+ // src/exchange.ts
1112
1124
  var POLL_INTERVAL_MS2 = 2e3;
1113
1125
  var POLL_TIMEOUT_MS = 10 * 6e4;
1114
1126
  async function exchangeCode(req) {
@@ -1203,21 +1215,13 @@ function humanizeAgo(iso) {
1203
1215
  }
1204
1216
  function readlineQuestion(req) {
1205
1217
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1206
- return new Promise((resolve9) => {
1218
+ return new Promise((resolve10) => {
1207
1219
  rl.question(req.prompt, (answer) => {
1208
1220
  rl.close();
1209
- resolve9(answer);
1221
+ resolve10(answer);
1210
1222
  });
1211
1223
  });
1212
1224
  }
1213
- function openBrowser(req) {
1214
- try {
1215
- if (process.platform === "darwin") execSync2(`open "${req.url}"`);
1216
- else if (process.platform === "win32") execSync2(`start "" "${req.url}"`);
1217
- else execSync2(`xdg-open "${req.url}"`);
1218
- } catch {
1219
- }
1220
- }
1221
1225
  function sleep(ms) {
1222
1226
  return new Promise((r) => setTimeout(r, ms));
1223
1227
  }
@@ -2286,10 +2290,10 @@ function rootsEqual(a, b) {
2286
2290
  }
2287
2291
  async function readlineQuestion2(question) {
2288
2292
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
2289
- return new Promise((resolve9) => {
2293
+ return new Promise((resolve10) => {
2290
2294
  rl.question(question, (input) => {
2291
2295
  rl.close();
2292
- resolve9(input);
2296
+ resolve10(input);
2293
2297
  });
2294
2298
  });
2295
2299
  }
@@ -2366,8 +2370,8 @@ function defaultPrompts() {
2366
2370
  }
2367
2371
  async function promptList(question, fallback) {
2368
2372
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
2369
- const answer = await new Promise((resolve9) => {
2370
- rl.question(question, (input) => resolve9(input.trim()));
2373
+ const answer = await new Promise((resolve10) => {
2374
+ rl.question(question, (input) => resolve10(input.trim()));
2371
2375
  });
2372
2376
  rl.close();
2373
2377
  const raw = !answer ? fallback : answer.split(",").map((s) => s.trim()).filter(Boolean);
@@ -2521,8 +2525,8 @@ function register5(program) {
2521
2525
  }
2522
2526
  async function confirm(question) {
2523
2527
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
2524
- const answer = await new Promise((resolve9) => {
2525
- rl.question(question, (input) => resolve9(input.trim()));
2528
+ const answer = await new Promise((resolve10) => {
2529
+ rl.question(question, (input) => resolve10(input.trim()));
2526
2530
  });
2527
2531
  rl.close();
2528
2532
  return /^y(es)?$/i.test(answer);
@@ -2690,13 +2694,35 @@ function publish(ctx, channel, payload2) {
2690
2694
  publisher(ctx, channel, payload2);
2691
2695
  }
2692
2696
 
2697
+ // src/graph/repo.ts
2698
+ import { homedir } from "os";
2699
+ import { dirname, resolve } from "path";
2700
+ import { fileURLToPath } from "url";
2701
+ function resolveVendor() {
2702
+ process.env["JARVIS_DATA_VENDOR"] ??= resolve(dirname(fileURLToPath(import.meta.url)), "data");
2703
+ return process.env["JARVIS_DATA_VENDOR"];
2704
+ }
2705
+ function resolveRepo(repo) {
2706
+ const cwd = repo ? resolve(process.cwd(), repo) : process.cwd();
2707
+ resolveVendor();
2708
+ loadEnvFile(resolve(cwd, ".env"));
2709
+ loadEnvFile(resolve(homedir(), ".jarvis", "data.env"));
2710
+ return cwd;
2711
+ }
2712
+ function loadEnvFile(path22) {
2713
+ try {
2714
+ process.loadEnvFile(path22);
2715
+ } catch {
2716
+ }
2717
+ }
2718
+
2693
2719
  // ../../providers/anthropic/src/anthropic.agent.provider.ts
2694
2720
  import { execFile as execFile2 } from "child_process";
2695
2721
  import { chmodSync, existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, statSync } from "fs";
2696
2722
  import { createRequire } from "module";
2697
2723
  import { randomUUID } from "crypto";
2698
- import { homedir } from "os";
2699
- import { dirname, join } from "path";
2724
+ import { homedir as homedir2 } from "os";
2725
+ import { dirname as dirname2, join } from "path";
2700
2726
  import { promisify as promisify2 } from "util";
2701
2727
  var run2 = promisify2(execFile2);
2702
2728
  var require2 = createRequire(import.meta.url);
@@ -2745,7 +2771,7 @@ function ensureSpawnHelperIsExecutable() {
2745
2771
  try {
2746
2772
  const entry = require2.resolve("node-pty");
2747
2773
  const helper = join(
2748
- dirname(entry),
2774
+ dirname2(entry),
2749
2775
  "..",
2750
2776
  "prebuilds",
2751
2777
  `${process.platform}-${process.arch}`,
@@ -2759,7 +2785,7 @@ function ensureSpawnHelperIsExecutable() {
2759
2785
  }
2760
2786
  }
2761
2787
  async function idOf(term) {
2762
- const entry = join(homedir(), ".claude", "sessions", `${term.pid}.json`);
2788
+ const entry = join(homedir2(), ".claude", "sessions", `${term.pid}.json`);
2763
2789
  for (let attempt = 0; attempt < 60; attempt++) {
2764
2790
  if (existsSync3(entry)) {
2765
2791
  try {
@@ -2768,12 +2794,12 @@ async function idOf(term) {
2768
2794
  } catch {
2769
2795
  }
2770
2796
  }
2771
- await new Promise((resolve9) => setTimeout(resolve9, 250));
2797
+ await new Promise((resolve10) => setTimeout(resolve10, 250));
2772
2798
  }
2773
2799
  throw new Error("Claude Code started but never registered a session id");
2774
2800
  }
2775
2801
  function candidatePaths2() {
2776
- const home = homedir();
2802
+ const home = homedir2();
2777
2803
  return [
2778
2804
  join(home, ".local", "bin", "claude"),
2779
2805
  join(home, ".claude", "local", "claude"),
@@ -2837,13 +2863,13 @@ async function live(req) {
2837
2863
  else options.signal.addEventListener("abort", () => term.kill(), { once: true });
2838
2864
  }
2839
2865
  rest();
2840
- await new Promise((resolve9) => {
2866
+ await new Promise((resolve10) => {
2841
2867
  term.onExit(({ exitCode, signal }) => {
2842
2868
  if (idle) clearTimeout(idle);
2843
2869
  if (closedForIdle) report2("stopped", "closed after sitting idle");
2844
2870
  else if (exitCode === 0) report2("done");
2845
2871
  else report2("failed", endedAs(exitCode, signal));
2846
- resolve9();
2872
+ resolve10();
2847
2873
  });
2848
2874
  });
2849
2875
  }
@@ -2855,10 +2881,10 @@ function endedAs(exitCode, signal) {
2855
2881
  // ../../providers/anthropic/src/anthropic.session.provider.ts
2856
2882
  import { stat as fsStat } from "fs/promises";
2857
2883
  import { createReadStream, readdirSync as readdirSync3, readFileSync as readFileSync3 } from "fs";
2858
- import { homedir as homedir2 } from "os";
2884
+ import { homedir as homedir3 } from "os";
2859
2885
  import { join as join2 } from "path";
2860
2886
  import readline from "readline";
2861
- var LIVE = join2(homedir2(), ".claude", "sessions");
2887
+ var LIVE = join2(homedir3(), ".claude", "sessions");
2862
2888
  var LIVE_TTL_MS = 1e3;
2863
2889
  var live2 = null;
2864
2890
  function listLiveSessions() {
@@ -4189,6 +4215,19 @@ function toHolders(commits) {
4189
4215
  }
4190
4216
  return distinct;
4191
4217
  }
4218
+ function toHoldersByItem(commits) {
4219
+ const byItem = /* @__PURE__ */ new Map();
4220
+ for (const commit of commits) {
4221
+ for (const id of readTrailers(commit.message).items) {
4222
+ const forItem = byItem.get(id);
4223
+ if (forItem) forItem.push(commit);
4224
+ else byItem.set(id, [commit]);
4225
+ }
4226
+ }
4227
+ const holders = /* @__PURE__ */ new Map();
4228
+ for (const [id, forItem] of byItem) holders.set(id, toHolders(forItem));
4229
+ return holders;
4230
+ }
4192
4231
  function warning(holders, mine) {
4193
4232
  const others = holders.filter((holder) => holder.session !== mine);
4194
4233
  if (others.length === 0) return "";
@@ -4207,6 +4246,7 @@ function isQuiet(commit) {
4207
4246
  }
4208
4247
  function readTrailers(message) {
4209
4248
  const events = [];
4249
+ const items = [];
4210
4250
  let session = "";
4211
4251
  let machine = "";
4212
4252
  for (const raw of message.split("\n")) {
@@ -4214,10 +4254,11 @@ function readTrailers(message) {
4214
4254
  const m = /^(Work-[A-Za-z]+):\s*(.*)$/.exec(line);
4215
4255
  if (!m) continue;
4216
4256
  if (m[1] === "Work-Event" && m[2]) events.push(m[2]);
4257
+ if (m[1] === "Work-Item" && m[2]) items.push(m[2]);
4217
4258
  if (m[1] === "Work-Session") session = m[2] ?? "";
4218
4259
  if (m[1] === "Work-Machine") machine = m[2] ?? "";
4219
4260
  }
4220
- return { session, machine, events };
4261
+ return { session, machine, events, items };
4221
4262
  }
4222
4263
  function describeAge(when) {
4223
4264
  const at = Date.parse(when);
@@ -4232,6 +4273,8 @@ function describeAge(when) {
4232
4273
  // ../../packages/board/src/board.git.ts
4233
4274
  var run3 = promisify4(execFile4);
4234
4275
  var FORMAT = "%x1e%H%x1f%aI%x1f%an%x1f%B";
4276
+ var SOURCED = "%x1e%H%x1f%S%x1f%aI%x1f%an%x1f%B";
4277
+ var TRAILER = "^Work-Item: ";
4235
4278
  var SEP = "";
4236
4279
  var REC = "";
4237
4280
  async function boardCommits(root, id) {
@@ -4279,6 +4322,48 @@ async function git2(root, argv) {
4279
4322
  function escapeGrep(value) {
4280
4323
  return value.replace(/[^A-Za-z0-9_-]/g, (c) => `\\${c}`);
4281
4324
  }
4325
+ async function boardHistory(root) {
4326
+ const [out, tips] = await Promise.all([
4327
+ git2(root, ["log", "--all", "--source", "--reverse", `--format=${SOURCED}`, "-E", `--grep=${TRAILER}`]),
4328
+ branchTips(root)
4329
+ ]);
4330
+ const commits = [];
4331
+ for (const record of out.split(REC)) {
4332
+ if (!record.trim()) continue;
4333
+ const [sha, ref, when, author, message] = record.split(SEP);
4334
+ if (!message || !sha) continue;
4335
+ const branch = shorten(ref ?? "");
4336
+ commits.push({
4337
+ sha,
4338
+ when: when ?? "",
4339
+ by: author ?? "",
4340
+ message,
4341
+ branch,
4342
+ branchLastCommitAt: tips.get(branch) ?? ""
4343
+ });
4344
+ }
4345
+ return commits;
4346
+ }
4347
+ async function branchTips(root) {
4348
+ const out = await git2(root, [
4349
+ "for-each-ref",
4350
+ `--format=%(refname:short)${SEP}%(committerdate:iso-strict)`,
4351
+ "refs/heads",
4352
+ "refs/remotes"
4353
+ ]);
4354
+ const tips = /* @__PURE__ */ new Map();
4355
+ for (const line of out.split("\n")) {
4356
+ const [name, when] = line.split(SEP);
4357
+ if (name && when) tips.set(name, when);
4358
+ }
4359
+ return tips;
4360
+ }
4361
+ function shorten(ref) {
4362
+ return ref.replace(/^refs\/(heads|remotes)\//, "");
4363
+ }
4364
+ async function listHoldersByItem(root) {
4365
+ return toHoldersByItem(await boardHistory(root));
4366
+ }
4282
4367
  async function listHolders(root, id) {
4283
4368
  return toHolders(await boardCommits(root, id));
4284
4369
  }
@@ -4291,11 +4376,11 @@ import { promisify as promisify5 } from "util";
4291
4376
 
4292
4377
  // ../../packages/errors/src/errors.ts
4293
4378
  var ServiceError = class extends Error {
4294
- constructor(message, code, statusCode = 500, context) {
4379
+ constructor(message, code, statusCode = 500, context2) {
4295
4380
  super(message);
4296
4381
  this.code = code;
4297
4382
  this.statusCode = statusCode;
4298
- this.context = context;
4383
+ this.context = context2;
4299
4384
  this.name = "ServiceError";
4300
4385
  Error.captureStackTrace?.(this, this.constructor);
4301
4386
  }
@@ -4320,8 +4405,8 @@ var NotFoundError = class extends ServiceError {
4320
4405
  }
4321
4406
  };
4322
4407
  var ConflictError = class extends ServiceError {
4323
- constructor(message, context) {
4324
- super(message, "CONFLICT", 409, context);
4408
+ constructor(message, context2) {
4409
+ super(message, "CONFLICT", 409, context2);
4325
4410
  this.name = "ConflictError";
4326
4411
  }
4327
4412
  };
@@ -4351,7 +4436,7 @@ function toWorkItem(req) {
4351
4436
  code: asList(front.code),
4352
4437
  covers: asList(front.covers),
4353
4438
  dependsOn: asList(front.depends_on),
4354
- epic: backlog ? parts[1] ?? "" : parts[2] ?? "",
4439
+ epic: backlog ? parts[1] ?? "" : parts.at(-4) ?? "",
4355
4440
  version: backlog ? "backlog" : parts[1] ?? "",
4356
4441
  path: req.path,
4357
4442
  holders: [],
@@ -4359,7 +4444,7 @@ function toWorkItem(req) {
4359
4444
  };
4360
4445
  }
4361
4446
  function toPage(items, req) {
4362
- const matched = items.filter((item) => req.status ? item.status === req.status : true).filter((item) => req.owner ? item.owner === req.owner : true).filter((item) => req.epic ? item.epic === req.epic : true).filter((item) => req.version ? item.version === req.version : true).sort(byPriorityThenName);
4447
+ const matched = items.filter((item2) => req.status ? item2.status === req.status : true).filter((item2) => req.owner ? item2.owner === req.owner : true).filter((item2) => req.epic ? item2.epic === req.epic : true).filter((item2) => req.version ? item2.version === req.version : true).sort(byPriorityThenName);
4363
4448
  const offset = req.offset ?? 0;
4364
4449
  const limit2 = Math.min(req.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
4365
4450
  const page = matched.slice(offset, offset + limit2);
@@ -4447,12 +4532,19 @@ async function listWorkItems(_ctx, req, options) {
4447
4532
  }
4448
4533
  async function findWorkItem(_ctx, req, options) {
4449
4534
  const all = await scanTree(options.root);
4450
- return all.find((item) => item.id === req.id) ?? null;
4535
+ return all.find((item2) => item2.id === req.id) ?? null;
4451
4536
  }
4452
4537
  async function getWorkItem(ctx, req, options) {
4453
- const item = await findWorkItem(ctx, req, options);
4454
- if (!item) throw new NotFoundError("work item", req.id);
4455
- return item;
4538
+ const item2 = await findWorkItem(ctx, req, options);
4539
+ if (!item2) throw new NotFoundError("work item", req.id);
4540
+ return item2;
4541
+ }
4542
+ async function getBrief(_ctx, req, options) {
4543
+ try {
4544
+ return await readFile(join3(options.root, "work", req.path), "utf-8");
4545
+ } catch {
4546
+ return null;
4547
+ }
4456
4548
  }
4457
4549
  async function createWorkItem(ctx, req, options) {
4458
4550
  await harness(
@@ -4577,8 +4669,8 @@ async function scanTree(root) {
4577
4669
  const text = await readFile(file, "utf-8");
4578
4670
  const changedAt = (await stat(file)).mtime.toISOString().slice(0, 10);
4579
4671
  const path22 = relative(work, file).split(sep).join("/");
4580
- const item = toWorkItem({ path: path22, text, changedAt });
4581
- if (item) items.push(item);
4672
+ const item2 = toWorkItem({ path: path22, text, changedAt });
4673
+ if (item2) items.push(item2);
4582
4674
  }
4583
4675
  }
4584
4676
  return items;
@@ -4606,14 +4698,10 @@ function notices(_ctx, _id) {
4606
4698
  }
4607
4699
  async function listWorkItems2(ctx, req, options) {
4608
4700
  const page = await listWorkItems(ctx, req, options);
4609
- const items = await Promise.all(
4610
- page.items.map(async (item) => ({
4611
- ...item,
4612
- holders: await listHolders(options.root, item.id)
4613
- }))
4614
- );
4701
+ const holders = await listHoldersByItem(options.root);
4702
+ const items = page.items.map((item2) => ({ ...item2, holders: holders.get(item2.id) ?? [] }));
4615
4703
  if (!req.takeable) return { ...page, items, notices: notices(ctx) };
4616
- const free = items.filter((item) => item.holders.length === 0);
4704
+ const free = items.filter((item2) => item2.holders.length === 0);
4617
4705
  return {
4618
4706
  items: free,
4619
4707
  offset: page.offset,
@@ -4623,13 +4711,21 @@ async function listWorkItems2(ctx, req, options) {
4623
4711
  notices: notices(ctx)
4624
4712
  };
4625
4713
  }
4714
+ async function getWorkItem2(ctx, req, options) {
4715
+ const item2 = await getWorkItem(ctx, { id: req.id }, options);
4716
+ const [holders, brief3] = await Promise.all([
4717
+ listHolders(options.root, item2.id),
4718
+ getBrief(ctx, { path: item2.path }, options)
4719
+ ]);
4720
+ return { item: { ...item2, holders }, brief: brief3, notices: notices(ctx, item2.id) };
4721
+ }
4626
4722
  async function takeWorkItem(ctx, req, options) {
4627
- const item = await getWorkItem(ctx, { id: req.id }, options);
4723
+ const item2 = await getWorkItem(ctx, { id: req.id }, options);
4628
4724
  const before = await listHolders(options.root, req.id);
4629
4725
  const note = warning(before, req.sessionId ?? "");
4630
4726
  if (req.sessionId)
4631
4727
  await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
4632
- const moved = item.status === "in-progress" ? await getWorkItem(ctx, { id: req.id }, options) : await moveWorkItem(
4728
+ const moved = item2.status === "in-progress" ? await getWorkItem(ctx, { id: req.id }, options) : await moveWorkItem(
4633
4729
  ctx,
4634
4730
  {
4635
4731
  id: req.id,
@@ -4645,19 +4741,19 @@ async function takeWorkItem(ctx, req, options) {
4645
4741
  };
4646
4742
  }
4647
4743
  async function updateWorkItem(ctx, req, options) {
4648
- let item = await getWorkItem(ctx, { id: req.id }, options);
4744
+ let item2 = await getWorkItem(ctx, { id: req.id }, options);
4649
4745
  if (req.observed) {
4650
- item = await observeWorkItem(
4746
+ item2 = await observeWorkItem(
4651
4747
  ctx,
4652
4748
  { id: req.id, ac: req.observed.ac, saw: req.observed.saw },
4653
4749
  options
4654
4750
  );
4655
4751
  }
4656
4752
  if (req.sessionId) {
4657
- item = await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
4753
+ item2 = await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
4658
4754
  }
4659
4755
  if (req.question) {
4660
- item = await askWorkItem(
4756
+ item2 = await askWorkItem(
4661
4757
  ctx,
4662
4758
  {
4663
4759
  id: req.id,
@@ -4669,7 +4765,7 @@ async function updateWorkItem(ctx, req, options) {
4669
4765
  );
4670
4766
  }
4671
4767
  return {
4672
- item: { ...item, holders: await listHolders(options.root, req.id) },
4768
+ item: { ...item2, holders: await listHolders(options.root, req.id) },
4673
4769
  notices: notices(ctx, req.id)
4674
4770
  };
4675
4771
  }
@@ -4677,7 +4773,7 @@ async function completeWorkItem2(ctx, req, options) {
4677
4773
  for (const seen of req.observed ?? []) {
4678
4774
  await observeWorkItem(ctx, { id: req.id, ac: seen.ac, saw: seen.saw }, options);
4679
4775
  }
4680
- const item = await completeWorkItem(
4776
+ const item2 = await completeWorkItem(
4681
4777
  ctx,
4682
4778
  {
4683
4779
  id: req.id,
@@ -4688,7 +4784,7 @@ async function completeWorkItem2(ctx, req, options) {
4688
4784
  options
4689
4785
  );
4690
4786
  return {
4691
- item: { ...item, holders: await listHolders(options.root, req.id) },
4787
+ item: { ...item2, holders: await listHolders(options.root, req.id) },
4692
4788
  notices: notices(ctx, req.id)
4693
4789
  };
4694
4790
  }
@@ -4697,9 +4793,9 @@ async function verifyWorkItem2(ctx, req, options) {
4697
4793
  return { ...verified, notices: notices(ctx, req.id) };
4698
4794
  }
4699
4795
  async function createWorkItem2(ctx, req, options) {
4700
- const item = await createWorkItem(ctx, req, options);
4796
+ const item2 = await createWorkItem(ctx, req, options);
4701
4797
  return {
4702
- item: { ...item, holders: await listHolders(options.root, item.id) },
4798
+ item: { ...item2, holders: await listHolders(options.root, item2.id) },
4703
4799
  notices: notices(ctx)
4704
4800
  };
4705
4801
  }
@@ -4707,17 +4803,17 @@ async function createWorkItem2(ctx, req, options) {
4707
4803
  // src/harness.ts
4708
4804
  import { spawnSync } from "child_process";
4709
4805
  import { existsSync as existsSync4 } from "fs";
4710
- import { dirname as dirname2, join as join4 } from "path";
4711
- import { fileURLToPath } from "url";
4806
+ import { dirname as dirname3, join as join4 } from "path";
4807
+ import { fileURLToPath as fileURLToPath2 } from "url";
4712
4808
  var ENTRY = join4("harness", "work.py");
4713
4809
  var INTERPRETERS = ["python3", "python"];
4714
4810
  var OLDEST = [3, 9];
4715
4811
  var INSTALLED = ["jarvis", "work"];
4716
4812
  function payload() {
4717
- let dir = dirname2(fileURLToPath(import.meta.url));
4813
+ let dir = dirname3(fileURLToPath2(import.meta.url));
4718
4814
  for (let up = 0; up < 6; up++) {
4719
4815
  if (existsSync4(join4(dir, ENTRY))) return join4(dir, ENTRY);
4720
- const parent = dirname2(dir);
4816
+ const parent = dirname3(dir);
4721
4817
  if (parent === dir) break;
4722
4818
  dir = parent;
4723
4819
  }
@@ -4795,6 +4891,9 @@ function parseList(params) {
4795
4891
  if (params && typeof params !== "object") throw new Error("params must be an object");
4796
4892
  return params ?? {};
4797
4893
  }
4894
+ function parseGet(params) {
4895
+ return { id: requireId(params) };
4896
+ }
4798
4897
  function parseTake(params) {
4799
4898
  const p = fields(params);
4800
4899
  const actor = p.actor;
@@ -4845,6 +4944,9 @@ function parseCreate(params) {
4845
4944
  async function list(ctx, req) {
4846
4945
  return listWorkItems2(ctx, req, boardOptions());
4847
4946
  }
4947
+ async function get(ctx, req) {
4948
+ return getWorkItem2(ctx, req, boardOptions());
4949
+ }
4848
4950
  async function take(ctx, req) {
4849
4951
  return takeWorkItem(ctx, req, boardOptions());
4850
4952
  }
@@ -4861,6 +4963,7 @@ async function create(ctx, req) {
4861
4963
  return createWorkItem2(ctx, req, boardOptions());
4862
4964
  }
4863
4965
  router.method("board.list", parseList, list);
4966
+ router.method("board.get", parseGet, get);
4864
4967
  router.method("board.take", parseTake, take);
4865
4968
  router.method("board.update", parseUpdate, update);
4866
4969
  router.method("board.verify", parseVerify, verify);
@@ -4869,7 +4972,7 @@ router.method("board.create", parseCreate, create);
4869
4972
 
4870
4973
  // ../../packages/data/src/finders/files.ts
4871
4974
  import { readFile as readFile2 } from "fs/promises";
4872
- import { relative as relative3, resolve } from "path";
4975
+ import { relative as relative3, resolve as resolve2 } from "path";
4873
4976
  import fg2 from "fast-glob";
4874
4977
 
4875
4978
  // ../../packages/data/src/frontmatter.ts
@@ -5119,10 +5222,10 @@ var GraphStore = class {
5119
5222
  }
5120
5223
  }
5121
5224
  };
5122
- function pushInto(map2, key, item) {
5225
+ function pushInto(map2, key, item2) {
5123
5226
  const list4 = map2.get(key);
5124
- if (list4) list4.push(item);
5125
- else map2.set(key, [item]);
5227
+ if (list4) list4.push(item2);
5228
+ else map2.set(key, [item2]);
5126
5229
  }
5127
5230
  function describeRef(to) {
5128
5231
  return typeof to === "string" ? to : `${to.type}:${to.name}`;
@@ -5237,36 +5340,36 @@ async function find(run6, spec) {
5237
5340
  const listed = await guard(run6, spec.name, () => spec.from.list(ctx));
5238
5341
  if (listed === void 0) return void 0;
5239
5342
  const kept = [];
5240
- for (const item of listed) {
5241
- const checked = await validate(run6, spec, item);
5343
+ for (const item2 of listed) {
5344
+ const checked = await validate(run6, spec, item2);
5242
5345
  if (checked) kept.push(checked);
5243
5346
  }
5244
5347
  return {
5245
5348
  spec,
5246
- entries: kept.map((item) => ({ item, record: {}, items: [] })),
5349
+ entries: kept.map((item2) => ({ item: item2, record: {}, items: [] })),
5247
5350
  ledger: { name: spec.name, items: kept.length, nodes: 0, edges: {}, reserved: {} }
5248
5351
  };
5249
5352
  }
5250
- async function validate(run6, spec, item) {
5353
+ async function validate(run6, spec, item2) {
5251
5354
  const schema = spec.from.schema;
5252
- if (!schema) return item;
5355
+ if (!schema) return item2;
5253
5356
  const checked = await guard(
5254
5357
  run6,
5255
5358
  spec.name,
5256
- async () => await schema["~standard"].validate(item.data)
5359
+ async () => await schema["~standard"].validate(item2.data)
5257
5360
  );
5258
5361
  if (checked === void 0) return void 0;
5259
5362
  if (checked.issues) {
5260
5363
  run6.report({
5261
5364
  severity: "error",
5262
5365
  code: "MAP_SCHEMA",
5263
- message: `${spec.name}: ${item.ref} does not match its schema \u2014 ${issueText(checked.issues)}. Keys present: ${keyText(item.data)}`,
5366
+ message: `${spec.name}: ${item2.ref} does not match its schema \u2014 ${issueText(checked.issues)}. Keys present: ${keyText(item2.data)}`,
5264
5367
  plugin: spec.name,
5265
- ...item.file !== void 0 ? { file: item.file } : {}
5368
+ ...item2.file !== void 0 ? { file: item2.file } : {}
5266
5369
  });
5267
5370
  return void 0;
5268
5371
  }
5269
- return { ...item, data: checked.value };
5372
+ return { ...item2, data: checked.value };
5270
5373
  }
5271
5374
  async function shape(run6, scope2) {
5272
5375
  const ctx = mapContext(run6, scope2.spec.name);
@@ -5378,8 +5481,8 @@ function toNodeInput(id, record, req) {
5378
5481
  if (key === "items" || key === "links" || key === "rel" || key === "inverse" || key === "ref")
5379
5482
  continue;
5380
5483
  if (RESERVED.includes(key) || key === "id") continue;
5381
- const json = jsonOf(value);
5382
- if (json !== void 0) free[key] = json;
5484
+ const json2 = jsonOf(value);
5485
+ if (json2 !== void 0) free[key] = json2;
5383
5486
  }
5384
5487
  return {
5385
5488
  id,
@@ -5409,16 +5512,16 @@ function jsonOf(value) {
5409
5512
  if (Array.isArray(value)) {
5410
5513
  const out = [];
5411
5514
  for (const one of value) {
5412
- const json = jsonOf(one);
5413
- if (json !== void 0) out.push(json);
5515
+ const json2 = jsonOf(one);
5516
+ if (json2 !== void 0) out.push(json2);
5414
5517
  }
5415
5518
  return out;
5416
5519
  }
5417
5520
  if (typeof value === "object") {
5418
5521
  const out = {};
5419
5522
  for (const [key, held] of Object.entries(value)) {
5420
- const json = jsonOf(held);
5421
- if (json !== void 0) out[key] = json;
5523
+ const json2 = jsonOf(held);
5524
+ if (json2 !== void 0) out[key] = json2;
5422
5525
  }
5423
5526
  return out;
5424
5527
  }
@@ -5802,13 +5905,13 @@ function edgeMatchesConfidence(edge, backendName, confidence) {
5802
5905
  // ../../packages/data/src/config.ts
5803
5906
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
5804
5907
  import { readFile as readFile4 } from "fs/promises";
5805
- import { dirname as dirname4, resolve as resolve3 } from "path";
5806
- import { fileURLToPath as fileURLToPath2 } from "url";
5908
+ import { dirname as dirname5, resolve as resolve4 } from "path";
5909
+ import { fileURLToPath as fileURLToPath3 } from "url";
5807
5910
  import { createJiti } from "jiti";
5808
5911
 
5809
5912
  // ../../packages/data/src/persistences/local.ts
5810
5913
  import { mkdir, readFile as readFile3, writeFile } from "fs/promises";
5811
- import { dirname as dirname3, join as join6, resolve as resolve2 } from "path";
5914
+ import { dirname as dirname4, join as join6, resolve as resolve3 } from "path";
5812
5915
  function localFiles(options = {}) {
5813
5916
  const root = options.root ?? ".data";
5814
5917
  const rel = {
@@ -5818,7 +5921,7 @@ function localFiles(options = {}) {
5818
5921
  state: options.state ?? "state.json",
5819
5922
  memory: options.memory ?? "memory.json"
5820
5923
  };
5821
- const pathOf = (ctx, key) => resolve2(
5924
+ const pathOf = (ctx, key) => resolve3(
5822
5925
  ctx.repoRoot,
5823
5926
  root,
5824
5927
  key.startsWith("plugin:") ? join6("plugins", `${key.slice(7)}.json`) : rel[key]
@@ -5834,7 +5937,7 @@ function localFiles(options = {}) {
5834
5937
  },
5835
5938
  save: async (ctx, key, bytes) => {
5836
5939
  const path22 = pathOf(ctx, key);
5837
- await mkdir(dirname3(path22), { recursive: true });
5940
+ await mkdir(dirname4(path22), { recursive: true });
5838
5941
  await writeFile(path22, bytes, "utf8");
5839
5942
  },
5840
5943
  locate: (ctx, key) => pathOf(ctx, key)
@@ -5850,10 +5953,10 @@ var CONFIG_NAMES = [
5850
5953
  "jarvis.config.cjs"
5851
5954
  ];
5852
5955
  async function loadConfig2(cwd) {
5853
- let dir = resolve3(cwd);
5956
+ let dir = resolve4(cwd);
5854
5957
  for (; ; ) {
5855
5958
  for (const name of CONFIG_NAMES) {
5856
- const path22 = resolve3(dir, name);
5959
+ const path22 = resolve4(dir, name);
5857
5960
  if (existsSync5(path22)) {
5858
5961
  const config2 = await importConfig(path22);
5859
5962
  validateConfig(config2, path22);
@@ -5864,7 +5967,7 @@ async function loadConfig2(cwd) {
5864
5967
  };
5865
5968
  }
5866
5969
  }
5867
- const parent = dirname4(dir);
5970
+ const parent = dirname5(dir);
5868
5971
  if (parent === dir) break;
5869
5972
  dir = parent;
5870
5973
  }
@@ -5880,17 +5983,17 @@ async function importConfig(path22) {
5880
5983
  }
5881
5984
  var PACKAGE_NAME = "@jarvis/data";
5882
5985
  function packageRoot() {
5883
- let dir = dirname4(fileURLToPath2(import.meta.url));
5986
+ let dir = dirname5(fileURLToPath3(import.meta.url));
5884
5987
  for (; ; ) {
5885
- if (existsSync5(resolve3(dir, "package.json"))) return dir;
5886
- const parent = dirname4(dir);
5988
+ if (existsSync5(resolve4(dir, "package.json"))) return dir;
5989
+ const parent = dirname5(dir);
5887
5990
  if (parent === dir) return dir;
5888
5991
  dir = parent;
5889
5992
  }
5890
5993
  }
5891
5994
  function selfAlias() {
5892
5995
  const root = packageRoot();
5893
- const pkg = JSON.parse(readFileSync4(resolve3(root, "package.json"), "utf8"));
5996
+ const pkg = JSON.parse(readFileSync4(resolve4(root, "package.json"), "utf8"));
5894
5997
  if (pkg.name !== PACKAGE_NAME) {
5895
5998
  const vendored = process.env[VENDOR_ENV];
5896
5999
  if (!vendored) {
@@ -5904,7 +6007,7 @@ function selfAlias() {
5904
6007
  for (const [subpath, entry] of Object.entries(pkg.exports ?? {})) {
5905
6008
  const target = typeof entry === "string" ? entry : entry.default;
5906
6009
  if (!target) continue;
5907
- alias[toSpecifier(subpath)] = resolve3(root, target);
6010
+ alias[toSpecifier(subpath)] = resolve4(root, target);
5908
6011
  }
5909
6012
  return alias;
5910
6013
  }
@@ -5920,8 +6023,8 @@ var VENDOR_SUBPATHS = [
5920
6023
  "stores"
5921
6024
  ];
5922
6025
  function toVendorAlias(dir) {
5923
- const alias = { [PACKAGE_NAME]: resolve3(dir, "index.mjs") };
5924
- for (const sub of VENDOR_SUBPATHS) alias[`${PACKAGE_NAME}/${sub}`] = resolve3(dir, `${sub}.mjs`);
6026
+ const alias = { [PACKAGE_NAME]: resolve4(dir, "index.mjs") };
6027
+ for (const sub of VENDOR_SUBPATHS) alias[`${PACKAGE_NAME}/${sub}`] = resolve4(dir, `${sub}.mjs`);
5925
6028
  return alias;
5926
6029
  }
5927
6030
  function toSpecifier(subpath) {
@@ -6026,8 +6129,8 @@ async function pool(items, limit2, work) {
6026
6129
  let next = 0;
6027
6130
  const runners = Array.from({ length: Math.min(limit2, items.length) }, async () => {
6028
6131
  for (let i = next++; i < items.length; i = next++) {
6029
- const item = items[i];
6030
- if (item !== void 0) await work(item);
6132
+ const item2 = items[i];
6133
+ if (item2 !== void 0) await work(item2);
6031
6134
  }
6032
6135
  });
6033
6136
  await Promise.all(runners);
@@ -6533,7 +6636,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6533
6636
  // ../../packages/data/src/mcp/freshness.ts
6534
6637
  import { execFile as execFile6 } from "child_process";
6535
6638
  import { stat as stat2 } from "fs/promises";
6536
- import { resolve as resolve4 } from "path";
6639
+ import { resolve as resolve5 } from "path";
6537
6640
  import { promisify as promisify6 } from "util";
6538
6641
 
6539
6642
  // ../../packages/data/src/context.ts
@@ -6669,7 +6772,7 @@ async function fetchHeadIso(root) {
6669
6772
  const path22 = await git3(root, ["rev-parse", "--git-path", "FETCH_HEAD"]);
6670
6773
  if (!path22) return void 0;
6671
6774
  try {
6672
- return (await stat2(resolve4(root, path22))).mtime.toISOString();
6775
+ return (await stat2(resolve5(root, path22))).mtime.toISOString();
6673
6776
  } catch {
6674
6777
  return void 0;
6675
6778
  }
@@ -6703,7 +6806,7 @@ function nativeGraphJson(ctx) {
6703
6806
  async function freshness(ctx) {
6704
6807
  const root = ctx.config.repoRoot;
6705
6808
  const graphRel = nativeGraphJson(ctx) ?? ".data/graphify/graph.json";
6706
- const graphPath = resolve4(root, graphRel);
6809
+ const graphPath = resolve5(root, graphRel);
6707
6810
  let mtimeIso;
6708
6811
  let mtimeMs;
6709
6812
  try {
@@ -6806,8 +6909,8 @@ function notReadyMs() {
6806
6909
  var PENDING = /* @__PURE__ */ Symbol("pending");
6807
6910
  async function readyWithin(pending) {
6808
6911
  let timer;
6809
- const timeout = new Promise((resolve9) => {
6810
- timer = setTimeout(() => resolve9(PENDING), notReadyMs());
6912
+ const timeout = new Promise((resolve10) => {
6913
+ timer = setTimeout(() => resolve10(PENDING), notReadyMs());
6811
6914
  timer.unref?.();
6812
6915
  });
6813
6916
  try {
@@ -7110,34 +7213,34 @@ function workLadder(graph2) {
7110
7213
  for (const node of graph2.nodes()) {
7111
7214
  if (typeof node.metadata.statusCategory === "string") items.push(node);
7112
7215
  }
7113
- const isItem = new Set(items.map((item) => item.id));
7216
+ const isItem = new Set(items.map((item2) => item2.id));
7114
7217
  const containers = /* @__PURE__ */ new Set();
7115
7218
  const holders = /* @__PURE__ */ new Map();
7116
- for (const item of items) {
7117
- for (const edge of graph2.edges({ from: item.id, relation: "belongs_to" })) {
7219
+ for (const item2 of items) {
7220
+ for (const edge of graph2.edges({ from: item2.id, relation: "belongs_to" })) {
7118
7221
  if (!isItem.has(edge.to)) continue;
7119
7222
  containers.add(edge.to);
7120
- holders.set(item.id, edge.to);
7223
+ holders.set(item2.id, edge.to);
7121
7224
  }
7122
7225
  }
7123
7226
  const children = /* @__PURE__ */ new Map();
7124
7227
  const roots = [];
7125
- for (const item of items) {
7126
- const home = holders.get(item.id);
7228
+ for (const item2 of items) {
7229
+ const home = holders.get(item2.id);
7127
7230
  if (home === void 0) {
7128
- roots.push(item);
7231
+ roots.push(item2);
7129
7232
  continue;
7130
7233
  }
7131
7234
  const bucket = children.get(home) ?? { containers: [], leaves: [] };
7132
- (containers.has(item.id) ? bucket.containers : bucket.leaves).push(item);
7235
+ (containers.has(item2.id) ? bucket.containers : bucket.leaves).push(item2);
7133
7236
  children.set(home, bucket);
7134
7237
  }
7135
7238
  const members = /* @__PURE__ */ new Map();
7136
- for (const item of items) {
7137
- if (containers.has(item.id)) continue;
7138
- const category = String(item.metadata.statusCategory);
7139
- const seen = /* @__PURE__ */ new Set([item.id]);
7140
- let home = holders.get(item.id);
7239
+ for (const item2 of items) {
7240
+ if (containers.has(item2.id)) continue;
7241
+ const category = String(item2.metadata.statusCategory);
7242
+ const seen = /* @__PURE__ */ new Set([item2.id]);
7243
+ let home = holders.get(item2.id);
7141
7244
  while (home !== void 0 && !seen.has(home)) {
7142
7245
  seen.add(home);
7143
7246
  const rollup = members.get(home) ?? {};
@@ -7149,12 +7252,12 @@ function workLadder(graph2) {
7149
7252
  return { items, containers, holders, children, members, roots };
7150
7253
  }
7151
7254
  function leavesOf(ladder) {
7152
- return ladder.items.filter((item) => !ladder.containers.has(item.id));
7255
+ return ladder.items.filter((item2) => !ladder.containers.has(item2.id));
7153
7256
  }
7154
7257
 
7155
7258
  // ../../packages/data/src/mcp/tools/cache.ts
7156
7259
  import { statSync as statSync2 } from "fs";
7157
- import { resolve as resolve5 } from "path";
7260
+ import { resolve as resolve6 } from "path";
7158
7261
  var cache = /* @__PURE__ */ new WeakMap();
7159
7262
  async function cachedEnforcerDiags(ctx, freshPaths) {
7160
7263
  const hit = cache.get(ctx);
@@ -7174,7 +7277,7 @@ function isFresh(snapshot, repoRoot, freshPaths) {
7174
7277
  for (const path22 of freshPaths) {
7175
7278
  let mtimeMs;
7176
7279
  try {
7177
- mtimeMs = statSync2(resolve5(repoRoot, path22)).mtimeMs;
7280
+ mtimeMs = statSync2(resolve6(repoRoot, path22)).mtimeMs;
7178
7281
  } catch {
7179
7282
  return false;
7180
7283
  }
@@ -7314,15 +7417,15 @@ async function brief(ctx, req) {
7314
7417
  const leaves = leavesOf(ladder);
7315
7418
  const byCategory = {};
7316
7419
  const inFlight = [];
7317
- for (const item of leaves) {
7318
- const category = String(item.metadata.statusCategory);
7420
+ for (const item2 of leaves) {
7421
+ const category = String(item2.metadata.statusCategory);
7319
7422
  byCategory[category] = (byCategory[category] ?? 0) + 1;
7320
7423
  if (category === "in-progress")
7321
- inFlight.push(renderWorkItem(ctx, item, ladder.holders.get(item.id)));
7424
+ inFlight.push(renderWorkItem(ctx, item2, ladder.holders.get(item2.id)));
7322
7425
  }
7323
7426
  const openItems = leaves.filter((i) => i.metadata.statusCategory === "open");
7324
- for (const item of openItems.slice(0, Math.max(0, itemCap - inFlight.length))) {
7325
- inFlight.push(renderWorkItem(ctx, item, ladder.holders.get(item.id)));
7427
+ for (const item2 of openItems.slice(0, Math.max(0, itemCap - inFlight.length))) {
7428
+ inFlight.push(renderWorkItem(ctx, item2, ladder.holders.get(item2.id)));
7326
7429
  }
7327
7430
  const shown = inFlight.slice(0, itemCap);
7328
7431
  const allContainers = [...ladder.containers].map((id) => ctx.graph.node(id)).filter((n) => n !== void 0).map((n) => ({
@@ -7407,15 +7510,15 @@ function byContainerRelevance(a, b) {
7407
7510
  const size = (c) => Object.values(c.members).reduce((sum, n) => sum + n, 0);
7408
7511
  return flight(b) - flight(a) || size(b) - size(a) || a.id.localeCompare(b.id);
7409
7512
  }
7410
- function renderWorkItem(ctx, item, home) {
7411
- const priority = item.metadata.priority;
7412
- const dependsOn = ctx.graph.edges({ from: item.id, relation: "depends_on" }).map((e) => e.to);
7513
+ function renderWorkItem(ctx, item2, home) {
7514
+ const priority = item2.metadata.priority;
7515
+ const dependsOn = ctx.graph.edges({ from: item2.id, relation: "depends_on" }).map((e) => e.to);
7413
7516
  return {
7414
- id: item.id,
7415
- ...labelOf(item) ? { title: labelOf(item) } : {},
7416
- type: item.type,
7417
- ...typeof item.metadata.status === "string" ? { status: item.metadata.status } : {},
7418
- statusCategory: String(item.metadata.statusCategory),
7517
+ id: item2.id,
7518
+ ...labelOf(item2) ? { title: labelOf(item2) } : {},
7519
+ type: item2.type,
7520
+ ...typeof item2.metadata.status === "string" ? { status: item2.metadata.status } : {},
7521
+ statusCategory: String(item2.metadata.statusCategory),
7419
7522
  ...typeof priority === "string" ? { priority } : {},
7420
7523
  ...dependsOn.length ? { dependsOn } : {},
7421
7524
  ...home !== void 0 ? { belongsTo: home } : {}
@@ -7476,7 +7579,7 @@ function listConnections(ctx, req) {
7476
7579
  const statusOf0 = start ? statusOf(start) : void 0;
7477
7580
  const seen = new Set(startIds);
7478
7581
  const withStatus = /* @__PURE__ */ new Map();
7479
- const context = /* @__PURE__ */ new Map();
7582
+ const context2 = /* @__PURE__ */ new Map();
7480
7583
  let settled = 0;
7481
7584
  const take2 = (id, via) => {
7482
7585
  if (seen.has(id)) return false;
@@ -7488,7 +7591,7 @@ function listConnections(ctx, req) {
7488
7591
  if (via === void 0 && wants(ctx, node, req.direction)) {
7489
7592
  const thing = render(ctx, node);
7490
7593
  const key = thing.id ?? thing.file ?? id;
7491
- if (!context.has(key)) context.set(key, thing);
7594
+ if (!context2.has(key)) context2.set(key, thing);
7492
7595
  }
7493
7596
  return true;
7494
7597
  }
@@ -7517,7 +7620,7 @@ function listConnections(ctx, req) {
7517
7620
  );
7518
7621
  const page = ordered.slice(0, limit2);
7519
7622
  const label = start ? labelOf(start) ?? start.id : req.node;
7520
- const read3 = [...context.values()];
7623
+ const read3 = [...context2.values()];
7521
7624
  const nothing = ordered.length === 0 && read3.length === 0;
7522
7625
  return {
7523
7626
  summary: nothing ? `Nothing in the map connects to ${label}.` : `${label} connects to ${ordered.length} thing(s) still open${settled > 0 ? ` (and ${settled} settled)` : ""}${read3.length > 0 ? `, plus ${read3.length} it is read against` : ""}.${ordered.length === 0 ? " Nothing open is on it." : ""}`,
@@ -7824,7 +7927,7 @@ function hashText2(text) {
7824
7927
 
7825
7928
  // ../../packages/data/src/mcp/tools/read.ts
7826
7929
  import { readFile as readFile5 } from "fs/promises";
7827
- import { resolve as resolve6 } from "path";
7930
+ import { resolve as resolve7 } from "path";
7828
7931
 
7829
7932
  // ../../packages/data/src/meta.ts
7830
7933
  function stringMeta(metadata, path22) {
@@ -7888,7 +7991,7 @@ async function read2(ctx, req) {
7888
7991
  };
7889
7992
  }
7890
7993
  try {
7891
- const full = await readFile5(resolve6(ctx.config.repoRoot, rel), "utf8");
7994
+ const full = await readFile5(resolve7(ctx.config.repoRoot, rel), "utf8");
7892
7995
  const span = sectionSpan(node);
7893
7996
  const stripped = span ? parseFrontmatter2(full).body : full;
7894
7997
  const text = span ? reanchorSection(stripped, node.id) ?? stripped.slice(span[0], span[1]) : full;
@@ -8331,10 +8434,10 @@ function buildEditSet(ctx, found, limit2, namedGovIds, origin) {
8331
8434
  confidence: "precise",
8332
8435
  ...origin ? { origin } : {}
8333
8436
  });
8334
- const named = namedGovIds.has(hit.id);
8437
+ const named2 = namedGovIds.has(hit.id);
8335
8438
  for (const r of enforcing.results) {
8336
8439
  if (!r.file) continue;
8337
- if (named) namedGovFiles.add(r.file);
8440
+ if (named2) namedGovFiles.add(r.file);
8338
8441
  const why = `enforces ${hit.id}`;
8339
8442
  const existing = byFile.get(r.file);
8340
8443
  if (existing) existing.why = existing.why ? `${existing.why}; ${why}` : why;
@@ -9084,11 +9187,11 @@ function graph() {
9084
9187
  }
9085
9188
  function refuseAnotherBranch(req) {
9086
9189
  const asked = req ?? {};
9087
- const named = typeof asked.branch === "string" ? asked.branch : asked.ref;
9088
- if (typeof named !== "string" || !named.trim()) return;
9190
+ const named2 = typeof asked.branch === "string" ? asked.branch : asked.ref;
9191
+ if (typeof named2 !== "string" || !named2.trim()) return;
9089
9192
  throw new RpcError(
9090
9193
  JSON_RPC.INVALID_PARAMS,
9091
- `the map answers for the branch this machine has checked out, and cannot answer for '${named}'. It is built from one checkout, so an answer for another branch would join this branch's code to that branch's work \u2014 which looks like an answer and is not one. Check that branch out and rebuild, or ask the BOARD instead, which does read any branch: \`work list --branch ${named}\` or \`work find\`.`,
9194
+ `the map answers for the branch this machine has checked out, and cannot answer for '${named2}'. It is built from one checkout, so an answer for another branch would join this branch's code to that branch's work \u2014 which looks like an answer and is not one. Check that branch out and rebuild, or ask the BOARD instead, which does read any branch: \`work list --branch ${named2}\` or \`work find\`.`,
9092
9195
  { type: "map_is_per_checkout" }
9093
9196
  );
9094
9197
  }
@@ -9540,7 +9643,7 @@ function parseList2(params) {
9540
9643
  if (params && typeof params !== "object") throw new Error("params must be an object");
9541
9644
  return params ?? {};
9542
9645
  }
9543
- function parseGet(params) {
9646
+ function parseGet2(params) {
9544
9647
  if (!params || typeof params !== "object") throw new Error("params required");
9545
9648
  const p = params;
9546
9649
  if (typeof p.id !== "string") throw new Error("id required");
@@ -9570,7 +9673,7 @@ function parseDelete(params) {
9570
9673
  async function list2(ctx, req) {
9571
9674
  return listNotifications(ctx, req);
9572
9675
  }
9573
- async function get(ctx, req) {
9676
+ async function get2(ctx, req) {
9574
9677
  return getNotification(ctx, req);
9575
9678
  }
9576
9679
  async function create2(ctx, req) {
@@ -9584,7 +9687,7 @@ async function remove(ctx, req) {
9584
9687
  return { ok: true };
9585
9688
  }
9586
9689
  router.method("notifications.list", parseList2, list2);
9587
- router.method("notifications.get", parseGet, get);
9690
+ router.method("notifications.get", parseGet2, get2);
9588
9691
  router.method("notifications.create", parseCreate2, create2);
9589
9692
  router.method("notifications.update", parseUpdate2, update2);
9590
9693
  router.method("notifications.delete", parseDelete, remove);
@@ -9667,7 +9770,7 @@ function parseList3(params) {
9667
9770
  if (params && typeof params !== "object") throw new Error("params must be an object");
9668
9771
  return params ?? {};
9669
9772
  }
9670
- function parseGet2(params) {
9773
+ function parseGet3(params) {
9671
9774
  if (!params || typeof params !== "object") throw new Error("params required");
9672
9775
  const p = params;
9673
9776
  if (typeof p.id !== "string") throw new Error("id required");
@@ -9677,12 +9780,12 @@ async function list3(ctx, req) {
9677
9780
  void ctx;
9678
9781
  return listSessions(req);
9679
9782
  }
9680
- async function get2(ctx, req) {
9783
+ async function get3(ctx, req) {
9681
9784
  void ctx;
9682
9785
  return getSession(req);
9683
9786
  }
9684
9787
  router.method("sessions.list", parseList3, list3);
9685
- router.method("sessions.get", parseGet2, get2);
9788
+ router.method("sessions.get", parseGet3, get3);
9686
9789
 
9687
9790
  // src/workdir.ts
9688
9791
  import { exec as execCb } from "child_process";
@@ -9788,8 +9891,8 @@ import { createRequire as createRequire2 } from "module";
9788
9891
  var _require = createRequire2(import.meta.url);
9789
9892
  var VERSION2 = _require("../package.json").version ?? "0.0.0";
9790
9893
  var IS_DEV = String(_require("../package.json").name ?? "").endsWith("-dev");
9791
- var SHA = "f529917";
9792
- var BUILT = "2026-09-08";
9894
+ var SHA = "debf214";
9895
+ var BUILT = "2026-09-09";
9793
9896
  var BUILD = SHA ?? "source";
9794
9897
  var BUILD_LABEL = `${SHA ? `${VERSION2} (${SHA}${BUILT ? ` ${BUILT}` : ""})` : `${VERSION2} (source)`}${IS_DEV ? " \u2014 development build" : ""}`;
9795
9898
 
@@ -10102,6 +10205,7 @@ async function startAgent(options) {
10102
10205
  stack: err.stack
10103
10206
  });
10104
10207
  });
10208
+ resolveVendor();
10105
10209
  setWorkspacePath(options.workspacePath);
10106
10210
  const upstream = createUpstreamClient(options.upstream);
10107
10211
  const SILENT_CHANNELS = /* @__PURE__ */ new Set(["env.heartbeat", "session.upserted"]);
@@ -10139,12 +10243,12 @@ async function startAgent(options) {
10139
10243
  upstream.onConnected(() => {
10140
10244
  if (!paired) {
10141
10245
  paired = true;
10142
- const board = findBoardOrigin(options.workspacePath);
10246
+ const board2 = findBoardOrigin(options.workspacePath);
10143
10247
  publish(envCtx, "env.paired", {
10144
10248
  envId,
10145
10249
  version: process.env.JARVIS_VERSION ?? "0.0.0",
10146
10250
  os: `${os9.type()} ${os9.release()}`,
10147
- ...board ? { boardRepo: board.repo, boardBranch: board.branch } : {}
10251
+ ...board2 ? { boardRepo: board2.repo, boardBranch: board2.branch } : {}
10148
10252
  });
10149
10253
  }
10150
10254
  publish(envCtx, "env.heartbeat", { envId, at: (/* @__PURE__ */ new Date()).toISOString() });
@@ -10242,7 +10346,7 @@ async function startAgent(options) {
10242
10346
  timestamp: Date.now()
10243
10347
  };
10244
10348
  publish(envCtx, "env.disconnected", { envId: options.envId, reason: "shutdown" });
10245
- await new Promise((resolve9) => setImmediate(resolve9));
10349
+ await new Promise((resolve10) => setImmediate(resolve10));
10246
10350
  }
10247
10351
  if (heartbeatTimer) clearInterval(heartbeatTimer);
10248
10352
  if (ipcServer) {
@@ -10519,10 +10623,10 @@ async function getHealth(req = {}) {
10519
10623
  async function probeSocket(url, probeMs) {
10520
10624
  const port = portOf(url);
10521
10625
  if (port === null) return { url, reachable: false };
10522
- const reachable = await new Promise((resolve9) => {
10626
+ const reachable = await new Promise((resolve10) => {
10523
10627
  const done = (answer) => {
10524
10628
  socket.destroy();
10525
- resolve9(answer);
10629
+ resolve10(answer);
10526
10630
  };
10527
10631
  const socket = net2.connect({ host: new URL(url).hostname, port }).once("connect", () => done(true)).once("error", () => done(false));
10528
10632
  socket.setTimeout(probeMs, () => done(false));
@@ -10695,10 +10799,10 @@ function removeDir(p) {
10695
10799
  }
10696
10800
  function confirm2(prompt) {
10697
10801
  const rl = createInterface4({ input: process.stdin, output: process.stdout });
10698
- return new Promise((resolve9) => {
10802
+ return new Promise((resolve10) => {
10699
10803
  rl.question(prompt, (answer) => {
10700
10804
  rl.close();
10701
- resolve9(/^y(es)?$/i.test(answer.trim()));
10805
+ resolve10(/^y(es)?$/i.test(answer.trim()));
10702
10806
  });
10703
10807
  });
10704
10808
  }
@@ -10817,15 +10921,15 @@ function register17(program) {
10817
10921
 
10818
10922
  // src/graph/build.ts
10819
10923
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, rmSync, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
10820
- import { dirname as dirname5, resolve as resolve7 } from "path";
10924
+ import { dirname as dirname6, resolve as resolve8 } from "path";
10821
10925
 
10822
10926
  // src/graph/graphify.ts
10823
10927
  import { spawnSync as spawnSync2 } from "child_process";
10824
- import { homedir as homedir3 } from "os";
10928
+ import { homedir as homedir4 } from "os";
10825
10929
  import { delimiter, join as join7 } from "path";
10826
10930
  var BIN = "graphify";
10827
10931
  var PACKAGE = "graphifyy";
10828
- var UV_BIN_DIR = join7(homedir3(), ".local", "bin");
10932
+ var UV_BIN_DIR = join7(homedir4(), ".local", "bin");
10829
10933
  function runnable(bin) {
10830
10934
  const probe = spawnSync2(bin, ["--help"], { stdio: "ignore" });
10831
10935
  return probe.error?.code !== "ENOENT";
@@ -11044,8 +11148,8 @@ ${formatLedger(ledger)}
11044
11148
  var DEFAULT_STALE_MS = 10 * 60 * 1e3;
11045
11149
  function acquireBuildLock(repoRoot, options = {}) {
11046
11150
  const staleMs = options.staleMs ?? DEFAULT_STALE_MS;
11047
- const lockPath = options.lockPath ?? resolve7(repoRoot, ".data", "build.lock");
11048
- mkdirSync2(dirname5(lockPath), { recursive: true });
11151
+ const lockPath = options.lockPath ?? resolve8(repoRoot, ".data", "build.lock");
11152
+ mkdirSync2(dirname6(lockPath), { recursive: true });
11049
11153
  const stamp = () => writeFileSync3(lockPath, `${process.pid}
11050
11154
  `, { flag: "wx" });
11051
11155
  try {
@@ -11082,24 +11186,6 @@ function isStale(lockPath, staleMs) {
11082
11186
  }
11083
11187
  }
11084
11188
 
11085
- // src/graph/repo.ts
11086
- import { homedir as homedir4 } from "os";
11087
- import { dirname as dirname6, resolve as resolve8 } from "path";
11088
- import { fileURLToPath as fileURLToPath3 } from "url";
11089
- function resolveRepo(repo) {
11090
- const cwd = repo ? resolve8(process.cwd(), repo) : process.cwd();
11091
- process.env["JARVIS_DATA_VENDOR"] ??= resolve8(dirname6(fileURLToPath3(import.meta.url)), "data");
11092
- loadEnvFile(resolve8(cwd, ".env"));
11093
- loadEnvFile(resolve8(homedir4(), ".jarvis", "data.env"));
11094
- return cwd;
11095
- }
11096
- function loadEnvFile(path22) {
11097
- try {
11098
- process.loadEnvFile(path22);
11099
- } catch {
11100
- }
11101
- }
11102
-
11103
11189
  // src/commands/build.ts
11104
11190
  function register18(program) {
11105
11191
  const build2 = program.command("build").description("Build an artifact from this repo");
@@ -11588,10 +11674,10 @@ function createTool(handlers) {
11588
11674
  }
11589
11675
  };
11590
11676
  }
11591
- function describeItem(item) {
11592
- const held = item.holders && item.holders.length > 0 ? ` \xB7 held by ${item.holders.map((holder) => holder.by || holder.session).join(", ")}` : "";
11593
- const priority = item.priority ? ` ${item.priority}` : "";
11594
- return `${item.id} \xB7 ${item.status}${priority}${held} \u2014 ${item.title}`;
11677
+ function describeItem(item2) {
11678
+ const held = item2.holders && item2.holders.length > 0 ? ` \xB7 held by ${item2.holders.map((holder) => holder.by || holder.session).join(", ")}` : "";
11679
+ const priority = item2.priority ? ` ${item2.priority}` : "";
11680
+ return `${item2.id} \xB7 ${item2.status}${priority}${held} \u2014 ${item2.title}`;
11595
11681
  }
11596
11682
  function describeList(response) {
11597
11683
  if (response.items.length === 0) return "No work items match.";
@@ -11888,9 +11974,140 @@ function register20(program) {
11888
11974
  });
11889
11975
  }
11890
11976
 
11977
+ // src/commands/ui.ts
11978
+ import { basename as basename2 } from "path";
11979
+
11980
+ // src/ui/server.ts
11981
+ import { createServer as createServer2 } from "http";
11982
+ import { readFile as readFile7 } from "fs/promises";
11983
+ import { basename, dirname as dirname7, extname, join as join9, resolve as resolve9 } from "path";
11984
+ import { fileURLToPath as fileURLToPath4 } from "url";
11985
+ var ASSETS = resolve9(dirname7(fileURLToPath4(import.meta.url)), "ui");
11986
+ var TYPES = {
11987
+ ".html": "text/html; charset=utf-8",
11988
+ ".js": "text/javascript; charset=utf-8",
11989
+ ".css": "text/css; charset=utf-8",
11990
+ ".map": "application/json; charset=utf-8"
11991
+ };
11992
+ function named(html, repo) {
11993
+ return html.replace("<title>Jarvis</title>", `<title>${basename(repo).replace(/[<>]/g, "")} \xB7 Jarvis</title>`);
11994
+ }
11995
+ function context() {
11996
+ return { requestId: `ui-${Date.now().toString(36)}`, userId: "local" };
11997
+ }
11998
+ function json(res, status2, body) {
11999
+ const payload2 = JSON.stringify(body);
12000
+ res.writeHead(status2, {
12001
+ "content-type": "application/json; charset=utf-8",
12002
+ "content-length": Buffer.byteLength(payload2),
12003
+ // The board changes under you as sessions work it; a cached page would show a board that has
12004
+ // moved on and give no sign of it.
12005
+ "cache-control": "no-store"
12006
+ });
12007
+ res.end(payload2);
12008
+ }
12009
+ async function board(repo, url, res) {
12010
+ const req = {};
12011
+ const limit2 = url.searchParams.get("limit");
12012
+ if (limit2) req.limit = Number.parseInt(limit2, 10);
12013
+ const offset = url.searchParams.get("offset");
12014
+ if (offset) req.offset = Number.parseInt(offset, 10);
12015
+ const status2 = url.searchParams.get("status");
12016
+ if (status2) req.status = status2;
12017
+ const page = await listWorkItems2(context(), req, { root: repo, harness: harness2() });
12018
+ json(res, 200, { items: page.items, total: page.total, hasMore: page.hasMore });
12019
+ }
12020
+ async function item(repo, id, res) {
12021
+ const read3 = await getWorkItem2(context(), { id }, { root: repo, harness: harness2() });
12022
+ json(res, 200, { item: read3.item, brief: read3.brief });
12023
+ }
12024
+ async function asset(repo, url, res) {
12025
+ const wanted = url.pathname === "/" || !extname(url.pathname) ? "/index.html" : url.pathname;
12026
+ const path22 = join9(ASSETS, wanted);
12027
+ if (!path22.startsWith(ASSETS)) {
12028
+ res.writeHead(403).end("no");
12029
+ return;
12030
+ }
12031
+ try {
12032
+ const raw = await readFile7(path22);
12033
+ const body = extname(path22) === ".html" ? Buffer.from(named(raw.toString("utf-8"), repo)) : raw;
12034
+ res.writeHead(200, {
12035
+ "content-type": TYPES[extname(path22)] ?? "application/octet-stream",
12036
+ "content-length": body.byteLength
12037
+ });
12038
+ res.end(body);
12039
+ } catch {
12040
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
12041
+ res.end(
12042
+ "not found. If this is a fresh checkout, the UI is built by `pnpm --filter @appchy/jarvis build`."
12043
+ );
12044
+ }
12045
+ }
12046
+ function serveUi(repo, port) {
12047
+ const server = createServer2((req, res) => {
12048
+ const url = new URL(req.url ?? "/", "http://localhost");
12049
+ void (async () => {
12050
+ try {
12051
+ if (url.pathname.startsWith("/api/v1/board/items")) {
12052
+ if (req.method !== "GET") {
12053
+ json(res, 405, {
12054
+ error: "this board is read-only here. Changing it runs the harness that owns the tree's invariants \u2014 use `jarvis work` for now."
12055
+ });
12056
+ return;
12057
+ }
12058
+ const id = url.pathname.slice("/api/v1/board/items".length).replace(/^\//, "");
12059
+ if (id) await item(repo, decodeURIComponent(id), res);
12060
+ else await board(repo, url, res);
12061
+ return;
12062
+ }
12063
+ await asset(repo, url, res);
12064
+ } catch (err) {
12065
+ const status2 = err instanceof ServiceError ? err.statusCode : 500;
12066
+ json(res, status2, { error: err instanceof Error ? err.message : String(err) });
12067
+ }
12068
+ })();
12069
+ });
12070
+ return new Promise((ok, fail) => {
12071
+ server.once("error", fail);
12072
+ server.listen(port, "127.0.0.1", () => {
12073
+ ok({
12074
+ url: `http://localhost:${port}`,
12075
+ close: () => new Promise((done) => server.close(() => done()))
12076
+ });
12077
+ });
12078
+ });
12079
+ }
12080
+
12081
+ // src/commands/ui.ts
12082
+ function register21(program) {
12083
+ program.command("ui").description("Open this repo's board in a browser \u2014 nothing to configure, nothing to join").option("--repo <path>", "The repo to show. Default: the working directory").option("--port <number>", "The port to listen on. Default: 3402").option("--no-open", "Print the address instead of opening a browser").action(async (opts) => {
12084
+ const repo = resolveRepo(opts.repo);
12085
+ const port = opts.port ? Number.parseInt(opts.port, 10) : 3402;
12086
+ let server;
12087
+ try {
12088
+ server = await serveUi(repo, port);
12089
+ } catch (err) {
12090
+ const busy = err.code === "EADDRINUSE";
12091
+ console.error(
12092
+ busy ? `port ${port} is already in use. Something else is on it \u2014 pass --port to pick another.` : `could not start: ${err instanceof Error ? err.message : String(err)}`
12093
+ );
12094
+ process.exitCode = 1;
12095
+ return;
12096
+ }
12097
+ console.log(`${basename2(repo)} \u2192 ${server.url}`);
12098
+ console.log("Reading the board from this checkout. Ctrl-C to stop.");
12099
+ if (opts.open !== false) openBrowser({ url: server.url });
12100
+ await new Promise((done) => {
12101
+ const stop = () => void server.close().then(done);
12102
+ process.once("SIGINT", stop);
12103
+ process.once("SIGTERM", stop);
12104
+ });
12105
+ });
12106
+ }
12107
+
11891
12108
  // src/commands/work.ts
11892
12109
  import { spawnSync as spawnSync3 } from "child_process";
11893
- function register21(program) {
12110
+ function register22(program) {
11894
12111
  program.command("work").description("The board \u2014 take work, prove it, finish it").argument("[args...]", "Passed to the harness untouched").allowUnknownOption().passThroughOptions().helpOption(false).action((passed) => {
11895
12112
  const problem = harnessProblem();
11896
12113
  if (problem) {
@@ -11934,6 +12151,7 @@ function createCli() {
11934
12151
  register20(program);
11935
12152
  register2(program);
11936
12153
  register21(program);
12154
+ register22(program);
11937
12155
  return program;
11938
12156
  }
11939
12157