diff --git a/bin/_claude-triage b/bin/_claude-triage index 651c6b2..938f7f2 100755 --- a/bin/_claude-triage +++ b/bin/_claude-triage @@ -196,15 +196,43 @@ async def main_async(args: argparse.Namespace) -> None: if not candidates: return - items: list[GenerationItem] = [] + # Cache key is (uuid, mtime) only — mtime is sufficient since a session's + # content can't change without its mtime advancing, and this lets us check + # the cache BEFORE reading the JSONL (the expensive part for a triage run + # over hundreds of sessions where most are unchanged). + cache = ResponseCache(CACHE_DIR) + template_id = "rclaude-triage-v1" + cached_results: list[dict] = [] + uncached: list[tuple[int, Path, str]] = [] for mtime, jsonl in candidates: + cache_key = f"{jsonl.stem}:{mtime}" + hit = None if args.refresh else cache.get(template_id, cache_key) + if hit is not None: + # Carry through identifying metadata in case the cached payload + # was written by an older script revision that didn't enrich. + cached_results.append({ + **hit, + "uuid": hit.get("uuid", jsonl.stem), + "mtime": hit.get("mtime", mtime), + }) + continue + uncached.append((mtime, jsonl, cache_key)) + if args.refresh: + for _, _, ck in uncached: + key_hash = cache._hash_key(template_id, ck) + path = cache._cache_path(template_id, key_hash) + if path.exists(): + try: path.unlink() + except OSError: pass + + items: list[GenerationItem] = [] + for mtime, jsonl, cache_key in uncached: cwd, first_user, transcript = compress_session(jsonl) if not cwd: continue - cache_key = f"{jsonl.stem}:{mtime}:{len(transcript)}" items.append( GenerationItem( - template_id="rclaude-triage-v1", + template_id=template_id, cache_key=cache_key, metadata={ "uuid": jsonl.stem, @@ -215,36 +243,26 @@ async def main_async(args: argparse.Namespace) -> None: }, ) ) - if not items: - return - cache = ResponseCache(CACHE_DIR) - if args.refresh: - for item in items: - key_hash = cache._hash_key(item.template_id, item.cache_key) - path = cache._cache_path(item.template_id, key_hash) - if path.exists(): - try: - path.unlink() - except OSError: - pass - - client = ClaudeClient(model=MODEL, max_concurrent=MAX_CONCURRENT) - try: - results = await run_batched( - client=client, - cache=cache, - items=items, - system_prompt=SYSTEM_PROMPT, - build_batch_prompt=build_prompt, - validate_result=validate, - enrich_result=enrich, - index_key="ref_index", - batch_size=BATCH_SIZE, - description="rclaude-triage", - ) - finally: - await client.close() + results = list(cached_results) + if items: + client = ClaudeClient(model=MODEL, max_concurrent=MAX_CONCURRENT) + try: + new_results = await run_batched( + client=client, + cache=cache, + items=items, + system_prompt=SYSTEM_PROMPT, + build_batch_prompt=build_prompt, + validate_result=validate, + enrich_result=enrich, + index_key="ref_index", + batch_size=BATCH_SIZE, + description="rclaude-triage", + ) + finally: + await client.close() + results.extend(new_results) def sort_key(r: dict) -> tuple[int, int]: try: