@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 +407 -189
- package/dist/bin.js.map +1 -1
- package/dist/ui/app.js +102 -0
- package/dist/ui/index.html +22 -0
- package/harness/presets/appchy/PRESET.md +4 -2
- package/package.json +11 -2
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
|
|
673
|
-
await this.dispatch(ctx,
|
|
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((
|
|
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(() =>
|
|
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((
|
|
1218
|
+
return new Promise((resolve10) => {
|
|
1207
1219
|
rl.question(req.prompt, (answer) => {
|
|
1208
1220
|
rl.close();
|
|
1209
|
-
|
|
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((
|
|
2293
|
+
return new Promise((resolve10) => {
|
|
2290
2294
|
rl.question(question, (input) => {
|
|
2291
2295
|
rl.close();
|
|
2292
|
-
|
|
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((
|
|
2370
|
-
rl.question(question, (input) =>
|
|
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((
|
|
2525
|
-
rl.question(question, (input) =>
|
|
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
|
-
|
|
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(
|
|
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((
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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
|
|
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(
|
|
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,
|
|
4379
|
+
constructor(message, code, statusCode = 500, context2) {
|
|
4295
4380
|
super(message);
|
|
4296
4381
|
this.code = code;
|
|
4297
4382
|
this.statusCode = statusCode;
|
|
4298
|
-
this.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,
|
|
4324
|
-
super(message, "CONFLICT", 409,
|
|
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
|
|
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((
|
|
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((
|
|
4535
|
+
return all.find((item2) => item2.id === req.id) ?? null;
|
|
4451
4536
|
}
|
|
4452
4537
|
async function getWorkItem(ctx, req, options) {
|
|
4453
|
-
const
|
|
4454
|
-
if (!
|
|
4455
|
-
return
|
|
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
|
|
4581
|
-
if (
|
|
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
|
|
4610
|
-
|
|
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((
|
|
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
|
|
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 =
|
|
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
|
|
4744
|
+
let item2 = await getWorkItem(ctx, { id: req.id }, options);
|
|
4649
4745
|
if (req.observed) {
|
|
4650
|
-
|
|
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
|
-
|
|
4753
|
+
item2 = await recordSession(ctx, { id: req.id, sessionId: req.sessionId }, options);
|
|
4658
4754
|
}
|
|
4659
4755
|
if (req.question) {
|
|
4660
|
-
|
|
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: { ...
|
|
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
|
|
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: { ...
|
|
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
|
|
4796
|
+
const item2 = await createWorkItem(ctx, req, options);
|
|
4701
4797
|
return {
|
|
4702
|
-
item: { ...
|
|
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
|
|
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 =
|
|
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 =
|
|
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,
|
|
5225
|
+
function pushInto(map2, key, item2) {
|
|
5123
5226
|
const list4 = map2.get(key);
|
|
5124
|
-
if (list4) list4.push(
|
|
5125
|
-
else map2.set(key, [
|
|
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
|
|
5241
|
-
const checked = await validate(run6, spec,
|
|
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((
|
|
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,
|
|
5353
|
+
async function validate(run6, spec, item2) {
|
|
5251
5354
|
const schema = spec.from.schema;
|
|
5252
|
-
if (!schema) return
|
|
5355
|
+
if (!schema) return item2;
|
|
5253
5356
|
const checked = await guard(
|
|
5254
5357
|
run6,
|
|
5255
5358
|
spec.name,
|
|
5256
|
-
async () => await schema["~standard"].validate(
|
|
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}: ${
|
|
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
|
-
...
|
|
5368
|
+
...item2.file !== void 0 ? { file: item2.file } : {}
|
|
5266
5369
|
});
|
|
5267
5370
|
return void 0;
|
|
5268
5371
|
}
|
|
5269
|
-
return { ...
|
|
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
|
|
5382
|
-
if (
|
|
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
|
|
5413
|
-
if (
|
|
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
|
|
5421
|
-
if (
|
|
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
|
|
5806
|
-
import { fileURLToPath as
|
|
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
|
|
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) =>
|
|
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(
|
|
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 =
|
|
5956
|
+
let dir = resolve4(cwd);
|
|
5854
5957
|
for (; ; ) {
|
|
5855
5958
|
for (const name of CONFIG_NAMES) {
|
|
5856
|
-
const path22 =
|
|
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 =
|
|
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 =
|
|
5986
|
+
let dir = dirname5(fileURLToPath3(import.meta.url));
|
|
5884
5987
|
for (; ; ) {
|
|
5885
|
-
if (existsSync5(
|
|
5886
|
-
const parent =
|
|
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(
|
|
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)] =
|
|
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]:
|
|
5924
|
-
for (const sub of VENDOR_SUBPATHS) alias[`${PACKAGE_NAME}/${sub}`] =
|
|
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
|
|
6030
|
-
if (
|
|
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
|
|
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(
|
|
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 =
|
|
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((
|
|
6810
|
-
timer = setTimeout(() =>
|
|
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((
|
|
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
|
|
7117
|
-
for (const edge of graph2.edges({ from:
|
|
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(
|
|
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
|
|
7126
|
-
const home = holders.get(
|
|
7228
|
+
for (const item2 of items) {
|
|
7229
|
+
const home = holders.get(item2.id);
|
|
7127
7230
|
if (home === void 0) {
|
|
7128
|
-
roots.push(
|
|
7231
|
+
roots.push(item2);
|
|
7129
7232
|
continue;
|
|
7130
7233
|
}
|
|
7131
7234
|
const bucket = children.get(home) ?? { containers: [], leaves: [] };
|
|
7132
|
-
(containers.has(
|
|
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
|
|
7137
|
-
if (containers.has(
|
|
7138
|
-
const category = String(
|
|
7139
|
-
const seen = /* @__PURE__ */ new Set([
|
|
7140
|
-
let home = holders.get(
|
|
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((
|
|
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
|
|
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(
|
|
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
|
|
7318
|
-
const category = String(
|
|
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,
|
|
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
|
|
7325
|
-
inFlight.push(renderWorkItem(ctx,
|
|
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,
|
|
7411
|
-
const priority =
|
|
7412
|
-
const dependsOn = ctx.graph.edges({ from:
|
|
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:
|
|
7415
|
-
...labelOf(
|
|
7416
|
-
type:
|
|
7417
|
-
...typeof
|
|
7418
|
-
statusCategory: String(
|
|
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
|
|
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 (!
|
|
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 = [...
|
|
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
|
|
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(
|
|
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
|
|
8437
|
+
const named2 = namedGovIds.has(hit.id);
|
|
8335
8438
|
for (const r of enforcing.results) {
|
|
8336
8439
|
if (!r.file) continue;
|
|
8337
|
-
if (
|
|
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
|
|
9088
|
-
if (typeof
|
|
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 '${
|
|
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
|
|
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
|
|
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",
|
|
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
|
|
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
|
|
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",
|
|
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 = "
|
|
9792
|
-
var BUILT = "2026-09-
|
|
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
|
|
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
|
-
...
|
|
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((
|
|
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((
|
|
10626
|
+
const reachable = await new Promise((resolve10) => {
|
|
10523
10627
|
const done = (answer) => {
|
|
10524
10628
|
socket.destroy();
|
|
10525
|
-
|
|
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((
|
|
10802
|
+
return new Promise((resolve10) => {
|
|
10699
10803
|
rl.question(prompt, (answer) => {
|
|
10700
10804
|
rl.close();
|
|
10701
|
-
|
|
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
|
|
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
|
|
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(
|
|
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 ??
|
|
11048
|
-
mkdirSync2(
|
|
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(
|
|
11592
|
-
const held =
|
|
11593
|
-
const priority =
|
|
11594
|
-
return `${
|
|
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
|
|
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
|
|