Skip to content

Commit bdbede5

Browse files
notkurtclaude
andcommitted
feat: add strategies/knowledge capture, mid-session writing, and dedup
Part A — Strategies & Knowledge capture: - Add strategies.md ledger with extractStrategyEntries(), appendStrategy(), getRelevantStrategies() and CLI/validation wiring - Add Knowledge section to SUMMARY_PROMPT with extractKnowledgeEntries(), appendKnowledge(), getRelevantKnowledge() - Enhance SUMMARY_PROMPT: format enforcement, broader decision criteria, user-question signal for "should we..."/"what if we..." patterns - Add isValidSummary() to reject unstructured summaries in background - Inject strategies + knowledge at session start via hooks - Include strategies in continuity block Part B — Unified knowledge format: - Rewrite buildKnowledge() for entry consolidation/dedup instead of free-form regeneration - Add migrateKnowledge() for auto-migrating old free-form knowledge.md - Update injectKnowledge() to render entries grouped by area - Update genesis() to produce structured entries - Include strategies in knowledge build and brief context Part C — Mid-session knowledge writing: - Add ghost decision/mistake/strategy/knowledge CLI commands for persisting entries during a session (not just at session end) - Add isDuplicateEntry() and dedup option to all append helpers - Pass dedup flag in background extraction to prevent double-writes - Update Ghost briefing to instruct Claude when to call each command - Update CLAUDE.md header with mid-session command table Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 884d4ae commit bdbede5

11 files changed

Lines changed: 856 additions & 179 deletions

File tree

src/background.ts

Lines changed: 107 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ import {
1515
addFrontmatterField,
1616
addTags,
1717
appendDecision,
18+
appendKnowledge,
1819
appendMistake,
20+
appendStrategy,
1921
checkpoint,
2022
deriveArea,
2123
detectCorrections,
2224
extractModifiedFiles,
2325
parseFrontmatter,
2426
} from "./session.js";
25-
import { extractSections, summarize } from "./summarize.js";
27+
import { extractSections, isValidSummary, summarize } from "./summarize.js";
2628

2729
const [repoRoot, sessionPath, sessionId] = process.argv.slice(2) as [string, string, string];
2830

@@ -92,7 +94,16 @@ try {
9294
log("Summarization complete, appending to session file");
9395
appendFileSync(sessionPath, `\n## Summary\n\n${summary}\n`);
9496

95-
// 2. Extract tags, decisions, mistakes
97+
// Validate summary format
98+
const validFormat = isValidSummary(summary);
99+
if (!validFormat) {
100+
log("Summary has unstructured format — marking in frontmatter, skipping knowledge extraction");
101+
const currentContent = readFileSync(sessionPath, "utf8");
102+
const updated = addFrontmatterField(currentContent, "summary_format", "unstructured");
103+
writeFileSync(sessionPath, updated);
104+
}
105+
106+
// 2. Extract tags, decisions, mistakes, strategies, knowledge
96107
const sections = extractSections(summary);
97108

98109
// Check if AI flagged this session as not relevant for knowledge
@@ -109,7 +120,7 @@ try {
109120
log(`Tagged: ${sections.tags.join(", ")}`);
110121
}
111122

112-
if (!sections.skipKnowledge) {
123+
if (!sections.skipKnowledge && validFormat) {
113124
// Read session data for context
114125
const sessionContent = readFileSync(sessionPath, "utf8");
115126
const { frontmatter } = parseFrontmatter(sessionContent);
@@ -121,37 +132,93 @@ try {
121132
const { title, description } = parseTitleDescription(decision.text);
122133
if (isJunkEntry(title)) continue;
123134
const files = decision.files.length > 0 ? decision.files : modifiedFiles.slice(0, 5);
124-
appendDecision(repoRoot, {
125-
title,
126-
description,
127-
sessionId,
128-
commitSha,
129-
files,
130-
area: deriveArea(files),
131-
date: sessionDate,
132-
tried: decision.tried,
133-
rule: decision.rule,
134-
});
135+
appendDecision(
136+
repoRoot,
137+
{
138+
title,
139+
description,
140+
sessionId,
141+
commitSha,
142+
files,
143+
area: deriveArea(files),
144+
date: sessionDate,
145+
tried: decision.tried,
146+
rule: decision.rule,
147+
},
148+
{ dedup: true },
149+
);
135150
}
136151
if (sections.decisions.length > 0) {
137152
log(`Logged ${sections.decisions.length} decision(s)`);
138153
}
139154

155+
for (const strategy of sections.strategies) {
156+
const { title, description } = parseTitleDescription(strategy.text);
157+
if (isJunkEntry(title)) continue;
158+
const files = strategy.files.length > 0 ? strategy.files : modifiedFiles.slice(0, 5);
159+
appendStrategy(
160+
repoRoot,
161+
{
162+
title,
163+
description,
164+
sessionId,
165+
commitSha,
166+
files,
167+
area: deriveArea(files),
168+
date: sessionDate,
169+
tried: strategy.tried,
170+
rule: strategy.rule,
171+
},
172+
{ dedup: true },
173+
);
174+
}
175+
if (sections.strategies.length > 0) {
176+
log(`Logged ${sections.strategies.length} strategy(ies)`);
177+
}
178+
179+
for (const knowledge of sections.knowledge) {
180+
const { title, description } = parseTitleDescription(knowledge.text);
181+
if (isJunkEntry(title)) continue;
182+
const files = knowledge.files.length > 0 ? knowledge.files : modifiedFiles.slice(0, 5);
183+
appendKnowledge(
184+
repoRoot,
185+
{
186+
title,
187+
description,
188+
sessionId,
189+
commitSha,
190+
files,
191+
area: deriveArea(files),
192+
date: sessionDate,
193+
tried: knowledge.tried,
194+
rule: knowledge.rule,
195+
},
196+
{ dedup: true },
197+
);
198+
}
199+
if (sections.knowledge.length > 0) {
200+
log(`Logged ${sections.knowledge.length} knowledge entry(ies)`);
201+
}
202+
140203
for (const mistake of sections.mistakes) {
141204
const { title, description } = parseTitleDescription(mistake.text);
142205
if (isJunkEntry(title)) continue;
143206
const files = mistake.files.length > 0 ? mistake.files : modifiedFiles.slice(0, 5);
144-
appendMistake(repoRoot, {
145-
title,
146-
description,
147-
sessionId,
148-
commitSha,
149-
files,
150-
area: deriveArea(files),
151-
date: sessionDate,
152-
tried: mistake.tried,
153-
rule: mistake.rule,
154-
});
207+
appendMistake(
208+
repoRoot,
209+
{
210+
title,
211+
description,
212+
sessionId,
213+
commitSha,
214+
files,
215+
area: deriveArea(files),
216+
date: sessionDate,
217+
tried: mistake.tried,
218+
rule: mistake.rule,
219+
},
220+
{ dedup: true },
221+
);
155222
}
156223
if (sections.mistakes.length > 0) {
157224
log(`Logged ${sections.mistakes.length} mistake(s)`);
@@ -166,17 +233,21 @@ try {
166233
}
167234
for (const [file, count] of Object.entries(fileCounts)) {
168235
if (count >= 2) {
169-
appendMistake(repoRoot, {
170-
title: `Repeated modifications to ${file}`,
171-
description: `File was modified across multiple consecutive turns — may indicate the AI struggled with this file. Review session ${sessionId} for the correct approach.`,
172-
sessionId,
173-
commitSha,
174-
files: [file],
175-
area: deriveArea([file]),
176-
date: sessionDate,
177-
tried: [],
178-
rule: "",
179-
});
236+
appendMistake(
237+
repoRoot,
238+
{
239+
title: `Repeated modifications to ${file}`,
240+
description: `File was modified across multiple consecutive turns — may indicate the AI struggled with this file. Review session ${sessionId} for the correct approach.`,
241+
sessionId,
242+
commitSha,
243+
files: [file],
244+
area: deriveArea([file]),
245+
date: sessionDate,
246+
tried: [],
247+
rule: "",
248+
},
249+
{ dedup: true },
250+
);
180251
log(`Auto-detected correction pattern for ${file}`);
181252
}
182253
}

src/hooks.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import {
1616
generateContinuityBlock,
1717
getCoModifiedFiles,
1818
getRelevantDecisions,
19+
getRelevantKnowledge,
1920
getRelevantMistakes,
21+
getRelevantStrategies,
2022
} from "./session.js";
2123

2224
// =============================================================================
@@ -77,15 +79,31 @@ export async function handleSessionStart(input: SessionStartInput): Promise<stri
7779
// Non-critical — skip silently
7880
}
7981

80-
// 4. Inject relevant decisions
82+
// 4. Inject relevant strategies
83+
try {
84+
const strategies = getRelevantStrategies(root, relevantFiles);
85+
if (strategies) parts.push(strategies);
86+
} catch {
87+
// Non-critical — skip silently
88+
}
89+
90+
// 5. Inject relevant decisions
8191
try {
8292
const decisions = getRelevantDecisions(root, relevantFiles);
8393
if (decisions) parts.push(decisions);
8494
} catch {
8595
// Non-critical — skip silently
8696
}
8797

88-
// 5. Inject co-modified file warnings
98+
// 6. Inject relevant knowledge
99+
try {
100+
const knowledge = getRelevantKnowledge(root, relevantFiles);
101+
if (knowledge) parts.push(knowledge);
102+
} catch {
103+
// Non-critical — skip silently
104+
}
105+
106+
// 7. Inject co-modified file warnings
89107
try {
90108
const graph = buildCoModGraph(root);
91109
const coMod = getCoModifiedFiles(graph, relevantFiles, 10);
@@ -98,7 +116,7 @@ export async function handleSessionStart(input: SessionStartInput): Promise<stri
98116
// Non-critical — skip silently
99117
}
100118

101-
// 6. Always inject Ghost briefing so Claude understands how to work with Ghost
119+
// 8. Always inject Ghost briefing so Claude understands how to work with Ghost
102120
parts.push(
103121
`> **Ghost is recording this session.** Prompts, file changes, and decisions are captured automatically.
104122
> At session end, Ghost extracts decisions, mistakes, and open items from your summary and stores them
@@ -108,6 +126,13 @@ export async function handleSessionStart(input: SessionStartInput): Promise<stri
108126
> per-session. Writing to CLAUDE.md bypasses Ghost's relevance scoring and creates stale, bloated context.
109127
> If asked to document something, put it where it belongs: code comments, README, or dedicated docs.
110128
>
129+
> **Persist important knowledge mid-session** — don't wait for session end:
130+
> - \`ghost decision "Title: reasoning"\` — When the user makes a technical decision
131+
> - \`ghost mistake "Title: what went wrong"\` — When something fails or the user flags a mistake
132+
> - \`ghost knowledge "Title: what was learned"\` — When you confirm how something works or learn a pattern
133+
> - \`ghost strategy "Title: approaches considered"\` — When trade-offs or alternatives are discussed
134+
> Write entries as they happen so knowledge survives context compaction. Use \`Title: detail\` format.
135+
>
111136
> **ALWAYS search Ghost before reading code or grepping.** When a user asks about a feature, bug, scenario,
112137
> or component — your FIRST action must be searching Ghost, not the codebase. Past sessions contain
113138
> architecture decisions, dead ends, failed approaches, and reasoning that code cannot reveal.

0 commit comments

Comments
 (0)