@dosu/cli 0.53.0-beta.5 → 0.53.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/dosu.js +1931 -488
  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.5", INSTALL_CHANNEL = "npm";
5021
+ var VERSION = "0.53.0-beta.7", 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 = "79ec0bbacf70";
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,179 +8635,749 @@ var init_watermark = __esm(() => {
8768
8635
  BACKOFF_MAX_MS = 24 * 60 * 60 * 1000;
8769
8636
  });
8770
8637
 
8771
- // src/sync/backlog.ts
8772
- function listSessionBacklog() {
8773
- try {
8774
- const state = loadSyncState();
8775
- let sessions = scanAgentSessions({});
8776
- if (state.project_filter?.length) {
8777
- const resolver = createProjectDirResolver();
8778
- sessions = filterSessionsByProject(sessions, state.project_filter, resolver.resolve);
8779
- resolver.flush();
8780
- }
8781
- const gate = gateSessions(sessions, state.watermark);
8782
- return { queued: gate.ready.reverse(), open: gate.open.reverse() };
8783
- } catch {
8784
- return { queued: [], open: [] };
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.");
8785
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
+ }));
8786
8658
  }
8787
- var init_backlog = __esm(() => {
8788
- init_project_dir();
8789
- init_scan();
8790
- init_watermark();
8791
- });
8659
+ var REPORT_NOTES_LIMIT = 500;
8792
8660
 
8793
- // 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";
8796
- function processAlive(pid) {
8797
- try {
8798
- process.kill(pid, 0);
8799
- return true;
8800
- } catch (err) {
8801
- return err.code === "EPERM";
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);
8802
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;
8803
8904
  }
8804
- function lockPath(configDir = getConfigDir()) {
8805
- return join11(configDir, LOCK_FILENAME);
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;
8806
8947
  }
8807
- function stopSyncRun(pid, configDir = getConfigDir(), kill = process.kill.bind(process)) {
8808
- if (!Number.isInteger(pid) || pid <= 0)
8809
- return false;
8810
- let killed = false;
8811
- try {
8812
- kill(-pid, "SIGTERM");
8813
- killed = true;
8814
- } catch {
8815
- try {
8816
- kill(pid, "SIGTERM");
8817
- killed = true;
8818
- } catch {}
8819
- }
8820
- try {
8821
- const path3 = lockPath(configDir);
8822
- if (existsSync12(path3) && readFileSync11(path3, "utf8") === String(pid)) {
8823
- rmSync2(path3, { force: true });
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;
8824
8968
  }
8825
- } catch {}
8826
- return killed;
8969
+ }
8970
+ return "";
8827
8971
  }
8828
- function fileLock(configDir = getConfigDir(), now = () => new Date) {
8829
- const path3 = lockPath(configDir);
8830
- let held = false;
8831
- const tryCreate = () => {
8832
- try {
8833
- writeFileSync8(path3, String(process.pid), { flag: "wx" });
8834
- return true;
8835
- } catch {
8836
- return false;
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;
8837
9068
  }
8838
- };
8839
- return {
8840
- acquire() {
8841
- if (tryCreate()) {
8842
- held = true;
8843
- return true;
8844
- }
8845
- try {
8846
- const age = now().getTime() - statSync5(path3).mtimeMs;
8847
- const holder = Number.parseInt(readFileSync11(path3, "utf8"), 10);
8848
- const holderDead = Number.isInteger(holder) && holder > 0 && !processAlive(holder);
8849
- if (age > STALE_LOCK_MS || holderDead) {
8850
- rmSync2(path3, { force: true });
8851
- held = tryCreate();
8852
- return held;
8853
- }
8854
- } catch {}
8855
- return false;
8856
- },
8857
- release() {
8858
- if (!held)
8859
- return;
8860
- held = false;
8861
- try {
8862
- if (existsSync12(path3) && readFileSync11(path3, "utf8") === String(process.pid)) {
8863
- rmSync2(path3, { force: true });
8864
- }
8865
- } catch {}
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
+ });
8866
9093
  }
8867
- };
8868
- }
8869
- var LOCK_FILENAME = "knowledge-sync.lock", STALE_LOCK_MS;
8870
- var init_lock = __esm(() => {
8871
- init_config();
8872
- STALE_LOCK_MS = 15 * 60 * 1000;
8873
- });
8874
-
8875
- // src/sync/status.ts
8876
- import { readFileSync as readFileSync12, statSync as statSync6 } from "node:fs";
8877
- function formatTokenCount(tokens) {
8878
- if (tokens < 1000)
8879
- return String(tokens);
8880
- if (tokens < 1e6) {
8881
- const k = tokens / 1000;
8882
- return `~${k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, "")}k`;
8883
9094
  }
8884
- const m3 = tokens / 1e6;
8885
- return `~${m3 >= 100 ? Math.round(m3) : m3.toFixed(1).replace(/\.0$/, "")}M`;
9095
+ return steps;
8886
9096
  }
8887
- function defaultIsProcessAlive2(pid) {
8888
- if (!Number.isInteger(pid) || pid <= 0)
8889
- return false;
8890
- try {
8891
- process.kill(pid, 0);
8892
- return true;
8893
- } catch (err) {
8894
- return err.code === "EPERM";
8895
- }
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))];
8896
9102
  }
8897
- function defaultReadLog() {
8898
- try {
8899
- return readFileSync12(logger.getLogPath(), "utf-8");
8900
- } catch {
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)
8901
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>`);
8902
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
+ `;
8903
9174
  }
8904
- function readLockFile(configDir) {
8905
- const path3 = lockPath(configDir);
8906
- try {
8907
- const pid = Number.parseInt(readFileSync12(path3, "utf-8"), 10);
8908
- return { pid: Number.isNaN(pid) ? -1 : pid, mtime: statSync6(path3).mtime };
8909
- } catch {
8910
- return null;
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
+ ];
8911
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
+ ];
8912
9228
  }
8913
- function recentSyncActivity(log3) {
8914
- return log3.split(`
8915
- `).filter((line) => line.includes("[sync]") || line.includes("[miner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
8916
- }
8917
- function getSyncStatus(deps = {}) {
8918
- const configDir = deps.configDir ?? getConfigDir();
8919
- const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive2;
8920
- const readLog = deps.readLog ?? defaultReadLog;
8921
- const lock = readLockFile(configDir);
8922
- const running = lock !== null && isProcessAlive(lock.pid);
8923
- const state = loadSyncState(configDir);
8924
- const retryAt = backoffUntil(state);
8925
- return {
8926
- running,
8927
- ...lock ? { pid: lock.pid, startedAt: lock.mtime.toISOString() } : {},
8928
- ...lock && !running ? { staleLock: true } : {},
8929
- state,
8930
- ...retryAt ? { backoffUntil: retryAt.toISOString() } : {},
8931
- recentActivity: recentSyncActivity(readLog())
8932
- };
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
+ `;
8933
9369
  }
8934
- var ACTIVITY_LINES = 10;
8935
- var init_status = __esm(() => {
8936
- init_config();
8937
- init_logger();
8938
- init_lock();
8939
- init_watermark();
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;
8940
9377
  });
8941
9378
 
8942
9379
  // src/sessions/read.ts
8943
- import { readFileSync as readFileSync13 } from "node:fs";
9380
+ import { readFileSync as readFileSync10 } from "node:fs";
8944
9381
  function asRecord(value) {
8945
9382
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
8946
9383
  }
@@ -9056,11 +9493,11 @@ function readSessionTurns(session) {
9056
9493
  try {
9057
9494
  switch (session.harness) {
9058
9495
  case "claude":
9059
- return readClaude(readFileSync13(session.path, "utf8"));
9496
+ return readClaude(readFileSync10(session.path, "utf8"));
9060
9497
  case "cursor":
9061
- return readCursor(readFileSync13(session.path, "utf8"));
9498
+ return readCursor(readFileSync10(session.path, "utf8"));
9062
9499
  case "codex":
9063
- return readCodex(readFileSync13(session.path, "utf8"));
9500
+ return readCodex(readFileSync10(session.path, "utf8"));
9064
9501
  case "opencode":
9065
9502
  return readOpencode(session.path, session.id);
9066
9503
  }
@@ -9068,11 +9505,58 @@ function readSessionTurns(session) {
9068
9505
  return [];
9069
9506
  }
9070
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
+ }
9071
9555
  function estimateSessionTokens(session) {
9072
9556
  let chars = 0;
9073
9557
  for (const turn of readSessionTurns(session))
9074
9558
  chars += turn.text.length;
9075
- return Math.round(chars / CHARS_PER_TOKEN);
9559
+ return Math.round(chars / CHARS_PER_TOKEN2);
9076
9560
  }
9077
9561
  function isWorthMining(session) {
9078
9562
  const turns = readSessionTurns(session);
@@ -9086,11 +9570,902 @@ function isWorthMining(session) {
9086
9570
  }
9087
9571
  return false;
9088
9572
  }
9089
- var PLAIN_TEXT, CODEX_TEXT, CHARS_PER_TOKEN = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
9573
+ var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS, CHARS_PER_TOKEN2 = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
9090
9574
  var init_read = __esm(() => {
9091
9575
  init_scan();
9092
9576
  PLAIN_TEXT = new Set(["text"]);
9093
9577
  CODEX_TEXT = new Set(["input_text", "output_text"]);
9578
+ REDISCOVERY_TOOLS = new Set([
9579
+ "Read",
9580
+ "Grep",
9581
+ "Glob",
9582
+ "Shell",
9583
+ "WebSearch",
9584
+ "WebFetch",
9585
+ "Task",
9586
+ "SemanticSearch",
9587
+ "Bash",
9588
+ "Edit",
9589
+ "MultiEdit",
9590
+ "NotebookEdit",
9591
+ "Agent",
9592
+ "exec_command",
9593
+ "write_stdin",
9594
+ "web_search",
9595
+ "web_search_call",
9596
+ "open_page",
9597
+ "apply_patch",
9598
+ "update_plan",
9599
+ "list_dir",
9600
+ "grep_files",
9601
+ "read_file"
9602
+ ]);
9603
+ });
9604
+
9605
+ // src/report/queries.ts
9606
+ function extractUserQueries(text) {
9607
+ const matches = [...text.matchAll(USER_QUERY_RE)].map((m3) => m3[1].trim()).filter(Boolean);
9608
+ if (matches.length > 0)
9609
+ return matches;
9610
+ const cleaned = text.replace(TIMESTAMP_RE, "").trim();
9611
+ if (cleaned.startsWith("# AGENTS.md") || cleaned.startsWith("<INSTRUCTIONS>") || cleaned.startsWith("<permissions instructions>")) {
9612
+ return [];
9613
+ }
9614
+ return cleaned ? [cleaned] : [];
9615
+ }
9616
+ function sessionTitleFromUserText(text, max = TITLE_CHARS) {
9617
+ const first = extractUserQueries(text)[0] ?? "";
9618
+ const collapsed = first.replace(/\s+/g, " ").trim();
9619
+ if (collapsed.length > max)
9620
+ return `${collapsed.slice(0, max - 1).trimEnd()}…`;
9621
+ return collapsed;
9622
+ }
9623
+ var TITLE_CHARS = 80, USER_QUERY_RE, TIMESTAMP_RE;
9624
+ var init_queries = __esm(() => {
9625
+ USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/gi;
9626
+ TIMESTAMP_RE = /<timestamp>[\s\S]*?<\/timestamp>/gi;
9627
+ });
9628
+
9629
+ // src/report/digest.ts
9630
+ import { readFileSync as readFileSync11 } from "node:fs";
9631
+ function asRecord2(value) {
9632
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
9633
+ }
9634
+ function estimateTokens(text) {
9635
+ return Math.round(text.length / CHARS_PER_TOKEN3);
9636
+ }
9637
+ function digestEstTokens(texts, tools, resultChars) {
9638
+ const countable = tools.filter((t2) => t2.name !== "tool_result");
9639
+ let payload = texts.join(`
9640
+ `);
9641
+ if (countable.length > 0)
9642
+ payload += JSON.stringify(countable);
9643
+ let tokens = estimateTokens(payload);
9644
+ if (resultChars > 0)
9645
+ tokens += Math.max(1, Math.round(resultChars / CHARS_PER_TOKEN3));
9646
+ return tokens;
9647
+ }
9648
+ function copyToolInputFields(entry, inp) {
9649
+ const row = asRecord2(inp);
9650
+ if (!row)
9651
+ return;
9652
+ if (typeof row.path === "string")
9653
+ entry.path = row.path;
9654
+ if (typeof row.file_path === "string") {
9655
+ entry.file_path = row.file_path;
9656
+ entry.path ??= row.file_path;
9657
+ }
9658
+ if (typeof row.pattern === "string")
9659
+ entry.pattern = row.pattern;
9660
+ if (row.command != null)
9661
+ entry.command_preview = String(row.command).slice(0, 160);
9662
+ if (row.prompt != null) {
9663
+ entry.prompt = String(row.prompt).slice(0, 160);
9664
+ entry.command_preview ??= entry.prompt;
9665
+ }
9666
+ if (typeof row.query === "string")
9667
+ entry.query = row.query;
9668
+ if (typeof row.description === "string" && !entry.command_preview) {
9669
+ entry.command_preview = row.description.slice(0, 160);
9670
+ }
9671
+ }
9672
+ function copyMcpFields(entry, name, inp) {
9673
+ const row = asRecord2(inp);
9674
+ if (!row)
9675
+ return;
9676
+ const isMcp = MCP_DIGEST_TOOLS.has(name) || name.startsWith("mcp__") || Boolean(row.toolName || row.server);
9677
+ if (!isMcp)
9678
+ return;
9679
+ const toolName = row.toolName ?? row.tool_name;
9680
+ if (typeof toolName === "string")
9681
+ entry.toolName = toolName;
9682
+ if (typeof row.server === "string")
9683
+ entry.server = row.server;
9684
+ if (name === "GetMcpTools" && typeof row.pattern === "string")
9685
+ entry.pattern = row.pattern;
9686
+ const args = asRecord2(row.arguments);
9687
+ if (args)
9688
+ entry.arguments = args;
9689
+ else if (name.startsWith("mcp__"))
9690
+ entry.arguments = row;
9691
+ }
9692
+ function blocksToDigest(content, maxTextChars) {
9693
+ const texts = [];
9694
+ const tools = [];
9695
+ let resultChars = 0;
9696
+ const blocks = Array.isArray(content) ? content : typeof content === "string" && content ? [{ type: "text", text: content }] : [];
9697
+ for (const block2 of blocks) {
9698
+ const item = asRecord2(block2);
9699
+ if (!item)
9700
+ continue;
9701
+ const btype = item.type;
9702
+ if ((btype === "text" || btype === "input_text" || btype === "output_text") && typeof item.text === "string" && item.text) {
9703
+ let t2 = item.text;
9704
+ if (t2.length > maxTextChars)
9705
+ t2 = `${t2.slice(0, maxTextChars)}
9706
+ …[truncated]`;
9707
+ texts.push(t2);
9708
+ } else if (btype === "tool_use" && typeof item.name === "string") {
9709
+ const entry = { name: item.name };
9710
+ const inp = item.input ?? {};
9711
+ copyToolInputFields(entry, inp);
9712
+ copyMcpFields(entry, item.name, inp);
9713
+ tools.push(entry);
9714
+ } else if (btype === "tool_result") {
9715
+ const c2 = item.content;
9716
+ if (typeof c2 === "string")
9717
+ resultChars += c2.length;
9718
+ else if (c2 != null)
9719
+ resultChars += JSON.stringify(c2).length;
9720
+ tools.push({ name: "tool_result" });
9721
+ }
9722
+ }
9723
+ return { texts, tools, resultChars };
9724
+ }
9725
+ function pushTurn2(turns, line, role, texts, tools, resultChars) {
9726
+ if (role === "user" && texts.length > 0) {
9727
+ const qs = extractUserQueries(texts.join(`
9728
+ `));
9729
+ if (qs.length > 0)
9730
+ texts.splice(0, texts.length, ...qs);
9731
+ }
9732
+ if (role === "user" && texts.length === 0)
9733
+ return;
9734
+ if (texts.length === 0 && tools.length === 0)
9735
+ return;
9736
+ turns.push({
9737
+ line,
9738
+ role,
9739
+ text: texts,
9740
+ tools,
9741
+ est_tokens: digestEstTokens(texts, tools, resultChars)
9742
+ });
9743
+ }
9744
+ function digestCursor(raw) {
9745
+ const turns = [];
9746
+ const lines = raw.split(`
9747
+ `);
9748
+ for (let i2 = 0;i2 < lines.length; i2++) {
9749
+ const line = lines[i2].trim();
9750
+ if (!line)
9751
+ continue;
9752
+ let obj;
9753
+ try {
9754
+ const parsed = asRecord2(JSON.parse(line));
9755
+ if (!parsed)
9756
+ continue;
9757
+ obj = parsed;
9758
+ } catch {
9759
+ continue;
9760
+ }
9761
+ const role = obj.role;
9762
+ if (role !== "user" && role !== "assistant")
9763
+ continue;
9764
+ const message = asRecord2(obj.message);
9765
+ const { texts, tools, resultChars } = blocksToDigest(message?.content, role === "assistant" ? MAX_ASSISTANT_CHARS : 2000);
9766
+ pushTurn2(turns, i2 + 1, role, texts, tools, resultChars);
9767
+ }
9768
+ return turns;
9769
+ }
9770
+ function digestClaude(raw) {
9771
+ const turns = [];
9772
+ const lines = raw.split(`
9773
+ `);
9774
+ for (let i2 = 0;i2 < lines.length; i2++) {
9775
+ const line = lines[i2].trim();
9776
+ if (!line)
9777
+ continue;
9778
+ let obj;
9779
+ try {
9780
+ const parsed = asRecord2(JSON.parse(line));
9781
+ if (!parsed)
9782
+ continue;
9783
+ obj = parsed;
9784
+ } catch {
9785
+ continue;
9786
+ }
9787
+ const etype = obj.type;
9788
+ if (etype !== "user" && etype !== "assistant")
9789
+ continue;
9790
+ if (obj.isSidechain === true)
9791
+ continue;
9792
+ const message = asRecord2(obj.message);
9793
+ const { texts, tools, resultChars } = blocksToDigest(message?.content, etype === "assistant" ? MAX_ASSISTANT_CHARS : 2000);
9794
+ pushTurn2(turns, i2 + 1, etype, texts, tools, resultChars);
9795
+ }
9796
+ return turns;
9797
+ }
9798
+ function digestCodex(raw) {
9799
+ const turns = [];
9800
+ const lines = raw.split(`
9801
+ `);
9802
+ for (let i2 = 0;i2 < lines.length; i2++) {
9803
+ const line = lines[i2].trim();
9804
+ if (!line)
9805
+ continue;
9806
+ let obj;
9807
+ try {
9808
+ const parsed = asRecord2(JSON.parse(line));
9809
+ if (!parsed)
9810
+ continue;
9811
+ obj = parsed;
9812
+ } catch {
9813
+ continue;
9814
+ }
9815
+ const payload = asRecord2(obj.payload) ?? {};
9816
+ const etype = obj.type;
9817
+ if (etype === "event_msg" && payload.type === "user_message") {
9818
+ const msg = String(payload.message ?? "").slice(0, 2000);
9819
+ if (msg) {
9820
+ turns.push({
9821
+ line: i2 + 1,
9822
+ role: "user",
9823
+ text: [msg],
9824
+ tools: [],
9825
+ est_tokens: estimateTokens(msg)
9826
+ });
9827
+ }
9828
+ } else if (etype === "event_msg" && payload.type === "agent_message") {
9829
+ let msg = String(payload.message ?? "");
9830
+ if (msg.length > MAX_ASSISTANT_CHARS)
9831
+ msg = `${msg.slice(0, MAX_ASSISTANT_CHARS)}
9832
+ …[truncated]`;
9833
+ if (msg) {
9834
+ turns.push({
9835
+ line: i2 + 1,
9836
+ role: "assistant",
9837
+ text: [msg],
9838
+ tools: [],
9839
+ est_tokens: estimateTokens(msg)
9840
+ });
9841
+ }
9842
+ } else if (etype === "response_item" && payload.type === "function_call") {
9843
+ const name = typeof payload.name === "string" ? payload.name : "function_call";
9844
+ const args = payload.arguments;
9845
+ const preview = typeof args === "string" ? args.slice(0, 160) : JSON.stringify(args ?? "").slice(0, 160);
9846
+ turns.push({
9847
+ line: i2 + 1,
9848
+ role: "assistant",
9849
+ text: [],
9850
+ tools: [{ name, command_preview: preview }],
9851
+ est_tokens: estimateTokens(preview)
9852
+ });
9853
+ }
9854
+ }
9855
+ return turns;
9856
+ }
9857
+ function sessionToDigest(session) {
9858
+ try {
9859
+ switch (session.harness) {
9860
+ case "claude":
9861
+ return { turns: digestClaude(readFileSync11(session.path, "utf8")) };
9862
+ case "cursor":
9863
+ return { turns: digestCursor(readFileSync11(session.path, "utf8")) };
9864
+ case "codex":
9865
+ return { turns: digestCodex(readFileSync11(session.path, "utf8")) };
9866
+ case "opencode": {
9867
+ const turns = readSessionTurns(session).map((turn, index) => ({
9868
+ role: turn.role,
9869
+ line: index + 1,
9870
+ est_tokens: Math.round(turn.text.length / CHARS_PER_TOKEN3),
9871
+ text: [turn.text],
9872
+ tools: []
9873
+ }));
9874
+ return { turns };
9875
+ }
9876
+ }
9877
+ } catch {
9878
+ return { turns: [] };
9879
+ }
9880
+ }
9881
+ function digestsForSessions(sessions) {
9882
+ const out = {};
9883
+ for (const session of sessions) {
9884
+ out[session.id] = sessionToDigest(session);
9885
+ }
9886
+ return out;
9887
+ }
9888
+ function digestTurnText(turn) {
9889
+ const texts = turn.text ?? [];
9890
+ if (typeof texts === "string")
9891
+ return texts;
9892
+ return texts.filter(Boolean).join(`
9893
+ `);
9894
+ }
9895
+ var CHARS_PER_TOKEN3 = 4, MAX_ASSISTANT_CHARS = 4000, MCP_DIGEST_TOOLS;
9896
+ var init_digest = __esm(() => {
9897
+ init_read();
9898
+ init_queries();
9899
+ MCP_DIGEST_TOOLS = new Set(["CallMcpTool", "GetMcpTools"]);
9900
+ });
9901
+
9902
+ // src/report/notes.ts
9903
+ function sessionIdFromReadInput(input) {
9904
+ if (typeof input !== "object" || input === null || Array.isArray(input))
9905
+ return;
9906
+ const id = input.id;
9907
+ return typeof id === "string" && id.trim() ? id : undefined;
9908
+ }
9909
+ function significantWords(text) {
9910
+ const out = new Set;
9911
+ for (const raw of text.toLowerCase().match(/[a-z][a-z0-9._-]{2,}/g) ?? []) {
9912
+ const word = raw.replace(/^\.+|\.+$/g, "");
9913
+ if (word.length >= 3 && !STOP_WORDS.has(word))
9914
+ out.add(word);
9915
+ }
9916
+ return out;
9917
+ }
9918
+ function overlapCount(a3, b2) {
9919
+ let n2 = 0;
9920
+ for (const word of a3)
9921
+ if (b2.has(word))
9922
+ n2 += 1;
9923
+ return n2;
9924
+ }
9925
+ function noteWords(note) {
9926
+ return { title: significantWords(note.title), content: significantWords(note.content) };
9927
+ }
9928
+ function sessionCycles(turns) {
9929
+ const starts = [];
9930
+ for (let i2 = 0;i2 < turns.length; i2++) {
9931
+ if (turns[i2].role === "user" && digestTurnText(turns[i2]).trim())
9932
+ starts.push(i2);
9933
+ }
9934
+ return starts.map((start, i2) => {
9935
+ const end = i2 + 1 < starts.length ? starts[i2 + 1] : turns.length;
9936
+ const text = turns.slice(start, end).map((t2) => {
9937
+ const paths = (t2.tools ?? []).map((tool) => tool.path || tool.pattern || tool.command_preview || "").filter(Boolean).join(" ");
9938
+ return `${digestTurnText(t2)} ${paths}`;
9939
+ }).join(`
9940
+ `);
9941
+ return { start, end, text };
9942
+ });
9943
+ }
9944
+ function cycleMatchScore(words, cycleText) {
9945
+ const cycleWords = significantWords(cycleText);
9946
+ return overlapCount(words.title, cycleWords) * 2 + overlapCount(words.content, cycleWords);
9947
+ }
9948
+ function cleanedUserQuery(text) {
9949
+ const queries = extractUserQueries(text);
9950
+ return (queries[0] ?? text).replace(/\s+/g, " ").trim();
9951
+ }
9952
+ function attributeRediscovery(notes, sessions) {
9953
+ const byId = new Map(sessions.map((s) => [s.id, s]));
9954
+ const candidates = notes.map((note) => ({
9955
+ title: note.title,
9956
+ content: note.content,
9957
+ transcript_id: note.transcript_id,
9958
+ repo: note.repo,
9959
+ branch: note.branch,
9960
+ status: "written"
9961
+ }));
9962
+ const indexesBySession = new Map;
9963
+ for (let i2 = 0;i2 < notes.length; i2++) {
9964
+ const id = notes[i2].transcript_id;
9965
+ if (!id || !byId.has(id))
9966
+ continue;
9967
+ const list = indexesBySession.get(id) ?? [];
9968
+ list.push(i2);
9969
+ indexesBySession.set(id, list);
9970
+ }
9971
+ for (const [sessionId, indexes] of indexesBySession) {
9972
+ const session = byId.get(sessionId);
9973
+ if (!session)
9974
+ continue;
9975
+ const turns = sessionToDigest(session).turns;
9976
+ const firstUser = turns.find((t2) => t2.role === "user");
9977
+ const firstText = firstUser ? digestTurnText(firstUser) : "";
9978
+ const sessionTitle = firstText ? sessionTitleFromUserText(firstText) : undefined;
9979
+ for (const i2 of indexes) {
9980
+ if (sessionTitle)
9981
+ candidates[i2].session_title = sessionTitle;
9982
+ if (firstText)
9983
+ candidates[i2].user_query = cleanedUserQuery(firstText).slice(0, 200);
9984
+ }
9985
+ if (turns.length === 0)
9986
+ continue;
9987
+ const cycles = sessionCycles(turns);
9988
+ const taken = new Set;
9989
+ const ranked = indexes.map((i2) => {
9990
+ const words = noteWords(notes[i2]);
9991
+ let bestScore = 0;
9992
+ for (const cycle of cycles) {
9993
+ const score = cycleMatchScore(words, cycle.text);
9994
+ if (score > bestScore)
9995
+ bestScore = score;
9996
+ }
9997
+ return { i: i2, words, bestScore };
9998
+ }).sort((a3, b2) => b2.bestScore - a3.bestScore);
9999
+ for (const item of ranked) {
10000
+ if (item.bestScore <= 0)
10001
+ continue;
10002
+ let pick = -1;
10003
+ let pickScore = 0;
10004
+ for (let ci = 0;ci < cycles.length; ci++) {
10005
+ if (taken.has(ci))
10006
+ continue;
10007
+ const score = cycleMatchScore(item.words, cycles[ci].text);
10008
+ if (score > pickScore) {
10009
+ pickScore = score;
10010
+ pick = ci;
10011
+ }
10012
+ }
10013
+ if (pick < 0 || pickScore <= 0)
10014
+ continue;
10015
+ taken.add(pick);
10016
+ const cycle = cycles[pick];
10017
+ const slice = turns.slice(cycle.start, cycle.end);
10018
+ const tokens = slice.reduce((sum, t2) => sum + (Number(t2.est_tokens) || 0), 0);
10019
+ candidates[item.i].approx_rediscovery_tokens = tokens;
10020
+ const startLine = slice[0]?.line ?? cycle.start + 1;
10021
+ const endLine = slice.at(-1)?.line ?? cycle.end;
10022
+ candidates[item.i].investigation_lines = `${startLine}-${endLine}`;
10023
+ const cycleUser = slice.find((t2) => t2.role === "user");
10024
+ if (cycleUser) {
10025
+ const q = digestTurnText(cycleUser);
10026
+ candidates[item.i].user_query = cleanedUserQuery(q).slice(0, 200);
10027
+ candidates[item.i].session_title = sessionTitleFromUserText(q);
10028
+ }
10029
+ }
10030
+ }
10031
+ return candidates;
10032
+ }
10033
+ function sessionsToInventory(sessions) {
10034
+ const transcripts = sessions.map((session) => {
10035
+ const turns = readSessionTurns(session);
10036
+ const queries = turns.filter((t2) => t2.role === "user").flatMap((t2) => extractUserQueries(t2.text));
10037
+ const title = queries[0] ? sessionTitleFromUserText(queries[0]) : undefined;
10038
+ return {
10039
+ source: session.harness,
10040
+ transcript_id: session.id,
10041
+ ...title ? { title } : {},
10042
+ learning_tokens: estimateSessionTokens(session),
10043
+ rediscovery_tool_calls: countRediscoveryToolCalls(session),
10044
+ user_queries: queries.slice(0, 3)
10045
+ };
10046
+ });
10047
+ const learning_tokens = transcripts.reduce((sum, t2) => sum + t2.learning_tokens, 0);
10048
+ return { transcripts, totals: { learning_tokens } };
10049
+ }
10050
+ var STOP_WORDS;
10051
+ var init_notes = __esm(() => {
10052
+ init_read();
10053
+ init_digest();
10054
+ init_queries();
10055
+ init_digest();
10056
+ STOP_WORDS = new Set([
10057
+ "about",
10058
+ "after",
10059
+ "also",
10060
+ "and",
10061
+ "are",
10062
+ "but",
10063
+ "can",
10064
+ "does",
10065
+ "for",
10066
+ "from",
10067
+ "have",
10068
+ "into",
10069
+ "just",
10070
+ "must",
10071
+ "not",
10072
+ "that",
10073
+ "the",
10074
+ "this",
10075
+ "they",
10076
+ "with",
10077
+ "what",
10078
+ "when",
10079
+ "which",
10080
+ "will",
10081
+ "your"
10082
+ ]);
10083
+ });
10084
+
10085
+ // src/report/write.ts
10086
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
10087
+ import { tmpdir } from "node:os";
10088
+ import { dirname as dirname4, join as join10, resolve } from "node:path";
10089
+ import { pathToFileURL } from "node:url";
10090
+ function defaultReportPath() {
10091
+ return join10(tmpdir(), "dosu-knowledge-report.html");
10092
+ }
10093
+ async function writeAndOpenReport(options) {
10094
+ const out = resolve(options.out ?? defaultReportPath());
10095
+ mkdirSync7(dirname4(out), { recursive: true });
10096
+ writeFileSync7(out, options.html, "utf8");
10097
+ if (options.open !== false) {
10098
+ const url = pathToFileURL(out).href;
10099
+ if (options.openUrl) {
10100
+ await options.openUrl(url);
10101
+ } else {
10102
+ const open2 = await Promise.resolve().then(() => (init_open(), exports_open));
10103
+ await open2.default(url);
10104
+ }
10105
+ }
10106
+ return out;
10107
+ }
10108
+ var init_write = () => {};
10109
+
10110
+ // src/report/generate.ts
10111
+ function sessionsForNotes(notes, sessions) {
10112
+ const ids = new Set(notes.map((n2) => n2.transcript_id).filter((id) => Boolean(id)));
10113
+ return sessions.filter((s) => ids.has(s.id));
10114
+ }
10115
+ function noteTime(note) {
10116
+ const parsed = Date.parse(note.at ?? "");
10117
+ return Number.isNaN(parsed) ? 0 : parsed;
10118
+ }
10119
+ async function emitKnowledgeReport(options = {}) {
10120
+ const cfg = loadConfig();
10121
+ const notes = [...options.notes ?? await (options.fetchNotes ?? fetchReportNotes)(cfg)].sort((a3, b2) => noteTime(a3) - noteTime(b2));
10122
+ const scanned = options.sessions ?? scanAgentSessions();
10123
+ const sessions = sessionsForNotes(notes, scanned);
10124
+ const candidates = attributeRediscovery(notes, sessions);
10125
+ const inventory = sessionsToInventory(sessions);
10126
+ const state = loadSyncState();
10127
+ if (inventory.totals && state.total_learning_tokens && !options.sessions) {
10128
+ inventory.totals.learning_tokens = state.total_learning_tokens;
10129
+ }
10130
+ const html = buildReportHtml({
10131
+ inventory,
10132
+ candidates,
10133
+ orgName: options.orgName ?? cfg.active_account?.target?.org_name ?? "Your team",
10134
+ repo: options.repo ?? notes.find((n2) => n2.repo)?.repo,
10135
+ branch: options.branch ?? notes.find((n2) => n2.branch)?.branch,
10136
+ generatedAt: options.generatedAt,
10137
+ digests: digestsForSessions(sessions)
10138
+ });
10139
+ return writeAndOpenReport({
10140
+ html,
10141
+ out: options.out ?? defaultReportPath(),
10142
+ open: options.open,
10143
+ openUrl: options.openUrl
10144
+ });
10145
+ }
10146
+ var init_generate = __esm(() => {
10147
+ init_config();
10148
+ init_scan();
10149
+ init_watermark();
10150
+ init_html();
10151
+ init_notes();
10152
+ init_write();
10153
+ });
10154
+
10155
+ // src/sessions/project-dir.ts
10156
+ import {
10157
+ closeSync as closeSync2,
10158
+ existsSync as existsSync11,
10159
+ mkdirSync as mkdirSync8,
10160
+ openSync as openSync2,
10161
+ readFileSync as readFileSync12,
10162
+ readSync,
10163
+ renameSync as renameSync3,
10164
+ statSync as statSync4,
10165
+ writeFileSync as writeFileSync8
10166
+ } from "node:fs";
10167
+ import { join as join11 } from "node:path";
10168
+ function cachePath(configDir) {
10169
+ return join11(configDir, CACHE_FILENAME2);
10170
+ }
10171
+ function loadCacheFile(configDir) {
10172
+ try {
10173
+ const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
10174
+ if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
10175
+ return {};
10176
+ return raw.entries;
10177
+ } catch {
10178
+ return {};
10179
+ }
10180
+ }
10181
+ function saveCacheFile(configDir, entries) {
10182
+ try {
10183
+ if (!existsSync11(configDir))
10184
+ mkdirSync8(configDir, { recursive: true, mode: 448 });
10185
+ const path3 = cachePath(configDir);
10186
+ const tmp = `${path3}.${process.pid}.tmp`;
10187
+ const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
10188
+ writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
10189
+ renameSync3(tmp, path3);
10190
+ } catch {}
10191
+ }
10192
+ function readHead(path3, bytes = HEAD_BYTES) {
10193
+ let fd;
10194
+ try {
10195
+ fd = openSync2(path3, "r");
10196
+ } catch {
10197
+ return null;
10198
+ }
10199
+ try {
10200
+ const buf = Buffer.alloc(bytes);
10201
+ const read = readSync(fd, buf, 0, bytes, 0);
10202
+ return buf.subarray(0, read).toString("utf-8");
10203
+ } catch {
10204
+ return null;
10205
+ } finally {
10206
+ closeSync2(fd);
10207
+ }
10208
+ }
10209
+ function cwdFromJsonlHead(text) {
10210
+ for (const line of text.split(`
10211
+ `).slice(0, HEAD_LINES)) {
10212
+ if (!line.includes('"cwd"'))
10213
+ continue;
10214
+ try {
10215
+ const obj = JSON.parse(line);
10216
+ const cwd = obj.cwd ?? obj.payload?.cwd;
10217
+ if (typeof cwd === "string" && cwd.startsWith("/"))
10218
+ return cwd;
10219
+ } catch {}
10220
+ }
10221
+ return null;
10222
+ }
10223
+ function unmungeSlug(slug, exists = existsSync11) {
10224
+ const tokens = slug.replace(/^-/, "").split("-");
10225
+ if (tokens.length === 0 || tokens[0] === "")
10226
+ return null;
10227
+ let budget = 5000;
10228
+ const walk = (prefix, index) => {
10229
+ if (budget-- <= 0)
10230
+ return null;
10231
+ if (index === tokens.length)
10232
+ return exists(prefix) ? prefix : null;
10233
+ const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
10234
+ if (asSegment)
10235
+ return asSegment;
10236
+ return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
10237
+ };
10238
+ return walk("", 0);
10239
+ }
10240
+ function fileMtime(path3) {
10241
+ try {
10242
+ return statSync4(path3).mtime.toISOString();
10243
+ } catch {
10244
+ return "";
10245
+ }
10246
+ }
10247
+ function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
10248
+ const exists = deps.exists ?? existsSync11;
10249
+ const head = deps.readHead ?? readHead;
10250
+ const mtime = deps.mtime ?? fileMtime;
10251
+ const entries = loadCacheFile(configDir);
10252
+ let dirty = false;
10253
+ const compute = (session) => {
10254
+ switch (session.harness) {
10255
+ case "opencode":
10256
+ return session.project ?? null;
10257
+ case "claude": {
10258
+ const text = head(session.path);
10259
+ const cwd = text ? cwdFromJsonlHead(text) : null;
10260
+ if (cwd)
10261
+ return cwd;
10262
+ return session.project ? unmungeSlug(session.project, exists) : null;
10263
+ }
10264
+ case "codex": {
10265
+ const text = head(session.path);
10266
+ return text ? cwdFromJsonlHead(text) : null;
10267
+ }
10268
+ case "cursor":
10269
+ return session.project ? unmungeSlug(session.project, exists) : null;
10270
+ default:
10271
+ return null;
10272
+ }
10273
+ };
10274
+ return {
10275
+ resolve(session) {
10276
+ const key = `${session.harness}/${session.id}`;
10277
+ const cached = entries[key];
10278
+ if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
10279
+ return cached.dir;
10280
+ }
10281
+ const dir = compute(session);
10282
+ entries[key] = { dir, mtime: mtime(session.path) };
10283
+ dirty = true;
10284
+ return dir;
10285
+ },
10286
+ flush() {
10287
+ if (!dirty)
10288
+ return;
10289
+ saveCacheFile(configDir, entries);
10290
+ dirty = false;
10291
+ }
10292
+ };
10293
+ }
10294
+ var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
10295
+ var init_project_dir = __esm(() => {
10296
+ init_config();
10297
+ HEAD_BYTES = 128 * 1024;
10298
+ });
10299
+
10300
+ // src/sync/backlog.ts
10301
+ function listSessionBacklog() {
10302
+ try {
10303
+ const state = loadSyncState();
10304
+ let sessions = scanAgentSessions({});
10305
+ if (state.project_filter?.length) {
10306
+ const resolver = createProjectDirResolver();
10307
+ sessions = filterSessionsByProject(sessions, state.project_filter, resolver.resolve);
10308
+ resolver.flush();
10309
+ }
10310
+ const gate = gateSessions(sessions, state.watermark);
10311
+ return { queued: gate.ready.reverse(), open: gate.open.reverse() };
10312
+ } catch {
10313
+ return { queued: [], open: [] };
10314
+ }
10315
+ }
10316
+ var init_backlog = __esm(() => {
10317
+ init_project_dir();
10318
+ init_scan();
10319
+ init_watermark();
10320
+ });
10321
+
10322
+ // src/sync/lock.ts
10323
+ import { existsSync as existsSync12, readFileSync as readFileSync13, rmSync as rmSync2, statSync as statSync5, writeFileSync as writeFileSync9 } from "node:fs";
10324
+ import { join as join12 } from "node:path";
10325
+ function processAlive(pid) {
10326
+ try {
10327
+ process.kill(pid, 0);
10328
+ return true;
10329
+ } catch (err) {
10330
+ return err.code === "EPERM";
10331
+ }
10332
+ }
10333
+ function lockPath(configDir = getConfigDir()) {
10334
+ return join12(configDir, LOCK_FILENAME);
10335
+ }
10336
+ function stopSyncRun(pid, configDir = getConfigDir(), kill = process.kill.bind(process)) {
10337
+ if (!Number.isInteger(pid) || pid <= 0)
10338
+ return false;
10339
+ let killed = false;
10340
+ try {
10341
+ kill(-pid, "SIGTERM");
10342
+ killed = true;
10343
+ } catch {
10344
+ try {
10345
+ kill(pid, "SIGTERM");
10346
+ killed = true;
10347
+ } catch {}
10348
+ }
10349
+ try {
10350
+ const path3 = lockPath(configDir);
10351
+ if (existsSync12(path3) && readFileSync13(path3, "utf8") === String(pid)) {
10352
+ rmSync2(path3, { force: true });
10353
+ }
10354
+ } catch {}
10355
+ return killed;
10356
+ }
10357
+ function fileLock(configDir = getConfigDir(), now = () => new Date) {
10358
+ const path3 = lockPath(configDir);
10359
+ let held = false;
10360
+ const tryCreate = () => {
10361
+ try {
10362
+ writeFileSync9(path3, String(process.pid), { flag: "wx" });
10363
+ return true;
10364
+ } catch {
10365
+ return false;
10366
+ }
10367
+ };
10368
+ return {
10369
+ acquire() {
10370
+ if (tryCreate()) {
10371
+ held = true;
10372
+ return true;
10373
+ }
10374
+ try {
10375
+ const age = now().getTime() - statSync5(path3).mtimeMs;
10376
+ const holder = Number.parseInt(readFileSync13(path3, "utf8"), 10);
10377
+ const holderDead = Number.isInteger(holder) && holder > 0 && !processAlive(holder);
10378
+ if (age > STALE_LOCK_MS || holderDead) {
10379
+ rmSync2(path3, { force: true });
10380
+ held = tryCreate();
10381
+ return held;
10382
+ }
10383
+ } catch {}
10384
+ return false;
10385
+ },
10386
+ release() {
10387
+ if (!held)
10388
+ return;
10389
+ held = false;
10390
+ try {
10391
+ if (existsSync12(path3) && readFileSync13(path3, "utf8") === String(process.pid)) {
10392
+ rmSync2(path3, { force: true });
10393
+ }
10394
+ } catch {}
10395
+ }
10396
+ };
10397
+ }
10398
+ var LOCK_FILENAME = "knowledge-sync.lock", STALE_LOCK_MS;
10399
+ var init_lock = __esm(() => {
10400
+ init_config();
10401
+ STALE_LOCK_MS = 15 * 60 * 1000;
10402
+ });
10403
+
10404
+ // src/sync/status.ts
10405
+ import { readFileSync as readFileSync14, statSync as statSync6 } from "node:fs";
10406
+ function formatTokenCount(tokens) {
10407
+ if (tokens < 1000)
10408
+ return String(tokens);
10409
+ if (tokens < 1e6) {
10410
+ const k = tokens / 1000;
10411
+ return `~${k >= 100 ? Math.round(k) : k.toFixed(1).replace(/\.0$/, "")}k`;
10412
+ }
10413
+ const m3 = tokens / 1e6;
10414
+ return `~${m3 >= 100 ? Math.round(m3) : m3.toFixed(1).replace(/\.0$/, "")}M`;
10415
+ }
10416
+ function defaultIsProcessAlive2(pid) {
10417
+ if (!Number.isInteger(pid) || pid <= 0)
10418
+ return false;
10419
+ try {
10420
+ process.kill(pid, 0);
10421
+ return true;
10422
+ } catch (err) {
10423
+ return err.code === "EPERM";
10424
+ }
10425
+ }
10426
+ function defaultReadLog() {
10427
+ try {
10428
+ return readFileSync14(logger.getLogPath(), "utf-8");
10429
+ } catch {
10430
+ return "";
10431
+ }
10432
+ }
10433
+ function readLockFile(configDir) {
10434
+ const path3 = lockPath(configDir);
10435
+ try {
10436
+ const pid = Number.parseInt(readFileSync14(path3, "utf-8"), 10);
10437
+ return { pid: Number.isNaN(pid) ? -1 : pid, mtime: statSync6(path3).mtime };
10438
+ } catch {
10439
+ return null;
10440
+ }
10441
+ }
10442
+ function recentSyncActivity(log3) {
10443
+ return log3.split(`
10444
+ `).filter((line) => line.includes("[sync]") || line.includes("[miner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
10445
+ }
10446
+ function getSyncStatus(deps = {}) {
10447
+ const configDir = deps.configDir ?? getConfigDir();
10448
+ const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive2;
10449
+ const readLog = deps.readLog ?? defaultReadLog;
10450
+ const lock = readLockFile(configDir);
10451
+ const running = lock !== null && isProcessAlive(lock.pid);
10452
+ const state = loadSyncState(configDir);
10453
+ const retryAt = backoffUntil(state);
10454
+ return {
10455
+ running,
10456
+ ...lock ? { pid: lock.pid, startedAt: lock.mtime.toISOString() } : {},
10457
+ ...lock && !running ? { staleLock: true } : {},
10458
+ state,
10459
+ ...retryAt ? { backoffUntil: retryAt.toISOString() } : {},
10460
+ recentActivity: recentSyncActivity(readLog())
10461
+ };
10462
+ }
10463
+ var ACTIVITY_LINES = 10;
10464
+ var init_status = __esm(() => {
10465
+ init_config();
10466
+ init_logger();
10467
+ init_lock();
10468
+ init_watermark();
9094
10469
  });
9095
10470
 
9096
10471
  // src/sync/sync.ts
@@ -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
  }
@@ -9304,12 +10679,12 @@ var init_sync = __esm(() => {
9304
10679
  });
9305
10680
 
9306
10681
  // 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";
10682
+ import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
10683
+ import { tmpdir as tmpdir2 } from "node:os";
10684
+ import { join as join13 } from "node:path";
9310
10685
  function createRunConfigDir() {
9311
- const path3 = mkdtempSync(join12(tmpdir(), "dosu-miner-"));
9312
- writeFileSync9(join12(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
10686
+ const path3 = mkdtempSync(join13(tmpdir2(), "dosu-miner-"));
10687
+ writeFileSync10(join13(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
9313
10688
  return {
9314
10689
  path: path3,
9315
10690
  cleanup() {
@@ -9320,7 +10695,7 @@ function createRunConfigDir() {
9320
10695
  var init_config_dir = () => {};
9321
10696
 
9322
10697
  // src/miner/conflicts.ts
9323
- import { existsSync as existsSync13, readFileSync as readFileSync14 } from "node:fs";
10698
+ import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
9324
10699
  function managedSettingsPaths(platform3 = process.platform) {
9325
10700
  switch (platform3) {
9326
10701
  case "darwin":
@@ -9353,7 +10728,7 @@ function detectSettingsConflicts(paths = managedSettingsPaths()) {
9353
10728
  continue;
9354
10729
  let settings2;
9355
10730
  try {
9356
- settings2 = JSON.parse(readFileSync14(file, "utf8"));
10731
+ settings2 = JSON.parse(readFileSync15(file, "utf8"));
9357
10732
  } catch {
9358
10733
  conflicts.push({ file, keys: ["<unreadable or invalid JSON>"] });
9359
10734
  continue;
@@ -9415,7 +10790,7 @@ var init_env = __esm(() => {
9415
10790
  import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
9416
10791
  import { createRequire as createRequire3 } from "node:module";
9417
10792
  import { homedir as homedir4 } from "node:os";
9418
- import { delimiter as delimiter2, dirname as dirname4, join as join13 } from "node:path";
10793
+ import { delimiter as delimiter2, dirname as dirname5, join as join14 } from "node:path";
9419
10794
  function binaryNames(platform3 = process.platform) {
9420
10795
  return platform3 === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
9421
10796
  }
@@ -9423,8 +10798,8 @@ function sdkNativeBinaryExists() {
9423
10798
  try {
9424
10799
  const requireRuntime = createRequire3(import.meta.url);
9425
10800
  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)));
10801
+ const pkgDir = dirname5(requireRuntime.resolve(`${pkg}/package.json`));
10802
+ return binaryNames().some((name) => existsSync14(join14(pkgDir, name)));
9428
10803
  } catch {
9429
10804
  return false;
9430
10805
  }
@@ -9440,10 +10815,10 @@ function isExecutable(path3) {
9440
10815
  function findSystemClaude(env = process.env, homeDir = homedir4()) {
9441
10816
  const names = binaryNames();
9442
10817
  const dirs = (env.PATH ?? "").split(delimiter2).filter(Boolean);
9443
- dirs.push(join13(homeDir, ".local", "bin"), join13(homeDir, ".claude", "local"));
10818
+ dirs.push(join14(homeDir, ".local", "bin"), join14(homeDir, ".claude", "local"));
9444
10819
  for (const dir of dirs) {
9445
10820
  for (const name of names) {
9446
- const candidate = join13(dir, name);
10821
+ const candidate = join14(dir, name);
9447
10822
  if (isExecutable(candidate))
9448
10823
  return candidate;
9449
10824
  }
@@ -52091,7 +53466,7 @@ __export(exports_util, {
52091
53466
  extend: () => extend,
52092
53467
  explicitlyAborted: () => explicitlyAborted,
52093
53468
  escapeRegex: () => escapeRegex,
52094
- esc: () => esc,
53469
+ esc: () => esc2,
52095
53470
  defineLazyInternal: () => defineLazyInternal,
52096
53471
  defineLazy: () => defineLazy,
52097
53472
  createTransparentProxy: () => createTransparentProxy,
@@ -52242,7 +53617,7 @@ function randomString(length = 10) {
52242
53617
  }
52243
53618
  return str;
52244
53619
  }
52245
- function esc(str) {
53620
+ function esc2(str) {
52246
53621
  return JSON.stringify(str);
52247
53622
  }
52248
53623
  function slugify(input) {
@@ -55363,7 +56738,7 @@ var init_schemas = __esm(() => {
55363
56738
  if (key === "__proto__")
55364
56739
  continue;
55365
56740
  const id2 = ids[key];
55366
- const k2 = typeof key === "symbol" ? `syms[${syms.indexOf(key)}]` : esc(key);
56741
+ const k2 = typeof key === "symbol" ? `syms[${syms.indexOf(key)}]` : esc2(key);
55367
56742
  const isPresent = `${k2} in input`;
55368
56743
  const schema = shape[key];
55369
56744
  const optin = schema?._zod?.optin;
@@ -64600,16 +65975,16 @@ function generateStringFormatCheck(doc, ctx, def, accessor) {
64600
65975
  doc.write(`if (${accessor} !== ${accessor}.toUpperCase()) return INVALID;`);
64601
65976
  break;
64602
65977
  case "includes":
64603
- doc.write(`if (!${accessor}.includes(${esc(def.includes)})) return INVALID;`);
65978
+ doc.write(`if (!${accessor}.includes(${esc2(def.includes)})) return INVALID;`);
64604
65979
  break;
64605
65980
  case "starts_with": {
64606
65981
  const prefix = def.prefix;
64607
- doc.write(`if (${accessor}.slice(0, ${prefix.length}) !== ${esc(prefix)}) return INVALID;`);
65982
+ doc.write(`if (${accessor}.slice(0, ${prefix.length}) !== ${esc2(prefix)}) return INVALID;`);
64608
65983
  break;
64609
65984
  }
64610
65985
  case "ends_with": {
64611
65986
  const suffix = def.suffix;
64612
- doc.write(`if (${accessor}.slice(-${suffix.length}) !== ${esc(suffix)}) return INVALID;`);
65987
+ doc.write(`if (${accessor}.slice(-${suffix.length}) !== ${esc2(suffix)}) return INVALID;`);
64613
65988
  break;
64614
65989
  }
64615
65990
  default: {
@@ -64813,8 +66188,8 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
64813
66188
  const keys = Object.keys(shape);
64814
66189
  const symbolKeys = Object.getOwnPropertySymbols(shape);
64815
66190
  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);
66191
+ const keyExpr = (k2) => typeof k2 === "symbol" ? addConstant(ctx, k2) : esc2(k2);
66192
+ const propKey = (k2) => typeof k2 === "symbol" ? `[${keyExpr(k2)}]` : esc2(k2);
64818
66193
  const propShape = shape;
64819
66194
  if (keys.includes("__proto__")) {
64820
66195
  throw new ZodCompileUnsupportedError('object shape key "__proto__"');
@@ -64858,7 +66233,7 @@ function generateObjectCheck(doc, ctx, schema, accessor, buildsValue = true) {
64858
66233
  if (catchall) {
64859
66234
  const catchallType = catchall._zod.def.type;
64860
66235
  if (catchallType === "never") {
64861
- const condition = keys.map((k2) => `k !== ${esc(k2)}`).join(" && ") || "true";
66236
+ const condition = keys.map((k2) => `k !== ${esc2(k2)}`).join(" && ") || "true";
64862
66237
  doc.write(`for (const k in ${accessor}) {`);
64863
66238
  doc.indented((d3) => {
64864
66239
  d3.write(`if (${condition}) return INVALID;`);
@@ -65120,7 +66495,7 @@ function generateLiteralCheck(doc, ctx, schema, accessor) {
65120
66495
  return accessor;
65121
66496
  }
65122
66497
  if (typeof value === "string") {
65123
- doc.write(`if (${accessor} !== ${esc(value)}) return INVALID;`);
66498
+ doc.write(`if (${accessor} !== ${esc2(value)}) return INVALID;`);
65124
66499
  } else if (typeof value === "number" || typeof value === "boolean") {
65125
66500
  doc.write(`if (${accessor} !== ${value}) return INVALID;`);
65126
66501
  } else if (value === null) {
@@ -65321,7 +66696,7 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
65321
66696
  }
65322
66697
  const discVar = newVar(ctx);
65323
66698
  const outputVar = newVar(ctx);
65324
- doc.write(`const ${discVar} = ${accessor}?.[${esc(def.discriminator)}];`);
66699
+ doc.write(`const ${discVar} = ${accessor}?.[${esc2(def.discriminator)}];`);
65325
66700
  doc.write(`let ${outputVar};`);
65326
66701
  let firstBranch = true;
65327
66702
  const claimed = new Set;
@@ -65351,7 +66726,7 @@ function generateDiscriminatedUnionCheck(doc, ctx, def, accessor) {
65351
66726
  }
65352
66727
  function literalEquality(ctx, accessor, value) {
65353
66728
  if (typeof value === "string")
65354
- return `${accessor} === ${esc(value)}`;
66729
+ return `${accessor} === ${esc2(value)}`;
65355
66730
  if (typeof value === "number") {
65356
66731
  if (Number.isNaN(value))
65357
66732
  return `Number.isNaN(${accessor})`;
@@ -65465,7 +66840,7 @@ function generateRecordCheck(doc, ctx, schema, accessor) {
65465
66840
  }
65466
66841
  function literalPropertyKey(ctx, key) {
65467
66842
  if (typeof key === "string")
65468
- return esc(key);
66843
+ return esc2(key);
65469
66844
  return addConstant(ctx, key);
65470
66845
  }
65471
66846
  function generateMapCheck(doc, ctx, schema, accessor) {
@@ -71401,6 +72776,15 @@ async function runMiner(options) {
71401
72776
  message: `Refusing to run: conflicting Claude Code settings would override the miner's auth (${detail})`
71402
72777
  };
71403
72778
  }
72779
+ const gatewayURL = options.gatewayURL ?? getLlmGatewayURL();
72780
+ if (!isAbsoluteHttpUrl(gatewayURL)) {
72781
+ return {
72782
+ outcome: "error",
72783
+ notesWritten: 0,
72784
+ turns: 0,
72785
+ message: "LLM gateway URL is not set. From source, use `bun run dev` (production endpoints) or `bun run dev:local` (local stack)."
72786
+ };
72787
+ }
71404
72788
  const { query } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
71405
72789
  const claudeExecutable = resolveClaudeExecutable();
71406
72790
  if (claudeExecutable) {
@@ -71414,10 +72798,11 @@ async function runMiner(options) {
71414
72798
  const abort = new AbortController;
71415
72799
  const timer = setTimeout(() => abort.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
71416
72800
  let notesWritten = 0;
72801
+ let lastSessionId;
71417
72802
  let turns = 0;
71418
72803
  const env = buildMinerEnv({
71419
72804
  apiKey: options.apiKey,
71420
- gatewayURL: options.gatewayURL ?? getLlmGatewayURL(),
72805
+ gatewayURL,
71421
72806
  configDir: configDir.path,
71422
72807
  runID,
71423
72808
  trigger: options.trigger,
@@ -71457,6 +72842,9 @@ async function runMiner(options) {
71457
72842
  message: `Tool ${toolName} is not permitted in mining runs.`
71458
72843
  };
71459
72844
  }
72845
+ if (toolName === `mcp__${SESSIONS_SERVER_NAME}__read_session`) {
72846
+ lastSessionId = sessionIdFromReadInput(input2) ?? lastSessionId;
72847
+ }
71460
72848
  if (toolName === `mcp__${KNOWLEDGE_SERVER_NAME}__write_knowledge`) {
71461
72849
  if (notesWritten >= maxNotes) {
71462
72850
  return {
@@ -71465,6 +72853,10 @@ async function runMiner(options) {
71465
72853
  };
71466
72854
  }
71467
72855
  notesWritten += 1;
72856
+ return {
72857
+ behavior: "allow",
72858
+ updatedInput: lastSessionId ? { ...input2, transcript_id: lastSessionId } : input2
72859
+ };
71468
72860
  }
71469
72861
  return { behavior: "allow", updatedInput: input2 };
71470
72862
  }
@@ -71525,6 +72917,7 @@ var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLE
71525
72917
  var init_runner = __esm(() => {
71526
72918
  init_logger();
71527
72919
  init_config_helpers();
72920
+ init_notes();
71528
72921
  init_config_dir();
71529
72922
  init_conflicts();
71530
72923
  init_env();
@@ -71548,17 +72941,17 @@ var init_runner = __esm(() => {
71548
72941
  });
71549
72942
 
71550
72943
  // 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";
72944
+ import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "node:fs";
72945
+ import { join as join20 } from "node:path";
71553
72946
  function getCachePath2() {
71554
- return join19(getConfigDir(), CACHE_FILENAME3);
72947
+ return join20(getConfigDir(), CACHE_FILENAME3);
71555
72948
  }
71556
72949
  function readSkillCache() {
71557
72950
  try {
71558
72951
  const path3 = getCachePath2();
71559
72952
  if (!existsSync17(path3))
71560
72953
  return null;
71561
- const data = JSON.parse(readFileSync17(path3, "utf-8"));
72954
+ const data = JSON.parse(readFileSync18(path3, "utf-8"));
71562
72955
  if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
71563
72956
  return data;
71564
72957
  }
@@ -71571,9 +72964,9 @@ function writeSkillCache(cache) {
71571
72964
  try {
71572
72965
  const dir = getConfigDir();
71573
72966
  if (!existsSync17(dir)) {
71574
- mkdirSync9(dir, { recursive: true, mode: 448 });
72967
+ mkdirSync10(dir, { recursive: true, mode: 448 });
71575
72968
  }
71576
- writeFileSync10(getCachePath2(), JSON.stringify(cache), { mode: 384 });
72969
+ writeFileSync11(getCachePath2(), JSON.stringify(cache), { mode: 384 });
71577
72970
  } catch {}
71578
72971
  }
71579
72972
  function clearInstalledSha() {
@@ -71647,7 +73040,7 @@ var init_skill_update_check = __esm(() => {
71647
73040
  // src/commands/skill.ts
71648
73041
  import { exec as exec2, execSync as execSync2 } from "node:child_process";
71649
73042
  import { homedir as homedir6 } from "node:os";
71650
- import { join as join20 } from "node:path";
73043
+ import { join as join21 } from "node:path";
71651
73044
  function skillAgentIDsForProviders(providerIDs) {
71652
73045
  return [
71653
73046
  ...new Set(providerIDs.map((providerID) => SKILL_AGENT_BY_PROVIDER[providerID]).filter((agent) => Boolean(agent)))
@@ -71658,17 +73051,17 @@ function skillInstallTargetForProvider(providerID) {
71658
73051
  if (!agentID)
71659
73052
  return null;
71660
73053
  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 };
73054
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join21(homedir6(), ".claude");
73055
+ return { path: join21(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
71663
73056
  }
71664
73057
  if (agentID === "windsurf") {
71665
73058
  return {
71666
- path: join20(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
73059
+ path: join21(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
71667
73060
  symlink: true
71668
73061
  };
71669
73062
  }
71670
73063
  return {
71671
- path: join20(homedir6(), ".agents", "skills", SKILL_NAME),
73064
+ path: join21(homedir6(), ".agents", "skills", SKILL_NAME),
71672
73065
  symlink: false
71673
73066
  };
71674
73067
  }
@@ -71677,12 +73070,12 @@ function skillAgentArgs(providerIDs) {
71677
73070
  return agents.map((agent) => `-a ${agent}`).join(" ");
71678
73071
  }
71679
73072
  function execQuiet(command) {
71680
- return new Promise((resolve5, reject) => {
73073
+ return new Promise((resolve6, reject) => {
71681
73074
  exec2(command, { windowsHide: true }, (error61) => {
71682
73075
  if (error61)
71683
73076
  reject(error61);
71684
73077
  else
71685
- resolve5();
73078
+ resolve6();
71686
73079
  });
71687
73080
  });
71688
73081
  }
@@ -71829,15 +73222,15 @@ import { randomUUID as randomUUID3 } from "node:crypto";
71829
73222
  import {
71830
73223
  existsSync as existsSync18,
71831
73224
  lstatSync as lstatSync2,
71832
- mkdirSync as mkdirSync10,
71833
- readFileSync as readFileSync18,
73225
+ mkdirSync as mkdirSync11,
73226
+ readFileSync as readFileSync19,
71834
73227
  renameSync as renameSync5,
71835
73228
  unlinkSync as unlinkSync3,
71836
- writeFileSync as writeFileSync11
73229
+ writeFileSync as writeFileSync12
71837
73230
  } from "node:fs";
71838
- import { join as join21 } from "node:path";
73231
+ import { join as join22 } from "node:path";
71839
73232
  function getTelemetrySettingsPath() {
71840
- return join21(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
73233
+ return join22(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
71841
73234
  }
71842
73235
  function loadTelemetrySettings() {
71843
73236
  return readTelemetrySettings().settings;
@@ -71905,7 +73298,7 @@ function readTelemetrySettings() {
71905
73298
  }
71906
73299
  let content;
71907
73300
  try {
71908
- content = readFileSync18(path3, "utf-8");
73301
+ content = readFileSync19(path3, "utf-8");
71909
73302
  } catch {
71910
73303
  return { status: "error", settings: disabledTelemetrySettings() };
71911
73304
  }
@@ -71938,8 +73331,8 @@ function writeTelemetrySettings(settings2) {
71938
73331
  const temporaryPath = `${path3}.${process.pid}.${randomUUID3()}.tmp`;
71939
73332
  try {
71940
73333
  if (!existsSync18(dir))
71941
- mkdirSync10(dir, { recursive: true, mode: 448 });
71942
- writeFileSync11(temporaryPath, `${JSON.stringify(settings2, null, 2)}
73334
+ mkdirSync11(dir, { recursive: true, mode: 448 });
73335
+ writeFileSync12(temporaryPath, `${JSON.stringify(settings2, null, 2)}
71943
73336
  `, {
71944
73337
  flag: "wx",
71945
73338
  mode: 384
@@ -71983,14 +73376,14 @@ var init_settings = __esm(() => {
71983
73376
  });
71984
73377
 
71985
73378
  // src/telemetry/debug-id.ts
71986
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "858ed0b9-5f8c-23ca-6475-6e2164756e21";
73379
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "d438e963-a417-e96d-6475-6e2164756e21";
71987
73380
 
71988
73381
  // src/telemetry/telemetry.ts
71989
73382
  import { randomUUID as randomUUID4 } from "node:crypto";
71990
73383
  import { existsSync as existsSync19 } from "node:fs";
71991
73384
  import { request as httpRequest2 } from "node:http";
71992
73385
  import { request as httpsRequest } from "node:https";
71993
- import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve5, sep as sep5 } from "node:path";
73386
+ import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve6, sep as sep5 } from "node:path";
71994
73387
  import { fileURLToPath as fileURLToPath2 } from "node:url";
71995
73388
  function safeString(value, pattern, fallback) {
71996
73389
  if (typeof value !== "string" || !pattern.test(value))
@@ -72116,7 +73509,7 @@ function parseOwnedStackLine(originalLine, packageRoot) {
72116
73509
  }
72117
73510
  if (!isAbsolute4(absolutePath))
72118
73511
  return;
72119
- const resolvedPath = resolve5(absolutePath);
73512
+ const resolvedPath = resolve6(absolutePath);
72120
73513
  const relativePath = relative2(packageRoot, resolvedPath);
72121
73514
  if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep5}`) || isAbsolute4(relativePath)) {
72122
73515
  return;
@@ -72354,7 +73747,7 @@ function fetchWithoutRedirect(url2, init = {}) {
72354
73747
  } catch (error61) {
72355
73748
  return Promise.reject(error61);
72356
73749
  }
72357
- return new Promise((resolve6, reject) => {
73750
+ return new Promise((resolve7, reject) => {
72358
73751
  let request;
72359
73752
  let response;
72360
73753
  let settled = false;
@@ -72378,7 +73771,7 @@ function fetchWithoutRedirect(url2, init = {}) {
72378
73771
  response?.destroy();
72379
73772
  request?.socket?.destroy();
72380
73773
  request?.destroy();
72381
- resolve6(value);
73774
+ resolve7(value);
72382
73775
  };
72383
73776
  const onAbort = () => {
72384
73777
  const error61 = new Error("telemetry request aborted");
@@ -72851,7 +74244,7 @@ var init_antigravity = __esm(() => {
72851
74244
  });
72852
74245
 
72853
74246
  // src/mcp/providers/claude.ts
72854
- import { join as join22 } from "node:path";
74247
+ import { join as join23 } from "node:path";
72855
74248
  var ClaudeProvider = () => createJSONProvider({
72856
74249
  providerName: "Claude Code",
72857
74250
  providerID: "claude",
@@ -72860,24 +74253,24 @@ var ClaudeProvider = () => createJSONProvider({
72860
74253
  paths: ["~/.claude"],
72861
74254
  globalPath: "~/.claude.json",
72862
74255
  topKey: "mcpServers",
72863
- localConfigPath: (cwd) => join22(cwd, ".mcp.json")
74256
+ localConfigPath: (cwd) => join23(cwd, ".mcp.json")
72864
74257
  });
72865
74258
  var init_claude = __esm(() => {
72866
74259
  init_base();
72867
74260
  });
72868
74261
 
72869
74262
  // src/mcp/providers/claude-desktop.ts
72870
- import { join as join23 } from "node:path";
74263
+ import { join as join24 } from "node:path";
72871
74264
  function configPath() {
72872
- return join23(appSupportDir(), "Claude", "claude_desktop_config.json");
74265
+ return join24(appSupportDir(), "Claude", "claude_desktop_config.json");
72873
74266
  }
72874
74267
  var ClaudeDesktopProvider = () => ({
72875
74268
  name: () => "Claude Desktop",
72876
74269
  id: () => "claude-desktop",
72877
74270
  supportsLocal: () => false,
72878
74271
  priority: () => 2,
72879
- detectPaths: () => [join23(appSupportDir(), "Claude")],
72880
- isInstalled: () => isInstalled([join23(appSupportDir(), "Claude")]),
74272
+ detectPaths: () => [join24(appSupportDir(), "Claude")],
74273
+ isInstalled: () => isInstalled([join24(appSupportDir(), "Claude")]),
72881
74274
  globalConfigPath: () => configPath(),
72882
74275
  isConfigured: () => isJSONKeyConfigured(configPath(), "mcpServers"),
72883
74276
  install(cfg, global2) {
@@ -72907,14 +74300,14 @@ var init_claude_desktop = __esm(() => {
72907
74300
  });
72908
74301
 
72909
74302
  // 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({
74303
+ import { join as join25 } from "node:path";
74304
+ var extensionDir = () => join25(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
72912
74305
  providerName: "Cline",
72913
74306
  providerID: "cline",
72914
74307
  local: false,
72915
74308
  priorityValue: 11,
72916
74309
  paths: [extensionDir()],
72917
- globalPath: join24(extensionDir(), "settings", "cline_mcp_settings.json"),
74310
+ globalPath: join25(extensionDir(), "settings", "cline_mcp_settings.json"),
72918
74311
  topKey: "mcpServers",
72919
74312
  buildServer: (cfg) => ({
72920
74313
  url: mcpURL(cfg.active_account.target.deployment_id),
@@ -72930,7 +74323,7 @@ var init_cline = __esm(() => {
72930
74323
  });
72931
74324
 
72932
74325
  // src/mcp/providers/cline-cli.ts
72933
- import { join as join25 } from "node:path";
74326
+ import { join as join26 } from "node:path";
72934
74327
  function clineDir() {
72935
74328
  return process.env.CLINE_DIR ?? expandHome("~/.cline");
72936
74329
  }
@@ -72940,7 +74333,7 @@ var ClineCliProvider = () => createJSONProvider({
72940
74333
  local: false,
72941
74334
  priorityValue: 12,
72942
74335
  paths: [clineDir()],
72943
- globalPath: join25(clineDir(), "data", "settings", "cline_mcp_settings.json"),
74336
+ globalPath: join26(clineDir(), "data", "settings", "cline_mcp_settings.json"),
72944
74337
  topKey: "mcpServers",
72945
74338
  buildServer: (cfg) => ({
72946
74339
  url: mcpURL(cfg.active_account.target.deployment_id),
@@ -72956,20 +74349,20 @@ var init_cline_cli = __esm(() => {
72956
74349
  });
72957
74350
 
72958
74351
  // src/mcp/providers/codex.ts
72959
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "node:fs";
72960
- import { join as join26 } from "node:path";
74352
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "node:fs";
74353
+ import { join as join27 } from "node:path";
72961
74354
  function codexHome2() {
72962
74355
  return process.env.CODEX_HOME ?? expandHome("~/.codex");
72963
74356
  }
72964
74357
  function getConfigPath2(global2) {
72965
74358
  if (global2)
72966
- return join26(codexHome2(), "config.toml");
72967
- return join26(process.cwd(), ".codex", "config.toml");
74359
+ return join27(codexHome2(), "config.toml");
74360
+ return join27(process.cwd(), ".codex", "config.toml");
72968
74361
  }
72969
74362
  function readTOML(path3) {
72970
74363
  if (!existsSync20(path3))
72971
74364
  return "";
72972
- return readFileSync19(path3, "utf-8");
74365
+ return readFileSync20(path3, "utf-8");
72973
74366
  }
72974
74367
  function writeTOML(path3, content) {
72975
74368
  writeSecureFile(path3, content);
@@ -73032,9 +74425,9 @@ var CodexProvider = () => ({
73032
74425
  priority: () => 8,
73033
74426
  detectPaths: () => ["~/.codex"],
73034
74427
  isInstalled: () => isInstalled(["~/.codex"]),
73035
- globalConfigPath: () => join26(codexHome2(), "config.toml"),
74428
+ globalConfigPath: () => join27(codexHome2(), "config.toml"),
73036
74429
  isConfigured: () => {
73037
- const content = readTOML(join26(codexHome2(), "config.toml"));
74430
+ const content = readTOML(join27(codexHome2(), "config.toml"));
73038
74431
  return content.includes("[mcp_servers.dosu]");
73039
74432
  },
73040
74433
  install(cfg, global2) {
@@ -73056,10 +74449,10 @@ var init_codex = __esm(() => {
73056
74449
  });
73057
74450
 
73058
74451
  // src/mcp/providers/copilot.ts
73059
- import { join as join27 } from "node:path";
74452
+ import { join as join28 } from "node:path";
73060
74453
  function globalPath() {
73061
74454
  if (process.env.XDG_CONFIG_HOME) {
73062
- return join27(process.env.XDG_CONFIG_HOME, "mcp-config.json");
74455
+ return join28(process.env.XDG_CONFIG_HOME, "mcp-config.json");
73063
74456
  }
73064
74457
  return expandHome("~/.copilot/mcp-config.json");
73065
74458
  }
@@ -73090,7 +74483,7 @@ var CopilotProvider = () => ({
73090
74483
  };
73091
74484
  installJSONServer(globalPath(), "mcpServers", server);
73092
74485
  } else {
73093
- const configPath2 = join27(process.cwd(), ".vscode", "mcp.json");
74486
+ const configPath2 = join28(process.cwd(), ".vscode", "mcp.json");
73094
74487
  const server = {
73095
74488
  type: "http",
73096
74489
  url: url2,
@@ -73103,7 +74496,7 @@ var CopilotProvider = () => ({
73103
74496
  if (global2) {
73104
74497
  removeJSONServer(globalPath(), "mcpServers");
73105
74498
  } else {
73106
- const configPath2 = join27(process.cwd(), ".vscode", "mcp.json");
74499
+ const configPath2 = join28(process.cwd(), ".vscode", "mcp.json");
73107
74500
  removeJSONServer(configPath2, "servers");
73108
74501
  }
73109
74502
  }
@@ -73115,7 +74508,7 @@ var init_copilot = __esm(() => {
73115
74508
  });
73116
74509
 
73117
74510
  // src/mcp/providers/cursor.ts
73118
- import { join as join28 } from "node:path";
74511
+ import { join as join29 } from "node:path";
73119
74512
  var CursorProvider = () => createJSONProvider({
73120
74513
  providerName: "Cursor",
73121
74514
  providerID: "cursor",
@@ -73128,7 +74521,7 @@ var CursorProvider = () => createJSONProvider({
73128
74521
  url: mcpURL(cfg.active_account.target.deployment_id),
73129
74522
  headers: mcpHeaders(cfg.active_account.target.api_key)
73130
74523
  }),
73131
- localConfigPath: (cwd) => join28(cwd, ".cursor", "mcp.json")
74524
+ localConfigPath: (cwd) => join29(cwd, ".cursor", "mcp.json")
73132
74525
  });
73133
74526
  var init_cursor = __esm(() => {
73134
74527
  init_config_helpers();
@@ -73136,7 +74529,7 @@ var init_cursor = __esm(() => {
73136
74529
  });
73137
74530
 
73138
74531
  // src/mcp/providers/factory.ts
73139
- import { join as join29 } from "node:path";
74532
+ import { join as join30 } from "node:path";
73140
74533
  var FactoryProvider = () => createJSONProvider({
73141
74534
  providerName: "Factory",
73142
74535
  providerID: "factory",
@@ -73145,14 +74538,14 @@ var FactoryProvider = () => createJSONProvider({
73145
74538
  paths: ["~/.factory"],
73146
74539
  globalPath: "~/.factory/mcp.json",
73147
74540
  topKey: "mcpServers",
73148
- localConfigPath: (cwd) => join29(cwd, ".factory", "mcp.json")
74541
+ localConfigPath: (cwd) => join30(cwd, ".factory", "mcp.json")
73149
74542
  });
73150
74543
  var init_factory = __esm(() => {
73151
74544
  init_base();
73152
74545
  });
73153
74546
 
73154
74547
  // src/mcp/providers/gemini.ts
73155
- import { join as join30 } from "node:path";
74548
+ import { join as join31 } from "node:path";
73156
74549
  var GeminiProvider = () => createJSONProvider({
73157
74550
  providerName: "Gemini CLI",
73158
74551
  providerID: "gemini",
@@ -73161,7 +74554,7 @@ var GeminiProvider = () => createJSONProvider({
73161
74554
  paths: ["~/.gemini"],
73162
74555
  globalPath: "~/.gemini/settings.json",
73163
74556
  topKey: "mcpServers",
73164
- localConfigPath: (cwd) => join30(cwd, ".gemini", "settings.json")
74557
+ localConfigPath: (cwd) => join31(cwd, ".gemini", "settings.json")
73165
74558
  });
73166
74559
  var init_gemini = __esm(() => {
73167
74560
  init_base();
@@ -73212,7 +74605,7 @@ var init_manual = __esm(() => {
73212
74605
 
73213
74606
  // src/mcp/providers/mcporter.ts
73214
74607
  import { existsSync as existsSync21 } from "node:fs";
73215
- import { join as join31 } from "node:path";
74608
+ import { join as join32 } from "node:path";
73216
74609
  function resolveGlobalConfigPath() {
73217
74610
  const jsonPath = expandHome("~/.mcporter/mcporter.json");
73218
74611
  if (existsSync21(jsonPath))
@@ -73239,7 +74632,7 @@ var MCPorterProvider = () => ({
73239
74632
  globalConfigPath: () => resolveGlobalConfigPath(),
73240
74633
  isConfigured: () => isJSONKeyConfigured(resolveGlobalConfigPath(), "mcpServers"),
73241
74634
  install(cfg, global2) {
73242
- const configPath2 = global2 ? resolveGlobalConfigPath() : join31(process.cwd(), "config", "mcporter.json");
74635
+ const configPath2 = global2 ? resolveGlobalConfigPath() : join32(process.cwd(), "config", "mcporter.json");
73243
74636
  const server = {
73244
74637
  type: "http",
73245
74638
  url: mcpEndpoint4(cfg),
@@ -73248,7 +74641,7 @@ var MCPorterProvider = () => ({
73248
74641
  installJSONServer(configPath2, "mcpServers", server);
73249
74642
  },
73250
74643
  remove(global2) {
73251
- const configPath2 = global2 ? resolveGlobalConfigPath() : join31(process.cwd(), "config", "mcporter.json");
74644
+ const configPath2 = global2 ? resolveGlobalConfigPath() : join32(process.cwd(), "config", "mcporter.json");
73252
74645
  removeJSONServer(configPath2, "mcpServers");
73253
74646
  }
73254
74647
  });
@@ -73259,7 +74652,7 @@ var init_mcporter = __esm(() => {
73259
74652
  });
73260
74653
 
73261
74654
  // src/mcp/providers/opencode.ts
73262
- import { join as join32 } from "node:path";
74655
+ import { join as join33 } from "node:path";
73263
74656
  var OpenCodeProvider = () => createJSONProvider({
73264
74657
  providerName: "OpenCode",
73265
74658
  providerID: "opencode",
@@ -73274,7 +74667,7 @@ var OpenCodeProvider = () => createJSONProvider({
73274
74667
  enabled: true,
73275
74668
  headers: mcpHeaders(cfg.active_account.target.api_key)
73276
74669
  }),
73277
- localConfigPath: (cwd) => join32(cwd, "opencode.json")
74670
+ localConfigPath: (cwd) => join33(cwd, "opencode.json")
73278
74671
  });
73279
74672
  var init_opencode = __esm(() => {
73280
74673
  init_config_helpers();
@@ -73282,16 +74675,16 @@ var init_opencode = __esm(() => {
73282
74675
  });
73283
74676
 
73284
74677
  // src/mcp/providers/vscode.ts
73285
- import { join as join33 } from "node:path";
74678
+ import { join as join34 } from "node:path";
73286
74679
  var VSCodeProvider = () => createJSONProvider({
73287
74680
  providerName: "VS Code",
73288
74681
  providerID: "vscode",
73289
74682
  local: true,
73290
74683
  priorityValue: 6,
73291
- paths: [join33(appSupportDir(), "Code")],
73292
- globalPath: join33(appSupportDir(), "Code", "User", "mcp.json"),
74684
+ paths: [join34(appSupportDir(), "Code")],
74685
+ globalPath: join34(appSupportDir(), "Code", "User", "mcp.json"),
73293
74686
  topKey: "servers",
73294
- localConfigPath: (cwd) => join33(cwd, ".vscode", "mcp.json")
74687
+ localConfigPath: (cwd) => join34(cwd, ".vscode", "mcp.json")
73295
74688
  });
73296
74689
  var init_vscode = __esm(() => {
73297
74690
  init_detect();
@@ -73300,14 +74693,14 @@ var init_vscode = __esm(() => {
73300
74693
 
73301
74694
  // src/mcp/providers/windsurf.ts
73302
74695
  import { homedir as homedir8 } from "node:os";
73303
- import { join as join34 } from "node:path";
74696
+ import { join as join35 } from "node:path";
73304
74697
  var WindsurfProvider = () => createJSONProvider({
73305
74698
  providerName: "Windsurf",
73306
74699
  providerID: "windsurf",
73307
74700
  local: false,
73308
74701
  priorityValue: 9,
73309
- paths: [join34(homedir8(), ".codeium", "windsurf")],
73310
- globalPath: join34(homedir8(), ".codeium", "windsurf", "mcp_config.json"),
74702
+ paths: [join35(homedir8(), ".codeium", "windsurf")],
74703
+ globalPath: join35(homedir8(), ".codeium", "windsurf", "mcp_config.json"),
73311
74704
  topKey: "mcpServers"
73312
74705
  });
73313
74706
  var init_windsurf = __esm(() => {
@@ -73316,12 +74709,12 @@ var init_windsurf = __esm(() => {
73316
74709
 
73317
74710
  // src/mcp/providers/zed.ts
73318
74711
  import { platform as platform3 } from "node:os";
73319
- import { join as join35 } from "node:path";
74712
+ import { join as join36 } from "node:path";
73320
74713
  function zedConfigDir() {
73321
74714
  const os3 = platform3();
73322
74715
  if (os3 === "darwin" || os3 === "win32")
73323
- return join35(appSupportDir(), "Zed");
73324
- return join35(appSupportDir(), "zed");
74716
+ return join36(appSupportDir(), "Zed");
74717
+ return join36(appSupportDir(), "zed");
73325
74718
  }
73326
74719
  var ZedProvider = () => createJSONProvider({
73327
74720
  providerName: "Zed",
@@ -73329,7 +74722,7 @@ var ZedProvider = () => createJSONProvider({
73329
74722
  local: true,
73330
74723
  priorityValue: 10,
73331
74724
  paths: [zedConfigDir()],
73332
- globalPath: join35(zedConfigDir(), "settings.json"),
74725
+ globalPath: join36(zedConfigDir(), "settings.json"),
73333
74726
  topKey: "context_servers",
73334
74727
  buildServer: (cfg) => ({
73335
74728
  source: "custom",
@@ -73337,7 +74730,7 @@ var ZedProvider = () => createJSONProvider({
73337
74730
  url: mcpURL(cfg.active_account.target.deployment_id),
73338
74731
  headers: mcpHeaders(cfg.active_account.target.api_key)
73339
74732
  }),
73340
- localConfigPath: (cwd) => join35(cwd, ".zed", "settings.json")
74733
+ localConfigPath: (cwd) => join36(cwd, ".zed", "settings.json")
73341
74734
  });
73342
74735
  var init_zed = __esm(() => {
73343
74736
  init_config_helpers();
@@ -73396,8 +74789,8 @@ var init_providers = __esm(() => {
73396
74789
  });
73397
74790
 
73398
74791
  // 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";
74792
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "node:fs";
74793
+ import { join as join37 } from "node:path";
73401
74794
  function isValidVersion(value) {
73402
74795
  return typeof value === "string" && SEMVER_PATTERN.test(value);
73403
74796
  }
@@ -73419,14 +74812,14 @@ function isNewerVersion(latest, current) {
73419
74812
  return false;
73420
74813
  }
73421
74814
  function getCachePath3() {
73422
- return join36(getConfigDir(), CACHE_FILENAME4);
74815
+ return join37(getConfigDir(), CACHE_FILENAME4);
73423
74816
  }
73424
74817
  function readCache() {
73425
74818
  try {
73426
74819
  const path3 = getCachePath3();
73427
74820
  if (!existsSync22(path3))
73428
74821
  return null;
73429
- const data = JSON.parse(readFileSync20(path3, "utf-8"));
74822
+ const data = JSON.parse(readFileSync21(path3, "utf-8"));
73430
74823
  if (typeof data.lastCheck === "number" && isValidVersion(data.latestVersion)) {
73431
74824
  return data;
73432
74825
  }
@@ -73439,9 +74832,9 @@ function writeCache(cache) {
73439
74832
  try {
73440
74833
  const dir = getConfigDir();
73441
74834
  if (!existsSync22(dir)) {
73442
- mkdirSync11(dir, { recursive: true, mode: 448 });
74835
+ mkdirSync12(dir, { recursive: true, mode: 448 });
73443
74836
  }
73444
- writeFileSync12(getCachePath3(), JSON.stringify(cache), { mode: 384 });
74837
+ writeFileSync13(getCachePath3(), JSON.stringify(cache), { mode: 384 });
73445
74838
  } catch {}
73446
74839
  }
73447
74840
  async function fetchLatestVersion() {
@@ -73551,14 +74944,14 @@ var init_update_check = __esm(() => {
73551
74944
  });
73552
74945
 
73553
74946
  // src/rules/installer.ts
73554
- import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync21, unlinkSync as unlinkSync4, writeFileSync as writeFileSync13 } from "node:fs";
74947
+ import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync22, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
73555
74948
  import { homedir as homedir9 } from "node:os";
73556
- import { dirname as dirname8, join as join37 } from "node:path";
74949
+ import { dirname as dirname9, join as join38 } from "node:path";
73557
74950
  function claudeConfigDir() {
73558
- return process.env.CLAUDE_CONFIG_DIR || join37(homedir9(), ".claude");
74951
+ return process.env.CLAUDE_CONFIG_DIR || join38(homedir9(), ".claude");
73559
74952
  }
73560
74953
  function codexHome3() {
73561
- return process.env.CODEX_HOME || join37(homedir9(), ".codex");
74954
+ return process.env.CODEX_HOME || join38(homedir9(), ".codex");
73562
74955
  }
73563
74956
  function isRuleAgent(agent) {
73564
74957
  return Object.hasOwn(RULE_TARGETS, agent);
@@ -73584,17 +74977,17 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
73584
74977
  return normalizeRule(FALLBACK_DOSU_RULE);
73585
74978
  }
73586
74979
  function ensureParent(path3) {
73587
- mkdirSync12(dirname8(path3), { recursive: true });
74980
+ mkdirSync13(dirname9(path3), { recursive: true });
73588
74981
  }
73589
74982
  function writeIfChanged(path3, content) {
73590
74983
  if (!existsSync23(path3)) {
73591
74984
  ensureParent(path3);
73592
- writeFileSync13(path3, content, "utf-8");
74985
+ writeFileSync14(path3, content, "utf-8");
73593
74986
  return "created";
73594
74987
  }
73595
- if (readFileSync21(path3, "utf-8") === content)
74988
+ if (readFileSync22(path3, "utf-8") === content)
73596
74989
  return "unchanged";
73597
- writeFileSync13(path3, content, "utf-8");
74990
+ writeFileSync14(path3, content, "utf-8");
73598
74991
  return "updated";
73599
74992
  }
73600
74993
  function findSection(content) {
@@ -73623,11 +75016,11 @@ function upsertSection(path3, content) {
73623
75016
  if (!existsSync23(path3)) {
73624
75017
  const section2 = buildSection(content);
73625
75018
  ensureParent(path3);
73626
- writeFileSync13(path3, `${section2}
75019
+ writeFileSync14(path3, `${section2}
73627
75020
  `, "utf-8");
73628
75021
  return "created";
73629
75022
  }
73630
- const existing = readFileSync21(path3, "utf-8");
75023
+ const existing = readFileSync22(path3, "utf-8");
73631
75024
  const lineEnding = lineEndingFor(existing);
73632
75025
  const section = buildSection(content, lineEnding);
73633
75026
  const location = findSection(existing);
@@ -73640,7 +75033,7 @@ function upsertSection(path3, content) {
73640
75033
  }
73641
75034
  if (next === existing)
73642
75035
  return "unchanged";
73643
- writeFileSync13(path3, next, "utf-8");
75036
+ writeFileSync14(path3, next, "utf-8");
73644
75037
  return "updated";
73645
75038
  }
73646
75039
  function installRuleForAgent(agent, content) {
@@ -73663,7 +75056,7 @@ function removeRuleForAgent(agent) {
73663
75056
  unlinkSync4(path3);
73664
75057
  return { agent, path: path3, action: "removed" };
73665
75058
  }
73666
- const existing = readFileSync21(path3, "utf-8");
75059
+ const existing = readFileSync22(path3, "utf-8");
73667
75060
  const lineEnding = lineEndingFor(existing);
73668
75061
  const location = findSection(existing);
73669
75062
  if (!location)
@@ -73672,7 +75065,7 @@ function removeRuleForAgent(agent) {
73672
75065
  if (!next) {
73673
75066
  unlinkSync4(path3);
73674
75067
  } else {
73675
- writeFileSync13(path3, `${next}${lineEnding}`, "utf-8");
75068
+ writeFileSync14(path3, `${next}${lineEnding}`, "utf-8");
73676
75069
  }
73677
75070
  return { agent, path: path3, action: "removed" };
73678
75071
  }
@@ -73700,36 +75093,36 @@ var init_installer = __esm(() => {
73700
75093
  RULE_TARGETS = {
73701
75094
  claude: {
73702
75095
  kind: "file",
73703
- path: () => join37(claudeConfigDir(), "rules", "dosu.md")
75096
+ path: () => join38(claudeConfigDir(), "rules", "dosu.md")
73704
75097
  },
73705
75098
  cursor: {
73706
75099
  kind: "file",
73707
- path: () => join37(homedir9(), ".cursor", "rules", "dosu.mdc"),
75100
+ path: () => join38(homedir9(), ".cursor", "rules", "dosu.mdc"),
73708
75101
  cursorFrontmatter: true
73709
75102
  },
73710
75103
  codex: {
73711
75104
  kind: "section",
73712
- path: () => join37(codexHome3(), "AGENTS.md")
75105
+ path: () => join38(codexHome3(), "AGENTS.md")
73713
75106
  },
73714
75107
  opencode: {
73715
75108
  kind: "section",
73716
- path: () => join37(homedir9(), ".config", "opencode", "AGENTS.md")
75109
+ path: () => join38(homedir9(), ".config", "opencode", "AGENTS.md")
73717
75110
  },
73718
75111
  gemini: {
73719
75112
  kind: "section",
73720
- path: () => join37(homedir9(), ".gemini", "GEMINI.md")
75113
+ path: () => join38(homedir9(), ".gemini", "GEMINI.md")
73721
75114
  },
73722
75115
  antigravity: {
73723
75116
  kind: "section",
73724
- path: () => join37(homedir9(), ".gemini", "GEMINI.md")
75117
+ path: () => join38(homedir9(), ".gemini", "GEMINI.md")
73725
75118
  }
73726
75119
  };
73727
75120
  });
73728
75121
 
73729
75122
  // src/setup/agents-md-step.ts
73730
75123
  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";
75124
+ import { existsSync as existsSync24, readFileSync as readFileSync23, writeFileSync as writeFileSync15 } from "node:fs";
75125
+ import { join as join39 } from "node:path";
73733
75126
  function inGitWorkTree(cwd = process.cwd()) {
73734
75127
  try {
73735
75128
  const stdout2 = execSync3("git rev-parse --is-inside-work-tree", {
@@ -73743,10 +75136,10 @@ function inGitWorkTree(cwd = process.cwd()) {
73743
75136
  }
73744
75137
  function dosuAgentsSectionState(cwd = process.cwd()) {
73745
75138
  try {
73746
- const path3 = join38(cwd, "AGENTS.md");
75139
+ const path3 = join39(cwd, "AGENTS.md");
73747
75140
  if (!existsSync24(path3))
73748
75141
  return "missing";
73749
- const content = readFileSync22(path3, "utf-8");
75142
+ const content = readFileSync23(path3, "utf-8");
73750
75143
  const startMatch = content.match(SECTION_START_RE);
73751
75144
  if (!startMatch || !content.includes(DOSU_SECTION_END))
73752
75145
  return "missing";
@@ -73781,14 +75174,14 @@ function findSection2(content) {
73781
75174
  return { start, end };
73782
75175
  }
73783
75176
  function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RULE) {
73784
- const path3 = join38(cwd, "AGENTS.md");
75177
+ const path3 = join39(cwd, "AGENTS.md");
73785
75178
  if (!existsSync24(path3)) {
73786
75179
  const section2 = buildDosuAgentsSection(content);
73787
- writeFileSync14(path3, `${section2}
75180
+ writeFileSync15(path3, `${section2}
73788
75181
  `);
73789
75182
  return { path: path3, action: "created" };
73790
75183
  }
73791
- const existing = readFileSync22(path3, "utf-8");
75184
+ const existing = readFileSync23(path3, "utf-8");
73792
75185
  const lineEnding = lineEndingFor2(existing);
73793
75186
  const section = renderDosuAgentsSection(content, lineEnding);
73794
75187
  const location = findSection2(existing);
@@ -73800,7 +75193,7 @@ function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RU
73800
75193
  }
73801
75194
  if (next === existing)
73802
75195
  return { path: path3, action: "unchanged" };
73803
- writeFileSync14(path3, next);
75196
+ writeFileSync15(path3, next);
73804
75197
  return { path: path3, action: "updated" };
73805
75198
  }
73806
75199
  async function stepUpdateAgentsMd(cwd = process.cwd(), content) {
@@ -73882,7 +75275,7 @@ var init_alt_screen = __esm(() => {
73882
75275
  });
73883
75276
 
73884
75277
  // src/tui/activity-view.ts
73885
- import { readFileSync as readFileSync23 } from "node:fs";
75278
+ import { readFileSync as readFileSync24 } from "node:fs";
73886
75279
  function reduceActivityViewKey(key) {
73887
75280
  if (key === "q" || key === ESC7 || key === CTRL_C4)
73888
75281
  return "back";
@@ -74074,7 +75467,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
74074
75467
  const mined = status.state.watermark ? `Mined sessions up to ${localTime(status.state.watermark)}` : "Nothing mined yet";
74075
75468
  const queueDetail = [];
74076
75469
  if (status.backoffUntil) {
74077
- queueDetail.push(`retrying after ${localTime(status.backoffUntil)}`);
75470
+ queueDetail.push(`retrying after ${localTime(status.backoffUntil)} · s syncs now`);
74078
75471
  }
74079
75472
  let progress = null;
74080
75473
  if (status.running && backlog) {
@@ -74127,7 +75520,7 @@ function renderActivityFrame(status, activity, width, backlog = null, pane = DEF
74127
75520
  }
74128
75521
  function defaultReadLog2() {
74129
75522
  try {
74130
- return readFileSync23(logger.getLogPath(), "utf-8");
75523
+ return readFileSync24(logger.getLogPath(), "utf-8");
74131
75524
  } catch {
74132
75525
  return "";
74133
75526
  }
@@ -74140,7 +75533,7 @@ function runActivityView(io2 = {}) {
74140
75533
  const getStatus = io2.getStatus ?? (() => getSyncStatus({ readLog: () => "" }));
74141
75534
  const readLog = io2.readLog ?? defaultReadLog2;
74142
75535
  const createFollower = io2.createFollower ?? ((emit) => createLogFollower(logger.getLogPath(), emit));
74143
- const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--quiet", "--bootstrap"]));
75536
+ const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--bootstrap"]));
74144
75537
  const stopSync = io2.stopSync ?? stopSyncRun;
74145
75538
  const setPaused = io2.setPaused ?? setSyncPaused;
74146
75539
  const listBacklog = io2.listBacklog ?? listSessionBacklog;
@@ -74212,7 +75605,7 @@ ${CLEAR_BELOW}`);
74212
75605
  const leaveAltScreen = enterAltScreen(output2);
74213
75606
  output2.write(HIDE_CURSOR3);
74214
75607
  draw();
74215
- return new Promise((resolve6) => {
75608
+ return new Promise((resolve7) => {
74216
75609
  const wasRaw = input2.isRaw ?? false;
74217
75610
  input2.setRawMode?.(true);
74218
75611
  input2.resume();
@@ -74229,7 +75622,7 @@ ${CLEAR_BELOW}`);
74229
75622
  input2.pause();
74230
75623
  leaveAltScreen();
74231
75624
  output2.write(SHOW_CURSOR3);
74232
- resolve6();
75625
+ resolve7();
74233
75626
  };
74234
75627
  const onData = (chunk) => {
74235
75628
  for (const key of parseKeys(chunk.toString())) {
@@ -74748,8 +76141,8 @@ h1 {
74748
76141
  async function startCallbackServer(opts = {}) {
74749
76142
  let resolveToken;
74750
76143
  let rejectToken;
74751
- const tokenPromise = new Promise((resolve6, reject) => {
74752
- resolveToken = resolve6;
76144
+ const tokenPromise = new Promise((resolve7, reject) => {
76145
+ resolveToken = resolve7;
74753
76146
  rejectToken = reject;
74754
76147
  });
74755
76148
  const http = __require("node:http");
@@ -74807,8 +76200,8 @@ async function startCallbackServer(opts = {}) {
74807
76200
  res.writeHead(200, { "Content-Type": "text/html" });
74808
76201
  res.end(buildSuccessHtml(email3 ?? undefined, opts.successVariant ?? "auth"));
74809
76202
  });
74810
- await new Promise((resolve6) => {
74811
- httpServer.listen(0, "localhost", () => resolve6());
76203
+ await new Promise((resolve7) => {
76204
+ httpServer.listen(0, "localhost", () => resolve7());
74812
76205
  });
74813
76206
  httpServer.unref();
74814
76207
  const addr = httpServer.address();
@@ -76112,7 +77505,7 @@ ${CLEAR_BELOW2}`);
76112
77505
  pagesPending = false;
76113
77506
  draw();
76114
77507
  });
76115
- return new Promise((resolve6) => {
77508
+ return new Promise((resolve7) => {
76116
77509
  const wasRaw = input2.isRaw ?? false;
76117
77510
  input2.setRawMode?.(true);
76118
77511
  input2.resume();
@@ -76127,7 +77520,7 @@ ${CLEAR_BELOW2}`);
76127
77520
  input2.pause();
76128
77521
  leaveAltScreen();
76129
77522
  output2.write(SHOW_CURSOR4);
76130
- resolve6();
77523
+ resolve7();
76131
77524
  };
76132
77525
  const onData = (chunk) => {
76133
77526
  for (const key of parseKeys(chunk.toString())) {
@@ -76528,7 +77921,7 @@ ${CLEAR_BELOW3}`);
76528
77921
  });
76529
77922
  draw();
76530
77923
  };
76531
- return new Promise((resolve6) => {
77924
+ return new Promise((resolve7) => {
76532
77925
  const wasRaw = input2.isRaw ?? false;
76533
77926
  input2.setRawMode?.(true);
76534
77927
  input2.resume();
@@ -76540,7 +77933,7 @@ ${CLEAR_BELOW3}`);
76540
77933
  input2.pause();
76541
77934
  leaveAltScreen();
76542
77935
  output2.write(SHOW_CURSOR5);
76543
- resolve6();
77936
+ resolve7();
76544
77937
  };
76545
77938
  const onData = (chunk) => {
76546
77939
  for (const key of parseKeys(chunk.toString())) {
@@ -76731,7 +78124,7 @@ async function runMainMenu() {
76731
78124
  const buildOptions = () => {
76732
78125
  if (!isAuthenticated(cfg)) {
76733
78126
  return [
76734
- { label: "Log in / Sign up", hint: "opens your browser", value: "auth" },
78127
+ { label: "Log in / Sign up", hint: "(opens your browser)", value: "auth" },
76735
78128
  { label: "Exit", value: "exit" }
76736
78129
  ];
76737
78130
  }
@@ -76747,6 +78140,7 @@ async function runMainMenu() {
76747
78140
  label: mining ? `Activity ⛏️ ${brand("mining sessions...")}` : "Activity",
76748
78141
  value: "sync"
76749
78142
  },
78143
+ { label: "Knowledge report", hint: "(opens in browser)", value: "report" },
76750
78144
  { label: "Analytics", value: "analytics" },
76751
78145
  { label: "Pages", value: "pages" },
76752
78146
  { label: "Settings", value: "settings" },
@@ -76771,6 +78165,10 @@ async function runMainMenu() {
76771
78165
  await runActivityView();
76772
78166
  home();
76773
78167
  break;
78168
+ case "report":
78169
+ await runKnowledgeReport();
78170
+ home();
78171
+ break;
76774
78172
  case "analytics":
76775
78173
  await runAnalyticsView();
76776
78174
  home();
@@ -76798,6 +78196,17 @@ async function runMainMenu() {
76798
78196
  }
76799
78197
  }
76800
78198
  }
78199
+ async function runKnowledgeReport() {
78200
+ const s = spinner2();
78201
+ s.start("Writing knowledge report...");
78202
+ try {
78203
+ const path3 = await emitKnowledgeReport({ open: true });
78204
+ s.stop(`Opened ${path3}`);
78205
+ } catch (err) {
78206
+ s.stop("Could not write the report");
78207
+ log2.error(err instanceof Error ? err.message : String(err));
78208
+ }
78209
+ }
76801
78210
  async function runSettings(cfg) {
76802
78211
  while (true) {
76803
78212
  const target = cfg.active_account?.target;
@@ -76925,6 +78334,7 @@ var init_tui = __esm(() => {
76925
78334
  init_config();
76926
78335
  init_agents();
76927
78336
  init_providers();
78337
+ init_generate();
76928
78338
  init_project_dir();
76929
78339
  init_scan();
76930
78340
  init_agents_md_step();
@@ -77204,7 +78614,7 @@ Could not open a browser automatically.`);
77204
78614
  throw new Error("Authentication timed out. Run 'dosu login' to try again.");
77205
78615
  }
77206
78616
  function sleep4(ms2) {
77207
- return new Promise((resolve6) => setTimeout(resolve6, ms2));
78617
+ return new Promise((resolve7) => setTimeout(resolve7, ms2));
77208
78618
  }
77209
78619
  var POLL_INTERVAL_MS = 5000;
77210
78620
  var init_device = __esm(() => {
@@ -77578,7 +78988,7 @@ import {
77578
78988
  constants as constants3,
77579
78989
  fstatSync,
77580
78990
  openSync as openSync5,
77581
- readFileSync as readFileSync24,
78991
+ readFileSync as readFileSync25,
77582
78992
  readSync as readSync4,
77583
78993
  unlinkSync as unlinkSync5
77584
78994
  } from "node:fs";
@@ -80708,6 +82118,7 @@ init_trpc();
80708
82118
  init_config();
80709
82119
  init_agents();
80710
82120
  init_formats();
82121
+ init_generate();
80711
82122
  init_backlog();
80712
82123
  init_detach();
80713
82124
  init_status();
@@ -80716,7 +82127,7 @@ init_watermark();
80716
82127
  var import_picocolors18 = __toESM(require_picocolors(), 1);
80717
82128
  import { existsSync as existsSync16 } from "node:fs";
80718
82129
  import { homedir as homedir5 } from "node:os";
80719
- import { delimiter as delimiter4, join as join18 } from "node:path";
82130
+ import { delimiter as delimiter4, join as join19 } from "node:path";
80720
82131
  function requireConfig7() {
80721
82132
  const cfg = requireLoginConfig();
80722
82133
  if (!cfg.active_account?.target?.org_id || !cfg.active_account?.target?.space_id) {
@@ -80823,7 +82234,7 @@ ${results.length - opts.limit} more results not shown.`));
80823
82234
  section("Mined", minedRows, "Mined at", "No mined sessions recorded yet.");
80824
82235
  }
80825
82236
  });
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) => {
82237
+ 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
82238
  if (opts.status) {
80828
82239
  const status = getSyncStatus();
80829
82240
  if (opts.json) {
@@ -80838,7 +82249,9 @@ ${results.length - opts.limit} more results not shown.`));
80838
82249
  "knowledge",
80839
82250
  "sync",
80840
82251
  ...opts.quiet ? ["--quiet"] : [],
80841
- ...opts.bootstrap ? ["--bootstrap"] : []
82252
+ ...opts.bootstrap ? ["--bootstrap"] : [],
82253
+ ...opts.report ? ["--report"] : [],
82254
+ ...opts.out ? ["--out", opts.out] : []
80842
82255
  ]);
80843
82256
  return;
80844
82257
  }
@@ -80859,12 +82272,42 @@ ${results.length - opts.limit} more results not shown.`));
80859
82272
  if (opts.quiet)
80860
82273
  return;
80861
82274
  if (opts.json) {
80862
- printResult(outcome, opts);
80863
82275
  if (outcome.status === "error")
80864
82276
  process.exitCode = 1;
82277
+ if (opts.report) {
82278
+ try {
82279
+ const report = await emitKnowledgeReport({ out: opts.out, open: false });
82280
+ printResult({ ...outcome, report }, opts);
82281
+ } catch (err) {
82282
+ const report_error = err instanceof Error ? err.message : String(err);
82283
+ printResult({ ...outcome, report_error }, opts);
82284
+ }
82285
+ } else {
82286
+ printResult(outcome, opts);
82287
+ }
80865
82288
  return;
80866
82289
  }
80867
82290
  printSyncOutcome(outcome);
82291
+ if (opts.report) {
82292
+ try {
82293
+ const report = await emitKnowledgeReport({ out: opts.out, open: true });
82294
+ console.log(`Wrote ${report}`);
82295
+ } catch (err) {
82296
+ console.error(err instanceof Error ? err.message : String(err));
82297
+ process.exitCode = 1;
82298
+ }
82299
+ }
82300
+ });
82301
+ 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) => {
82302
+ const path3 = await emitKnowledgeReport({
82303
+ out: opts.out,
82304
+ open: opts.json ? false : opts.open
82305
+ });
82306
+ if (opts.json) {
82307
+ printResult({ report: path3 }, opts);
82308
+ return;
82309
+ }
82310
+ console.log(`Wrote ${path3}`);
80868
82311
  });
80869
82312
  cmd.addCommand(hooksCommand());
80870
82313
  return cmd;
@@ -81082,7 +82525,7 @@ function reportHookFailure(agent, err) {
81082
82525
  }
81083
82526
  function dosuOnPath() {
81084
82527
  const bin = process.platform === "win32" ? "dosu.cmd" : "dosu";
81085
- return (process.env.PATH ?? "").split(delimiter4).some((dir) => dir !== "" && existsSync16(join18(dir, bin)));
82528
+ return (process.env.PATH ?? "").split(delimiter4).some((dir) => dir !== "" && existsSync16(join19(dir, bin)));
81086
82529
  }
81087
82530
 
81088
82531
  // src/commands/libraries.ts
@@ -81422,7 +82865,7 @@ init_dist();
81422
82865
  init_commander();
81423
82866
  init_trpc();
81424
82867
  var import_picocolors22 = __toESM(require_picocolors(), 1);
81425
- import { readFileSync as readFileSync16 } from "node:fs";
82868
+ import { readFileSync as readFileSync17 } from "node:fs";
81426
82869
  function requireConfig10() {
81427
82870
  return requireLoginConfig();
81428
82871
  }
@@ -81598,7 +83041,7 @@ function reviewCommand() {
81598
83041
  let body = opts.body;
81599
83042
  if (opts.bodyFile) {
81600
83043
  try {
81601
- body = readFileSync16(opts.bodyFile, "utf-8");
83044
+ body = readFileSync17(opts.bodyFile, "utf-8");
81602
83045
  } catch (err) {
81603
83046
  console.error(import_picocolors22.default.red(`Failed to read --body-file: ${err.message}`));
81604
83047
  process.exit(1);
@@ -82387,8 +83830,8 @@ async function finishTelemetry(operation) {
82387
83830
  try {
82388
83831
  await Promise.race([
82389
83832
  Promise.resolve().then(operation),
82390
- new Promise((resolve6) => {
82391
- timeout = setTimeout(resolve6, TELEMETRY_FLUSH_TIMEOUT_MS);
83833
+ new Promise((resolve7) => {
83834
+ timeout = setTimeout(resolve7, TELEMETRY_FLUSH_TIMEOUT_MS);
82392
83835
  })
82393
83836
  ]);
82394
83837
  } catch {} finally {
@@ -82742,7 +84185,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
82742
84185
  }
82743
84186
  if (opts.follow) {
82744
84187
  try {
82745
- const lines = readFileSync24(logPath, "utf-8").split(`
84188
+ const lines = readFileSync25(logPath, "utf-8").split(`
82746
84189
  `);
82747
84190
  if (lines.at(-1) === "")
82748
84191
  lines.pop();
@@ -82758,7 +84201,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
82758
84201
  if (opts.tail !== undefined) {
82759
84202
  const n5 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
82760
84203
  try {
82761
- const content = readFileSync24(logPath, "utf-8");
84204
+ const content = readFileSync25(logPath, "utf-8");
82762
84205
  const lines = content.split(`
82763
84206
  `);
82764
84207
  console.log(lines.slice(-n5).join(`
@@ -82829,4 +84272,4 @@ execute().catch((err) => {
82829
84272
  process.exit(1);
82830
84273
  });
82831
84274
 
82832
- //# debugId=858ed0b9-5f8c-23ca-6475-6e2164756e21
84275
+ //# debugId=d438e963-a417-e96d-6475-6e2164756e21