runcastle 1.2.6 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/runcastle.js +316 -85
- package/drizzle/0026_polite_vertigo.sql +2 -0
- package/drizzle/meta/0026_snapshot.json +921 -0
- package/drizzle/meta/_journal.json +7 -0
- package/index.js +312 -81
- package/package.json +1 -1
- package/skills/burner/review-ticket.md +118 -36
- package/skills/packs/README.md +6 -4
- package/skills/packs/runcastle/.claude-plugin/plugin.json +1 -1
- package/skills/packs/runcastle/skills/code-review/SKILL.md +131 -0
- package/skills/packs/runcastle/skills/project/SKILL.md +38 -13
- package/skills/packs/runcastle/skills/tickets/SKILL.md +6 -1
- package/web/assets/index-B3xwZ1FR.css +1 -0
- package/web/assets/index-C0S4i8wY.js +142 -0
- package/web/index.html +2 -2
- package/web/assets/index-bof4-dhY.css +0 -1
- package/web/assets/index-qHtxEVWq.js +0 -142
package/bin/runcastle.js
CHANGED
|
@@ -409,7 +409,9 @@ var init_schemas = __esm(() => {
|
|
|
409
409
|
transcriptPath: z2.string().optional(),
|
|
410
410
|
status: SessionStatus,
|
|
411
411
|
awaitingInput: z2.boolean(),
|
|
412
|
-
worktreePath: z2.string()
|
|
412
|
+
worktreePath: z2.string(),
|
|
413
|
+
title: z2.string().optional(),
|
|
414
|
+
createdAt: z2.number().optional()
|
|
413
415
|
});
|
|
414
416
|
Run = z2.object({
|
|
415
417
|
id: z2.string(),
|
|
@@ -650,7 +652,9 @@ var init_db_schema = __esm(() => {
|
|
|
650
652
|
transcriptPath: text("transcript_path"),
|
|
651
653
|
status: text("status").notNull().$type(),
|
|
652
654
|
awaitingInput: integer("awaiting_input", { mode: "boolean" }).notNull().default(false),
|
|
653
|
-
worktreePath: text("worktree_path").notNull()
|
|
655
|
+
worktreePath: text("worktree_path").notNull(),
|
|
656
|
+
title: text("title"),
|
|
657
|
+
createdAt: integer("created_at")
|
|
654
658
|
});
|
|
655
659
|
tickets = sqliteTable("tickets", {
|
|
656
660
|
id: text("id").primaryKey(),
|
|
@@ -948,24 +952,49 @@ var init_resolve_executable = __esm(() => {
|
|
|
948
952
|
});
|
|
949
953
|
|
|
950
954
|
// src/pty/kill-tree.ts
|
|
951
|
-
import {
|
|
952
|
-
|
|
955
|
+
import { spawn } from "child_process";
|
|
956
|
+
function taskkillTree(pid) {
|
|
957
|
+
return new Promise((resolve3) => {
|
|
958
|
+
let settled = false;
|
|
959
|
+
const settle = () => {
|
|
960
|
+
if (settled)
|
|
961
|
+
return;
|
|
962
|
+
settled = true;
|
|
963
|
+
clearTimeout(timer);
|
|
964
|
+
resolve3();
|
|
965
|
+
};
|
|
966
|
+
const timer = setTimeout(settle, TASKKILL_TIMEOUT_MS);
|
|
967
|
+
timer.unref?.();
|
|
968
|
+
try {
|
|
969
|
+
const child = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
|
|
970
|
+
windowsHide: true,
|
|
971
|
+
stdio: "ignore"
|
|
972
|
+
});
|
|
973
|
+
child.on("exit", settle);
|
|
974
|
+
child.on("close", settle);
|
|
975
|
+
child.on("error", settle);
|
|
976
|
+
} catch {
|
|
977
|
+
settle();
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
}
|
|
953
981
|
async function killProcessTree(pid) {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
982
|
+
const started = Date.now();
|
|
983
|
+
const isWin32 = process.platform === "win32";
|
|
984
|
+
if (isWin32) {
|
|
985
|
+
await taskkillTree(pid);
|
|
986
|
+
} else {
|
|
987
|
+
try {
|
|
958
988
|
process.kill(-pid, "SIGTERM");
|
|
959
|
-
}
|
|
960
|
-
}
|
|
989
|
+
} catch {}
|
|
990
|
+
}
|
|
991
|
+
console.error(`[pty-teardown] killProcessTree: pid=${pid} branch=${isWin32 ? "taskkill" : "pgroup"} settled after ${Date.now() - started}ms`);
|
|
961
992
|
}
|
|
962
|
-
var
|
|
963
|
-
var init_kill_tree =
|
|
964
|
-
execFileAsync = promisify(execFile);
|
|
965
|
-
});
|
|
993
|
+
var TASKKILL_TIMEOUT_MS = 3000;
|
|
994
|
+
var init_kill_tree = () => {};
|
|
966
995
|
|
|
967
996
|
// src/pty/pty-sidecar.ts
|
|
968
|
-
import { spawn } from "child_process";
|
|
997
|
+
import { spawn as spawn2 } from "child_process";
|
|
969
998
|
import { createRequire } from "module";
|
|
970
999
|
import { existsSync as existsSync4 } from "fs";
|
|
971
1000
|
import { fileURLToPath } from "url";
|
|
@@ -991,7 +1020,7 @@ function createSidecarPtySession(cmd, args, opts) {
|
|
|
991
1020
|
const nodeExe = resolveNodeExecutable();
|
|
992
1021
|
const nodePtyEntry = resolveNodePtyEntry();
|
|
993
1022
|
const hostArgs = nodePtyEntry ? [HOST_PATH, nodePtyEntry] : [HOST_PATH];
|
|
994
|
-
const child =
|
|
1023
|
+
const child = spawn2(nodeExe, hostArgs, {
|
|
995
1024
|
stdio: ["pipe", "pipe", "pipe"],
|
|
996
1025
|
windowsHide: true
|
|
997
1026
|
});
|
|
@@ -1300,15 +1329,36 @@ var init_ring_buffer = __esm(() => {
|
|
|
1300
1329
|
});
|
|
1301
1330
|
|
|
1302
1331
|
// src/pty/registry.ts
|
|
1303
|
-
function
|
|
1332
|
+
function log(msg) {
|
|
1333
|
+
console.error(`[pty-teardown] ${msg}`);
|
|
1334
|
+
}
|
|
1335
|
+
function withDeadline(body, ms) {
|
|
1304
1336
|
return new Promise((resolve3) => {
|
|
1305
|
-
const timer = setTimeout(resolve3,
|
|
1306
|
-
|
|
1337
|
+
const timer = setTimeout(() => resolve3(false), ms);
|
|
1338
|
+
body().catch(() => {}).then(() => {
|
|
1307
1339
|
clearTimeout(timer);
|
|
1308
|
-
resolve3();
|
|
1340
|
+
resolve3(true);
|
|
1309
1341
|
});
|
|
1310
1342
|
});
|
|
1311
1343
|
}
|
|
1344
|
+
async function tearDownEntry(entry) {
|
|
1345
|
+
const id = entry.sessionId;
|
|
1346
|
+
if (entry.exited) {
|
|
1347
|
+
log(`${id}: skip (exited=true)`);
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
const ptyPid = entry.pty.pid;
|
|
1351
|
+
const started = Date.now();
|
|
1352
|
+
log(`${id}: start exited=false ptyPid=${ptyPid} t=${started}`);
|
|
1353
|
+
const finished = await withDeadline(async () => {
|
|
1354
|
+
await entry.pty.killTree();
|
|
1355
|
+
log(`${id}: killTree settled after ${Date.now() - started}ms`);
|
|
1356
|
+
try {
|
|
1357
|
+
entry.pty.kill();
|
|
1358
|
+
} catch {}
|
|
1359
|
+
}, TEARDOWN_TIMEOUT_MS);
|
|
1360
|
+
log(`${id}: done after ${Date.now() - started}ms deadline=${finished ? "no" : "FIRED"}`);
|
|
1361
|
+
}
|
|
1312
1362
|
|
|
1313
1363
|
class PtyRegistry {
|
|
1314
1364
|
entries = new Map;
|
|
@@ -1365,16 +1415,20 @@ class PtyRegistry {
|
|
|
1365
1415
|
}
|
|
1366
1416
|
kill(sessionId) {
|
|
1367
1417
|
const entry = this.entries.get(sessionId);
|
|
1368
|
-
if (!entry)
|
|
1418
|
+
if (!entry) {
|
|
1419
|
+
log(`${sessionId}: registry miss (kill)`);
|
|
1369
1420
|
return false;
|
|
1370
|
-
|
|
1421
|
+
}
|
|
1422
|
+
tearDownEntry(entry);
|
|
1371
1423
|
return true;
|
|
1372
1424
|
}
|
|
1373
1425
|
async killTree(sessionId) {
|
|
1374
1426
|
const entry = this.entries.get(sessionId);
|
|
1375
|
-
if (!entry)
|
|
1427
|
+
if (!entry) {
|
|
1428
|
+
log(`${sessionId}: registry miss (killTree)`);
|
|
1376
1429
|
return false;
|
|
1377
|
-
|
|
1430
|
+
}
|
|
1431
|
+
await tearDownEntry(entry);
|
|
1378
1432
|
return true;
|
|
1379
1433
|
}
|
|
1380
1434
|
remove(sessionId) {
|
|
@@ -1383,15 +1437,7 @@ class PtyRegistry {
|
|
|
1383
1437
|
async killAllTrees() {
|
|
1384
1438
|
const all = [...this.entries.values()];
|
|
1385
1439
|
this.entries.clear();
|
|
1386
|
-
await Promise.allSettled(all.map((entry) =>
|
|
1387
|
-
}
|
|
1388
|
-
async tearDown(entry) {
|
|
1389
|
-
if (entry.exited)
|
|
1390
|
-
return;
|
|
1391
|
-
await killTreeBounded(entry.pty);
|
|
1392
|
-
try {
|
|
1393
|
-
entry.pty.kill();
|
|
1394
|
-
} catch {}
|
|
1440
|
+
await Promise.allSettled(all.map((entry) => tearDownEntry(entry)));
|
|
1395
1441
|
}
|
|
1396
1442
|
ids() {
|
|
1397
1443
|
return [...this.entries.keys()];
|
|
@@ -1403,7 +1449,7 @@ function ptyRegistry() {
|
|
|
1403
1449
|
g[REGISTRY_KEY] = new PtyRegistry;
|
|
1404
1450
|
return g[REGISTRY_KEY];
|
|
1405
1451
|
}
|
|
1406
|
-
var
|
|
1452
|
+
var TEARDOWN_TIMEOUT_MS = 5000, REGISTRY_KEY;
|
|
1407
1453
|
var init_registry = __esm(() => {
|
|
1408
1454
|
init_pty();
|
|
1409
1455
|
init_ring_buffer();
|
|
@@ -1631,7 +1677,9 @@ function rowToSession(row) {
|
|
|
1631
1677
|
transcriptPath: row.transcriptPath ?? undefined,
|
|
1632
1678
|
status: row.status,
|
|
1633
1679
|
awaitingInput: row.awaitingInput,
|
|
1634
|
-
worktreePath: row.worktreePath
|
|
1680
|
+
worktreePath: row.worktreePath,
|
|
1681
|
+
title: row.title ?? undefined,
|
|
1682
|
+
createdAt: row.createdAt ?? undefined
|
|
1635
1683
|
});
|
|
1636
1684
|
}
|
|
1637
1685
|
function tryGetFeature(ctx, id) {
|
|
@@ -2095,7 +2143,7 @@ function sleep(ms, signal) {
|
|
|
2095
2143
|
var APP_READY_BUDGET_MS = 120000, APP_READY_INTERVAL_MS = 1000, APP_READY_ATTEMPT_TIMEOUT_MS = 5000;
|
|
2096
2144
|
|
|
2097
2145
|
// src/services/drive-hooks.ts
|
|
2098
|
-
import { spawn as
|
|
2146
|
+
import { spawn as spawn3 } from "child_process";
|
|
2099
2147
|
function tailLines(text2, max2 = OUTPUT_TAIL_LINES) {
|
|
2100
2148
|
const lines = text2.replace(/\r\n/g, `
|
|
2101
2149
|
`).split(`
|
|
@@ -2116,7 +2164,7 @@ function hookSpawnTarget(command) {
|
|
|
2116
2164
|
return { file: "/bin/sh", args: ["-c", command], verbatim: false };
|
|
2117
2165
|
}
|
|
2118
2166
|
function runDriveHook(command, opts) {
|
|
2119
|
-
const spawnFn = opts.spawnFn ??
|
|
2167
|
+
const spawnFn = opts.spawnFn ?? spawn3;
|
|
2120
2168
|
const now = opts.now ?? Date.now;
|
|
2121
2169
|
const timeoutMs = opts.timeoutMs ?? DRIVE_HOOK_TIMEOUT_MS;
|
|
2122
2170
|
const startedAt = now();
|
|
@@ -3509,7 +3557,9 @@ function createSessionRow(ctx, input) {
|
|
|
3509
3557
|
ccSessionId: null,
|
|
3510
3558
|
transcriptPath: null,
|
|
3511
3559
|
status: "launching",
|
|
3512
|
-
worktreePath: input.worktreePath
|
|
3560
|
+
worktreePath: input.worktreePath,
|
|
3561
|
+
title: null,
|
|
3562
|
+
createdAt: Date.now()
|
|
3513
3563
|
}).returning().get();
|
|
3514
3564
|
return rowToSession(row);
|
|
3515
3565
|
}
|
|
@@ -3796,6 +3846,12 @@ function activeProjectSession(ctx, projectId, kind) {
|
|
|
3796
3846
|
const row = ctx.db.select().from(sessions).where(and5(eq5(sessions.projectId, projectId), eq5(sessions.kind, kind), inArray(sessions.status, ["launching", "live"]))).orderBy(desc3(sql2`rowid`)).limit(1).get();
|
|
3797
3847
|
return row ? rowToSession(row) : null;
|
|
3798
3848
|
}
|
|
3849
|
+
function projectSessions(ctx, projectId, kind) {
|
|
3850
|
+
return ctx.db.select().from(sessions).where(and5(eq5(sessions.projectId, projectId), eq5(sessions.kind, kind))).orderBy(desc3(sql2`rowid`)).all().map(rowToSession);
|
|
3851
|
+
}
|
|
3852
|
+
function setSessionTitle(ctx, id, title) {
|
|
3853
|
+
ctx.db.update(sessions).set({ title }).where(eq5(sessions.id, id)).run();
|
|
3854
|
+
}
|
|
3799
3855
|
function hasCompletedProjectSession(ctx, projectId, kind) {
|
|
3800
3856
|
return ctx.db.select({ id: sessions.id }).from(sessions).where(and5(eq5(sessions.projectId, projectId), eq5(sessions.kind, kind), eq5(sessions.status, "ended"))).limit(1).get() !== undefined;
|
|
3801
3857
|
}
|
|
@@ -4513,7 +4569,7 @@ function renderProjectPrompt(brief) {
|
|
|
4513
4569
|
"## runcastle MCP tools",
|
|
4514
4570
|
"Four, and none of the feature pipeline's \u2014 a session with no feature has no business",
|
|
4515
4571
|
"advancing one through a gate:",
|
|
4516
|
-
"- `create_feature({ title, oneLiner, baseBranch?, brief?,
|
|
4572
|
+
"- `create_feature({ title, oneLiner, baseBranch?, brief?, tickets? })` \u2014 the point of",
|
|
4517
4573
|
" this session. It does NOT open a terminal on what it creates; the new card appearing",
|
|
4518
4574
|
" in the rail is the feedback, and the human decides what to work on next.",
|
|
4519
4575
|
"- `get_project_context()` \u2014 the project, its charter, its live ADRs, and a one-line",
|
|
@@ -7002,13 +7058,47 @@ async function startDraft(ctx, featureId, opts = {}) {
|
|
|
7002
7058
|
}
|
|
7003
7059
|
return started;
|
|
7004
7060
|
}
|
|
7061
|
+
function quickTicketTitle(prose) {
|
|
7062
|
+
const line = prose.split(`
|
|
7063
|
+
`)[0].trim();
|
|
7064
|
+
if (line.length <= TICKET_TITLE_MAX)
|
|
7065
|
+
return line;
|
|
7066
|
+
const cut = line.slice(0, TICKET_TITLE_MAX);
|
|
7067
|
+
const space = cut.lastIndexOf(" ");
|
|
7068
|
+
return `${(space > 0 ? cut.slice(0, space) : cut).trimEnd()}\u2026`;
|
|
7069
|
+
}
|
|
7070
|
+
function quickBrief(title, proses) {
|
|
7071
|
+
const body = proses.length === 1 ? proses[0] : proses.map((p, i) => `## Ticket ${i + 1}
|
|
7072
|
+
|
|
7073
|
+
${p}`).join(`
|
|
7074
|
+
|
|
7075
|
+
`);
|
|
7076
|
+
return `# ${title}
|
|
7077
|
+
|
|
7078
|
+
${body}
|
|
7079
|
+
`;
|
|
7080
|
+
}
|
|
7081
|
+
function quickReviewTicket(proses) {
|
|
7082
|
+
return {
|
|
7083
|
+
title: "Review the integrated change",
|
|
7084
|
+
goal: "Review the integrated feature branch and report what you find. Code-review the branch's " + "diff against its base \u2014 always, whatever this change turned out to be \u2014 and additionally " + "drive the app when the diff touches something a human can operate. Write one note per " + "finding; finding bugs is a successful review, and the notes are the deliverable.",
|
|
7085
|
+
context: "This feature came through the quick-change door, so there was no grill session and there " + "is no spec.md or decisions.md to review it against: the whole statement of intent is the " + `${proses.length === 1 ? "sentence" : `${proses.length} sentences`} the human typed, which ` + "are the other tickets in this batch and are reproduced verbatim in brief.md. Nobody " + "prescribed a walkthrough for you either \u2014 judge from the diff whether there is anything " + "drivable, and if there is not, say so and let the code review stand alone. Your digest is " + "the lap's prose summary of what landed; the review page leads with it.",
|
|
7086
|
+
acceptanceCriteria: [
|
|
7087
|
+
"The branch's diff against its base is code-reviewed on both axes \u2014 the repo's own " + "standards, and the change against what was asked for.",
|
|
7088
|
+
...proses.map((prose) => `Landed and does what it says: ${prose}`)
|
|
7089
|
+
],
|
|
7090
|
+
seams: [],
|
|
7091
|
+
blockedBy: proses.map((_, i) => i + 1),
|
|
7092
|
+
kind: "review"
|
|
7093
|
+
};
|
|
7094
|
+
}
|
|
7005
7095
|
async function quickChange(ctx, input) {
|
|
7006
7096
|
const project = requireProjectById(ctx, input.projectId);
|
|
7007
7097
|
const title = input.title.trim();
|
|
7008
|
-
const
|
|
7098
|
+
const proses = input.tickets.map((t) => t.trim()).filter((t) => t !== "");
|
|
7009
7099
|
if (!title)
|
|
7010
7100
|
throw new InvalidInputError("a quick change needs a title");
|
|
7011
|
-
if (
|
|
7101
|
+
if (proses.length === 0)
|
|
7012
7102
|
throw new InvalidInputError("a quick change needs a sentence describing the change");
|
|
7013
7103
|
const slug = uniqueSlug(ctx, project.id, title);
|
|
7014
7104
|
const branch = `feature/${slug}`;
|
|
@@ -7019,7 +7109,7 @@ async function quickChange(ctx, input) {
|
|
|
7019
7109
|
projectId: project.id,
|
|
7020
7110
|
slug,
|
|
7021
7111
|
title,
|
|
7022
|
-
oneLiner:
|
|
7112
|
+
oneLiner: proses[0].split(`
|
|
7023
7113
|
`)[0].trim(),
|
|
7024
7114
|
mapped: false,
|
|
7025
7115
|
lap: 1,
|
|
@@ -7035,25 +7125,25 @@ async function quickChange(ctx, input) {
|
|
|
7035
7125
|
message: branchReady ? `feature.created (${branch} \u2190 ${baseBranch})` : "feature.created (branch pending)",
|
|
7036
7126
|
data: { slug, branch, baseBranch, branchReady }
|
|
7037
7127
|
});
|
|
7038
|
-
scaffoldDocs(ctx, feature, { brief:
|
|
7039
|
-
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
const [ticket] = storeTickets(ctx, feature.id, [
|
|
7043
|
-
{
|
|
7044
|
-
title,
|
|
7128
|
+
scaffoldDocs(ctx, feature, { brief: quickBrief(title, proses) });
|
|
7129
|
+
const stored = storeTickets(ctx, feature.id, [
|
|
7130
|
+
...proses.map((prose) => ({
|
|
7131
|
+
title: quickTicketTitle(prose),
|
|
7045
7132
|
goal: prose,
|
|
7046
7133
|
context: prose,
|
|
7047
7134
|
acceptanceCriteria: [prose],
|
|
7048
7135
|
seams: [],
|
|
7049
7136
|
blockedBy: []
|
|
7050
|
-
}
|
|
7137
|
+
})),
|
|
7138
|
+
quickReviewTicket(proses)
|
|
7051
7139
|
]);
|
|
7140
|
+
const typed = stored.slice(0, proses.length);
|
|
7141
|
+
const review = stored[stored.length - 1];
|
|
7142
|
+
const tally = typed.length === 1 ? "one ticket" : `${typed.length} tickets`;
|
|
7052
7143
|
emit(ctx, feature.id, {
|
|
7053
7144
|
type: "feature.quick_change",
|
|
7054
|
-
message: `quick change \u2014 born at implementation on lap 1 with
|
|
7055
|
-
|
|
7056
|
-
data: { slug, ticketSeq: ticket.seq, phase: "implementation" }
|
|
7145
|
+
message: `quick change \u2014 born at implementation on lap 1 with ${tally} (${typed.map((t) => `#${t.seq}`).join(", ")}) plus a review ticket (#${review.seq}); no grill session, no spec.md`,
|
|
7146
|
+
data: { slug, ticketSeqs: stored.map((t) => t.seq), phase: "implementation" }
|
|
7057
7147
|
});
|
|
7058
7148
|
if (branchReady) {
|
|
7059
7149
|
try {
|
|
@@ -7393,6 +7483,7 @@ function slugify(title) {
|
|
|
7393
7483
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7394
7484
|
return slug || "feature";
|
|
7395
7485
|
}
|
|
7486
|
+
var TICKET_TITLE_MAX = 72;
|
|
7396
7487
|
var init_features = __esm(() => {
|
|
7397
7488
|
init_src();
|
|
7398
7489
|
init_paths();
|
|
@@ -7528,20 +7619,47 @@ function promotionTicket(feature, note) {
|
|
|
7528
7619
|
blockedBy: []
|
|
7529
7620
|
};
|
|
7530
7621
|
}
|
|
7622
|
+
function freezeAsTickets(ctx, noteIds) {
|
|
7623
|
+
if (noteIds.length === 0)
|
|
7624
|
+
throw new InvalidInputError("no notes selected to promote");
|
|
7625
|
+
if (new Set(noteIds).size !== noteIds.length)
|
|
7626
|
+
throw new InvalidInputError("the same note appears twice in the selection");
|
|
7627
|
+
const selected = noteIds.map((id) => getNote(ctx, id));
|
|
7628
|
+
for (const note of selected)
|
|
7629
|
+
assertOpen(note, "promote");
|
|
7630
|
+
const featureId = selected[0].featureId;
|
|
7631
|
+
if (selected.some((n) => n.featureId !== featureId))
|
|
7632
|
+
throw new InvalidInputError("every note in a promotion must belong to the same feature");
|
|
7633
|
+
const feature = getFeatureRow(ctx, featureId);
|
|
7634
|
+
const tickets2 = storeTickets(ctx, featureId, selected.map((note) => promotionTicket(feature, note)));
|
|
7635
|
+
const now = Date.now();
|
|
7636
|
+
selected.forEach((note, i) => {
|
|
7637
|
+
ctx.db.update(testNotes).set({ status: "promoted", ticketId: tickets2[i].id, updatedAt: now }).where(eq10(testNotes.id, note.id)).run();
|
|
7638
|
+
});
|
|
7639
|
+
return { feature, notes: selected.map((n) => getNote(ctx, n.id)), tickets: tickets2 };
|
|
7640
|
+
}
|
|
7531
7641
|
function promoteNote(ctx, noteId) {
|
|
7532
|
-
const
|
|
7533
|
-
|
|
7534
|
-
const
|
|
7535
|
-
|
|
7536
|
-
ctx.db.update(testNotes).set({ status: "promoted", ticketId: ticket.id, updatedAt: Date.now() }).where(eq10(testNotes.id, noteId)).run();
|
|
7537
|
-
emit(ctx, current2.featureId, {
|
|
7642
|
+
const { feature, notes, tickets: tickets2 } = freezeAsTickets(ctx, [noteId]);
|
|
7643
|
+
const [note] = notes;
|
|
7644
|
+
const [ticket] = tickets2;
|
|
7645
|
+
emit(ctx, feature.id, {
|
|
7538
7646
|
type: "note.promoted",
|
|
7539
7647
|
message: `note promoted to ticket ${ticket.seq}`,
|
|
7540
7648
|
ticketId: ticket.id,
|
|
7541
7649
|
data: { noteId, seq: ticket.seq }
|
|
7542
7650
|
});
|
|
7543
7651
|
renderTestNotes(ctx, feature);
|
|
7544
|
-
return { note
|
|
7652
|
+
return { note, ticket };
|
|
7653
|
+
}
|
|
7654
|
+
function promoteMany(ctx, noteIds) {
|
|
7655
|
+
const { feature, notes, tickets: tickets2 } = freezeAsTickets(ctx, noteIds);
|
|
7656
|
+
emit(ctx, feature.id, {
|
|
7657
|
+
type: "notes.promoted",
|
|
7658
|
+
message: `${notes.length} note${notes.length === 1 ? "" : "s"} promoted to ticket${tickets2.length === 1 ? ` ${tickets2[0].seq}` : `s ${tickets2.map((t) => t.seq).join(", ")}`}`,
|
|
7659
|
+
data: { noteIds, seqs: tickets2.map((t) => t.seq) }
|
|
7660
|
+
});
|
|
7661
|
+
renderTestNotes(ctx, feature);
|
|
7662
|
+
return { notes, tickets: tickets2 };
|
|
7545
7663
|
}
|
|
7546
7664
|
function noteLine(ctx, note) {
|
|
7547
7665
|
if (note.status === "open")
|
|
@@ -7808,17 +7926,17 @@ function createFeatureProject(ctx, session, input) {
|
|
|
7808
7926
|
if (!DRAFTING_KINDS.includes(session.kind)) {
|
|
7809
7927
|
throw new GateError(`a ${session.kind} session is read-only and may not create features, drafts included. ` + "Tell the human what is worth capturing; the project session is where features are made.");
|
|
7810
7928
|
}
|
|
7811
|
-
if (!input.draft || input.
|
|
7812
|
-
throw new GateError(`a ${session.kind} session may only PARK a feature here: call create_feature with ` + "`draft: true` and a `brief` carrying why you deferred it. Cutting a branch \u2014 a full " + "create or the quick-change `
|
|
7929
|
+
if (!input.draft || input.tickets) {
|
|
7930
|
+
throw new GateError(`a ${session.kind} session may only PARK a feature here: call create_feature with ` + "`draft: true` and a `brief` carrying why you deferred it. Cutting a branch \u2014 a full " + "create or the quick-change `tickets` shape \u2014 belongs to the project session; tell the " + "human to open it.");
|
|
7813
7931
|
}
|
|
7814
7932
|
return projectForFeature(ctx, getFeatureRow(ctx, requireFeatureId(session)));
|
|
7815
7933
|
}
|
|
7816
7934
|
async function toolCreateFeature(ctx, session, input) {
|
|
7817
7935
|
const project = createFeatureProject(ctx, session, input);
|
|
7818
|
-
const feature = input.
|
|
7936
|
+
const feature = input.tickets ? await quickChange(ctx, {
|
|
7819
7937
|
projectId: project.id,
|
|
7820
7938
|
title: input.title,
|
|
7821
|
-
|
|
7939
|
+
tickets: input.tickets,
|
|
7822
7940
|
baseBranch: input.baseBranch
|
|
7823
7941
|
}) : await createFeature(ctx, {
|
|
7824
7942
|
projectId: project.id,
|
|
@@ -7958,7 +8076,7 @@ function buildMcpServer() {
|
|
|
7958
8076
|
});
|
|
7959
8077
|
server.registerTool("add_test_note", {
|
|
7960
8078
|
title: "Add a review note",
|
|
7961
|
-
description: "Record one review finding on the feature you are reviewing. It lands in the same notes " + "the human writes while test-driving \u2014 attributed to you \u2014 so it shows up in the review " + "panel
|
|
8079
|
+
description: "Record one review finding on the feature you are reviewing. It lands in the same notes " + "the human writes while test-driving \u2014 attributed to you \u2014 so it shows up in the review " + "panel, where the human triages every open note at once into fix tickets or the next " + "lap\u2019s session. One note per finding, written " + "as an observation the human can reproduce (what you did, what happened, what you " + "expected), plus a closing note summarising the pass. Findings are not failure: report " + "what you saw and let the human decide.",
|
|
7962
8080
|
inputSchema: { text: z3.string().min(1) }
|
|
7963
8081
|
}, async (args, extra) => {
|
|
7964
8082
|
const rc = await resolveRunCaller(extra);
|
|
@@ -7976,14 +8094,14 @@ function buildMcpServer() {
|
|
|
7976
8094
|
});
|
|
7977
8095
|
server.registerTool("create_feature", {
|
|
7978
8096
|
title: "Create a feature",
|
|
7979
|
-
description: "Create a feature from the project session \u2014 the end of intake. Pass `brief` with the " + "reasoning you just worked out with the human (why this feature exists, what it must NOT " + "swallow): it becomes brief.md verbatim, and without it that reasoning is lost. Pass " + "`draft: true` to PARK it instead of starting it \u2014 a row and its brief, no branch and no " + "files, until the human clicks Start; ask them per feature whether to start it now or " + "park it. Pass " + "`
|
|
8097
|
+
description: "Create a feature from the project session \u2014 the end of intake. Pass `brief` with the " + "reasoning you just worked out with the human (why this feature exists, what it must NOT " + "swallow): it becomes brief.md verbatim, and without it that reasoning is lost. Pass " + "`draft: true` to PARK it instead of starting it \u2014 a row and its brief, no branch and no " + "files, until the human clicks Start; ask them per feature whether to start it now or " + "park it. Pass " + "`tickets: [prose, ...]` for a quick change \u2014 work too small to deserve a grill \u2014 which " + "creates ONE feature at the implementation phase carrying a ticket per prose (plus the " + "review ticket every batch closes with), and whose brief.md is those proses themselves " + "(so `brief` is unused there). Send every sentence of one quick change in a single call: " + "calling this " + "once per ticket would make one feature per ticket. From a feature session (ideation, " + "revisit, waypoint, converge) this tool parks drafts and nothing else: `draft: true` is " + "required and the `tickets` shape is refused, so deflect scope creep here and leave full " + "creation to the project session. This does NOT open " + "a terminal on what it creates; the new card in the rail is the feedback, and the human " + "decides what to work on next.",
|
|
7980
8098
|
inputSchema: {
|
|
7981
8099
|
title: z3.string().min(1),
|
|
7982
8100
|
oneLiner: z3.string(),
|
|
7983
8101
|
baseBranch: z3.string().optional(),
|
|
7984
8102
|
brief: z3.string().optional(),
|
|
7985
8103
|
draft: z3.boolean().optional(),
|
|
7986
|
-
|
|
8104
|
+
tickets: z3.array(z3.string()).min(1).optional()
|
|
7987
8105
|
}
|
|
7988
8106
|
}, async (args, extra) => {
|
|
7989
8107
|
const rs = await resolveCtxSession(extra);
|
|
@@ -9329,17 +9447,30 @@ async function launchProjectSession(ctx, input, opts = {}) {
|
|
|
9329
9447
|
kind: "project",
|
|
9330
9448
|
worktreePath
|
|
9331
9449
|
});
|
|
9332
|
-
const
|
|
9450
|
+
const resumeRowId = input.fresh ? undefined : input.resumeSessionId;
|
|
9451
|
+
const resumeSessionId = resumeRowId ? getSessionRow(ctx, resumeRowId)?.ccSessionId ?? undefined : undefined;
|
|
9333
9452
|
emitProject(ctx, project.id, {
|
|
9334
9453
|
type: "session.launching",
|
|
9335
|
-
message: "launching project
|
|
9336
|
-
data: {
|
|
9454
|
+
message: resumeSessionId ? "resuming a project conversation" : "launching a new project chat",
|
|
9455
|
+
data: {
|
|
9456
|
+
sessionId: session.id,
|
|
9457
|
+
kind: "project",
|
|
9458
|
+
worktreePath,
|
|
9459
|
+
branch: PROJECT_BRANCH,
|
|
9460
|
+
...resumeSessionId ? {} : { fresh: true }
|
|
9461
|
+
}
|
|
9337
9462
|
});
|
|
9338
9463
|
if (resumeSessionId) {
|
|
9339
9464
|
emitProject(ctx, project.id, {
|
|
9340
9465
|
type: "session.resumed",
|
|
9341
9466
|
message: "resuming the previous project conversation",
|
|
9342
|
-
data: { sessionId: session.id, resumeSessionId }
|
|
9467
|
+
data: { sessionId: session.id, resumeSessionId, resumedFrom: resumeRowId }
|
|
9468
|
+
});
|
|
9469
|
+
} else if (resumeRowId) {
|
|
9470
|
+
emitProject(ctx, project.id, {
|
|
9471
|
+
type: "session.resume_unavailable",
|
|
9472
|
+
message: "that conversation was never picked up by Claude Code \u2014 starting a new chat instead",
|
|
9473
|
+
data: { sessionId: session.id, resumedFrom: resumeRowId }
|
|
9343
9474
|
});
|
|
9344
9475
|
}
|
|
9345
9476
|
const artifacts = await writeSessionArtifacts({
|
|
@@ -9664,7 +9795,7 @@ var init_feature = __esm(() => {
|
|
|
9664
9795
|
quickChange: publicProcedure.input(z6.object({
|
|
9665
9796
|
projectId: z6.string(),
|
|
9666
9797
|
title: z6.string().min(1),
|
|
9667
|
-
|
|
9798
|
+
tickets: z6.array(z6.string()).min(1),
|
|
9668
9799
|
baseBranch: z6.string().optional()
|
|
9669
9800
|
})).mutation(({ ctx, input }) => quickChange(ctx, input)),
|
|
9670
9801
|
list: publicProcedure.input(z6.object({ projectId: z6.string() })).query(({ ctx, input }) => list(ctx, input.projectId)),
|
|
@@ -9864,6 +9995,98 @@ var init_fsbrowse = __esm(() => {
|
|
|
9864
9995
|
COMMON_DIRS = ["Projects", "projects", "code", "Code", "src", "dev", "repos", "git"];
|
|
9865
9996
|
});
|
|
9866
9997
|
|
|
9998
|
+
// src/services/transcripts.ts
|
|
9999
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
10000
|
+
function contentText(content) {
|
|
10001
|
+
if (typeof content === "string")
|
|
10002
|
+
return content.trim();
|
|
10003
|
+
if (!Array.isArray(content))
|
|
10004
|
+
return "";
|
|
10005
|
+
return content.map((block) => block && typeof block === "object" && block.type === "text" ? String(block.text ?? "") : "").join("").trim();
|
|
10006
|
+
}
|
|
10007
|
+
function parseTranscript(jsonl) {
|
|
10008
|
+
const turns = [];
|
|
10009
|
+
for (const line of jsonl.split(`
|
|
10010
|
+
`)) {
|
|
10011
|
+
const trimmed = line.trim();
|
|
10012
|
+
if (!trimmed)
|
|
10013
|
+
continue;
|
|
10014
|
+
let entry;
|
|
10015
|
+
try {
|
|
10016
|
+
entry = JSON.parse(trimmed);
|
|
10017
|
+
} catch {
|
|
10018
|
+
continue;
|
|
10019
|
+
}
|
|
10020
|
+
if (entry.type !== "user" && entry.type !== "assistant")
|
|
10021
|
+
continue;
|
|
10022
|
+
if (entry.isMeta)
|
|
10023
|
+
continue;
|
|
10024
|
+
const text2 = contentText(entry.message?.content);
|
|
10025
|
+
if (!text2)
|
|
10026
|
+
continue;
|
|
10027
|
+
turns.push({ role: entry.type, text: text2 });
|
|
10028
|
+
}
|
|
10029
|
+
return turns;
|
|
10030
|
+
}
|
|
10031
|
+
function readTranscript2(path) {
|
|
10032
|
+
if (!path)
|
|
10033
|
+
return [];
|
|
10034
|
+
try {
|
|
10035
|
+
return parseTranscript(readFileSync10(path, "utf8"));
|
|
10036
|
+
} catch {
|
|
10037
|
+
return [];
|
|
10038
|
+
}
|
|
10039
|
+
}
|
|
10040
|
+
var init_transcripts = () => {};
|
|
10041
|
+
|
|
10042
|
+
// src/services/conversations.ts
|
|
10043
|
+
function withoutKickoff(turns, kind) {
|
|
10044
|
+
const kickoff = kickoffLineFor(kind);
|
|
10045
|
+
return turns.filter((turn) => !(turn.role === "user" && promptMatchesKickoff(kickoff, turn.text)));
|
|
10046
|
+
}
|
|
10047
|
+
function deriveTitle(turns) {
|
|
10048
|
+
const first = withoutKickoff(turns, "project").find((turn) => turn.role === "user");
|
|
10049
|
+
return first ? elide(first.text) : null;
|
|
10050
|
+
}
|
|
10051
|
+
function elide(text2) {
|
|
10052
|
+
const line = text2.replace(/\s+/g, " ").trim();
|
|
10053
|
+
if (line.length <= TITLE_MAX2)
|
|
10054
|
+
return line;
|
|
10055
|
+
return `${line.slice(0, TITLE_MAX2).trimEnd()}\u2026`;
|
|
10056
|
+
}
|
|
10057
|
+
function untitled(createdAt) {
|
|
10058
|
+
return createdAt ? `Chat from ${new Date(createdAt).toISOString().slice(0, 10)}` : "Untitled chat";
|
|
10059
|
+
}
|
|
10060
|
+
function listProjectConversations(ctx, projectId) {
|
|
10061
|
+
return projectSessions(ctx, projectId, "project").map((session) => {
|
|
10062
|
+
const createdAt = session.createdAt ?? null;
|
|
10063
|
+
let title = session.title ?? null;
|
|
10064
|
+
if (!title) {
|
|
10065
|
+
title = deriveTitle(readTranscript2(session.transcriptPath));
|
|
10066
|
+
if (title)
|
|
10067
|
+
setSessionTitle(ctx, session.id, title);
|
|
10068
|
+
}
|
|
10069
|
+
return {
|
|
10070
|
+
id: session.id,
|
|
10071
|
+
title: title ?? untitled(createdAt),
|
|
10072
|
+
createdAt,
|
|
10073
|
+
status: session.status,
|
|
10074
|
+
resumable: !!session.ccSessionId
|
|
10075
|
+
};
|
|
10076
|
+
});
|
|
10077
|
+
}
|
|
10078
|
+
function conversationTranscript(ctx, sessionId) {
|
|
10079
|
+
const session = getSessionRow(ctx, sessionId);
|
|
10080
|
+
if (!session)
|
|
10081
|
+
return [];
|
|
10082
|
+
return withoutKickoff(readTranscript2(session.transcriptPath), session.kind);
|
|
10083
|
+
}
|
|
10084
|
+
var TITLE_MAX2 = 60;
|
|
10085
|
+
var init_conversations = __esm(() => {
|
|
10086
|
+
init_sessions();
|
|
10087
|
+
init_transcripts();
|
|
10088
|
+
});
|
|
10089
|
+
|
|
9867
10090
|
// src/services/projects.ts
|
|
9868
10091
|
import { existsSync as existsSync17 } from "fs";
|
|
9869
10092
|
import { basename as basename2, join as join18 } from "path";
|
|
@@ -9981,6 +10204,7 @@ var init_project = __esm(() => {
|
|
|
9981
10204
|
init_prep();
|
|
9982
10205
|
init_launcher();
|
|
9983
10206
|
init_sessions();
|
|
10207
|
+
init_conversations();
|
|
9984
10208
|
init_projects();
|
|
9985
10209
|
init_repo();
|
|
9986
10210
|
init_context();
|
|
@@ -9994,8 +10218,14 @@ var init_project = __esm(() => {
|
|
|
9994
10218
|
rename: publicProcedure.input(z7.object({ projectId: z7.string(), name: ProjectName })).mutation(({ ctx, input }) => renameProject(ctx, input.projectId, input.name)),
|
|
9995
10219
|
talkToPrep: publicProcedure.input(z7.object({ projectId: z7.string(), fresh: z7.boolean().optional() })).mutation(({ ctx, input }) => launchPrepareSession(ctx, { projectId: input.projectId, fresh: input.fresh })),
|
|
9996
10220
|
prepSession: publicProcedure.input(z7.object({ projectId: z7.string() })).query(({ ctx, input }) => activeProjectSession(ctx, input.projectId, "prepare")),
|
|
9997
|
-
talkToProject: publicProcedure.input(z7.object({
|
|
10221
|
+
talkToProject: publicProcedure.input(z7.object({
|
|
10222
|
+
projectId: z7.string(),
|
|
10223
|
+
fresh: z7.boolean().optional(),
|
|
10224
|
+
resumeSessionId: z7.string().optional()
|
|
10225
|
+
})).mutation(({ ctx, input }) => launchProjectSession(ctx, input)),
|
|
9998
10226
|
projectSession: publicProcedure.input(z7.object({ projectId: z7.string() })).query(({ ctx, input }) => activeProjectSession(ctx, input.projectId, "project")),
|
|
10227
|
+
conversations: publicProcedure.input(z7.object({ projectId: z7.string() })).query(({ ctx, input }) => listProjectConversations(ctx, input.projectId)),
|
|
10228
|
+
conversationTranscript: publicProcedure.input(z7.object({ sessionId: z7.string() })).query(({ ctx, input }) => conversationTranscript(ctx, input.sessionId)),
|
|
9999
10229
|
prep: publicProcedure.input(z7.object({ projectId: z7.string() })).query(({ ctx, input }) => prepView(ctx, requireProjectById(ctx, input.projectId))),
|
|
10000
10230
|
dryRunStop: publicProcedure.input(z7.object({ projectId: z7.string() })).mutation(({ ctx, input }) => dryRunDrive(ctx, requireProjectById(ctx, input.projectId), "stop"))
|
|
10001
10231
|
});
|
|
@@ -10021,7 +10251,7 @@ var init_run = __esm(() => {
|
|
|
10021
10251
|
});
|
|
10022
10252
|
|
|
10023
10253
|
// src/services/settings.ts
|
|
10024
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as
|
|
10254
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
10025
10255
|
import { dirname as dirname10 } from "path";
|
|
10026
10256
|
import * as z9 from "zod";
|
|
10027
10257
|
import { eq as eq14 } from "drizzle-orm";
|
|
@@ -10029,7 +10259,7 @@ function readRawConfig(configFile) {
|
|
|
10029
10259
|
if (!existsSync18(configFile))
|
|
10030
10260
|
return {};
|
|
10031
10261
|
try {
|
|
10032
|
-
const parsed = JSON.parse(
|
|
10262
|
+
const parsed = JSON.parse(readFileSync11(configFile, "utf8"));
|
|
10033
10263
|
if (typeof parsed !== "object" || parsed === null)
|
|
10034
10264
|
return {};
|
|
10035
10265
|
return foldLegacyModelConfig(parsed);
|
|
@@ -10550,13 +10780,13 @@ var init_doctor = __esm(() => {
|
|
|
10550
10780
|
});
|
|
10551
10781
|
|
|
10552
10782
|
// src/doctor/system-exec.ts
|
|
10553
|
-
import { spawn as
|
|
10783
|
+
import { spawn as spawn4 } from "child_process";
|
|
10554
10784
|
function createSystemExec(opts = {}) {
|
|
10555
10785
|
return (command, args) => new Promise((resolve11) => {
|
|
10556
10786
|
const { file, args: spawnArgs } = resolveSpawnTarget(command, args);
|
|
10557
10787
|
let stdout = "";
|
|
10558
10788
|
let stderr = "";
|
|
10559
|
-
const child =
|
|
10789
|
+
const child = spawn4(file, spawnArgs, { cwd: opts.cwd, windowsHide: true });
|
|
10560
10790
|
child.stdout?.on("data", (d) => {
|
|
10561
10791
|
stdout += d.toString();
|
|
10562
10792
|
});
|
|
@@ -10576,7 +10806,7 @@ var init_system_exec = __esm(() => {
|
|
|
10576
10806
|
});
|
|
10577
10807
|
|
|
10578
10808
|
// src/services/setup.ts
|
|
10579
|
-
import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as
|
|
10809
|
+
import { copyFileSync, existsSync as existsSync19, mkdirSync as mkdirSync12, readFileSync as readFileSync12, readdirSync as readdirSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
10580
10810
|
import { dirname as dirname11, join as join19 } from "path";
|
|
10581
10811
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
10582
10812
|
async function writeGitIdentity(exec, identity) {
|
|
@@ -10657,7 +10887,7 @@ function resolveSandcastleBin() {
|
|
|
10657
10887
|
for (let hops = 0;hops < 6; hops++) {
|
|
10658
10888
|
const manifestPath = join19(dir, "package.json");
|
|
10659
10889
|
if (existsSync19(manifestPath)) {
|
|
10660
|
-
const manifest = JSON.parse(
|
|
10890
|
+
const manifest = JSON.parse(readFileSync12(manifestPath, "utf8"));
|
|
10661
10891
|
if (manifest.name === "@ai-hero/sandcastle" && manifest.bin?.sandcastle) {
|
|
10662
10892
|
return join19(dir, manifest.bin.sandcastle);
|
|
10663
10893
|
}
|
|
@@ -10734,7 +10964,7 @@ function createTokenVerifier(exec) {
|
|
|
10734
10964
|
function fileAfkTokenIo(exec) {
|
|
10735
10965
|
const path = envPath();
|
|
10736
10966
|
return {
|
|
10737
|
-
read: () => existsSync19(path) ?
|
|
10967
|
+
read: () => existsSync19(path) ? readFileSync12(path, "utf8") : "",
|
|
10738
10968
|
write: (content) => {
|
|
10739
10969
|
mkdirSync12(dirname11(path), { recursive: true });
|
|
10740
10970
|
writeFileSync8(path, content, "utf8");
|
|
@@ -10814,7 +11044,8 @@ var init_test_notes2 = __esm(() => {
|
|
|
10814
11044
|
edit: publicProcedure.input(z12.object({ noteId: z12.string(), text: z12.string().min(1) })).mutation(({ ctx, input }) => editNote(ctx, input.noteId, input.text)),
|
|
10815
11045
|
remove: publicProcedure.input(z12.object({ noteId: z12.string() })).mutation(({ ctx, input }) => deleteNote(ctx, input.noteId)),
|
|
10816
11046
|
toggle: publicProcedure.input(z12.object({ noteId: z12.string() })).mutation(({ ctx, input }) => toggleNote(ctx, input.noteId)),
|
|
10817
|
-
promote: publicProcedure.input(z12.object({ noteId: z12.string() })).mutation(({ ctx, input }) => promoteNote(ctx, input.noteId))
|
|
11047
|
+
promote: publicProcedure.input(z12.object({ noteId: z12.string() })).mutation(({ ctx, input }) => promoteNote(ctx, input.noteId)),
|
|
11048
|
+
promoteMany: publicProcedure.input(z12.object({ noteIds: z12.array(z12.string()).min(1) })).mutation(({ ctx, input }) => promoteMany(ctx, input.noteIds))
|
|
10818
11049
|
});
|
|
10819
11050
|
});
|
|
10820
11051
|
|
|
@@ -11075,7 +11306,7 @@ __export(exports_cli, {
|
|
|
11075
11306
|
resolveDoctorEnv: () => resolveDoctorEnv,
|
|
11076
11307
|
parseMode: () => parseMode
|
|
11077
11308
|
});
|
|
11078
|
-
import { existsSync as existsSync20, readFileSync as
|
|
11309
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13 } from "fs";
|
|
11079
11310
|
function parseMode(argv) {
|
|
11080
11311
|
return argv.includes("--gate") || argv.includes("--boot") ? "gate" : "diagnostic";
|
|
11081
11312
|
}
|
|
@@ -11084,7 +11315,7 @@ function envWithToken() {
|
|
|
11084
11315
|
try {
|
|
11085
11316
|
const path = envPath();
|
|
11086
11317
|
if (existsSync20(path)) {
|
|
11087
|
-
const fromFile = parseEnvFile2(
|
|
11318
|
+
const fromFile = parseEnvFile2(readFileSync13(path, "utf8")).CLAUDE_CODE_OAUTH_TOKEN;
|
|
11088
11319
|
if (fromFile && fromFile.length > 0)
|
|
11089
11320
|
merged.CLAUDE_CODE_OAUTH_TOKEN = fromFile;
|
|
11090
11321
|
}
|
|
@@ -11106,10 +11337,10 @@ function resolveDoctorEnv() {
|
|
|
11106
11337
|
...imageName ? { imageName } : {}
|
|
11107
11338
|
};
|
|
11108
11339
|
}
|
|
11109
|
-
async function runCli(argv,
|
|
11340
|
+
async function runCli(argv, log2 = console.log) {
|
|
11110
11341
|
const mode = parseMode(argv);
|
|
11111
11342
|
const report = await runDoctor(resolveDoctorEnv());
|
|
11112
|
-
|
|
11343
|
+
log2(formatReport(report, mode));
|
|
11113
11344
|
return exitCodeFor(report, mode);
|
|
11114
11345
|
}
|
|
11115
11346
|
var init_cli = __esm(() => {
|