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

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 +164 -8
  2. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -5018,7 +5018,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
5018
5018
  function getVersionString() {
5019
5019
  return `v${VERSION}`;
5020
5020
  }
5021
- var VERSION = "0.53.0-beta.7", INSTALL_CHANNEL = "npm";
5021
+ var VERSION = "0.53.0-beta.9", INSTALL_CHANNEL = "npm";
5022
5022
 
5023
5023
  // src/debug/logger.ts
5024
5024
  import {
@@ -5156,7 +5156,7 @@ var init_logger = __esm(() => {
5156
5156
  });
5157
5157
 
5158
5158
  // src/client/contract.ts
5159
- var CLI_CONTRACT_HASH = "79ec0bbacf70";
5159
+ var CLI_CONTRACT_HASH = "d7c6f0f0e8d0";
5160
5160
 
5161
5161
  // src/client/trpc.ts
5162
5162
  var exports_trpc = {};
@@ -10678,6 +10678,134 @@ var init_sync = __esm(() => {
10678
10678
  init_watermark();
10679
10679
  });
10680
10680
 
10681
+ // src/report/backfill.ts
10682
+ function noteTime2(at) {
10683
+ const parsed = Date.parse(at ?? "");
10684
+ return Number.isNaN(parsed) ? Number.NaN : parsed;
10685
+ }
10686
+ function correlateBackfill(input) {
10687
+ const batches = new Map;
10688
+ for (const e of input.ledger) {
10689
+ const members = batches.get(e.at) ?? [];
10690
+ members.push(e.session);
10691
+ batches.set(e.at, members);
10692
+ }
10693
+ const batchTimes = [...batches.keys()].map((at) => ({ at, t: Date.parse(at) })).filter((b2) => !Number.isNaN(b2.t));
10694
+ const sessionByKey = new Map(input.sessions.map((s) => [`${s.harness}/${s.id}`, s]));
10695
+ const cyclesCache = new Map;
10696
+ const cyclesFor = (session) => {
10697
+ let cycles = cyclesCache.get(session.id);
10698
+ if (!cycles) {
10699
+ cycles = sessionCycles(sessionToDigest(session).turns);
10700
+ cyclesCache.set(session.id, cycles);
10701
+ }
10702
+ return cycles;
10703
+ };
10704
+ const mappings = [];
10705
+ let ambiguous = 0;
10706
+ let noBatch = 0;
10707
+ for (const note of input.notes) {
10708
+ const time = noteTime2(note.at);
10709
+ if (Number.isNaN(time)) {
10710
+ noBatch += 1;
10711
+ continue;
10712
+ }
10713
+ let batchAt;
10714
+ let bestD = Number.POSITIVE_INFINITY;
10715
+ for (const b2 of batchTimes) {
10716
+ const d = Math.abs(b2.t - time);
10717
+ if (d <= BATCH_TOLERANCE_MS && d < bestD) {
10718
+ bestD = d;
10719
+ batchAt = b2.at;
10720
+ }
10721
+ }
10722
+ if (!batchAt) {
10723
+ noBatch += 1;
10724
+ continue;
10725
+ }
10726
+ const members = (batches.get(batchAt) ?? []).map((k) => sessionByKey.get(k)).filter((s) => s !== undefined);
10727
+ if (members.length === 0) {
10728
+ noBatch += 1;
10729
+ continue;
10730
+ }
10731
+ const words = noteWords(note);
10732
+ let best;
10733
+ let second = 0;
10734
+ for (const session of members) {
10735
+ let score = 0;
10736
+ for (const cycle of cyclesFor(session)) {
10737
+ const s = cycleMatchScore(words, cycle.text);
10738
+ if (s > score)
10739
+ score = s;
10740
+ }
10741
+ if (!best || score > best.score) {
10742
+ second = best?.score ?? 0;
10743
+ best = { session, score };
10744
+ } else if (score > second) {
10745
+ second = score;
10746
+ }
10747
+ }
10748
+ if (!best || best.score < MIN_SCORE || second > 0 && best.score < second * CLEAR_WIN_RATIO) {
10749
+ ambiguous += 1;
10750
+ continue;
10751
+ }
10752
+ mappings.push({ note_id: note.id, transcript_id: best.session.id });
10753
+ }
10754
+ return { mappings, ambiguous, noBatch };
10755
+ }
10756
+ var BATCH_TOLERANCE_MS, CLEAR_WIN_RATIO = 1.5, MIN_SCORE = 3;
10757
+ var init_backfill = __esm(() => {
10758
+ init_digest();
10759
+ init_notes();
10760
+ BATCH_TOLERANCE_MS = 30 * 60 * 1000;
10761
+ });
10762
+
10763
+ // src/report/backfill-run.ts
10764
+ var exports_backfill_run = {};
10765
+ __export(exports_backfill_run, {
10766
+ runBackfill: () => runBackfill
10767
+ });
10768
+ async function defaultFetchUnattributed(cfg) {
10769
+ const orgId = cfg.active_account?.target?.org_id;
10770
+ if (!orgId || !cfg.active_account?.session.access_token) {
10771
+ throw new Error("Not signed in. Run `dosu setup` to connect an account first.");
10772
+ }
10773
+ const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
10774
+ const client = createTypedClient2(cfg);
10775
+ const { notes } = await client.notes.listMine.query({ org_id: orgId, limit: 500 });
10776
+ return notes.filter((n2) => !n2.transcript_id).map((n2) => ({ id: n2.id, title: n2.title, content: n2.body, at: n2.created_at }));
10777
+ }
10778
+ async function defaultApply(cfg, mappings) {
10779
+ const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
10780
+ const client = createTypedClient2(cfg);
10781
+ let updated = 0;
10782
+ for (let i2 = 0;i2 < mappings.length; i2 += MUTATION_CHUNK) {
10783
+ const chunk = mappings.slice(i2, i2 + MUTATION_CHUNK);
10784
+ const res = await client.notes.attributeTranscripts.mutate({ mappings: chunk });
10785
+ updated += res.updated;
10786
+ }
10787
+ return updated;
10788
+ }
10789
+ async function runBackfill(deps = {}) {
10790
+ const cfg = (deps.loadCfg ?? loadConfig)();
10791
+ const notes = await (deps.fetchUnattributed ?? defaultFetchUnattributed)(cfg);
10792
+ if (notes.length === 0) {
10793
+ return { candidates: 0, mappings: [], ambiguous: 0, noBatch: 0, updated: 0 };
10794
+ }
10795
+ const ledger = (deps.ledger ?? (() => loadSyncState().mined_sessions ?? []))();
10796
+ const sessions = (deps.scan ?? scanAgentSessions)();
10797
+ const { mappings, ambiguous, noBatch } = correlateBackfill({ notes, ledger, sessions });
10798
+ const updated = mappings.length > 0 ? await (deps.apply ?? defaultApply)(cfg, mappings) : 0;
10799
+ return { candidates: notes.length, mappings, ambiguous, noBatch, updated };
10800
+ }
10801
+ var MUTATION_CHUNK = 500;
10802
+ var init_backfill_run = __esm(() => {
10803
+ init_config();
10804
+ init_scan();
10805
+ init_watermark();
10806
+ init_backfill();
10807
+ });
10808
+
10681
10809
  // src/miner/config-dir.ts
10682
10810
  import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
10683
10811
  import { tmpdir as tmpdir2 } from "node:os";
@@ -10850,7 +10978,7 @@ function buildMinerPrompt(sessions) {
10850
10978
 
10851
10979
  ${list}
10852
10980
 
10853
- Read each session with read_session, decide what (if anything) is durable per your rules, dedupe against read_knowledge, then write the distilled notes with write_knowledge. When you are done, reply with a one-line summary: how many sessions you read and how many notes you wrote.`;
10981
+ Work through them ONE AT A TIME: read a single session with read_session, decide what (if anything) is durable per your rules, dedupe against read_knowledge, and write that session's notes with write_knowledge BEFORE moving on to the next session. Each note is attributed to the session you read just before writing it, so do not read the next session until you have written this one's notes. When you are done, reply with a one-line summary: how many sessions you read and how many notes you wrote.`;
10854
10982
  }
10855
10983
 
10856
10984
  // src/miner/prompt-core.ts
@@ -72798,7 +72926,8 @@ async function runMiner(options) {
72798
72926
  const abort = new AbortController;
72799
72927
  const timer = setTimeout(() => abort.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS2);
72800
72928
  let notesWritten = 0;
72801
- let lastSessionId;
72929
+ let currentSession;
72930
+ const readsSinceWrite = new Set;
72802
72931
  let turns = 0;
72803
72932
  const env = buildMinerEnv({
72804
72933
  apiKey: options.apiKey,
@@ -72843,7 +72972,11 @@ async function runMiner(options) {
72843
72972
  };
72844
72973
  }
72845
72974
  if (toolName === `mcp__${SESSIONS_SERVER_NAME}__read_session`) {
72846
- lastSessionId = sessionIdFromReadInput(input2) ?? lastSessionId;
72975
+ const id2 = sessionIdFromReadInput(input2);
72976
+ if (id2) {
72977
+ currentSession = id2;
72978
+ readsSinceWrite.add(id2);
72979
+ }
72847
72980
  }
72848
72981
  if (toolName === `mcp__${KNOWLEDGE_SERVER_NAME}__write_knowledge`) {
72849
72982
  if (notesWritten >= maxNotes) {
@@ -72852,10 +72985,20 @@ async function runMiner(options) {
72852
72985
  message: `Note cap reached (${maxNotes} per run); stop writing and summarize.`
72853
72986
  };
72854
72987
  }
72988
+ if (readsSinceWrite.size > 1) {
72989
+ readsSinceWrite.clear();
72990
+ currentSession = undefined;
72991
+ return {
72992
+ behavior: "deny",
72993
+ message: "Write each session's notes right after reading THAT session, before reading the next. You have read multiple sessions without writing, so a note cannot be attributed to one. Re-read only the session this note is about, then write it."
72994
+ };
72995
+ }
72855
72996
  notesWritten += 1;
72997
+ readsSinceWrite.clear();
72998
+ const { transcript_id: _authoredByModel, ...clean } = input2;
72856
72999
  return {
72857
73000
  behavior: "allow",
72858
- updatedInput: lastSessionId ? { ...input2, transcript_id: lastSessionId } : input2
73001
+ updatedInput: currentSession ? { ...clean, transcript_id: currentSession } : clean
72859
73002
  };
72860
73003
  }
72861
73004
  return { behavior: "allow", updatedInput: input2 };
@@ -73376,7 +73519,7 @@ var init_settings = __esm(() => {
73376
73519
  });
73377
73520
 
73378
73521
  // src/telemetry/debug-id.ts
73379
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "d438e963-a417-e96d-6475-6e2164756e21";
73522
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "971484cf-da0f-e0c4-6475-6e2164756e21";
73380
73523
 
73381
73524
  // src/telemetry/telemetry.ts
73382
73525
  import { randomUUID as randomUUID4 } from "node:crypto";
@@ -82309,6 +82452,19 @@ ${results.length - opts.limit} more results not shown.`));
82309
82452
  }
82310
82453
  console.log(`Wrote ${path3}`);
82311
82454
  });
82455
+ cmd.command("backfill-transcripts").description("One-shot: attribute pre-existing notes to the local sessions that produced them, so the report can show their traces").option("--json", "Output the result as JSON").action(async (opts) => {
82456
+ const { runBackfill: runBackfill2 } = await Promise.resolve().then(() => (init_backfill_run(), exports_backfill_run));
82457
+ const result = await runBackfill2();
82458
+ if (opts.json) {
82459
+ printResult(result, opts);
82460
+ return;
82461
+ }
82462
+ if (result.candidates === 0) {
82463
+ console.log("All your notes already have a transcript — nothing to backfill.");
82464
+ return;
82465
+ }
82466
+ console.log(`Attributed ${result.updated} of ${result.candidates} notes (${result.ambiguous} ambiguous, ${result.noBatch} without a local mining batch). Run 'dosu knowledge report' to see their traces.`);
82467
+ });
82312
82468
  cmd.addCommand(hooksCommand());
82313
82469
  return cmd;
82314
82470
  }
@@ -84272,4 +84428,4 @@ execute().catch((err) => {
84272
84428
  process.exit(1);
84273
84429
  });
84274
84430
 
84275
- //# debugId=d438e963-a417-e96d-6475-6e2164756e21
84431
+ //# debugId=971484cf-da0f-e0c4-6475-6e2164756e21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.53.0-beta.7",
3
+ "version": "0.53.0-beta.9",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",