@dosu/cli 0.53.0-beta.1 → 0.53.0-beta.11
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/README.md +1 -1
- package/bin/dosu.js +2624 -678
- package/package.json +3 -3
package/bin/dosu.js
CHANGED
|
@@ -2529,8 +2529,20 @@ function getSupabaseURL() {
|
|
|
2529
2529
|
function getSupabaseAnonKey() {
|
|
2530
2530
|
return process.env.SUPABASE_ANON_KEY_OVERRIDE ?? "sb_publishable_NePQHMbqqlFOp1aaHbp5Bg_zyA-kYd6" ?? "";
|
|
2531
2531
|
}
|
|
2532
|
+
function isAbsoluteHttpUrl(value) {
|
|
2533
|
+
try {
|
|
2534
|
+
const parsed = new URL(value);
|
|
2535
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
2536
|
+
} catch {
|
|
2537
|
+
return false;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2532
2540
|
function getLlmGatewayURL() {
|
|
2533
|
-
|
|
2541
|
+
const override = process.env.DOSU_LLM_GATEWAY_URL_OVERRIDE;
|
|
2542
|
+
if (override)
|
|
2543
|
+
return override;
|
|
2544
|
+
const backend = getBackendURL().replace(/\/$/, "");
|
|
2545
|
+
return backend ? `${backend}/v1/llm-gateway` : "";
|
|
2534
2546
|
}
|
|
2535
2547
|
|
|
2536
2548
|
// src/client/client.ts
|
|
@@ -5006,7 +5018,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
|
|
|
5006
5018
|
function getVersionString() {
|
|
5007
5019
|
return `v${VERSION}`;
|
|
5008
5020
|
}
|
|
5009
|
-
var VERSION = "0.53.0-beta.
|
|
5021
|
+
var VERSION = "0.53.0-beta.11", INSTALL_CHANNEL = "npm";
|
|
5010
5022
|
|
|
5011
5023
|
// src/debug/logger.ts
|
|
5012
5024
|
import {
|
|
@@ -5144,7 +5156,7 @@ var init_logger = __esm(() => {
|
|
|
5144
5156
|
});
|
|
5145
5157
|
|
|
5146
5158
|
// src/client/contract.ts
|
|
5147
|
-
var CLI_CONTRACT_HASH = "
|
|
5159
|
+
var CLI_CONTRACT_HASH = "d7c6f0f0e8d0";
|
|
5148
5160
|
|
|
5149
5161
|
// src/client/trpc.ts
|
|
5150
5162
|
var exports_trpc = {};
|
|
@@ -5327,6 +5339,48 @@ function breadcrumb(segments, width = contentWidth()) {
|
|
|
5327
5339
|
function visibleWidth(text) {
|
|
5328
5340
|
return text.replace(ANSI_PATTERN2, "").length;
|
|
5329
5341
|
}
|
|
5342
|
+
function tabStrip(labels, active2, width, { spread = true } = {}) {
|
|
5343
|
+
if (!spread) {
|
|
5344
|
+
const cellW = Math.max(...labels.map(([, label]) => label.length)) + 2 * TAB_CELL_PAD;
|
|
5345
|
+
let row2 = "";
|
|
5346
|
+
let rule2 = "";
|
|
5347
|
+
for (const [id, label] of labels) {
|
|
5348
|
+
const left = Math.floor((cellW - label.length) / 2);
|
|
5349
|
+
const cell = " ".repeat(left) + label + " ".repeat(cellW - label.length - left);
|
|
5350
|
+
row2 += id === active2 ? brand(import_picocolors9.default.bold(cell)) : import_picocolors9.default.dim(cell);
|
|
5351
|
+
rule2 += id === active2 ? brand("━".repeat(cellW)) : import_picocolors9.default.dim("─".repeat(cellW));
|
|
5352
|
+
}
|
|
5353
|
+
return [row2, rule2];
|
|
5354
|
+
}
|
|
5355
|
+
const GAP_MIN = 3;
|
|
5356
|
+
const totalLen = labels.reduce((sum, [, label]) => sum + label.length, 0);
|
|
5357
|
+
const slots = Math.max(1, labels.length - 1);
|
|
5358
|
+
const spare = Math.max(GAP_MIN * slots, width - totalLen);
|
|
5359
|
+
const baseGap = Math.floor(spare / slots);
|
|
5360
|
+
const bonus = spare % slots;
|
|
5361
|
+
let row = "";
|
|
5362
|
+
let col = 0;
|
|
5363
|
+
let activeStart = 0;
|
|
5364
|
+
let activeLen = 0;
|
|
5365
|
+
labels.forEach(([id, label], i2) => {
|
|
5366
|
+
if (i2 > 0) {
|
|
5367
|
+
const gap = baseGap + (i2 <= bonus ? 1 : 0);
|
|
5368
|
+
row += " ".repeat(gap);
|
|
5369
|
+
col += gap;
|
|
5370
|
+
}
|
|
5371
|
+
if (id === active2) {
|
|
5372
|
+
activeStart = col;
|
|
5373
|
+
activeLen = label.length;
|
|
5374
|
+
row += brand(import_picocolors9.default.bold(label));
|
|
5375
|
+
} else {
|
|
5376
|
+
row += import_picocolors9.default.dim(label);
|
|
5377
|
+
}
|
|
5378
|
+
col += label.length;
|
|
5379
|
+
});
|
|
5380
|
+
const tail = Math.max(0, width - activeStart - activeLen);
|
|
5381
|
+
const rule = import_picocolors9.default.dim("─".repeat(activeStart)) + brand("━".repeat(activeLen)) + import_picocolors9.default.dim("─".repeat(tail));
|
|
5382
|
+
return [row, rule];
|
|
5383
|
+
}
|
|
5330
5384
|
function centerBlock(lines, width) {
|
|
5331
5385
|
const blockWidth = Math.max(...lines.map(visibleWidth));
|
|
5332
5386
|
const pad = " ".repeat(Math.max(0, Math.floor((width - blockWidth) / 2)));
|
|
@@ -5383,8 +5437,9 @@ function installCenteredLayout(stream = process.stdout) {
|
|
|
5383
5437
|
active = false;
|
|
5384
5438
|
};
|
|
5385
5439
|
}
|
|
5386
|
-
var import_picocolors9, ESC2, CONTENT_WIDTH = 64, TOKEN_PATTERN, COLUMN_RESET_FINALS, active = false, ANSI_PATTERN2;
|
|
5440
|
+
var import_picocolors9, ESC2, CONTENT_WIDTH = 64, TOKEN_PATTERN, COLUMN_RESET_FINALS, active = false, ANSI_PATTERN2, TAB_CELL_PAD = 2;
|
|
5387
5441
|
var init_layout = __esm(() => {
|
|
5442
|
+
init_styles();
|
|
5388
5443
|
import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
5389
5444
|
ESC2 = String.fromCharCode(27);
|
|
5390
5445
|
TOKEN_PATTERN = new RegExp(`(\r
|
|
@@ -8303,391 +8358,16 @@ var init_agents = __esm(() => {
|
|
|
8303
8358
|
init_formats();
|
|
8304
8359
|
});
|
|
8305
8360
|
|
|
8306
|
-
// src/sync/lock.ts
|
|
8307
|
-
import { existsSync as existsSync9, readFileSync as readFileSync9, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8308
|
-
import { join as join8 } from "node:path";
|
|
8309
|
-
function processAlive(pid) {
|
|
8310
|
-
try {
|
|
8311
|
-
process.kill(pid, 0);
|
|
8312
|
-
return true;
|
|
8313
|
-
} catch (err) {
|
|
8314
|
-
return err.code === "EPERM";
|
|
8315
|
-
}
|
|
8316
|
-
}
|
|
8317
|
-
function lockPath(configDir = getConfigDir()) {
|
|
8318
|
-
return join8(configDir, LOCK_FILENAME);
|
|
8319
|
-
}
|
|
8320
|
-
function fileLock(configDir = getConfigDir(), now = () => new Date) {
|
|
8321
|
-
const path3 = lockPath(configDir);
|
|
8322
|
-
let held = false;
|
|
8323
|
-
const tryCreate = () => {
|
|
8324
|
-
try {
|
|
8325
|
-
writeFileSync6(path3, String(process.pid), { flag: "wx" });
|
|
8326
|
-
return true;
|
|
8327
|
-
} catch {
|
|
8328
|
-
return false;
|
|
8329
|
-
}
|
|
8330
|
-
};
|
|
8331
|
-
return {
|
|
8332
|
-
acquire() {
|
|
8333
|
-
if (tryCreate()) {
|
|
8334
|
-
held = true;
|
|
8335
|
-
return true;
|
|
8336
|
-
}
|
|
8337
|
-
try {
|
|
8338
|
-
const age = now().getTime() - statSync3(path3).mtimeMs;
|
|
8339
|
-
const holder = Number.parseInt(readFileSync9(path3, "utf8"), 10);
|
|
8340
|
-
const holderDead = Number.isInteger(holder) && holder > 0 && !processAlive(holder);
|
|
8341
|
-
if (age > STALE_LOCK_MS || holderDead) {
|
|
8342
|
-
rmSync2(path3, { force: true });
|
|
8343
|
-
held = tryCreate();
|
|
8344
|
-
return held;
|
|
8345
|
-
}
|
|
8346
|
-
} catch {}
|
|
8347
|
-
return false;
|
|
8348
|
-
},
|
|
8349
|
-
release() {
|
|
8350
|
-
if (!held)
|
|
8351
|
-
return;
|
|
8352
|
-
held = false;
|
|
8353
|
-
try {
|
|
8354
|
-
if (existsSync9(path3) && readFileSync9(path3, "utf8") === String(process.pid)) {
|
|
8355
|
-
rmSync2(path3, { force: true });
|
|
8356
|
-
}
|
|
8357
|
-
} catch {}
|
|
8358
|
-
}
|
|
8359
|
-
};
|
|
8360
|
-
}
|
|
8361
|
-
var LOCK_FILENAME = "knowledge-sync.lock", STALE_LOCK_MS;
|
|
8362
|
-
var init_lock = __esm(() => {
|
|
8363
|
-
init_config();
|
|
8364
|
-
STALE_LOCK_MS = 15 * 60 * 1000;
|
|
8365
|
-
});
|
|
8366
|
-
|
|
8367
|
-
// src/sync/watermark.ts
|
|
8368
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8369
|
-
import { join as join9 } from "node:path";
|
|
8370
|
-
function syncStatePath(configDir = getConfigDir()) {
|
|
8371
|
-
return join9(configDir, STATE_FILENAME);
|
|
8372
|
-
}
|
|
8373
|
-
function loadSyncState(configDir = getConfigDir()) {
|
|
8374
|
-
const empty = {
|
|
8375
|
-
schema_version: STATE_SCHEMA_VERSION,
|
|
8376
|
-
watermark: null,
|
|
8377
|
-
consecutive_failures: 0
|
|
8378
|
-
};
|
|
8379
|
-
const path3 = syncStatePath(configDir);
|
|
8380
|
-
if (!existsSync10(path3))
|
|
8381
|
-
return empty;
|
|
8382
|
-
try {
|
|
8383
|
-
const raw = JSON.parse(readFileSync10(path3, "utf-8"));
|
|
8384
|
-
if (raw.schema_version !== STATE_SCHEMA_VERSION)
|
|
8385
|
-
return empty;
|
|
8386
|
-
const minedSessions = Array.isArray(raw.mined_sessions) ? raw.mined_sessions.filter((record) => typeof record === "object" && record !== null && typeof record.at === "string" && typeof record.session === "string").map((record) => ({
|
|
8387
|
-
at: record.at,
|
|
8388
|
-
session: record.session,
|
|
8389
|
-
...typeof record.project === "string" ? { project: record.project } : {}
|
|
8390
|
-
})) : [];
|
|
8391
|
-
const rawRefusal = raw.last_refusal;
|
|
8392
|
-
const lastRefusal = rawRefusal && typeof rawRefusal.at === "string" && typeof rawRefusal.outcome === "string" && typeof rawRefusal.message === "string" ? { at: rawRefusal.at, outcome: rawRefusal.outcome, message: rawRefusal.message } : undefined;
|
|
8393
|
-
return {
|
|
8394
|
-
schema_version: STATE_SCHEMA_VERSION,
|
|
8395
|
-
watermark: typeof raw.watermark === "string" ? raw.watermark : null,
|
|
8396
|
-
last_attempt_at: typeof raw.last_attempt_at === "string" ? raw.last_attempt_at : undefined,
|
|
8397
|
-
consecutive_failures: typeof raw.consecutive_failures === "number" && raw.consecutive_failures >= 0 ? raw.consecutive_failures : 0,
|
|
8398
|
-
mined_sessions: minedSessions,
|
|
8399
|
-
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined : minedSessions.length,
|
|
8400
|
-
total_notes: typeof raw.total_notes === "number" && raw.total_notes >= 0 ? raw.total_notes : 0,
|
|
8401
|
-
total_learning_tokens: typeof raw.total_learning_tokens === "number" && raw.total_learning_tokens >= 0 ? raw.total_learning_tokens : 0,
|
|
8402
|
-
...lastRefusal ? { last_refusal: lastRefusal } : {},
|
|
8403
|
-
...Array.isArray(raw.project_filter) ? {
|
|
8404
|
-
project_filter: raw.project_filter.filter((p2) => typeof p2 === "string")
|
|
8405
|
-
} : {}
|
|
8406
|
-
};
|
|
8407
|
-
} catch {
|
|
8408
|
-
return empty;
|
|
8409
|
-
}
|
|
8410
|
-
}
|
|
8411
|
-
function saveSyncState(state, configDir = getConfigDir()) {
|
|
8412
|
-
if (!existsSync10(configDir))
|
|
8413
|
-
mkdirSync6(configDir, { recursive: true, mode: 448 });
|
|
8414
|
-
const path3 = syncStatePath(configDir);
|
|
8415
|
-
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8416
|
-
writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
8417
|
-
renameSync2(tmp, path3);
|
|
8418
|
-
}
|
|
8419
|
-
function backoffUntil(state) {
|
|
8420
|
-
if (state.consecutive_failures === 0 || !state.last_attempt_at)
|
|
8421
|
-
return null;
|
|
8422
|
-
const last = Date.parse(state.last_attempt_at);
|
|
8423
|
-
if (Number.isNaN(last))
|
|
8424
|
-
return null;
|
|
8425
|
-
const delay = Math.min(BACKOFF_BASE_MS * 2 ** (state.consecutive_failures - 1), BACKOFF_MAX_MS);
|
|
8426
|
-
return new Date(last + delay);
|
|
8427
|
-
}
|
|
8428
|
-
function isUnderDir(dir, base) {
|
|
8429
|
-
const root = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
8430
|
-
return dir === root || dir.startsWith(`${root}/`);
|
|
8431
|
-
}
|
|
8432
|
-
function filterSessionsByProject(sessions, filter, resolveDir) {
|
|
8433
|
-
if (!filter || filter.length === 0)
|
|
8434
|
-
return [...sessions];
|
|
8435
|
-
const includeUnknown = filter.includes(UNKNOWN_PROJECT);
|
|
8436
|
-
const dirs = filter.filter((entry) => entry !== UNKNOWN_PROJECT);
|
|
8437
|
-
return sessions.filter((session) => {
|
|
8438
|
-
const dir = resolveDir(session);
|
|
8439
|
-
if (!dir)
|
|
8440
|
-
return includeUnknown;
|
|
8441
|
-
return dirs.some((base) => isUnderDir(dir, base));
|
|
8442
|
-
});
|
|
8443
|
-
}
|
|
8444
|
-
function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAULT_QUIET_PERIOD_MS) {
|
|
8445
|
-
const watermarkMs = watermark ? Date.parse(watermark) : Number.NEGATIVE_INFINITY;
|
|
8446
|
-
const completedBefore = now.getTime() - quietPeriodMs;
|
|
8447
|
-
const ready = [];
|
|
8448
|
-
const open2 = [];
|
|
8449
|
-
for (const session of sessions) {
|
|
8450
|
-
const updated = Date.parse(session.updated);
|
|
8451
|
-
if (Number.isNaN(updated) || updated <= watermarkMs)
|
|
8452
|
-
continue;
|
|
8453
|
-
if (updated > completedBefore) {
|
|
8454
|
-
open2.push(session);
|
|
8455
|
-
} else {
|
|
8456
|
-
ready.push(session);
|
|
8457
|
-
}
|
|
8458
|
-
}
|
|
8459
|
-
return { ready, open: open2 };
|
|
8460
|
-
}
|
|
8461
|
-
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, MINED_HISTORY_LIMIT = 500, UNKNOWN_PROJECT = "(unknown)";
|
|
8462
|
-
var init_watermark = __esm(() => {
|
|
8463
|
-
init_config();
|
|
8464
|
-
DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
|
|
8465
|
-
BACKOFF_BASE_MS = 15 * 60 * 1000;
|
|
8466
|
-
BACKOFF_MAX_MS = 24 * 60 * 60 * 1000;
|
|
8467
|
-
});
|
|
8468
|
-
|
|
8469
|
-
// src/sync/status.ts
|
|
8470
|
-
import { readFileSync as readFileSync11, statSync as statSync4 } from "node:fs";
|
|
8471
|
-
function formatTokenCount(tokens) {
|
|
8472
|
-
if (tokens < 1000)
|
|
8473
|
-
return String(tokens);
|
|
8474
|
-
if (tokens < 1e6) {
|
|
8475
|
-
const k = tokens / 1000;
|
|
8476
|
-
return `~${k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, "")}k`;
|
|
8477
|
-
}
|
|
8478
|
-
const m3 = tokens / 1e6;
|
|
8479
|
-
return `~${m3 >= 100 ? Math.round(m3) : m3.toFixed(1).replace(/\.0$/, "")}M`;
|
|
8480
|
-
}
|
|
8481
|
-
function defaultIsProcessAlive2(pid) {
|
|
8482
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
8483
|
-
return false;
|
|
8484
|
-
try {
|
|
8485
|
-
process.kill(pid, 0);
|
|
8486
|
-
return true;
|
|
8487
|
-
} catch (err) {
|
|
8488
|
-
return err.code === "EPERM";
|
|
8489
|
-
}
|
|
8490
|
-
}
|
|
8491
|
-
function defaultReadLog() {
|
|
8492
|
-
try {
|
|
8493
|
-
return readFileSync11(logger.getLogPath(), "utf-8");
|
|
8494
|
-
} catch {
|
|
8495
|
-
return "";
|
|
8496
|
-
}
|
|
8497
|
-
}
|
|
8498
|
-
function readLockFile(configDir) {
|
|
8499
|
-
const path3 = lockPath(configDir);
|
|
8500
|
-
try {
|
|
8501
|
-
const pid = Number.parseInt(readFileSync11(path3, "utf-8"), 10);
|
|
8502
|
-
return { pid: Number.isNaN(pid) ? -1 : pid, mtime: statSync4(path3).mtime };
|
|
8503
|
-
} catch {
|
|
8504
|
-
return null;
|
|
8505
|
-
}
|
|
8506
|
-
}
|
|
8507
|
-
function recentSyncActivity(log3) {
|
|
8508
|
-
return log3.split(`
|
|
8509
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[miner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
8510
|
-
}
|
|
8511
|
-
function getSyncStatus(deps = {}) {
|
|
8512
|
-
const configDir = deps.configDir ?? getConfigDir();
|
|
8513
|
-
const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive2;
|
|
8514
|
-
const readLog = deps.readLog ?? defaultReadLog;
|
|
8515
|
-
const lock = readLockFile(configDir);
|
|
8516
|
-
const running = lock !== null && isProcessAlive(lock.pid);
|
|
8517
|
-
const state = loadSyncState(configDir);
|
|
8518
|
-
const retryAt = backoffUntil(state);
|
|
8519
|
-
return {
|
|
8520
|
-
running,
|
|
8521
|
-
...lock ? { pid: lock.pid, startedAt: lock.mtime.toISOString() } : {},
|
|
8522
|
-
...lock && !running ? { staleLock: true } : {},
|
|
8523
|
-
state,
|
|
8524
|
-
...retryAt ? { backoffUntil: retryAt.toISOString() } : {},
|
|
8525
|
-
recentActivity: recentSyncActivity(readLog())
|
|
8526
|
-
};
|
|
8527
|
-
}
|
|
8528
|
-
var ACTIVITY_LINES = 10;
|
|
8529
|
-
var init_status = __esm(() => {
|
|
8530
|
-
init_config();
|
|
8531
|
-
init_logger();
|
|
8532
|
-
init_lock();
|
|
8533
|
-
init_watermark();
|
|
8534
|
-
});
|
|
8535
|
-
|
|
8536
|
-
// src/sessions/project-dir.ts
|
|
8537
|
-
import {
|
|
8538
|
-
closeSync as closeSync2,
|
|
8539
|
-
existsSync as existsSync11,
|
|
8540
|
-
mkdirSync as mkdirSync7,
|
|
8541
|
-
openSync as openSync2,
|
|
8542
|
-
readFileSync as readFileSync12,
|
|
8543
|
-
readSync,
|
|
8544
|
-
renameSync as renameSync3,
|
|
8545
|
-
statSync as statSync5,
|
|
8546
|
-
writeFileSync as writeFileSync8
|
|
8547
|
-
} from "node:fs";
|
|
8548
|
-
import { join as join10 } from "node:path";
|
|
8549
|
-
function cachePath(configDir) {
|
|
8550
|
-
return join10(configDir, CACHE_FILENAME2);
|
|
8551
|
-
}
|
|
8552
|
-
function loadCacheFile(configDir) {
|
|
8553
|
-
try {
|
|
8554
|
-
const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
|
|
8555
|
-
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
8556
|
-
return {};
|
|
8557
|
-
return raw.entries;
|
|
8558
|
-
} catch {
|
|
8559
|
-
return {};
|
|
8560
|
-
}
|
|
8561
|
-
}
|
|
8562
|
-
function saveCacheFile(configDir, entries) {
|
|
8563
|
-
try {
|
|
8564
|
-
if (!existsSync11(configDir))
|
|
8565
|
-
mkdirSync7(configDir, { recursive: true, mode: 448 });
|
|
8566
|
-
const path3 = cachePath(configDir);
|
|
8567
|
-
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8568
|
-
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
8569
|
-
writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
|
|
8570
|
-
renameSync3(tmp, path3);
|
|
8571
|
-
} catch {}
|
|
8572
|
-
}
|
|
8573
|
-
function readHead(path3, bytes = HEAD_BYTES) {
|
|
8574
|
-
let fd;
|
|
8575
|
-
try {
|
|
8576
|
-
fd = openSync2(path3, "r");
|
|
8577
|
-
} catch {
|
|
8578
|
-
return null;
|
|
8579
|
-
}
|
|
8580
|
-
try {
|
|
8581
|
-
const buf = Buffer.alloc(bytes);
|
|
8582
|
-
const read = readSync(fd, buf, 0, bytes, 0);
|
|
8583
|
-
return buf.subarray(0, read).toString("utf-8");
|
|
8584
|
-
} catch {
|
|
8585
|
-
return null;
|
|
8586
|
-
} finally {
|
|
8587
|
-
closeSync2(fd);
|
|
8588
|
-
}
|
|
8589
|
-
}
|
|
8590
|
-
function cwdFromJsonlHead(text) {
|
|
8591
|
-
for (const line of text.split(`
|
|
8592
|
-
`).slice(0, HEAD_LINES)) {
|
|
8593
|
-
if (!line.includes('"cwd"'))
|
|
8594
|
-
continue;
|
|
8595
|
-
try {
|
|
8596
|
-
const obj = JSON.parse(line);
|
|
8597
|
-
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
8598
|
-
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
8599
|
-
return cwd;
|
|
8600
|
-
} catch {}
|
|
8601
|
-
}
|
|
8602
|
-
return null;
|
|
8603
|
-
}
|
|
8604
|
-
function unmungeSlug(slug, exists = existsSync11) {
|
|
8605
|
-
const tokens = slug.replace(/^-/, "").split("-");
|
|
8606
|
-
if (tokens.length === 0 || tokens[0] === "")
|
|
8607
|
-
return null;
|
|
8608
|
-
let budget = 5000;
|
|
8609
|
-
const walk = (prefix, index) => {
|
|
8610
|
-
if (budget-- <= 0)
|
|
8611
|
-
return null;
|
|
8612
|
-
if (index === tokens.length)
|
|
8613
|
-
return exists(prefix) ? prefix : null;
|
|
8614
|
-
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
8615
|
-
if (asSegment)
|
|
8616
|
-
return asSegment;
|
|
8617
|
-
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
8618
|
-
};
|
|
8619
|
-
return walk("", 0);
|
|
8620
|
-
}
|
|
8621
|
-
function fileMtime(path3) {
|
|
8622
|
-
try {
|
|
8623
|
-
return statSync5(path3).mtime.toISOString();
|
|
8624
|
-
} catch {
|
|
8625
|
-
return "";
|
|
8626
|
-
}
|
|
8627
|
-
}
|
|
8628
|
-
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
8629
|
-
const exists = deps.exists ?? existsSync11;
|
|
8630
|
-
const head = deps.readHead ?? readHead;
|
|
8631
|
-
const mtime = deps.mtime ?? fileMtime;
|
|
8632
|
-
const entries = loadCacheFile(configDir);
|
|
8633
|
-
let dirty = false;
|
|
8634
|
-
const compute = (session) => {
|
|
8635
|
-
switch (session.harness) {
|
|
8636
|
-
case "opencode":
|
|
8637
|
-
return session.project ?? null;
|
|
8638
|
-
case "claude": {
|
|
8639
|
-
const text = head(session.path);
|
|
8640
|
-
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
8641
|
-
if (cwd)
|
|
8642
|
-
return cwd;
|
|
8643
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8644
|
-
}
|
|
8645
|
-
case "codex": {
|
|
8646
|
-
const text = head(session.path);
|
|
8647
|
-
return text ? cwdFromJsonlHead(text) : null;
|
|
8648
|
-
}
|
|
8649
|
-
case "cursor":
|
|
8650
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8651
|
-
default:
|
|
8652
|
-
return null;
|
|
8653
|
-
}
|
|
8654
|
-
};
|
|
8655
|
-
return {
|
|
8656
|
-
resolve(session) {
|
|
8657
|
-
const key = `${session.harness}/${session.id}`;
|
|
8658
|
-
const cached = entries[key];
|
|
8659
|
-
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
8660
|
-
return cached.dir;
|
|
8661
|
-
}
|
|
8662
|
-
const dir = compute(session);
|
|
8663
|
-
entries[key] = { dir, mtime: mtime(session.path) };
|
|
8664
|
-
dirty = true;
|
|
8665
|
-
return dir;
|
|
8666
|
-
},
|
|
8667
|
-
flush() {
|
|
8668
|
-
if (!dirty)
|
|
8669
|
-
return;
|
|
8670
|
-
saveCacheFile(configDir, entries);
|
|
8671
|
-
dirty = false;
|
|
8672
|
-
}
|
|
8673
|
-
};
|
|
8674
|
-
}
|
|
8675
|
-
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
8676
|
-
var init_project_dir = __esm(() => {
|
|
8677
|
-
init_config();
|
|
8678
|
-
HEAD_BYTES = 128 * 1024;
|
|
8679
|
-
});
|
|
8680
|
-
|
|
8681
8361
|
// src/sessions/scan.ts
|
|
8682
|
-
import { existsSync as
|
|
8362
|
+
import { existsSync as existsSync9, readdirSync, statSync as statSync3 } from "node:fs";
|
|
8683
8363
|
import { createRequire as createRequire2 } from "node:module";
|
|
8684
8364
|
import { homedir as homedir3 } from "node:os";
|
|
8685
|
-
import { basename, join as
|
|
8365
|
+
import { basename, join as join8 } from "node:path";
|
|
8686
8366
|
function listDir(dir) {
|
|
8687
8367
|
try {
|
|
8688
8368
|
return readdirSync(dir, { withFileTypes: true }).map((entry) => ({
|
|
8689
8369
|
name: entry.name,
|
|
8690
|
-
path:
|
|
8370
|
+
path: join8(dir, entry.name),
|
|
8691
8371
|
isDir: entry.isDirectory()
|
|
8692
8372
|
}));
|
|
8693
8373
|
} catch {
|
|
@@ -8700,7 +8380,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8700
8380
|
return null;
|
|
8701
8381
|
let mtime;
|
|
8702
8382
|
try {
|
|
8703
|
-
mtime =
|
|
8383
|
+
mtime = statSync3(filePath).mtime;
|
|
8704
8384
|
} catch {
|
|
8705
8385
|
return null;
|
|
8706
8386
|
}
|
|
@@ -8714,7 +8394,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8714
8394
|
}
|
|
8715
8395
|
function scanClaude(home) {
|
|
8716
8396
|
const sessions = [];
|
|
8717
|
-
for (const project of listDir(
|
|
8397
|
+
for (const project of listDir(join8(home, ".claude", "projects"))) {
|
|
8718
8398
|
if (!project.isDir)
|
|
8719
8399
|
continue;
|
|
8720
8400
|
for (const entry of listDir(project.path)) {
|
|
@@ -8729,10 +8409,10 @@ function scanClaude(home) {
|
|
|
8729
8409
|
}
|
|
8730
8410
|
function scanCursor(home) {
|
|
8731
8411
|
const sessions = [];
|
|
8732
|
-
for (const project of listDir(
|
|
8412
|
+
for (const project of listDir(join8(home, ".cursor", "projects"))) {
|
|
8733
8413
|
if (!project.isDir)
|
|
8734
8414
|
continue;
|
|
8735
|
-
for (const entry of listDir(
|
|
8415
|
+
for (const entry of listDir(join8(project.path, "agent-transcripts"))) {
|
|
8736
8416
|
const files = entry.isDir ? listDir(entry.path).filter((f2) => !f2.isDir) : [entry];
|
|
8737
8417
|
for (const file of files) {
|
|
8738
8418
|
const session = sessionFromFile(file.path, "cursor", project.name);
|
|
@@ -8744,9 +8424,9 @@ function scanCursor(home) {
|
|
|
8744
8424
|
return sessions;
|
|
8745
8425
|
}
|
|
8746
8426
|
function scanCodex(home, env) {
|
|
8747
|
-
const codexHome2 = env.CODEX_HOME ??
|
|
8427
|
+
const codexHome2 = env.CODEX_HOME ?? join8(home, ".codex");
|
|
8748
8428
|
const sessions = [];
|
|
8749
|
-
for (const year of listDir(
|
|
8429
|
+
for (const year of listDir(join8(codexHome2, "sessions"))) {
|
|
8750
8430
|
if (!year.isDir)
|
|
8751
8431
|
continue;
|
|
8752
8432
|
for (const month of listDir(year.path)) {
|
|
@@ -8802,9 +8482,9 @@ function querySqlite(dbPath, sql) {
|
|
|
8802
8482
|
}
|
|
8803
8483
|
}
|
|
8804
8484
|
function scanOpencode(home, env) {
|
|
8805
|
-
const dataDir = env.XDG_DATA_HOME ??
|
|
8806
|
-
const dbPath =
|
|
8807
|
-
if (!
|
|
8485
|
+
const dataDir = env.XDG_DATA_HOME ?? join8(home, ".local", "share");
|
|
8486
|
+
const dbPath = join8(dataDir, "opencode", "opencode.db");
|
|
8487
|
+
if (!existsSync9(dbPath))
|
|
8808
8488
|
return [];
|
|
8809
8489
|
const rows = querySqlite(dbPath, "SELECT id, directory, time_updated FROM session WHERE parent_id IS NULL");
|
|
8810
8490
|
if (!rows)
|
|
@@ -8841,8 +8521,863 @@ function scanAgentSessions(options = {}) {
|
|
|
8841
8521
|
}
|
|
8842
8522
|
var init_scan = () => {};
|
|
8843
8523
|
|
|
8524
|
+
// src/sync/watermark.ts
|
|
8525
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8526
|
+
import { join as join9 } from "node:path";
|
|
8527
|
+
function syncStatePath(configDir = getConfigDir()) {
|
|
8528
|
+
return join9(configDir, STATE_FILENAME);
|
|
8529
|
+
}
|
|
8530
|
+
function loadSyncState(configDir = getConfigDir()) {
|
|
8531
|
+
const empty = {
|
|
8532
|
+
schema_version: STATE_SCHEMA_VERSION,
|
|
8533
|
+
watermark: null,
|
|
8534
|
+
consecutive_failures: 0
|
|
8535
|
+
};
|
|
8536
|
+
const path3 = syncStatePath(configDir);
|
|
8537
|
+
if (!existsSync10(path3))
|
|
8538
|
+
return empty;
|
|
8539
|
+
try {
|
|
8540
|
+
const raw = JSON.parse(readFileSync9(path3, "utf-8"));
|
|
8541
|
+
if (raw.schema_version !== STATE_SCHEMA_VERSION)
|
|
8542
|
+
return empty;
|
|
8543
|
+
const minedSessions = Array.isArray(raw.mined_sessions) ? raw.mined_sessions.filter((record) => typeof record === "object" && record !== null && typeof record.at === "string" && typeof record.session === "string").map((record) => ({
|
|
8544
|
+
at: record.at,
|
|
8545
|
+
session: record.session,
|
|
8546
|
+
...typeof record.project === "string" ? { project: record.project } : {}
|
|
8547
|
+
})) : [];
|
|
8548
|
+
const rawRefusal = raw.last_refusal;
|
|
8549
|
+
const lastRefusal = rawRefusal && typeof rawRefusal.at === "string" && typeof rawRefusal.outcome === "string" && typeof rawRefusal.message === "string" ? { at: rawRefusal.at, outcome: rawRefusal.outcome, message: rawRefusal.message } : undefined;
|
|
8550
|
+
const rawRun = raw.run;
|
|
8551
|
+
const run2 = rawRun && typeof rawRun.pid === "number" && typeof rawRun.started_at === "string" && typeof rawRun.baseline_mined === "number" && rawRun.baseline_mined >= 0 ? { pid: rawRun.pid, started_at: rawRun.started_at, baseline_mined: rawRun.baseline_mined } : undefined;
|
|
8552
|
+
return {
|
|
8553
|
+
schema_version: STATE_SCHEMA_VERSION,
|
|
8554
|
+
watermark: typeof raw.watermark === "string" ? raw.watermark : null,
|
|
8555
|
+
last_attempt_at: typeof raw.last_attempt_at === "string" ? raw.last_attempt_at : undefined,
|
|
8556
|
+
consecutive_failures: typeof raw.consecutive_failures === "number" && raw.consecutive_failures >= 0 ? raw.consecutive_failures : 0,
|
|
8557
|
+
mined_sessions: minedSessions,
|
|
8558
|
+
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined : minedSessions.length,
|
|
8559
|
+
total_notes: typeof raw.total_notes === "number" && raw.total_notes >= 0 ? raw.total_notes : 0,
|
|
8560
|
+
total_learning_tokens: typeof raw.total_learning_tokens === "number" && raw.total_learning_tokens >= 0 ? raw.total_learning_tokens : 0,
|
|
8561
|
+
...lastRefusal ? { last_refusal: lastRefusal } : {},
|
|
8562
|
+
...run2 ? { run: run2 } : {},
|
|
8563
|
+
...raw.paused === true ? { paused: true } : {},
|
|
8564
|
+
...Array.isArray(raw.project_filter) ? {
|
|
8565
|
+
project_filter: raw.project_filter.filter((p2) => typeof p2 === "string")
|
|
8566
|
+
} : {}
|
|
8567
|
+
};
|
|
8568
|
+
} catch {
|
|
8569
|
+
return empty;
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8572
|
+
function setSyncPaused(paused, configDir = getConfigDir()) {
|
|
8573
|
+
const state = loadSyncState(configDir);
|
|
8574
|
+
if (paused)
|
|
8575
|
+
state.paused = true;
|
|
8576
|
+
else
|
|
8577
|
+
delete state.paused;
|
|
8578
|
+
saveSyncState(state, configDir);
|
|
8579
|
+
}
|
|
8580
|
+
function saveSyncState(state, configDir = getConfigDir()) {
|
|
8581
|
+
if (!existsSync10(configDir))
|
|
8582
|
+
mkdirSync6(configDir, { recursive: true, mode: 448 });
|
|
8583
|
+
const path3 = syncStatePath(configDir);
|
|
8584
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8585
|
+
writeFileSync6(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
8586
|
+
renameSync2(tmp, path3);
|
|
8587
|
+
}
|
|
8588
|
+
function backoffUntil(state) {
|
|
8589
|
+
if (state.consecutive_failures === 0 || !state.last_attempt_at)
|
|
8590
|
+
return null;
|
|
8591
|
+
const last = Date.parse(state.last_attempt_at);
|
|
8592
|
+
if (Number.isNaN(last))
|
|
8593
|
+
return null;
|
|
8594
|
+
const delay = Math.min(BACKOFF_BASE_MS * 2 ** (state.consecutive_failures - 1), BACKOFF_MAX_MS);
|
|
8595
|
+
return new Date(last + delay);
|
|
8596
|
+
}
|
|
8597
|
+
function isUnderDir(dir, base) {
|
|
8598
|
+
const root = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
8599
|
+
return dir === root || dir.startsWith(`${root}/`);
|
|
8600
|
+
}
|
|
8601
|
+
function filterSessionsByProject(sessions, filter, resolveDir) {
|
|
8602
|
+
if (!filter || filter.length === 0)
|
|
8603
|
+
return [...sessions];
|
|
8604
|
+
const includeUnknown = filter.includes(UNKNOWN_PROJECT);
|
|
8605
|
+
const dirs = filter.filter((entry) => entry !== UNKNOWN_PROJECT);
|
|
8606
|
+
return sessions.filter((session) => {
|
|
8607
|
+
const dir = resolveDir(session);
|
|
8608
|
+
if (!dir)
|
|
8609
|
+
return includeUnknown;
|
|
8610
|
+
return dirs.some((base) => isUnderDir(dir, base));
|
|
8611
|
+
});
|
|
8612
|
+
}
|
|
8613
|
+
function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAULT_QUIET_PERIOD_MS) {
|
|
8614
|
+
const watermarkMs = watermark ? Date.parse(watermark) : Number.NEGATIVE_INFINITY;
|
|
8615
|
+
const completedBefore = now.getTime() - quietPeriodMs;
|
|
8616
|
+
const ready = [];
|
|
8617
|
+
const open2 = [];
|
|
8618
|
+
for (const session of sessions) {
|
|
8619
|
+
const updated = Date.parse(session.updated);
|
|
8620
|
+
if (Number.isNaN(updated) || updated <= watermarkMs)
|
|
8621
|
+
continue;
|
|
8622
|
+
if (updated > completedBefore) {
|
|
8623
|
+
open2.push(session);
|
|
8624
|
+
} else {
|
|
8625
|
+
ready.push(session);
|
|
8626
|
+
}
|
|
8627
|
+
}
|
|
8628
|
+
return { ready, open: open2 };
|
|
8629
|
+
}
|
|
8630
|
+
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, MINED_HISTORY_LIMIT = 500, UNKNOWN_PROJECT = "(unknown)";
|
|
8631
|
+
var init_watermark = __esm(() => {
|
|
8632
|
+
init_config();
|
|
8633
|
+
DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
|
|
8634
|
+
BACKOFF_BASE_MS = 15 * 60 * 1000;
|
|
8635
|
+
BACKOFF_MAX_MS = 24 * 60 * 60 * 1000;
|
|
8636
|
+
});
|
|
8637
|
+
|
|
8638
|
+
// src/report/fetch.ts
|
|
8639
|
+
async function fetchReportNotes(cfg) {
|
|
8640
|
+
const orgId = cfg.active_account?.target?.org_id;
|
|
8641
|
+
if (!orgId || !cfg.active_account?.session.access_token) {
|
|
8642
|
+
throw new Error("Not signed in. Run `dosu setup` to connect an account first.");
|
|
8643
|
+
}
|
|
8644
|
+
const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
|
|
8645
|
+
const client = createTypedClient2(cfg);
|
|
8646
|
+
const { notes } = await client.notes.listMine.query({
|
|
8647
|
+
org_id: orgId,
|
|
8648
|
+
limit: REPORT_NOTES_LIMIT
|
|
8649
|
+
});
|
|
8650
|
+
return notes.map((note) => ({
|
|
8651
|
+
title: note.title,
|
|
8652
|
+
content: note.body,
|
|
8653
|
+
at: note.created_at,
|
|
8654
|
+
...note.transcript_id ? { transcript_id: note.transcript_id } : {},
|
|
8655
|
+
...note.repo ? { repo: note.repo } : {},
|
|
8656
|
+
...note.branch ? { branch: note.branch } : {}
|
|
8657
|
+
}));
|
|
8658
|
+
}
|
|
8659
|
+
var REPORT_NOTES_LIMIT = 500;
|
|
8660
|
+
|
|
8661
|
+
// src/report/css.ts
|
|
8662
|
+
var REPORT_CSS = `
|
|
8663
|
+
:root {
|
|
8664
|
+
--ink: #14201c;
|
|
8665
|
+
--muted: #5c6b64;
|
|
8666
|
+
--line: #d5ddd8;
|
|
8667
|
+
--bg: #f4f7f5;
|
|
8668
|
+
--card: #ffffff;
|
|
8669
|
+
--accent: #0b6e4f;
|
|
8670
|
+
--accent-soft: #e3f2eb;
|
|
8671
|
+
--warn: #8a5a00;
|
|
8672
|
+
--warn-bg: #fff6e5;
|
|
8673
|
+
}
|
|
8674
|
+
* { box-sizing: border-box; }
|
|
8675
|
+
body {
|
|
8676
|
+
margin: 0;
|
|
8677
|
+
font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
|
|
8678
|
+
color: var(--ink);
|
|
8679
|
+
background: var(--bg);
|
|
8680
|
+
line-height: 1.45;
|
|
8681
|
+
}
|
|
8682
|
+
.wrap { max-width: 880px; margin: 0 auto; padding: 2.5rem 1.5rem 4rem; }
|
|
8683
|
+
header.hero {
|
|
8684
|
+
border-bottom: 2px solid var(--ink);
|
|
8685
|
+
padding-bottom: 1.25rem;
|
|
8686
|
+
margin-bottom: 1.75rem;
|
|
8687
|
+
}
|
|
8688
|
+
.eyebrow {
|
|
8689
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8690
|
+
text-transform: uppercase;
|
|
8691
|
+
letter-spacing: 0.08em;
|
|
8692
|
+
font-size: 0.72rem;
|
|
8693
|
+
color: var(--muted);
|
|
8694
|
+
margin: 0 0 0.4rem;
|
|
8695
|
+
}
|
|
8696
|
+
h1 { font-size: 2rem; font-weight: 600; margin: 0 0 0.5rem; letter-spacing: -0.02em; }
|
|
8697
|
+
h2 { font-size: 1.25rem; margin: 2rem 0 0.75rem; border-top: 1px solid var(--line); padding-top: 1.25rem; }
|
|
8698
|
+
h3 { margin: 0; font-size: 1.05rem; }
|
|
8699
|
+
.lede { color: var(--muted); margin: 0 0 1rem; }
|
|
8700
|
+
.meta-line { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.85rem; color: var(--muted); }
|
|
8701
|
+
.toolbar { display: flex; flex-wrap: wrap; gap: 0.6rem; margin: 1rem 0 0; }
|
|
8702
|
+
button, .btn {
|
|
8703
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8704
|
+
border: 1px solid var(--ink);
|
|
8705
|
+
background: var(--ink);
|
|
8706
|
+
color: #fff;
|
|
8707
|
+
padding: 0.55rem 0.9rem;
|
|
8708
|
+
border-radius: 4px;
|
|
8709
|
+
cursor: pointer;
|
|
8710
|
+
font-size: 0.9rem;
|
|
8711
|
+
text-decoration: none;
|
|
8712
|
+
display: inline-block;
|
|
8713
|
+
}
|
|
8714
|
+
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; margin: 1rem 0; }
|
|
8715
|
+
.stat { background: var(--card); border: 1px solid var(--line); padding: 0.85rem 1rem; border-radius: 6px; }
|
|
8716
|
+
.stat.highlight { background: var(--accent-soft); border-color: #b7d8c7; }
|
|
8717
|
+
.stat .label {
|
|
8718
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8719
|
+
font-size: 0.72rem;
|
|
8720
|
+
text-transform: uppercase;
|
|
8721
|
+
letter-spacing: 0.06em;
|
|
8722
|
+
color: var(--muted);
|
|
8723
|
+
}
|
|
8724
|
+
.stat .value { font-size: 1.35rem; font-weight: 600; margin-top: 0.25rem; }
|
|
8725
|
+
.pct { font-size: 0.9rem; font-weight: 500; color: var(--accent); }
|
|
8726
|
+
table { width: 100%; border-collapse: collapse; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.82rem; background: var(--card); }
|
|
8727
|
+
th, td { border-bottom: 1px solid var(--line); padding: 0.45rem 0.5rem; text-align: left; vertical-align: top; }
|
|
8728
|
+
th { color: var(--muted); font-weight: 600; }
|
|
8729
|
+
td.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
8730
|
+
.card { background: var(--card); border: 1px solid var(--line); border-radius: 8px; padding: 1rem 1.1rem; margin: 0.75rem 0; }
|
|
8731
|
+
.card header { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: baseline; }
|
|
8732
|
+
.badge {
|
|
8733
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8734
|
+
font-size: 0.68rem;
|
|
8735
|
+
text-transform: uppercase;
|
|
8736
|
+
letter-spacing: 0.05em;
|
|
8737
|
+
border: 1px solid var(--line);
|
|
8738
|
+
padding: 0.1rem 0.4rem;
|
|
8739
|
+
border-radius: 3px;
|
|
8740
|
+
color: var(--muted);
|
|
8741
|
+
}
|
|
8742
|
+
.status-written { background: var(--accent-soft); color: var(--accent); border-color: #b7d8c7; }
|
|
8743
|
+
.status-pending { background: var(--warn-bg); color: var(--warn); border-color: #efd59a; }
|
|
8744
|
+
.status-proposed { background: #eef1f0; }
|
|
8745
|
+
.status-already_in_library { background: #eef1f0; }
|
|
8746
|
+
.note-body {
|
|
8747
|
+
white-space: pre-wrap;
|
|
8748
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8749
|
+
font-size: 0.86rem;
|
|
8750
|
+
background: var(--bg);
|
|
8751
|
+
border-radius: 4px;
|
|
8752
|
+
padding: 0.75rem;
|
|
8753
|
+
overflow-x: auto;
|
|
8754
|
+
}
|
|
8755
|
+
.idea { font-size: 1.05rem; color: var(--ink); margin: 0.5rem 0 0.4rem; }
|
|
8756
|
+
.work { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.85rem; color: var(--muted); margin: 0.25rem 0 0.5rem; }
|
|
8757
|
+
.work-label {
|
|
8758
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8759
|
+
font-size: 0.68rem;
|
|
8760
|
+
text-transform: uppercase;
|
|
8761
|
+
letter-spacing: 0.06em;
|
|
8762
|
+
color: var(--muted);
|
|
8763
|
+
margin-right: 0.4rem;
|
|
8764
|
+
}
|
|
8765
|
+
.note-tech { margin-top: 0.6rem; }
|
|
8766
|
+
.note-tech summary { cursor: pointer; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.82rem; color: var(--muted); margin-top: 0.5rem; }
|
|
8767
|
+
.trace { margin-top: 0.65rem; }
|
|
8768
|
+
.trace > summary { cursor: pointer; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.82rem; color: var(--muted); }
|
|
8769
|
+
.trace-bar { display: flex; height: 8px; border-radius: 999px; overflow: hidden; background: #e8eee9; margin: 0.5rem 0 0.35rem; }
|
|
8770
|
+
.tb { display: block; height: 100%; }
|
|
8771
|
+
.tb.context { background: var(--accent); }
|
|
8772
|
+
.tb.planning { background: #8a97a3; }
|
|
8773
|
+
.tb.other { background: #b5a89a; }
|
|
8774
|
+
.trace-legend { font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.75rem; color: var(--muted); margin: 0 0 0.45rem; }
|
|
8775
|
+
.trace-steps { list-style: none; padding: 0; margin: 0.35rem 0 0; }
|
|
8776
|
+
.trace-steps li {
|
|
8777
|
+
display: grid;
|
|
8778
|
+
grid-template-columns: auto 1fr auto;
|
|
8779
|
+
column-gap: 0.4rem;
|
|
8780
|
+
align-items: baseline;
|
|
8781
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8782
|
+
font-size: 0.78rem;
|
|
8783
|
+
padding: 0.16rem 0;
|
|
8784
|
+
border-bottom: 1px solid var(--line);
|
|
8785
|
+
}
|
|
8786
|
+
.trace-steps .chip { font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.08rem 0.38rem; margin: 0; }
|
|
8787
|
+
.trace-steps .pv { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
8788
|
+
.trace-steps .tok { font-variant-numeric: tabular-nums; color: var(--muted); text-align: right; }
|
|
8789
|
+
.ts-head { color: var(--muted); font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.04em; border-bottom: 1px solid var(--line); }
|
|
8790
|
+
.ts-head .chip { background: none; border: 0; padding-left: 0; color: inherit; }
|
|
8791
|
+
.ts-head .pv, .ts-head .tok { color: inherit; overflow: visible; text-overflow: unset; }
|
|
8792
|
+
.ts-user .chip { background: #eef1f0; }
|
|
8793
|
+
.ts-context .chip { background: var(--accent-soft); color: var(--accent); border-color: #b7d8c7; }
|
|
8794
|
+
.ts-planning .chip { background: #eef1f4; }
|
|
8795
|
+
.ts-other .chip { background: #f3efe9; }
|
|
8796
|
+
.ts-code { opacity: 0.55; color: var(--muted); }
|
|
8797
|
+
.ts-omit .pv { font-style: italic; }
|
|
8798
|
+
.meta, .query, .muted, .footnote {
|
|
8799
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
8800
|
+
font-size: 0.85rem;
|
|
8801
|
+
color: var(--muted);
|
|
8802
|
+
}
|
|
8803
|
+
code { font-size: 0.84em; }
|
|
8804
|
+
footer.cta { margin-top: 2.5rem; padding: 1.25rem; background: var(--accent-soft); border: 1px solid #b7d8c7; border-radius: 8px; }
|
|
8805
|
+
footer.cta h2 { border: 0; padding: 0; margin: 0 0 0.5rem; }
|
|
8806
|
+
@media print {
|
|
8807
|
+
body { background: #fff; }
|
|
8808
|
+
.toolbar, .no-print { display: none !important; }
|
|
8809
|
+
.wrap { max-width: none; padding: 0; }
|
|
8810
|
+
.card, .stat { break-inside: avoid; }
|
|
8811
|
+
a { color: inherit; text-decoration: none; }
|
|
8812
|
+
details.trace > *:not(summary) { display: none !important; }
|
|
8813
|
+
}
|
|
8814
|
+
`;
|
|
8815
|
+
|
|
8816
|
+
// src/report/html.ts
|
|
8817
|
+
function esc(value) {
|
|
8818
|
+
return String(value ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
8819
|
+
}
|
|
8820
|
+
function fmtInt(n2) {
|
|
8821
|
+
const v = Number(n2);
|
|
8822
|
+
if (!Number.isFinite(v))
|
|
8823
|
+
return "—";
|
|
8824
|
+
return Math.trunc(v).toLocaleString("en-US");
|
|
8825
|
+
}
|
|
8826
|
+
function applyStatusDefaults(candidates, dryRun) {
|
|
8827
|
+
const fallback = dryRun ? "proposed" : "written";
|
|
8828
|
+
const known = new Set(["written", "pending", "proposed", "already_in_library"]);
|
|
8829
|
+
return candidates.map((c2) => {
|
|
8830
|
+
const current = (c2.status ?? "").trim().toLowerCase();
|
|
8831
|
+
return { ...c2, status: known.has(current) ? current : fallback };
|
|
8832
|
+
});
|
|
8833
|
+
}
|
|
8834
|
+
function tokenTotalsFromCandidates(candidates, inventory) {
|
|
8835
|
+
if (candidates.length === 0)
|
|
8836
|
+
return null;
|
|
8837
|
+
let replaced = 0;
|
|
8838
|
+
let hasRediscovery = false;
|
|
8839
|
+
let readCost = 0;
|
|
8840
|
+
for (const c2 of candidates) {
|
|
8841
|
+
const raw = c2.approx_rediscovery_tokens;
|
|
8842
|
+
if (raw != null) {
|
|
8843
|
+
hasRediscovery = true;
|
|
8844
|
+
replaced += Math.max(0, Number(raw) || 0);
|
|
8845
|
+
}
|
|
8846
|
+
const blob = `${c2.title ?? ""}
|
|
8847
|
+
${c2.content ?? ""}`;
|
|
8848
|
+
readCost += Math.round(blob.length / CHARS_PER_TOKEN);
|
|
8849
|
+
}
|
|
8850
|
+
if (!hasRediscovery)
|
|
8851
|
+
return null;
|
|
8852
|
+
let baseline = inventory.totals?.learning_tokens ?? 0;
|
|
8853
|
+
if (!baseline) {
|
|
8854
|
+
baseline = inventory.transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
8855
|
+
}
|
|
8856
|
+
const saved = Math.max(0, replaced - readCost);
|
|
8857
|
+
const pct = baseline ? Math.round(1000 * saved / baseline) / 10 : 0;
|
|
8858
|
+
return {
|
|
8859
|
+
baseline_tokens: baseline,
|
|
8860
|
+
replaced_baseline_tokens: replaced,
|
|
8861
|
+
read_knowledge_tokens: readCost,
|
|
8862
|
+
tokens_saved: saved,
|
|
8863
|
+
pct_saved: pct
|
|
8864
|
+
};
|
|
8865
|
+
}
|
|
8866
|
+
function presentationCopy(c2) {
|
|
8867
|
+
const idea = (c2.plain_english || "").trim() || (c2.content || "").trim();
|
|
8868
|
+
const how = (c2.how_found || "").trim();
|
|
8869
|
+
if (how)
|
|
8870
|
+
return [idea, how];
|
|
8871
|
+
const raw = c2.approx_rediscovery_tokens ?? c2.baseline_tokens;
|
|
8872
|
+
const n2 = raw == null ? 0 : Number(raw) || 0;
|
|
8873
|
+
const work = n2 ? `About ${n2.toLocaleString("en-US")} tokens of reading, searching, and tracing in this session before the conclusion.` : "Investigation stretch was not measured.";
|
|
8874
|
+
return [idea, work];
|
|
8875
|
+
}
|
|
8876
|
+
function parseLineSpec(spec) {
|
|
8877
|
+
const out = new Set;
|
|
8878
|
+
if (spec == null)
|
|
8879
|
+
return out;
|
|
8880
|
+
const raw = Array.isArray(spec) ? spec.map(String).join(",") : String(spec);
|
|
8881
|
+
if (!raw.trim())
|
|
8882
|
+
return out;
|
|
8883
|
+
for (const part of raw.split(",")) {
|
|
8884
|
+
const trimmed = part.trim();
|
|
8885
|
+
if (!trimmed)
|
|
8886
|
+
continue;
|
|
8887
|
+
if (trimmed.includes("-")) {
|
|
8888
|
+
const [a3, b2] = trimmed.split("-", 2);
|
|
8889
|
+
let start = Number.parseInt(a3.trim(), 10);
|
|
8890
|
+
let end = Number.parseInt(b2.trim(), 10);
|
|
8891
|
+
if (!Number.isFinite(start) || !Number.isFinite(end))
|
|
8892
|
+
continue;
|
|
8893
|
+
if (end < start)
|
|
8894
|
+
[start, end] = [end, start];
|
|
8895
|
+
for (let i2 = start;i2 <= end; i2++)
|
|
8896
|
+
out.add(i2);
|
|
8897
|
+
} else {
|
|
8898
|
+
const n2 = Number.parseInt(trimmed, 10);
|
|
8899
|
+
if (Number.isFinite(n2))
|
|
8900
|
+
out.add(n2);
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
return out;
|
|
8904
|
+
}
|
|
8905
|
+
function expandDigestTurns(turns, spec) {
|
|
8906
|
+
if (turns.length === 0 || spec.size === 0)
|
|
8907
|
+
return [];
|
|
8908
|
+
const minLine = Math.min(...spec);
|
|
8909
|
+
const maxLine = Math.max(...spec);
|
|
8910
|
+
let start = minLine;
|
|
8911
|
+
for (const turn of turns) {
|
|
8912
|
+
const line = Number(turn.line) || 0;
|
|
8913
|
+
if (turn.role === "user" && line <= minLine)
|
|
8914
|
+
start = line;
|
|
8915
|
+
}
|
|
8916
|
+
return turns.filter((turn) => {
|
|
8917
|
+
const line = Number(turn.line) || 0;
|
|
8918
|
+
return start <= line && line <= maxLine;
|
|
8919
|
+
});
|
|
8920
|
+
}
|
|
8921
|
+
function collapsePreview(text, limit = PREVIEW_CHARS) {
|
|
8922
|
+
const cleaned = (text || "").replace(TAG_RE, " ").replace(WS_RE, " ").trim();
|
|
8923
|
+
if (cleaned.length > limit)
|
|
8924
|
+
return `${cleaned.slice(0, limit - 1).trimEnd()}…`;
|
|
8925
|
+
return cleaned;
|
|
8926
|
+
}
|
|
8927
|
+
function turnText(turn) {
|
|
8928
|
+
const texts = turn.text ?? [];
|
|
8929
|
+
if (typeof texts === "string")
|
|
8930
|
+
return texts;
|
|
8931
|
+
return texts.filter(Boolean).join(" ");
|
|
8932
|
+
}
|
|
8933
|
+
function innerToolName(tool) {
|
|
8934
|
+
const name = String(tool.name ?? "");
|
|
8935
|
+
for (const key of ["toolName", "tool_name"]) {
|
|
8936
|
+
const val = tool[key];
|
|
8937
|
+
if (val)
|
|
8938
|
+
return String(val);
|
|
8939
|
+
}
|
|
8940
|
+
if (tool.knowledge?.tool)
|
|
8941
|
+
return String(tool.knowledge.tool);
|
|
8942
|
+
if (name.startsWith("mcp:"))
|
|
8943
|
+
return name.split(":", 2)[1] ?? name;
|
|
8944
|
+
if (name.startsWith("mcp__"))
|
|
8945
|
+
return name.split("__").at(-1) ?? name;
|
|
8946
|
+
return name;
|
|
8947
|
+
}
|
|
8948
|
+
function displayToolName(tool) {
|
|
8949
|
+
const inner = innerToolName(tool);
|
|
8950
|
+
const wrapper = String(tool.name ?? "");
|
|
8951
|
+
if (inner === "GetMcpTools" || wrapper === "GetMcpTools")
|
|
8952
|
+
return "MCP schema";
|
|
8953
|
+
if (SQL_DISPLAY.has(inner))
|
|
8954
|
+
return "SQL";
|
|
8955
|
+
if (LOGS_DISPLAY.has(inner))
|
|
8956
|
+
return "Logs";
|
|
8957
|
+
return inner || wrapper || "tool";
|
|
8958
|
+
}
|
|
8959
|
+
function firstArgPreview(args, keys) {
|
|
8960
|
+
if (typeof args !== "object" || args === null)
|
|
8961
|
+
return "";
|
|
8962
|
+
const row = args;
|
|
8963
|
+
for (const key of keys) {
|
|
8964
|
+
if (row[key]) {
|
|
8965
|
+
const pv = collapsePreview(String(row[key]));
|
|
8966
|
+
if (pv)
|
|
8967
|
+
return pv;
|
|
8968
|
+
}
|
|
8969
|
+
}
|
|
8970
|
+
return "";
|
|
8971
|
+
}
|
|
8972
|
+
function toolPreview(tool, text) {
|
|
8973
|
+
const name = String(tool.name ?? "");
|
|
8974
|
+
const inner = innerToolName(tool);
|
|
8975
|
+
if (tool.path) {
|
|
8976
|
+
const raw = String(tool.path);
|
|
8977
|
+
const leaf = raw.includes("/") ? raw.split("/").at(-1) : raw;
|
|
8978
|
+
const pv = collapsePreview(leaf ?? "");
|
|
8979
|
+
if (pv)
|
|
8980
|
+
return pv;
|
|
8981
|
+
}
|
|
8982
|
+
if (tool.pattern) {
|
|
8983
|
+
const pv = collapsePreview(String(tool.pattern));
|
|
8984
|
+
if (pv)
|
|
8985
|
+
return pv;
|
|
8986
|
+
}
|
|
8987
|
+
if (tool.command_preview) {
|
|
8988
|
+
const pv = collapsePreview(String(tool.command_preview));
|
|
8989
|
+
if (pv)
|
|
8990
|
+
return pv;
|
|
8991
|
+
}
|
|
8992
|
+
if (tool.query) {
|
|
8993
|
+
const pv = collapsePreview(String(tool.query));
|
|
8994
|
+
if (pv)
|
|
8995
|
+
return pv;
|
|
8996
|
+
}
|
|
8997
|
+
if (tool.file_path) {
|
|
8998
|
+
const raw = String(tool.file_path);
|
|
8999
|
+
const leaf = raw.includes("/") ? raw.split("/").at(-1) : raw;
|
|
9000
|
+
const pv = collapsePreview(leaf ?? "");
|
|
9001
|
+
if (pv)
|
|
9002
|
+
return pv;
|
|
9003
|
+
}
|
|
9004
|
+
if (tool.prompt) {
|
|
9005
|
+
const pv = collapsePreview(String(tool.prompt));
|
|
9006
|
+
if (pv)
|
|
9007
|
+
return pv;
|
|
9008
|
+
}
|
|
9009
|
+
if (tool.knowledge) {
|
|
9010
|
+
const pv = firstArgPreview(tool.knowledge.arguments, ["query", "sql", "command"]);
|
|
9011
|
+
if (pv)
|
|
9012
|
+
return pv;
|
|
9013
|
+
}
|
|
9014
|
+
const fromArgs = firstArgPreview(tool.arguments, ["query", "sql", "command", "description"]);
|
|
9015
|
+
if (fromArgs)
|
|
9016
|
+
return fromArgs;
|
|
9017
|
+
const toolName = tool.toolName || tool.tool_name;
|
|
9018
|
+
if (toolName) {
|
|
9019
|
+
const combo = tool.server ? `${toolName} · ${tool.server}` : String(toolName);
|
|
9020
|
+
const pv = collapsePreview(combo);
|
|
9021
|
+
if (pv)
|
|
9022
|
+
return pv;
|
|
9023
|
+
}
|
|
9024
|
+
const fromText = collapsePreview(text);
|
|
9025
|
+
if (fromText)
|
|
9026
|
+
return fromText;
|
|
9027
|
+
if (name === "GetMcpTools" || inner === "GetMcpTools")
|
|
9028
|
+
return "Look up available MCP tools";
|
|
9029
|
+
if (name === "CallMcpTool") {
|
|
9030
|
+
const extra = toolName || (inner !== "CallMcpTool" ? inner : "");
|
|
9031
|
+
return extra ? `MCP call · ${extra}` : "MCP call";
|
|
9032
|
+
}
|
|
9033
|
+
return "No input recorded";
|
|
9034
|
+
}
|
|
9035
|
+
function classifyDigestTool(tool) {
|
|
9036
|
+
const name = String(tool.name ?? "");
|
|
9037
|
+
const inner = innerToolName(tool);
|
|
9038
|
+
const canonical = inner || name;
|
|
9039
|
+
if (["Write", "Edit", "MultiEdit", "StrReplace", "Delete"].includes(canonical))
|
|
9040
|
+
return "code";
|
|
9041
|
+
if (["TodoWrite", "update_plan", "SwitchMode"].includes(canonical))
|
|
9042
|
+
return "planning";
|
|
9043
|
+
if (["write_knowledge", "finalize_session_knowledge"].includes(canonical))
|
|
9044
|
+
return "other";
|
|
9045
|
+
return "context";
|
|
9046
|
+
}
|
|
9047
|
+
function usableTools(turn) {
|
|
9048
|
+
return (turn.tools ?? []).filter((tool) => !SKIP_TOOL_NAMES.has(String(tool.name ?? "")));
|
|
9049
|
+
}
|
|
9050
|
+
function buildTraceSteps(turns) {
|
|
9051
|
+
const steps = [];
|
|
9052
|
+
for (const turn of turns) {
|
|
9053
|
+
const role = turn.role ?? "";
|
|
9054
|
+
const est = Number(turn.est_tokens) || 0;
|
|
9055
|
+
const text = turnText(turn);
|
|
9056
|
+
const tools = usableTools(turn);
|
|
9057
|
+
if (role === "user") {
|
|
9058
|
+
if (!text)
|
|
9059
|
+
continue;
|
|
9060
|
+
steps.push({
|
|
9061
|
+
kind: "user",
|
|
9062
|
+
bucket: "other",
|
|
9063
|
+
label: "Question",
|
|
9064
|
+
preview: collapsePreview(text) || "No input recorded",
|
|
9065
|
+
tokens: est
|
|
9066
|
+
});
|
|
9067
|
+
continue;
|
|
9068
|
+
}
|
|
9069
|
+
if (tools.length > 0) {
|
|
9070
|
+
const share2 = Math.trunc(est / tools.length);
|
|
9071
|
+
const remainder = est - share2 * tools.length;
|
|
9072
|
+
tools.forEach((tool, i2) => {
|
|
9073
|
+
const bucket = classifyDigestTool(tool);
|
|
9074
|
+
const preview = toolPreview(tool, text);
|
|
9075
|
+
steps.push({
|
|
9076
|
+
kind: "tool",
|
|
9077
|
+
bucket,
|
|
9078
|
+
label: displayToolName(tool),
|
|
9079
|
+
preview: bucket === "code" ? `${preview} · implementation (not counted)` : preview,
|
|
9080
|
+
tokens: share2 + (i2 === 0 ? remainder : 0)
|
|
9081
|
+
});
|
|
9082
|
+
});
|
|
9083
|
+
continue;
|
|
9084
|
+
}
|
|
9085
|
+
if (text) {
|
|
9086
|
+
steps.push({
|
|
9087
|
+
kind: "reasoning",
|
|
9088
|
+
bucket: "other",
|
|
9089
|
+
label: "Reasoning",
|
|
9090
|
+
preview: collapsePreview(text),
|
|
9091
|
+
tokens: est
|
|
9092
|
+
});
|
|
9093
|
+
}
|
|
9094
|
+
}
|
|
9095
|
+
return steps;
|
|
9096
|
+
}
|
|
9097
|
+
function capTraceSteps(steps) {
|
|
9098
|
+
if (steps.length <= MAX_TRACE_STEPS)
|
|
9099
|
+
return steps;
|
|
9100
|
+
const omitted = steps.length - MAX_TRACE_STEPS;
|
|
9101
|
+
return [steps[0], { omitted }, ...steps.slice(-(MAX_TRACE_STEPS - 1))];
|
|
9102
|
+
}
|
|
9103
|
+
function renderTraceHtml(candidate, digests) {
|
|
9104
|
+
const tid = String(candidate.transcript_id ?? "").trim();
|
|
9105
|
+
const digest = tid ? digests?.[tid] : undefined;
|
|
9106
|
+
if (!digest)
|
|
9107
|
+
return "";
|
|
9108
|
+
const spec = parseLineSpec(candidate.investigation_lines);
|
|
9109
|
+
if (spec.size === 0)
|
|
9110
|
+
return "";
|
|
9111
|
+
const turns = expandDigestTurns(digest.turns, spec);
|
|
9112
|
+
if (turns.length === 0)
|
|
9113
|
+
return "";
|
|
9114
|
+
const steps = buildTraceSteps(turns);
|
|
9115
|
+
if (steps.length === 0)
|
|
9116
|
+
return "";
|
|
9117
|
+
const learning = { context: 0, planning: 0, other: 0 };
|
|
9118
|
+
const toolCounts = new Map;
|
|
9119
|
+
let reasoningN = 0;
|
|
9120
|
+
for (const step of steps) {
|
|
9121
|
+
const bucket = step.bucket;
|
|
9122
|
+
const tokens = Number(step.tokens) || 0;
|
|
9123
|
+
if (bucket === "context" || bucket === "planning" || bucket === "other") {
|
|
9124
|
+
learning[bucket] += tokens;
|
|
9125
|
+
}
|
|
9126
|
+
if (step.kind === "reasoning")
|
|
9127
|
+
reasoningN += 1;
|
|
9128
|
+
else if (step.kind === "tool" && bucket !== "code") {
|
|
9129
|
+
const label = String(step.label || "tool");
|
|
9130
|
+
toolCounts.set(label, (toolCounts.get(label) ?? 0) + 1);
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
const learningTotal = learning.context + learning.planning + learning.other;
|
|
9134
|
+
const official = candidate.approx_rediscovery_tokens;
|
|
9135
|
+
const summaryTokens = official != null && Number.isFinite(Number(official)) ? Number(official) : learningTotal;
|
|
9136
|
+
const bits = [...toolCounts.entries()].sort((a3, b2) => b2[1] - a3[1]).slice(0, 5).map(([name, n2]) => `${n2} ${name}`);
|
|
9137
|
+
if (reasoningN)
|
|
9138
|
+
bits.push(`${reasoningN} reasoning`);
|
|
9139
|
+
bits.push(`~${fmtInt(summaryTokens)} tok`);
|
|
9140
|
+
const summary = `Work to learn this · ${bits.join(" · ")}`;
|
|
9141
|
+
const learnSum = learningTotal || 1;
|
|
9142
|
+
const bar = ["context", "planning", "other"].map((bucket) => {
|
|
9143
|
+
const pct = learningTotal ? 100 * learning[bucket] / learnSum : 0;
|
|
9144
|
+
return `<span class="tb ${bucket}" style="width:${pct.toFixed(1)}%"></span>`;
|
|
9145
|
+
}).join("");
|
|
9146
|
+
const legendParts = [];
|
|
9147
|
+
if (learning.context)
|
|
9148
|
+
legendParts.push(`Reads & search ${fmtInt(learning.context)}`);
|
|
9149
|
+
if (learning.planning)
|
|
9150
|
+
legendParts.push(`Planning ${fmtInt(learning.planning)}`);
|
|
9151
|
+
if (learning.other)
|
|
9152
|
+
legendParts.push(`Reasoning ${fmtInt(learning.other)}`);
|
|
9153
|
+
const rows = [
|
|
9154
|
+
'<li class="ts-head" aria-hidden="true"><span class="chip">Action</span><span class="pv">What it did</span><span class="tok">Tokens</span></li>'
|
|
9155
|
+
];
|
|
9156
|
+
for (const step of capTraceSteps(steps)) {
|
|
9157
|
+
if (step.omitted) {
|
|
9158
|
+
rows.push(`<li class="ts-omit"><span class="chip"></span><span class="pv">${esc(step.omitted)} earlier steps omitted</span><span class="tok"></span></li>`);
|
|
9159
|
+
continue;
|
|
9160
|
+
}
|
|
9161
|
+
const bucket = String(step.bucket || "other");
|
|
9162
|
+
const css = step.kind === "user" ? "ts-user" : { context: "ts-context", planning: "ts-planning", other: "ts-other", code: "ts-code" }[bucket] ?? "ts-other";
|
|
9163
|
+
const tok = step.tokens ? `<span class="tok">${esc(fmtInt(step.tokens))}</span>` : '<span class="tok"></span>';
|
|
9164
|
+
rows.push(`<li class="${css}"><span class="chip">${esc(step.label)}</span><span class="pv">${esc(step.preview)}</span>${tok}</li>`);
|
|
9165
|
+
}
|
|
9166
|
+
return `
|
|
9167
|
+
<details class="trace">
|
|
9168
|
+
<summary>${esc(summary)}</summary>
|
|
9169
|
+
<div class="trace-bar" title="context / planning / other">${bar}</div>
|
|
9170
|
+
<p class="trace-legend">${legendParts.join(" · ")}</p>
|
|
9171
|
+
<ol class="trace-steps">${rows.join("")}</ol>
|
|
9172
|
+
</details>
|
|
9173
|
+
`;
|
|
9174
|
+
}
|
|
9175
|
+
function notesSectionCopy(candidates) {
|
|
9176
|
+
const n2 = candidates.length;
|
|
9177
|
+
const written = candidates.filter((c2) => (c2.status || "written").toLowerCase() === "written").length;
|
|
9178
|
+
const pending = candidates.filter((c2) => (c2.status || "").toLowerCase() === "pending").length;
|
|
9179
|
+
const already = candidates.filter((c2) => (c2.status || "").toLowerCase() === "already_in_library").length;
|
|
9180
|
+
const proposed = n2 - written - pending - already;
|
|
9181
|
+
if (n2 > 0 && already === n2) {
|
|
9182
|
+
return [
|
|
9183
|
+
"Notes already in the Library",
|
|
9184
|
+
"These pages were already present — no new write_knowledge calls.",
|
|
9185
|
+
"Share",
|
|
9186
|
+
"Nothing new to write; the Library already had these pages."
|
|
9187
|
+
];
|
|
9188
|
+
}
|
|
9189
|
+
if (n2 > 0 && written === n2) {
|
|
9190
|
+
return [
|
|
9191
|
+
"Notes written to Dosu",
|
|
9192
|
+
"",
|
|
9193
|
+
"Share",
|
|
9194
|
+
"Notes are on the backfill branch and in the candidate-topic pipeline. Print / Save as PDF if you want a copy."
|
|
9195
|
+
];
|
|
9196
|
+
}
|
|
9197
|
+
if (n2 > 0 && proposed === n2) {
|
|
9198
|
+
return [
|
|
9199
|
+
"Proposed write_knowledge calls",
|
|
9200
|
+
"Plain-English takeaway plus what it took to find — not the original user prompts.",
|
|
9201
|
+
"Next step",
|
|
9202
|
+
"Run the skill (without dry-run) so these payloads are written via write_knowledge."
|
|
9203
|
+
];
|
|
9204
|
+
}
|
|
9205
|
+
if (n2 === 0) {
|
|
9206
|
+
return [
|
|
9207
|
+
"write_knowledge notes",
|
|
9208
|
+
"Plain-English takeaway plus what it took to find — not the original user prompts.",
|
|
9209
|
+
"Next step",
|
|
9210
|
+
"Run knowledge sync so learnings are extracted and written via write_knowledge."
|
|
9211
|
+
];
|
|
9212
|
+
}
|
|
9213
|
+
const bits = [];
|
|
9214
|
+
if (written)
|
|
9215
|
+
bits.push(`${written} written`);
|
|
9216
|
+
if (proposed)
|
|
9217
|
+
bits.push(`${proposed} proposed`);
|
|
9218
|
+
if (pending)
|
|
9219
|
+
bits.push(`${pending} pending`);
|
|
9220
|
+
if (already)
|
|
9221
|
+
bits.push(`${already} already in library`);
|
|
9222
|
+
return [
|
|
9223
|
+
"write_knowledge notes",
|
|
9224
|
+
`${bits.join(", ")} — Plain-English takeaway plus what it took to find — not the original user prompts.`,
|
|
9225
|
+
"Share",
|
|
9226
|
+
"Written notes are in the candidate-topic pipeline. Proposed/pending items still need a write."
|
|
9227
|
+
];
|
|
9228
|
+
}
|
|
9229
|
+
function buildReportHtml(options) {
|
|
9230
|
+
const inventory = options.inventory;
|
|
9231
|
+
const transcripts = inventory.transcripts ?? [];
|
|
9232
|
+
let candidates = applyStatusDefaults(options.candidates ?? [], Boolean(options.dryRun));
|
|
9233
|
+
candidates = [...candidates].sort((a3, b2) => {
|
|
9234
|
+
const av = Number(a3.approx_rediscovery_tokens) || 0;
|
|
9235
|
+
const bv = Number(b2.approx_rediscovery_tokens) || 0;
|
|
9236
|
+
return bv - av;
|
|
9237
|
+
});
|
|
9238
|
+
const org = options.orgName || "Your team";
|
|
9239
|
+
const repo = options.repo || inventory.cwd || "—";
|
|
9240
|
+
const branch = options.branch || "—";
|
|
9241
|
+
const summary = options.summary || "Local agent session logs were mined into Dosu notes so the next task can reuse them — reducing rediscovery cost.";
|
|
9242
|
+
const generated = `${(options.generatedAt ?? new Date).toISOString().replace("T", " ").slice(0, 16)} UTC`;
|
|
9243
|
+
const derived = tokenTotalsFromCandidates(candidates, inventory);
|
|
9244
|
+
const [notesHeading, notesLede, footerTitle, footerBody] = notesSectionCopy(candidates);
|
|
9245
|
+
const candidateRows = candidates.map((c2, i2) => {
|
|
9246
|
+
const [idea, work] = presentationCopy(c2);
|
|
9247
|
+
const traceHtml = renderTraceHtml(c2, options.digests);
|
|
9248
|
+
const content = (c2.content || "").trim();
|
|
9249
|
+
const ideaHtml = idea ? `<p class="idea">${esc(idea)}</p>` : "<p class='muted'>Extract a lean note before writing to Dosu.</p>";
|
|
9250
|
+
const workHtml = `<p class="work"><span class="work-label">To find this</span> ${esc(work)}</p>`;
|
|
9251
|
+
const techHtml = content && content !== idea ? `<details class="note-tech"><summary>Technical note</summary><pre class="note-body">${esc(content)}</pre></details>` : "";
|
|
9252
|
+
const status = c2.status || "written";
|
|
9253
|
+
const queryHtml = c2.user_query ? `<p class="query"><strong>Trigger:</strong> ${esc(c2.user_query)}</p>` : "";
|
|
9254
|
+
return `
|
|
9255
|
+
<article class="card" id="c-${i2 + 1}">
|
|
9256
|
+
<header>
|
|
9257
|
+
<span class="badge status-${esc(status)}">${esc(status)}</span>
|
|
9258
|
+
<h3>${esc(c2.title || "Untitled")}</h3>
|
|
9259
|
+
</header>
|
|
9260
|
+
<p class="meta">
|
|
9261
|
+
session ${esc(c2.session_title || c2.transcript_id || "—")}
|
|
9262
|
+
· rediscovery ~${fmtInt(c2.approx_rediscovery_tokens ?? c2.baseline_tokens)} tok
|
|
9263
|
+
</p>
|
|
9264
|
+
${ideaHtml}
|
|
9265
|
+
${workHtml}
|
|
9266
|
+
${traceHtml}
|
|
9267
|
+
${techHtml}
|
|
9268
|
+
${queryHtml}
|
|
9269
|
+
</article>`;
|
|
9270
|
+
});
|
|
9271
|
+
const heavy = [...transcripts].sort((a3, b2) => (b2.learning_tokens || 0) - (a3.learning_tokens || 0)).slice(0, 8);
|
|
9272
|
+
const heavyRows = heavy.map((t2) => {
|
|
9273
|
+
const title = (t2.title || "").trim() || t2.transcript_id;
|
|
9274
|
+
const query = (t2.user_queries?.[0] || "").slice(0, 100);
|
|
9275
|
+
return `<tr>
|
|
9276
|
+
<td>${esc(t2.source)}</td>
|
|
9277
|
+
<td>${esc(title)}</td>
|
|
9278
|
+
<td class="num">${fmtInt(t2.learning_tokens)}</td>
|
|
9279
|
+
<td class="num">${fmtInt(t2.rediscovery_tool_calls)}</td>
|
|
9280
|
+
<td>${esc(query)}</td>
|
|
9281
|
+
</tr>`;
|
|
9282
|
+
}).join("");
|
|
9283
|
+
let tokenSection;
|
|
9284
|
+
if (derived) {
|
|
9285
|
+
tokenSection = `
|
|
9286
|
+
<section>
|
|
9287
|
+
<h2>Estimated context savings</h2>
|
|
9288
|
+
<p class="lede">Counterfactual: replace rediscovery stretches with a Dosu <code>read_knowledge</code> hit.</p>
|
|
9289
|
+
<div class="stats">
|
|
9290
|
+
<div class="stat"><div class="label">Baseline (cost to learn)</div><div class="value">${fmtInt(derived.baseline_tokens)}</div></div>
|
|
9291
|
+
<div class="stat"><div class="label">Learning replaced</div><div class="value">${fmtInt(derived.replaced_baseline_tokens)}</div></div>
|
|
9292
|
+
<div class="stat"><div class="label">Read cost</div><div class="value">${fmtInt(derived.read_knowledge_tokens)}</div></div>
|
|
9293
|
+
<div class="stat highlight"><div class="label">Est. tokens saved</div><div class="value">${fmtInt(derived.tokens_saved)} <span class="pct">(${esc(`${derived.pct_saved}%`)})</span></div></div>
|
|
9294
|
+
</div>
|
|
9295
|
+
</section>`;
|
|
9296
|
+
} else {
|
|
9297
|
+
tokenSection = `
|
|
9298
|
+
<section>
|
|
9299
|
+
<h2>Estimated context savings</h2>
|
|
9300
|
+
<p class="muted">No rediscovery estimates on the notes yet — each candidate needs <code>approx_rediscovery_tokens</code>.</p>
|
|
9301
|
+
</section>`;
|
|
9302
|
+
}
|
|
9303
|
+
const notesBody = candidateRows.join("") || '<p class="muted">No write_knowledge payloads yet. Run knowledge sync so the miner extracts learnings.</p>';
|
|
9304
|
+
const lede = notesLede ? `<p class="lede">${esc(notesLede)}</p>` : "";
|
|
9305
|
+
return `<!DOCTYPE html>
|
|
9306
|
+
<html lang="en">
|
|
9307
|
+
<head>
|
|
9308
|
+
<meta charset="utf-8" />
|
|
9309
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
9310
|
+
<title>Dosu knowledge report — ${esc(org)}</title>
|
|
9311
|
+
<style>
|
|
9312
|
+
${REPORT_CSS}
|
|
9313
|
+
</style>
|
|
9314
|
+
</head>
|
|
9315
|
+
<body>
|
|
9316
|
+
<div class="wrap">
|
|
9317
|
+
<header class="hero">
|
|
9318
|
+
<p class="eyebrow">Dosu · Knowledge report</p>
|
|
9319
|
+
<h1>${esc(org)}</h1>
|
|
9320
|
+
<p class="lede">${esc(summary)}</p>
|
|
9321
|
+
<p class="meta-line">
|
|
9322
|
+
Repo <code>${esc(repo)}</code>
|
|
9323
|
+
· branch <code>${esc(branch)}</code>
|
|
9324
|
+
· generated ${esc(generated)}
|
|
9325
|
+
</p>
|
|
9326
|
+
<div class="toolbar no-print">
|
|
9327
|
+
<button type="button" onclick="window.print()">Print / Save as PDF</button>
|
|
9328
|
+
</div>
|
|
9329
|
+
</header>
|
|
9330
|
+
|
|
9331
|
+
${tokenSection}
|
|
9332
|
+
|
|
9333
|
+
<section>
|
|
9334
|
+
<h2>${esc(notesHeading)} <span class="muted">(${candidates.length})</span></h2>
|
|
9335
|
+
${lede}
|
|
9336
|
+
${notesBody}
|
|
9337
|
+
</section>
|
|
9338
|
+
|
|
9339
|
+
<section>
|
|
9340
|
+
<h2>Heaviest sessions</h2>
|
|
9341
|
+
<p class="lede">Where learning cost was highest — prime targets for Dosu cache hits.</p>
|
|
9342
|
+
<table>
|
|
9343
|
+
<thead>
|
|
9344
|
+
<tr>
|
|
9345
|
+
<th>Host</th><th>Session</th><th>Learning tokens</th><th>Rediscovery tools</th><th>Query</th>
|
|
9346
|
+
</tr>
|
|
9347
|
+
</thead>
|
|
9348
|
+
<tbody>
|
|
9349
|
+
${heavyRows || '<tr><td colspan="5" class="muted">No sessions</td></tr>'}
|
|
9350
|
+
</tbody>
|
|
9351
|
+
</table>
|
|
9352
|
+
</section>
|
|
9353
|
+
|
|
9354
|
+
<footer class="cta">
|
|
9355
|
+
<h2>${esc(footerTitle)}</h2>
|
|
9356
|
+
<p>${esc(footerBody)}</p>
|
|
9357
|
+
<p class="meta">
|
|
9358
|
+
Print tip: use <strong>Print / Save as PDF</strong> above (or ⌘P / Ctrl+P).
|
|
9359
|
+
Session logs stay between you and your agent — only note text is written to Dosu.
|
|
9360
|
+
</p>
|
|
9361
|
+
<div class="toolbar no-print">
|
|
9362
|
+
<button type="button" onclick="window.print()">Print / Save as PDF</button>
|
|
9363
|
+
</div>
|
|
9364
|
+
</footer>
|
|
9365
|
+
</div>
|
|
9366
|
+
</body>
|
|
9367
|
+
</html>
|
|
9368
|
+
`;
|
|
9369
|
+
}
|
|
9370
|
+
var CHARS_PER_TOKEN = 4, MAX_TRACE_STEPS = 50, PREVIEW_CHARS = 80, SQL_DISPLAY, LOGS_DISPLAY, SKIP_TOOL_NAMES, TAG_RE, WS_RE;
|
|
9371
|
+
var init_html = __esm(() => {
|
|
9372
|
+
SQL_DISPLAY = new Set(["execute_sql", "query_run", "list_tables"]);
|
|
9373
|
+
LOGS_DISPLAY = new Set(["query_logs"]);
|
|
9374
|
+
SKIP_TOOL_NAMES = new Set(["tool_result"]);
|
|
9375
|
+
TAG_RE = /<[^>]+>/g;
|
|
9376
|
+
WS_RE = /\s+/g;
|
|
9377
|
+
});
|
|
9378
|
+
|
|
8844
9379
|
// src/sessions/read.ts
|
|
8845
|
-
import { readFileSync as
|
|
9380
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
8846
9381
|
function asRecord(value) {
|
|
8847
9382
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
8848
9383
|
}
|
|
@@ -8958,11 +9493,11 @@ function readSessionTurns(session) {
|
|
|
8958
9493
|
try {
|
|
8959
9494
|
switch (session.harness) {
|
|
8960
9495
|
case "claude":
|
|
8961
|
-
return readClaude(
|
|
9496
|
+
return readClaude(readFileSync10(session.path, "utf8"));
|
|
8962
9497
|
case "cursor":
|
|
8963
|
-
return readCursor(
|
|
9498
|
+
return readCursor(readFileSync10(session.path, "utf8"));
|
|
8964
9499
|
case "codex":
|
|
8965
|
-
return readCodex(
|
|
9500
|
+
return readCodex(readFileSync10(session.path, "utf8"));
|
|
8966
9501
|
case "opencode":
|
|
8967
9502
|
return readOpencode(session.path, session.id);
|
|
8968
9503
|
}
|
|
@@ -8970,11 +9505,58 @@ function readSessionTurns(session) {
|
|
|
8970
9505
|
return [];
|
|
8971
9506
|
}
|
|
8972
9507
|
}
|
|
9508
|
+
function isRediscoveryTool(name) {
|
|
9509
|
+
return REDISCOVERY_TOOLS.has(name) || name.startsWith("web_search");
|
|
9510
|
+
}
|
|
9511
|
+
function toolNamesFromContent(content) {
|
|
9512
|
+
if (!Array.isArray(content))
|
|
9513
|
+
return [];
|
|
9514
|
+
const names = [];
|
|
9515
|
+
for (const entry of content) {
|
|
9516
|
+
const item = asRecord(entry);
|
|
9517
|
+
if (item?.type === "tool_use" && typeof item.name === "string")
|
|
9518
|
+
names.push(item.name);
|
|
9519
|
+
}
|
|
9520
|
+
return names;
|
|
9521
|
+
}
|
|
9522
|
+
function countRediscoveryToolCalls(session) {
|
|
9523
|
+
try {
|
|
9524
|
+
switch (session.harness) {
|
|
9525
|
+
case "claude":
|
|
9526
|
+
case "cursor": {
|
|
9527
|
+
let n2 = 0;
|
|
9528
|
+
for (const record of jsonlRecords(readFileSync10(session.path, "utf8"))) {
|
|
9529
|
+
const message = asRecord(record.message);
|
|
9530
|
+
for (const name of toolNamesFromContent(message?.content)) {
|
|
9531
|
+
if (isRediscoveryTool(name))
|
|
9532
|
+
n2 += 1;
|
|
9533
|
+
}
|
|
9534
|
+
}
|
|
9535
|
+
return n2;
|
|
9536
|
+
}
|
|
9537
|
+
case "codex": {
|
|
9538
|
+
let n2 = 0;
|
|
9539
|
+
for (const record of jsonlRecords(readFileSync10(session.path, "utf8"))) {
|
|
9540
|
+
const payload = asRecord(record.payload);
|
|
9541
|
+
if (payload?.type === "function_call" && typeof payload.name === "string") {
|
|
9542
|
+
if (isRediscoveryTool(payload.name))
|
|
9543
|
+
n2 += 1;
|
|
9544
|
+
}
|
|
9545
|
+
}
|
|
9546
|
+
return n2;
|
|
9547
|
+
}
|
|
9548
|
+
case "opencode":
|
|
9549
|
+
return 0;
|
|
9550
|
+
}
|
|
9551
|
+
} catch {
|
|
9552
|
+
return 0;
|
|
9553
|
+
}
|
|
9554
|
+
}
|
|
8973
9555
|
function estimateSessionTokens(session) {
|
|
8974
9556
|
let chars = 0;
|
|
8975
9557
|
for (const turn of readSessionTurns(session))
|
|
8976
9558
|
chars += turn.text.length;
|
|
8977
|
-
return Math.round(chars /
|
|
9559
|
+
return Math.round(chars / CHARS_PER_TOKEN2);
|
|
8978
9560
|
}
|
|
8979
9561
|
function isWorthMining(session) {
|
|
8980
9562
|
const turns = readSessionTurns(session);
|
|
@@ -8988,11 +9570,902 @@ function isWorthMining(session) {
|
|
|
8988
9570
|
}
|
|
8989
9571
|
return false;
|
|
8990
9572
|
}
|
|
8991
|
-
var PLAIN_TEXT, CODEX_TEXT,
|
|
9573
|
+
var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS, CHARS_PER_TOKEN2 = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
|
|
8992
9574
|
var init_read = __esm(() => {
|
|
8993
9575
|
init_scan();
|
|
8994
9576
|
PLAIN_TEXT = new Set(["text"]);
|
|
8995
9577
|
CODEX_TEXT = new Set(["input_text", "output_text"]);
|
|
9578
|
+
REDISCOVERY_TOOLS = new Set([
|
|
9579
|
+
"Read",
|
|
9580
|
+
"Grep",
|
|
9581
|
+
"Glob",
|
|
9582
|
+
"Shell",
|
|
9583
|
+
"WebSearch",
|
|
9584
|
+
"WebFetch",
|
|
9585
|
+
"Task",
|
|
9586
|
+
"SemanticSearch",
|
|
9587
|
+
"Bash",
|
|
9588
|
+
"Edit",
|
|
9589
|
+
"MultiEdit",
|
|
9590
|
+
"NotebookEdit",
|
|
9591
|
+
"Agent",
|
|
9592
|
+
"exec_command",
|
|
9593
|
+
"write_stdin",
|
|
9594
|
+
"web_search",
|
|
9595
|
+
"web_search_call",
|
|
9596
|
+
"open_page",
|
|
9597
|
+
"apply_patch",
|
|
9598
|
+
"update_plan",
|
|
9599
|
+
"list_dir",
|
|
9600
|
+
"grep_files",
|
|
9601
|
+
"read_file"
|
|
9602
|
+
]);
|
|
9603
|
+
});
|
|
9604
|
+
|
|
9605
|
+
// src/report/queries.ts
|
|
9606
|
+
function extractUserQueries(text) {
|
|
9607
|
+
const matches = [...text.matchAll(USER_QUERY_RE)].map((m3) => m3[1].trim()).filter(Boolean);
|
|
9608
|
+
if (matches.length > 0)
|
|
9609
|
+
return matches;
|
|
9610
|
+
const cleaned = text.replace(TIMESTAMP_RE, "").trim();
|
|
9611
|
+
if (cleaned.startsWith("# AGENTS.md") || cleaned.startsWith("<INSTRUCTIONS>") || cleaned.startsWith("<permissions instructions>")) {
|
|
9612
|
+
return [];
|
|
9613
|
+
}
|
|
9614
|
+
return cleaned ? [cleaned] : [];
|
|
9615
|
+
}
|
|
9616
|
+
function sessionTitleFromUserText(text, max = TITLE_CHARS) {
|
|
9617
|
+
const first = extractUserQueries(text)[0] ?? "";
|
|
9618
|
+
const collapsed = first.replace(/\s+/g, " ").trim();
|
|
9619
|
+
if (collapsed.length > max)
|
|
9620
|
+
return `${collapsed.slice(0, max - 1).trimEnd()}…`;
|
|
9621
|
+
return collapsed;
|
|
9622
|
+
}
|
|
9623
|
+
var TITLE_CHARS = 80, USER_QUERY_RE, TIMESTAMP_RE;
|
|
9624
|
+
var init_queries = __esm(() => {
|
|
9625
|
+
USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/gi;
|
|
9626
|
+
TIMESTAMP_RE = /<timestamp>[\s\S]*?<\/timestamp>/gi;
|
|
9627
|
+
});
|
|
9628
|
+
|
|
9629
|
+
// src/report/digest.ts
|
|
9630
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
9631
|
+
function asRecord2(value) {
|
|
9632
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9633
|
+
}
|
|
9634
|
+
function estimateTokens(text) {
|
|
9635
|
+
return Math.round(text.length / CHARS_PER_TOKEN3);
|
|
9636
|
+
}
|
|
9637
|
+
function digestEstTokens(texts, tools, resultChars) {
|
|
9638
|
+
const countable = tools.filter((t2) => t2.name !== "tool_result");
|
|
9639
|
+
let payload = texts.join(`
|
|
9640
|
+
`);
|
|
9641
|
+
if (countable.length > 0)
|
|
9642
|
+
payload += JSON.stringify(countable);
|
|
9643
|
+
let tokens = estimateTokens(payload);
|
|
9644
|
+
if (resultChars > 0)
|
|
9645
|
+
tokens += Math.max(1, Math.round(resultChars / CHARS_PER_TOKEN3));
|
|
9646
|
+
return tokens;
|
|
9647
|
+
}
|
|
9648
|
+
function copyToolInputFields(entry, inp) {
|
|
9649
|
+
const row = asRecord2(inp);
|
|
9650
|
+
if (!row)
|
|
9651
|
+
return;
|
|
9652
|
+
if (typeof row.path === "string")
|
|
9653
|
+
entry.path = row.path;
|
|
9654
|
+
if (typeof row.file_path === "string") {
|
|
9655
|
+
entry.file_path = row.file_path;
|
|
9656
|
+
entry.path ??= row.file_path;
|
|
9657
|
+
}
|
|
9658
|
+
if (typeof row.pattern === "string")
|
|
9659
|
+
entry.pattern = row.pattern;
|
|
9660
|
+
if (row.command != null)
|
|
9661
|
+
entry.command_preview = String(row.command).slice(0, 160);
|
|
9662
|
+
if (row.prompt != null) {
|
|
9663
|
+
entry.prompt = String(row.prompt).slice(0, 160);
|
|
9664
|
+
entry.command_preview ??= entry.prompt;
|
|
9665
|
+
}
|
|
9666
|
+
if (typeof row.query === "string")
|
|
9667
|
+
entry.query = row.query;
|
|
9668
|
+
if (typeof row.description === "string" && !entry.command_preview) {
|
|
9669
|
+
entry.command_preview = row.description.slice(0, 160);
|
|
9670
|
+
}
|
|
9671
|
+
}
|
|
9672
|
+
function copyMcpFields(entry, name, inp) {
|
|
9673
|
+
const row = asRecord2(inp);
|
|
9674
|
+
if (!row)
|
|
9675
|
+
return;
|
|
9676
|
+
const isMcp = MCP_DIGEST_TOOLS.has(name) || name.startsWith("mcp__") || Boolean(row.toolName || row.server);
|
|
9677
|
+
if (!isMcp)
|
|
9678
|
+
return;
|
|
9679
|
+
const toolName = row.toolName ?? row.tool_name;
|
|
9680
|
+
if (typeof toolName === "string")
|
|
9681
|
+
entry.toolName = toolName;
|
|
9682
|
+
if (typeof row.server === "string")
|
|
9683
|
+
entry.server = row.server;
|
|
9684
|
+
if (name === "GetMcpTools" && typeof row.pattern === "string")
|
|
9685
|
+
entry.pattern = row.pattern;
|
|
9686
|
+
const args = asRecord2(row.arguments);
|
|
9687
|
+
if (args)
|
|
9688
|
+
entry.arguments = args;
|
|
9689
|
+
else if (name.startsWith("mcp__"))
|
|
9690
|
+
entry.arguments = row;
|
|
9691
|
+
}
|
|
9692
|
+
function blocksToDigest(content, maxTextChars) {
|
|
9693
|
+
const texts = [];
|
|
9694
|
+
const tools = [];
|
|
9695
|
+
let resultChars = 0;
|
|
9696
|
+
const blocks = Array.isArray(content) ? content : typeof content === "string" && content ? [{ type: "text", text: content }] : [];
|
|
9697
|
+
for (const block2 of blocks) {
|
|
9698
|
+
const item = asRecord2(block2);
|
|
9699
|
+
if (!item)
|
|
9700
|
+
continue;
|
|
9701
|
+
const btype = item.type;
|
|
9702
|
+
if ((btype === "text" || btype === "input_text" || btype === "output_text") && typeof item.text === "string" && item.text) {
|
|
9703
|
+
let t2 = item.text;
|
|
9704
|
+
if (t2.length > maxTextChars)
|
|
9705
|
+
t2 = `${t2.slice(0, maxTextChars)}
|
|
9706
|
+
…[truncated]`;
|
|
9707
|
+
texts.push(t2);
|
|
9708
|
+
} else if (btype === "tool_use" && typeof item.name === "string") {
|
|
9709
|
+
const entry = { name: item.name };
|
|
9710
|
+
const inp = item.input ?? {};
|
|
9711
|
+
copyToolInputFields(entry, inp);
|
|
9712
|
+
copyMcpFields(entry, item.name, inp);
|
|
9713
|
+
tools.push(entry);
|
|
9714
|
+
} else if (btype === "tool_result") {
|
|
9715
|
+
const c2 = item.content;
|
|
9716
|
+
if (typeof c2 === "string")
|
|
9717
|
+
resultChars += c2.length;
|
|
9718
|
+
else if (c2 != null)
|
|
9719
|
+
resultChars += JSON.stringify(c2).length;
|
|
9720
|
+
tools.push({ name: "tool_result" });
|
|
9721
|
+
}
|
|
9722
|
+
}
|
|
9723
|
+
return { texts, tools, resultChars };
|
|
9724
|
+
}
|
|
9725
|
+
function pushTurn2(turns, line, role, texts, tools, resultChars) {
|
|
9726
|
+
if (role === "user" && texts.length > 0) {
|
|
9727
|
+
const qs = extractUserQueries(texts.join(`
|
|
9728
|
+
`));
|
|
9729
|
+
if (qs.length > 0)
|
|
9730
|
+
texts.splice(0, texts.length, ...qs);
|
|
9731
|
+
}
|
|
9732
|
+
if (role === "user" && texts.length === 0)
|
|
9733
|
+
return;
|
|
9734
|
+
if (texts.length === 0 && tools.length === 0)
|
|
9735
|
+
return;
|
|
9736
|
+
turns.push({
|
|
9737
|
+
line,
|
|
9738
|
+
role,
|
|
9739
|
+
text: texts,
|
|
9740
|
+
tools,
|
|
9741
|
+
est_tokens: digestEstTokens(texts, tools, resultChars)
|
|
9742
|
+
});
|
|
9743
|
+
}
|
|
9744
|
+
function digestCursor(raw) {
|
|
9745
|
+
const turns = [];
|
|
9746
|
+
const lines = raw.split(`
|
|
9747
|
+
`);
|
|
9748
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
9749
|
+
const line = lines[i2].trim();
|
|
9750
|
+
if (!line)
|
|
9751
|
+
continue;
|
|
9752
|
+
let obj;
|
|
9753
|
+
try {
|
|
9754
|
+
const parsed = asRecord2(JSON.parse(line));
|
|
9755
|
+
if (!parsed)
|
|
9756
|
+
continue;
|
|
9757
|
+
obj = parsed;
|
|
9758
|
+
} catch {
|
|
9759
|
+
continue;
|
|
9760
|
+
}
|
|
9761
|
+
const role = obj.role;
|
|
9762
|
+
if (role !== "user" && role !== "assistant")
|
|
9763
|
+
continue;
|
|
9764
|
+
const message = asRecord2(obj.message);
|
|
9765
|
+
const { texts, tools, resultChars } = blocksToDigest(message?.content, role === "assistant" ? MAX_ASSISTANT_CHARS : 2000);
|
|
9766
|
+
pushTurn2(turns, i2 + 1, role, texts, tools, resultChars);
|
|
9767
|
+
}
|
|
9768
|
+
return turns;
|
|
9769
|
+
}
|
|
9770
|
+
function digestClaude(raw) {
|
|
9771
|
+
const turns = [];
|
|
9772
|
+
const lines = raw.split(`
|
|
9773
|
+
`);
|
|
9774
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
9775
|
+
const line = lines[i2].trim();
|
|
9776
|
+
if (!line)
|
|
9777
|
+
continue;
|
|
9778
|
+
let obj;
|
|
9779
|
+
try {
|
|
9780
|
+
const parsed = asRecord2(JSON.parse(line));
|
|
9781
|
+
if (!parsed)
|
|
9782
|
+
continue;
|
|
9783
|
+
obj = parsed;
|
|
9784
|
+
} catch {
|
|
9785
|
+
continue;
|
|
9786
|
+
}
|
|
9787
|
+
const etype = obj.type;
|
|
9788
|
+
if (etype !== "user" && etype !== "assistant")
|
|
9789
|
+
continue;
|
|
9790
|
+
if (obj.isSidechain === true)
|
|
9791
|
+
continue;
|
|
9792
|
+
const message = asRecord2(obj.message);
|
|
9793
|
+
const { texts, tools, resultChars } = blocksToDigest(message?.content, etype === "assistant" ? MAX_ASSISTANT_CHARS : 2000);
|
|
9794
|
+
pushTurn2(turns, i2 + 1, etype, texts, tools, resultChars);
|
|
9795
|
+
}
|
|
9796
|
+
return turns;
|
|
9797
|
+
}
|
|
9798
|
+
function digestCodex(raw) {
|
|
9799
|
+
const turns = [];
|
|
9800
|
+
const lines = raw.split(`
|
|
9801
|
+
`);
|
|
9802
|
+
for (let i2 = 0;i2 < lines.length; i2++) {
|
|
9803
|
+
const line = lines[i2].trim();
|
|
9804
|
+
if (!line)
|
|
9805
|
+
continue;
|
|
9806
|
+
let obj;
|
|
9807
|
+
try {
|
|
9808
|
+
const parsed = asRecord2(JSON.parse(line));
|
|
9809
|
+
if (!parsed)
|
|
9810
|
+
continue;
|
|
9811
|
+
obj = parsed;
|
|
9812
|
+
} catch {
|
|
9813
|
+
continue;
|
|
9814
|
+
}
|
|
9815
|
+
const payload = asRecord2(obj.payload) ?? {};
|
|
9816
|
+
const etype = obj.type;
|
|
9817
|
+
if (etype === "event_msg" && payload.type === "user_message") {
|
|
9818
|
+
const msg = String(payload.message ?? "").slice(0, 2000);
|
|
9819
|
+
if (msg) {
|
|
9820
|
+
turns.push({
|
|
9821
|
+
line: i2 + 1,
|
|
9822
|
+
role: "user",
|
|
9823
|
+
text: [msg],
|
|
9824
|
+
tools: [],
|
|
9825
|
+
est_tokens: estimateTokens(msg)
|
|
9826
|
+
});
|
|
9827
|
+
}
|
|
9828
|
+
} else if (etype === "event_msg" && payload.type === "agent_message") {
|
|
9829
|
+
let msg = String(payload.message ?? "");
|
|
9830
|
+
if (msg.length > MAX_ASSISTANT_CHARS)
|
|
9831
|
+
msg = `${msg.slice(0, MAX_ASSISTANT_CHARS)}
|
|
9832
|
+
…[truncated]`;
|
|
9833
|
+
if (msg) {
|
|
9834
|
+
turns.push({
|
|
9835
|
+
line: i2 + 1,
|
|
9836
|
+
role: "assistant",
|
|
9837
|
+
text: [msg],
|
|
9838
|
+
tools: [],
|
|
9839
|
+
est_tokens: estimateTokens(msg)
|
|
9840
|
+
});
|
|
9841
|
+
}
|
|
9842
|
+
} else if (etype === "response_item" && payload.type === "function_call") {
|
|
9843
|
+
const name = typeof payload.name === "string" ? payload.name : "function_call";
|
|
9844
|
+
const args = payload.arguments;
|
|
9845
|
+
const preview = typeof args === "string" ? args.slice(0, 160) : JSON.stringify(args ?? "").slice(0, 160);
|
|
9846
|
+
turns.push({
|
|
9847
|
+
line: i2 + 1,
|
|
9848
|
+
role: "assistant",
|
|
9849
|
+
text: [],
|
|
9850
|
+
tools: [{ name, command_preview: preview }],
|
|
9851
|
+
est_tokens: estimateTokens(preview)
|
|
9852
|
+
});
|
|
9853
|
+
}
|
|
9854
|
+
}
|
|
9855
|
+
return turns;
|
|
9856
|
+
}
|
|
9857
|
+
function sessionToDigest(session) {
|
|
9858
|
+
try {
|
|
9859
|
+
switch (session.harness) {
|
|
9860
|
+
case "claude":
|
|
9861
|
+
return { turns: digestClaude(readFileSync11(session.path, "utf8")) };
|
|
9862
|
+
case "cursor":
|
|
9863
|
+
return { turns: digestCursor(readFileSync11(session.path, "utf8")) };
|
|
9864
|
+
case "codex":
|
|
9865
|
+
return { turns: digestCodex(readFileSync11(session.path, "utf8")) };
|
|
9866
|
+
case "opencode": {
|
|
9867
|
+
const turns = readSessionTurns(session).map((turn, index) => ({
|
|
9868
|
+
role: turn.role,
|
|
9869
|
+
line: index + 1,
|
|
9870
|
+
est_tokens: Math.round(turn.text.length / CHARS_PER_TOKEN3),
|
|
9871
|
+
text: [turn.text],
|
|
9872
|
+
tools: []
|
|
9873
|
+
}));
|
|
9874
|
+
return { turns };
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
} catch {
|
|
9878
|
+
return { turns: [] };
|
|
9879
|
+
}
|
|
9880
|
+
}
|
|
9881
|
+
function digestsForSessions(sessions) {
|
|
9882
|
+
const out = {};
|
|
9883
|
+
for (const session of sessions) {
|
|
9884
|
+
out[session.id] = sessionToDigest(session);
|
|
9885
|
+
}
|
|
9886
|
+
return out;
|
|
9887
|
+
}
|
|
9888
|
+
function digestTurnText(turn) {
|
|
9889
|
+
const texts = turn.text ?? [];
|
|
9890
|
+
if (typeof texts === "string")
|
|
9891
|
+
return texts;
|
|
9892
|
+
return texts.filter(Boolean).join(`
|
|
9893
|
+
`);
|
|
9894
|
+
}
|
|
9895
|
+
var CHARS_PER_TOKEN3 = 4, MAX_ASSISTANT_CHARS = 4000, MCP_DIGEST_TOOLS;
|
|
9896
|
+
var init_digest = __esm(() => {
|
|
9897
|
+
init_read();
|
|
9898
|
+
init_queries();
|
|
9899
|
+
MCP_DIGEST_TOOLS = new Set(["CallMcpTool", "GetMcpTools"]);
|
|
9900
|
+
});
|
|
9901
|
+
|
|
9902
|
+
// src/report/notes.ts
|
|
9903
|
+
function sessionIdFromReadInput(input) {
|
|
9904
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
9905
|
+
return;
|
|
9906
|
+
const id = input.id;
|
|
9907
|
+
return typeof id === "string" && id.trim() ? id : undefined;
|
|
9908
|
+
}
|
|
9909
|
+
function significantWords(text) {
|
|
9910
|
+
const out = new Set;
|
|
9911
|
+
for (const raw of text.toLowerCase().match(/[a-z][a-z0-9._-]{2,}/g) ?? []) {
|
|
9912
|
+
const word = raw.replace(/^\.+|\.+$/g, "");
|
|
9913
|
+
if (word.length >= 3 && !STOP_WORDS.has(word))
|
|
9914
|
+
out.add(word);
|
|
9915
|
+
}
|
|
9916
|
+
return out;
|
|
9917
|
+
}
|
|
9918
|
+
function overlapCount(a3, b2) {
|
|
9919
|
+
let n2 = 0;
|
|
9920
|
+
for (const word of a3)
|
|
9921
|
+
if (b2.has(word))
|
|
9922
|
+
n2 += 1;
|
|
9923
|
+
return n2;
|
|
9924
|
+
}
|
|
9925
|
+
function noteWords(note) {
|
|
9926
|
+
return { title: significantWords(note.title), content: significantWords(note.content) };
|
|
9927
|
+
}
|
|
9928
|
+
function sessionCycles(turns) {
|
|
9929
|
+
const starts = [];
|
|
9930
|
+
for (let i2 = 0;i2 < turns.length; i2++) {
|
|
9931
|
+
if (turns[i2].role === "user" && digestTurnText(turns[i2]).trim())
|
|
9932
|
+
starts.push(i2);
|
|
9933
|
+
}
|
|
9934
|
+
return starts.map((start, i2) => {
|
|
9935
|
+
const end = i2 + 1 < starts.length ? starts[i2 + 1] : turns.length;
|
|
9936
|
+
const text = turns.slice(start, end).map((t2) => {
|
|
9937
|
+
const paths = (t2.tools ?? []).map((tool) => tool.path || tool.pattern || tool.command_preview || "").filter(Boolean).join(" ");
|
|
9938
|
+
return `${digestTurnText(t2)} ${paths}`;
|
|
9939
|
+
}).join(`
|
|
9940
|
+
`);
|
|
9941
|
+
return { start, end, text };
|
|
9942
|
+
});
|
|
9943
|
+
}
|
|
9944
|
+
function cycleMatchScore(words, cycleText) {
|
|
9945
|
+
const cycleWords = significantWords(cycleText);
|
|
9946
|
+
return overlapCount(words.title, cycleWords) * 2 + overlapCount(words.content, cycleWords);
|
|
9947
|
+
}
|
|
9948
|
+
function cleanedUserQuery(text) {
|
|
9949
|
+
const queries = extractUserQueries(text);
|
|
9950
|
+
return (queries[0] ?? text).replace(/\s+/g, " ").trim();
|
|
9951
|
+
}
|
|
9952
|
+
function attributeRediscovery(notes, sessions) {
|
|
9953
|
+
const byId = new Map(sessions.map((s) => [s.id, s]));
|
|
9954
|
+
const candidates = notes.map((note) => ({
|
|
9955
|
+
title: note.title,
|
|
9956
|
+
content: note.content,
|
|
9957
|
+
transcript_id: note.transcript_id,
|
|
9958
|
+
repo: note.repo,
|
|
9959
|
+
branch: note.branch,
|
|
9960
|
+
status: "written"
|
|
9961
|
+
}));
|
|
9962
|
+
const indexesBySession = new Map;
|
|
9963
|
+
for (let i2 = 0;i2 < notes.length; i2++) {
|
|
9964
|
+
const id = notes[i2].transcript_id;
|
|
9965
|
+
if (!id || !byId.has(id))
|
|
9966
|
+
continue;
|
|
9967
|
+
const list = indexesBySession.get(id) ?? [];
|
|
9968
|
+
list.push(i2);
|
|
9969
|
+
indexesBySession.set(id, list);
|
|
9970
|
+
}
|
|
9971
|
+
for (const [sessionId, indexes] of indexesBySession) {
|
|
9972
|
+
const session = byId.get(sessionId);
|
|
9973
|
+
if (!session)
|
|
9974
|
+
continue;
|
|
9975
|
+
const turns = sessionToDigest(session).turns;
|
|
9976
|
+
const firstUser = turns.find((t2) => t2.role === "user");
|
|
9977
|
+
const firstText = firstUser ? digestTurnText(firstUser) : "";
|
|
9978
|
+
const sessionTitle = firstText ? sessionTitleFromUserText(firstText) : undefined;
|
|
9979
|
+
for (const i2 of indexes) {
|
|
9980
|
+
if (sessionTitle)
|
|
9981
|
+
candidates[i2].session_title = sessionTitle;
|
|
9982
|
+
if (firstText)
|
|
9983
|
+
candidates[i2].user_query = cleanedUserQuery(firstText).slice(0, 200);
|
|
9984
|
+
}
|
|
9985
|
+
if (turns.length === 0)
|
|
9986
|
+
continue;
|
|
9987
|
+
const cycles = sessionCycles(turns);
|
|
9988
|
+
const taken = new Set;
|
|
9989
|
+
const ranked = indexes.map((i2) => {
|
|
9990
|
+
const words = noteWords(notes[i2]);
|
|
9991
|
+
let bestScore = 0;
|
|
9992
|
+
for (const cycle of cycles) {
|
|
9993
|
+
const score = cycleMatchScore(words, cycle.text);
|
|
9994
|
+
if (score > bestScore)
|
|
9995
|
+
bestScore = score;
|
|
9996
|
+
}
|
|
9997
|
+
return { i: i2, words, bestScore };
|
|
9998
|
+
}).sort((a3, b2) => b2.bestScore - a3.bestScore);
|
|
9999
|
+
for (const item of ranked) {
|
|
10000
|
+
if (item.bestScore <= 0)
|
|
10001
|
+
continue;
|
|
10002
|
+
let pick = -1;
|
|
10003
|
+
let pickScore = 0;
|
|
10004
|
+
for (let ci = 0;ci < cycles.length; ci++) {
|
|
10005
|
+
if (taken.has(ci))
|
|
10006
|
+
continue;
|
|
10007
|
+
const score = cycleMatchScore(item.words, cycles[ci].text);
|
|
10008
|
+
if (score > pickScore) {
|
|
10009
|
+
pickScore = score;
|
|
10010
|
+
pick = ci;
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
if (pick < 0 || pickScore <= 0)
|
|
10014
|
+
continue;
|
|
10015
|
+
taken.add(pick);
|
|
10016
|
+
const cycle = cycles[pick];
|
|
10017
|
+
const slice = turns.slice(cycle.start, cycle.end);
|
|
10018
|
+
const tokens = slice.reduce((sum, t2) => sum + (Number(t2.est_tokens) || 0), 0);
|
|
10019
|
+
candidates[item.i].approx_rediscovery_tokens = tokens;
|
|
10020
|
+
const startLine = slice[0]?.line ?? cycle.start + 1;
|
|
10021
|
+
const endLine = slice.at(-1)?.line ?? cycle.end;
|
|
10022
|
+
candidates[item.i].investigation_lines = `${startLine}-${endLine}`;
|
|
10023
|
+
const cycleUser = slice.find((t2) => t2.role === "user");
|
|
10024
|
+
if (cycleUser) {
|
|
10025
|
+
const q = digestTurnText(cycleUser);
|
|
10026
|
+
candidates[item.i].user_query = cleanedUserQuery(q).slice(0, 200);
|
|
10027
|
+
candidates[item.i].session_title = sessionTitleFromUserText(q);
|
|
10028
|
+
}
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
10031
|
+
return candidates;
|
|
10032
|
+
}
|
|
10033
|
+
function sessionsToInventory(sessions) {
|
|
10034
|
+
const transcripts = sessions.map((session) => {
|
|
10035
|
+
const turns = readSessionTurns(session);
|
|
10036
|
+
const queries = turns.filter((t2) => t2.role === "user").flatMap((t2) => extractUserQueries(t2.text));
|
|
10037
|
+
const title = queries[0] ? sessionTitleFromUserText(queries[0]) : undefined;
|
|
10038
|
+
return {
|
|
10039
|
+
source: session.harness,
|
|
10040
|
+
transcript_id: session.id,
|
|
10041
|
+
...title ? { title } : {},
|
|
10042
|
+
learning_tokens: estimateSessionTokens(session),
|
|
10043
|
+
rediscovery_tool_calls: countRediscoveryToolCalls(session),
|
|
10044
|
+
user_queries: queries.slice(0, 3)
|
|
10045
|
+
};
|
|
10046
|
+
});
|
|
10047
|
+
const learning_tokens = transcripts.reduce((sum, t2) => sum + t2.learning_tokens, 0);
|
|
10048
|
+
return { transcripts, totals: { learning_tokens } };
|
|
10049
|
+
}
|
|
10050
|
+
var STOP_WORDS;
|
|
10051
|
+
var init_notes = __esm(() => {
|
|
10052
|
+
init_read();
|
|
10053
|
+
init_digest();
|
|
10054
|
+
init_queries();
|
|
10055
|
+
init_digest();
|
|
10056
|
+
STOP_WORDS = new Set([
|
|
10057
|
+
"about",
|
|
10058
|
+
"after",
|
|
10059
|
+
"also",
|
|
10060
|
+
"and",
|
|
10061
|
+
"are",
|
|
10062
|
+
"but",
|
|
10063
|
+
"can",
|
|
10064
|
+
"does",
|
|
10065
|
+
"for",
|
|
10066
|
+
"from",
|
|
10067
|
+
"have",
|
|
10068
|
+
"into",
|
|
10069
|
+
"just",
|
|
10070
|
+
"must",
|
|
10071
|
+
"not",
|
|
10072
|
+
"that",
|
|
10073
|
+
"the",
|
|
10074
|
+
"this",
|
|
10075
|
+
"they",
|
|
10076
|
+
"with",
|
|
10077
|
+
"what",
|
|
10078
|
+
"when",
|
|
10079
|
+
"which",
|
|
10080
|
+
"will",
|
|
10081
|
+
"your"
|
|
10082
|
+
]);
|
|
10083
|
+
});
|
|
10084
|
+
|
|
10085
|
+
// src/report/write.ts
|
|
10086
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10087
|
+
import { tmpdir } from "node:os";
|
|
10088
|
+
import { dirname as dirname4, join as join10, resolve } from "node:path";
|
|
10089
|
+
import { pathToFileURL } from "node:url";
|
|
10090
|
+
function defaultReportPath() {
|
|
10091
|
+
return join10(tmpdir(), "dosu-knowledge-report.html");
|
|
10092
|
+
}
|
|
10093
|
+
async function writeAndOpenReport(options) {
|
|
10094
|
+
const out = resolve(options.out ?? defaultReportPath());
|
|
10095
|
+
mkdirSync7(dirname4(out), { recursive: true });
|
|
10096
|
+
writeFileSync7(out, options.html, "utf8");
|
|
10097
|
+
if (options.open !== false) {
|
|
10098
|
+
const url = pathToFileURL(out).href;
|
|
10099
|
+
if (options.openUrl) {
|
|
10100
|
+
await options.openUrl(url);
|
|
10101
|
+
} else {
|
|
10102
|
+
const open2 = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
10103
|
+
await open2.default(url);
|
|
10104
|
+
}
|
|
10105
|
+
}
|
|
10106
|
+
return out;
|
|
10107
|
+
}
|
|
10108
|
+
var init_write = () => {};
|
|
10109
|
+
|
|
10110
|
+
// src/report/generate.ts
|
|
10111
|
+
function sessionsForNotes(notes, sessions) {
|
|
10112
|
+
const ids = new Set(notes.map((n2) => n2.transcript_id).filter((id) => Boolean(id)));
|
|
10113
|
+
return sessions.filter((s) => ids.has(s.id));
|
|
10114
|
+
}
|
|
10115
|
+
function noteTime(note) {
|
|
10116
|
+
const parsed = Date.parse(note.at ?? "");
|
|
10117
|
+
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10118
|
+
}
|
|
10119
|
+
async function emitKnowledgeReport(options = {}) {
|
|
10120
|
+
const cfg = loadConfig();
|
|
10121
|
+
const notes = [...options.notes ?? await (options.fetchNotes ?? fetchReportNotes)(cfg)].sort((a3, b2) => noteTime(a3) - noteTime(b2));
|
|
10122
|
+
const scanned = options.sessions ?? scanAgentSessions();
|
|
10123
|
+
const sessions = sessionsForNotes(notes, scanned);
|
|
10124
|
+
const candidates = attributeRediscovery(notes, sessions);
|
|
10125
|
+
const inventory = sessionsToInventory(sessions);
|
|
10126
|
+
const state = loadSyncState();
|
|
10127
|
+
if (inventory.totals && state.total_learning_tokens && !options.sessions) {
|
|
10128
|
+
inventory.totals.learning_tokens = state.total_learning_tokens;
|
|
10129
|
+
}
|
|
10130
|
+
const html = buildReportHtml({
|
|
10131
|
+
inventory,
|
|
10132
|
+
candidates,
|
|
10133
|
+
orgName: options.orgName ?? cfg.active_account?.target?.org_name ?? "Your team",
|
|
10134
|
+
repo: options.repo ?? notes.find((n2) => n2.repo)?.repo,
|
|
10135
|
+
branch: options.branch ?? notes.find((n2) => n2.branch)?.branch,
|
|
10136
|
+
generatedAt: options.generatedAt,
|
|
10137
|
+
digests: digestsForSessions(sessions)
|
|
10138
|
+
});
|
|
10139
|
+
return writeAndOpenReport({
|
|
10140
|
+
html,
|
|
10141
|
+
out: options.out ?? defaultReportPath(),
|
|
10142
|
+
open: options.open,
|
|
10143
|
+
openUrl: options.openUrl
|
|
10144
|
+
});
|
|
10145
|
+
}
|
|
10146
|
+
var init_generate = __esm(() => {
|
|
10147
|
+
init_config();
|
|
10148
|
+
init_scan();
|
|
10149
|
+
init_watermark();
|
|
10150
|
+
init_html();
|
|
10151
|
+
init_notes();
|
|
10152
|
+
init_write();
|
|
10153
|
+
});
|
|
10154
|
+
|
|
10155
|
+
// src/sessions/project-dir.ts
|
|
10156
|
+
import {
|
|
10157
|
+
closeSync as closeSync2,
|
|
10158
|
+
existsSync as existsSync11,
|
|
10159
|
+
mkdirSync as mkdirSync8,
|
|
10160
|
+
openSync as openSync2,
|
|
10161
|
+
readFileSync as readFileSync12,
|
|
10162
|
+
readSync,
|
|
10163
|
+
renameSync as renameSync3,
|
|
10164
|
+
statSync as statSync4,
|
|
10165
|
+
writeFileSync as writeFileSync8
|
|
10166
|
+
} from "node:fs";
|
|
10167
|
+
import { join as join11 } from "node:path";
|
|
10168
|
+
function cachePath(configDir) {
|
|
10169
|
+
return join11(configDir, CACHE_FILENAME2);
|
|
10170
|
+
}
|
|
10171
|
+
function loadCacheFile(configDir) {
|
|
10172
|
+
try {
|
|
10173
|
+
const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
|
|
10174
|
+
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
10175
|
+
return {};
|
|
10176
|
+
return raw.entries;
|
|
10177
|
+
} catch {
|
|
10178
|
+
return {};
|
|
10179
|
+
}
|
|
10180
|
+
}
|
|
10181
|
+
function saveCacheFile(configDir, entries) {
|
|
10182
|
+
try {
|
|
10183
|
+
if (!existsSync11(configDir))
|
|
10184
|
+
mkdirSync8(configDir, { recursive: true, mode: 448 });
|
|
10185
|
+
const path3 = cachePath(configDir);
|
|
10186
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
10187
|
+
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
10188
|
+
writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
|
|
10189
|
+
renameSync3(tmp, path3);
|
|
10190
|
+
} catch {}
|
|
10191
|
+
}
|
|
10192
|
+
function readHead(path3, bytes = HEAD_BYTES) {
|
|
10193
|
+
let fd;
|
|
10194
|
+
try {
|
|
10195
|
+
fd = openSync2(path3, "r");
|
|
10196
|
+
} catch {
|
|
10197
|
+
return null;
|
|
10198
|
+
}
|
|
10199
|
+
try {
|
|
10200
|
+
const buf = Buffer.alloc(bytes);
|
|
10201
|
+
const read = readSync(fd, buf, 0, bytes, 0);
|
|
10202
|
+
return buf.subarray(0, read).toString("utf-8");
|
|
10203
|
+
} catch {
|
|
10204
|
+
return null;
|
|
10205
|
+
} finally {
|
|
10206
|
+
closeSync2(fd);
|
|
10207
|
+
}
|
|
10208
|
+
}
|
|
10209
|
+
function cwdFromJsonlHead(text) {
|
|
10210
|
+
for (const line of text.split(`
|
|
10211
|
+
`).slice(0, HEAD_LINES)) {
|
|
10212
|
+
if (!line.includes('"cwd"'))
|
|
10213
|
+
continue;
|
|
10214
|
+
try {
|
|
10215
|
+
const obj = JSON.parse(line);
|
|
10216
|
+
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
10217
|
+
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
10218
|
+
return cwd;
|
|
10219
|
+
} catch {}
|
|
10220
|
+
}
|
|
10221
|
+
return null;
|
|
10222
|
+
}
|
|
10223
|
+
function unmungeSlug(slug, exists = existsSync11) {
|
|
10224
|
+
const tokens = slug.replace(/^-/, "").split("-");
|
|
10225
|
+
if (tokens.length === 0 || tokens[0] === "")
|
|
10226
|
+
return null;
|
|
10227
|
+
let budget = 5000;
|
|
10228
|
+
const walk = (prefix, index) => {
|
|
10229
|
+
if (budget-- <= 0)
|
|
10230
|
+
return null;
|
|
10231
|
+
if (index === tokens.length)
|
|
10232
|
+
return exists(prefix) ? prefix : null;
|
|
10233
|
+
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
10234
|
+
if (asSegment)
|
|
10235
|
+
return asSegment;
|
|
10236
|
+
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
10237
|
+
};
|
|
10238
|
+
return walk("", 0);
|
|
10239
|
+
}
|
|
10240
|
+
function fileMtime(path3) {
|
|
10241
|
+
try {
|
|
10242
|
+
return statSync4(path3).mtime.toISOString();
|
|
10243
|
+
} catch {
|
|
10244
|
+
return "";
|
|
10245
|
+
}
|
|
10246
|
+
}
|
|
10247
|
+
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
10248
|
+
const exists = deps.exists ?? existsSync11;
|
|
10249
|
+
const head = deps.readHead ?? readHead;
|
|
10250
|
+
const mtime = deps.mtime ?? fileMtime;
|
|
10251
|
+
const entries = loadCacheFile(configDir);
|
|
10252
|
+
let dirty = false;
|
|
10253
|
+
const compute = (session) => {
|
|
10254
|
+
switch (session.harness) {
|
|
10255
|
+
case "opencode":
|
|
10256
|
+
return session.project ?? null;
|
|
10257
|
+
case "claude": {
|
|
10258
|
+
const text = head(session.path);
|
|
10259
|
+
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
10260
|
+
if (cwd)
|
|
10261
|
+
return cwd;
|
|
10262
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10263
|
+
}
|
|
10264
|
+
case "codex": {
|
|
10265
|
+
const text = head(session.path);
|
|
10266
|
+
return text ? cwdFromJsonlHead(text) : null;
|
|
10267
|
+
}
|
|
10268
|
+
case "cursor":
|
|
10269
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10270
|
+
default:
|
|
10271
|
+
return null;
|
|
10272
|
+
}
|
|
10273
|
+
};
|
|
10274
|
+
return {
|
|
10275
|
+
resolve(session) {
|
|
10276
|
+
const key = `${session.harness}/${session.id}`;
|
|
10277
|
+
const cached = entries[key];
|
|
10278
|
+
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
10279
|
+
return cached.dir;
|
|
10280
|
+
}
|
|
10281
|
+
const dir = compute(session);
|
|
10282
|
+
entries[key] = { dir, mtime: mtime(session.path) };
|
|
10283
|
+
dirty = true;
|
|
10284
|
+
return dir;
|
|
10285
|
+
},
|
|
10286
|
+
flush() {
|
|
10287
|
+
if (!dirty)
|
|
10288
|
+
return;
|
|
10289
|
+
saveCacheFile(configDir, entries);
|
|
10290
|
+
dirty = false;
|
|
10291
|
+
}
|
|
10292
|
+
};
|
|
10293
|
+
}
|
|
10294
|
+
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
10295
|
+
var init_project_dir = __esm(() => {
|
|
10296
|
+
init_config();
|
|
10297
|
+
HEAD_BYTES = 128 * 1024;
|
|
10298
|
+
});
|
|
10299
|
+
|
|
10300
|
+
// src/sync/backlog.ts
|
|
10301
|
+
function listSessionBacklog() {
|
|
10302
|
+
try {
|
|
10303
|
+
const state = loadSyncState();
|
|
10304
|
+
let sessions = scanAgentSessions({});
|
|
10305
|
+
if (state.project_filter?.length) {
|
|
10306
|
+
const resolver = createProjectDirResolver();
|
|
10307
|
+
sessions = filterSessionsByProject(sessions, state.project_filter, resolver.resolve);
|
|
10308
|
+
resolver.flush();
|
|
10309
|
+
}
|
|
10310
|
+
const gate = gateSessions(sessions, state.watermark);
|
|
10311
|
+
return { queued: gate.ready.reverse(), open: gate.open.reverse() };
|
|
10312
|
+
} catch {
|
|
10313
|
+
return { queued: [], open: [] };
|
|
10314
|
+
}
|
|
10315
|
+
}
|
|
10316
|
+
var init_backlog = __esm(() => {
|
|
10317
|
+
init_project_dir();
|
|
10318
|
+
init_scan();
|
|
10319
|
+
init_watermark();
|
|
10320
|
+
});
|
|
10321
|
+
|
|
10322
|
+
// src/sync/lock.ts
|
|
10323
|
+
import { existsSync as existsSync12, readFileSync as readFileSync13, rmSync as rmSync2, statSync as statSync5, writeFileSync as writeFileSync9 } from "node:fs";
|
|
10324
|
+
import { join as join12 } from "node:path";
|
|
10325
|
+
function processAlive(pid) {
|
|
10326
|
+
try {
|
|
10327
|
+
process.kill(pid, 0);
|
|
10328
|
+
return true;
|
|
10329
|
+
} catch (err) {
|
|
10330
|
+
return err.code === "EPERM";
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
function lockPath(configDir = getConfigDir()) {
|
|
10334
|
+
return join12(configDir, LOCK_FILENAME);
|
|
10335
|
+
}
|
|
10336
|
+
function stopSyncRun(pid, configDir = getConfigDir(), kill = process.kill.bind(process)) {
|
|
10337
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
10338
|
+
return false;
|
|
10339
|
+
let killed = false;
|
|
10340
|
+
try {
|
|
10341
|
+
kill(-pid, "SIGTERM");
|
|
10342
|
+
killed = true;
|
|
10343
|
+
} catch {
|
|
10344
|
+
try {
|
|
10345
|
+
kill(pid, "SIGTERM");
|
|
10346
|
+
killed = true;
|
|
10347
|
+
} catch {}
|
|
10348
|
+
}
|
|
10349
|
+
try {
|
|
10350
|
+
const path3 = lockPath(configDir);
|
|
10351
|
+
if (existsSync12(path3) && readFileSync13(path3, "utf8") === String(pid)) {
|
|
10352
|
+
rmSync2(path3, { force: true });
|
|
10353
|
+
}
|
|
10354
|
+
} catch {}
|
|
10355
|
+
return killed;
|
|
10356
|
+
}
|
|
10357
|
+
function fileLock(configDir = getConfigDir(), now = () => new Date) {
|
|
10358
|
+
const path3 = lockPath(configDir);
|
|
10359
|
+
let held = false;
|
|
10360
|
+
const tryCreate = () => {
|
|
10361
|
+
try {
|
|
10362
|
+
writeFileSync9(path3, String(process.pid), { flag: "wx" });
|
|
10363
|
+
return true;
|
|
10364
|
+
} catch {
|
|
10365
|
+
return false;
|
|
10366
|
+
}
|
|
10367
|
+
};
|
|
10368
|
+
return {
|
|
10369
|
+
acquire() {
|
|
10370
|
+
if (tryCreate()) {
|
|
10371
|
+
held = true;
|
|
10372
|
+
return true;
|
|
10373
|
+
}
|
|
10374
|
+
try {
|
|
10375
|
+
const age = now().getTime() - statSync5(path3).mtimeMs;
|
|
10376
|
+
const holder = Number.parseInt(readFileSync13(path3, "utf8"), 10);
|
|
10377
|
+
const holderDead = Number.isInteger(holder) && holder > 0 && !processAlive(holder);
|
|
10378
|
+
if (age > STALE_LOCK_MS || holderDead) {
|
|
10379
|
+
rmSync2(path3, { force: true });
|
|
10380
|
+
held = tryCreate();
|
|
10381
|
+
return held;
|
|
10382
|
+
}
|
|
10383
|
+
} catch {}
|
|
10384
|
+
return false;
|
|
10385
|
+
},
|
|
10386
|
+
release() {
|
|
10387
|
+
if (!held)
|
|
10388
|
+
return;
|
|
10389
|
+
held = false;
|
|
10390
|
+
try {
|
|
10391
|
+
if (existsSync12(path3) && readFileSync13(path3, "utf8") === String(process.pid)) {
|
|
10392
|
+
rmSync2(path3, { force: true });
|
|
10393
|
+
}
|
|
10394
|
+
} catch {}
|
|
10395
|
+
}
|
|
10396
|
+
};
|
|
10397
|
+
}
|
|
10398
|
+
var LOCK_FILENAME = "knowledge-sync.lock", STALE_LOCK_MS;
|
|
10399
|
+
var init_lock = __esm(() => {
|
|
10400
|
+
init_config();
|
|
10401
|
+
STALE_LOCK_MS = 15 * 60 * 1000;
|
|
10402
|
+
});
|
|
10403
|
+
|
|
10404
|
+
// src/sync/status.ts
|
|
10405
|
+
import { readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
|
|
10406
|
+
function formatTokenCount(tokens) {
|
|
10407
|
+
if (tokens < 1000)
|
|
10408
|
+
return String(tokens);
|
|
10409
|
+
if (tokens < 1e6) {
|
|
10410
|
+
const k = tokens / 1000;
|
|
10411
|
+
return `~${k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, "")}k`;
|
|
10412
|
+
}
|
|
10413
|
+
const m3 = tokens / 1e6;
|
|
10414
|
+
return `~${m3 >= 100 ? Math.round(m3) : m3.toFixed(1).replace(/\.0$/, "")}M`;
|
|
10415
|
+
}
|
|
10416
|
+
function defaultIsProcessAlive2(pid) {
|
|
10417
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
10418
|
+
return false;
|
|
10419
|
+
try {
|
|
10420
|
+
process.kill(pid, 0);
|
|
10421
|
+
return true;
|
|
10422
|
+
} catch (err) {
|
|
10423
|
+
return err.code === "EPERM";
|
|
10424
|
+
}
|
|
10425
|
+
}
|
|
10426
|
+
function defaultReadLog() {
|
|
10427
|
+
try {
|
|
10428
|
+
return readFileSync14(logger.getLogPath(), "utf-8");
|
|
10429
|
+
} catch {
|
|
10430
|
+
return "";
|
|
10431
|
+
}
|
|
10432
|
+
}
|
|
10433
|
+
function readLockFile(configDir) {
|
|
10434
|
+
const path3 = lockPath(configDir);
|
|
10435
|
+
try {
|
|
10436
|
+
const pid = Number.parseInt(readFileSync14(path3, "utf-8"), 10);
|
|
10437
|
+
return { pid: Number.isNaN(pid) ? -1 : pid, mtime: statSync6(path3).mtime };
|
|
10438
|
+
} catch {
|
|
10439
|
+
return null;
|
|
10440
|
+
}
|
|
10441
|
+
}
|
|
10442
|
+
function recentSyncActivity(log3) {
|
|
10443
|
+
return log3.split(`
|
|
10444
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[miner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
10445
|
+
}
|
|
10446
|
+
function getSyncStatus(deps = {}) {
|
|
10447
|
+
const configDir = deps.configDir ?? getConfigDir();
|
|
10448
|
+
const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive2;
|
|
10449
|
+
const readLog = deps.readLog ?? defaultReadLog;
|
|
10450
|
+
const lock = readLockFile(configDir);
|
|
10451
|
+
const running = lock !== null && isProcessAlive(lock.pid);
|
|
10452
|
+
const state = loadSyncState(configDir);
|
|
10453
|
+
const retryAt = backoffUntil(state);
|
|
10454
|
+
return {
|
|
10455
|
+
running,
|
|
10456
|
+
...lock ? { pid: lock.pid, startedAt: lock.mtime.toISOString() } : {},
|
|
10457
|
+
...lock && !running ? { staleLock: true } : {},
|
|
10458
|
+
state,
|
|
10459
|
+
...retryAt ? { backoffUntil: retryAt.toISOString() } : {},
|
|
10460
|
+
recentActivity: recentSyncActivity(readLog())
|
|
10461
|
+
};
|
|
10462
|
+
}
|
|
10463
|
+
var ACTIVITY_LINES = 10;
|
|
10464
|
+
var init_status = __esm(() => {
|
|
10465
|
+
init_config();
|
|
10466
|
+
init_logger();
|
|
10467
|
+
init_lock();
|
|
10468
|
+
init_watermark();
|
|
8996
10469
|
});
|
|
8997
10470
|
|
|
8998
10471
|
// src/sync/sync.ts
|
|
@@ -9016,11 +10489,18 @@ async function runKnowledgeSync(options = {}) {
|
|
|
9016
10489
|
const now = deps.now ?? (() => new Date);
|
|
9017
10490
|
const state = loadState();
|
|
9018
10491
|
if (options.quiet) {
|
|
10492
|
+
if (state.paused) {
|
|
10493
|
+
logger.debug("sync", "skipping quiet sync: mining is paused");
|
|
10494
|
+
return { status: "skipped-paused", readySessions: 0, inFlightSessions: 0, sessions: [] };
|
|
10495
|
+
}
|
|
9019
10496
|
const retryAt = backoffUntil(state);
|
|
9020
10497
|
if (retryAt && now() < retryAt) {
|
|
9021
10498
|
logger.debug("sync", `skipping quiet sync: backoff until ${retryAt.toISOString()}`);
|
|
9022
10499
|
return { status: "skipped-backoff", readySessions: 0, inFlightSessions: 0, sessions: [] };
|
|
9023
10500
|
}
|
|
10501
|
+
} else if (state.paused) {
|
|
10502
|
+
delete state.paused;
|
|
10503
|
+
logger.debug("sync", "manual sync resumes paused mining");
|
|
9024
10504
|
}
|
|
9025
10505
|
let ready;
|
|
9026
10506
|
let open2;
|
|
@@ -9031,14 +10511,14 @@ async function runKnowledgeSync(options = {}) {
|
|
|
9031
10511
|
}));
|
|
9032
10512
|
let sessions = await listSessions();
|
|
9033
10513
|
if (state.project_filter?.length) {
|
|
9034
|
-
let
|
|
10514
|
+
let resolve2 = deps.resolveProjectDir;
|
|
9035
10515
|
let flush;
|
|
9036
|
-
if (!
|
|
10516
|
+
if (!resolve2) {
|
|
9037
10517
|
const resolver = createProjectDirResolver();
|
|
9038
|
-
|
|
10518
|
+
resolve2 = resolver.resolve;
|
|
9039
10519
|
flush = resolver.flush;
|
|
9040
10520
|
}
|
|
9041
|
-
sessions = filterSessionsByProject(sessions, state.project_filter,
|
|
10521
|
+
sessions = filterSessionsByProject(sessions, state.project_filter, resolve2);
|
|
9042
10522
|
flush?.();
|
|
9043
10523
|
logger.debug("sync", `project filter active: ${state.project_filter.join(", ")}`);
|
|
9044
10524
|
}
|
|
@@ -9075,6 +10555,13 @@ async function runKnowledgeSync(options = {}) {
|
|
|
9075
10555
|
return { status: "skipped-lock", ...base };
|
|
9076
10556
|
}
|
|
9077
10557
|
try {
|
|
10558
|
+
if (state.run?.pid !== process.pid) {
|
|
10559
|
+
state.run = {
|
|
10560
|
+
pid: process.pid,
|
|
10561
|
+
started_at: now().toISOString(),
|
|
10562
|
+
baseline_mined: state.total_mined ?? 0
|
|
10563
|
+
};
|
|
10564
|
+
}
|
|
9078
10565
|
const worthMining = deps.worthMining ?? isWorthMining;
|
|
9079
10566
|
const examined = [];
|
|
9080
10567
|
const batch = [];
|
|
@@ -9191,13 +10678,141 @@ var init_sync = __esm(() => {
|
|
|
9191
10678
|
init_watermark();
|
|
9192
10679
|
});
|
|
9193
10680
|
|
|
10681
|
+
// src/report/backfill.ts
|
|
10682
|
+
function noteTime2(at) {
|
|
10683
|
+
const parsed = Date.parse(at ?? "");
|
|
10684
|
+
return Number.isNaN(parsed) ? Number.NaN : parsed;
|
|
10685
|
+
}
|
|
10686
|
+
function correlateBackfill(input) {
|
|
10687
|
+
const batches = new Map;
|
|
10688
|
+
for (const e of input.ledger) {
|
|
10689
|
+
const members = batches.get(e.at) ?? [];
|
|
10690
|
+
members.push(e.session);
|
|
10691
|
+
batches.set(e.at, members);
|
|
10692
|
+
}
|
|
10693
|
+
const batchTimes = [...batches.keys()].map((at) => ({ at, t: Date.parse(at) })).filter((b2) => !Number.isNaN(b2.t));
|
|
10694
|
+
const sessionByKey = new Map(input.sessions.map((s) => [`${s.harness}/${s.id}`, s]));
|
|
10695
|
+
const cyclesCache = new Map;
|
|
10696
|
+
const cyclesFor = (session) => {
|
|
10697
|
+
let cycles = cyclesCache.get(session.id);
|
|
10698
|
+
if (!cycles) {
|
|
10699
|
+
cycles = sessionCycles(sessionToDigest(session).turns);
|
|
10700
|
+
cyclesCache.set(session.id, cycles);
|
|
10701
|
+
}
|
|
10702
|
+
return cycles;
|
|
10703
|
+
};
|
|
10704
|
+
const mappings = [];
|
|
10705
|
+
let ambiguous = 0;
|
|
10706
|
+
let noBatch = 0;
|
|
10707
|
+
for (const note of input.notes) {
|
|
10708
|
+
const time = noteTime2(note.at);
|
|
10709
|
+
if (Number.isNaN(time)) {
|
|
10710
|
+
noBatch += 1;
|
|
10711
|
+
continue;
|
|
10712
|
+
}
|
|
10713
|
+
let batchAt;
|
|
10714
|
+
let bestD = Number.POSITIVE_INFINITY;
|
|
10715
|
+
for (const b2 of batchTimes) {
|
|
10716
|
+
const d = Math.abs(b2.t - time);
|
|
10717
|
+
if (d <= BATCH_TOLERANCE_MS && d < bestD) {
|
|
10718
|
+
bestD = d;
|
|
10719
|
+
batchAt = b2.at;
|
|
10720
|
+
}
|
|
10721
|
+
}
|
|
10722
|
+
if (!batchAt) {
|
|
10723
|
+
noBatch += 1;
|
|
10724
|
+
continue;
|
|
10725
|
+
}
|
|
10726
|
+
const members = (batches.get(batchAt) ?? []).map((k) => sessionByKey.get(k)).filter((s) => s !== undefined);
|
|
10727
|
+
if (members.length === 0) {
|
|
10728
|
+
noBatch += 1;
|
|
10729
|
+
continue;
|
|
10730
|
+
}
|
|
10731
|
+
const words = noteWords(note);
|
|
10732
|
+
let best;
|
|
10733
|
+
let second = 0;
|
|
10734
|
+
for (const session of members) {
|
|
10735
|
+
let score = 0;
|
|
10736
|
+
for (const cycle of cyclesFor(session)) {
|
|
10737
|
+
const s = cycleMatchScore(words, cycle.text);
|
|
10738
|
+
if (s > score)
|
|
10739
|
+
score = s;
|
|
10740
|
+
}
|
|
10741
|
+
if (!best || score > best.score) {
|
|
10742
|
+
second = best?.score ?? 0;
|
|
10743
|
+
best = { session, score };
|
|
10744
|
+
} else if (score > second) {
|
|
10745
|
+
second = score;
|
|
10746
|
+
}
|
|
10747
|
+
}
|
|
10748
|
+
if (!best || best.score < MIN_SCORE || second > 0 && best.score < second * CLEAR_WIN_RATIO) {
|
|
10749
|
+
ambiguous += 1;
|
|
10750
|
+
continue;
|
|
10751
|
+
}
|
|
10752
|
+
mappings.push({ note_id: note.id, transcript_id: best.session.id });
|
|
10753
|
+
}
|
|
10754
|
+
return { mappings, ambiguous, noBatch };
|
|
10755
|
+
}
|
|
10756
|
+
var BATCH_TOLERANCE_MS, CLEAR_WIN_RATIO = 1.5, MIN_SCORE = 3;
|
|
10757
|
+
var init_backfill = __esm(() => {
|
|
10758
|
+
init_digest();
|
|
10759
|
+
init_notes();
|
|
10760
|
+
BATCH_TOLERANCE_MS = 30 * 60 * 1000;
|
|
10761
|
+
});
|
|
10762
|
+
|
|
10763
|
+
// src/report/backfill-run.ts
|
|
10764
|
+
var exports_backfill_run = {};
|
|
10765
|
+
__export(exports_backfill_run, {
|
|
10766
|
+
runBackfill: () => runBackfill
|
|
10767
|
+
});
|
|
10768
|
+
async function defaultFetchUnattributed(cfg) {
|
|
10769
|
+
const orgId = cfg.active_account?.target?.org_id;
|
|
10770
|
+
if (!orgId || !cfg.active_account?.session.access_token) {
|
|
10771
|
+
throw new Error("Not signed in. Run `dosu setup` to connect an account first.");
|
|
10772
|
+
}
|
|
10773
|
+
const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
|
|
10774
|
+
const client = createTypedClient2(cfg);
|
|
10775
|
+
const { notes } = await client.notes.listMine.query({ org_id: orgId, limit: 500 });
|
|
10776
|
+
return notes.filter((n2) => !n2.transcript_id).map((n2) => ({ id: n2.id, title: n2.title, content: n2.body, at: n2.created_at }));
|
|
10777
|
+
}
|
|
10778
|
+
async function defaultApply(cfg, mappings) {
|
|
10779
|
+
const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
|
|
10780
|
+
const client = createTypedClient2(cfg);
|
|
10781
|
+
let updated = 0;
|
|
10782
|
+
for (let i2 = 0;i2 < mappings.length; i2 += MUTATION_CHUNK) {
|
|
10783
|
+
const chunk = mappings.slice(i2, i2 + MUTATION_CHUNK);
|
|
10784
|
+
const res = await client.notes.attributeTranscripts.mutate({ mappings: chunk });
|
|
10785
|
+
updated += res.updated;
|
|
10786
|
+
}
|
|
10787
|
+
return updated;
|
|
10788
|
+
}
|
|
10789
|
+
async function runBackfill(deps = {}) {
|
|
10790
|
+
const cfg = (deps.loadCfg ?? loadConfig)();
|
|
10791
|
+
const notes = await (deps.fetchUnattributed ?? defaultFetchUnattributed)(cfg);
|
|
10792
|
+
if (notes.length === 0) {
|
|
10793
|
+
return { candidates: 0, mappings: [], ambiguous: 0, noBatch: 0, updated: 0 };
|
|
10794
|
+
}
|
|
10795
|
+
const ledger = (deps.ledger ?? (() => loadSyncState().mined_sessions ?? []))();
|
|
10796
|
+
const sessions = (deps.scan ?? scanAgentSessions)();
|
|
10797
|
+
const { mappings, ambiguous, noBatch } = correlateBackfill({ notes, ledger, sessions });
|
|
10798
|
+
const updated = mappings.length > 0 ? await (deps.apply ?? defaultApply)(cfg, mappings) : 0;
|
|
10799
|
+
return { candidates: notes.length, mappings, ambiguous, noBatch, updated };
|
|
10800
|
+
}
|
|
10801
|
+
var MUTATION_CHUNK = 500;
|
|
10802
|
+
var init_backfill_run = __esm(() => {
|
|
10803
|
+
init_config();
|
|
10804
|
+
init_scan();
|
|
10805
|
+
init_watermark();
|
|
10806
|
+
init_backfill();
|
|
10807
|
+
});
|
|
10808
|
+
|
|
9194
10809
|
// src/miner/config-dir.ts
|
|
9195
|
-
import { mkdtempSync, rmSync as rmSync3, writeFileSync as
|
|
9196
|
-
import { tmpdir } from "node:os";
|
|
9197
|
-
import { join as
|
|
10810
|
+
import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10811
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
10812
|
+
import { join as join13 } from "node:path";
|
|
9198
10813
|
function createRunConfigDir() {
|
|
9199
|
-
const path3 = mkdtempSync(
|
|
9200
|
-
|
|
10814
|
+
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-miner-"));
|
|
10815
|
+
writeFileSync10(join13(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
|
|
9201
10816
|
return {
|
|
9202
10817
|
path: path3,
|
|
9203
10818
|
cleanup() {
|
|
@@ -9208,7 +10823,7 @@ function createRunConfigDir() {
|
|
|
9208
10823
|
var init_config_dir = () => {};
|
|
9209
10824
|
|
|
9210
10825
|
// src/miner/conflicts.ts
|
|
9211
|
-
import { existsSync as existsSync13, readFileSync as
|
|
10826
|
+
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
|
|
9212
10827
|
function managedSettingsPaths(platform3 = process.platform) {
|
|
9213
10828
|
switch (platform3) {
|
|
9214
10829
|
case "darwin":
|
|
@@ -9241,7 +10856,7 @@ function detectSettingsConflicts(paths = managedSettingsPaths()) {
|
|
|
9241
10856
|
continue;
|
|
9242
10857
|
let settings2;
|
|
9243
10858
|
try {
|
|
9244
|
-
settings2 = JSON.parse(
|
|
10859
|
+
settings2 = JSON.parse(readFileSync15(file, "utf8"));
|
|
9245
10860
|
} catch {
|
|
9246
10861
|
conflicts.push({ file, keys: ["<unreadable or invalid JSON>"] });
|
|
9247
10862
|
continue;
|
|
@@ -9303,7 +10918,7 @@ var init_env = __esm(() => {
|
|
|
9303
10918
|
import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
|
|
9304
10919
|
import { createRequire as createRequire3 } from "node:module";
|
|
9305
10920
|
import { homedir as homedir4 } from "node:os";
|
|
9306
|
-
import { delimiter as delimiter2, dirname as
|
|
10921
|
+
import { delimiter as delimiter2, dirname as dirname5, join as join14 } from "node:path";
|
|
9307
10922
|
function binaryNames(platform3 = process.platform) {
|
|
9308
10923
|
return platform3 === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
9309
10924
|
}
|
|
@@ -9311,8 +10926,8 @@ function sdkNativeBinaryExists() {
|
|
|
9311
10926
|
try {
|
|
9312
10927
|
const requireRuntime = createRequire3(import.meta.url);
|
|
9313
10928
|
const pkg = `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}`;
|
|
9314
|
-
const pkgDir =
|
|
9315
|
-
return binaryNames().some((name) => existsSync14(
|
|
10929
|
+
const pkgDir = dirname5(requireRuntime.resolve(`${pkg}/package.json`));
|
|
10930
|
+
return binaryNames().some((name) => existsSync14(join14(pkgDir, name)));
|
|
9316
10931
|
} catch {
|
|
9317
10932
|
return false;
|
|
9318
10933
|
}
|
|
@@ -9328,10 +10943,10 @@ function isExecutable(path3) {
|
|
|
9328
10943
|
function findSystemClaude(env = process.env, homeDir = homedir4()) {
|
|
9329
10944
|
const names = binaryNames();
|
|
9330
10945
|
const dirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
|
|
9331
|
-
dirs.push(
|
|
10946
|
+
dirs.push(join14(homeDir, ".local", "bin"), join14(homeDir, ".claude", "local"));
|
|
9332
10947
|
for (const dir of dirs) {
|
|
9333
10948
|
for (const name of names) {
|
|
9334
|
-
const candidate =
|
|
10949
|
+
const candidate = join14(dir, name);
|
|
9335
10950
|
if (isExecutable(candidate))
|
|
9336
10951
|
return candidate;
|
|
9337
10952
|
}
|
|
@@ -9363,24 +10978,16 @@ function buildMinerPrompt(sessions) {
|
|
|
9363
10978
|
|
|
9364
10979
|
${list}
|
|
9365
10980
|
|
|
9366
|
-
|
|
10981
|
+
Work through them ONE AT A TIME: read a single session with read_session, decide what (if anything) is durable per your rules, dedupe against read_knowledge, and write that session's notes with write_knowledge BEFORE moving on to the next session. Each note is attributed to the session you read just before writing it, so do not read the next session until you have written this one's notes. When you are done, reply with a one-line summary: how many sessions you read and how many notes you wrote.`;
|
|
9367
10982
|
}
|
|
9368
10983
|
|
|
9369
10984
|
// src/miner/prompt-core.ts
|
|
9370
|
-
function extractMinerCore(markdown) {
|
|
9371
|
-
const start = markdown.indexOf(MINER_CORE_START);
|
|
9372
|
-
const end = markdown.indexOf(MINER_CORE_END);
|
|
9373
|
-
if (start === -1 || end === -1 || end <= start) {
|
|
9374
|
-
throw new Error(`miner-core markers not found (expected ${MINER_CORE_START} … ${MINER_CORE_END})`);
|
|
9375
|
-
}
|
|
9376
|
-
return markdown.slice(start + MINER_CORE_START.length, end).trim();
|
|
9377
|
-
}
|
|
9378
|
-
var MINER_CORE_START = "<!-- dosu:miner-core:start -->", MINER_CORE_END = "<!-- dosu:miner-core:end -->";
|
|
9379
|
-
|
|
9380
|
-
// src/miner/prompt-core.generated.ts
|
|
9381
10985
|
var MINER_CORE_RULES = `Rules — non-negotiable:
|
|
9382
|
-
1. Before writing anything, call read_knowledge with the candidate topic
|
|
9383
|
-
|
|
10986
|
+
1. Before writing anything, call read_knowledge with the candidate topic. If the same fact is
|
|
10987
|
+
already recorded, skip it — never write a duplicate or near-duplicate. If the topic exists but
|
|
10988
|
+
the session shows it has changed or been superseded, do write: a note stating the current state
|
|
10989
|
+
and what changed. Only restatements of already-recorded facts are duplicates; an update to an
|
|
10990
|
+
existing topic is not.
|
|
9384
10991
|
2. Write ONLY durable, non-obvious knowledge:
|
|
9385
10992
|
- decisions and their rationale (chose A over B because ...)
|
|
9386
10993
|
- non-obvious constraints (API, schema, RLS, feature flags, deploy quirks)
|
|
@@ -9408,43 +11015,6 @@ verified, omit both.
|
|
|
9408
11015
|
long verbatim transcript spans. Summarize in your own words.
|
|
9409
11016
|
9. A trivial session with no real user query is normal: skip it silently.`;
|
|
9410
11017
|
|
|
9411
|
-
// src/miner/prompt-source.ts
|
|
9412
|
-
import { readFileSync as readFileSync15 } from "node:fs";
|
|
9413
|
-
import { homedir as homedir5 } from "node:os";
|
|
9414
|
-
import { join as join14 } from "node:path";
|
|
9415
|
-
function candidateSkillPromptPaths(env = process.env) {
|
|
9416
|
-
const paths = [];
|
|
9417
|
-
if (env.DOSU_SKILL_REPO?.trim()) {
|
|
9418
|
-
paths.push(join14(env.DOSU_SKILL_REPO.trim(), "skills", SKILL_PROMPT_RELPATH));
|
|
9419
|
-
}
|
|
9420
|
-
paths.push(join14(homedir5(), ".agents", "skills", SKILL_PROMPT_RELPATH));
|
|
9421
|
-
const claudeConfigDir = env.CLAUDE_CONFIG_DIR?.trim() || join14(homedir5(), ".claude");
|
|
9422
|
-
paths.push(join14(claudeConfigDir, "skills", SKILL_PROMPT_RELPATH));
|
|
9423
|
-
paths.push(join14(homedir5(), ".codeium", "windsurf", "skills", SKILL_PROMPT_RELPATH));
|
|
9424
|
-
return paths;
|
|
9425
|
-
}
|
|
9426
|
-
function resolveMinerCoreRules(env = process.env) {
|
|
9427
|
-
for (const path3 of candidateSkillPromptPaths(env)) {
|
|
9428
|
-
let markdown;
|
|
9429
|
-
try {
|
|
9430
|
-
markdown = readFileSync15(path3, "utf-8");
|
|
9431
|
-
} catch {
|
|
9432
|
-
continue;
|
|
9433
|
-
}
|
|
9434
|
-
try {
|
|
9435
|
-
return { rules: extractMinerCore(markdown), source: path3 };
|
|
9436
|
-
} catch (err) {
|
|
9437
|
-
logger.debug("miner", `skipping unusable skill rules at ${path3}: ${err}`);
|
|
9438
|
-
}
|
|
9439
|
-
}
|
|
9440
|
-
return { rules: MINER_CORE_RULES, source: "bundled" };
|
|
9441
|
-
}
|
|
9442
|
-
var SKILL_PROMPT_RELPATH;
|
|
9443
|
-
var init_prompt_source = __esm(() => {
|
|
9444
|
-
init_logger();
|
|
9445
|
-
SKILL_PROMPT_RELPATH = join14("log-to-dosu-knowledge", "references", "miner-system-prompt.md");
|
|
9446
|
-
});
|
|
9447
|
-
|
|
9448
11018
|
// node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
9449
11019
|
var exports_sdk = {};
|
|
9450
11020
|
__export(exports_sdk, {
|
|
@@ -52024,7 +53594,7 @@ __export(exports_util, {
|
|
|
52024
53594
|
extend: () => extend,
|
|
52025
53595
|
explicitlyAborted: () => explicitlyAborted,
|
|
52026
53596
|
escapeRegex: () => escapeRegex,
|
|
52027
|
-
esc: () =>
|
|
53597
|
+
esc: () => esc2,
|
|
52028
53598
|
defineLazyInternal: () => defineLazyInternal,
|
|
52029
53599
|
defineLazy: () => defineLazy,
|
|
52030
53600
|
createTransparentProxy: () => createTransparentProxy,
|
|
@@ -52175,7 +53745,7 @@ function randomString(length = 10) {
|
|
|
52175
53745
|
}
|
|
52176
53746
|
return str;
|
|
52177
53747
|
}
|
|
52178
|
-
function
|
|
53748
|
+
function esc2(str) {
|
|
52179
53749
|
return JSON.stringify(str);
|
|
52180
53750
|
}
|
|
52181
53751
|
function slugify(input) {
|
|
@@ -55296,7 +56866,7 @@ var init_schemas = __esm(() => {
|
|
|
55296
56866
|
if (key === "__proto__")
|
|
55297
56867
|
continue;
|
|
55298
56868
|
const id2 = ids[key];
|
|
55299
|
-
const k2 = typeof key === "symbol" ? `syms[${syms.indexOf(key)}]` :
|
|
56869
|
+
const k2 = typeof key === "symbol" ? `syms[${syms.indexOf(key)}]` : esc2(key);
|
|
55300
56870
|
const isPresent = `${k2} in input`;
|
|
55301
56871
|
const schema = shape[key];
|
|
55302
56872
|
const optin = schema?._zod?.optin;
|
|
@@ -64533,16 +66103,16 @@ function generateStringFormatCheck(doc, ctx, def, accessor) {
|
|
|
64533
66103
|
doc.write(`if (${accessor} !== ${accessor}.toUpperCase()) return INVALID;`);
|
|
64534
66104
|
break;
|
|
64535
66105
|
case "includes":
|
|
64536
|
-
doc.write(`if (!${accessor}.includes(${
|
|
66106
|
+
doc.write(`if (!${accessor}.includes(${esc2(def.includes)})) return INVALID;`);
|
|
64537
66107
|
break;
|
|
64538
66108
|
case "starts_with": {
|
|
64539
66109
|
const prefix = def.prefix;
|
|
64540
|
-
doc.write(`if (${accessor}.slice(0, ${prefix.length}) !== ${
|
|
66110
|
+
doc.write(`if (${accessor}.slice(0, ${prefix.length}) !== ${esc2(prefix)}) return INVALID;`);
|
|
64541
66111
|
break;
|
|
64542
66112
|
}
|
|
64543
66113
|
case "ends_with": {
|
|
64544
66114
|
const suffix = def.suffix;
|
|
64545
|
-
doc.write(`if (${accessor}.slice(-${suffix.length}) !== ${
|
|
66115
|
+
doc.write(`if (${accessor}.slice(-${suffix.length}) !== ${esc2(suffix)}) return INVALID;`);
|
|
64546
66116
|
break;
|
|
64547
66117
|
}
|
|
64548
66118
|
default: {
|
|
@@ -64746,8 +66316,8 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
64746
66316
|
const keys = Object.keys(shape);
|
|
64747
66317
|
const symbolKeys = Object.getOwnPropertySymbols(shape);
|
|
64748
66318
|
const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys;
|
|
64749
|
-
const keyExpr = (k2) => typeof k2 === "symbol" ? addConstant(ctx, k2) :
|
|
64750
|
-
const propKey = (k2) => typeof k2 === "symbol" ? `[${keyExpr(k2)}]` :
|
|
66319
|
+
const keyExpr = (k2) => typeof k2 === "symbol" ? addConstant(ctx, k2) : esc2(k2);
|
|
66320
|
+
const propKey = (k2) => typeof k2 === "symbol" ? `[${keyExpr(k2)}]` : esc2(k2);
|
|
64751
66321
|
const propShape = shape;
|
|
64752
66322
|
if (keys.includes("__proto__")) {
|
|
64753
66323
|
throw new ZodCompileUnsupportedError('object shape key "__proto__"');
|
|
@@ -64791,7 +66361,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
|
|
|
64791
66361
|
if (catchall) {
|
|
64792
66362
|
const catchallType = catchall._zod.def.type;
|
|
64793
66363
|
if (catchallType === "never") {
|
|
64794
|
-
const condition = keys.map((k2) => `k !== ${
|
|
66364
|
+
const condition = keys.map((k2) => `k !== ${esc2(k2)}`).join(" && ") || "true";
|
|
64795
66365
|
doc.write(`for (const k in ${accessor}) {`);
|
|
64796
66366
|
doc.indented((d3) => {
|
|
64797
66367
|
d3.write(`if (${condition}) return INVALID;`);
|
|
@@ -65053,7 +66623,7 @@ function generateLiteralCheck(doc, ctx, schema, accessor) {
|
|
|
65053
66623
|
return accessor;
|
|
65054
66624
|
}
|
|
65055
66625
|
if (typeof value === "string") {
|
|
65056
|
-
doc.write(`if (${accessor} !== ${
|
|
66626
|
+
doc.write(`if (${accessor} !== ${esc2(value)}) return INVALID;`);
|
|
65057
66627
|
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
65058
66628
|
doc.write(`if (${accessor} !== ${value}) return INVALID;`);
|
|
65059
66629
|
} else if (value === null) {
|
|
@@ -65254,7 +66824,7 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
|
|
|
65254
66824
|
}
|
|
65255
66825
|
const discVar = newVar(ctx);
|
|
65256
66826
|
const outputVar = newVar(ctx);
|
|
65257
|
-
doc.write(`const ${discVar} = ${accessor}?.[${
|
|
66827
|
+
doc.write(`const ${discVar} = ${accessor}?.[${esc2(def.discriminator)}];`);
|
|
65258
66828
|
doc.write(`let ${outputVar};`);
|
|
65259
66829
|
let firstBranch = true;
|
|
65260
66830
|
const claimed = new Set;
|
|
@@ -65284,7 +66854,7 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
|
|
|
65284
66854
|
}
|
|
65285
66855
|
function literalEquality(ctx, accessor, value) {
|
|
65286
66856
|
if (typeof value === "string")
|
|
65287
|
-
return `${accessor} === ${
|
|
66857
|
+
return `${accessor} === ${esc2(value)}`;
|
|
65288
66858
|
if (typeof value === "number") {
|
|
65289
66859
|
if (Number.isNaN(value))
|
|
65290
66860
|
return `Number.isNaN(${accessor})`;
|
|
@@ -65398,7 +66968,7 @@ function generateRecordCheck(doc, ctx, schema, accessor) {
|
|
|
65398
66968
|
}
|
|
65399
66969
|
function literalPropertyKey(ctx, key) {
|
|
65400
66970
|
if (typeof key === "string")
|
|
65401
|
-
return
|
|
66971
|
+
return esc2(key);
|
|
65402
66972
|
return addConstant(ctx, key);
|
|
65403
66973
|
}
|
|
65404
66974
|
function generateMapCheck(doc, ctx, schema, accessor) {
|
|
@@ -71334,6 +72904,15 @@ async function runMiner(options) {
|
|
|
71334
72904
|
message: `Refusing to run: conflicting Claude Code settings would override the miner's auth (${detail})`
|
|
71335
72905
|
};
|
|
71336
72906
|
}
|
|
72907
|
+
const gatewayURL = options.gatewayURL ?? getLlmGatewayURL();
|
|
72908
|
+
if (!isAbsoluteHttpUrl(gatewayURL)) {
|
|
72909
|
+
return {
|
|
72910
|
+
outcome: "error",
|
|
72911
|
+
notesWritten: 0,
|
|
72912
|
+
turns: 0,
|
|
72913
|
+
message: "LLM gateway URL is not set. From source, use `bun run dev` (production endpoints) or `bun run dev:local` (local stack)."
|
|
72914
|
+
};
|
|
72915
|
+
}
|
|
71337
72916
|
const { query } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
|
|
71338
72917
|
const claudeExecutable = resolveClaudeExecutable();
|
|
71339
72918
|
if (claudeExecutable) {
|
|
@@ -71347,23 +72926,23 @@ async function runMiner(options) {
|
|
|
71347
72926
|
const abort = new AbortController;
|
|
71348
72927
|
const timer = setTimeout(() => abort.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
|
|
71349
72928
|
let notesWritten = 0;
|
|
72929
|
+
let currentSession;
|
|
72930
|
+
const readsSinceWrite = new Set;
|
|
71350
72931
|
let turns = 0;
|
|
71351
72932
|
const env = buildMinerEnv({
|
|
71352
72933
|
apiKey: options.apiKey,
|
|
71353
|
-
gatewayURL
|
|
72934
|
+
gatewayURL,
|
|
71354
72935
|
configDir: configDir.path,
|
|
71355
72936
|
runID,
|
|
71356
72937
|
trigger: options.trigger,
|
|
71357
72938
|
cliVersion: getVersionString(),
|
|
71358
72939
|
deploymentID: options.deploymentID
|
|
71359
72940
|
});
|
|
71360
|
-
const coreRules = resolveMinerCoreRules();
|
|
71361
|
-
logger.debug("miner", `write-knowledge rules source: ${coreRules.source}`);
|
|
71362
72941
|
try {
|
|
71363
72942
|
const run2 = query({
|
|
71364
72943
|
prompt: buildMinerPrompt(options.sessions),
|
|
71365
72944
|
options: {
|
|
71366
|
-
systemPrompt: buildMinerSystemPrompt(
|
|
72945
|
+
systemPrompt: buildMinerSystemPrompt(MINER_CORE_RULES),
|
|
71367
72946
|
env,
|
|
71368
72947
|
abortController: abort,
|
|
71369
72948
|
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
@@ -71392,6 +72971,13 @@ async function runMiner(options) {
|
|
|
71392
72971
|
message: `Tool ${toolName} is not permitted in mining runs.`
|
|
71393
72972
|
};
|
|
71394
72973
|
}
|
|
72974
|
+
if (toolName === `mcp__${SESSIONS_SERVER_NAME}__read_session`) {
|
|
72975
|
+
const id2 = sessionIdFromReadInput(input2);
|
|
72976
|
+
if (id2) {
|
|
72977
|
+
currentSession = id2;
|
|
72978
|
+
readsSinceWrite.add(id2);
|
|
72979
|
+
}
|
|
72980
|
+
}
|
|
71395
72981
|
if (toolName === `mcp__${KNOWLEDGE_SERVER_NAME}__write_knowledge`) {
|
|
71396
72982
|
if (notesWritten >= maxNotes) {
|
|
71397
72983
|
return {
|
|
@@ -71399,7 +72985,21 @@ async function runMiner(options) {
|
|
|
71399
72985
|
message: `Note cap reached (${maxNotes} per run); stop writing and summarize.`
|
|
71400
72986
|
};
|
|
71401
72987
|
}
|
|
72988
|
+
if (readsSinceWrite.size > 1) {
|
|
72989
|
+
readsSinceWrite.clear();
|
|
72990
|
+
currentSession = undefined;
|
|
72991
|
+
return {
|
|
72992
|
+
behavior: "deny",
|
|
72993
|
+
message: "Write each session's notes right after reading THAT session, before reading the next. You have read multiple sessions without writing, so a note cannot be attributed to one. Re-read only the session this note is about, then write it."
|
|
72994
|
+
};
|
|
72995
|
+
}
|
|
71402
72996
|
notesWritten += 1;
|
|
72997
|
+
readsSinceWrite.clear();
|
|
72998
|
+
const { transcript_id: _authoredByModel, ...clean } = input2;
|
|
72999
|
+
return {
|
|
73000
|
+
behavior: "allow",
|
|
73001
|
+
updatedInput: currentSession ? { ...clean, transcript_id: currentSession } : clean
|
|
73002
|
+
};
|
|
71403
73003
|
}
|
|
71404
73004
|
return { behavior: "allow", updatedInput: input2 };
|
|
71405
73005
|
}
|
|
@@ -71460,11 +73060,11 @@ var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLE
|
|
|
71460
73060
|
var init_runner = __esm(() => {
|
|
71461
73061
|
init_logger();
|
|
71462
73062
|
init_config_helpers();
|
|
73063
|
+
init_notes();
|
|
71463
73064
|
init_config_dir();
|
|
71464
73065
|
init_conflicts();
|
|
71465
73066
|
init_env();
|
|
71466
73067
|
init_executable();
|
|
71467
|
-
init_prompt_source();
|
|
71468
73068
|
init_tools();
|
|
71469
73069
|
DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1000;
|
|
71470
73070
|
GATEWAY_ERRORS = {
|
|
@@ -71484,7 +73084,7 @@ var init_runner = __esm(() => {
|
|
|
71484
73084
|
});
|
|
71485
73085
|
|
|
71486
73086
|
// src/version/skill-update-check.ts
|
|
71487
|
-
import { existsSync as existsSync17, mkdirSync as
|
|
73087
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "node:fs";
|
|
71488
73088
|
import { join as join20 } from "node:path";
|
|
71489
73089
|
function getCachePath2() {
|
|
71490
73090
|
return join20(getConfigDir(), CACHE_FILENAME3);
|
|
@@ -71507,9 +73107,9 @@ function writeSkillCache(cache) {
|
|
|
71507
73107
|
try {
|
|
71508
73108
|
const dir = getConfigDir();
|
|
71509
73109
|
if (!existsSync17(dir)) {
|
|
71510
|
-
|
|
73110
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
71511
73111
|
}
|
|
71512
|
-
|
|
73112
|
+
writeFileSync11(getCachePath2(), JSON.stringify(cache), { mode: 384 });
|
|
71513
73113
|
} catch {}
|
|
71514
73114
|
}
|
|
71515
73115
|
function clearInstalledSha() {
|
|
@@ -71582,7 +73182,7 @@ var init_skill_update_check = __esm(() => {
|
|
|
71582
73182
|
|
|
71583
73183
|
// src/commands/skill.ts
|
|
71584
73184
|
import { exec as exec2, execSync as execSync2 } from "node:child_process";
|
|
71585
|
-
import { homedir as
|
|
73185
|
+
import { homedir as homedir6 } from "node:os";
|
|
71586
73186
|
import { join as join21 } from "node:path";
|
|
71587
73187
|
function skillAgentIDsForProviders(providerIDs) {
|
|
71588
73188
|
return [
|
|
@@ -71594,17 +73194,17 @@ function skillInstallTargetForProvider(providerID) {
|
|
|
71594
73194
|
if (!agentID)
|
|
71595
73195
|
return null;
|
|
71596
73196
|
if (agentID === "claude-code") {
|
|
71597
|
-
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join21(
|
|
73197
|
+
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join21(homedir6(), ".claude");
|
|
71598
73198
|
return { path: join21(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
|
|
71599
73199
|
}
|
|
71600
73200
|
if (agentID === "windsurf") {
|
|
71601
73201
|
return {
|
|
71602
|
-
path: join21(
|
|
73202
|
+
path: join21(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
|
|
71603
73203
|
symlink: true
|
|
71604
73204
|
};
|
|
71605
73205
|
}
|
|
71606
73206
|
return {
|
|
71607
|
-
path: join21(
|
|
73207
|
+
path: join21(homedir6(), ".agents", "skills", SKILL_NAME),
|
|
71608
73208
|
symlink: false
|
|
71609
73209
|
};
|
|
71610
73210
|
}
|
|
@@ -71613,12 +73213,12 @@ function skillAgentArgs(providerIDs) {
|
|
|
71613
73213
|
return agents.map((agent) => `-a ${agent}`).join(" ");
|
|
71614
73214
|
}
|
|
71615
73215
|
function execQuiet(command) {
|
|
71616
|
-
return new Promise((
|
|
73216
|
+
return new Promise((resolve6, reject) => {
|
|
71617
73217
|
exec2(command, { windowsHide: true }, (error61) => {
|
|
71618
73218
|
if (error61)
|
|
71619
73219
|
reject(error61);
|
|
71620
73220
|
else
|
|
71621
|
-
|
|
73221
|
+
resolve6();
|
|
71622
73222
|
});
|
|
71623
73223
|
});
|
|
71624
73224
|
}
|
|
@@ -71765,11 +73365,11 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
71765
73365
|
import {
|
|
71766
73366
|
existsSync as existsSync18,
|
|
71767
73367
|
lstatSync as lstatSync2,
|
|
71768
|
-
mkdirSync as
|
|
73368
|
+
mkdirSync as mkdirSync11,
|
|
71769
73369
|
readFileSync as readFileSync19,
|
|
71770
73370
|
renameSync as renameSync5,
|
|
71771
73371
|
unlinkSync as unlinkSync3,
|
|
71772
|
-
writeFileSync as
|
|
73372
|
+
writeFileSync as writeFileSync12
|
|
71773
73373
|
} from "node:fs";
|
|
71774
73374
|
import { join as join22 } from "node:path";
|
|
71775
73375
|
function getTelemetrySettingsPath() {
|
|
@@ -71874,8 +73474,8 @@ function writeTelemetrySettings(settings2) {
|
|
|
71874
73474
|
const temporaryPath = `${path3}.${process.pid}.${randomUUID3()}.tmp`;
|
|
71875
73475
|
try {
|
|
71876
73476
|
if (!existsSync18(dir))
|
|
71877
|
-
|
|
71878
|
-
|
|
73477
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
73478
|
+
writeFileSync12(temporaryPath, `${JSON.stringify(settings2, null, 2)}
|
|
71879
73479
|
`, {
|
|
71880
73480
|
flag: "wx",
|
|
71881
73481
|
mode: 384
|
|
@@ -71919,14 +73519,14 @@ var init_settings = __esm(() => {
|
|
|
71919
73519
|
});
|
|
71920
73520
|
|
|
71921
73521
|
// src/telemetry/debug-id.ts
|
|
71922
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
73522
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "fa7cf492-7aa5-180c-6475-6e2164756e21";
|
|
71923
73523
|
|
|
71924
73524
|
// src/telemetry/telemetry.ts
|
|
71925
73525
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
71926
73526
|
import { existsSync as existsSync19 } from "node:fs";
|
|
71927
73527
|
import { request as httpRequest2 } from "node:http";
|
|
71928
73528
|
import { request as httpsRequest } from "node:https";
|
|
71929
|
-
import { isAbsolute as isAbsolute4, relative as relative2, resolve as
|
|
73529
|
+
import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve6, sep as sep5 } from "node:path";
|
|
71930
73530
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
71931
73531
|
function safeString(value, pattern, fallback) {
|
|
71932
73532
|
if (typeof value !== "string" || !pattern.test(value))
|
|
@@ -72052,7 +73652,7 @@ function parseOwnedStackLine(originalLine, packageRoot) {
|
|
|
72052
73652
|
}
|
|
72053
73653
|
if (!isAbsolute4(absolutePath))
|
|
72054
73654
|
return;
|
|
72055
|
-
const resolvedPath =
|
|
73655
|
+
const resolvedPath = resolve6(absolutePath);
|
|
72056
73656
|
const relativePath = relative2(packageRoot, resolvedPath);
|
|
72057
73657
|
if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep5}`) || isAbsolute4(relativePath)) {
|
|
72058
73658
|
return;
|
|
@@ -72290,7 +73890,7 @@ function fetchWithoutRedirect(url2, init = {}) {
|
|
|
72290
73890
|
} catch (error61) {
|
|
72291
73891
|
return Promise.reject(error61);
|
|
72292
73892
|
}
|
|
72293
|
-
return new Promise((
|
|
73893
|
+
return new Promise((resolve7, reject) => {
|
|
72294
73894
|
let request;
|
|
72295
73895
|
let response;
|
|
72296
73896
|
let settled = false;
|
|
@@ -72314,7 +73914,7 @@ function fetchWithoutRedirect(url2, init = {}) {
|
|
|
72314
73914
|
response?.destroy();
|
|
72315
73915
|
request?.socket?.destroy();
|
|
72316
73916
|
request?.destroy();
|
|
72317
|
-
|
|
73917
|
+
resolve7(value);
|
|
72318
73918
|
};
|
|
72319
73919
|
const onAbort = () => {
|
|
72320
73920
|
const error61 = new Error("telemetry request aborted");
|
|
@@ -73235,15 +74835,15 @@ var init_vscode = __esm(() => {
|
|
|
73235
74835
|
});
|
|
73236
74836
|
|
|
73237
74837
|
// src/mcp/providers/windsurf.ts
|
|
73238
|
-
import { homedir as
|
|
74838
|
+
import { homedir as homedir8 } from "node:os";
|
|
73239
74839
|
import { join as join35 } from "node:path";
|
|
73240
74840
|
var WindsurfProvider = () => createJSONProvider({
|
|
73241
74841
|
providerName: "Windsurf",
|
|
73242
74842
|
providerID: "windsurf",
|
|
73243
74843
|
local: false,
|
|
73244
74844
|
priorityValue: 9,
|
|
73245
|
-
paths: [join35(
|
|
73246
|
-
globalPath: join35(
|
|
74845
|
+
paths: [join35(homedir8(), ".codeium", "windsurf")],
|
|
74846
|
+
globalPath: join35(homedir8(), ".codeium", "windsurf", "mcp_config.json"),
|
|
73247
74847
|
topKey: "mcpServers"
|
|
73248
74848
|
});
|
|
73249
74849
|
var init_windsurf = __esm(() => {
|
|
@@ -73332,7 +74932,7 @@ var init_providers = __esm(() => {
|
|
|
73332
74932
|
});
|
|
73333
74933
|
|
|
73334
74934
|
// src/version/update-check.ts
|
|
73335
|
-
import { existsSync as existsSync22, mkdirSync as
|
|
74935
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "node:fs";
|
|
73336
74936
|
import { join as join37 } from "node:path";
|
|
73337
74937
|
function isValidVersion(value) {
|
|
73338
74938
|
return typeof value === "string" && SEMVER_PATTERN.test(value);
|
|
@@ -73375,9 +74975,9 @@ function writeCache(cache) {
|
|
|
73375
74975
|
try {
|
|
73376
74976
|
const dir = getConfigDir();
|
|
73377
74977
|
if (!existsSync22(dir)) {
|
|
73378
|
-
|
|
74978
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
73379
74979
|
}
|
|
73380
|
-
|
|
74980
|
+
writeFileSync13(getCachePath3(), JSON.stringify(cache), { mode: 384 });
|
|
73381
74981
|
} catch {}
|
|
73382
74982
|
}
|
|
73383
74983
|
async function fetchLatestVersion() {
|
|
@@ -73487,14 +75087,14 @@ var init_update_check = __esm(() => {
|
|
|
73487
75087
|
});
|
|
73488
75088
|
|
|
73489
75089
|
// src/rules/installer.ts
|
|
73490
|
-
import { existsSync as existsSync23, mkdirSync as
|
|
73491
|
-
import { homedir as
|
|
73492
|
-
import { dirname as
|
|
75090
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync22, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
|
|
75091
|
+
import { homedir as homedir9 } from "node:os";
|
|
75092
|
+
import { dirname as dirname9, join as join38 } from "node:path";
|
|
73493
75093
|
function claudeConfigDir() {
|
|
73494
|
-
return process.env.CLAUDE_CONFIG_DIR || join38(
|
|
75094
|
+
return process.env.CLAUDE_CONFIG_DIR || join38(homedir9(), ".claude");
|
|
73495
75095
|
}
|
|
73496
75096
|
function codexHome3() {
|
|
73497
|
-
return process.env.CODEX_HOME || join38(
|
|
75097
|
+
return process.env.CODEX_HOME || join38(homedir9(), ".codex");
|
|
73498
75098
|
}
|
|
73499
75099
|
function isRuleAgent(agent) {
|
|
73500
75100
|
return Object.hasOwn(RULE_TARGETS, agent);
|
|
@@ -73520,17 +75120,17 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
|
|
|
73520
75120
|
return normalizeRule(FALLBACK_DOSU_RULE);
|
|
73521
75121
|
}
|
|
73522
75122
|
function ensureParent(path3) {
|
|
73523
|
-
|
|
75123
|
+
mkdirSync13(dirname9(path3), { recursive: true });
|
|
73524
75124
|
}
|
|
73525
75125
|
function writeIfChanged(path3, content) {
|
|
73526
75126
|
if (!existsSync23(path3)) {
|
|
73527
75127
|
ensureParent(path3);
|
|
73528
|
-
|
|
75128
|
+
writeFileSync14(path3, content, "utf-8");
|
|
73529
75129
|
return "created";
|
|
73530
75130
|
}
|
|
73531
75131
|
if (readFileSync22(path3, "utf-8") === content)
|
|
73532
75132
|
return "unchanged";
|
|
73533
|
-
|
|
75133
|
+
writeFileSync14(path3, content, "utf-8");
|
|
73534
75134
|
return "updated";
|
|
73535
75135
|
}
|
|
73536
75136
|
function findSection(content) {
|
|
@@ -73559,7 +75159,7 @@ function upsertSection(path3, content) {
|
|
|
73559
75159
|
if (!existsSync23(path3)) {
|
|
73560
75160
|
const section2 = buildSection(content);
|
|
73561
75161
|
ensureParent(path3);
|
|
73562
|
-
|
|
75162
|
+
writeFileSync14(path3, `${section2}
|
|
73563
75163
|
`, "utf-8");
|
|
73564
75164
|
return "created";
|
|
73565
75165
|
}
|
|
@@ -73576,7 +75176,7 @@ function upsertSection(path3, content) {
|
|
|
73576
75176
|
}
|
|
73577
75177
|
if (next === existing)
|
|
73578
75178
|
return "unchanged";
|
|
73579
|
-
|
|
75179
|
+
writeFileSync14(path3, next, "utf-8");
|
|
73580
75180
|
return "updated";
|
|
73581
75181
|
}
|
|
73582
75182
|
function installRuleForAgent(agent, content) {
|
|
@@ -73608,7 +75208,7 @@ function removeRuleForAgent(agent) {
|
|
|
73608
75208
|
if (!next) {
|
|
73609
75209
|
unlinkSync4(path3);
|
|
73610
75210
|
} else {
|
|
73611
|
-
|
|
75211
|
+
writeFileSync14(path3, `${next}${lineEnding}`, "utf-8");
|
|
73612
75212
|
}
|
|
73613
75213
|
return { agent, path: path3, action: "removed" };
|
|
73614
75214
|
}
|
|
@@ -73640,7 +75240,7 @@ var init_installer = __esm(() => {
|
|
|
73640
75240
|
},
|
|
73641
75241
|
cursor: {
|
|
73642
75242
|
kind: "file",
|
|
73643
|
-
path: () => join38(
|
|
75243
|
+
path: () => join38(homedir9(), ".cursor", "rules", "dosu.mdc"),
|
|
73644
75244
|
cursorFrontmatter: true
|
|
73645
75245
|
},
|
|
73646
75246
|
codex: {
|
|
@@ -73649,22 +75249,22 @@ var init_installer = __esm(() => {
|
|
|
73649
75249
|
},
|
|
73650
75250
|
opencode: {
|
|
73651
75251
|
kind: "section",
|
|
73652
|
-
path: () => join38(
|
|
75252
|
+
path: () => join38(homedir9(), ".config", "opencode", "AGENTS.md")
|
|
73653
75253
|
},
|
|
73654
75254
|
gemini: {
|
|
73655
75255
|
kind: "section",
|
|
73656
|
-
path: () => join38(
|
|
75256
|
+
path: () => join38(homedir9(), ".gemini", "GEMINI.md")
|
|
73657
75257
|
},
|
|
73658
75258
|
antigravity: {
|
|
73659
75259
|
kind: "section",
|
|
73660
|
-
path: () => join38(
|
|
75260
|
+
path: () => join38(homedir9(), ".gemini", "GEMINI.md")
|
|
73661
75261
|
}
|
|
73662
75262
|
};
|
|
73663
75263
|
});
|
|
73664
75264
|
|
|
73665
75265
|
// src/setup/agents-md-step.ts
|
|
73666
75266
|
import { execSync as execSync3 } from "node:child_process";
|
|
73667
|
-
import { existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as
|
|
75267
|
+
import { existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
|
|
73668
75268
|
import { join as join39 } from "node:path";
|
|
73669
75269
|
function inGitWorkTree(cwd = process.cwd()) {
|
|
73670
75270
|
try {
|
|
@@ -73720,7 +75320,7 @@ function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RU
|
|
|
73720
75320
|
const path3 = join39(cwd, "AGENTS.md");
|
|
73721
75321
|
if (!existsSync24(path3)) {
|
|
73722
75322
|
const section2 = buildDosuAgentsSection(content);
|
|
73723
|
-
|
|
75323
|
+
writeFileSync15(path3, `${section2}
|
|
73724
75324
|
`);
|
|
73725
75325
|
return { path: path3, action: "created" };
|
|
73726
75326
|
}
|
|
@@ -73736,7 +75336,7 @@ function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RU
|
|
|
73736
75336
|
}
|
|
73737
75337
|
if (next === existing)
|
|
73738
75338
|
return { path: path3, action: "unchanged" };
|
|
73739
|
-
|
|
75339
|
+
writeFileSync15(path3, next);
|
|
73740
75340
|
return { path: path3, action: "updated" };
|
|
73741
75341
|
}
|
|
73742
75342
|
async function stepUpdateAgentsMd(cwd = process.cwd(), content) {
|
|
@@ -73832,6 +75432,8 @@ function reduceActivityViewKey(key) {
|
|
|
73832
75432
|
return "down";
|
|
73833
75433
|
if (key === "s")
|
|
73834
75434
|
return "sync";
|
|
75435
|
+
if (key === "f")
|
|
75436
|
+
return "full";
|
|
73835
75437
|
return "none";
|
|
73836
75438
|
}
|
|
73837
75439
|
function cycleTab(tab, delta = 1) {
|
|
@@ -73847,9 +75449,16 @@ function reduceSyncConfirmKey(key) {
|
|
|
73847
75449
|
return "cancel";
|
|
73848
75450
|
return "none";
|
|
73849
75451
|
}
|
|
73850
|
-
function
|
|
75452
|
+
function syncConfirmMode(status) {
|
|
75453
|
+
if (status.running)
|
|
75454
|
+
return "stop";
|
|
75455
|
+
if (status.state.paused)
|
|
75456
|
+
return "resume";
|
|
75457
|
+
return "start";
|
|
75458
|
+
}
|
|
75459
|
+
function formatActivityLine(line, width, full = false) {
|
|
73851
75460
|
const compact = stripAnsiCodes(line).replace(/^\[(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})[^\]]*\]/, (_match, _date2, time3) => time3);
|
|
73852
|
-
if (compact.length <= width)
|
|
75461
|
+
if (full || compact.length <= width)
|
|
73853
75462
|
return compact;
|
|
73854
75463
|
return `${compact.slice(0, Math.max(0, width - 1))}…`;
|
|
73855
75464
|
}
|
|
@@ -73861,34 +75470,33 @@ function appendSyncActivity(buffer, chunk, max = ACTIVITY_VIEW_BUFFER_LINES) {
|
|
|
73861
75470
|
function clip(text, max) {
|
|
73862
75471
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
73863
75472
|
}
|
|
73864
|
-
function formatMinedRow(record2) {
|
|
75473
|
+
function formatMinedRow(record2, full = false) {
|
|
73865
75474
|
const match = record2.at.match(/^\d{4}-(\d{2}-\d{2})T(\d{2}:\d{2})/);
|
|
73866
75475
|
const stamp = match ? `${match[1]} ${match[2]}` : record2.at;
|
|
73867
75476
|
const slash = record2.session.indexOf("/");
|
|
73868
75477
|
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
73869
75478
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
73870
|
-
const
|
|
73871
|
-
|
|
75479
|
+
const rawProject = record2.project ?? "-";
|
|
75480
|
+
const project = full ? rawProject : clip(rawProject, 28);
|
|
75481
|
+
return `${harness.padEnd(8)} ${stamp} ${project} ${full ? id2 : clip(id2, 24)}`;
|
|
73872
75482
|
}
|
|
73873
|
-
function formatQueuedRow(session) {
|
|
75483
|
+
function formatQueuedRow(session, full = false) {
|
|
73874
75484
|
const stamp = session.updated.replace("T", " ").slice(5, 16);
|
|
73875
|
-
const
|
|
73876
|
-
|
|
73877
|
-
}
|
|
73878
|
-
|
|
73879
|
-
|
|
73880
|
-
|
|
73881
|
-
|
|
73882
|
-
|
|
73883
|
-
|
|
73884
|
-
|
|
73885
|
-
|
|
73886
|
-
|
|
73887
|
-
|
|
73888
|
-
return { queued: gate.ready.reverse(), open: gate.open.reverse() };
|
|
73889
|
-
} catch {
|
|
73890
|
-
return { queued: [], open: [] };
|
|
75485
|
+
const rawProject = session.project ?? "-";
|
|
75486
|
+
const project = full ? rawProject : clip(rawProject, 28);
|
|
75487
|
+
return `${session.harness.padEnd(8)} ${stamp} ${project} ${full ? session.id : clip(session.id, 24)}`;
|
|
75488
|
+
}
|
|
75489
|
+
function wrapRow(text, width, indent = " ") {
|
|
75490
|
+
if (text.length <= width)
|
|
75491
|
+
return [text];
|
|
75492
|
+
const out = [text.slice(0, width)];
|
|
75493
|
+
let rest = text.slice(width);
|
|
75494
|
+
const inner = Math.max(1, width - indent.length);
|
|
75495
|
+
while (rest.length > 0) {
|
|
75496
|
+
out.push(indent + rest.slice(0, inner));
|
|
75497
|
+
rest = rest.slice(inner);
|
|
73891
75498
|
}
|
|
75499
|
+
return out;
|
|
73892
75500
|
}
|
|
73893
75501
|
function windowList(lines, scroll, height = ACTIVITY_VIEW_LIST_LINES) {
|
|
73894
75502
|
const clamped = Math.max(0, Math.min(scroll, Math.max(0, lines.length - height)));
|
|
@@ -73948,6 +75556,9 @@ function statusLine(status) {
|
|
|
73948
75556
|
const pid = status.pid !== undefined ? ` (pid ${status.pid})` : "";
|
|
73949
75557
|
return `⛏️ ${import_picocolors30.default.bold(brand("Mining sessions..."))}${import_picocolors30.default.dim(`${pid}${since}`)}`;
|
|
73950
75558
|
}
|
|
75559
|
+
if (status.state.paused) {
|
|
75560
|
+
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("· mining stays off until you resume")}`;
|
|
75561
|
+
}
|
|
73951
75562
|
if (status.staleLock) {
|
|
73952
75563
|
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Not running")} ${import_picocolors30.default.dim("· a previous run exited without cleaning up")}`;
|
|
73953
75564
|
}
|
|
@@ -73966,40 +75577,12 @@ function progressLine(done, ready, width, notes = 0) {
|
|
|
73966
75577
|
return `${bar} ${done}/${total} mined · ${pct}%${suffix}`;
|
|
73967
75578
|
}
|
|
73968
75579
|
function tabBar(tab, queuedCount, openCount, minedCount, width) {
|
|
73969
|
-
|
|
75580
|
+
return tabStrip([
|
|
73970
75581
|
["activity", "Activity"],
|
|
73971
75582
|
["mined", `Mined (${minedCount})`],
|
|
73972
75583
|
["queued", `Queued (${queuedCount})`],
|
|
73973
75584
|
["open", `Open (${openCount})`]
|
|
73974
|
-
];
|
|
73975
|
-
const GAP_MIN = 3;
|
|
73976
|
-
const totalLen = labels.reduce((sum, [, label]) => sum + label.length, 0);
|
|
73977
|
-
const slots = labels.length - 1;
|
|
73978
|
-
const spare = Math.max(GAP_MIN * slots, width - totalLen);
|
|
73979
|
-
const baseGap = Math.floor(spare / slots);
|
|
73980
|
-
const bonus = spare % slots;
|
|
73981
|
-
let row = "";
|
|
73982
|
-
let col = 0;
|
|
73983
|
-
let activeStart = 0;
|
|
73984
|
-
let activeLen = 0;
|
|
73985
|
-
labels.forEach(([id2, label], i5) => {
|
|
73986
|
-
if (i5 > 0) {
|
|
73987
|
-
const gap = baseGap + (i5 <= bonus ? 1 : 0);
|
|
73988
|
-
row += " ".repeat(gap);
|
|
73989
|
-
col += gap;
|
|
73990
|
-
}
|
|
73991
|
-
if (id2 === tab) {
|
|
73992
|
-
activeStart = col;
|
|
73993
|
-
activeLen = label.length;
|
|
73994
|
-
row += brand(import_picocolors30.default.bold(label));
|
|
73995
|
-
} else {
|
|
73996
|
-
row += import_picocolors30.default.dim(label);
|
|
73997
|
-
}
|
|
73998
|
-
col += label.length;
|
|
73999
|
-
});
|
|
74000
|
-
const tail = Math.max(0, width - activeStart - activeLen);
|
|
74001
|
-
const rule2 = import_picocolors30.default.dim("─".repeat(activeStart)) + brand("━".repeat(activeLen)) + import_picocolors30.default.dim("─".repeat(tail));
|
|
74002
|
-
return [row, rule2];
|
|
75585
|
+
], tab, width);
|
|
74003
75586
|
}
|
|
74004
75587
|
function activityWidth(columns) {
|
|
74005
75588
|
return Math.max(20, columns - 2 * layoutMargin(columns) - 1);
|
|
@@ -74019,15 +75602,17 @@ function wrapLine(text, width, indent = " ") {
|
|
|
74019
75602
|
out.push(line);
|
|
74020
75603
|
return out;
|
|
74021
75604
|
}
|
|
74022
|
-
function confirmBox(queuedCount, backlog, width) {
|
|
75605
|
+
function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
74023
75606
|
const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} open, mined once ${backlog.inFlight === 1 ? "it goes" : "they go"} quiet)` : "";
|
|
74024
|
-
const scope = queuedCount > 0 ? `${queuedCount} session${queuedCount === 1 ? "" : "s"} queued${inFlight} · runs in the background` : `queue empty${inFlight} · a run would only pick up sessions that finish from here`;
|
|
75607
|
+
const scope = mode === "stop" ? "the run is killed mid-batch · mining stays paused until you resume" : queuedCount > 0 ? `${queuedCount} session${queuedCount === 1 ? "" : "s"} queued${inFlight} · runs in the background` : `queue empty${inFlight} · a run would only pick up sessions that finish from here`;
|
|
75608
|
+
const title = mode === "stop" ? "Stop mining?" : mode === "resume" ? "Resume mining?" : "Start mining now?";
|
|
75609
|
+
const verb = mode === "stop" ? "stop" : mode === "resume" ? "resume" : "start";
|
|
74025
75610
|
const maxInner = Math.max(20, Math.min(width, contentWidth()) - 4);
|
|
74026
75611
|
const rows = [
|
|
74027
|
-
|
|
75612
|
+
import_picocolors30.default.bold(brand(title)),
|
|
74028
75613
|
...wrapLine(scope, maxInner, "").map((line) => import_picocolors30.default.dim(line)),
|
|
74029
75614
|
"",
|
|
74030
|
-
`${import_picocolors30.default.bold("enter")}
|
|
75615
|
+
`${import_picocolors30.default.bold("enter")} ${verb} · ${import_picocolors30.default.bold("esc")} cancel`
|
|
74031
75616
|
];
|
|
74032
75617
|
const inner = Math.min(maxInner, Math.max(...rows.map(visibleWidth)));
|
|
74033
75618
|
const pad = (row) => row + " ".repeat(Math.max(0, inner - visibleWidth(row)));
|
|
@@ -74041,7 +75626,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
|
|
|
74041
75626
|
const mined = status.state.watermark ? `Mined sessions up to ${localTime(status.state.watermark)}` : "Nothing mined yet";
|
|
74042
75627
|
const queueDetail = [];
|
|
74043
75628
|
if (status.backoffUntil) {
|
|
74044
|
-
queueDetail.push(`retrying after ${localTime(status.backoffUntil)}`);
|
|
75629
|
+
queueDetail.push(`retrying after ${localTime(status.backoffUntil)} · s syncs now`);
|
|
74045
75630
|
}
|
|
74046
75631
|
let progress = null;
|
|
74047
75632
|
if (status.running && backlog) {
|
|
@@ -74051,14 +75636,18 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
|
|
|
74051
75636
|
}
|
|
74052
75637
|
const refusal = !status.running && status.state.last_refusal;
|
|
74053
75638
|
const refusalLines = refusal ? wrapLine(`! Mining paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors30.default.yellow(line)) : [];
|
|
74054
|
-
const
|
|
74055
|
-
const
|
|
74056
|
-
const
|
|
74057
|
-
const
|
|
75639
|
+
const fullRows = Boolean(pane.fullRows);
|
|
75640
|
+
const minedRows = (status.state.mined_sessions ?? []).map((record2) => fullRows ? formatMinedRow(record2, true) : formatActivityLine(formatMinedRow(record2), width));
|
|
75641
|
+
const sessionRow = (session) => fullRows ? formatQueuedRow(session, true) : formatActivityLine(formatQueuedRow(session), width);
|
|
75642
|
+
const queuedRows = queued.map(sessionRow);
|
|
75643
|
+
const openRows = open5.map(sessionRow);
|
|
75644
|
+
const source = pane.tab === "activity" ? activity.map((line) => formatActivityLine(line, width, fullRows)) : pane.tab === "queued" ? queuedRows : pane.tab === "open" ? openRows : minedRows;
|
|
74058
75645
|
const emptyMined = status.state.watermark ? "No sessions recorded yet. History starts with the next mining run." : "No mined sessions yet.";
|
|
74059
75646
|
const empty = pane.tab === "activity" ? "No sync activity in the log yet." : pane.tab === "queued" ? "Queue empty. Finished agent sessions appear here." : pane.tab === "open" ? "No open sessions. Live agent sessions sit here until they go quiet." : emptyMined;
|
|
74060
|
-
const
|
|
74061
|
-
const
|
|
75647
|
+
const height = fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75648
|
+
const { visible, above, below } = windowList(source, pane.scroll, height);
|
|
75649
|
+
const display = fullRows ? visible.flatMap((row) => wrapRow(row, width)) : visible;
|
|
75650
|
+
const listRows = display.length > 0 ? display.map((row) => import_picocolors30.default.dim(row)) : [import_picocolors30.default.dim(empty)];
|
|
74062
75651
|
const scrollParts = [];
|
|
74063
75652
|
if (above > 0)
|
|
74064
75653
|
scrollParts.push(`↑ ${above} earlier`);
|
|
@@ -74075,13 +75664,14 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
|
|
|
74075
75664
|
"",
|
|
74076
75665
|
...tabBar(pane.tab, queued.length, open5.length, status.state.total_mined ?? minedRows.length, width),
|
|
74077
75666
|
...listRows,
|
|
74078
|
-
...scrollParts.length > 0 ? [import_picocolors30.default.dim(scrollParts.join(" · "))] : [],
|
|
75667
|
+
...scrollParts.length > 0 ? [import_picocolors30.default.dim(clip(scrollParts.join(" · "), width))] : [],
|
|
74079
75668
|
"",
|
|
74080
|
-
...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width), width) : [
|
|
75669
|
+
...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width, pane.confirm), width) : [
|
|
74081
75670
|
import_picocolors30.default.dim([
|
|
74082
75671
|
"tab switch",
|
|
74083
75672
|
"↑↓ scroll",
|
|
74084
|
-
|
|
75673
|
+
fullRows ? "f clip" : "f full rows",
|
|
75674
|
+
status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
|
|
74085
75675
|
"esc back"
|
|
74086
75676
|
].join(" · "))
|
|
74087
75677
|
]
|
|
@@ -74104,8 +75694,10 @@ function runActivityView(io2 = {}) {
|
|
|
74104
75694
|
const getStatus = io2.getStatus ?? (() => getSyncStatus({ readLog: () => "" }));
|
|
74105
75695
|
const readLog = io2.readLog ?? defaultReadLog2;
|
|
74106
75696
|
const createFollower = io2.createFollower ?? ((emit) => createLogFollower(logger.getLogPath(), emit));
|
|
74107
|
-
const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--
|
|
74108
|
-
const
|
|
75697
|
+
const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--bootstrap"]));
|
|
75698
|
+
const stopSync = io2.stopSync ?? stopSyncRun;
|
|
75699
|
+
const setPaused = io2.setPaused ?? setSyncPaused;
|
|
75700
|
+
const listBacklog = io2.listBacklog ?? listSessionBacklog;
|
|
74109
75701
|
const pollMs = io2.pollMs ?? ACTIVITY_VIEW_POLL_MS;
|
|
74110
75702
|
const seed = readLog();
|
|
74111
75703
|
let activity = appendSyncActivity([], seed);
|
|
@@ -74113,7 +75705,8 @@ function runActivityView(io2 = {}) {
|
|
|
74113
75705
|
let runProgress = foldRunProgress(null, seed);
|
|
74114
75706
|
let tab = "activity";
|
|
74115
75707
|
let scroll = 0;
|
|
74116
|
-
let
|
|
75708
|
+
let fullRows = false;
|
|
75709
|
+
let confirmSync = null;
|
|
74117
75710
|
let status = getStatus();
|
|
74118
75711
|
let sessions = listBacklog();
|
|
74119
75712
|
let queuedWatermark = status.state.watermark;
|
|
@@ -74131,12 +75724,18 @@ function runActivityView(io2 = {}) {
|
|
|
74131
75724
|
return sessions.open.length;
|
|
74132
75725
|
return (status.state.mined_sessions ?? []).length;
|
|
74133
75726
|
};
|
|
75727
|
+
const listHeight = () => fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
74134
75728
|
let lastFrame = null;
|
|
74135
75729
|
let minedBeforeRun = null;
|
|
74136
75730
|
const draw = () => {
|
|
74137
75731
|
status = getStatus();
|
|
74138
75732
|
if (status.running) {
|
|
74139
|
-
|
|
75733
|
+
const run2 = status.state.run;
|
|
75734
|
+
if (run2 && run2.pid === status.pid) {
|
|
75735
|
+
minedBeforeRun = run2.baseline_mined;
|
|
75736
|
+
} else {
|
|
75737
|
+
minedBeforeRun ??= status.state.total_mined ?? 0;
|
|
75738
|
+
}
|
|
74140
75739
|
} else {
|
|
74141
75740
|
minedBeforeRun = null;
|
|
74142
75741
|
}
|
|
@@ -74144,10 +75743,12 @@ function runActivityView(io2 = {}) {
|
|
|
74144
75743
|
queuedWatermark = status.state.watermark;
|
|
74145
75744
|
sessions = listBacklog();
|
|
74146
75745
|
}
|
|
74147
|
-
if (confirmSync && status.running)
|
|
74148
|
-
confirmSync =
|
|
75746
|
+
if ((confirmSync === "start" || confirmSync === "resume") && status.running)
|
|
75747
|
+
confirmSync = null;
|
|
75748
|
+
if (confirmSync === "stop" && !status.running)
|
|
75749
|
+
confirmSync = null;
|
|
74149
75750
|
const width = activityWidth(output2.columns ?? 80);
|
|
74150
|
-
const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync }, sessions.queued, minedBeforeRun ?? 0, sessions.open, runProgress);
|
|
75751
|
+
const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync ?? undefined, fullRows }, sessions.queued, minedBeforeRun ?? 0, sessions.open, runProgress);
|
|
74151
75752
|
if (frame === lastFrame)
|
|
74152
75753
|
return;
|
|
74153
75754
|
lastFrame = frame;
|
|
@@ -74167,7 +75768,7 @@ ${CLEAR_BELOW}`);
|
|
|
74167
75768
|
const leaveAltScreen = enterAltScreen(output2);
|
|
74168
75769
|
output2.write(HIDE_CURSOR3);
|
|
74169
75770
|
draw();
|
|
74170
|
-
return new Promise((
|
|
75771
|
+
return new Promise((resolve7) => {
|
|
74171
75772
|
const wasRaw = input2.isRaw ?? false;
|
|
74172
75773
|
input2.setRawMode?.(true);
|
|
74173
75774
|
input2.resume();
|
|
@@ -74184,21 +75785,32 @@ ${CLEAR_BELOW}`);
|
|
|
74184
75785
|
input2.pause();
|
|
74185
75786
|
leaveAltScreen();
|
|
74186
75787
|
output2.write(SHOW_CURSOR3);
|
|
74187
|
-
|
|
75788
|
+
resolve7();
|
|
74188
75789
|
};
|
|
74189
75790
|
const onData = (chunk) => {
|
|
74190
75791
|
for (const key of parseKeys(chunk.toString())) {
|
|
74191
75792
|
if (confirmSync) {
|
|
74192
75793
|
const decision = reduceSyncConfirmKey(key);
|
|
74193
75794
|
if (decision === "start") {
|
|
74194
|
-
|
|
74195
|
-
|
|
74196
|
-
|
|
75795
|
+
const mode = confirmSync;
|
|
75796
|
+
confirmSync = null;
|
|
75797
|
+
let note;
|
|
75798
|
+
if (mode === "stop") {
|
|
75799
|
+
const ok2 = status.pid !== undefined && stopSync(status.pid);
|
|
75800
|
+
if (ok2)
|
|
75801
|
+
setPaused(true);
|
|
75802
|
+
note = ok2 ? "[sync] mining stopped · paused until you resume" : "[sync] could not stop the run · it may have just finished";
|
|
75803
|
+
} else {
|
|
75804
|
+
if (mode === "resume")
|
|
75805
|
+
setPaused(false);
|
|
75806
|
+
const ok2 = startSync();
|
|
75807
|
+
note = ok2 ? "[sync] sync requested · starting a background run" : "[sync] could not start a background run · try `dosu knowledge sync`";
|
|
75808
|
+
}
|
|
74197
75809
|
activity = appendSyncActivity(activity, `[${new Date().toISOString()}] [INFO] ${note}
|
|
74198
75810
|
`);
|
|
74199
75811
|
draw();
|
|
74200
75812
|
} else if (decision === "cancel") {
|
|
74201
|
-
confirmSync =
|
|
75813
|
+
confirmSync = null;
|
|
74202
75814
|
draw();
|
|
74203
75815
|
}
|
|
74204
75816
|
continue;
|
|
@@ -74209,18 +75821,20 @@ ${CLEAR_BELOW}`);
|
|
|
74209
75821
|
return;
|
|
74210
75822
|
}
|
|
74211
75823
|
if (action === "sync") {
|
|
74212
|
-
|
|
74213
|
-
|
|
74214
|
-
draw();
|
|
74215
|
-
}
|
|
75824
|
+
confirmSync = syncConfirmMode(status);
|
|
75825
|
+
draw();
|
|
74216
75826
|
} else if (action === "tab" || action === "tab-back") {
|
|
74217
75827
|
tab = cycleTab(tab, action === "tab" ? 1 : -1);
|
|
74218
75828
|
scroll = 0;
|
|
74219
75829
|
if (tab === "queued" || tab === "open")
|
|
74220
75830
|
sessions = listBacklog();
|
|
74221
75831
|
draw();
|
|
75832
|
+
} else if (action === "full") {
|
|
75833
|
+
fullRows = !fullRows;
|
|
75834
|
+
scroll = 0;
|
|
75835
|
+
draw();
|
|
74222
75836
|
} else if (action === "up") {
|
|
74223
|
-
const maxScroll = Math.max(0, activeListLength() -
|
|
75837
|
+
const maxScroll = Math.max(0, activeListLength() - listHeight());
|
|
74224
75838
|
if (scroll < maxScroll) {
|
|
74225
75839
|
scroll += 1;
|
|
74226
75840
|
draw();
|
|
@@ -74236,14 +75850,14 @@ ${CLEAR_BELOW}`);
|
|
|
74236
75850
|
input2.on("data", onData);
|
|
74237
75851
|
});
|
|
74238
75852
|
}
|
|
74239
|
-
var import_picocolors30, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE;
|
|
75853
|
+
var import_picocolors30, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE;
|
|
74240
75854
|
var init_activity_view = __esm(() => {
|
|
74241
75855
|
init_follow();
|
|
74242
75856
|
init_logger();
|
|
74243
|
-
init_project_dir();
|
|
74244
|
-
init_scan();
|
|
74245
75857
|
init_styles();
|
|
75858
|
+
init_backlog();
|
|
74246
75859
|
init_detach();
|
|
75860
|
+
init_lock();
|
|
74247
75861
|
init_status();
|
|
74248
75862
|
init_watermark();
|
|
74249
75863
|
init_alt_screen();
|
|
@@ -74694,8 +76308,8 @@ h1 {
|
|
|
74694
76308
|
async function startCallbackServer(opts = {}) {
|
|
74695
76309
|
let resolveToken;
|
|
74696
76310
|
let rejectToken;
|
|
74697
|
-
const tokenPromise = new Promise((
|
|
74698
|
-
resolveToken =
|
|
76311
|
+
const tokenPromise = new Promise((resolve7, reject) => {
|
|
76312
|
+
resolveToken = resolve7;
|
|
74699
76313
|
rejectToken = reject;
|
|
74700
76314
|
});
|
|
74701
76315
|
const http = __require("node:http");
|
|
@@ -74753,8 +76367,8 @@ async function startCallbackServer(opts = {}) {
|
|
|
74753
76367
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
74754
76368
|
res.end(buildSuccessHtml(email3 ?? undefined, opts.successVariant ?? "auth"));
|
|
74755
76369
|
});
|
|
74756
|
-
await new Promise((
|
|
74757
|
-
httpServer.listen(0, "localhost", () =>
|
|
76370
|
+
await new Promise((resolve7) => {
|
|
76371
|
+
httpServer.listen(0, "localhost", () => resolve7());
|
|
74758
76372
|
});
|
|
74759
76373
|
httpServer.unref();
|
|
74760
76374
|
const addr = httpServer.address();
|
|
@@ -75883,12 +77497,21 @@ var init_flow2 = __esm(() => {
|
|
|
75883
77497
|
function reduceAnalyticsViewKey(key) {
|
|
75884
77498
|
if (key === "q" || key === ESC8 || key === CTRL_C5)
|
|
75885
77499
|
return "back";
|
|
77500
|
+
if (key === "\t" || key === KEY_RIGHT4)
|
|
77501
|
+
return "tab";
|
|
77502
|
+
if (key === KEY_LEFT4)
|
|
77503
|
+
return "tab-back";
|
|
75886
77504
|
if (key === KEY_UP5 || key === "k")
|
|
75887
77505
|
return "up";
|
|
75888
77506
|
if (key === KEY_DOWN5 || key === "j")
|
|
75889
77507
|
return "down";
|
|
75890
77508
|
return "none";
|
|
75891
77509
|
}
|
|
77510
|
+
function cycleAnalyticsTab(tab, delta = 1) {
|
|
77511
|
+
const index = ANALYTICS_VIEW_TABS.indexOf(tab);
|
|
77512
|
+
const next = (index + delta + ANALYTICS_VIEW_TABS.length) % ANALYTICS_VIEW_TABS.length;
|
|
77513
|
+
return ANALYTICS_VIEW_TABS[next];
|
|
77514
|
+
}
|
|
75892
77515
|
function clip2(text, max) {
|
|
75893
77516
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
75894
77517
|
}
|
|
@@ -75925,38 +77548,37 @@ function loadPageStatsFromConfig() {
|
|
|
75925
77548
|
return Promise.resolve(null);
|
|
75926
77549
|
}
|
|
75927
77550
|
}
|
|
75928
|
-
function
|
|
77551
|
+
function overviewRows(state) {
|
|
75929
77552
|
const minedTotal = state.total_mined ?? 0;
|
|
75930
77553
|
const notes = state.total_notes ?? 0;
|
|
75931
77554
|
const tokens = state.total_learning_tokens ?? 0;
|
|
75932
|
-
|
|
75933
|
-
|
|
75934
|
-
|
|
75935
|
-
|
|
75936
|
-
|
|
75937
|
-
|
|
75938
|
-
|
|
75939
|
-
|
|
75940
|
-
|
|
75941
|
-
|
|
75942
|
-
|
|
75943
|
-
|
|
75944
|
-
|
|
75945
|
-
rows.push("", "Mined by project (recent history)");
|
|
75946
|
-
const top = [...byProject.entries()].sort((a9, b2) => b2[1] - a9[1]).slice(0, 8);
|
|
75947
|
-
for (const [project, count] of top) {
|
|
75948
|
-
rows.push(`${label(clip2(project, 24))}${count}`);
|
|
75949
|
-
}
|
|
75950
|
-
}
|
|
77555
|
+
if (minedTotal === 0 && notes === 0)
|
|
77556
|
+
return [];
|
|
77557
|
+
const rows = [`${label("Sessions mined")}${minedTotal}`, `${label("Suggested pages")}${notes}`];
|
|
77558
|
+
if (tokens > 0) {
|
|
77559
|
+
rows.push(`${label("Investigation distilled")}${formatTokenCount(tokens)} tokens`);
|
|
77560
|
+
}
|
|
77561
|
+
return rows;
|
|
77562
|
+
}
|
|
77563
|
+
function projectRows(state) {
|
|
77564
|
+
const byProject = new Map;
|
|
77565
|
+
for (const record2 of state.mined_sessions ?? []) {
|
|
77566
|
+
const key = record2.project ?? "(unknown)";
|
|
77567
|
+
byProject.set(key, (byProject.get(key) ?? 0) + 1);
|
|
75951
77568
|
}
|
|
77569
|
+
const data = [...byProject.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([project, count]) => `${label(clip2(project, 24))}${count}`);
|
|
77570
|
+
return data.length > 0 ? [`${label("Project")}Sessions`, ...data] : [];
|
|
77571
|
+
}
|
|
77572
|
+
function pageRows(pageStats) {
|
|
77573
|
+
const rows = [];
|
|
75952
77574
|
if (pageStats && pageStats.topCited.length > 0) {
|
|
75953
|
-
rows.push(
|
|
77575
|
+
rows.push(`${label(`Top cited (${TOP_CITED_DAYS}d)`)}Citations`);
|
|
75954
77576
|
for (const row of pageStats.topCited) {
|
|
75955
77577
|
rows.push(`${label(clip2(row.title, 24))}${row.citation_count}`);
|
|
75956
77578
|
}
|
|
75957
77579
|
}
|
|
75958
77580
|
if (pageStats && pageStats.topUpdated.length > 0) {
|
|
75959
|
-
rows.push(...rows.length > 0 ? [""] : [], "Recently updated
|
|
77581
|
+
rows.push(...rows.length > 0 ? [""] : [], `${label("Recently updated")}Updated`);
|
|
75960
77582
|
for (const row of pageStats.topUpdated) {
|
|
75961
77583
|
rows.push(`${label(clip2(row.title, 24))}${row.updated_at.slice(0, 10)}`);
|
|
75962
77584
|
}
|
|
@@ -75971,22 +77593,36 @@ function windowReport(lines, scroll, height = ANALYTICS_VIEW_LINES) {
|
|
|
75971
77593
|
below: Math.max(0, lines.length - clamped - height)
|
|
75972
77594
|
};
|
|
75973
77595
|
}
|
|
75974
|
-
function
|
|
75975
|
-
|
|
77596
|
+
function analyticsTabRows(tab, state, pageStats) {
|
|
77597
|
+
if (tab === "overview")
|
|
77598
|
+
return overviewRows(state);
|
|
77599
|
+
if (tab === "projects")
|
|
77600
|
+
return projectRows(state);
|
|
77601
|
+
return pageRows(pageStats);
|
|
77602
|
+
}
|
|
77603
|
+
function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending = false, width = contentWidth()) {
|
|
77604
|
+
const rows = analyticsTabRows(tab, state, pageStats);
|
|
75976
77605
|
const { visible, above, below } = windowReport(rows, scroll);
|
|
75977
|
-
const
|
|
77606
|
+
const empty = tab === "overview" ? "No analytics yet. They appear after the first mining run." : tab === "projects" ? "No per-project history yet. It fills in as sessions are mined." : pagesPending ? "Loading page analytics..." : "No page analytics yet.";
|
|
77607
|
+
const listRows = visible.length > 0 ? visible.map((row) => import_picocolors32.default.dim(row)) : [import_picocolors32.default.dim(empty)];
|
|
75978
77608
|
const scrollParts = [];
|
|
75979
77609
|
if (above > 0)
|
|
75980
77610
|
scrollParts.push(`↑ ${above} earlier`);
|
|
75981
77611
|
if (below > 0)
|
|
75982
77612
|
scrollParts.push(`↓ ${below} more`);
|
|
75983
77613
|
const lines = [
|
|
75984
|
-
breadcrumb(["Home", "Analytics"]),
|
|
77614
|
+
breadcrumb(["Home", "Analytics"], width),
|
|
77615
|
+
"",
|
|
77616
|
+
...tabStrip([
|
|
77617
|
+
["overview", "Overview"],
|
|
77618
|
+
["projects", "Projects"],
|
|
77619
|
+
["pages", "Pages"]
|
|
77620
|
+
], tab, width, { spread: false }),
|
|
75985
77621
|
"",
|
|
75986
77622
|
...listRows,
|
|
75987
77623
|
...scrollParts.length > 0 ? [import_picocolors32.default.dim(scrollParts.join(" · "))] : [],
|
|
75988
77624
|
"",
|
|
75989
|
-
import_picocolors32.default.dim("↑↓ scroll · esc back")
|
|
77625
|
+
import_picocolors32.default.dim("tab switch · ↑↓ scroll · esc back")
|
|
75990
77626
|
];
|
|
75991
77627
|
return lines.join(`
|
|
75992
77628
|
`);
|
|
@@ -75999,14 +77635,17 @@ function runAnalyticsView(io2 = {}) {
|
|
|
75999
77635
|
const getStatus = io2.getStatus ?? (() => getSyncStatus({ readLog: () => "" }));
|
|
76000
77636
|
const loadPageStats = io2.loadPageStats ?? loadPageStatsFromConfig;
|
|
76001
77637
|
const pollMs = io2.pollMs ?? ANALYTICS_VIEW_POLL_MS;
|
|
77638
|
+
let tab = "overview";
|
|
76002
77639
|
let scroll = 0;
|
|
76003
77640
|
let status = getStatus();
|
|
76004
77641
|
let pageStats = null;
|
|
77642
|
+
let pagesPending = true;
|
|
76005
77643
|
let closed = false;
|
|
76006
77644
|
let lastFrame = null;
|
|
76007
77645
|
const draw = () => {
|
|
76008
77646
|
status = getStatus();
|
|
76009
|
-
const
|
|
77647
|
+
const width = Math.min(contentWidth(output2.columns ?? 80), (output2.columns ?? 80) - 1);
|
|
77648
|
+
const frame = renderAnalyticsFrame(status.state, tab, scroll, pageStats, pagesPending, width);
|
|
76010
77649
|
if (frame === lastFrame)
|
|
76011
77650
|
return;
|
|
76012
77651
|
lastFrame = frame;
|
|
@@ -76027,12 +77666,13 @@ ${CLEAR_BELOW2}`);
|
|
|
76027
77666
|
output2.write(HIDE_CURSOR4);
|
|
76028
77667
|
draw();
|
|
76029
77668
|
loadPageStats().then((stats) => {
|
|
76030
|
-
if (closed
|
|
77669
|
+
if (closed)
|
|
76031
77670
|
return;
|
|
76032
77671
|
pageStats = stats;
|
|
77672
|
+
pagesPending = false;
|
|
76033
77673
|
draw();
|
|
76034
77674
|
});
|
|
76035
|
-
return new Promise((
|
|
77675
|
+
return new Promise((resolve7) => {
|
|
76036
77676
|
const wasRaw = input2.isRaw ?? false;
|
|
76037
77677
|
input2.setRawMode?.(true);
|
|
76038
77678
|
input2.resume();
|
|
@@ -76047,7 +77687,7 @@ ${CLEAR_BELOW2}`);
|
|
|
76047
77687
|
input2.pause();
|
|
76048
77688
|
leaveAltScreen();
|
|
76049
77689
|
output2.write(SHOW_CURSOR4);
|
|
76050
|
-
|
|
77690
|
+
resolve7();
|
|
76051
77691
|
};
|
|
76052
77692
|
const onData = (chunk) => {
|
|
76053
77693
|
for (const key of parseKeys(chunk.toString())) {
|
|
@@ -76056,13 +77696,17 @@ ${CLEAR_BELOW2}`);
|
|
|
76056
77696
|
finish();
|
|
76057
77697
|
return;
|
|
76058
77698
|
}
|
|
76059
|
-
if (action === "
|
|
77699
|
+
if (action === "tab" || action === "tab-back") {
|
|
77700
|
+
tab = cycleAnalyticsTab(tab, action === "tab" ? 1 : -1);
|
|
77701
|
+
scroll = 0;
|
|
77702
|
+
draw();
|
|
77703
|
+
} else if (action === "up") {
|
|
76060
77704
|
if (scroll > 0) {
|
|
76061
77705
|
scroll -= 1;
|
|
76062
77706
|
draw();
|
|
76063
77707
|
}
|
|
76064
77708
|
} else if (action === "down") {
|
|
76065
|
-
const maxScroll = Math.max(0,
|
|
77709
|
+
const maxScroll = Math.max(0, analyticsTabRows(tab, status.state, pageStats).length - ANALYTICS_VIEW_LINES);
|
|
76066
77710
|
if (scroll < maxScroll) {
|
|
76067
77711
|
scroll += 1;
|
|
76068
77712
|
draw();
|
|
@@ -76073,7 +77717,7 @@ ${CLEAR_BELOW2}`);
|
|
|
76073
77717
|
input2.on("data", onData);
|
|
76074
77718
|
});
|
|
76075
77719
|
}
|
|
76076
|
-
var import_picocolors32, ESC8, HIDE_CURSOR4, SHOW_CURSOR4, CTRL_C5, KEY_UP5, KEY_DOWN5, CURSOR_HOME2, CLEAR_BELOW2, CLEAR_EOL2, ANALYTICS_VIEW_POLL_MS = 1000, ANALYTICS_VIEW_LINES =
|
|
77720
|
+
var import_picocolors32, ESC8, HIDE_CURSOR4, SHOW_CURSOR4, CTRL_C5, KEY_UP5, KEY_DOWN5, KEY_RIGHT4, KEY_LEFT4, CURSOR_HOME2, CLEAR_BELOW2, CLEAR_EOL2, ANALYTICS_VIEW_POLL_MS = 1000, ANALYTICS_VIEW_LINES = 14, ANALYTICS_VIEW_TABS, TOP_PAGES_LIMIT = 5, TOP_CITED_DAYS = 30, label = (text) => text.padEnd(26);
|
|
76077
77721
|
var init_analytics_view = __esm(() => {
|
|
76078
77722
|
init_trpc();
|
|
76079
77723
|
init_config();
|
|
@@ -76088,9 +77732,12 @@ var init_analytics_view = __esm(() => {
|
|
|
76088
77732
|
CTRL_C5 = String.fromCharCode(3);
|
|
76089
77733
|
KEY_UP5 = `${ESC8}[A`;
|
|
76090
77734
|
KEY_DOWN5 = `${ESC8}[B`;
|
|
77735
|
+
KEY_RIGHT4 = `${ESC8}[C`;
|
|
77736
|
+
KEY_LEFT4 = `${ESC8}[D`;
|
|
76091
77737
|
CURSOR_HOME2 = `${ESC8}[H`;
|
|
76092
77738
|
CLEAR_BELOW2 = `${ESC8}[0J`;
|
|
76093
77739
|
CLEAR_EOL2 = `${ESC8}[K`;
|
|
77740
|
+
ANALYTICS_VIEW_TABS = ["overview", "projects", "pages"];
|
|
76094
77741
|
});
|
|
76095
77742
|
|
|
76096
77743
|
// src/tui/banner.ts
|
|
@@ -76108,7 +77755,7 @@ function checklistRows(ctx) {
|
|
|
76108
77755
|
const rows = [["workspace", ctx.directory]];
|
|
76109
77756
|
rows.push([
|
|
76110
77757
|
"account",
|
|
76111
|
-
ctx.signedIn ? `${on2} signed in` : `${off} ${import_picocolors33.default.dim(
|
|
77758
|
+
ctx.sessionExpired ? `${off} ${import_picocolors33.default.yellow("session expired")} ${import_picocolors33.default.dim(`${DOT3} run Log in`)}` : ctx.signedIn ? `${on2} signed in` : `${off} ${import_picocolors33.default.dim(`not signed in ${DOT3} run Setup`)}`
|
|
76112
77759
|
]);
|
|
76113
77760
|
const missing = new Set(ctx.setupMissing ?? []);
|
|
76114
77761
|
const warnRow = `${off} ${import_picocolors33.default.yellow("not configured")} ${import_picocolors33.default.dim(`${DOT3} run Setup`)}`;
|
|
@@ -76144,8 +77791,8 @@ function checklistRows(ctx) {
|
|
|
76144
77791
|
`${import_picocolors33.default.yellow(`↑ ${ctx.update.version} available`)} ${import_picocolors33.default.dim(`${DOT3} ${ctx.update.hint}`)}`
|
|
76145
77792
|
]);
|
|
76146
77793
|
}
|
|
76147
|
-
const labelWidth = Math.max(...rows.map(([
|
|
76148
|
-
return rows.map(([
|
|
77794
|
+
const labelWidth = Math.max(...rows.map(([label2]) => label2.length));
|
|
77795
|
+
return rows.map(([label2, value]) => `${import_picocolors33.default.dim(label2.padEnd(labelWidth))} ${value}`);
|
|
76149
77796
|
}
|
|
76150
77797
|
function renderBanner(ctx) {
|
|
76151
77798
|
const logo = LOGO_ROWS.map(paintLogoRow);
|
|
@@ -76274,7 +77921,7 @@ function wrapBody(text, width) {
|
|
|
76274
77921
|
function clip3(text, max) {
|
|
76275
77922
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
76276
77923
|
}
|
|
76277
|
-
function
|
|
77924
|
+
function pageRows2(pages, width) {
|
|
76278
77925
|
const titleWidth = Math.max(16, width - 32);
|
|
76279
77926
|
return pages.map((page) => {
|
|
76280
77927
|
const title = clip3(page.title, titleWidth).padEnd(titleWidth + 1);
|
|
@@ -76298,7 +77945,7 @@ function renderPagesFrame(model, width, selected) {
|
|
|
76298
77945
|
} else if (model.pages.length === 0) {
|
|
76299
77946
|
body.push(import_picocolors34.default.dim("No pages in this Library yet."));
|
|
76300
77947
|
} else {
|
|
76301
|
-
const rows =
|
|
77948
|
+
const rows = pageRows2(model.pages, width - 2);
|
|
76302
77949
|
const { start, above } = windowSelection(rows.length, selected);
|
|
76303
77950
|
const visible = rows.slice(start, start + PAGES_VIEW_LINES);
|
|
76304
77951
|
body.push(...visible.map((row, i5) => start + i5 === selected ? `${brand("▸")} ${import_picocolors34.default.bold(row)}` : ` ${import_picocolors34.default.dim(row)}`));
|
|
@@ -76441,7 +78088,7 @@ ${CLEAR_BELOW3}`);
|
|
|
76441
78088
|
});
|
|
76442
78089
|
draw();
|
|
76443
78090
|
};
|
|
76444
|
-
return new Promise((
|
|
78091
|
+
return new Promise((resolve7) => {
|
|
76445
78092
|
const wasRaw = input2.isRaw ?? false;
|
|
76446
78093
|
input2.setRawMode?.(true);
|
|
76447
78094
|
input2.resume();
|
|
@@ -76453,7 +78100,7 @@ ${CLEAR_BELOW3}`);
|
|
|
76453
78100
|
input2.pause();
|
|
76454
78101
|
leaveAltScreen();
|
|
76455
78102
|
output2.write(SHOW_CURSOR5);
|
|
76456
|
-
|
|
78103
|
+
resolve7();
|
|
76457
78104
|
};
|
|
76458
78105
|
const onData = (chunk) => {
|
|
76459
78106
|
for (const key of parseKeys(chunk.toString())) {
|
|
@@ -76525,9 +78172,19 @@ __export(exports_tui, {
|
|
|
76525
78172
|
runTUI: () => runTUI,
|
|
76526
78173
|
handleLogout: () => handleLogout
|
|
76527
78174
|
});
|
|
76528
|
-
import { homedir as
|
|
78175
|
+
import { homedir as homedir10 } from "node:os";
|
|
76529
78176
|
import { basename as basename4 } from "node:path";
|
|
76530
|
-
function
|
|
78177
|
+
async function probeSession(cfg) {
|
|
78178
|
+
if (!isAuthenticated(cfg) || !isTokenExpired(cfg))
|
|
78179
|
+
return { expired: false };
|
|
78180
|
+
try {
|
|
78181
|
+
await new Client(cfg).refreshToken();
|
|
78182
|
+
return { expired: false };
|
|
78183
|
+
} catch (err) {
|
|
78184
|
+
return { expired: err instanceof SessionExpiredError };
|
|
78185
|
+
}
|
|
78186
|
+
}
|
|
78187
|
+
function bannerContext(cfg, session) {
|
|
76531
78188
|
let webAppHost = "app.dosu.dev";
|
|
76532
78189
|
try {
|
|
76533
78190
|
webAppHost = new URL(getWebAppURL()).host;
|
|
@@ -76545,9 +78202,10 @@ function bannerContext(cfg) {
|
|
|
76545
78202
|
webAppHost,
|
|
76546
78203
|
directory: basename4(process.cwd()),
|
|
76547
78204
|
signedIn: isAuthenticated(cfg),
|
|
78205
|
+
sessionExpired: session.expired,
|
|
76548
78206
|
deploymentName: cfg.active_account?.target?.deployment_name,
|
|
76549
78207
|
libraryName: cfg.active_account?.target?.library_name,
|
|
76550
|
-
setupMissing: isAuthenticated(cfg) ? missingSetupSteps(cfg) : [],
|
|
78208
|
+
setupMissing: isAuthenticated(cfg) && !session.expired ? missingSetupSteps(cfg) : [],
|
|
76551
78209
|
...inGitWorkTree() ? { repoAgentsMd: dosuAgentsSectionState() } : {},
|
|
76552
78210
|
agents,
|
|
76553
78211
|
mining: isMining(),
|
|
@@ -76624,17 +78282,18 @@ function isMining() {
|
|
|
76624
78282
|
return false;
|
|
76625
78283
|
}
|
|
76626
78284
|
}
|
|
76627
|
-
function drawHome(cfg) {
|
|
78285
|
+
function drawHome(cfg, session) {
|
|
76628
78286
|
if (process.stdout.isTTY) {
|
|
76629
78287
|
process.stdout.write(CLEAR_SCREEN);
|
|
76630
78288
|
process.stdout.write(`
|
|
76631
78289
|
`.repeat(frameTopMargin()));
|
|
76632
78290
|
}
|
|
76633
|
-
process.stdout.write(`${renderBanner(bannerContext(cfg))}
|
|
78291
|
+
process.stdout.write(`${renderBanner(bannerContext(cfg, session))}
|
|
76634
78292
|
`);
|
|
76635
78293
|
}
|
|
76636
78294
|
async function runMainMenu() {
|
|
76637
78295
|
const cfg = loadConfig();
|
|
78296
|
+
const session = await probeSession(cfg);
|
|
76638
78297
|
const runSetupAndReload = async () => {
|
|
76639
78298
|
await runSetup();
|
|
76640
78299
|
const fresh = loadConfig();
|
|
@@ -76642,9 +78301,19 @@ async function runMainMenu() {
|
|
|
76642
78301
|
cfg.active_account = fresh.active_account;
|
|
76643
78302
|
};
|
|
76644
78303
|
const buildOptions = () => {
|
|
78304
|
+
if (session.expired) {
|
|
78305
|
+
return [
|
|
78306
|
+
{
|
|
78307
|
+
label: "Log in again",
|
|
78308
|
+
hint: "(session expired · opens your browser)",
|
|
78309
|
+
value: "auth"
|
|
78310
|
+
},
|
|
78311
|
+
{ label: "Exit", value: "exit" }
|
|
78312
|
+
];
|
|
78313
|
+
}
|
|
76645
78314
|
if (!isAuthenticated(cfg)) {
|
|
76646
78315
|
return [
|
|
76647
|
-
{ label: "Log in / Sign up", hint: "opens your browser", value: "auth" },
|
|
78316
|
+
{ label: "Log in / Sign up", hint: "(opens your browser)", value: "auth" },
|
|
76648
78317
|
{ label: "Exit", value: "exit" }
|
|
76649
78318
|
];
|
|
76650
78319
|
}
|
|
@@ -76660,15 +78329,16 @@ async function runMainMenu() {
|
|
|
76660
78329
|
label: mining ? `Activity ⛏️ ${brand("mining sessions...")}` : "Activity",
|
|
76661
78330
|
value: "sync"
|
|
76662
78331
|
},
|
|
78332
|
+
{ label: "Knowledge report", hint: "(opens in browser)", value: "report" },
|
|
76663
78333
|
{ label: "Analytics", value: "analytics" },
|
|
76664
78334
|
{ label: "Pages", value: "pages" },
|
|
76665
78335
|
{ label: "Settings", value: "settings" },
|
|
76666
78336
|
{ label: "Exit", value: "exit" }
|
|
76667
78337
|
];
|
|
76668
78338
|
};
|
|
76669
|
-
const home = () => drawHome(cfg);
|
|
78339
|
+
const home = () => drawHome(cfg, session);
|
|
76670
78340
|
home();
|
|
76671
|
-
if (isAuthenticated(cfg) && !isSetUp(cfg)) {
|
|
78341
|
+
if (isAuthenticated(cfg) && !session.expired && !isSetUp(cfg)) {
|
|
76672
78342
|
await runSetupAndReload();
|
|
76673
78343
|
home();
|
|
76674
78344
|
}
|
|
@@ -76684,6 +78354,10 @@ async function runMainMenu() {
|
|
|
76684
78354
|
await runActivityView();
|
|
76685
78355
|
home();
|
|
76686
78356
|
break;
|
|
78357
|
+
case "report":
|
|
78358
|
+
await runKnowledgeReport();
|
|
78359
|
+
home();
|
|
78360
|
+
break;
|
|
76687
78361
|
case "analytics":
|
|
76688
78362
|
await runAnalyticsView();
|
|
76689
78363
|
home();
|
|
@@ -76698,6 +78372,7 @@ async function runMainMenu() {
|
|
|
76698
78372
|
break;
|
|
76699
78373
|
case "auth":
|
|
76700
78374
|
await handleAuthenticate(cfg);
|
|
78375
|
+
session.expired = session.expired && isTokenExpired(cfg);
|
|
76701
78376
|
home();
|
|
76702
78377
|
if (isAuthenticated(cfg) && !isSetUp(cfg)) {
|
|
76703
78378
|
await runSetupAndReload();
|
|
@@ -76711,6 +78386,17 @@ async function runMainMenu() {
|
|
|
76711
78386
|
}
|
|
76712
78387
|
}
|
|
76713
78388
|
}
|
|
78389
|
+
async function runKnowledgeReport() {
|
|
78390
|
+
const s = spinner2();
|
|
78391
|
+
s.start("Writing knowledge report...");
|
|
78392
|
+
try {
|
|
78393
|
+
const path3 = await emitKnowledgeReport({ open: true });
|
|
78394
|
+
s.stop(`Opened ${path3}`);
|
|
78395
|
+
} catch (err) {
|
|
78396
|
+
s.stop("Could not write the report");
|
|
78397
|
+
log2.error(err instanceof Error ? err.message : String(err));
|
|
78398
|
+
}
|
|
78399
|
+
}
|
|
76714
78400
|
async function runSettings(cfg) {
|
|
76715
78401
|
while (true) {
|
|
76716
78402
|
const target = cfg.active_account?.target;
|
|
@@ -76751,7 +78437,7 @@ async function runSettings(cfg) {
|
|
|
76751
78437
|
}
|
|
76752
78438
|
}
|
|
76753
78439
|
function displayDir(dir) {
|
|
76754
|
-
const home =
|
|
78440
|
+
const home = homedir10();
|
|
76755
78441
|
return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
|
|
76756
78442
|
}
|
|
76757
78443
|
function discoverProjectDirs() {
|
|
@@ -76835,9 +78521,11 @@ function handleLogout(cfg) {
|
|
|
76835
78521
|
}
|
|
76836
78522
|
var import_picocolors35, ESC10, CLEAR_SCREEN;
|
|
76837
78523
|
var init_tui = __esm(() => {
|
|
78524
|
+
init_client();
|
|
76838
78525
|
init_config();
|
|
76839
78526
|
init_agents();
|
|
76840
78527
|
init_providers();
|
|
78528
|
+
init_generate();
|
|
76841
78529
|
init_project_dir();
|
|
76842
78530
|
init_scan();
|
|
76843
78531
|
init_agents_md_step();
|
|
@@ -77117,7 +78805,7 @@ Could not open a browser automatically.`);
|
|
|
77117
78805
|
throw new Error("Authentication timed out. Run 'dosu login' to try again.");
|
|
77118
78806
|
}
|
|
77119
78807
|
function sleep4(ms2) {
|
|
77120
|
-
return new Promise((
|
|
78808
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms2));
|
|
77121
78809
|
}
|
|
77122
78810
|
var POLL_INTERVAL_MS = 5000;
|
|
77123
78811
|
var init_device = __esm(() => {
|
|
@@ -80621,12 +82309,15 @@ init_trpc();
|
|
|
80621
82309
|
init_config();
|
|
80622
82310
|
init_agents();
|
|
80623
82311
|
init_formats();
|
|
82312
|
+
init_generate();
|
|
82313
|
+
init_backlog();
|
|
80624
82314
|
init_detach();
|
|
80625
82315
|
init_status();
|
|
80626
82316
|
init_sync();
|
|
82317
|
+
init_watermark();
|
|
80627
82318
|
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
80628
82319
|
import { existsSync as existsSync16 } from "node:fs";
|
|
80629
|
-
import { homedir as
|
|
82320
|
+
import { homedir as homedir5 } from "node:os";
|
|
80630
82321
|
import { delimiter as delimiter4, join as join19 } from "node:path";
|
|
80631
82322
|
function requireConfig7() {
|
|
80632
82323
|
const cfg = requireLoginConfig();
|
|
@@ -80690,7 +82381,51 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
80690
82381
|
console.log(` ID: ${store.id}`);
|
|
80691
82382
|
console.log(` Space ID: ${store.space_id}`);
|
|
80692
82383
|
});
|
|
80693
|
-
cmd.command("
|
|
82384
|
+
cmd.command("sessions").description("List local agent sessions with full project and session ids (the untruncated view of the Activity screen's tabs)").option("--queued", "Only sessions queued for mining").option("--open", "Only live sessions still inside the quiet period").option("--mined", "Only recent mined-session history").option("--json", "Output as JSON").action((opts) => {
|
|
82385
|
+
const all = !opts.queued && !opts.open && !opts.mined;
|
|
82386
|
+
const wantQueued = all || Boolean(opts.queued);
|
|
82387
|
+
const wantOpen = all || Boolean(opts.open);
|
|
82388
|
+
const wantMined = all || Boolean(opts.mined);
|
|
82389
|
+
const backlog = wantQueued || wantOpen ? listSessionBacklog() : { queued: [], open: [] };
|
|
82390
|
+
const mined = wantMined ? loadSyncState().mined_sessions ?? [] : [];
|
|
82391
|
+
if (opts.json) {
|
|
82392
|
+
printResult({
|
|
82393
|
+
...wantQueued ? { queued: backlog.queued } : {},
|
|
82394
|
+
...wantOpen ? { open: backlog.open } : {},
|
|
82395
|
+
...wantMined ? { mined } : {}
|
|
82396
|
+
}, opts);
|
|
82397
|
+
return;
|
|
82398
|
+
}
|
|
82399
|
+
const sessionRows = (sessions) => sessions.map((s) => [s.harness, s.updated, s.project ?? "-", s.id]);
|
|
82400
|
+
const minedRows = mined.map((record2) => {
|
|
82401
|
+
const slash = record2.session.indexOf("/");
|
|
82402
|
+
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
82403
|
+
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
82404
|
+
return [harness, record2.at, record2.project ?? "-", id2];
|
|
82405
|
+
});
|
|
82406
|
+
let first = true;
|
|
82407
|
+
const section = (title, rows, stamp, emptyMsg) => {
|
|
82408
|
+
if (!first)
|
|
82409
|
+
console.log();
|
|
82410
|
+
first = false;
|
|
82411
|
+
console.log(import_picocolors18.default.bold(`${title} (${rows.length})`));
|
|
82412
|
+
if (rows.length === 0) {
|
|
82413
|
+
console.log(import_picocolors18.default.dim(` ${emptyMsg}`));
|
|
82414
|
+
return;
|
|
82415
|
+
}
|
|
82416
|
+
printTable(["Agent", stamp, "Project", "Session"], rows);
|
|
82417
|
+
};
|
|
82418
|
+
if (wantQueued) {
|
|
82419
|
+
section("Queued", sessionRows(backlog.queued), "Updated", "Queue empty. Finished agent sessions appear here.");
|
|
82420
|
+
}
|
|
82421
|
+
if (wantOpen) {
|
|
82422
|
+
section("Open", sessionRows(backlog.open), "Updated", "No open sessions. Live agent sessions sit here until they go quiet.");
|
|
82423
|
+
}
|
|
82424
|
+
if (wantMined) {
|
|
82425
|
+
section("Mined", minedRows, "Mined at", "No mined sessions recorded yet.");
|
|
82426
|
+
}
|
|
82427
|
+
});
|
|
82428
|
+
cmd.command("sync").description("Scan local agent session history and report the mining backlog").option("--quiet", "Background mode for hooks: honor backoff, exit 0, print nothing").option("--detach", "Re-spawn detached and return immediately (used by agent hooks)").option("--bootstrap", "Backfill mode: mine the full local session history regardless of age and drain the backlog (used by setup)").option("--status", "Show whether a sync is running now, plus watermark and recent activity").option("--report", "Write the same HTML harvest report as the log-to-dosu-knowledge skill and open it").option("--out <path>", "HTML report path (default: tmp/dosu-knowledge-report.html)").option("--json", "Output as JSON").action(async (opts) => {
|
|
80694
82429
|
if (opts.status) {
|
|
80695
82430
|
const status = getSyncStatus();
|
|
80696
82431
|
if (opts.json) {
|
|
@@ -80705,7 +82440,9 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
80705
82440
|
"knowledge",
|
|
80706
82441
|
"sync",
|
|
80707
82442
|
...opts.quiet ? ["--quiet"] : [],
|
|
80708
|
-
...opts.bootstrap ? ["--bootstrap"] : []
|
|
82443
|
+
...opts.bootstrap ? ["--bootstrap"] : [],
|
|
82444
|
+
...opts.report ? ["--report"] : [],
|
|
82445
|
+
...opts.out ? ["--out", opts.out] : []
|
|
80709
82446
|
]);
|
|
80710
82447
|
return;
|
|
80711
82448
|
}
|
|
@@ -80726,12 +82463,55 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
80726
82463
|
if (opts.quiet)
|
|
80727
82464
|
return;
|
|
80728
82465
|
if (opts.json) {
|
|
80729
|
-
printResult(outcome, opts);
|
|
80730
82466
|
if (outcome.status === "error")
|
|
80731
82467
|
process.exitCode = 1;
|
|
82468
|
+
if (opts.report) {
|
|
82469
|
+
try {
|
|
82470
|
+
const report = await emitKnowledgeReport({ out: opts.out, open: false });
|
|
82471
|
+
printResult({ ...outcome, report }, opts);
|
|
82472
|
+
} catch (err) {
|
|
82473
|
+
const report_error = err instanceof Error ? err.message : String(err);
|
|
82474
|
+
printResult({ ...outcome, report_error }, opts);
|
|
82475
|
+
}
|
|
82476
|
+
} else {
|
|
82477
|
+
printResult(outcome, opts);
|
|
82478
|
+
}
|
|
80732
82479
|
return;
|
|
80733
82480
|
}
|
|
80734
82481
|
printSyncOutcome(outcome);
|
|
82482
|
+
if (opts.report) {
|
|
82483
|
+
try {
|
|
82484
|
+
const report = await emitKnowledgeReport({ out: opts.out, open: true });
|
|
82485
|
+
console.log(`Wrote ${report}`);
|
|
82486
|
+
} catch (err) {
|
|
82487
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
82488
|
+
process.exitCode = 1;
|
|
82489
|
+
}
|
|
82490
|
+
}
|
|
82491
|
+
});
|
|
82492
|
+
cmd.command("report").description("Render the knowledge report from your Dosu notes and the local session logs").option("--out <path>", "HTML report path (default: tmp/dosu-knowledge-report.html)").option("--json", "Output the report path as JSON").option("--no-open", "Write the file without opening a browser").action(async (opts) => {
|
|
82493
|
+
const path3 = await emitKnowledgeReport({
|
|
82494
|
+
out: opts.out,
|
|
82495
|
+
open: opts.json ? false : opts.open
|
|
82496
|
+
});
|
|
82497
|
+
if (opts.json) {
|
|
82498
|
+
printResult({ report: path3 }, opts);
|
|
82499
|
+
return;
|
|
82500
|
+
}
|
|
82501
|
+
console.log(`Wrote ${path3}`);
|
|
82502
|
+
});
|
|
82503
|
+
cmd.command("backfill-transcripts").description("One-shot: attribute pre-existing notes to the local sessions that produced them, so the report can show their traces").option("--json", "Output the result as JSON").action(async (opts) => {
|
|
82504
|
+
const { runBackfill: runBackfill2 } = await Promise.resolve().then(() => (init_backfill_run(), exports_backfill_run));
|
|
82505
|
+
const result = await runBackfill2();
|
|
82506
|
+
if (opts.json) {
|
|
82507
|
+
printResult(result, opts);
|
|
82508
|
+
return;
|
|
82509
|
+
}
|
|
82510
|
+
if (result.candidates === 0) {
|
|
82511
|
+
console.log("All your notes already have a transcript — nothing to backfill.");
|
|
82512
|
+
return;
|
|
82513
|
+
}
|
|
82514
|
+
console.log(`Attributed ${result.updated} of ${result.candidates} notes (${result.ambiguous} ambiguous, ${result.noBatch} without a local mining batch). Run 'dosu knowledge report' to see their traces.`);
|
|
80735
82515
|
});
|
|
80736
82516
|
cmd.addCommand(hooksCommand());
|
|
80737
82517
|
return cmd;
|
|
@@ -80771,10 +82551,13 @@ function printSyncStatus(status, now = new Date) {
|
|
|
80771
82551
|
} else {
|
|
80772
82552
|
console.log("○ No sync running.");
|
|
80773
82553
|
}
|
|
82554
|
+
if (status.state.paused) {
|
|
82555
|
+
console.log(import_picocolors18.default.yellow(" Mining paused: stopped by you. Resume from the Activity screen or run 'dosu knowledge sync'."));
|
|
82556
|
+
}
|
|
80774
82557
|
const wm2 = status.state.watermark;
|
|
80775
82558
|
console.log(` Mined through: ${wm2 ? `${wm2} (${formatAge(wm2, now)})` : "nothing mined yet"}`);
|
|
80776
82559
|
if (status.state.project_filter?.length) {
|
|
80777
|
-
const home =
|
|
82560
|
+
const home = homedir5();
|
|
80778
82561
|
const scope = status.state.project_filter.map((dir) => dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir).join(", ");
|
|
80779
82562
|
console.log(` Mining scope: ${scope}`);
|
|
80780
82563
|
}
|
|
@@ -80847,6 +82630,10 @@ function printSyncOutcome(outcome) {
|
|
|
80847
82630
|
console.log(import_picocolors18.default.dim("Skipped: a recent sync failed; waiting out the retry backoff."));
|
|
80848
82631
|
break;
|
|
80849
82632
|
}
|
|
82633
|
+
case "skipped-paused": {
|
|
82634
|
+
console.log(import_picocolors18.default.dim("Skipped: mining is paused. Run 'dosu knowledge sync' to resume."));
|
|
82635
|
+
break;
|
|
82636
|
+
}
|
|
80850
82637
|
}
|
|
80851
82638
|
}
|
|
80852
82639
|
function resolveHookAgents(ids) {
|
|
@@ -80953,7 +82740,6 @@ var LIBRARY_VISIBILITIES = ["public", "internal", "private"];
|
|
|
80953
82740
|
var CONFIG_SETTINGS = {
|
|
80954
82741
|
commit_to_trigger_pr: true,
|
|
80955
82742
|
default_accept_review: true,
|
|
80956
|
-
default_save_publish: true,
|
|
80957
82743
|
review_timeout_days: true
|
|
80958
82744
|
};
|
|
80959
82745
|
var CONFIG_SETTING_NAMES = Object.keys(CONFIG_SETTINGS);
|
|
@@ -81597,7 +83383,10 @@ init_skill();
|
|
|
81597
83383
|
// src/commands/sources.ts
|
|
81598
83384
|
init_commander();
|
|
81599
83385
|
init_trpc();
|
|
83386
|
+
init_github_step();
|
|
83387
|
+
init_installation_server();
|
|
81600
83388
|
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
83389
|
+
var CONNECT_TIMEOUT_DEFAULT_SECONDS = 600;
|
|
81601
83390
|
function requireConfig11() {
|
|
81602
83391
|
const cfg = requireLoginConfig();
|
|
81603
83392
|
if (!cfg.active_account?.target?.org_id) {
|
|
@@ -81606,6 +83395,15 @@ function requireConfig11() {
|
|
|
81606
83395
|
}
|
|
81607
83396
|
return cfg;
|
|
81608
83397
|
}
|
|
83398
|
+
function rejectWebOnlyProvider(provider, json2) {
|
|
83399
|
+
const url2 = getWebAppURL();
|
|
83400
|
+
if (json2) {
|
|
83401
|
+
printResult({ provider, cli_supported: false, connect_via: "web", url: url2 }, { json: true });
|
|
83402
|
+
} else {
|
|
83403
|
+
console.error(import_picocolors25.default.yellow(`Connecting '${provider}' sources is web-only for now. Open ${url2}, add the source ` + `under Data Sources, then re-run 'dosu sources list'.`));
|
|
83404
|
+
}
|
|
83405
|
+
process.exit(1);
|
|
83406
|
+
}
|
|
81609
83407
|
function sourcesCommand() {
|
|
81610
83408
|
const cmd = new Command("sources").description("Manage connected data sources");
|
|
81611
83409
|
cmd.command("list").description("List all connected data sources").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -81688,6 +83486,154 @@ function sourcesCommand() {
|
|
|
81688
83486
|
}
|
|
81689
83487
|
console.log(import_picocolors25.default.green("Data source deleted."));
|
|
81690
83488
|
});
|
|
83489
|
+
cmd.command("connect").description("Install or update a provider integration (currently GitHub) via the browser").argument("[provider]", "Provider to connect (github)", "github").option("--json", "Emit machine-readable NDJSON progress events").option("--timeout <seconds>", "Seconds to wait for the browser install to complete", String(CONNECT_TIMEOUT_DEFAULT_SECONDS)).option("--no-open", "Print the URL without opening a browser").action(async (provider, opts) => {
|
|
83490
|
+
const providerName = provider.toLowerCase();
|
|
83491
|
+
if (providerName !== "github") {
|
|
83492
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83493
|
+
return;
|
|
83494
|
+
}
|
|
83495
|
+
const cfg = requireConfig11();
|
|
83496
|
+
const client = createTypedClient(cfg);
|
|
83497
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83498
|
+
const timeoutSeconds = Math.max(1, Number.parseInt(opts.timeout ?? "", 10)) || CONNECT_TIMEOUT_DEFAULT_SECONDS;
|
|
83499
|
+
const before = await fetchListForOrg(client, orgID);
|
|
83500
|
+
const { server, installationPromise } = await startInstallationCallbackServer();
|
|
83501
|
+
let timeoutId;
|
|
83502
|
+
try {
|
|
83503
|
+
const connectURL = new URL("/cli/connect-github", getWebAppURL());
|
|
83504
|
+
connectURL.searchParams.set("callback", `http://localhost:${server.port}/callback`);
|
|
83505
|
+
const url2 = connectURL.toString();
|
|
83506
|
+
if (opts.json) {
|
|
83507
|
+
console.log(JSON.stringify({
|
|
83508
|
+
event: "awaiting_install",
|
|
83509
|
+
provider: "github",
|
|
83510
|
+
url: url2,
|
|
83511
|
+
timeout_seconds: timeoutSeconds
|
|
83512
|
+
}));
|
|
83513
|
+
} else {
|
|
83514
|
+
console.log(`Open this URL to install or update the Dosu GitHub App:
|
|
83515
|
+
${import_picocolors25.default.cyan(url2)}`);
|
|
83516
|
+
if (opts.open !== false) {
|
|
83517
|
+
try {
|
|
83518
|
+
const open5 = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
83519
|
+
await open5.default(url2);
|
|
83520
|
+
} catch {}
|
|
83521
|
+
}
|
|
83522
|
+
console.log(import_picocolors25.default.dim("Waiting for the install to complete..."));
|
|
83523
|
+
}
|
|
83524
|
+
const timedOut = new Promise((resolve6) => {
|
|
83525
|
+
timeoutId = setTimeout(() => resolve6(null), timeoutSeconds * 1000);
|
|
83526
|
+
});
|
|
83527
|
+
const result = await Promise.race([installationPromise, timedOut]);
|
|
83528
|
+
if (result === null) {
|
|
83529
|
+
if (opts.json) {
|
|
83530
|
+
console.log(JSON.stringify({ event: "timeout", timeout_seconds: timeoutSeconds }));
|
|
83531
|
+
} else {
|
|
83532
|
+
console.error(import_picocolors25.default.red(`Timed out after ${timeoutSeconds}s. Finish the install in the browser, then re-run 'dosu sources connect github'.`));
|
|
83533
|
+
}
|
|
83534
|
+
process.exit(1);
|
|
83535
|
+
return;
|
|
83536
|
+
}
|
|
83537
|
+
const refresh = await waitForRepositoryRefresh(client, orgID, before);
|
|
83538
|
+
const beforeIds = new Set(before.map((repo) => repo.repository_id));
|
|
83539
|
+
const newRepositories = refresh.repos.filter((repo) => !beforeIds.has(repo.repository_id)).map((repo) => ({ slug: repo.slug, repository_id: repo.repository_id }));
|
|
83540
|
+
if (opts.json) {
|
|
83541
|
+
console.log(JSON.stringify({
|
|
83542
|
+
event: "installed",
|
|
83543
|
+
installation_id: result.installation_id,
|
|
83544
|
+
new_repositories: newRepositories
|
|
83545
|
+
}));
|
|
83546
|
+
return;
|
|
83547
|
+
}
|
|
83548
|
+
console.log(import_picocolors25.default.green(`GitHub App connected (installation ${result.installation_id}).`));
|
|
83549
|
+
if (newRepositories.length > 0) {
|
|
83550
|
+
for (const repo of newRepositories) {
|
|
83551
|
+
console.log(` ${repo.slug}`);
|
|
83552
|
+
}
|
|
83553
|
+
console.log(import_picocolors25.default.dim("Next: dosu sources create github --repo <owner/name> --library <library-id>"));
|
|
83554
|
+
} else {
|
|
83555
|
+
console.log(import_picocolors25.default.dim("No new repositories visible yet — GitHub may still be syncing. " + "Re-run 'dosu sources connect github' or check 'dosu sources list' shortly."));
|
|
83556
|
+
}
|
|
83557
|
+
} finally {
|
|
83558
|
+
clearTimeout(timeoutId);
|
|
83559
|
+
server.close();
|
|
83560
|
+
}
|
|
83561
|
+
});
|
|
83562
|
+
cmd.command("create").description("Create a data source from an installed provider and attach it to a library (requires confirmation)").argument("<provider>", "Provider (currently: github)").requiredOption("--repo <owner/name>", "GitHub repository slug visible to the Dosu app").option("--library <id>", "Library (space) ID to attach to; defaults to the active library").option("--confirm", "Apply without the interactive prompt").option("--json", "Output as JSON").action(async (provider, opts) => {
|
|
83563
|
+
const providerName = provider.toLowerCase();
|
|
83564
|
+
if (providerName !== "github") {
|
|
83565
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83566
|
+
return;
|
|
83567
|
+
}
|
|
83568
|
+
const cfg = requireConfig11();
|
|
83569
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83570
|
+
const libraryID = opts.library ?? cfg.active_account?.target?.space_id;
|
|
83571
|
+
if (!libraryID) {
|
|
83572
|
+
console.error(import_picocolors25.default.red("No library specified and no active library. Pass --library <id>."));
|
|
83573
|
+
process.exit(1);
|
|
83574
|
+
return;
|
|
83575
|
+
}
|
|
83576
|
+
const client = createTypedClient(cfg);
|
|
83577
|
+
const repos = await fetchListForOrg(client, orgID);
|
|
83578
|
+
const repo = repos.find((r9) => r9.slug.toLowerCase() === opts.repo.toLowerCase());
|
|
83579
|
+
if (!repo) {
|
|
83580
|
+
console.error(import_picocolors25.default.red(`Repository '${opts.repo}' is not visible to Dosu. Run ` + `'dosu sources connect github' to grant the GitHub App access, then retry.`));
|
|
83581
|
+
process.exit(1);
|
|
83582
|
+
return;
|
|
83583
|
+
}
|
|
83584
|
+
if (repo.is_fork === true) {
|
|
83585
|
+
console.error(import_picocolors25.default.red(`'${repo.slug}' is a fork and can't be synced.` + (repo.fork_parent_slug ? ` Connect '${repo.fork_parent_slug}' instead.` : "")));
|
|
83586
|
+
process.exit(1);
|
|
83587
|
+
return;
|
|
83588
|
+
}
|
|
83589
|
+
if (!await confirmAction({
|
|
83590
|
+
confirmed: opts.confirm,
|
|
83591
|
+
json: opts.json,
|
|
83592
|
+
message: `Create a data source for ${repo.slug} and attach it to library ${libraryID}?`,
|
|
83593
|
+
preview: {
|
|
83594
|
+
action: "create_source",
|
|
83595
|
+
provider: "github",
|
|
83596
|
+
repository: repo.slug,
|
|
83597
|
+
repository_id: repo.repository_id,
|
|
83598
|
+
library_id: libraryID
|
|
83599
|
+
}
|
|
83600
|
+
}))
|
|
83601
|
+
return;
|
|
83602
|
+
const existingDeployments = await fetchOrgGithubDeployments(client, orgID);
|
|
83603
|
+
const existingDataSources = await fetchOrgGithubDataSources(client, orgID);
|
|
83604
|
+
const created = await createDeploymentForRepo(client, orgID, libraryID, repo, {
|
|
83605
|
+
deploymentID: existingDeployments.get(repo.repository_id),
|
|
83606
|
+
dataSourceID: existingDataSources.get(repo.repository_id)
|
|
83607
|
+
});
|
|
83608
|
+
if (!created) {
|
|
83609
|
+
console.error(import_picocolors25.default.red("Could not create the data source. Check 'dosu logs --tail 50' for details."));
|
|
83610
|
+
process.exit(1);
|
|
83611
|
+
return;
|
|
83612
|
+
}
|
|
83613
|
+
const survivors = await verifyDataSourcesPersist(client, orgID, [created.data_source_id]);
|
|
83614
|
+
if (survivors.dropped.size > 0) {
|
|
83615
|
+
await deleteOrphanDeployment(client, created.deployment_id, repo.slug);
|
|
83616
|
+
console.error(import_picocolors25.default.red(`Dosu couldn't sync '${repo.slug}' (the GitHub App may not have access to it). ` + "Nothing was created."));
|
|
83617
|
+
process.exit(1);
|
|
83618
|
+
return;
|
|
83619
|
+
}
|
|
83620
|
+
const receipt = {
|
|
83621
|
+
provider: "github",
|
|
83622
|
+
repository: repo.slug,
|
|
83623
|
+
repository_id: repo.repository_id,
|
|
83624
|
+
data_source_id: created.data_source_id,
|
|
83625
|
+
deployment_id: created.deployment_id,
|
|
83626
|
+
library_id: libraryID,
|
|
83627
|
+
attached: true
|
|
83628
|
+
};
|
|
83629
|
+
if (opts.json) {
|
|
83630
|
+
printResult(receipt, opts);
|
|
83631
|
+
return;
|
|
83632
|
+
}
|
|
83633
|
+
console.log(import_picocolors25.default.green(`Connected ${repo.slug} to library ${libraryID}.`));
|
|
83634
|
+
console.log(import_picocolors25.default.dim(` data source ${created.data_source_id}`));
|
|
83635
|
+
console.log(import_picocolors25.default.dim(` deployment ${created.deployment_id}`));
|
|
83636
|
+
});
|
|
81691
83637
|
return cmd;
|
|
81692
83638
|
}
|
|
81693
83639
|
|
|
@@ -81957,7 +83903,7 @@ init_skill();
|
|
|
81957
83903
|
var import_picocolors28 = __toESM(require_picocolors(), 1);
|
|
81958
83904
|
import { spawnSync } from "node:child_process";
|
|
81959
83905
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
81960
|
-
import { homedir as
|
|
83906
|
+
import { homedir as homedir7 } from "node:os";
|
|
81961
83907
|
import { posix, win32 as win322 } from "node:path";
|
|
81962
83908
|
var PACKAGE_NAME = "@dosu/cli";
|
|
81963
83909
|
var LATEST_PACKAGE = `${PACKAGE_NAME}@latest`;
|
|
@@ -82034,7 +83980,7 @@ function packageRoots(manager, platform3, comSpec, env, cwd) {
|
|
|
82034
83980
|
manager === "npm" ? path3.join(lines[0], "@dosu", "cli") : path3.join(lines[0], "node_modules", "@dosu", "cli")
|
|
82035
83981
|
];
|
|
82036
83982
|
}
|
|
82037
|
-
function owningPackageManager(entrypoint, platform3, comSpec, env = process.env, cwd =
|
|
83983
|
+
function owningPackageManager(entrypoint, platform3, comSpec, env = process.env, cwd = homedir7()) {
|
|
82038
83984
|
const path3 = platform3 === "win32" ? win322 : posix;
|
|
82039
83985
|
if (!entrypoint || !path3.isAbsolute(entrypoint))
|
|
82040
83986
|
return null;
|
|
@@ -82087,7 +84033,7 @@ Or install Dosu globally with your package manager:`);
|
|
|
82087
84033
|
function runUpgrade(channel = INSTALL_CHANNEL, options = {}) {
|
|
82088
84034
|
const platform3 = options.platform ?? process.platform;
|
|
82089
84035
|
const env = options.env ?? process.env;
|
|
82090
|
-
const cwd = options.cwd ??
|
|
84036
|
+
const cwd = options.cwd ?? homedir7();
|
|
82091
84037
|
let manager = null;
|
|
82092
84038
|
if (channel === "npm") {
|
|
82093
84039
|
if (isNpxInvocation(channel, env)) {
|
|
@@ -82248,8 +84194,8 @@ async function finishTelemetry(operation) {
|
|
|
82248
84194
|
try {
|
|
82249
84195
|
await Promise.race([
|
|
82250
84196
|
Promise.resolve().then(operation),
|
|
82251
|
-
new Promise((
|
|
82252
|
-
timeout = setTimeout(
|
|
84197
|
+
new Promise((resolve7) => {
|
|
84198
|
+
timeout = setTimeout(resolve7, TELEMETRY_FLUSH_TIMEOUT_MS);
|
|
82253
84199
|
})
|
|
82254
84200
|
]);
|
|
82255
84201
|
} catch {} finally {
|
|
@@ -82690,4 +84636,4 @@ execute().catch((err) => {
|
|
|
82690
84636
|
process.exit(1);
|
|
82691
84637
|
});
|
|
82692
84638
|
|
|
82693
|
-
//# debugId=
|
|
84639
|
+
//# debugId=fa7cf492-7aa5-180c-6475-6e2164756e21
|