@@ -12,10 +12,11 @@ import { EmbeddingService } from './services/embedding';
1212import { SearchEngine } from './services/search' ;
1313import { LLMOrchestrator } from './services/llm' ;
1414import { RestorationEngine } from './services/restoration' ;
15+ import { HistoryCodeLensProvider } from './services/codeLens' ;
1516import { ChatParticipant } from './chat/participant' ;
1617import { WebviewProvider } from './webview/provider' ;
1718import { logger , LogLevel } from './utils/logger' ;
18- import { generateWorkspaceId } from './utils' ;
19+ import { generateWorkspaceId , getRelativePath } from './utils' ;
1920import { eventEmitter } from './utils/events' ;
2021import { secrets } from './services/secrets' ;
2122import { LOCAL_EMBEDDING_DIMENSIONS } from './services/localEmbedding' ;
@@ -36,6 +37,7 @@ let embeddingService: EmbeddingService;
3637let searchEngine : SearchEngine ;
3738let llmOrchestrator : LLMOrchestrator ;
3839let restorationEngine : RestorationEngine ;
40+ let codeLensProvider : HistoryCodeLensProvider | undefined ;
3941let _chatParticipant : ChatParticipant ;
4042let webviewProvider : WebviewProvider ;
4143let currentSession : Session | null = null ;
@@ -77,8 +79,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
7779 context . subscriptions . push ( outputChannel ) ;
7880 logger . initialize ( outputChannel ) ;
7981
80- // Enable debug logging to help diagnose issues
81- logger . setLevel ( LogLevel . DEBUG ) ;
82+ // Default to INFO; set codeHistorian.debug to surface DEBUG diagnostics.
83+ const debugEnabled = vscode . workspace . getConfiguration ( EXTENSION_ID ) . get < boolean > ( 'debug' , false ) ;
84+ logger . setLevel ( debugEnabled ? LogLevel . DEBUG : LogLevel . INFO ) ;
8285
8386 logger . info ( 'Activating Code Historian extension' ) ;
8487
@@ -228,22 +231,32 @@ async function initializeServices(context: vscode.ExtensionContext): Promise<voi
228231 // Initialize restoration engine
229232 restorationEngine = new RestorationEngine ( workspaceRoot , metadataDb , storagePath ) ;
230233
234+ // Register the inline-history CodeLens provider.
235+ codeLensProvider = new HistoryCodeLensProvider ( metadataDb , workspaceId , workspaceRoot ) ;
236+ context . subscriptions . push (
237+ vscode . languages . registerCodeLensProvider ( { scheme : 'file' } , codeLensProvider ) ,
238+ codeLensProvider
239+ ) ;
240+
231241 // Initialize capture engine
232242 const captureConfig = getCaptureConfig ( ) ;
233243 captureEngine = new CaptureEngine ( context , workspaceRoot , metadataDb , captureConfig ) ;
234244
235- // Connect capture engine to embedding service via events
236- // When a change is captured, process it to generate embeddings
245+ // Connect capture engine to embedding service via events.
246+ // We embed on CHANGES_FLUSHED (after the rows are inserted) rather than on
247+ // CHANGE_CAPTURED. Embedding earlier would call updateEmbeddingId() before the
248+ // row exists, leaving embedding_id NULL and causing the change to be
249+ // re-embedded (a duplicate vector) on every restart.
237250 context . subscriptions . push (
238- eventEmitter . on ( EVENTS . CHANGE_CAPTURED , async change => {
251+ eventEmitter . on ( EVENTS . CHANGES_FLUSHED , async ( changes : ChangeRecord [ ] ) => {
239252 try {
240- // Only process if embedding service is configured
241253 if ( embeddingService . isConfigured ( ) ) {
242- await embeddingService . processChange ( change ) ;
243- logger . debug ( `Generated embedding for change: ${ change . id } ` ) ;
254+ await embeddingService . processChanges ( changes ) ;
255+ // Refresh CodeLens so new history shows up inline.
256+ codeLensProvider ?. refresh ( ) ;
244257 }
245258 } catch ( error ) {
246- logger . warn ( ` Failed to generate embedding for change ${ change . id } :` , error as Error ) ;
259+ logger . warn ( ' Failed to generate embeddings for flushed changes:' , error as Error ) ;
247260 // Don't throw - embedding failure shouldn't block capture
248261 }
249262 } )
@@ -306,19 +319,17 @@ function registerCommands(context: vscode.ExtensionContext): void {
306319 return ;
307320 }
308321
309- const changes = metadataDb . getChanges (
310- workspaceId ,
311- {
312- filePatterns : [ fileUri . fsPath ] ,
313- } ,
314- 100
315- ) ;
322+ const workspaceRoot = vscode . workspace . workspaceFolders ?. [ 0 ] ?. uri . fsPath || '' ;
323+ const relativePath = getRelativePath ( fileUri . fsPath , workspaceRoot ) ;
324+ const changes = metadataDb . getChangesForFile ( workspaceId , relativePath , 200 ) ;
316325
317326 if ( changes . length === 0 ) {
318327 vscode . window . showInformationMessage ( 'No history found for this file' ) ;
319328 return ;
320329 }
321330
331+ await vscode . commands . executeCommand ( 'workbench.view.extension.codeHistorian' ) ;
332+
322333 // Show results in webview
323334 webviewProvider . postMessage ( {
324335 type : 'searchResults' ,
@@ -415,22 +426,27 @@ function registerCommands(context: vscode.ExtensionContext): void {
415426
416427 // Show symbol history command — time-travel a single function/class/variable
417428 context . subscriptions . push (
418- vscode . commands . registerCommand ( COMMANDS . SHOW_SYMBOL_HISTORY , async ( ) => {
419- // Default to the word under the cursor, if any.
420- const editor = vscode . window . activeTextEditor ;
421- let defaultSymbol = '' ;
422- if ( editor ) {
423- const range = editor . document . getWordRangeAtPosition ( editor . selection . active ) ;
424- if ( range ) {
425- defaultSymbol = editor . document . getText ( range ) ;
429+ vscode . commands . registerCommand ( COMMANDS . SHOW_SYMBOL_HISTORY , async ( symbolArg ?: string ) => {
430+ let symbol = typeof symbolArg === 'string' ? symbolArg : undefined ;
431+
432+ // No argument (invoked from palette/context menu): prompt, defaulting to
433+ // the word under the cursor.
434+ if ( ! symbol ) {
435+ const editor = vscode . window . activeTextEditor ;
436+ let defaultSymbol = '' ;
437+ if ( editor ) {
438+ const range = editor . document . getWordRangeAtPosition ( editor . selection . active ) ;
439+ if ( range ) {
440+ defaultSymbol = editor . document . getText ( range ) ;
441+ }
426442 }
427- }
428443
429- const symbol = await vscode . window . showInputBox ( {
430- prompt : 'Show history for symbol (function, class, or variable name)' ,
431- placeHolder : 'e.g. parseConfig' ,
432- value : defaultSymbol ,
433- } ) ;
444+ symbol = await vscode . window . showInputBox ( {
445+ prompt : 'Show history for symbol (function, class, or variable name)' ,
446+ placeHolder : 'e.g. parseConfig' ,
447+ value : defaultSymbol ,
448+ } ) ;
449+ }
434450
435451 if ( ! symbol ) {
436452 return ;
@@ -637,6 +653,24 @@ async function handleWebviewMessage(message: WebviewToExtensionMessage): Promise
637653
638654 if ( result . success ) {
639655 await webviewProvider . sendToast ( 'success' , `Restored ${ result . linesRestored } line(s)` ) ;
656+ codeLensProvider ?. refresh ( ) ;
657+
658+ // Offer a one-click Undo via a native notification when a backup
659+ // was created.
660+ if ( result . backupId ) {
661+ const backupId = result . backupId ;
662+ void vscode . window
663+ . showInformationMessage ( `Restored ${ result . linesRestored } line(s).` , 'Undo' )
664+ . then ( async choice => {
665+ if ( choice === 'Undo' ) {
666+ const undone = await restorationEngine . undoRestoration ( backupId ) ;
667+ await webviewProvider . sendToast (
668+ undone ? 'info' : 'error' ,
669+ undone ? 'Restore undone' : 'Could not undo restore'
670+ ) ;
671+ }
672+ } ) ;
673+ }
640674 } else {
641675 await webviewProvider . sendToast ( 'error' , `Restoration failed: ${ result . error } ` ) ;
642676 }
@@ -647,18 +681,39 @@ async function handleWebviewMessage(message: WebviewToExtensionMessage): Promise
647681 break ;
648682 }
649683
684+ case 'compareWithCurrent' : {
685+ // Reuse the restoration preview, which opens VS Code's native diff
686+ // editor (current file ↔ the file as it was before this change).
687+ try {
688+ await restorationEngine . previewRestoration ( message . data . changeId ) ;
689+ } catch ( error ) {
690+ await webviewProvider . sendToast (
691+ 'error' ,
692+ `Cannot compare: ${ ( error as Error ) . message } `
693+ ) ;
694+ }
695+ break ;
696+ }
697+
698+ case 'openDiff' : {
699+ try {
700+ await restorationEngine . openDiff ( message . data . changeId ) ;
701+ } catch ( error ) {
702+ await webviewProvider . sendToast ( 'error' , `Cannot open diff: ${ ( error as Error ) . message } ` ) ;
703+ }
704+ break ;
705+ }
706+
650707 case 'getSettings' : {
651708 await webviewProvider . postMessage ( { type : 'settings' , data : getCurrentSettings ( ) } ) ;
652709 break ;
653710 }
654711
655712 case 'updateSettings' : {
656- logger . info ( 'Received updateSettings from webview:' , JSON . stringify ( message . data , null , 2 ) ) ;
657- logger . show ( ) ; // Show the output channel for debugging
713+ // Note: settings payloads can contain API keys, so we don't log them.
658714 await updateSettings ( message . data ) ;
659715 // Send back the confirmed settings from VS Code configuration
660716 const confirmedSettings = getCurrentSettings ( ) ;
661- logger . info ( 'Sending back confirmed settings:' , JSON . stringify ( confirmedSettings , null , 2 ) ) ;
662717 await webviewProvider . postMessage ( { type : 'settings' , data : confirmedSettings } ) ;
663718 await webviewProvider . sendToast ( 'success' , 'Settings saved' ) ;
664719 break ;
@@ -1172,8 +1227,6 @@ function getCurrentSettings(): SettingsData {
11721227async function updateSettings ( settings : Partial < SettingsData > ) : Promise < void > {
11731228 const config = vscode . workspace . getConfiguration ( EXTENSION_ID ) ;
11741229
1175- logger . info ( 'updateSettings called with:' , JSON . stringify ( settings , null , 2 ) ) ;
1176-
11771230 // Capture settings
11781231 if ( settings . capture ) {
11791232 if ( settings . capture . enabled !== undefined ) {
@@ -1303,7 +1356,6 @@ async function updateSettings(settings: Partial<SettingsData>): Promise<void> {
13031356 // Using a small delay to ensure VS Code has updated the configuration
13041357 await new Promise ( resolve => setTimeout ( resolve , 100 ) ) ;
13051358 const llmConfig = getLLMConfig ( ) ;
1306- logger . info ( 'LLM config after save:' , JSON . stringify ( llmConfig ) ) ;
13071359 llmOrchestrator . updateConfig ( llmConfig ) ;
13081360 }
13091361
@@ -1332,12 +1384,7 @@ async function updateSettings(settings: Partial<SettingsData>): Promise<void> {
13321384 // Wait for VS Code configuration to fully propagate
13331385 await new Promise ( resolve => setTimeout ( resolve , 200 ) ) ;
13341386
1335- // Log the final settings after all updates
1336- const finalSettings = getCurrentSettings ( ) ;
1337- logger . info (
1338- 'Settings saved successfully. Final settings:' ,
1339- JSON . stringify ( finalSettings , null , 2 )
1340- ) ;
1387+ logger . info ( 'Settings saved' ) ;
13411388}
13421389
13431390/**
0 commit comments