@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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/bin/dosu.js +2594 -668
  3. 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
- return process.env.DOSU_LLM_GATEWAY_URL_OVERRIDE ?? `${getBackendURL()}/v1/llm-gateway`;
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.1", INSTALL_CHANNEL = "npm";
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 = "bb1ad28625bd";
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 existsSync12, readdirSync, statSync as statSync6 } from "node:fs";
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 join11 } from "node:path";
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: join11(dir, entry.name),
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 = statSync6(filePath).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(join11(home, ".claude", "projects"))) {
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(join11(home, ".cursor", "projects"))) {
8412
+ for (const project of listDir(join8(home, ".cursor", "projects"))) {
8733
8413
  if (!project.isDir)
8734
8414
  continue;
8735
- for (const entry of listDir(join11(project.path, "agent-transcripts"))) {
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 ?? join11(home, ".codex");
8427
+ const codexHome2 = env.CODEX_HOME ?? join8(home, ".codex");
8748
8428
  const sessions = [];
8749
- for (const year of listDir(join11(codexHome2, "sessions"))) {
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 ?? join11(home, ".local", "share");
8806
- const dbPath = join11(dataDir, "opencode", "opencode.db");
8807
- if (!existsSync12(dbPath))
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
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 &amp; 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 readFileSync13 } from "node:fs";
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(readFileSync13(session.path, "utf8"));
9496
+ return readClaude(readFileSync10(session.path, "utf8"));
8962
9497
  case "cursor":
8963
- return readCursor(readFileSync13(session.path, "utf8"));
9498
+ return readCursor(readFileSync10(session.path, "utf8"));
8964
9499
  case "codex":
8965
- return readCodex(readFileSync13(session.path, "utf8"));
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 / CHARS_PER_TOKEN);
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, CHARS_PER_TOKEN = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
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 resolve = deps.resolveProjectDir;
10514
+ let resolve2 = deps.resolveProjectDir;
9035
10515
  let flush;
9036
- if (!resolve) {
10516
+ if (!resolve2) {
9037
10517
  const resolver = createProjectDirResolver();
9038
- resolve = resolver.resolve;
10518
+ resolve2 = resolver.resolve;
9039
10519
  flush = resolver.flush;
9040
10520
  }
9041
- sessions = filterSessionsByProject(sessions, state.project_filter, resolve);
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 writeFileSync9 } from "node:fs";
9196
- import { tmpdir } from "node:os";
9197
- import { join as join12 } from "node:path";
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(join12(tmpdir(), "dosu-miner-"));
9200
- writeFileSync9(join12(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
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 readFileSync14 } from "node:fs";
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(readFileSync14(file, "utf8"));
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 dirname4, join as join13 } from "node:path";
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 = dirname4(requireRuntime.resolve(`${pkg}/package.json`));
9315
- return binaryNames().some((name) => existsSync14(join13(pkgDir, name)));
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(join13(homeDir, ".local", "bin"), join13(homeDir, ".claude", "local"));
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 = join13(dir, name);
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
- Read each session with read_session, decide what (if anything) is durable per your rules, dedupe against read_knowledge, then write the distilled notes with write_knowledge. When you are done, reply with a one-line summary: how many sessions you read and how many notes you wrote.`;
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 to check whether the
9383
- knowledge already exists. Never write a duplicate or near-duplicate.
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: () => 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 esc(str) {
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)}]` : esc(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(${esc(def.includes)})) return INVALID;`);
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}) !== ${esc(prefix)}) return INVALID;`);
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}) !== ${esc(suffix)}) return INVALID;`);
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) : esc(k2);
64750
- const propKey = (k2) => typeof k2 === "symbol" ? `[${keyExpr(k2)}]` : esc(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 !== ${esc(k2)}`).join(" && ") || "true";
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} !== ${esc(value)}) return INVALID;`);
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}?.[${esc(def.discriminator)}];`);
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} === ${esc(value)}`;
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 esc(key);
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: options.gatewayURL ?? getLlmGatewayURL(),
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(coreRules.rules),
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 mkdirSync9, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "node:fs";
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
- mkdirSync9(dir, { recursive: true, mode: 448 });
73110
+ mkdirSync10(dir, { recursive: true, mode: 448 });
71511
73111
  }
71512
- writeFileSync10(getCachePath2(), JSON.stringify(cache), { mode: 384 });
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 homedir7 } from "node:os";
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(homedir7(), ".claude");
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(homedir7(), ".codeium", "windsurf", "skills", SKILL_NAME),
73202
+ path: join21(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
71603
73203
  symlink: true
71604
73204
  };
71605
73205
  }
71606
73206
  return {
71607
- path: join21(homedir7(), ".agents", "skills", SKILL_NAME),
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((resolve5, reject) => {
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
- resolve5();
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 mkdirSync10,
73368
+ mkdirSync as mkdirSync11,
71769
73369
  readFileSync as readFileSync19,
71770
73370
  renameSync as renameSync5,
71771
73371
  unlinkSync as unlinkSync3,
71772
- writeFileSync as writeFileSync11
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
- mkdirSync10(dir, { recursive: true, mode: 448 });
71878
- writeFileSync11(temporaryPath, `${JSON.stringify(settings2, null, 2)}
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 = "fd3fcdcd-886d-763b-6475-6e2164756e21";
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 resolve5, sep as sep5 } from "node:path";
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 = resolve5(absolutePath);
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((resolve6, reject) => {
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
- resolve6(value);
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 homedir9 } from "node:os";
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(homedir9(), ".codeium", "windsurf")],
73246
- globalPath: join35(homedir9(), ".codeium", "windsurf", "mcp_config.json"),
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 mkdirSync11, readFileSync as readFileSync21, writeFileSync as writeFileSync12 } from "node:fs";
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
- mkdirSync11(dir, { recursive: true, mode: 448 });
74978
+ mkdirSync12(dir, { recursive: true, mode: 448 });
73379
74979
  }
73380
- writeFileSync12(getCachePath3(), JSON.stringify(cache), { mode: 384 });
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 mkdirSync12, readFileSync as readFileSync22, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
73491
- import { homedir as homedir10 } from "node:os";
73492
- import { dirname as dirname8, join as join38 } from "node:path";
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(homedir10(), ".claude");
75094
+ return process.env.CLAUDE_CONFIG_DIR || join38(homedir9(), ".claude");
73495
75095
  }
73496
75096
  function codexHome3() {
73497
- return process.env.CODEX_HOME || join38(homedir10(), ".codex");
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
- mkdirSync12(dirname8(path3), { recursive: true });
75123
+ mkdirSync13(dirname9(path3), { recursive: true });
73524
75124
  }
73525
75125
  function writeIfChanged(path3, content) {
73526
75126
  if (!existsSync23(path3)) {
73527
75127
  ensureParent(path3);
73528
- writeFileSync13(path3, content, "utf-8");
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
- writeFileSync13(path3, content, "utf-8");
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
- writeFileSync13(path3, `${section2}
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
- writeFileSync13(path3, next, "utf-8");
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
- writeFileSync13(path3, `${next}${lineEnding}`, "utf-8");
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(homedir10(), ".cursor", "rules", "dosu.mdc"),
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(homedir10(), ".config", "opencode", "AGENTS.md")
75252
+ path: () => join38(homedir9(), ".config", "opencode", "AGENTS.md")
73653
75253
  },
73654
75254
  gemini: {
73655
75255
  kind: "section",
73656
- path: () => join38(homedir10(), ".gemini", "GEMINI.md")
75256
+ path: () => join38(homedir9(), ".gemini", "GEMINI.md")
73657
75257
  },
73658
75258
  antigravity: {
73659
75259
  kind: "section",
73660
- path: () => join38(homedir10(), ".gemini", "GEMINI.md")
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 writeFileSync14 } from "node:fs";
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
- writeFileSync14(path3, `${section2}
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
- writeFileSync14(path3, next);
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 project = clip(record2.project ?? "-", 28);
73871
- return `${harness.padEnd(8)} ${stamp} ${project} ${clip(id2, 24)}`;
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 project = clip(session.project ?? "-", 28);
73876
- return `${session.harness.padEnd(8)} ${stamp} ${project} ${clip(session.id, 24)}`;
73877
- }
73878
- function defaultListBacklog() {
73879
- try {
73880
- const state = loadSyncState();
73881
- let sessions = scanAgentSessions({});
73882
- if (state.project_filter?.length) {
73883
- const resolver = createProjectDirResolver();
73884
- sessions = filterSessionsByProject(sessions, state.project_filter, resolver.resolve);
73885
- resolver.flush();
73886
- }
73887
- const gate = gateSessions(sessions, state.watermark);
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
- const labels = [
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
- `⛏ ${import_picocolors30.default.bold(brand("Start mining now?"))}`,
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")} start · ${import_picocolors30.default.bold("esc")} cancel`
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 minedRows = (status.state.mined_sessions ?? []).map((record2) => formatActivityLine(formatMinedRow(record2), width));
74055
- const queuedRows = queued.map((session) => formatActivityLine(formatQueuedRow(session), width));
74056
- const openRows = open5.map((session) => formatActivityLine(formatQueuedRow(session), width));
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 { visible, above, below } = windowList(source, pane.scroll);
74061
- const listRows = visible.length > 0 ? visible.map((row) => import_picocolors30.default.dim(row)) : [import_picocolors30.default.dim(empty)];
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
- ...status.running ? [] : ["s sync now"],
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", "--quiet", "--bootstrap"]));
74108
- const listBacklog = io2.listBacklog ?? defaultListBacklog;
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 confirmSync = false;
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
- minedBeforeRun ??= status.state.total_mined ?? 0;
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 = false;
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((resolve6) => {
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
- resolve6();
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
- confirmSync = false;
74195
- const ok2 = startSync();
74196
- const note = ok2 ? "[sync] sync requested · starting a background run" : "[sync] could not start a background run · try `dosu knowledge sync`";
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 = false;
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
- if (!status.running) {
74213
- confirmSync = true;
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() - ACTIVITY_VIEW_LIST_LINES);
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((resolve6, reject) => {
74698
- resolveToken = resolve6;
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((resolve6) => {
74757
- httpServer.listen(0, "localhost", () => resolve6());
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 analyticsRows(state, pageStats = null) {
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
- const label = (text) => text.padEnd(26);
75933
- const rows = [];
75934
- if (minedTotal > 0 || notes > 0) {
75935
- rows.push(`${label("Sessions mined")}${minedTotal}`, `${label("Suggested pages")}${notes}`);
75936
- if (tokens > 0) {
75937
- rows.push(`${label("Investigation distilled")}${formatTokenCount(tokens)} tokens`);
75938
- }
75939
- const byProject = new Map;
75940
- for (const record2 of state.mined_sessions ?? []) {
75941
- const key = record2.project ?? "(unknown)";
75942
- byProject.set(key, (byProject.get(key) ?? 0) + 1);
75943
- }
75944
- if (byProject.size > 0) {
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(...rows.length > 0 ? [""] : [], `Top cited pages (${TOP_CITED_DAYS}d)`);
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 pages");
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 renderAnalyticsFrame(state, scroll, pageStats = null) {
75975
- const rows = analyticsRows(state, pageStats);
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 listRows = visible.length > 0 ? visible.map((row) => import_picocolors32.default.dim(row)) : [import_picocolors32.default.dim("No analytics yet. They appear after the first mining run.")];
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 frame = renderAnalyticsFrame(status.state, scroll, pageStats);
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 || !stats)
77673
+ if (closed)
76031
77674
  return;
76032
77675
  pageStats = stats;
77676
+ pagesPending = false;
76033
77677
  draw();
76034
77678
  });
76035
- return new Promise((resolve6) => {
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
- resolve6();
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 === "up") {
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, analyticsRows(status.state, pageStats).length - ANALYTICS_VIEW_LINES);
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 = 12, TOP_PAGES_LIMIT = 5, TOP_CITED_DAYS = 30;
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(([label]) => label.length));
76148
- return rows.map(([label, value]) => `${import_picocolors33.default.dim(label.padEnd(labelWidth))} ${value}`);
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 pageRows(pages, width) {
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 = pageRows(model.pages, width - 2);
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((resolve6) => {
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
- resolve6();
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 homedir11 } from "node:os";
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 = homedir11();
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((resolve6) => setTimeout(resolve6, ms2));
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 homedir6 } from "node:os";
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("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("--json", "Output as JSON").action(async (opts) => {
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 = homedir6();
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 homedir8 } from "node:os";
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 = homedir8()) {
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 ?? homedir8();
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((resolve6) => {
82252
- timeout = setTimeout(resolve6, TELEMETRY_FLUSH_TIMEOUT_MS);
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=fd3fcdcd-886d-763b-6475-6e2164756e21
84619
+ //# debugId=6096b455-0361-b435-6475-6e2164756e21