@stitchdb/cli 0.7.3 → 0.7.4

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 +52 -28
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -490,9 +490,11 @@ async function cmdHook(args) {
490
490
  await handlePreReadHook(cfg, event, cwd || process.cwd()).catch(() => { });
491
491
  return;
492
492
  }
493
- // ── PostToolUse on Read: opportunistically save a fresh summary ───────
494
- if (eventName === 'PostToolUse' && event?.tool_name === 'Read') {
495
- await handlePostReadHook(cfg, event, cwd || process.cwd()).catch(() => { });
493
+ // ── PostToolUse on Read/Edit/Write/MultiEdit: keep the file-summary
494
+ // cache fresh. After an edit we re-hash the on-disk file and
495
+ // re-summarise, so a stale "old version" memory never lingers.
496
+ if (eventName === 'PostToolUse' && ['Read', 'Edit', 'Write', 'MultiEdit'].includes(String(event?.tool_name))) {
497
+ await handlePostFileChangeHook(cfg, event, cwd || process.cwd()).catch(() => { });
496
498
  return;
497
499
  }
498
500
  // ── SessionStart: self-heal hook config + inject prior context ─────────
@@ -735,13 +737,14 @@ function lastAssistantTextFromTranscript(transcriptPath) {
735
737
  const FILE_SUMMARY_MIN_BYTES = 500; // skip tiny files
736
738
  const FILE_SUMMARY_MAX_BYTES = 200_000; // skip absolutely huge files
737
739
  const FILE_SUMMARY_COOLDOWN_MS = 5 * 60_000; // per-file: don't re-summarize more than once per 5 min
738
- const FILE_SUMMARY_PROMPT = `You are summarising a single source file for an AI coding assistant cache.
740
+ const FILE_SUMMARY_PROMPT = `You are summarising a single source file for an AI coding assistant's persistent project cache. Other agents will read this summary before deciding whether to open the file in full, so it must enable a real mental model of the project — not just describe the file in isolation.
739
741
 
740
- Return ONLY a 2-3 sentence summary describing:
741
- What the file does (purpose, the few exported symbols / commands / endpoints worth knowing).
742
- Any important pattern, convention, or non-obvious behaviour another agent would benefit from before reading the file in full.
742
+ Return ONLY a 2-4 sentence summary that covers:
743
+ 1. PURPOSE what this file does, its key exported symbols / routes / commands / classes worth knowing, any non-obvious invariant or pattern.
744
+ 2. CONNECTIONS concrete other files/modules this depends on (real import paths, real symbol names) AND who/what typically depends on this. Use real names from the file content; don't generalise.
745
+ 3. WHEN TO OPEN — what kind of task forces an edit here vs a peek. (e.g. "edit this when adding a new auth provider; safe to skip for purely UI changes").
743
746
 
744
- Skip preamble. Skip "this file ". Be specific. No markdown fences. No prose around it.
747
+ Be dense and specific. No preamble. No "this file ...". No markdown fences. No prose around it. Plain text only.
745
748
 
746
749
  File path: {{PATH}}
747
750
 
@@ -820,47 +823,64 @@ async function handlePreReadHook(cfg, event, cwd) {
820
823
  const payload = { hookSpecificOutput: { hookEventName: 'PreToolUse', additionalContext: note } };
821
824
  process.stdout.write(JSON.stringify(payload));
822
825
  }
823
- // PostToolUse on Read: kick off a background `claude -p` to summarize this
824
- // file IF we don't already have a summary at the current hash. Fire-and-forget.
825
- async function handlePostReadHook(cfg, event, cwd) {
826
+ // PostToolUse handler for tools that observe-or-modify a single file
827
+ // (Read, Edit, Write, MultiEdit). Kicks off a background `claude -p` to
828
+ // (re-)summarise this file unless we already have an up-to-date cached
829
+ // summary for this exact hash. Fire-and-forget.
830
+ //
831
+ // Read flow: file content same as cache → no-op. Different → re-summarise.
832
+ // Edit flow: file content always different from cache → re-summarise. The
833
+ // existing summary is replaced by cmdSummarizeFile so no stale
834
+ // memory persists.
835
+ // Write/MultiEdit: same as Edit.
836
+ //
837
+ // A 30 s "min-interval" floor on top of the hash-keyed cooldown prevents a
838
+ // flurry of edits within seconds from spawning N parallel claude -p calls
839
+ // for the same file.
840
+ async function handlePostFileChangeHook(cfg, event, cwd) {
826
841
  const filePath = String(event?.tool_input?.file_path || '');
827
842
  if (!filePath)
828
843
  return;
829
844
  const rel = relPathFor(cwd, filePath);
830
- const content = readFileContentFromHookEvent(event);
831
- // Fall back to reading from disk if hook payload didn't carry content.
832
- let body = content;
833
- if (!body) {
834
- try {
835
- body = fs.readFileSync(filePath, 'utf8');
836
- }
837
- catch {
838
- return;
839
- }
845
+ // After Edit/Write the on-disk content is what we want; the hook payload
846
+ // may also carry it, but reading from disk is always correct.
847
+ let body = '';
848
+ try {
849
+ body = fs.readFileSync(filePath, 'utf8');
850
+ }
851
+ catch {
852
+ return;
840
853
  }
841
- if (!body || body.length < FILE_SUMMARY_MIN_BYTES)
854
+ if (!body)
855
+ return;
856
+ // Tiny files: not worth a summary. Huge files: skip; would exhaust context.
857
+ if (body.length < FILE_SUMMARY_MIN_BYTES)
842
858
  return;
843
859
  if (body.length > FILE_SUMMARY_MAX_BYTES)
844
860
  return;
845
861
  const fullHash = await sha256Hex(body);
846
862
  const hashPrefix = fullHash.slice(0, 16);
847
- // Per-file cooldown so a hot-edited file doesn't re-summarize on every read.
848
863
  const state = loadFileSummaryState();
849
864
  const last = state.files[rel];
865
+ // Hash-keyed dedupe: same content as last spawn → nothing to do.
850
866
  if (last && last.hash === hashPrefix && Date.now() - last.lastSummarizedAt < FILE_SUMMARY_COOLDOWN_MS)
851
867
  return;
868
+ // Min-interval floor: regardless of hash change, don't spawn more often
869
+ // than once per 30 s for the same file (catches edit flurries).
870
+ const MIN_RESPAWN_MS = 30_000;
871
+ if (last && Date.now() - last.lastSummarizedAt < MIN_RESPAWN_MS)
872
+ return;
873
+ // Optimisation: confirm the API view is also stale before spending tokens.
852
874
  const stitch = client(cfg);
853
875
  const cached = await lookupFileSummary(stitch, rel);
854
876
  if (cached && cached.hash === hashPrefix) {
855
- // Already summarized this exact version — refresh cooldown and bail.
856
877
  state.files[rel] = { hash: hashPrefix, lastSummarizedAt: Date.now() };
857
878
  saveFileSummaryState(state);
858
879
  return;
859
880
  }
860
- // Mark BEFORE spawning so a flurry of Read events doesn't fire N spawns.
881
+ // Mark BEFORE spawning so overlapping events don't fire N spawns.
861
882
  state.files[rel] = { hash: hashPrefix, lastSummarizedAt: Date.now() };
862
883
  saveFileSummaryState(state);
863
- // Spawn detached: stitch _summarize-file <abs-path> <hash> <rel>
864
884
  try {
865
885
  const cliPath = process.argv[1] || (await import('node:url')).fileURLToPath(import.meta.url);
866
886
  const child = spawn(process.argv[0], [cliPath, '_summarize-file', filePath, hashPrefix, rel], {
@@ -1688,13 +1708,17 @@ const STITCH_SESSION_START_HOOK = {
1688
1708
  matcher: '*',
1689
1709
  hooks: [{ type: 'command', command: 'stitch _hook SessionStart' }],
1690
1710
  };
1691
- // File summary cache: only fires for the Read tool, both pre and post.
1711
+ // File summary cache:
1712
+ // • PreToolUse fires only on Read (cached summary surfaces before the read).
1713
+ // • PostToolUse fires on Read, Edit, Write, MultiEdit — Edit/Write/MultiEdit
1714
+ // invalidate any prior summary so a stale "old version" memory never
1715
+ // lingers. The matcher is a regex (Claude Code accepts pipe alternation).
1692
1716
  const STITCH_PRE_READ_HOOK = {
1693
1717
  matcher: 'Read',
1694
1718
  hooks: [{ type: 'command', command: 'stitch _hook' }],
1695
1719
  };
1696
1720
  const STITCH_POST_READ_HOOK = {
1697
- matcher: 'Read',
1721
+ matcher: 'Read|Edit|Write|MultiEdit',
1698
1722
  hooks: [{ type: 'command', command: 'stitch _hook' }],
1699
1723
  };
1700
1724
  function mergeHook(existing, entry) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stitchdb/cli",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "description": "Stitch CLI — manage memory + run agents from your terminal",
5
5
  "type": "module",
6
6
  "bin": {