@dosu/cli 0.53.0-beta.1 → 0.53.0-beta.10
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 +2594 -668
- 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.10", 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 = "6096b455-0361-b435-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,6 +75449,13 @@ function reduceSyncConfirmKey(key) {
|
|
|
73847
75449
|
return "cancel";
|
|
73848
75450
|
return "none";
|
|
73849
75451
|
}
|
|
75452
|
+
function syncConfirmMode(status) {
|
|
75453
|
+
if (status.running)
|
|
75454
|
+
return "stop";
|
|
75455
|
+
if (status.state.paused)
|
|
75456
|
+
return "resume";
|
|
75457
|
+
return "start";
|
|
75458
|
+
}
|
|
73850
75459
|
function formatActivityLine(line, width) {
|
|
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
75461
|
if (compact.length <= width)
|
|
@@ -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,19 +75636,26 @@ 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
|
|
75639
|
+
const fullRows = Boolean(pane.fullRows) && pane.tab !== "activity";
|
|
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);
|
|
74057
75644
|
const source = pane.tab === "activity" ? activity.map((line) => formatActivityLine(line, width)) : 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`);
|
|
74065
75654
|
if (below > 0)
|
|
74066
75655
|
scrollParts.push(`↓ ${below} newer`);
|
|
75656
|
+
if (pane.tab !== "activity" && visible.length > 0) {
|
|
75657
|
+
scrollParts.push(fullRows ? `f clip · copy: dosu knowledge sessions --${pane.tab}` : `f full rows · dosu knowledge sessions --${pane.tab}`);
|
|
75658
|
+
}
|
|
74067
75659
|
const lines = [
|
|
74068
75660
|
breadcrumb(["Home", "Activity"], width),
|
|
74069
75661
|
"",
|
|
@@ -74075,13 +75667,13 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
|
|
|
74075
75667
|
"",
|
|
74076
75668
|
...tabBar(pane.tab, queued.length, open5.length, status.state.total_mined ?? minedRows.length, width),
|
|
74077
75669
|
...listRows,
|
|
74078
|
-
...scrollParts.length > 0 ? [import_picocolors30.default.dim(scrollParts.join(" · "))] : [],
|
|
75670
|
+
...scrollParts.length > 0 ? [import_picocolors30.default.dim(clip(scrollParts.join(" · "), width))] : [],
|
|
74079
75671
|
"",
|
|
74080
|
-
...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width), width) : [
|
|
75672
|
+
...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width, pane.confirm), width) : [
|
|
74081
75673
|
import_picocolors30.default.dim([
|
|
74082
75674
|
"tab switch",
|
|
74083
75675
|
"↑↓ scroll",
|
|
74084
|
-
|
|
75676
|
+
status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
|
|
74085
75677
|
"esc back"
|
|
74086
75678
|
].join(" · "))
|
|
74087
75679
|
]
|
|
@@ -74104,8 +75696,10 @@ function runActivityView(io2 = {}) {
|
|
|
74104
75696
|
const getStatus = io2.getStatus ?? (() => getSyncStatus({ readLog: () => "" }));
|
|
74105
75697
|
const readLog = io2.readLog ?? defaultReadLog2;
|
|
74106
75698
|
const createFollower = io2.createFollower ?? ((emit) => createLogFollower(logger.getLogPath(), emit));
|
|
74107
|
-
const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--
|
|
74108
|
-
const
|
|
75699
|
+
const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--bootstrap"]));
|
|
75700
|
+
const stopSync = io2.stopSync ?? stopSyncRun;
|
|
75701
|
+
const setPaused = io2.setPaused ?? setSyncPaused;
|
|
75702
|
+
const listBacklog = io2.listBacklog ?? listSessionBacklog;
|
|
74109
75703
|
const pollMs = io2.pollMs ?? ACTIVITY_VIEW_POLL_MS;
|
|
74110
75704
|
const seed = readLog();
|
|
74111
75705
|
let activity = appendSyncActivity([], seed);
|
|
@@ -74113,7 +75707,8 @@ function runActivityView(io2 = {}) {
|
|
|
74113
75707
|
let runProgress = foldRunProgress(null, seed);
|
|
74114
75708
|
let tab = "activity";
|
|
74115
75709
|
let scroll = 0;
|
|
74116
|
-
let
|
|
75710
|
+
let fullRows = false;
|
|
75711
|
+
let confirmSync = null;
|
|
74117
75712
|
let status = getStatus();
|
|
74118
75713
|
let sessions = listBacklog();
|
|
74119
75714
|
let queuedWatermark = status.state.watermark;
|
|
@@ -74131,12 +75726,18 @@ function runActivityView(io2 = {}) {
|
|
|
74131
75726
|
return sessions.open.length;
|
|
74132
75727
|
return (status.state.mined_sessions ?? []).length;
|
|
74133
75728
|
};
|
|
75729
|
+
const listHeight = () => fullRows && tab !== "activity" ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
74134
75730
|
let lastFrame = null;
|
|
74135
75731
|
let minedBeforeRun = null;
|
|
74136
75732
|
const draw = () => {
|
|
74137
75733
|
status = getStatus();
|
|
74138
75734
|
if (status.running) {
|
|
74139
|
-
|
|
75735
|
+
const run2 = status.state.run;
|
|
75736
|
+
if (run2 && run2.pid === status.pid) {
|
|
75737
|
+
minedBeforeRun = run2.baseline_mined;
|
|
75738
|
+
} else {
|
|
75739
|
+
minedBeforeRun ??= status.state.total_mined ?? 0;
|
|
75740
|
+
}
|
|
74140
75741
|
} else {
|
|
74141
75742
|
minedBeforeRun = null;
|
|
74142
75743
|
}
|
|
@@ -74144,10 +75745,12 @@ function runActivityView(io2 = {}) {
|
|
|
74144
75745
|
queuedWatermark = status.state.watermark;
|
|
74145
75746
|
sessions = listBacklog();
|
|
74146
75747
|
}
|
|
74147
|
-
if (confirmSync && status.running)
|
|
74148
|
-
confirmSync =
|
|
75748
|
+
if ((confirmSync === "start" || confirmSync === "resume") && status.running)
|
|
75749
|
+
confirmSync = null;
|
|
75750
|
+
if (confirmSync === "stop" && !status.running)
|
|
75751
|
+
confirmSync = null;
|
|
74149
75752
|
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);
|
|
75753
|
+
const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync ?? undefined, fullRows }, sessions.queued, minedBeforeRun ?? 0, sessions.open, runProgress);
|
|
74151
75754
|
if (frame === lastFrame)
|
|
74152
75755
|
return;
|
|
74153
75756
|
lastFrame = frame;
|
|
@@ -74167,7 +75770,7 @@ ${CLEAR_BELOW}`);
|
|
|
74167
75770
|
const leaveAltScreen = enterAltScreen(output2);
|
|
74168
75771
|
output2.write(HIDE_CURSOR3);
|
|
74169
75772
|
draw();
|
|
74170
|
-
return new Promise((
|
|
75773
|
+
return new Promise((resolve7) => {
|
|
74171
75774
|
const wasRaw = input2.isRaw ?? false;
|
|
74172
75775
|
input2.setRawMode?.(true);
|
|
74173
75776
|
input2.resume();
|
|
@@ -74184,21 +75787,32 @@ ${CLEAR_BELOW}`);
|
|
|
74184
75787
|
input2.pause();
|
|
74185
75788
|
leaveAltScreen();
|
|
74186
75789
|
output2.write(SHOW_CURSOR3);
|
|
74187
|
-
|
|
75790
|
+
resolve7();
|
|
74188
75791
|
};
|
|
74189
75792
|
const onData = (chunk) => {
|
|
74190
75793
|
for (const key of parseKeys(chunk.toString())) {
|
|
74191
75794
|
if (confirmSync) {
|
|
74192
75795
|
const decision = reduceSyncConfirmKey(key);
|
|
74193
75796
|
if (decision === "start") {
|
|
74194
|
-
|
|
74195
|
-
|
|
74196
|
-
|
|
75797
|
+
const mode = confirmSync;
|
|
75798
|
+
confirmSync = null;
|
|
75799
|
+
let note;
|
|
75800
|
+
if (mode === "stop") {
|
|
75801
|
+
const ok2 = status.pid !== undefined && stopSync(status.pid);
|
|
75802
|
+
if (ok2)
|
|
75803
|
+
setPaused(true);
|
|
75804
|
+
note = ok2 ? "[sync] mining stopped · paused until you resume" : "[sync] could not stop the run · it may have just finished";
|
|
75805
|
+
} else {
|
|
75806
|
+
if (mode === "resume")
|
|
75807
|
+
setPaused(false);
|
|
75808
|
+
const ok2 = startSync();
|
|
75809
|
+
note = ok2 ? "[sync] sync requested · starting a background run" : "[sync] could not start a background run · try `dosu knowledge sync`";
|
|
75810
|
+
}
|
|
74197
75811
|
activity = appendSyncActivity(activity, `[${new Date().toISOString()}] [INFO] ${note}
|
|
74198
75812
|
`);
|
|
74199
75813
|
draw();
|
|
74200
75814
|
} else if (decision === "cancel") {
|
|
74201
|
-
confirmSync =
|
|
75815
|
+
confirmSync = null;
|
|
74202
75816
|
draw();
|
|
74203
75817
|
}
|
|
74204
75818
|
continue;
|
|
@@ -74209,18 +75823,22 @@ ${CLEAR_BELOW}`);
|
|
|
74209
75823
|
return;
|
|
74210
75824
|
}
|
|
74211
75825
|
if (action === "sync") {
|
|
74212
|
-
|
|
74213
|
-
|
|
74214
|
-
draw();
|
|
74215
|
-
}
|
|
75826
|
+
confirmSync = syncConfirmMode(status);
|
|
75827
|
+
draw();
|
|
74216
75828
|
} else if (action === "tab" || action === "tab-back") {
|
|
74217
75829
|
tab = cycleTab(tab, action === "tab" ? 1 : -1);
|
|
74218
75830
|
scroll = 0;
|
|
74219
75831
|
if (tab === "queued" || tab === "open")
|
|
74220
75832
|
sessions = listBacklog();
|
|
74221
75833
|
draw();
|
|
75834
|
+
} else if (action === "full") {
|
|
75835
|
+
if (tab !== "activity") {
|
|
75836
|
+
fullRows = !fullRows;
|
|
75837
|
+
scroll = 0;
|
|
75838
|
+
draw();
|
|
75839
|
+
}
|
|
74222
75840
|
} else if (action === "up") {
|
|
74223
|
-
const maxScroll = Math.max(0, activeListLength() -
|
|
75841
|
+
const maxScroll = Math.max(0, activeListLength() - listHeight());
|
|
74224
75842
|
if (scroll < maxScroll) {
|
|
74225
75843
|
scroll += 1;
|
|
74226
75844
|
draw();
|
|
@@ -74236,14 +75854,14 @@ ${CLEAR_BELOW}`);
|
|
|
74236
75854
|
input2.on("data", onData);
|
|
74237
75855
|
});
|
|
74238
75856
|
}
|
|
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;
|
|
75857
|
+
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
75858
|
var init_activity_view = __esm(() => {
|
|
74241
75859
|
init_follow();
|
|
74242
75860
|
init_logger();
|
|
74243
|
-
init_project_dir();
|
|
74244
|
-
init_scan();
|
|
74245
75861
|
init_styles();
|
|
75862
|
+
init_backlog();
|
|
74246
75863
|
init_detach();
|
|
75864
|
+
init_lock();
|
|
74247
75865
|
init_status();
|
|
74248
75866
|
init_watermark();
|
|
74249
75867
|
init_alt_screen();
|
|
@@ -74694,8 +76312,8 @@ h1 {
|
|
|
74694
76312
|
async function startCallbackServer(opts = {}) {
|
|
74695
76313
|
let resolveToken;
|
|
74696
76314
|
let rejectToken;
|
|
74697
|
-
const tokenPromise = new Promise((
|
|
74698
|
-
resolveToken =
|
|
76315
|
+
const tokenPromise = new Promise((resolve7, reject) => {
|
|
76316
|
+
resolveToken = resolve7;
|
|
74699
76317
|
rejectToken = reject;
|
|
74700
76318
|
});
|
|
74701
76319
|
const http = __require("node:http");
|
|
@@ -74753,8 +76371,8 @@ async function startCallbackServer(opts = {}) {
|
|
|
74753
76371
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
74754
76372
|
res.end(buildSuccessHtml(email3 ?? undefined, opts.successVariant ?? "auth"));
|
|
74755
76373
|
});
|
|
74756
|
-
await new Promise((
|
|
74757
|
-
httpServer.listen(0, "localhost", () =>
|
|
76374
|
+
await new Promise((resolve7) => {
|
|
76375
|
+
httpServer.listen(0, "localhost", () => resolve7());
|
|
74758
76376
|
});
|
|
74759
76377
|
httpServer.unref();
|
|
74760
76378
|
const addr = httpServer.address();
|
|
@@ -75883,12 +77501,21 @@ var init_flow2 = __esm(() => {
|
|
|
75883
77501
|
function reduceAnalyticsViewKey(key) {
|
|
75884
77502
|
if (key === "q" || key === ESC8 || key === CTRL_C5)
|
|
75885
77503
|
return "back";
|
|
77504
|
+
if (key === "\t" || key === KEY_RIGHT4)
|
|
77505
|
+
return "tab";
|
|
77506
|
+
if (key === KEY_LEFT4)
|
|
77507
|
+
return "tab-back";
|
|
75886
77508
|
if (key === KEY_UP5 || key === "k")
|
|
75887
77509
|
return "up";
|
|
75888
77510
|
if (key === KEY_DOWN5 || key === "j")
|
|
75889
77511
|
return "down";
|
|
75890
77512
|
return "none";
|
|
75891
77513
|
}
|
|
77514
|
+
function cycleAnalyticsTab(tab, delta = 1) {
|
|
77515
|
+
const index = ANALYTICS_VIEW_TABS.indexOf(tab);
|
|
77516
|
+
const next = (index + delta + ANALYTICS_VIEW_TABS.length) % ANALYTICS_VIEW_TABS.length;
|
|
77517
|
+
return ANALYTICS_VIEW_TABS[next];
|
|
77518
|
+
}
|
|
75892
77519
|
function clip2(text, max) {
|
|
75893
77520
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
75894
77521
|
}
|
|
@@ -75925,38 +77552,37 @@ function loadPageStatsFromConfig() {
|
|
|
75925
77552
|
return Promise.resolve(null);
|
|
75926
77553
|
}
|
|
75927
77554
|
}
|
|
75928
|
-
function
|
|
77555
|
+
function overviewRows(state) {
|
|
75929
77556
|
const minedTotal = state.total_mined ?? 0;
|
|
75930
77557
|
const notes = state.total_notes ?? 0;
|
|
75931
77558
|
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
|
-
}
|
|
77559
|
+
if (minedTotal === 0 && notes === 0)
|
|
77560
|
+
return [];
|
|
77561
|
+
const rows = [`${label("Sessions mined")}${minedTotal}`, `${label("Suggested pages")}${notes}`];
|
|
77562
|
+
if (tokens > 0) {
|
|
77563
|
+
rows.push(`${label("Investigation distilled")}${formatTokenCount(tokens)} tokens`);
|
|
77564
|
+
}
|
|
77565
|
+
return rows;
|
|
77566
|
+
}
|
|
77567
|
+
function projectRows(state) {
|
|
77568
|
+
const byProject = new Map;
|
|
77569
|
+
for (const record2 of state.mined_sessions ?? []) {
|
|
77570
|
+
const key = record2.project ?? "(unknown)";
|
|
77571
|
+
byProject.set(key, (byProject.get(key) ?? 0) + 1);
|
|
75951
77572
|
}
|
|
77573
|
+
const data = [...byProject.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([project, count]) => `${label(clip2(project, 24))}${count}`);
|
|
77574
|
+
return data.length > 0 ? [`${label("Project")}Sessions`, ...data] : [];
|
|
77575
|
+
}
|
|
77576
|
+
function pageRows(pageStats) {
|
|
77577
|
+
const rows = [];
|
|
75952
77578
|
if (pageStats && pageStats.topCited.length > 0) {
|
|
75953
|
-
rows.push(
|
|
77579
|
+
rows.push(`${label(`Top cited (${TOP_CITED_DAYS}d)`)}Citations`);
|
|
75954
77580
|
for (const row of pageStats.topCited) {
|
|
75955
77581
|
rows.push(`${label(clip2(row.title, 24))}${row.citation_count}`);
|
|
75956
77582
|
}
|
|
75957
77583
|
}
|
|
75958
77584
|
if (pageStats && pageStats.topUpdated.length > 0) {
|
|
75959
|
-
rows.push(...rows.length > 0 ? [""] : [], "Recently updated
|
|
77585
|
+
rows.push(...rows.length > 0 ? [""] : [], `${label("Recently updated")}Updated`);
|
|
75960
77586
|
for (const row of pageStats.topUpdated) {
|
|
75961
77587
|
rows.push(`${label(clip2(row.title, 24))}${row.updated_at.slice(0, 10)}`);
|
|
75962
77588
|
}
|
|
@@ -75971,22 +77597,36 @@ function windowReport(lines, scroll, height = ANALYTICS_VIEW_LINES) {
|
|
|
75971
77597
|
below: Math.max(0, lines.length - clamped - height)
|
|
75972
77598
|
};
|
|
75973
77599
|
}
|
|
75974
|
-
function
|
|
75975
|
-
|
|
77600
|
+
function analyticsTabRows(tab, state, pageStats) {
|
|
77601
|
+
if (tab === "overview")
|
|
77602
|
+
return overviewRows(state);
|
|
77603
|
+
if (tab === "projects")
|
|
77604
|
+
return projectRows(state);
|
|
77605
|
+
return pageRows(pageStats);
|
|
77606
|
+
}
|
|
77607
|
+
function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending = false, width = contentWidth()) {
|
|
77608
|
+
const rows = analyticsTabRows(tab, state, pageStats);
|
|
75976
77609
|
const { visible, above, below } = windowReport(rows, scroll);
|
|
75977
|
-
const
|
|
77610
|
+
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.";
|
|
77611
|
+
const listRows = visible.length > 0 ? visible.map((row) => import_picocolors32.default.dim(row)) : [import_picocolors32.default.dim(empty)];
|
|
75978
77612
|
const scrollParts = [];
|
|
75979
77613
|
if (above > 0)
|
|
75980
77614
|
scrollParts.push(`↑ ${above} earlier`);
|
|
75981
77615
|
if (below > 0)
|
|
75982
77616
|
scrollParts.push(`↓ ${below} more`);
|
|
75983
77617
|
const lines = [
|
|
75984
|
-
breadcrumb(["Home", "Analytics"]),
|
|
77618
|
+
breadcrumb(["Home", "Analytics"], width),
|
|
77619
|
+
"",
|
|
77620
|
+
...tabStrip([
|
|
77621
|
+
["overview", "Overview"],
|
|
77622
|
+
["projects", "Projects"],
|
|
77623
|
+
["pages", "Pages"]
|
|
77624
|
+
], tab, width, { spread: false }),
|
|
75985
77625
|
"",
|
|
75986
77626
|
...listRows,
|
|
75987
77627
|
...scrollParts.length > 0 ? [import_picocolors32.default.dim(scrollParts.join(" · "))] : [],
|
|
75988
77628
|
"",
|
|
75989
|
-
import_picocolors32.default.dim("↑↓ scroll · esc back")
|
|
77629
|
+
import_picocolors32.default.dim("tab switch · ↑↓ scroll · esc back")
|
|
75990
77630
|
];
|
|
75991
77631
|
return lines.join(`
|
|
75992
77632
|
`);
|
|
@@ -75999,14 +77639,17 @@ function runAnalyticsView(io2 = {}) {
|
|
|
75999
77639
|
const getStatus = io2.getStatus ?? (() => getSyncStatus({ readLog: () => "" }));
|
|
76000
77640
|
const loadPageStats = io2.loadPageStats ?? loadPageStatsFromConfig;
|
|
76001
77641
|
const pollMs = io2.pollMs ?? ANALYTICS_VIEW_POLL_MS;
|
|
77642
|
+
let tab = "overview";
|
|
76002
77643
|
let scroll = 0;
|
|
76003
77644
|
let status = getStatus();
|
|
76004
77645
|
let pageStats = null;
|
|
77646
|
+
let pagesPending = true;
|
|
76005
77647
|
let closed = false;
|
|
76006
77648
|
let lastFrame = null;
|
|
76007
77649
|
const draw = () => {
|
|
76008
77650
|
status = getStatus();
|
|
76009
|
-
const
|
|
77651
|
+
const width = Math.min(contentWidth(output2.columns ?? 80), (output2.columns ?? 80) - 1);
|
|
77652
|
+
const frame = renderAnalyticsFrame(status.state, tab, scroll, pageStats, pagesPending, width);
|
|
76010
77653
|
if (frame === lastFrame)
|
|
76011
77654
|
return;
|
|
76012
77655
|
lastFrame = frame;
|
|
@@ -76027,12 +77670,13 @@ ${CLEAR_BELOW2}`);
|
|
|
76027
77670
|
output2.write(HIDE_CURSOR4);
|
|
76028
77671
|
draw();
|
|
76029
77672
|
loadPageStats().then((stats) => {
|
|
76030
|
-
if (closed
|
|
77673
|
+
if (closed)
|
|
76031
77674
|
return;
|
|
76032
77675
|
pageStats = stats;
|
|
77676
|
+
pagesPending = false;
|
|
76033
77677
|
draw();
|
|
76034
77678
|
});
|
|
76035
|
-
return new Promise((
|
|
77679
|
+
return new Promise((resolve7) => {
|
|
76036
77680
|
const wasRaw = input2.isRaw ?? false;
|
|
76037
77681
|
input2.setRawMode?.(true);
|
|
76038
77682
|
input2.resume();
|
|
@@ -76047,7 +77691,7 @@ ${CLEAR_BELOW2}`);
|
|
|
76047
77691
|
input2.pause();
|
|
76048
77692
|
leaveAltScreen();
|
|
76049
77693
|
output2.write(SHOW_CURSOR4);
|
|
76050
|
-
|
|
77694
|
+
resolve7();
|
|
76051
77695
|
};
|
|
76052
77696
|
const onData = (chunk) => {
|
|
76053
77697
|
for (const key of parseKeys(chunk.toString())) {
|
|
@@ -76056,13 +77700,17 @@ ${CLEAR_BELOW2}`);
|
|
|
76056
77700
|
finish();
|
|
76057
77701
|
return;
|
|
76058
77702
|
}
|
|
76059
|
-
if (action === "
|
|
77703
|
+
if (action === "tab" || action === "tab-back") {
|
|
77704
|
+
tab = cycleAnalyticsTab(tab, action === "tab" ? 1 : -1);
|
|
77705
|
+
scroll = 0;
|
|
77706
|
+
draw();
|
|
77707
|
+
} else if (action === "up") {
|
|
76060
77708
|
if (scroll > 0) {
|
|
76061
77709
|
scroll -= 1;
|
|
76062
77710
|
draw();
|
|
76063
77711
|
}
|
|
76064
77712
|
} else if (action === "down") {
|
|
76065
|
-
const maxScroll = Math.max(0,
|
|
77713
|
+
const maxScroll = Math.max(0, analyticsTabRows(tab, status.state, pageStats).length - ANALYTICS_VIEW_LINES);
|
|
76066
77714
|
if (scroll < maxScroll) {
|
|
76067
77715
|
scroll += 1;
|
|
76068
77716
|
draw();
|
|
@@ -76073,7 +77721,7 @@ ${CLEAR_BELOW2}`);
|
|
|
76073
77721
|
input2.on("data", onData);
|
|
76074
77722
|
});
|
|
76075
77723
|
}
|
|
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 =
|
|
77724
|
+
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
77725
|
var init_analytics_view = __esm(() => {
|
|
76078
77726
|
init_trpc();
|
|
76079
77727
|
init_config();
|
|
@@ -76088,9 +77736,12 @@ var init_analytics_view = __esm(() => {
|
|
|
76088
77736
|
CTRL_C5 = String.fromCharCode(3);
|
|
76089
77737
|
KEY_UP5 = `${ESC8}[A`;
|
|
76090
77738
|
KEY_DOWN5 = `${ESC8}[B`;
|
|
77739
|
+
KEY_RIGHT4 = `${ESC8}[C`;
|
|
77740
|
+
KEY_LEFT4 = `${ESC8}[D`;
|
|
76091
77741
|
CURSOR_HOME2 = `${ESC8}[H`;
|
|
76092
77742
|
CLEAR_BELOW2 = `${ESC8}[0J`;
|
|
76093
77743
|
CLEAR_EOL2 = `${ESC8}[K`;
|
|
77744
|
+
ANALYTICS_VIEW_TABS = ["overview", "projects", "pages"];
|
|
76094
77745
|
});
|
|
76095
77746
|
|
|
76096
77747
|
// src/tui/banner.ts
|
|
@@ -76144,8 +77795,8 @@ function checklistRows(ctx) {
|
|
|
76144
77795
|
`${import_picocolors33.default.yellow(`↑ ${ctx.update.version} available`)} ${import_picocolors33.default.dim(`${DOT3} ${ctx.update.hint}`)}`
|
|
76145
77796
|
]);
|
|
76146
77797
|
}
|
|
76147
|
-
const labelWidth = Math.max(...rows.map(([
|
|
76148
|
-
return rows.map(([
|
|
77798
|
+
const labelWidth = Math.max(...rows.map(([label2]) => label2.length));
|
|
77799
|
+
return rows.map(([label2, value]) => `${import_picocolors33.default.dim(label2.padEnd(labelWidth))} ${value}`);
|
|
76149
77800
|
}
|
|
76150
77801
|
function renderBanner(ctx) {
|
|
76151
77802
|
const logo = LOGO_ROWS.map(paintLogoRow);
|
|
@@ -76274,7 +77925,7 @@ function wrapBody(text, width) {
|
|
|
76274
77925
|
function clip3(text, max) {
|
|
76275
77926
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
76276
77927
|
}
|
|
76277
|
-
function
|
|
77928
|
+
function pageRows2(pages, width) {
|
|
76278
77929
|
const titleWidth = Math.max(16, width - 32);
|
|
76279
77930
|
return pages.map((page) => {
|
|
76280
77931
|
const title = clip3(page.title, titleWidth).padEnd(titleWidth + 1);
|
|
@@ -76298,7 +77949,7 @@ function renderPagesFrame(model, width, selected) {
|
|
|
76298
77949
|
} else if (model.pages.length === 0) {
|
|
76299
77950
|
body.push(import_picocolors34.default.dim("No pages in this Library yet."));
|
|
76300
77951
|
} else {
|
|
76301
|
-
const rows =
|
|
77952
|
+
const rows = pageRows2(model.pages, width - 2);
|
|
76302
77953
|
const { start, above } = windowSelection(rows.length, selected);
|
|
76303
77954
|
const visible = rows.slice(start, start + PAGES_VIEW_LINES);
|
|
76304
77955
|
body.push(...visible.map((row, i5) => start + i5 === selected ? `${brand("▸")} ${import_picocolors34.default.bold(row)}` : ` ${import_picocolors34.default.dim(row)}`));
|
|
@@ -76441,7 +78092,7 @@ ${CLEAR_BELOW3}`);
|
|
|
76441
78092
|
});
|
|
76442
78093
|
draw();
|
|
76443
78094
|
};
|
|
76444
|
-
return new Promise((
|
|
78095
|
+
return new Promise((resolve7) => {
|
|
76445
78096
|
const wasRaw = input2.isRaw ?? false;
|
|
76446
78097
|
input2.setRawMode?.(true);
|
|
76447
78098
|
input2.resume();
|
|
@@ -76453,7 +78104,7 @@ ${CLEAR_BELOW3}`);
|
|
|
76453
78104
|
input2.pause();
|
|
76454
78105
|
leaveAltScreen();
|
|
76455
78106
|
output2.write(SHOW_CURSOR5);
|
|
76456
|
-
|
|
78107
|
+
resolve7();
|
|
76457
78108
|
};
|
|
76458
78109
|
const onData = (chunk) => {
|
|
76459
78110
|
for (const key of parseKeys(chunk.toString())) {
|
|
@@ -76525,7 +78176,7 @@ __export(exports_tui, {
|
|
|
76525
78176
|
runTUI: () => runTUI,
|
|
76526
78177
|
handleLogout: () => handleLogout
|
|
76527
78178
|
});
|
|
76528
|
-
import { homedir as
|
|
78179
|
+
import { homedir as homedir10 } from "node:os";
|
|
76529
78180
|
import { basename as basename4 } from "node:path";
|
|
76530
78181
|
function bannerContext(cfg) {
|
|
76531
78182
|
let webAppHost = "app.dosu.dev";
|
|
@@ -76644,7 +78295,7 @@ async function runMainMenu() {
|
|
|
76644
78295
|
const buildOptions = () => {
|
|
76645
78296
|
if (!isAuthenticated(cfg)) {
|
|
76646
78297
|
return [
|
|
76647
|
-
{ label: "Log in / Sign up", hint: "opens your browser", value: "auth" },
|
|
78298
|
+
{ label: "Log in / Sign up", hint: "(opens your browser)", value: "auth" },
|
|
76648
78299
|
{ label: "Exit", value: "exit" }
|
|
76649
78300
|
];
|
|
76650
78301
|
}
|
|
@@ -76660,6 +78311,7 @@ async function runMainMenu() {
|
|
|
76660
78311
|
label: mining ? `Activity ⛏️ ${brand("mining sessions...")}` : "Activity",
|
|
76661
78312
|
value: "sync"
|
|
76662
78313
|
},
|
|
78314
|
+
{ label: "Knowledge report", hint: "(opens in browser)", value: "report" },
|
|
76663
78315
|
{ label: "Analytics", value: "analytics" },
|
|
76664
78316
|
{ label: "Pages", value: "pages" },
|
|
76665
78317
|
{ label: "Settings", value: "settings" },
|
|
@@ -76684,6 +78336,10 @@ async function runMainMenu() {
|
|
|
76684
78336
|
await runActivityView();
|
|
76685
78337
|
home();
|
|
76686
78338
|
break;
|
|
78339
|
+
case "report":
|
|
78340
|
+
await runKnowledgeReport();
|
|
78341
|
+
home();
|
|
78342
|
+
break;
|
|
76687
78343
|
case "analytics":
|
|
76688
78344
|
await runAnalyticsView();
|
|
76689
78345
|
home();
|
|
@@ -76711,6 +78367,17 @@ async function runMainMenu() {
|
|
|
76711
78367
|
}
|
|
76712
78368
|
}
|
|
76713
78369
|
}
|
|
78370
|
+
async function runKnowledgeReport() {
|
|
78371
|
+
const s = spinner2();
|
|
78372
|
+
s.start("Writing knowledge report...");
|
|
78373
|
+
try {
|
|
78374
|
+
const path3 = await emitKnowledgeReport({ open: true });
|
|
78375
|
+
s.stop(`Opened ${path3}`);
|
|
78376
|
+
} catch (err) {
|
|
78377
|
+
s.stop("Could not write the report");
|
|
78378
|
+
log2.error(err instanceof Error ? err.message : String(err));
|
|
78379
|
+
}
|
|
78380
|
+
}
|
|
76714
78381
|
async function runSettings(cfg) {
|
|
76715
78382
|
while (true) {
|
|
76716
78383
|
const target = cfg.active_account?.target;
|
|
@@ -76751,7 +78418,7 @@ async function runSettings(cfg) {
|
|
|
76751
78418
|
}
|
|
76752
78419
|
}
|
|
76753
78420
|
function displayDir(dir) {
|
|
76754
|
-
const home =
|
|
78421
|
+
const home = homedir10();
|
|
76755
78422
|
return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
|
|
76756
78423
|
}
|
|
76757
78424
|
function discoverProjectDirs() {
|
|
@@ -76838,6 +78505,7 @@ var init_tui = __esm(() => {
|
|
|
76838
78505
|
init_config();
|
|
76839
78506
|
init_agents();
|
|
76840
78507
|
init_providers();
|
|
78508
|
+
init_generate();
|
|
76841
78509
|
init_project_dir();
|
|
76842
78510
|
init_scan();
|
|
76843
78511
|
init_agents_md_step();
|
|
@@ -77117,7 +78785,7 @@ Could not open a browser automatically.`);
|
|
|
77117
78785
|
throw new Error("Authentication timed out. Run 'dosu login' to try again.");
|
|
77118
78786
|
}
|
|
77119
78787
|
function sleep4(ms2) {
|
|
77120
|
-
return new Promise((
|
|
78788
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms2));
|
|
77121
78789
|
}
|
|
77122
78790
|
var POLL_INTERVAL_MS = 5000;
|
|
77123
78791
|
var init_device = __esm(() => {
|
|
@@ -80621,12 +82289,15 @@ init_trpc();
|
|
|
80621
82289
|
init_config();
|
|
80622
82290
|
init_agents();
|
|
80623
82291
|
init_formats();
|
|
82292
|
+
init_generate();
|
|
82293
|
+
init_backlog();
|
|
80624
82294
|
init_detach();
|
|
80625
82295
|
init_status();
|
|
80626
82296
|
init_sync();
|
|
82297
|
+
init_watermark();
|
|
80627
82298
|
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
80628
82299
|
import { existsSync as existsSync16 } from "node:fs";
|
|
80629
|
-
import { homedir as
|
|
82300
|
+
import { homedir as homedir5 } from "node:os";
|
|
80630
82301
|
import { delimiter as delimiter4, join as join19 } from "node:path";
|
|
80631
82302
|
function requireConfig7() {
|
|
80632
82303
|
const cfg = requireLoginConfig();
|
|
@@ -80690,7 +82361,51 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
80690
82361
|
console.log(` ID: ${store.id}`);
|
|
80691
82362
|
console.log(` Space ID: ${store.space_id}`);
|
|
80692
82363
|
});
|
|
80693
|
-
cmd.command("
|
|
82364
|
+
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) => {
|
|
82365
|
+
const all = !opts.queued && !opts.open && !opts.mined;
|
|
82366
|
+
const wantQueued = all || Boolean(opts.queued);
|
|
82367
|
+
const wantOpen = all || Boolean(opts.open);
|
|
82368
|
+
const wantMined = all || Boolean(opts.mined);
|
|
82369
|
+
const backlog = wantQueued || wantOpen ? listSessionBacklog() : { queued: [], open: [] };
|
|
82370
|
+
const mined = wantMined ? loadSyncState().mined_sessions ?? [] : [];
|
|
82371
|
+
if (opts.json) {
|
|
82372
|
+
printResult({
|
|
82373
|
+
...wantQueued ? { queued: backlog.queued } : {},
|
|
82374
|
+
...wantOpen ? { open: backlog.open } : {},
|
|
82375
|
+
...wantMined ? { mined } : {}
|
|
82376
|
+
}, opts);
|
|
82377
|
+
return;
|
|
82378
|
+
}
|
|
82379
|
+
const sessionRows = (sessions) => sessions.map((s) => [s.harness, s.updated, s.project ?? "-", s.id]);
|
|
82380
|
+
const minedRows = mined.map((record2) => {
|
|
82381
|
+
const slash = record2.session.indexOf("/");
|
|
82382
|
+
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
82383
|
+
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
82384
|
+
return [harness, record2.at, record2.project ?? "-", id2];
|
|
82385
|
+
});
|
|
82386
|
+
let first = true;
|
|
82387
|
+
const section = (title, rows, stamp, emptyMsg) => {
|
|
82388
|
+
if (!first)
|
|
82389
|
+
console.log();
|
|
82390
|
+
first = false;
|
|
82391
|
+
console.log(import_picocolors18.default.bold(`${title} (${rows.length})`));
|
|
82392
|
+
if (rows.length === 0) {
|
|
82393
|
+
console.log(import_picocolors18.default.dim(` ${emptyMsg}`));
|
|
82394
|
+
return;
|
|
82395
|
+
}
|
|
82396
|
+
printTable(["Agent", stamp, "Project", "Session"], rows);
|
|
82397
|
+
};
|
|
82398
|
+
if (wantQueued) {
|
|
82399
|
+
section("Queued", sessionRows(backlog.queued), "Updated", "Queue empty. Finished agent sessions appear here.");
|
|
82400
|
+
}
|
|
82401
|
+
if (wantOpen) {
|
|
82402
|
+
section("Open", sessionRows(backlog.open), "Updated", "No open sessions. Live agent sessions sit here until they go quiet.");
|
|
82403
|
+
}
|
|
82404
|
+
if (wantMined) {
|
|
82405
|
+
section("Mined", minedRows, "Mined at", "No mined sessions recorded yet.");
|
|
82406
|
+
}
|
|
82407
|
+
});
|
|
82408
|
+
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
82409
|
if (opts.status) {
|
|
80695
82410
|
const status = getSyncStatus();
|
|
80696
82411
|
if (opts.json) {
|
|
@@ -80705,7 +82420,9 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
80705
82420
|
"knowledge",
|
|
80706
82421
|
"sync",
|
|
80707
82422
|
...opts.quiet ? ["--quiet"] : [],
|
|
80708
|
-
...opts.bootstrap ? ["--bootstrap"] : []
|
|
82423
|
+
...opts.bootstrap ? ["--bootstrap"] : [],
|
|
82424
|
+
...opts.report ? ["--report"] : [],
|
|
82425
|
+
...opts.out ? ["--out", opts.out] : []
|
|
80709
82426
|
]);
|
|
80710
82427
|
return;
|
|
80711
82428
|
}
|
|
@@ -80726,12 +82443,55 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
80726
82443
|
if (opts.quiet)
|
|
80727
82444
|
return;
|
|
80728
82445
|
if (opts.json) {
|
|
80729
|
-
printResult(outcome, opts);
|
|
80730
82446
|
if (outcome.status === "error")
|
|
80731
82447
|
process.exitCode = 1;
|
|
82448
|
+
if (opts.report) {
|
|
82449
|
+
try {
|
|
82450
|
+
const report = await emitKnowledgeReport({ out: opts.out, open: false });
|
|
82451
|
+
printResult({ ...outcome, report }, opts);
|
|
82452
|
+
} catch (err) {
|
|
82453
|
+
const report_error = err instanceof Error ? err.message : String(err);
|
|
82454
|
+
printResult({ ...outcome, report_error }, opts);
|
|
82455
|
+
}
|
|
82456
|
+
} else {
|
|
82457
|
+
printResult(outcome, opts);
|
|
82458
|
+
}
|
|
80732
82459
|
return;
|
|
80733
82460
|
}
|
|
80734
82461
|
printSyncOutcome(outcome);
|
|
82462
|
+
if (opts.report) {
|
|
82463
|
+
try {
|
|
82464
|
+
const report = await emitKnowledgeReport({ out: opts.out, open: true });
|
|
82465
|
+
console.log(`Wrote ${report}`);
|
|
82466
|
+
} catch (err) {
|
|
82467
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
82468
|
+
process.exitCode = 1;
|
|
82469
|
+
}
|
|
82470
|
+
}
|
|
82471
|
+
});
|
|
82472
|
+
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) => {
|
|
82473
|
+
const path3 = await emitKnowledgeReport({
|
|
82474
|
+
out: opts.out,
|
|
82475
|
+
open: opts.json ? false : opts.open
|
|
82476
|
+
});
|
|
82477
|
+
if (opts.json) {
|
|
82478
|
+
printResult({ report: path3 }, opts);
|
|
82479
|
+
return;
|
|
82480
|
+
}
|
|
82481
|
+
console.log(`Wrote ${path3}`);
|
|
82482
|
+
});
|
|
82483
|
+
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) => {
|
|
82484
|
+
const { runBackfill: runBackfill2 } = await Promise.resolve().then(() => (init_backfill_run(), exports_backfill_run));
|
|
82485
|
+
const result = await runBackfill2();
|
|
82486
|
+
if (opts.json) {
|
|
82487
|
+
printResult(result, opts);
|
|
82488
|
+
return;
|
|
82489
|
+
}
|
|
82490
|
+
if (result.candidates === 0) {
|
|
82491
|
+
console.log("All your notes already have a transcript — nothing to backfill.");
|
|
82492
|
+
return;
|
|
82493
|
+
}
|
|
82494
|
+
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
82495
|
});
|
|
80736
82496
|
cmd.addCommand(hooksCommand());
|
|
80737
82497
|
return cmd;
|
|
@@ -80771,10 +82531,13 @@ function printSyncStatus(status, now = new Date) {
|
|
|
80771
82531
|
} else {
|
|
80772
82532
|
console.log("○ No sync running.");
|
|
80773
82533
|
}
|
|
82534
|
+
if (status.state.paused) {
|
|
82535
|
+
console.log(import_picocolors18.default.yellow(" Mining paused: stopped by you. Resume from the Activity screen or run 'dosu knowledge sync'."));
|
|
82536
|
+
}
|
|
80774
82537
|
const wm2 = status.state.watermark;
|
|
80775
82538
|
console.log(` Mined through: ${wm2 ? `${wm2} (${formatAge(wm2, now)})` : "nothing mined yet"}`);
|
|
80776
82539
|
if (status.state.project_filter?.length) {
|
|
80777
|
-
const home =
|
|
82540
|
+
const home = homedir5();
|
|
80778
82541
|
const scope = status.state.project_filter.map((dir) => dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir).join(", ");
|
|
80779
82542
|
console.log(` Mining scope: ${scope}`);
|
|
80780
82543
|
}
|
|
@@ -80847,6 +82610,10 @@ function printSyncOutcome(outcome) {
|
|
|
80847
82610
|
console.log(import_picocolors18.default.dim("Skipped: a recent sync failed; waiting out the retry backoff."));
|
|
80848
82611
|
break;
|
|
80849
82612
|
}
|
|
82613
|
+
case "skipped-paused": {
|
|
82614
|
+
console.log(import_picocolors18.default.dim("Skipped: mining is paused. Run 'dosu knowledge sync' to resume."));
|
|
82615
|
+
break;
|
|
82616
|
+
}
|
|
80850
82617
|
}
|
|
80851
82618
|
}
|
|
80852
82619
|
function resolveHookAgents(ids) {
|
|
@@ -80953,7 +82720,6 @@ var LIBRARY_VISIBILITIES = ["public", "internal", "private"];
|
|
|
80953
82720
|
var CONFIG_SETTINGS = {
|
|
80954
82721
|
commit_to_trigger_pr: true,
|
|
80955
82722
|
default_accept_review: true,
|
|
80956
|
-
default_save_publish: true,
|
|
80957
82723
|
review_timeout_days: true
|
|
80958
82724
|
};
|
|
80959
82725
|
var CONFIG_SETTING_NAMES = Object.keys(CONFIG_SETTINGS);
|
|
@@ -81597,7 +83363,10 @@ init_skill();
|
|
|
81597
83363
|
// src/commands/sources.ts
|
|
81598
83364
|
init_commander();
|
|
81599
83365
|
init_trpc();
|
|
83366
|
+
init_github_step();
|
|
83367
|
+
init_installation_server();
|
|
81600
83368
|
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
83369
|
+
var CONNECT_TIMEOUT_DEFAULT_SECONDS = 600;
|
|
81601
83370
|
function requireConfig11() {
|
|
81602
83371
|
const cfg = requireLoginConfig();
|
|
81603
83372
|
if (!cfg.active_account?.target?.org_id) {
|
|
@@ -81606,6 +83375,15 @@ function requireConfig11() {
|
|
|
81606
83375
|
}
|
|
81607
83376
|
return cfg;
|
|
81608
83377
|
}
|
|
83378
|
+
function rejectWebOnlyProvider(provider, json2) {
|
|
83379
|
+
const url2 = getWebAppURL();
|
|
83380
|
+
if (json2) {
|
|
83381
|
+
printResult({ provider, cli_supported: false, connect_via: "web", url: url2 }, { json: true });
|
|
83382
|
+
} else {
|
|
83383
|
+
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'.`));
|
|
83384
|
+
}
|
|
83385
|
+
process.exit(1);
|
|
83386
|
+
}
|
|
81609
83387
|
function sourcesCommand() {
|
|
81610
83388
|
const cmd = new Command("sources").description("Manage connected data sources");
|
|
81611
83389
|
cmd.command("list").description("List all connected data sources").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -81688,6 +83466,154 @@ function sourcesCommand() {
|
|
|
81688
83466
|
}
|
|
81689
83467
|
console.log(import_picocolors25.default.green("Data source deleted."));
|
|
81690
83468
|
});
|
|
83469
|
+
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) => {
|
|
83470
|
+
const providerName = provider.toLowerCase();
|
|
83471
|
+
if (providerName !== "github") {
|
|
83472
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83473
|
+
return;
|
|
83474
|
+
}
|
|
83475
|
+
const cfg = requireConfig11();
|
|
83476
|
+
const client = createTypedClient(cfg);
|
|
83477
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83478
|
+
const timeoutSeconds = Math.max(1, Number.parseInt(opts.timeout ?? "", 10)) || CONNECT_TIMEOUT_DEFAULT_SECONDS;
|
|
83479
|
+
const before = await fetchListForOrg(client, orgID);
|
|
83480
|
+
const { server, installationPromise } = await startInstallationCallbackServer();
|
|
83481
|
+
let timeoutId;
|
|
83482
|
+
try {
|
|
83483
|
+
const connectURL = new URL("/cli/connect-github", getWebAppURL());
|
|
83484
|
+
connectURL.searchParams.set("callback", `http://localhost:${server.port}/callback`);
|
|
83485
|
+
const url2 = connectURL.toString();
|
|
83486
|
+
if (opts.json) {
|
|
83487
|
+
console.log(JSON.stringify({
|
|
83488
|
+
event: "awaiting_install",
|
|
83489
|
+
provider: "github",
|
|
83490
|
+
url: url2,
|
|
83491
|
+
timeout_seconds: timeoutSeconds
|
|
83492
|
+
}));
|
|
83493
|
+
} else {
|
|
83494
|
+
console.log(`Open this URL to install or update the Dosu GitHub App:
|
|
83495
|
+
${import_picocolors25.default.cyan(url2)}`);
|
|
83496
|
+
if (opts.open !== false) {
|
|
83497
|
+
try {
|
|
83498
|
+
const open5 = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
83499
|
+
await open5.default(url2);
|
|
83500
|
+
} catch {}
|
|
83501
|
+
}
|
|
83502
|
+
console.log(import_picocolors25.default.dim("Waiting for the install to complete..."));
|
|
83503
|
+
}
|
|
83504
|
+
const timedOut = new Promise((resolve6) => {
|
|
83505
|
+
timeoutId = setTimeout(() => resolve6(null), timeoutSeconds * 1000);
|
|
83506
|
+
});
|
|
83507
|
+
const result = await Promise.race([installationPromise, timedOut]);
|
|
83508
|
+
if (result === null) {
|
|
83509
|
+
if (opts.json) {
|
|
83510
|
+
console.log(JSON.stringify({ event: "timeout", timeout_seconds: timeoutSeconds }));
|
|
83511
|
+
} else {
|
|
83512
|
+
console.error(import_picocolors25.default.red(`Timed out after ${timeoutSeconds}s. Finish the install in the browser, then re-run 'dosu sources connect github'.`));
|
|
83513
|
+
}
|
|
83514
|
+
process.exit(1);
|
|
83515
|
+
return;
|
|
83516
|
+
}
|
|
83517
|
+
const refresh = await waitForRepositoryRefresh(client, orgID, before);
|
|
83518
|
+
const beforeIds = new Set(before.map((repo) => repo.repository_id));
|
|
83519
|
+
const newRepositories = refresh.repos.filter((repo) => !beforeIds.has(repo.repository_id)).map((repo) => ({ slug: repo.slug, repository_id: repo.repository_id }));
|
|
83520
|
+
if (opts.json) {
|
|
83521
|
+
console.log(JSON.stringify({
|
|
83522
|
+
event: "installed",
|
|
83523
|
+
installation_id: result.installation_id,
|
|
83524
|
+
new_repositories: newRepositories
|
|
83525
|
+
}));
|
|
83526
|
+
return;
|
|
83527
|
+
}
|
|
83528
|
+
console.log(import_picocolors25.default.green(`GitHub App connected (installation ${result.installation_id}).`));
|
|
83529
|
+
if (newRepositories.length > 0) {
|
|
83530
|
+
for (const repo of newRepositories) {
|
|
83531
|
+
console.log(` ${repo.slug}`);
|
|
83532
|
+
}
|
|
83533
|
+
console.log(import_picocolors25.default.dim("Next: dosu sources create github --repo <owner/name> --library <library-id>"));
|
|
83534
|
+
} else {
|
|
83535
|
+
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."));
|
|
83536
|
+
}
|
|
83537
|
+
} finally {
|
|
83538
|
+
clearTimeout(timeoutId);
|
|
83539
|
+
server.close();
|
|
83540
|
+
}
|
|
83541
|
+
});
|
|
83542
|
+
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) => {
|
|
83543
|
+
const providerName = provider.toLowerCase();
|
|
83544
|
+
if (providerName !== "github") {
|
|
83545
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83546
|
+
return;
|
|
83547
|
+
}
|
|
83548
|
+
const cfg = requireConfig11();
|
|
83549
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83550
|
+
const libraryID = opts.library ?? cfg.active_account?.target?.space_id;
|
|
83551
|
+
if (!libraryID) {
|
|
83552
|
+
console.error(import_picocolors25.default.red("No library specified and no active library. Pass --library <id>."));
|
|
83553
|
+
process.exit(1);
|
|
83554
|
+
return;
|
|
83555
|
+
}
|
|
83556
|
+
const client = createTypedClient(cfg);
|
|
83557
|
+
const repos = await fetchListForOrg(client, orgID);
|
|
83558
|
+
const repo = repos.find((r9) => r9.slug.toLowerCase() === opts.repo.toLowerCase());
|
|
83559
|
+
if (!repo) {
|
|
83560
|
+
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.`));
|
|
83561
|
+
process.exit(1);
|
|
83562
|
+
return;
|
|
83563
|
+
}
|
|
83564
|
+
if (repo.is_fork === true) {
|
|
83565
|
+
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.` : "")));
|
|
83566
|
+
process.exit(1);
|
|
83567
|
+
return;
|
|
83568
|
+
}
|
|
83569
|
+
if (!await confirmAction({
|
|
83570
|
+
confirmed: opts.confirm,
|
|
83571
|
+
json: opts.json,
|
|
83572
|
+
message: `Create a data source for ${repo.slug} and attach it to library ${libraryID}?`,
|
|
83573
|
+
preview: {
|
|
83574
|
+
action: "create_source",
|
|
83575
|
+
provider: "github",
|
|
83576
|
+
repository: repo.slug,
|
|
83577
|
+
repository_id: repo.repository_id,
|
|
83578
|
+
library_id: libraryID
|
|
83579
|
+
}
|
|
83580
|
+
}))
|
|
83581
|
+
return;
|
|
83582
|
+
const existingDeployments = await fetchOrgGithubDeployments(client, orgID);
|
|
83583
|
+
const existingDataSources = await fetchOrgGithubDataSources(client, orgID);
|
|
83584
|
+
const created = await createDeploymentForRepo(client, orgID, libraryID, repo, {
|
|
83585
|
+
deploymentID: existingDeployments.get(repo.repository_id),
|
|
83586
|
+
dataSourceID: existingDataSources.get(repo.repository_id)
|
|
83587
|
+
});
|
|
83588
|
+
if (!created) {
|
|
83589
|
+
console.error(import_picocolors25.default.red("Could not create the data source. Check 'dosu logs --tail 50' for details."));
|
|
83590
|
+
process.exit(1);
|
|
83591
|
+
return;
|
|
83592
|
+
}
|
|
83593
|
+
const survivors = await verifyDataSourcesPersist(client, orgID, [created.data_source_id]);
|
|
83594
|
+
if (survivors.dropped.size > 0) {
|
|
83595
|
+
await deleteOrphanDeployment(client, created.deployment_id, repo.slug);
|
|
83596
|
+
console.error(import_picocolors25.default.red(`Dosu couldn't sync '${repo.slug}' (the GitHub App may not have access to it). ` + "Nothing was created."));
|
|
83597
|
+
process.exit(1);
|
|
83598
|
+
return;
|
|
83599
|
+
}
|
|
83600
|
+
const receipt = {
|
|
83601
|
+
provider: "github",
|
|
83602
|
+
repository: repo.slug,
|
|
83603
|
+
repository_id: repo.repository_id,
|
|
83604
|
+
data_source_id: created.data_source_id,
|
|
83605
|
+
deployment_id: created.deployment_id,
|
|
83606
|
+
library_id: libraryID,
|
|
83607
|
+
attached: true
|
|
83608
|
+
};
|
|
83609
|
+
if (opts.json) {
|
|
83610
|
+
printResult(receipt, opts);
|
|
83611
|
+
return;
|
|
83612
|
+
}
|
|
83613
|
+
console.log(import_picocolors25.default.green(`Connected ${repo.slug} to library ${libraryID}.`));
|
|
83614
|
+
console.log(import_picocolors25.default.dim(` data source ${created.data_source_id}`));
|
|
83615
|
+
console.log(import_picocolors25.default.dim(` deployment ${created.deployment_id}`));
|
|
83616
|
+
});
|
|
81691
83617
|
return cmd;
|
|
81692
83618
|
}
|
|
81693
83619
|
|
|
@@ -81957,7 +83883,7 @@ init_skill();
|
|
|
81957
83883
|
var import_picocolors28 = __toESM(require_picocolors(), 1);
|
|
81958
83884
|
import { spawnSync } from "node:child_process";
|
|
81959
83885
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
81960
|
-
import { homedir as
|
|
83886
|
+
import { homedir as homedir7 } from "node:os";
|
|
81961
83887
|
import { posix, win32 as win322 } from "node:path";
|
|
81962
83888
|
var PACKAGE_NAME = "@dosu/cli";
|
|
81963
83889
|
var LATEST_PACKAGE = `${PACKAGE_NAME}@latest`;
|
|
@@ -82034,7 +83960,7 @@ function packageRoots(manager, platform3, comSpec, env, cwd) {
|
|
|
82034
83960
|
manager === "npm" ? path3.join(lines[0], "@dosu", "cli") : path3.join(lines[0], "node_modules", "@dosu", "cli")
|
|
82035
83961
|
];
|
|
82036
83962
|
}
|
|
82037
|
-
function owningPackageManager(entrypoint, platform3, comSpec, env = process.env, cwd =
|
|
83963
|
+
function owningPackageManager(entrypoint, platform3, comSpec, env = process.env, cwd = homedir7()) {
|
|
82038
83964
|
const path3 = platform3 === "win32" ? win322 : posix;
|
|
82039
83965
|
if (!entrypoint || !path3.isAbsolute(entrypoint))
|
|
82040
83966
|
return null;
|
|
@@ -82087,7 +84013,7 @@ Or install Dosu globally with your package manager:`);
|
|
|
82087
84013
|
function runUpgrade(channel = INSTALL_CHANNEL, options = {}) {
|
|
82088
84014
|
const platform3 = options.platform ?? process.platform;
|
|
82089
84015
|
const env = options.env ?? process.env;
|
|
82090
|
-
const cwd = options.cwd ??
|
|
84016
|
+
const cwd = options.cwd ?? homedir7();
|
|
82091
84017
|
let manager = null;
|
|
82092
84018
|
if (channel === "npm") {
|
|
82093
84019
|
if (isNpxInvocation(channel, env)) {
|
|
@@ -82248,8 +84174,8 @@ async function finishTelemetry(operation) {
|
|
|
82248
84174
|
try {
|
|
82249
84175
|
await Promise.race([
|
|
82250
84176
|
Promise.resolve().then(operation),
|
|
82251
|
-
new Promise((
|
|
82252
|
-
timeout = setTimeout(
|
|
84177
|
+
new Promise((resolve7) => {
|
|
84178
|
+
timeout = setTimeout(resolve7, TELEMETRY_FLUSH_TIMEOUT_MS);
|
|
82253
84179
|
})
|
|
82254
84180
|
]);
|
|
82255
84181
|
} catch {} finally {
|
|
@@ -82690,4 +84616,4 @@ execute().catch((err) => {
|
|
|
82690
84616
|
process.exit(1);
|
|
82691
84617
|
});
|
|
82692
84618
|
|
|
82693
|
-
//# debugId=
|
|
84619
|
+
//# debugId=6096b455-0361-b435-6475-6e2164756e21
|