@stitchdb/cli 0.8.0 → 0.10.0

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/dist/cli.js +57 -10
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -628,6 +628,7 @@ async function cmdHook(args) {
628
628
  try {
629
629
  const stitch = client(cfg);
630
630
  const projectTag = (threadName.split('/')[0] || threadName).toLowerCase();
631
+ const baseUrl = cfg.baseUrl || 'https://db.stitchdb.com';
631
632
  const [thread, memHits, workspaces, fileSummaries, aboutMems] = await Promise.all([
632
633
  stitch.thread(threadName).recall({ last: 5 }).catch(() => ({ thread_id: '', recent: [], semantic: [] })),
633
634
  stitch.recall(projectTag, { k: 8 }).catch(() => []),
@@ -635,10 +636,26 @@ async function cmdHook(args) {
635
636
  stitch.list({ limit: 12 }).then((all) => all.filter((m) => m.tags.some((t) => t.startsWith('file:')))).catch(() => []),
636
637
  stitch.list({ tag: 'workspace:about', limit: 1 }).catch(() => []),
637
638
  ]);
638
- const currentWs = Array.isArray(workspaces) && workspaces.length > 0 ? workspaces[0] : null;
639
+ // Look up the current workspace using the client's resolved id; fall
640
+ // back to first if resolveWorkspace failed (shouldn't normally).
641
+ const currentWsId = await stitch.resolveWorkspace().catch(() => null);
642
+ const currentWs = (Array.isArray(workspaces) ? workspaces : [])
643
+ .find((w) => w.id === currentWsId) || (Array.isArray(workspaces) ? workspaces[0] : null);
644
+ // Cross-project user-level memories from the special `_global` workspace.
645
+ // Pull top 8 most-recent — these are user preferences / rules that apply
646
+ // EVERYWHERE, regardless of which project this session is in.
647
+ let globalMems = [];
648
+ try {
649
+ const globalWs = (Array.isArray(workspaces) ? workspaces : []).find((w) => w.name === '_global');
650
+ if (globalWs) {
651
+ const globalClient = new Stitch({ apiKey: cfg.apiKey, baseUrl, workspace: globalWs.id });
652
+ globalMems = await globalClient.list({ limit: 8 }).catch(() => []);
653
+ }
654
+ }
655
+ catch { /* ignore */ }
639
656
  const lines = [];
640
657
  lines.push('<stitch-context>');
641
- lines.push(`Project: ${threadName} · Workspace: ${currentWs?.name || '(unknown)'} · Stitch MCP tools: recall, remember, thread_recall, thread_append, workspace_setup, file_summary, file_summary_save.`);
658
+ lines.push(`Project: ${threadName} · Workspace: ${currentWs?.name || '(unknown)'} · Stitch MCP tools: recall, remember, recall_global, remember_global, thread_recall, thread_append, workspace_setup, file_summary, file_summary_save.`);
642
659
  lines.push('');
643
660
  // Nudge the AI to set a meaningful workspace name once.
644
661
  if (currentWs?.name === 'default') {
@@ -646,6 +663,15 @@ async function cmdHook(args) {
646
663
  lines.push('Call the `workspace_setup` MCP tool with a slug-style name based on this project (e.g. the package.json name or repo dir).');
647
664
  lines.push('');
648
665
  }
666
+ // Global preferences first — they should bias everything else.
667
+ if (globalMems.length > 0) {
668
+ lines.push('### User-level rules & preferences (apply across all projects)');
669
+ for (const m of globalMems) {
670
+ const txt = String(m.content || '').replace(/\n+/g, ' ').slice(0, 350);
671
+ lines.push(`- **[${m.kind}]** ${txt}`);
672
+ }
673
+ lines.push('');
674
+ }
649
675
  if (Array.isArray(aboutMems) && aboutMems.length > 0) {
650
676
  lines.push('### About this workspace');
651
677
  lines.push(String(aboutMems[0].content || '').slice(0, 400));
@@ -657,7 +683,13 @@ async function cmdHook(args) {
657
683
  for (const m of sortedMems) {
658
684
  const isAuto = Array.isArray(m.tags) && m.tags.includes('auto');
659
685
  const txt = String(m.content || '').replace(/\n+/g, ' ').slice(0, 350);
660
- lines.push(`- **[${m.kind}${isAuto ? '·auto' : ''}]** ${txt}`);
686
+ // Source receipt: when this memory came from a distilled thread,
687
+ // mark the source so the agent knows it can call thread_recall on
688
+ // that thread for verbatim context behind the fact.
689
+ const src = m.source_thread_id
690
+ ? ` _(from thread, ${m.source_turn_ids?.length || 0} turns)_`
691
+ : '';
692
+ lines.push(`- **[${m.kind}${isAuto ? '·auto' : ''}]** ${txt}${src}`);
661
693
  }
662
694
  lines.push('');
663
695
  }
@@ -679,7 +711,7 @@ async function cmdHook(args) {
679
711
  }
680
712
  lines.push('');
681
713
  }
682
- lines.push('Call `recall` for deeper search, `thread_recall` for older turns, `file_summary` BEFORE reading any non-trivial file (saves tokens).');
714
+ lines.push('Call `recall` for project memory, `recall_global` for cross-project user prefs, `thread_recall` for older turns, `file_summary` BEFORE reading any non-trivial file. Save user-level habits/preferences with `remember_global`; project facts with `remember`.');
683
715
  lines.push('</stitch-context>');
684
716
  process.stdout.write(lines.join('\n'));
685
717
  }
@@ -1054,7 +1086,6 @@ async function cmdSummarizeFile(args) {
1054
1086
  return;
1055
1087
  try {
1056
1088
  const stitch = client(cfg);
1057
- const projectTag = (inferThread() || 'default').split('/')[0];
1058
1089
  // Replace any prior summary for this path.
1059
1090
  const prior = await stitch.list({ tag: `file:${rel}`, limit: 50 }).catch(() => []);
1060
1091
  for (const p of prior) {
@@ -1063,9 +1094,13 @@ async function cmdSummarizeFile(args) {
1063
1094
  }
1064
1095
  catch { }
1065
1096
  }
1097
+ // No project:* tag — the workspace itself is the project identity now.
1098
+ // Adding a project tag based on inferThread() is the bug that caused
1099
+ // cross-workspace contamination when the workspace pin and the thread
1100
+ // pin disagreed.
1066
1101
  await stitch.remember(summary, {
1067
1102
  kind: 'snippet',
1068
- tags: [`file:${rel}`, `hash:${hashPrefix}`, 'auto:file-summary', `project:${projectTag}`],
1103
+ tags: [`file:${rel}`, `hash:${hashPrefix}`, 'auto:file-summary'],
1069
1104
  });
1070
1105
  }
1071
1106
  catch { /* silent — never break a session */ }
@@ -1209,13 +1244,25 @@ async function cmdDistill(args) {
1209
1244
  bumpDistillCooldown(thread);
1210
1245
  return;
1211
1246
  }
1212
- // Push each to Stitch as a memory with auto:true tag.
1213
- const projectTag = thread.split('/')[0] || thread;
1247
+ // Push each to Stitch as a memory with auto:true tag, plus receipts:
1248
+ // the source thread + the IDs of the turns this distill batch covered,
1249
+ // and a short excerpt from the most recent user turn so the dashboard /
1250
+ // recall surface can preview the conversational context behind the fact
1251
+ // without an extra round-trip.
1252
+ const sourceTurnIds = recall.recent.map((t) => t.id);
1253
+ const lastUserTurn = [...recall.recent].reverse().find((t) => t.role === 'user');
1254
+ const sourceExcerpt = lastUserTurn ? String(lastUserTurn.content || '').slice(0, 300) : undefined;
1214
1255
  let saved = 0;
1215
1256
  for (const m of memories) {
1216
1257
  try {
1217
- const tags = ['auto', 'auto:distill', `thread:${thread}`, `project:${projectTag}`, ...(m.tags || [])];
1218
- await stitch.remember(m.content, { kind: m.kind, tags });
1258
+ const tags = ['auto', 'auto:distill', `thread:${thread}`, ...(m.tags || [])];
1259
+ await stitch.remember(m.content, {
1260
+ kind: m.kind,
1261
+ tags,
1262
+ source_thread_id: recall.thread_id,
1263
+ source_turn_ids: sourceTurnIds,
1264
+ source_excerpt: sourceExcerpt,
1265
+ });
1219
1266
  saved++;
1220
1267
  }
1221
1268
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stitchdb/cli",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Stitch CLI — manage memory + run agents from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,7 @@
16
16
  "license": "MIT",
17
17
  "engines": { "node": ">=20" },
18
18
  "dependencies": {
19
- "@stitchdb/agent": "^0.2.0"
19
+ "@stitchdb/agent": "^0.3.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "typescript": "^5.4.0",