@dosu/cli 0.53.0-beta.6 → 0.53.0-beta.8

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 (2) hide show
  1. package/bin/dosu.js +2078 -494
  2. 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.6", INSTALL_CHANNEL = "npm";
5021
+ var VERSION = "0.53.0-beta.8", 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 = "1d4be4b6214f";
5159
+ var CLI_CONTRACT_HASH = "d7c6f0f0e8d0";
5148
5160
 
5149
5161
  // src/client/trpc.ts
5150
5162
  var exports_trpc = {};
@@ -8346,161 +8358,16 @@ var init_agents = __esm(() => {
8346
8358
  init_formats();
8347
8359
  });
8348
8360
 
8349
- // src/sessions/project-dir.ts
8350
- import {
8351
- closeSync as closeSync2,
8352
- existsSync as existsSync9,
8353
- mkdirSync as mkdirSync6,
8354
- openSync as openSync2,
8355
- readFileSync as readFileSync9,
8356
- readSync,
8357
- renameSync as renameSync2,
8358
- statSync as statSync3,
8359
- writeFileSync as writeFileSync6
8360
- } from "node:fs";
8361
- import { join as join8 } from "node:path";
8362
- function cachePath(configDir) {
8363
- return join8(configDir, CACHE_FILENAME2);
8364
- }
8365
- function loadCacheFile(configDir) {
8366
- try {
8367
- const raw = JSON.parse(readFileSync9(cachePath(configDir), "utf-8"));
8368
- if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
8369
- return {};
8370
- return raw.entries;
8371
- } catch {
8372
- return {};
8373
- }
8374
- }
8375
- function saveCacheFile(configDir, entries) {
8376
- try {
8377
- if (!existsSync9(configDir))
8378
- mkdirSync6(configDir, { recursive: true, mode: 448 });
8379
- const path3 = cachePath(configDir);
8380
- const tmp = `${path3}.${process.pid}.tmp`;
8381
- const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
8382
- writeFileSync6(tmp, JSON.stringify(file), { mode: 384 });
8383
- renameSync2(tmp, path3);
8384
- } catch {}
8385
- }
8386
- function readHead(path3, bytes = HEAD_BYTES) {
8387
- let fd;
8388
- try {
8389
- fd = openSync2(path3, "r");
8390
- } catch {
8391
- return null;
8392
- }
8393
- try {
8394
- const buf = Buffer.alloc(bytes);
8395
- const read = readSync(fd, buf, 0, bytes, 0);
8396
- return buf.subarray(0, read).toString("utf-8");
8397
- } catch {
8398
- return null;
8399
- } finally {
8400
- closeSync2(fd);
8401
- }
8402
- }
8403
- function cwdFromJsonlHead(text) {
8404
- for (const line of text.split(`
8405
- `).slice(0, HEAD_LINES)) {
8406
- if (!line.includes('"cwd"'))
8407
- continue;
8408
- try {
8409
- const obj = JSON.parse(line);
8410
- const cwd = obj.cwd ?? obj.payload?.cwd;
8411
- if (typeof cwd === "string" && cwd.startsWith("/"))
8412
- return cwd;
8413
- } catch {}
8414
- }
8415
- return null;
8416
- }
8417
- function unmungeSlug(slug, exists = existsSync9) {
8418
- const tokens = slug.replace(/^-/, "").split("-");
8419
- if (tokens.length === 0 || tokens[0] === "")
8420
- return null;
8421
- let budget = 5000;
8422
- const walk = (prefix, index) => {
8423
- if (budget-- <= 0)
8424
- return null;
8425
- if (index === tokens.length)
8426
- return exists(prefix) ? prefix : null;
8427
- const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
8428
- if (asSegment)
8429
- return asSegment;
8430
- return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
8431
- };
8432
- return walk("", 0);
8433
- }
8434
- function fileMtime(path3) {
8435
- try {
8436
- return statSync3(path3).mtime.toISOString();
8437
- } catch {
8438
- return "";
8439
- }
8440
- }
8441
- function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
8442
- const exists = deps.exists ?? existsSync9;
8443
- const head = deps.readHead ?? readHead;
8444
- const mtime = deps.mtime ?? fileMtime;
8445
- const entries = loadCacheFile(configDir);
8446
- let dirty = false;
8447
- const compute = (session) => {
8448
- switch (session.harness) {
8449
- case "opencode":
8450
- return session.project ?? null;
8451
- case "claude": {
8452
- const text = head(session.path);
8453
- const cwd = text ? cwdFromJsonlHead(text) : null;
8454
- if (cwd)
8455
- return cwd;
8456
- return session.project ? unmungeSlug(session.project, exists) : null;
8457
- }
8458
- case "codex": {
8459
- const text = head(session.path);
8460
- return text ? cwdFromJsonlHead(text) : null;
8461
- }
8462
- case "cursor":
8463
- return session.project ? unmungeSlug(session.project, exists) : null;
8464
- default:
8465
- return null;
8466
- }
8467
- };
8468
- return {
8469
- resolve(session) {
8470
- const key = `${session.harness}/${session.id}`;
8471
- const cached = entries[key];
8472
- if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
8473
- return cached.dir;
8474
- }
8475
- const dir = compute(session);
8476
- entries[key] = { dir, mtime: mtime(session.path) };
8477
- dirty = true;
8478
- return dir;
8479
- },
8480
- flush() {
8481
- if (!dirty)
8482
- return;
8483
- saveCacheFile(configDir, entries);
8484
- dirty = false;
8485
- }
8486
- };
8487
- }
8488
- var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
8489
- var init_project_dir = __esm(() => {
8490
- init_config();
8491
- HEAD_BYTES = 128 * 1024;
8492
- });
8493
-
8494
8361
  // src/sessions/scan.ts
8495
- import { existsSync as existsSync10, readdirSync, statSync as statSync4 } from "node:fs";
8362
+ import { existsSync as existsSync9, readdirSync, statSync as statSync3 } from "node:fs";
8496
8363
  import { createRequire as createRequire2 } from "node:module";
8497
8364
  import { homedir as homedir3 } from "node:os";
8498
- import { basename, join as join9 } from "node:path";
8365
+ import { basename, join as join8 } from "node:path";
8499
8366
  function listDir(dir) {
8500
8367
  try {
8501
8368
  return readdirSync(dir, { withFileTypes: true }).map((entry) => ({
8502
8369
  name: entry.name,
8503
- path: join9(dir, entry.name),
8370
+ path: join8(dir, entry.name),
8504
8371
  isDir: entry.isDirectory()
8505
8372
  }));
8506
8373
  } catch {
@@ -8513,7 +8380,7 @@ function sessionFromFile(filePath, harness, project) {
8513
8380
  return null;
8514
8381
  let mtime;
8515
8382
  try {
8516
- mtime = statSync4(filePath).mtime;
8383
+ mtime = statSync3(filePath).mtime;
8517
8384
  } catch {
8518
8385
  return null;
8519
8386
  }
@@ -8527,7 +8394,7 @@ function sessionFromFile(filePath, harness, project) {
8527
8394
  }
8528
8395
  function scanClaude(home) {
8529
8396
  const sessions = [];
8530
- for (const project of listDir(join9(home, ".claude", "projects"))) {
8397
+ for (const project of listDir(join8(home, ".claude", "projects"))) {
8531
8398
  if (!project.isDir)
8532
8399
  continue;
8533
8400
  for (const entry of listDir(project.path)) {
@@ -8542,10 +8409,10 @@ function scanClaude(home) {
8542
8409
  }
8543
8410
  function scanCursor(home) {
8544
8411
  const sessions = [];
8545
- for (const project of listDir(join9(home, ".cursor", "projects"))) {
8412
+ for (const project of listDir(join8(home, ".cursor", "projects"))) {
8546
8413
  if (!project.isDir)
8547
8414
  continue;
8548
- for (const entry of listDir(join9(project.path, "agent-transcripts"))) {
8415
+ for (const entry of listDir(join8(project.path, "agent-transcripts"))) {
8549
8416
  const files = entry.isDir ? listDir(entry.path).filter((f2) => !f2.isDir) : [entry];
8550
8417
  for (const file of files) {
8551
8418
  const session = sessionFromFile(file.path, "cursor", project.name);
@@ -8557,9 +8424,9 @@ function scanCursor(home) {
8557
8424
  return sessions;
8558
8425
  }
8559
8426
  function scanCodex(home, env) {
8560
- const codexHome2 = env.CODEX_HOME ?? join9(home, ".codex");
8427
+ const codexHome2 = env.CODEX_HOME ?? join8(home, ".codex");
8561
8428
  const sessions = [];
8562
- for (const year of listDir(join9(codexHome2, "sessions"))) {
8429
+ for (const year of listDir(join8(codexHome2, "sessions"))) {
8563
8430
  if (!year.isDir)
8564
8431
  continue;
8565
8432
  for (const month of listDir(year.path)) {
@@ -8615,9 +8482,9 @@ function querySqlite(dbPath, sql) {
8615
8482
  }
8616
8483
  }
8617
8484
  function scanOpencode(home, env) {
8618
- const dataDir = env.XDG_DATA_HOME ?? join9(home, ".local", "share");
8619
- const dbPath = join9(dataDir, "opencode", "opencode.db");
8620
- if (!existsSync10(dbPath))
8485
+ const dataDir = env.XDG_DATA_HOME ?? join8(home, ".local", "share");
8486
+ const dbPath = join8(dataDir, "opencode", "opencode.db");
8487
+ if (!existsSync9(dbPath))
8621
8488
  return [];
8622
8489
  const rows = querySqlite(dbPath, "SELECT id, directory, time_updated FROM session WHERE parent_id IS NULL");
8623
8490
  if (!rows)
@@ -8655,10 +8522,10 @@ function scanAgentSessions(options = {}) {
8655
8522
  var init_scan = () => {};
8656
8523
 
8657
8524
  // src/sync/watermark.ts
8658
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "node:fs";
8659
- import { join as join10 } from "node:path";
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";
8660
8527
  function syncStatePath(configDir = getConfigDir()) {
8661
- return join10(configDir, STATE_FILENAME);
8528
+ return join9(configDir, STATE_FILENAME);
8662
8529
  }
8663
8530
  function loadSyncState(configDir = getConfigDir()) {
8664
8531
  const empty = {
@@ -8667,10 +8534,10 @@ function loadSyncState(configDir = getConfigDir()) {
8667
8534
  consecutive_failures: 0
8668
8535
  };
8669
8536
  const path3 = syncStatePath(configDir);
8670
- if (!existsSync11(path3))
8537
+ if (!existsSync10(path3))
8671
8538
  return empty;
8672
8539
  try {
8673
- const raw = JSON.parse(readFileSync10(path3, "utf-8"));
8540
+ const raw = JSON.parse(readFileSync9(path3, "utf-8"));
8674
8541
  if (raw.schema_version !== STATE_SCHEMA_VERSION)
8675
8542
  return empty;
8676
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) => ({
@@ -8711,12 +8578,12 @@ function setSyncPaused(paused, configDir = getConfigDir()) {
8711
8578
  saveSyncState(state, configDir);
8712
8579
  }
8713
8580
  function saveSyncState(state, configDir = getConfigDir()) {
8714
- if (!existsSync11(configDir))
8715
- mkdirSync7(configDir, { recursive: true, mode: 448 });
8581
+ if (!existsSync10(configDir))
8582
+ mkdirSync6(configDir, { recursive: true, mode: 448 });
8716
8583
  const path3 = syncStatePath(configDir);
8717
8584
  const tmp = `${path3}.${process.pid}.tmp`;
8718
- writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
8719
- renameSync3(tmp, path3);
8585
+ writeFileSync6(tmp, JSON.stringify(state, null, 2), { mode: 384 });
8586
+ renameSync2(tmp, path3);
8720
8587
  }
8721
8588
  function backoffUntil(state) {
8722
8589
  if (state.consecutive_failures === 0 || !state.last_attempt_at)
@@ -8768,6 +8635,1668 @@ var init_watermark = __esm(() => {
8768
8635
  BACKOFF_MAX_MS = 24 * 60 * 60 * 1000;
8769
8636
  });
8770
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
+
9379
+ // src/sessions/read.ts
9380
+ import { readFileSync as readFileSync10 } from "node:fs";
9381
+ function asRecord(value) {
9382
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
9383
+ }
9384
+ function jsonlRecords(raw) {
9385
+ const records = [];
9386
+ for (const line of raw.split(`
9387
+ `)) {
9388
+ if (line.trim() === "")
9389
+ continue;
9390
+ try {
9391
+ const record = asRecord(JSON.parse(line));
9392
+ if (record)
9393
+ records.push(record);
9394
+ } catch {}
9395
+ }
9396
+ return records;
9397
+ }
9398
+ function textFromContent(content, textTypes) {
9399
+ if (typeof content === "string")
9400
+ return content;
9401
+ if (!Array.isArray(content))
9402
+ return "";
9403
+ const parts = [];
9404
+ for (const entry of content) {
9405
+ const item = asRecord(entry);
9406
+ if (!item)
9407
+ continue;
9408
+ if (typeof item.type === "string" && textTypes.has(item.type) && typeof item.text === "string") {
9409
+ parts.push(item.text);
9410
+ }
9411
+ }
9412
+ return parts.join(`
9413
+ `);
9414
+ }
9415
+ function pushTurn(turns, role, text) {
9416
+ if (text.trim() !== "")
9417
+ turns.push({ role, text });
9418
+ }
9419
+ function readClaude(raw) {
9420
+ const turns = [];
9421
+ for (const record of jsonlRecords(raw)) {
9422
+ const role = record.type;
9423
+ if (role !== "user" && role !== "assistant")
9424
+ continue;
9425
+ if (record.isSidechain === true)
9426
+ continue;
9427
+ const message = asRecord(record.message);
9428
+ if (!message)
9429
+ continue;
9430
+ pushTurn(turns, role, textFromContent(message.content, PLAIN_TEXT));
9431
+ }
9432
+ return turns;
9433
+ }
9434
+ function readCursor(raw) {
9435
+ const turns = [];
9436
+ for (const record of jsonlRecords(raw)) {
9437
+ const role = record.role;
9438
+ if (role !== "user" && role !== "assistant")
9439
+ continue;
9440
+ const message = asRecord(record.message);
9441
+ if (!message)
9442
+ continue;
9443
+ pushTurn(turns, role, textFromContent(message.content, PLAIN_TEXT));
9444
+ }
9445
+ return turns;
9446
+ }
9447
+ function isCodexInjectedBlock(text) {
9448
+ const head = text.trimStart().toLowerCase();
9449
+ return head.startsWith("<user_instructions>") || head.startsWith("<environment_context>") || head.startsWith("<recommended_plugins>");
9450
+ }
9451
+ function readCodex(raw) {
9452
+ const turns = [];
9453
+ for (const record of jsonlRecords(raw)) {
9454
+ if (record.type !== "response_item")
9455
+ continue;
9456
+ const payload = asRecord(record.payload);
9457
+ if (payload?.type !== "message")
9458
+ continue;
9459
+ const role = payload.role;
9460
+ if (role !== "user" && role !== "assistant")
9461
+ continue;
9462
+ const text = textFromContent(payload.content, CODEX_TEXT);
9463
+ if (role === "user" && isCodexInjectedBlock(text))
9464
+ continue;
9465
+ pushTurn(turns, role, text);
9466
+ }
9467
+ return turns;
9468
+ }
9469
+ function readOpencode(dbPath, sessionId) {
9470
+ if (!/^[A-Za-z0-9_-]+$/.test(sessionId))
9471
+ return [];
9472
+ const rows = querySqlite(dbPath, "SELECT m.data AS message, p.data AS part FROM part p " + "JOIN message m ON m.id = p.message_id " + `WHERE p.session_id = '${sessionId}' ORDER BY p.time_created, p.id`);
9473
+ if (!rows)
9474
+ return [];
9475
+ const turns = [];
9476
+ for (const row of rows) {
9477
+ if (typeof row.message !== "string" || typeof row.part !== "string")
9478
+ continue;
9479
+ try {
9480
+ const message = asRecord(JSON.parse(row.message));
9481
+ const part = asRecord(JSON.parse(row.part));
9482
+ const role = message?.role;
9483
+ if (role !== "user" && role !== "assistant")
9484
+ continue;
9485
+ if (part?.type !== "text" || typeof part.text !== "string")
9486
+ continue;
9487
+ pushTurn(turns, role, part.text);
9488
+ } catch {}
9489
+ }
9490
+ return turns;
9491
+ }
9492
+ function readSessionTurns(session) {
9493
+ try {
9494
+ switch (session.harness) {
9495
+ case "claude":
9496
+ return readClaude(readFileSync10(session.path, "utf8"));
9497
+ case "cursor":
9498
+ return readCursor(readFileSync10(session.path, "utf8"));
9499
+ case "codex":
9500
+ return readCodex(readFileSync10(session.path, "utf8"));
9501
+ case "opencode":
9502
+ return readOpencode(session.path, session.id);
9503
+ }
9504
+ } catch {
9505
+ return [];
9506
+ }
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
+ }
9555
+ function estimateSessionTokens(session) {
9556
+ let chars = 0;
9557
+ for (const turn of readSessionTurns(session))
9558
+ chars += turn.text.length;
9559
+ return Math.round(chars / CHARS_PER_TOKEN2);
9560
+ }
9561
+ function isWorthMining(session) {
9562
+ const turns = readSessionTurns(session);
9563
+ if (turns.length < MIN_WORTH_TURNS)
9564
+ return false;
9565
+ let chars = 0;
9566
+ for (const turn of turns) {
9567
+ chars += turn.text.length;
9568
+ if (chars >= MIN_WORTH_CHARS)
9569
+ return true;
9570
+ }
9571
+ return false;
9572
+ }
9573
+ var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS, CHARS_PER_TOKEN2 = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
9574
+ var init_read = __esm(() => {
9575
+ init_scan();
9576
+ PLAIN_TEXT = new Set(["text"]);
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
+
8771
10300
  // src/sync/backlog.ts
8772
10301
  function listSessionBacklog() {
8773
10302
  try {
@@ -8791,8 +10320,8 @@ var init_backlog = __esm(() => {
8791
10320
  });
8792
10321
 
8793
10322
  // src/sync/lock.ts
8794
- import { existsSync as existsSync12, readFileSync as readFileSync11, rmSync as rmSync2, statSync as statSync5, writeFileSync as writeFileSync8 } from "node:fs";
8795
- import { join as join11 } from "node:path";
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";
8796
10325
  function processAlive(pid) {
8797
10326
  try {
8798
10327
  process.kill(pid, 0);
@@ -8802,7 +10331,7 @@ function processAlive(pid) {
8802
10331
  }
8803
10332
  }
8804
10333
  function lockPath(configDir = getConfigDir()) {
8805
- return join11(configDir, LOCK_FILENAME);
10334
+ return join12(configDir, LOCK_FILENAME);
8806
10335
  }
8807
10336
  function stopSyncRun(pid, configDir = getConfigDir(), kill = process.kill.bind(process)) {
8808
10337
  if (!Number.isInteger(pid) || pid <= 0)
@@ -8819,7 +10348,7 @@ function stopSyncRun(pid, configDir = getConfigDir(), kill = process.kill.bind(p
8819
10348
  }
8820
10349
  try {
8821
10350
  const path3 = lockPath(configDir);
8822
- if (existsSync12(path3) && readFileSync11(path3, "utf8") === String(pid)) {
10351
+ if (existsSync12(path3) && readFileSync13(path3, "utf8") === String(pid)) {
8823
10352
  rmSync2(path3, { force: true });
8824
10353
  }
8825
10354
  } catch {}
@@ -8830,7 +10359,7 @@ function fileLock(configDir = getConfigDir(), now = () => new Date) {
8830
10359
  let held = false;
8831
10360
  const tryCreate = () => {
8832
10361
  try {
8833
- writeFileSync8(path3, String(process.pid), { flag: "wx" });
10362
+ writeFileSync9(path3, String(process.pid), { flag: "wx" });
8834
10363
  return true;
8835
10364
  } catch {
8836
10365
  return false;
@@ -8844,7 +10373,7 @@ function fileLock(configDir = getConfigDir(), now = () => new Date) {
8844
10373
  }
8845
10374
  try {
8846
10375
  const age = now().getTime() - statSync5(path3).mtimeMs;
8847
- const holder = Number.parseInt(readFileSync11(path3, "utf8"), 10);
10376
+ const holder = Number.parseInt(readFileSync13(path3, "utf8"), 10);
8848
10377
  const holderDead = Number.isInteger(holder) && holder > 0 && !processAlive(holder);
8849
10378
  if (age > STALE_LOCK_MS || holderDead) {
8850
10379
  rmSync2(path3, { force: true });
@@ -8859,7 +10388,7 @@ function fileLock(configDir = getConfigDir(), now = () => new Date) {
8859
10388
  return;
8860
10389
  held = false;
8861
10390
  try {
8862
- if (existsSync12(path3) && readFileSync11(path3, "utf8") === String(process.pid)) {
10391
+ if (existsSync12(path3) && readFileSync13(path3, "utf8") === String(process.pid)) {
8863
10392
  rmSync2(path3, { force: true });
8864
10393
  }
8865
10394
  } catch {}
@@ -8873,7 +10402,7 @@ var init_lock = __esm(() => {
8873
10402
  });
8874
10403
 
8875
10404
  // src/sync/status.ts
8876
- import { readFileSync as readFileSync12, statSync as statSync6 } from "node:fs";
10405
+ import { readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
8877
10406
  function formatTokenCount(tokens) {
8878
10407
  if (tokens < 1000)
8879
10408
  return String(tokens);
@@ -8896,7 +10425,7 @@ function defaultIsProcessAlive2(pid) {
8896
10425
  }
8897
10426
  function defaultReadLog() {
8898
10427
  try {
8899
- return readFileSync12(logger.getLogPath(), "utf-8");
10428
+ return readFileSync14(logger.getLogPath(), "utf-8");
8900
10429
  } catch {
8901
10430
  return "";
8902
10431
  }
@@ -8904,7 +10433,7 @@ function defaultReadLog() {
8904
10433
  function readLockFile(configDir) {
8905
10434
  const path3 = lockPath(configDir);
8906
10435
  try {
8907
- const pid = Number.parseInt(readFileSync12(path3, "utf-8"), 10);
10436
+ const pid = Number.parseInt(readFileSync14(path3, "utf-8"), 10);
8908
10437
  return { pid: Number.isNaN(pid) ? -1 : pid, mtime: statSync6(path3).mtime };
8909
10438
  } catch {
8910
10439
  return null;
@@ -8939,160 +10468,6 @@ var init_status = __esm(() => {
8939
10468
  init_watermark();
8940
10469
  });
8941
10470
 
8942
- // src/sessions/read.ts
8943
- import { readFileSync as readFileSync13 } from "node:fs";
8944
- function asRecord(value) {
8945
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
8946
- }
8947
- function jsonlRecords(raw) {
8948
- const records = [];
8949
- for (const line of raw.split(`
8950
- `)) {
8951
- if (line.trim() === "")
8952
- continue;
8953
- try {
8954
- const record = asRecord(JSON.parse(line));
8955
- if (record)
8956
- records.push(record);
8957
- } catch {}
8958
- }
8959
- return records;
8960
- }
8961
- function textFromContent(content, textTypes) {
8962
- if (typeof content === "string")
8963
- return content;
8964
- if (!Array.isArray(content))
8965
- return "";
8966
- const parts = [];
8967
- for (const entry of content) {
8968
- const item = asRecord(entry);
8969
- if (!item)
8970
- continue;
8971
- if (typeof item.type === "string" && textTypes.has(item.type) && typeof item.text === "string") {
8972
- parts.push(item.text);
8973
- }
8974
- }
8975
- return parts.join(`
8976
- `);
8977
- }
8978
- function pushTurn(turns, role, text) {
8979
- if (text.trim() !== "")
8980
- turns.push({ role, text });
8981
- }
8982
- function readClaude(raw) {
8983
- const turns = [];
8984
- for (const record of jsonlRecords(raw)) {
8985
- const role = record.type;
8986
- if (role !== "user" && role !== "assistant")
8987
- continue;
8988
- if (record.isSidechain === true)
8989
- continue;
8990
- const message = asRecord(record.message);
8991
- if (!message)
8992
- continue;
8993
- pushTurn(turns, role, textFromContent(message.content, PLAIN_TEXT));
8994
- }
8995
- return turns;
8996
- }
8997
- function readCursor(raw) {
8998
- const turns = [];
8999
- for (const record of jsonlRecords(raw)) {
9000
- const role = record.role;
9001
- if (role !== "user" && role !== "assistant")
9002
- continue;
9003
- const message = asRecord(record.message);
9004
- if (!message)
9005
- continue;
9006
- pushTurn(turns, role, textFromContent(message.content, PLAIN_TEXT));
9007
- }
9008
- return turns;
9009
- }
9010
- function isCodexInjectedBlock(text) {
9011
- const head = text.trimStart().toLowerCase();
9012
- return head.startsWith("<user_instructions>") || head.startsWith("<environment_context>") || head.startsWith("<recommended_plugins>");
9013
- }
9014
- function readCodex(raw) {
9015
- const turns = [];
9016
- for (const record of jsonlRecords(raw)) {
9017
- if (record.type !== "response_item")
9018
- continue;
9019
- const payload = asRecord(record.payload);
9020
- if (payload?.type !== "message")
9021
- continue;
9022
- const role = payload.role;
9023
- if (role !== "user" && role !== "assistant")
9024
- continue;
9025
- const text = textFromContent(payload.content, CODEX_TEXT);
9026
- if (role === "user" && isCodexInjectedBlock(text))
9027
- continue;
9028
- pushTurn(turns, role, text);
9029
- }
9030
- return turns;
9031
- }
9032
- function readOpencode(dbPath, sessionId) {
9033
- if (!/^[A-Za-z0-9_-]+$/.test(sessionId))
9034
- return [];
9035
- const rows = querySqlite(dbPath, "SELECT m.data AS message, p.data AS part FROM part p " + "JOIN message m ON m.id = p.message_id " + `WHERE p.session_id = '${sessionId}' ORDER BY p.time_created, p.id`);
9036
- if (!rows)
9037
- return [];
9038
- const turns = [];
9039
- for (const row of rows) {
9040
- if (typeof row.message !== "string" || typeof row.part !== "string")
9041
- continue;
9042
- try {
9043
- const message = asRecord(JSON.parse(row.message));
9044
- const part = asRecord(JSON.parse(row.part));
9045
- const role = message?.role;
9046
- if (role !== "user" && role !== "assistant")
9047
- continue;
9048
- if (part?.type !== "text" || typeof part.text !== "string")
9049
- continue;
9050
- pushTurn(turns, role, part.text);
9051
- } catch {}
9052
- }
9053
- return turns;
9054
- }
9055
- function readSessionTurns(session) {
9056
- try {
9057
- switch (session.harness) {
9058
- case "claude":
9059
- return readClaude(readFileSync13(session.path, "utf8"));
9060
- case "cursor":
9061
- return readCursor(readFileSync13(session.path, "utf8"));
9062
- case "codex":
9063
- return readCodex(readFileSync13(session.path, "utf8"));
9064
- case "opencode":
9065
- return readOpencode(session.path, session.id);
9066
- }
9067
- } catch {
9068
- return [];
9069
- }
9070
- }
9071
- function estimateSessionTokens(session) {
9072
- let chars = 0;
9073
- for (const turn of readSessionTurns(session))
9074
- chars += turn.text.length;
9075
- return Math.round(chars / CHARS_PER_TOKEN);
9076
- }
9077
- function isWorthMining(session) {
9078
- const turns = readSessionTurns(session);
9079
- if (turns.length < MIN_WORTH_TURNS)
9080
- return false;
9081
- let chars = 0;
9082
- for (const turn of turns) {
9083
- chars += turn.text.length;
9084
- if (chars >= MIN_WORTH_CHARS)
9085
- return true;
9086
- }
9087
- return false;
9088
- }
9089
- var PLAIN_TEXT, CODEX_TEXT, CHARS_PER_TOKEN = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
9090
- var init_read = __esm(() => {
9091
- init_scan();
9092
- PLAIN_TEXT = new Set(["text"]);
9093
- CODEX_TEXT = new Set(["input_text", "output_text"]);
9094
- });
9095
-
9096
10471
  // src/sync/sync.ts
9097
10472
  function logGateResult(ready, inFlight, watermark) {
9098
10473
  const preview = ready.slice(0, LOG_PREVIEW_LIMIT).map((s) => `${s.harness}/${s.id}`).join(", ");
@@ -9136,14 +10511,14 @@ async function runKnowledgeSync(options = {}) {
9136
10511
  }));
9137
10512
  let sessions = await listSessions();
9138
10513
  if (state.project_filter?.length) {
9139
- let resolve = deps.resolveProjectDir;
10514
+ let resolve2 = deps.resolveProjectDir;
9140
10515
  let flush;
9141
- if (!resolve) {
10516
+ if (!resolve2) {
9142
10517
  const resolver = createProjectDirResolver();
9143
- resolve = resolver.resolve;
10518
+ resolve2 = resolver.resolve;
9144
10519
  flush = resolver.flush;
9145
10520
  }
9146
- sessions = filterSessionsByProject(sessions, state.project_filter, resolve);
10521
+ sessions = filterSessionsByProject(sessions, state.project_filter, resolve2);
9147
10522
  flush?.();
9148
10523
  logger.debug("sync", `project filter active: ${state.project_filter.join(", ")}`);
9149
10524
  }
@@ -9303,13 +10678,141 @@ var init_sync = __esm(() => {
9303
10678
  init_watermark();
9304
10679
  });
9305
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
+
9306
10809
  // src/miner/config-dir.ts
9307
- import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "node:fs";
9308
- import { tmpdir } from "node:os";
9309
- 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";
9310
10813
  function createRunConfigDir() {
9311
- const path3 = mkdtempSync(join12(tmpdir(), "dosu-miner-"));
9312
- 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 }));
9313
10816
  return {
9314
10817
  path: path3,
9315
10818
  cleanup() {
@@ -9320,7 +10823,7 @@ function createRunConfigDir() {
9320
10823
  var init_config_dir = () => {};
9321
10824
 
9322
10825
  // src/miner/conflicts.ts
9323
- import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
10826
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
9324
10827
  function managedSettingsPaths(platform3 = process.platform) {
9325
10828
  switch (platform3) {
9326
10829
  case "darwin":
@@ -9353,7 +10856,7 @@ function detectSettingsConflicts(paths = managedSettingsPaths()) {
9353
10856
  continue;
9354
10857
  let settings2;
9355
10858
  try {
9356
- settings2 = JSON.parse(readFileSync14(file, "utf8"));
10859
+ settings2 = JSON.parse(readFileSync15(file, "utf8"));
9357
10860
  } catch {
9358
10861
  conflicts.push({ file, keys: ["<unreadable or invalid JSON>"] });
9359
10862
  continue;
@@ -9415,7 +10918,7 @@ var init_env = __esm(() => {
9415
10918
  import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
9416
10919
  import { createRequire as createRequire3 } from "node:module";
9417
10920
  import { homedir as homedir4 } from "node:os";
9418
- 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";
9419
10922
  function binaryNames(platform3 = process.platform) {
9420
10923
  return platform3 === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
9421
10924
  }
@@ -9423,8 +10926,8 @@ function sdkNativeBinaryExists() {
9423
10926
  try {
9424
10927
  const requireRuntime = createRequire3(import.meta.url);
9425
10928
  const pkg = `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}`;
9426
- const pkgDir = dirname4(requireRuntime.resolve(`${pkg}/package.json`));
9427
- 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)));
9428
10931
  } catch {
9429
10932
  return false;
9430
10933
  }
@@ -9440,10 +10943,10 @@ function isExecutable(path3) {
9440
10943
  function findSystemClaude(env = process.env, homeDir = homedir4()) {
9441
10944
  const names = binaryNames();
9442
10945
  const dirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
9443
- dirs.push(join13(homeDir, ".local", "bin"), join13(homeDir, ".claude", "local"));
10946
+ dirs.push(join14(homeDir, ".local", "bin"), join14(homeDir, ".claude", "local"));
9444
10947
  for (const dir of dirs) {
9445
10948
  for (const name of names) {
9446
- const candidate = join13(dir, name);
10949
+ const candidate = join14(dir, name);
9447
10950
  if (isExecutable(candidate))
9448
10951
  return candidate;
9449
10952
  }
@@ -52091,7 +53594,7 @@ __export(exports_util, {
52091
53594
  extend: () => extend,
52092
53595
  explicitlyAborted: () => explicitlyAborted,
52093
53596
  escapeRegex: () => escapeRegex,
52094
- esc: () => esc,
53597
+ esc: () => esc2,
52095
53598
  defineLazyInternal: () => defineLazyInternal,
52096
53599
  defineLazy: () => defineLazy,
52097
53600
  createTransparentProxy: () => createTransparentProxy,
@@ -52242,7 +53745,7 @@ function randomString(length = 10) {
52242
53745
  }
52243
53746
  return str;
52244
53747
  }
52245
- function esc(str) {
53748
+ function esc2(str) {
52246
53749
  return JSON.stringify(str);
52247
53750
  }
52248
53751
  function slugify(input) {
@@ -55363,7 +56866,7 @@ var init_schemas = __esm(() => {
55363
56866
  if (key === "__proto__")
55364
56867
  continue;
55365
56868
  const id2 = ids[key];
55366
- const k2 = typeof key === "symbol" ? `syms[${syms.indexOf(key)}]` : esc(key);
56869
+ const k2 = typeof key === "symbol" ? `syms[${syms.indexOf(key)}]` : esc2(key);
55367
56870
  const isPresent = `${k2} in input`;
55368
56871
  const schema = shape[key];
55369
56872
  const optin = schema?._zod?.optin;
@@ -64600,16 +66103,16 @@ function generateStringFormatCheck(doc, ctx, def, accessor) {
64600
66103
  doc.write(`if (${accessor} !== ${accessor}.toUpperCase()) return INVALID;`);
64601
66104
  break;
64602
66105
  case "includes":
64603
- doc.write(`if (!${accessor}.includes(${esc(def.includes)})) return INVALID;`);
66106
+ doc.write(`if (!${accessor}.includes(${esc2(def.includes)})) return INVALID;`);
64604
66107
  break;
64605
66108
  case "starts_with": {
64606
66109
  const prefix = def.prefix;
64607
- 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;`);
64608
66111
  break;
64609
66112
  }
64610
66113
  case "ends_with": {
64611
66114
  const suffix = def.suffix;
64612
- doc.write(`if (${accessor}.slice(-${suffix.length}) !== ${esc(suffix)}) return INVALID;`);
66115
+ doc.write(`if (${accessor}.slice(-${suffix.length}) !== ${esc2(suffix)}) return INVALID;`);
64613
66116
  break;
64614
66117
  }
64615
66118
  default: {
@@ -64813,8 +66316,8 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
64813
66316
  const keys = Object.keys(shape);
64814
66317
  const symbolKeys = Object.getOwnPropertySymbols(shape);
64815
66318
  const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys;
64816
- const keyExpr = (k2) => typeof k2 === "symbol" ? addConstant(ctx, k2) : esc(k2);
64817
- 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);
64818
66321
  const propShape = shape;
64819
66322
  if (keys.includes("__proto__")) {
64820
66323
  throw new ZodCompileUnsupportedError('object shape key "__proto__"');
@@ -64858,7 +66361,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
64858
66361
  if (catchall) {
64859
66362
  const catchallType = catchall._zod.def.type;
64860
66363
  if (catchallType === "never") {
64861
- const condition = keys.map((k2) => `k !== ${esc(k2)}`).join(" && ") || "true";
66364
+ const condition = keys.map((k2) => `k !== ${esc2(k2)}`).join(" && ") || "true";
64862
66365
  doc.write(`for (const k in ${accessor}) {`);
64863
66366
  doc.indented((d3) => {
64864
66367
  d3.write(`if (${condition}) return INVALID;`);
@@ -65120,7 +66623,7 @@ function generateLiteralCheck(doc, ctx, schema, accessor) {
65120
66623
  return accessor;
65121
66624
  }
65122
66625
  if (typeof value === "string") {
65123
- doc.write(`if (${accessor} !== ${esc(value)}) return INVALID;`);
66626
+ doc.write(`if (${accessor} !== ${esc2(value)}) return INVALID;`);
65124
66627
  } else if (typeof value === "number" || typeof value === "boolean") {
65125
66628
  doc.write(`if (${accessor} !== ${value}) return INVALID;`);
65126
66629
  } else if (value === null) {
@@ -65321,7 +66824,7 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
65321
66824
  }
65322
66825
  const discVar = newVar(ctx);
65323
66826
  const outputVar = newVar(ctx);
65324
- doc.write(`const ${discVar} = ${accessor}?.[${esc(def.discriminator)}];`);
66827
+ doc.write(`const ${discVar} = ${accessor}?.[${esc2(def.discriminator)}];`);
65325
66828
  doc.write(`let ${outputVar};`);
65326
66829
  let firstBranch = true;
65327
66830
  const claimed = new Set;
@@ -65351,7 +66854,7 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
65351
66854
  }
65352
66855
  function literalEquality(ctx, accessor, value) {
65353
66856
  if (typeof value === "string")
65354
- return `${accessor} === ${esc(value)}`;
66857
+ return `${accessor} === ${esc2(value)}`;
65355
66858
  if (typeof value === "number") {
65356
66859
  if (Number.isNaN(value))
65357
66860
  return `Number.isNaN(${accessor})`;
@@ -65465,7 +66968,7 @@ function generateRecordCheck(doc, ctx, schema, accessor) {
65465
66968
  }
65466
66969
  function literalPropertyKey(ctx, key) {
65467
66970
  if (typeof key === "string")
65468
- return esc(key);
66971
+ return esc2(key);
65469
66972
  return addConstant(ctx, key);
65470
66973
  }
65471
66974
  function generateMapCheck(doc, ctx, schema, accessor) {
@@ -71401,6 +72904,15 @@ async function runMiner(options) {
71401
72904
  message: `Refusing to run: conflicting Claude Code settings would override the miner's auth (${detail})`
71402
72905
  };
71403
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
+ }
71404
72916
  const { query } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
71405
72917
  const claudeExecutable = resolveClaudeExecutable();
71406
72918
  if (claudeExecutable) {
@@ -71414,10 +72926,11 @@ async function runMiner(options) {
71414
72926
  const abort = new AbortController;
71415
72927
  const timer = setTimeout(() => abort.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
71416
72928
  let notesWritten = 0;
72929
+ let lastSessionId;
71417
72930
  let turns = 0;
71418
72931
  const env = buildMinerEnv({
71419
72932
  apiKey: options.apiKey,
71420
- gatewayURL: options.gatewayURL ?? getLlmGatewayURL(),
72933
+ gatewayURL,
71421
72934
  configDir: configDir.path,
71422
72935
  runID,
71423
72936
  trigger: options.trigger,
@@ -71457,6 +72970,9 @@ async function runMiner(options) {
71457
72970
  message: `Tool ${toolName} is not permitted in mining runs.`
71458
72971
  };
71459
72972
  }
72973
+ if (toolName === `mcp__${SESSIONS_SERVER_NAME}__read_session`) {
72974
+ lastSessionId = sessionIdFromReadInput(input2) ?? lastSessionId;
72975
+ }
71460
72976
  if (toolName === `mcp__${KNOWLEDGE_SERVER_NAME}__write_knowledge`) {
71461
72977
  if (notesWritten >= maxNotes) {
71462
72978
  return {
@@ -71465,6 +72981,10 @@ async function runMiner(options) {
71465
72981
  };
71466
72982
  }
71467
72983
  notesWritten += 1;
72984
+ return {
72985
+ behavior: "allow",
72986
+ updatedInput: lastSessionId ? { ...input2, transcript_id: lastSessionId } : input2
72987
+ };
71468
72988
  }
71469
72989
  return { behavior: "allow", updatedInput: input2 };
71470
72990
  }
@@ -71525,6 +73045,7 @@ var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLE
71525
73045
  var init_runner = __esm(() => {
71526
73046
  init_logger();
71527
73047
  init_config_helpers();
73048
+ init_notes();
71528
73049
  init_config_dir();
71529
73050
  init_conflicts();
71530
73051
  init_env();
@@ -71548,17 +73069,17 @@ var init_runner = __esm(() => {
71548
73069
  });
71549
73070
 
71550
73071
  // src/version/skill-update-check.ts
71551
- import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
71552
- import { join as join19 } from "node:path";
73072
+ import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "node:fs";
73073
+ import { join as join20 } from "node:path";
71553
73074
  function getCachePath2() {
71554
- return join19(getConfigDir(), CACHE_FILENAME3);
73075
+ return join20(getConfigDir(), CACHE_FILENAME3);
71555
73076
  }
71556
73077
  function readSkillCache() {
71557
73078
  try {
71558
73079
  const path3 = getCachePath2();
71559
73080
  if (!existsSync17(path3))
71560
73081
  return null;
71561
- const data = JSON.parse(readFileSync17(path3, "utf-8"));
73082
+ const data = JSON.parse(readFileSync18(path3, "utf-8"));
71562
73083
  if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
71563
73084
  return data;
71564
73085
  }
@@ -71571,9 +73092,9 @@ function writeSkillCache(cache) {
71571
73092
  try {
71572
73093
  const dir = getConfigDir();
71573
73094
  if (!existsSync17(dir)) {
71574
- mkdirSync9(dir, { recursive: true, mode: 448 });
73095
+ mkdirSync10(dir, { recursive: true, mode: 448 });
71575
73096
  }
71576
- writeFileSync10(getCachePath2(), JSON.stringify(cache), { mode: 384 });
73097
+ writeFileSync11(getCachePath2(), JSON.stringify(cache), { mode: 384 });
71577
73098
  } catch {}
71578
73099
  }
71579
73100
  function clearInstalledSha() {
@@ -71647,7 +73168,7 @@ var init_skill_update_check = __esm(() => {
71647
73168
  // src/commands/skill.ts
71648
73169
  import { exec as exec2, execSync as execSync2 } from "node:child_process";
71649
73170
  import { homedir as homedir6 } from "node:os";
71650
- import { join as join20 } from "node:path";
73171
+ import { join as join21 } from "node:path";
71651
73172
  function skillAgentIDsForProviders(providerIDs) {
71652
73173
  return [
71653
73174
  ...new Set(providerIDs.map((providerID) => SKILL_AGENT_BY_PROVIDER[providerID]).filter((agent) => Boolean(agent)))
@@ -71658,17 +73179,17 @@ function skillInstallTargetForProvider(providerID) {
71658
73179
  if (!agentID)
71659
73180
  return null;
71660
73181
  if (agentID === "claude-code") {
71661
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join20(homedir6(), ".claude");
71662
- return { path: join20(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
73182
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join21(homedir6(), ".claude");
73183
+ return { path: join21(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
71663
73184
  }
71664
73185
  if (agentID === "windsurf") {
71665
73186
  return {
71666
- path: join20(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
73187
+ path: join21(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
71667
73188
  symlink: true
71668
73189
  };
71669
73190
  }
71670
73191
  return {
71671
- path: join20(homedir6(), ".agents", "skills", SKILL_NAME),
73192
+ path: join21(homedir6(), ".agents", "skills", SKILL_NAME),
71672
73193
  symlink: false
71673
73194
  };
71674
73195
  }
@@ -71677,12 +73198,12 @@ function skillAgentArgs(providerIDs) {
71677
73198
  return agents.map((agent) => `-a ${agent}`).join(" ");
71678
73199
  }
71679
73200
  function execQuiet(command) {
71680
- return new Promise((resolve5, reject) => {
73201
+ return new Promise((resolve6, reject) => {
71681
73202
  exec2(command, { windowsHide: true }, (error61) => {
71682
73203
  if (error61)
71683
73204
  reject(error61);
71684
73205
  else
71685
- resolve5();
73206
+ resolve6();
71686
73207
  });
71687
73208
  });
71688
73209
  }
@@ -71829,15 +73350,15 @@ import { randomUUID as randomUUID3 } from "node:crypto";
71829
73350
  import {
71830
73351
  existsSync as existsSync18,
71831
73352
  lstatSync as lstatSync2,
71832
- mkdirSync as mkdirSync10,
71833
- readFileSync as readFileSync18,
73353
+ mkdirSync as mkdirSync11,
73354
+ readFileSync as readFileSync19,
71834
73355
  renameSync as renameSync5,
71835
73356
  unlinkSync as unlinkSync3,
71836
- writeFileSync as writeFileSync11
73357
+ writeFileSync as writeFileSync12
71837
73358
  } from "node:fs";
71838
- import { join as join21 } from "node:path";
73359
+ import { join as join22 } from "node:path";
71839
73360
  function getTelemetrySettingsPath() {
71840
- return join21(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
73361
+ return join22(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
71841
73362
  }
71842
73363
  function loadTelemetrySettings() {
71843
73364
  return readTelemetrySettings().settings;
@@ -71905,7 +73426,7 @@ function readTelemetrySettings() {
71905
73426
  }
71906
73427
  let content;
71907
73428
  try {
71908
- content = readFileSync18(path3, "utf-8");
73429
+ content = readFileSync19(path3, "utf-8");
71909
73430
  } catch {
71910
73431
  return { status: "error", settings: disabledTelemetrySettings() };
71911
73432
  }
@@ -71938,8 +73459,8 @@ function writeTelemetrySettings(settings2) {
71938
73459
  const temporaryPath = `${path3}.${process.pid}.${randomUUID3()}.tmp`;
71939
73460
  try {
71940
73461
  if (!existsSync18(dir))
71941
- mkdirSync10(dir, { recursive: true, mode: 448 });
71942
- writeFileSync11(temporaryPath, `${JSON.stringify(settings2, null, 2)}
73462
+ mkdirSync11(dir, { recursive: true, mode: 448 });
73463
+ writeFileSync12(temporaryPath, `${JSON.stringify(settings2, null, 2)}
71943
73464
  `, {
71944
73465
  flag: "wx",
71945
73466
  mode: 384
@@ -71983,14 +73504,14 @@ var init_settings = __esm(() => {
71983
73504
  });
71984
73505
 
71985
73506
  // src/telemetry/debug-id.ts
71986
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "1952c680-18dc-c6aa-6475-6e2164756e21";
73507
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "cf222ab5-3069-6407-6475-6e2164756e21";
71987
73508
 
71988
73509
  // src/telemetry/telemetry.ts
71989
73510
  import { randomUUID as randomUUID4 } from "node:crypto";
71990
73511
  import { existsSync as existsSync19 } from "node:fs";
71991
73512
  import { request as httpRequest2 } from "node:http";
71992
73513
  import { request as httpsRequest } from "node:https";
71993
- import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve5, sep as sep5 } from "node:path";
73514
+ import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve6, sep as sep5 } from "node:path";
71994
73515
  import { fileURLToPath as fileURLToPath2 } from "node:url";
71995
73516
  function safeString(value, pattern, fallback) {
71996
73517
  if (typeof value !== "string" || !pattern.test(value))
@@ -72116,7 +73637,7 @@ function parseOwnedStackLine(originalLine, packageRoot) {
72116
73637
  }
72117
73638
  if (!isAbsolute4(absolutePath))
72118
73639
  return;
72119
- const resolvedPath = resolve5(absolutePath);
73640
+ const resolvedPath = resolve6(absolutePath);
72120
73641
  const relativePath = relative2(packageRoot, resolvedPath);
72121
73642
  if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep5}`) || isAbsolute4(relativePath)) {
72122
73643
  return;
@@ -72354,7 +73875,7 @@ function fetchWithoutRedirect(url2, init = {}) {
72354
73875
  } catch (error61) {
72355
73876
  return Promise.reject(error61);
72356
73877
  }
72357
- return new Promise((resolve6, reject) => {
73878
+ return new Promise((resolve7, reject) => {
72358
73879
  let request;
72359
73880
  let response;
72360
73881
  let settled = false;
@@ -72378,7 +73899,7 @@ function fetchWithoutRedirect(url2, init = {}) {
72378
73899
  response?.destroy();
72379
73900
  request?.socket?.destroy();
72380
73901
  request?.destroy();
72381
- resolve6(value);
73902
+ resolve7(value);
72382
73903
  };
72383
73904
  const onAbort = () => {
72384
73905
  const error61 = new Error("telemetry request aborted");
@@ -72851,7 +74372,7 @@ var init_antigravity = __esm(() => {
72851
74372
  });
72852
74373
 
72853
74374
  // src/mcp/providers/claude.ts
72854
- import { join as join22 } from "node:path";
74375
+ import { join as join23 } from "node:path";
72855
74376
  var ClaudeProvider = () => createJSONProvider({
72856
74377
  providerName: "Claude Code",
72857
74378
  providerID: "claude",
@@ -72860,24 +74381,24 @@ var ClaudeProvider = () => createJSONProvider({
72860
74381
  paths: ["~/.claude"],
72861
74382
  globalPath: "~/.claude.json",
72862
74383
  topKey: "mcpServers",
72863
- localConfigPath: (cwd) => join22(cwd, ".mcp.json")
74384
+ localConfigPath: (cwd) => join23(cwd, ".mcp.json")
72864
74385
  });
72865
74386
  var init_claude = __esm(() => {
72866
74387
  init_base();
72867
74388
  });
72868
74389
 
72869
74390
  // src/mcp/providers/claude-desktop.ts
72870
- import { join as join23 } from "node:path";
74391
+ import { join as join24 } from "node:path";
72871
74392
  function configPath() {
72872
- return join23(appSupportDir(), "Claude", "claude_desktop_config.json");
74393
+ return join24(appSupportDir(), "Claude", "claude_desktop_config.json");
72873
74394
  }
72874
74395
  var ClaudeDesktopProvider = () => ({
72875
74396
  name: () => "Claude Desktop",
72876
74397
  id: () => "claude-desktop",
72877
74398
  supportsLocal: () => false,
72878
74399
  priority: () => 2,
72879
- detectPaths: () => [join23(appSupportDir(), "Claude")],
72880
- isInstalled: () => isInstalled([join23(appSupportDir(), "Claude")]),
74400
+ detectPaths: () => [join24(appSupportDir(), "Claude")],
74401
+ isInstalled: () => isInstalled([join24(appSupportDir(), "Claude")]),
72881
74402
  globalConfigPath: () => configPath(),
72882
74403
  isConfigured: () => isJSONKeyConfigured(configPath(), "mcpServers"),
72883
74404
  install(cfg, global2) {
@@ -72907,14 +74428,14 @@ var init_claude_desktop = __esm(() => {
72907
74428
  });
72908
74429
 
72909
74430
  // src/mcp/providers/cline.ts
72910
- import { join as join24 } from "node:path";
72911
- var extensionDir = () => join24(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
74431
+ import { join as join25 } from "node:path";
74432
+ var extensionDir = () => join25(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
72912
74433
  providerName: "Cline",
72913
74434
  providerID: "cline",
72914
74435
  local: false,
72915
74436
  priorityValue: 11,
72916
74437
  paths: [extensionDir()],
72917
- globalPath: join24(extensionDir(), "settings", "cline_mcp_settings.json"),
74438
+ globalPath: join25(extensionDir(), "settings", "cline_mcp_settings.json"),
72918
74439
  topKey: "mcpServers",
72919
74440
  buildServer: (cfg) => ({
72920
74441
  url: mcpURL(cfg.active_account.target.deployment_id),
@@ -72930,7 +74451,7 @@ var init_cline = __esm(() => {
72930
74451
  });
72931
74452
 
72932
74453
  // src/mcp/providers/cline-cli.ts
72933
- import { join as join25 } from "node:path";
74454
+ import { join as join26 } from "node:path";
72934
74455
  function clineDir() {
72935
74456
  return process.env.CLINE_DIR ?? expandHome("~/.cline");
72936
74457
  }
@@ -72940,7 +74461,7 @@ var ClineCliProvider = () => createJSONProvider({
72940
74461
  local: false,
72941
74462
  priorityValue: 12,
72942
74463
  paths: [clineDir()],
72943
- globalPath: join25(clineDir(), "data", "settings", "cline_mcp_settings.json"),
74464
+ globalPath: join26(clineDir(), "data", "settings", "cline_mcp_settings.json"),
72944
74465
  topKey: "mcpServers",
72945
74466
  buildServer: (cfg) => ({
72946
74467
  url: mcpURL(cfg.active_account.target.deployment_id),
@@ -72956,20 +74477,20 @@ var init_cline_cli = __esm(() => {
72956
74477
  });
72957
74478
 
72958
74479
  // src/mcp/providers/codex.ts
72959
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
72960
- import { join as join26 } from "node:path";
74480
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "node:fs";
74481
+ import { join as join27 } from "node:path";
72961
74482
  function codexHome2() {
72962
74483
  return process.env.CODEX_HOME ?? expandHome("~/.codex");
72963
74484
  }
72964
74485
  function getConfigPath2(global2) {
72965
74486
  if (global2)
72966
- return join26(codexHome2(), "config.toml");
72967
- return join26(process.cwd(), ".codex", "config.toml");
74487
+ return join27(codexHome2(), "config.toml");
74488
+ return join27(process.cwd(), ".codex", "config.toml");
72968
74489
  }
72969
74490
  function readTOML(path3) {
72970
74491
  if (!existsSync20(path3))
72971
74492
  return "";
72972
- return readFileSync19(path3, "utf-8");
74493
+ return readFileSync20(path3, "utf-8");
72973
74494
  }
72974
74495
  function writeTOML(path3, content) {
72975
74496
  writeSecureFile(path3, content);
@@ -73032,9 +74553,9 @@ var CodexProvider = () => ({
73032
74553
  priority: () => 8,
73033
74554
  detectPaths: () => ["~/.codex"],
73034
74555
  isInstalled: () => isInstalled(["~/.codex"]),
73035
- globalConfigPath: () => join26(codexHome2(), "config.toml"),
74556
+ globalConfigPath: () => join27(codexHome2(), "config.toml"),
73036
74557
  isConfigured: () => {
73037
- const content = readTOML(join26(codexHome2(), "config.toml"));
74558
+ const content = readTOML(join27(codexHome2(), "config.toml"));
73038
74559
  return content.includes("[mcp_servers.dosu]");
73039
74560
  },
73040
74561
  install(cfg, global2) {
@@ -73056,10 +74577,10 @@ var init_codex = __esm(() => {
73056
74577
  });
73057
74578
 
73058
74579
  // src/mcp/providers/copilot.ts
73059
- import { join as join27 } from "node:path";
74580
+ import { join as join28 } from "node:path";
73060
74581
  function globalPath() {
73061
74582
  if (process.env.XDG_CONFIG_HOME) {
73062
- return join27(process.env.XDG_CONFIG_HOME, "mcp-config.json");
74583
+ return join28(process.env.XDG_CONFIG_HOME, "mcp-config.json");
73063
74584
  }
73064
74585
  return expandHome("~/.copilot/mcp-config.json");
73065
74586
  }
@@ -73090,7 +74611,7 @@ var CopilotProvider = () => ({
73090
74611
  };
73091
74612
  installJSONServer(globalPath(), "mcpServers", server);
73092
74613
  } else {
73093
- const configPath2 = join27(process.cwd(), ".vscode", "mcp.json");
74614
+ const configPath2 = join28(process.cwd(), ".vscode", "mcp.json");
73094
74615
  const server = {
73095
74616
  type: "http",
73096
74617
  url: url2,
@@ -73103,7 +74624,7 @@ var CopilotProvider = () => ({
73103
74624
  if (global2) {
73104
74625
  removeJSONServer(globalPath(), "mcpServers");
73105
74626
  } else {
73106
- const configPath2 = join27(process.cwd(), ".vscode", "mcp.json");
74627
+ const configPath2 = join28(process.cwd(), ".vscode", "mcp.json");
73107
74628
  removeJSONServer(configPath2, "servers");
73108
74629
  }
73109
74630
  }
@@ -73115,7 +74636,7 @@ var init_copilot = __esm(() => {
73115
74636
  });
73116
74637
 
73117
74638
  // src/mcp/providers/cursor.ts
73118
- import { join as join28 } from "node:path";
74639
+ import { join as join29 } from "node:path";
73119
74640
  var CursorProvider = () => createJSONProvider({
73120
74641
  providerName: "Cursor",
73121
74642
  providerID: "cursor",
@@ -73128,7 +74649,7 @@ var CursorProvider = () => createJSONProvider({
73128
74649
  url: mcpURL(cfg.active_account.target.deployment_id),
73129
74650
  headers: mcpHeaders(cfg.active_account.target.api_key)
73130
74651
  }),
73131
- localConfigPath: (cwd) => join28(cwd, ".cursor", "mcp.json")
74652
+ localConfigPath: (cwd) => join29(cwd, ".cursor", "mcp.json")
73132
74653
  });
73133
74654
  var init_cursor = __esm(() => {
73134
74655
  init_config_helpers();
@@ -73136,7 +74657,7 @@ var init_cursor = __esm(() => {
73136
74657
  });
73137
74658
 
73138
74659
  // src/mcp/providers/factory.ts
73139
- import { join as join29 } from "node:path";
74660
+ import { join as join30 } from "node:path";
73140
74661
  var FactoryProvider = () => createJSONProvider({
73141
74662
  providerName: "Factory",
73142
74663
  providerID: "factory",
@@ -73145,14 +74666,14 @@ var FactoryProvider = () => createJSONProvider({
73145
74666
  paths: ["~/.factory"],
73146
74667
  globalPath: "~/.factory/mcp.json",
73147
74668
  topKey: "mcpServers",
73148
- localConfigPath: (cwd) => join29(cwd, ".factory", "mcp.json")
74669
+ localConfigPath: (cwd) => join30(cwd, ".factory", "mcp.json")
73149
74670
  });
73150
74671
  var init_factory = __esm(() => {
73151
74672
  init_base();
73152
74673
  });
73153
74674
 
73154
74675
  // src/mcp/providers/gemini.ts
73155
- import { join as join30 } from "node:path";
74676
+ import { join as join31 } from "node:path";
73156
74677
  var GeminiProvider = () => createJSONProvider({
73157
74678
  providerName: "Gemini CLI",
73158
74679
  providerID: "gemini",
@@ -73161,7 +74682,7 @@ var GeminiProvider = () => createJSONProvider({
73161
74682
  paths: ["~/.gemini"],
73162
74683
  globalPath: "~/.gemini/settings.json",
73163
74684
  topKey: "mcpServers",
73164
- localConfigPath: (cwd) => join30(cwd, ".gemini", "settings.json")
74685
+ localConfigPath: (cwd) => join31(cwd, ".gemini", "settings.json")
73165
74686
  });
73166
74687
  var init_gemini = __esm(() => {
73167
74688
  init_base();
@@ -73212,7 +74733,7 @@ var init_manual = __esm(() => {
73212
74733
 
73213
74734
  // src/mcp/providers/mcporter.ts
73214
74735
  import { existsSync as existsSync21 } from "node:fs";
73215
- import { join as join31 } from "node:path";
74736
+ import { join as join32 } from "node:path";
73216
74737
  function resolveGlobalConfigPath() {
73217
74738
  const jsonPath = expandHome("~/.mcporter/mcporter.json");
73218
74739
  if (existsSync21(jsonPath))
@@ -73239,7 +74760,7 @@ var MCPorterProvider = () => ({
73239
74760
  globalConfigPath: () => resolveGlobalConfigPath(),
73240
74761
  isConfigured: () => isJSONKeyConfigured(resolveGlobalConfigPath(), "mcpServers"),
73241
74762
  install(cfg, global2) {
73242
- const configPath2 = global2 ? resolveGlobalConfigPath() : join31(process.cwd(), "config", "mcporter.json");
74763
+ const configPath2 = global2 ? resolveGlobalConfigPath() : join32(process.cwd(), "config", "mcporter.json");
73243
74764
  const server = {
73244
74765
  type: "http",
73245
74766
  url: mcpEndpoint4(cfg),
@@ -73248,7 +74769,7 @@ var MCPorterProvider = () => ({
73248
74769
  installJSONServer(configPath2, "mcpServers", server);
73249
74770
  },
73250
74771
  remove(global2) {
73251
- const configPath2 = global2 ? resolveGlobalConfigPath() : join31(process.cwd(), "config", "mcporter.json");
74772
+ const configPath2 = global2 ? resolveGlobalConfigPath() : join32(process.cwd(), "config", "mcporter.json");
73252
74773
  removeJSONServer(configPath2, "mcpServers");
73253
74774
  }
73254
74775
  });
@@ -73259,7 +74780,7 @@ var init_mcporter = __esm(() => {
73259
74780
  });
73260
74781
 
73261
74782
  // src/mcp/providers/opencode.ts
73262
- import { join as join32 } from "node:path";
74783
+ import { join as join33 } from "node:path";
73263
74784
  var OpenCodeProvider = () => createJSONProvider({
73264
74785
  providerName: "OpenCode",
73265
74786
  providerID: "opencode",
@@ -73274,7 +74795,7 @@ var OpenCodeProvider = () => createJSONProvider({
73274
74795
  enabled: true,
73275
74796
  headers: mcpHeaders(cfg.active_account.target.api_key)
73276
74797
  }),
73277
- localConfigPath: (cwd) => join32(cwd, "opencode.json")
74798
+ localConfigPath: (cwd) => join33(cwd, "opencode.json")
73278
74799
  });
73279
74800
  var init_opencode = __esm(() => {
73280
74801
  init_config_helpers();
@@ -73282,16 +74803,16 @@ var init_opencode = __esm(() => {
73282
74803
  });
73283
74804
 
73284
74805
  // src/mcp/providers/vscode.ts
73285
- import { join as join33 } from "node:path";
74806
+ import { join as join34 } from "node:path";
73286
74807
  var VSCodeProvider = () => createJSONProvider({
73287
74808
  providerName: "VS Code",
73288
74809
  providerID: "vscode",
73289
74810
  local: true,
73290
74811
  priorityValue: 6,
73291
- paths: [join33(appSupportDir(), "Code")],
73292
- globalPath: join33(appSupportDir(), "Code", "User", "mcp.json"),
74812
+ paths: [join34(appSupportDir(), "Code")],
74813
+ globalPath: join34(appSupportDir(), "Code", "User", "mcp.json"),
73293
74814
  topKey: "servers",
73294
- localConfigPath: (cwd) => join33(cwd, ".vscode", "mcp.json")
74815
+ localConfigPath: (cwd) => join34(cwd, ".vscode", "mcp.json")
73295
74816
  });
73296
74817
  var init_vscode = __esm(() => {
73297
74818
  init_detect();
@@ -73300,14 +74821,14 @@ var init_vscode = __esm(() => {
73300
74821
 
73301
74822
  // src/mcp/providers/windsurf.ts
73302
74823
  import { homedir as homedir8 } from "node:os";
73303
- import { join as join34 } from "node:path";
74824
+ import { join as join35 } from "node:path";
73304
74825
  var WindsurfProvider = () => createJSONProvider({
73305
74826
  providerName: "Windsurf",
73306
74827
  providerID: "windsurf",
73307
74828
  local: false,
73308
74829
  priorityValue: 9,
73309
- paths: [join34(homedir8(), ".codeium", "windsurf")],
73310
- globalPath: join34(homedir8(), ".codeium", "windsurf", "mcp_config.json"),
74830
+ paths: [join35(homedir8(), ".codeium", "windsurf")],
74831
+ globalPath: join35(homedir8(), ".codeium", "windsurf", "mcp_config.json"),
73311
74832
  topKey: "mcpServers"
73312
74833
  });
73313
74834
  var init_windsurf = __esm(() => {
@@ -73316,12 +74837,12 @@ var init_windsurf = __esm(() => {
73316
74837
 
73317
74838
  // src/mcp/providers/zed.ts
73318
74839
  import { platform as platform3 } from "node:os";
73319
- import { join as join35 } from "node:path";
74840
+ import { join as join36 } from "node:path";
73320
74841
  function zedConfigDir() {
73321
74842
  const os3 = platform3();
73322
74843
  if (os3 === "darwin" || os3 === "win32")
73323
- return join35(appSupportDir(), "Zed");
73324
- return join35(appSupportDir(), "zed");
74844
+ return join36(appSupportDir(), "Zed");
74845
+ return join36(appSupportDir(), "zed");
73325
74846
  }
73326
74847
  var ZedProvider = () => createJSONProvider({
73327
74848
  providerName: "Zed",
@@ -73329,7 +74850,7 @@ var ZedProvider = () => createJSONProvider({
73329
74850
  local: true,
73330
74851
  priorityValue: 10,
73331
74852
  paths: [zedConfigDir()],
73332
- globalPath: join35(zedConfigDir(), "settings.json"),
74853
+ globalPath: join36(zedConfigDir(), "settings.json"),
73333
74854
  topKey: "context_servers",
73334
74855
  buildServer: (cfg) => ({
73335
74856
  source: "custom",
@@ -73337,7 +74858,7 @@ var ZedProvider = () => createJSONProvider({
73337
74858
  url: mcpURL(cfg.active_account.target.deployment_id),
73338
74859
  headers: mcpHeaders(cfg.active_account.target.api_key)
73339
74860
  }),
73340
- localConfigPath: (cwd) => join35(cwd, ".zed", "settings.json")
74861
+ localConfigPath: (cwd) => join36(cwd, ".zed", "settings.json")
73341
74862
  });
73342
74863
  var init_zed = __esm(() => {
73343
74864
  init_config_helpers();
@@ -73396,8 +74917,8 @@ var init_providers = __esm(() => {
73396
74917
  });
73397
74918
 
73398
74919
  // src/version/update-check.ts
73399
- import { existsSync as existsSync22, mkdirSync as mkdirSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "node:fs";
73400
- import { join as join36 } from "node:path";
74920
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "node:fs";
74921
+ import { join as join37 } from "node:path";
73401
74922
  function isValidVersion(value) {
73402
74923
  return typeof value === "string" && SEMVER_PATTERN.test(value);
73403
74924
  }
@@ -73419,14 +74940,14 @@ function isNewerVersion(latest, current) {
73419
74940
  return false;
73420
74941
  }
73421
74942
  function getCachePath3() {
73422
- return join36(getConfigDir(), CACHE_FILENAME4);
74943
+ return join37(getConfigDir(), CACHE_FILENAME4);
73423
74944
  }
73424
74945
  function readCache() {
73425
74946
  try {
73426
74947
  const path3 = getCachePath3();
73427
74948
  if (!existsSync22(path3))
73428
74949
  return null;
73429
- const data = JSON.parse(readFileSync20(path3, "utf-8"));
74950
+ const data = JSON.parse(readFileSync21(path3, "utf-8"));
73430
74951
  if (typeof data.lastCheck === "number" && isValidVersion(data.latestVersion)) {
73431
74952
  return data;
73432
74953
  }
@@ -73439,9 +74960,9 @@ function writeCache(cache) {
73439
74960
  try {
73440
74961
  const dir = getConfigDir();
73441
74962
  if (!existsSync22(dir)) {
73442
- mkdirSync11(dir, { recursive: true, mode: 448 });
74963
+ mkdirSync12(dir, { recursive: true, mode: 448 });
73443
74964
  }
73444
- writeFileSync12(getCachePath3(), JSON.stringify(cache), { mode: 384 });
74965
+ writeFileSync13(getCachePath3(), JSON.stringify(cache), { mode: 384 });
73445
74966
  } catch {}
73446
74967
  }
73447
74968
  async function fetchLatestVersion() {
@@ -73551,14 +75072,14 @@ var init_update_check = __esm(() => {
73551
75072
  });
73552
75073
 
73553
75074
  // src/rules/installer.ts
73554
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync21, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
75075
+ import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync22, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
73555
75076
  import { homedir as homedir9 } from "node:os";
73556
- import { dirname as dirname8, join as join37 } from "node:path";
75077
+ import { dirname as dirname9, join as join38 } from "node:path";
73557
75078
  function claudeConfigDir() {
73558
- return process.env.CLAUDE_CONFIG_DIR || join37(homedir9(), ".claude");
75079
+ return process.env.CLAUDE_CONFIG_DIR || join38(homedir9(), ".claude");
73559
75080
  }
73560
75081
  function codexHome3() {
73561
- return process.env.CODEX_HOME || join37(homedir9(), ".codex");
75082
+ return process.env.CODEX_HOME || join38(homedir9(), ".codex");
73562
75083
  }
73563
75084
  function isRuleAgent(agent) {
73564
75085
  return Object.hasOwn(RULE_TARGETS, agent);
@@ -73584,17 +75105,17 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
73584
75105
  return normalizeRule(FALLBACK_DOSU_RULE);
73585
75106
  }
73586
75107
  function ensureParent(path3) {
73587
- mkdirSync12(dirname8(path3), { recursive: true });
75108
+ mkdirSync13(dirname9(path3), { recursive: true });
73588
75109
  }
73589
75110
  function writeIfChanged(path3, content) {
73590
75111
  if (!existsSync23(path3)) {
73591
75112
  ensureParent(path3);
73592
- writeFileSync13(path3, content, "utf-8");
75113
+ writeFileSync14(path3, content, "utf-8");
73593
75114
  return "created";
73594
75115
  }
73595
- if (readFileSync21(path3, "utf-8") === content)
75116
+ if (readFileSync22(path3, "utf-8") === content)
73596
75117
  return "unchanged";
73597
- writeFileSync13(path3, content, "utf-8");
75118
+ writeFileSync14(path3, content, "utf-8");
73598
75119
  return "updated";
73599
75120
  }
73600
75121
  function findSection(content) {
@@ -73623,11 +75144,11 @@ function upsertSection(path3, content) {
73623
75144
  if (!existsSync23(path3)) {
73624
75145
  const section2 = buildSection(content);
73625
75146
  ensureParent(path3);
73626
- writeFileSync13(path3, `${section2}
75147
+ writeFileSync14(path3, `${section2}
73627
75148
  `, "utf-8");
73628
75149
  return "created";
73629
75150
  }
73630
- const existing = readFileSync21(path3, "utf-8");
75151
+ const existing = readFileSync22(path3, "utf-8");
73631
75152
  const lineEnding = lineEndingFor(existing);
73632
75153
  const section = buildSection(content, lineEnding);
73633
75154
  const location = findSection(existing);
@@ -73640,7 +75161,7 @@ function upsertSection(path3, content) {
73640
75161
  }
73641
75162
  if (next === existing)
73642
75163
  return "unchanged";
73643
- writeFileSync13(path3, next, "utf-8");
75164
+ writeFileSync14(path3, next, "utf-8");
73644
75165
  return "updated";
73645
75166
  }
73646
75167
  function installRuleForAgent(agent, content) {
@@ -73663,7 +75184,7 @@ function removeRuleForAgent(agent) {
73663
75184
  unlinkSync4(path3);
73664
75185
  return { agent, path: path3, action: "removed" };
73665
75186
  }
73666
- const existing = readFileSync21(path3, "utf-8");
75187
+ const existing = readFileSync22(path3, "utf-8");
73667
75188
  const lineEnding = lineEndingFor(existing);
73668
75189
  const location = findSection(existing);
73669
75190
  if (!location)
@@ -73672,7 +75193,7 @@ function removeRuleForAgent(agent) {
73672
75193
  if (!next) {
73673
75194
  unlinkSync4(path3);
73674
75195
  } else {
73675
- writeFileSync13(path3, `${next}${lineEnding}`, "utf-8");
75196
+ writeFileSync14(path3, `${next}${lineEnding}`, "utf-8");
73676
75197
  }
73677
75198
  return { agent, path: path3, action: "removed" };
73678
75199
  }
@@ -73700,36 +75221,36 @@ var init_installer = __esm(() => {
73700
75221
  RULE_TARGETS = {
73701
75222
  claude: {
73702
75223
  kind: "file",
73703
- path: () => join37(claudeConfigDir(), "rules", "dosu.md")
75224
+ path: () => join38(claudeConfigDir(), "rules", "dosu.md")
73704
75225
  },
73705
75226
  cursor: {
73706
75227
  kind: "file",
73707
- path: () => join37(homedir9(), ".cursor", "rules", "dosu.mdc"),
75228
+ path: () => join38(homedir9(), ".cursor", "rules", "dosu.mdc"),
73708
75229
  cursorFrontmatter: true
73709
75230
  },
73710
75231
  codex: {
73711
75232
  kind: "section",
73712
- path: () => join37(codexHome3(), "AGENTS.md")
75233
+ path: () => join38(codexHome3(), "AGENTS.md")
73713
75234
  },
73714
75235
  opencode: {
73715
75236
  kind: "section",
73716
- path: () => join37(homedir9(), ".config", "opencode", "AGENTS.md")
75237
+ path: () => join38(homedir9(), ".config", "opencode", "AGENTS.md")
73717
75238
  },
73718
75239
  gemini: {
73719
75240
  kind: "section",
73720
- path: () => join37(homedir9(), ".gemini", "GEMINI.md")
75241
+ path: () => join38(homedir9(), ".gemini", "GEMINI.md")
73721
75242
  },
73722
75243
  antigravity: {
73723
75244
  kind: "section",
73724
- path: () => join37(homedir9(), ".gemini", "GEMINI.md")
75245
+ path: () => join38(homedir9(), ".gemini", "GEMINI.md")
73725
75246
  }
73726
75247
  };
73727
75248
  });
73728
75249
 
73729
75250
  // src/setup/agents-md-step.ts
73730
75251
  import { execSync as execSync3 } from "node:child_process";
73731
- import { existsSync as existsSync24, readFileSync as readFileSync22, writeFileSync as writeFileSync14 } from "node:fs";
73732
- import { join as join38 } from "node:path";
75252
+ import { existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
75253
+ import { join as join39 } from "node:path";
73733
75254
  function inGitWorkTree(cwd = process.cwd()) {
73734
75255
  try {
73735
75256
  const stdout2 = execSync3("git rev-parse --is-inside-work-tree", {
@@ -73743,10 +75264,10 @@ function inGitWorkTree(cwd = process.cwd()) {
73743
75264
  }
73744
75265
  function dosuAgentsSectionState(cwd = process.cwd()) {
73745
75266
  try {
73746
- const path3 = join38(cwd, "AGENTS.md");
75267
+ const path3 = join39(cwd, "AGENTS.md");
73747
75268
  if (!existsSync24(path3))
73748
75269
  return "missing";
73749
- const content = readFileSync22(path3, "utf-8");
75270
+ const content = readFileSync23(path3, "utf-8");
73750
75271
  const startMatch = content.match(SECTION_START_RE);
73751
75272
  if (!startMatch || !content.includes(DOSU_SECTION_END))
73752
75273
  return "missing";
@@ -73781,14 +75302,14 @@ function findSection2(content) {
73781
75302
  return { start, end };
73782
75303
  }
73783
75304
  function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RULE) {
73784
- const path3 = join38(cwd, "AGENTS.md");
75305
+ const path3 = join39(cwd, "AGENTS.md");
73785
75306
  if (!existsSync24(path3)) {
73786
75307
  const section2 = buildDosuAgentsSection(content);
73787
- writeFileSync14(path3, `${section2}
75308
+ writeFileSync15(path3, `${section2}
73788
75309
  `);
73789
75310
  return { path: path3, action: "created" };
73790
75311
  }
73791
- const existing = readFileSync22(path3, "utf-8");
75312
+ const existing = readFileSync23(path3, "utf-8");
73792
75313
  const lineEnding = lineEndingFor2(existing);
73793
75314
  const section = renderDosuAgentsSection(content, lineEnding);
73794
75315
  const location = findSection2(existing);
@@ -73800,7 +75321,7 @@ function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RU
73800
75321
  }
73801
75322
  if (next === existing)
73802
75323
  return { path: path3, action: "unchanged" };
73803
- writeFileSync14(path3, next);
75324
+ writeFileSync15(path3, next);
73804
75325
  return { path: path3, action: "updated" };
73805
75326
  }
73806
75327
  async function stepUpdateAgentsMd(cwd = process.cwd(), content) {
@@ -73882,7 +75403,7 @@ var init_alt_screen = __esm(() => {
73882
75403
  });
73883
75404
 
73884
75405
  // src/tui/activity-view.ts
73885
- import { readFileSync as readFileSync23 } from "node:fs";
75406
+ import { readFileSync as readFileSync24 } from "node:fs";
73886
75407
  function reduceActivityViewKey(key) {
73887
75408
  if (key === "q" || key === ESC7 || key === CTRL_C4)
73888
75409
  return "back";
@@ -74127,7 +75648,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
74127
75648
  }
74128
75649
  function defaultReadLog2() {
74129
75650
  try {
74130
- return readFileSync23(logger.getLogPath(), "utf-8");
75651
+ return readFileSync24(logger.getLogPath(), "utf-8");
74131
75652
  } catch {
74132
75653
  return "";
74133
75654
  }
@@ -74212,7 +75733,7 @@ ${CLEAR_BELOW}`);
74212
75733
  const leaveAltScreen = enterAltScreen(output2);
74213
75734
  output2.write(HIDE_CURSOR3);
74214
75735
  draw();
74215
- return new Promise((resolve6) => {
75736
+ return new Promise((resolve7) => {
74216
75737
  const wasRaw = input2.isRaw ?? false;
74217
75738
  input2.setRawMode?.(true);
74218
75739
  input2.resume();
@@ -74229,7 +75750,7 @@ ${CLEAR_BELOW}`);
74229
75750
  input2.pause();
74230
75751
  leaveAltScreen();
74231
75752
  output2.write(SHOW_CURSOR3);
74232
- resolve6();
75753
+ resolve7();
74233
75754
  };
74234
75755
  const onData = (chunk) => {
74235
75756
  for (const key of parseKeys(chunk.toString())) {
@@ -74748,8 +76269,8 @@ h1 {
74748
76269
  async function startCallbackServer(opts = {}) {
74749
76270
  let resolveToken;
74750
76271
  let rejectToken;
74751
- const tokenPromise = new Promise((resolve6, reject) => {
74752
- resolveToken = resolve6;
76272
+ const tokenPromise = new Promise((resolve7, reject) => {
76273
+ resolveToken = resolve7;
74753
76274
  rejectToken = reject;
74754
76275
  });
74755
76276
  const http = __require("node:http");
@@ -74807,8 +76328,8 @@ async function startCallbackServer(opts = {}) {
74807
76328
  res.writeHead(200, { "Content-Type": "text/html" });
74808
76329
  res.end(buildSuccessHtml(email3 ?? undefined, opts.successVariant ?? "auth"));
74809
76330
  });
74810
- await new Promise((resolve6) => {
74811
- httpServer.listen(0, "localhost", () => resolve6());
76331
+ await new Promise((resolve7) => {
76332
+ httpServer.listen(0, "localhost", () => resolve7());
74812
76333
  });
74813
76334
  httpServer.unref();
74814
76335
  const addr = httpServer.address();
@@ -76112,7 +77633,7 @@ ${CLEAR_BELOW2}`);
76112
77633
  pagesPending = false;
76113
77634
  draw();
76114
77635
  });
76115
- return new Promise((resolve6) => {
77636
+ return new Promise((resolve7) => {
76116
77637
  const wasRaw = input2.isRaw ?? false;
76117
77638
  input2.setRawMode?.(true);
76118
77639
  input2.resume();
@@ -76127,7 +77648,7 @@ ${CLEAR_BELOW2}`);
76127
77648
  input2.pause();
76128
77649
  leaveAltScreen();
76129
77650
  output2.write(SHOW_CURSOR4);
76130
- resolve6();
77651
+ resolve7();
76131
77652
  };
76132
77653
  const onData = (chunk) => {
76133
77654
  for (const key of parseKeys(chunk.toString())) {
@@ -76528,7 +78049,7 @@ ${CLEAR_BELOW3}`);
76528
78049
  });
76529
78050
  draw();
76530
78051
  };
76531
- return new Promise((resolve6) => {
78052
+ return new Promise((resolve7) => {
76532
78053
  const wasRaw = input2.isRaw ?? false;
76533
78054
  input2.setRawMode?.(true);
76534
78055
  input2.resume();
@@ -76540,7 +78061,7 @@ ${CLEAR_BELOW3}`);
76540
78061
  input2.pause();
76541
78062
  leaveAltScreen();
76542
78063
  output2.write(SHOW_CURSOR5);
76543
- resolve6();
78064
+ resolve7();
76544
78065
  };
76545
78066
  const onData = (chunk) => {
76546
78067
  for (const key of parseKeys(chunk.toString())) {
@@ -76731,7 +78252,7 @@ async function runMainMenu() {
76731
78252
  const buildOptions = () => {
76732
78253
  if (!isAuthenticated(cfg)) {
76733
78254
  return [
76734
- { label: "Log in / Sign up", hint: "opens your browser", value: "auth" },
78255
+ { label: "Log in / Sign up", hint: "(opens your browser)", value: "auth" },
76735
78256
  { label: "Exit", value: "exit" }
76736
78257
  ];
76737
78258
  }
@@ -76747,6 +78268,7 @@ async function runMainMenu() {
76747
78268
  label: mining ? `Activity ⛏️ ${brand("mining sessions...")}` : "Activity",
76748
78269
  value: "sync"
76749
78270
  },
78271
+ { label: "Knowledge report", hint: "(opens in browser)", value: "report" },
76750
78272
  { label: "Analytics", value: "analytics" },
76751
78273
  { label: "Pages", value: "pages" },
76752
78274
  { label: "Settings", value: "settings" },
@@ -76771,6 +78293,10 @@ async function runMainMenu() {
76771
78293
  await runActivityView();
76772
78294
  home();
76773
78295
  break;
78296
+ case "report":
78297
+ await runKnowledgeReport();
78298
+ home();
78299
+ break;
76774
78300
  case "analytics":
76775
78301
  await runAnalyticsView();
76776
78302
  home();
@@ -76798,6 +78324,17 @@ async function runMainMenu() {
76798
78324
  }
76799
78325
  }
76800
78326
  }
78327
+ async function runKnowledgeReport() {
78328
+ const s = spinner2();
78329
+ s.start("Writing knowledge report...");
78330
+ try {
78331
+ const path3 = await emitKnowledgeReport({ open: true });
78332
+ s.stop(`Opened ${path3}`);
78333
+ } catch (err) {
78334
+ s.stop("Could not write the report");
78335
+ log2.error(err instanceof Error ? err.message : String(err));
78336
+ }
78337
+ }
76801
78338
  async function runSettings(cfg) {
76802
78339
  while (true) {
76803
78340
  const target = cfg.active_account?.target;
@@ -76925,6 +78462,7 @@ var init_tui = __esm(() => {
76925
78462
  init_config();
76926
78463
  init_agents();
76927
78464
  init_providers();
78465
+ init_generate();
76928
78466
  init_project_dir();
76929
78467
  init_scan();
76930
78468
  init_agents_md_step();
@@ -77204,7 +78742,7 @@ Could not open a browser automatically.`);
77204
78742
  throw new Error("Authentication timed out. Run 'dosu login' to try again.");
77205
78743
  }
77206
78744
  function sleep4(ms2) {
77207
- return new Promise((resolve6) => setTimeout(resolve6, ms2));
78745
+ return new Promise((resolve7) => setTimeout(resolve7, ms2));
77208
78746
  }
77209
78747
  var POLL_INTERVAL_MS = 5000;
77210
78748
  var init_device = __esm(() => {
@@ -77578,7 +79116,7 @@ import {
77578
79116
  constants as constants3,
77579
79117
  fstatSync,
77580
79118
  openSync as openSync5,
77581
- readFileSync as readFileSync24,
79119
+ readFileSync as readFileSync25,
77582
79120
  readSync as readSync4,
77583
79121
  unlinkSync as unlinkSync5
77584
79122
  } from "node:fs";
@@ -80708,6 +82246,7 @@ init_trpc();
80708
82246
  init_config();
80709
82247
  init_agents();
80710
82248
  init_formats();
82249
+ init_generate();
80711
82250
  init_backlog();
80712
82251
  init_detach();
80713
82252
  init_status();
@@ -80716,7 +82255,7 @@ init_watermark();
80716
82255
  var import_picocolors18 = __toESM(require_picocolors(), 1);
80717
82256
  import { existsSync as existsSync16 } from "node:fs";
80718
82257
  import { homedir as homedir5 } from "node:os";
80719
- import { delimiter as delimiter4, join as join18 } from "node:path";
82258
+ import { delimiter as delimiter4, join as join19 } from "node:path";
80720
82259
  function requireConfig7() {
80721
82260
  const cfg = requireLoginConfig();
80722
82261
  if (!cfg.active_account?.target?.org_id || !cfg.active_account?.target?.space_id) {
@@ -80823,7 +82362,7 @@ ${results.length - opts.limit} more results not shown.`));
80823
82362
  section("Mined", minedRows, "Mined at", "No mined sessions recorded yet.");
80824
82363
  }
80825
82364
  });
80826
- 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) => {
82365
+ 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) => {
80827
82366
  if (opts.status) {
80828
82367
  const status = getSyncStatus();
80829
82368
  if (opts.json) {
@@ -80838,7 +82377,9 @@ ${results.length - opts.limit} more results not shown.`));
80838
82377
  "knowledge",
80839
82378
  "sync",
80840
82379
  ...opts.quiet ? ["--quiet"] : [],
80841
- ...opts.bootstrap ? ["--bootstrap"] : []
82380
+ ...opts.bootstrap ? ["--bootstrap"] : [],
82381
+ ...opts.report ? ["--report"] : [],
82382
+ ...opts.out ? ["--out", opts.out] : []
80842
82383
  ]);
80843
82384
  return;
80844
82385
  }
@@ -80859,12 +82400,55 @@ ${results.length - opts.limit} more results not shown.`));
80859
82400
  if (opts.quiet)
80860
82401
  return;
80861
82402
  if (opts.json) {
80862
- printResult(outcome, opts);
80863
82403
  if (outcome.status === "error")
80864
82404
  process.exitCode = 1;
82405
+ if (opts.report) {
82406
+ try {
82407
+ const report = await emitKnowledgeReport({ out: opts.out, open: false });
82408
+ printResult({ ...outcome, report }, opts);
82409
+ } catch (err) {
82410
+ const report_error = err instanceof Error ? err.message : String(err);
82411
+ printResult({ ...outcome, report_error }, opts);
82412
+ }
82413
+ } else {
82414
+ printResult(outcome, opts);
82415
+ }
80865
82416
  return;
80866
82417
  }
80867
82418
  printSyncOutcome(outcome);
82419
+ if (opts.report) {
82420
+ try {
82421
+ const report = await emitKnowledgeReport({ out: opts.out, open: true });
82422
+ console.log(`Wrote ${report}`);
82423
+ } catch (err) {
82424
+ console.error(err instanceof Error ? err.message : String(err));
82425
+ process.exitCode = 1;
82426
+ }
82427
+ }
82428
+ });
82429
+ 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) => {
82430
+ const path3 = await emitKnowledgeReport({
82431
+ out: opts.out,
82432
+ open: opts.json ? false : opts.open
82433
+ });
82434
+ if (opts.json) {
82435
+ printResult({ report: path3 }, opts);
82436
+ return;
82437
+ }
82438
+ console.log(`Wrote ${path3}`);
82439
+ });
82440
+ 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) => {
82441
+ const { runBackfill: runBackfill2 } = await Promise.resolve().then(() => (init_backfill_run(), exports_backfill_run));
82442
+ const result = await runBackfill2();
82443
+ if (opts.json) {
82444
+ printResult(result, opts);
82445
+ return;
82446
+ }
82447
+ if (result.candidates === 0) {
82448
+ console.log("All your notes already have a transcript — nothing to backfill.");
82449
+ return;
82450
+ }
82451
+ 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.`);
80868
82452
  });
80869
82453
  cmd.addCommand(hooksCommand());
80870
82454
  return cmd;
@@ -81082,7 +82666,7 @@ function reportHookFailure(agent, err) {
81082
82666
  }
81083
82667
  function dosuOnPath() {
81084
82668
  const bin = process.platform === "win32" ? "dosu.cmd" : "dosu";
81085
- return (process.env.PATH ?? "").split(delimiter4).some((dir) => dir !== "" && existsSync16(join18(dir, bin)));
82669
+ return (process.env.PATH ?? "").split(delimiter4).some((dir) => dir !== "" && existsSync16(join19(dir, bin)));
81086
82670
  }
81087
82671
 
81088
82672
  // src/commands/libraries.ts
@@ -81422,7 +83006,7 @@ init_dist();
81422
83006
  init_commander();
81423
83007
  init_trpc();
81424
83008
  var import_picocolors22 = __toESM(require_picocolors(), 1);
81425
- import { readFileSync as readFileSync16 } from "node:fs";
83009
+ import { readFileSync as readFileSync17 } from "node:fs";
81426
83010
  function requireConfig10() {
81427
83011
  return requireLoginConfig();
81428
83012
  }
@@ -81598,7 +83182,7 @@ function reviewCommand() {
81598
83182
  let body = opts.body;
81599
83183
  if (opts.bodyFile) {
81600
83184
  try {
81601
- body = readFileSync16(opts.bodyFile, "utf-8");
83185
+ body = readFileSync17(opts.bodyFile, "utf-8");
81602
83186
  } catch (err) {
81603
83187
  console.error(import_picocolors22.default.red(`Failed to read --body-file: ${err.message}`));
81604
83188
  process.exit(1);
@@ -82387,8 +83971,8 @@ async function finishTelemetry(operation) {
82387
83971
  try {
82388
83972
  await Promise.race([
82389
83973
  Promise.resolve().then(operation),
82390
- new Promise((resolve6) => {
82391
- timeout = setTimeout(resolve6, TELEMETRY_FLUSH_TIMEOUT_MS);
83974
+ new Promise((resolve7) => {
83975
+ timeout = setTimeout(resolve7, TELEMETRY_FLUSH_TIMEOUT_MS);
82392
83976
  })
82393
83977
  ]);
82394
83978
  } catch {} finally {
@@ -82742,7 +84326,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
82742
84326
  }
82743
84327
  if (opts.follow) {
82744
84328
  try {
82745
- const lines = readFileSync24(logPath, "utf-8").split(`
84329
+ const lines = readFileSync25(logPath, "utf-8").split(`
82746
84330
  `);
82747
84331
  if (lines.at(-1) === "")
82748
84332
  lines.pop();
@@ -82758,7 +84342,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
82758
84342
  if (opts.tail !== undefined) {
82759
84343
  const n5 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
82760
84344
  try {
82761
- const content = readFileSync24(logPath, "utf-8");
84345
+ const content = readFileSync25(logPath, "utf-8");
82762
84346
  const lines = content.split(`
82763
84347
  `);
82764
84348
  console.log(lines.slice(-n5).join(`
@@ -82829,4 +84413,4 @@ execute().catch((err) => {
82829
84413
  process.exit(1);
82830
84414
  });
82831
84415
 
82832
- //# debugId=1952c680-18dc-c6aa-6475-6e2164756e21
84416
+ //# debugId=cf222ab5-3069-6407-6475-6e2164756e21