From 3843d119279ee95ab59cb798ce5d33a9a6728c95 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 21:10:51 +0000 Subject: [PATCH 1/2] feat: add AI summary escape hatch to skip irrelevant sessions Add a Relevance section to the AI summarization prompt that lets the AI flag sessions as "skip" when they contain no useful project knowledge (test chats, meta-conversations, tooling troubleshooting). When flagged, the session is still stored and summarized but: - Decisions, mistakes, and corrections are not ingested into knowledge - A skip_knowledge frontmatter field marks the session for filtering - Knowledge base rebuilds skip these sessions - ghost log shows a (skipped) indicator https://claude.ai/code/session_01FwyHciZ86DLhgvEH5Gi5Uu --- src/background.ts | 144 +++++++++++++++++++++++------------------- src/index.ts | 3 +- src/knowledge.ts | 20 +++++- src/session.ts | 2 +- src/summarize.test.ts | 79 +++++++++++++++++++++++ src/summarize.ts | 17 +++++ 6 files changed, 195 insertions(+), 70 deletions(-) diff --git a/src/background.ts b/src/background.ts index 7074718..f8c2fc0 100644 --- a/src/background.ts +++ b/src/background.ts @@ -12,6 +12,7 @@ import { SESSION_DIR } from "./paths.js"; import { indexSession } from "./qmd.js"; import { redactSecrets } from "./redact.js"; import { + addFrontmatterField, addTags, appendDecision, appendMistake, @@ -94,79 +95,90 @@ try { // 2. Extract tags, decisions, mistakes const sections = extractSections(summary); + // Check if AI flagged this session as not relevant for knowledge + if (sections.skipKnowledge) { + log("Session flagged as skip_knowledge by AI — skipping knowledge ingestion"); + // Mark in frontmatter so downstream consumers can filter + const currentContent = readFileSync(sessionPath, "utf8"); + const updated = addFrontmatterField(currentContent, "skip_knowledge", true); + writeFileSync(sessionPath, updated); + } + if (sections.tags.length > 0) { addTags(repoRoot, sessionId, sections.tags); log(`Tagged: ${sections.tags.join(", ")}`); } - // Read session data for context - const sessionContent = readFileSync(sessionPath, "utf8"); - const { frontmatter } = parseFrontmatter(sessionContent); - const modifiedFiles = extractModifiedFiles(sessionContent); - const commitSha = (frontmatter.base_commit as string) || "unknown"; - const sessionDate = sessionId.slice(0, 10); - - for (const decision of sections.decisions) { - const { title, description } = parseTitleDescription(decision.text); - if (isJunkEntry(title)) continue; - const files = decision.files.length > 0 ? decision.files : modifiedFiles.slice(0, 5); - appendDecision(repoRoot, { - title, - description, - sessionId, - commitSha, - files, - area: deriveArea(files), - date: sessionDate, - tried: decision.tried, - rule: decision.rule, - }); - } - if (sections.decisions.length > 0) { - log(`Logged ${sections.decisions.length} decision(s)`); - } - - for (const mistake of sections.mistakes) { - const { title, description } = parseTitleDescription(mistake.text); - if (isJunkEntry(title)) continue; - const files = mistake.files.length > 0 ? mistake.files : modifiedFiles.slice(0, 5); - appendMistake(repoRoot, { - title, - description, - sessionId, - commitSha, - files, - area: deriveArea(files), - date: sessionDate, - tried: mistake.tried, - rule: mistake.rule, - }); - } - if (sections.mistakes.length > 0) { - log(`Logged ${sections.mistakes.length} mistake(s)`); - } + if (!sections.skipKnowledge) { + // Read session data for context + const sessionContent = readFileSync(sessionPath, "utf8"); + const { frontmatter } = parseFrontmatter(sessionContent); + const modifiedFiles = extractModifiedFiles(sessionContent); + const commitSha = (frontmatter.base_commit as string) || "unknown"; + const sessionDate = sessionId.slice(0, 10); + + for (const decision of sections.decisions) { + const { title, description } = parseTitleDescription(decision.text); + if (isJunkEntry(title)) continue; + const files = decision.files.length > 0 ? decision.files : modifiedFiles.slice(0, 5); + appendDecision(repoRoot, { + title, + description, + sessionId, + commitSha, + files, + area: deriveArea(files), + date: sessionDate, + tried: decision.tried, + rule: decision.rule, + }); + } + if (sections.decisions.length > 0) { + log(`Logged ${sections.decisions.length} decision(s)`); + } - // Auto-detect corrections: files modified in consecutive turns - const corrections = detectCorrections(sessionContent); - if (corrections.length > 0) { - const fileCounts: Record = {}; - for (const c of corrections) { - fileCounts[c.file] = (fileCounts[c.file] || 0) + 1; + for (const mistake of sections.mistakes) { + const { title, description } = parseTitleDescription(mistake.text); + if (isJunkEntry(title)) continue; + const files = mistake.files.length > 0 ? mistake.files : modifiedFiles.slice(0, 5); + appendMistake(repoRoot, { + title, + description, + sessionId, + commitSha, + files, + area: deriveArea(files), + date: sessionDate, + tried: mistake.tried, + rule: mistake.rule, + }); } - for (const [file, count] of Object.entries(fileCounts)) { - if (count >= 2) { - appendMistake(repoRoot, { - title: `Repeated modifications to ${file}`, - description: `File was modified across multiple consecutive turns — may indicate the AI struggled with this file. Review session ${sessionId} for the correct approach.`, - sessionId, - commitSha, - files: [file], - area: deriveArea([file]), - date: sessionDate, - tried: [], - rule: "", - }); - log(`Auto-detected correction pattern for ${file}`); + if (sections.mistakes.length > 0) { + log(`Logged ${sections.mistakes.length} mistake(s)`); + } + + // Auto-detect corrections: files modified in consecutive turns + const corrections = detectCorrections(sessionContent); + if (corrections.length > 0) { + const fileCounts: Record = {}; + for (const c of corrections) { + fileCounts[c.file] = (fileCounts[c.file] || 0) + 1; + } + for (const [file, count] of Object.entries(fileCounts)) { + if (count >= 2) { + appendMistake(repoRoot, { + title: `Repeated modifications to ${file}`, + description: `File was modified across multiple consecutive turns — may indicate the AI struggled with this file. Review session ${sessionId} for the correct approach.`, + sessionId, + commitSha, + files: [file], + area: deriveArea([file]), + date: sessionDate, + tried: [], + rule: "", + }); + log(`Auto-detected correction pattern for ${file}`); + } } } } diff --git a/src/index.ts b/src/index.ts index 12ba6f8..f8eed3a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -327,7 +327,8 @@ if (import.meta.main) { const { frontmatter } = parseFrontmatter(content); const tags = (frontmatter.tags as string[]) || []; const tagStr = tags.length > 0 ? ` ${c.dim}[${tags.join(", ")}]${c.reset}` : ""; - console.log(`${c.cyan}${id}${c.reset} ${c.dim}${frontmatter.branch || ""}${c.reset}${tagStr}`); + const skipStr = frontmatter.skip_knowledge ? ` ${c.dim}(skipped)${c.reset}` : ""; + console.log(`${c.cyan}${id}${c.reset} ${c.dim}${frontmatter.branch || ""}${c.reset}${tagStr}${skipStr}`); } if (sessions.length > count) { console.log(`${c.dim}... and ${sessions.length - count} more${c.reset}`); diff --git a/src/knowledge.ts b/src/knowledge.ts index 38d87e0..747605a 100644 --- a/src/knowledge.ts +++ b/src/knowledge.ts @@ -4,7 +4,14 @@ import { checkClaude } from "./deps.js"; import { completedDir, decisionsPath, knowledgePath, mistakesPath, SESSION_DIR } from "./paths.js"; import { searchSessions } from "./qmd.js"; import type { KnowledgeEntry } from "./session.js"; -import { appendDecision, appendMistake, deriveArea, listCompletedSessions, parseKnowledgeEntries } from "./session.js"; +import { + appendDecision, + appendMistake, + deriveArea, + listCompletedSessions, + parseKnowledgeEntries, + parseFrontmatter, +} from "./session.js"; // ============================================================================= // Claude CLI Check @@ -66,17 +73,26 @@ export async function buildKnowledge(repoRoot: string): Promise { return; } - // Gather all session summaries + // Gather all session summaries (skip sessions flagged as not relevant) const summaries: string[] = []; + let skippedCount = 0; for (const id of sessions) { const path = join(completedDir(repoRoot), `${id}.md`); if (!existsSync(path)) continue; const content = readFileSync(path, "utf8"); + const { frontmatter } = parseFrontmatter(content); + if (frontmatter.skip_knowledge) { + skippedCount++; + continue; + } const summaryMatch = content.match(/## Summary\n([\s\S]*?)$/); if (summaryMatch) { summaries.push(`### Session ${id}\n${summaryMatch[1]!.trim()}`); } } + if (skippedCount > 0) { + console.log(`Skipped ${skippedCount} session(s) flagged as not relevant.`); + } if (summaries.length === 0) { console.log("No session summaries found. Run sessions first or wait for AI summarization."); diff --git a/src/session.ts b/src/session.ts index 8325bb9..d10ac2d 100644 --- a/src/session.ts +++ b/src/session.ts @@ -1027,7 +1027,7 @@ export function parseFrontmatter(content: string): { frontmatter: Record { + test("returns skipKnowledge=true when Relevance is 'skip'", () => { + const summary = `## Intent +Testing the AI tool. + +## Changes +- No real changes. + +## Decisions +None + +## Mistakes +None + +## Open Items +None + +## Relevance +skip + +## Tags +test`; + + const sections = extractSections(summary); + expect(sections.skipKnowledge).toBe(true); + }); + + test("returns skipKnowledge=false when Relevance is 'keep'", () => { + const summary = `## Intent +Migrate cart system. + +## Changes +- Updated fees. + +## Decisions +None + +## Mistakes +None + +## Open Items +None + +## Relevance +keep + +## Tags +area:cart`; + + const sections = extractSections(summary); + expect(sections.skipKnowledge).toBe(false); + }); + + test("returns skipKnowledge=false when Relevance section is missing", () => { + const summary = `## Intent +Quick fix. + +## Tags +fix`; + + const sections = extractSections(summary); + expect(sections.skipKnowledge).toBe(false); + }); + + test("handles case-insensitive 'Skip'", () => { + const summary = `## Intent +Test chat. + +## Relevance +Skip + +## Tags +test`; + + const sections = extractSections(summary); + expect(sections.skipKnowledge).toBe(true); + }); +}); + describe("extractMistakeEntries — junk filtering", () => { test("filters 'None' variations", () => { expect(extractMistakeEntries("## Mistakes\nNone")).toEqual([]); diff --git a/src/summarize.ts b/src/summarize.ts index 843cfe0..ee4a4d4 100644 --- a/src/summarize.ts +++ b/src/summarize.ts @@ -41,6 +41,14 @@ The Tried:, Files:, and Rule: lines are optional — omit if not applicable. ## Open Items Anything left unfinished or flagged for follow-up +## Relevance +Assess whether this session contains knowledge worth preserving for future project context. +Write "skip" on its own line if the session is NOT relevant — e.g. test/demo chats, conversations about +the AI itself, troubleshooting the tooling rather than the project, trivial or throwaway interactions, +or anything that would add noise rather than signal to the project knowledge base. +Write "keep" on its own line if the session contains useful project context (decisions, patterns, mistakes, etc.). +Default to "keep" if uncertain. + ## Tags Comma-separated topic tags inferred from the session content. Use namespace:value format where appropriate (e.g. area:cart, type:bug-fix).`; @@ -87,6 +95,7 @@ export interface ExtractedSections { intent: string; changes: string; openItems: string; + skipKnowledge: boolean; } /** Extract structured sections from an AI summary */ @@ -98,9 +107,17 @@ export function extractSections(summary: string): ExtractedSections { intent: extractNamedSection(summary, "Intent"), changes: extractNamedSection(summary, "Changes"), openItems: extractNamedSection(summary, "Open Items"), + skipKnowledge: extractSkipKnowledge(summary), }; } +/** Check if the AI flagged this session as not worth preserving */ +function extractSkipKnowledge(summary: string): boolean { + const section = extractNamedSection(summary, "Relevance"); + if (!section) return false; + return /^\s*skip\s*$/im.test(section); +} + /** Extract tags from the Tags section */ function extractTags(summary: string): string[] { const section = extractNamedSection(summary, "Tags"); From 8e7631a60a5b99ce2e70e4a1c056a6cedff84cff Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 16 Feb 2026 21:13:06 +0000 Subject: [PATCH 2/2] fix: sort imports alphabetically in knowledge.ts for biome lint https://claude.ai/code/session_01FwyHciZ86DLhgvEH5Gi5Uu --- src/knowledge.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knowledge.ts b/src/knowledge.ts index 747605a..c68a1f1 100644 --- a/src/knowledge.ts +++ b/src/knowledge.ts @@ -9,8 +9,8 @@ import { appendMistake, deriveArea, listCompletedSessions, - parseKnowledgeEntries, parseFrontmatter, + parseKnowledgeEntries, } from "./session.js"; // =============================================================================