Improve logging and cleanup in file system watcher and git ops

Added detailed logging to WatchFileSystemAdaptor and FileSystemWatcher for better traceability during initialization and test stabilization. Introduced a constant for the temporary git index prefix in gitOperations. Removed the unused comparison.ts utility for tiddler comparison. Enhanced comments and logging for AI commit message generation context.
This commit is contained in:
lin onetwo 2026-01-05 19:06:50 +08:00
parent d04f497a02
commit fe66b9ecb9
5 changed files with 25 additions and 63 deletions

View file

@ -8,6 +8,14 @@ import { exec as gitExec } from 'dugite';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
// Limits for how much git content we send to the AI service.
// These values are chosen to balance useful context vs. token / payload size:
// - MAX_UNTRACKED_FILES: cap the number of untracked files included so a large
// number of new files does not overwhelm the prompt or increase latency.
// - MAX_FILE_CONTENT_LENGTH: truncate individual file contents to keep the
// prompt small while still giving the model enough context about changes.
// - MAX_DIFF_LENGTH: hard cap on total diff length to stay within typical
// model/token limits and avoid request failures due to oversized payloads.
const MAX_UNTRACKED_FILES = 5;
const MAX_FILE_CONTENT_LENGTH = 500;
const MAX_DIFF_LENGTH = 3000;

View file

@ -10,6 +10,9 @@ import * as path from 'node:path';
import { defaultGitInfo } from './defaultGitInfo';
import type { GitFileStatus, IFileDiffResult, IGitLogOptions, IGitLogResult } from './interface';
/** Prefix for temporary Git index directories used during amend/undo operations */
const TEMP_GIT_INDEX_PREFIX = 'tidgi-git-index-';
/**
* Helper to create git environment variables for commit operations
* This ensures commits work in environments without git config (like CI)
@ -181,7 +184,8 @@ function parseGitStatusCode(statusCode: string): GitFileStatus {
export async function getCommitFiles(repoPath: string, commitHash: string): Promise<Array<import('./interface').IFileWithStatus>> {
// Handle uncommitted changes
if (!commitHash || commitHash === '') {
// Use -uall to show all untracked files, not just directories
// Use -uall to show all untracked files, not just directories.
// This is important for AI commit message generation to see the full context.
const result = await gitExec(['-c', 'core.quotePath=false', 'status', '--porcelain', '-uall'], repoPath);
if (result.exitCode !== 0) {
@ -710,7 +714,7 @@ export async function addToGitignore(repoPath: string, pattern: string): Promise
export async function amendCommitMessage(repoPath: string, newMessage: string): Promise<void> {
// Use a temporary index so we amend only the commit message and do not
// accidentally include user's staged changes or alter their index state.
const temporaryDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'tidgi-git-index-'));
const temporaryDirectory = await fs.mkdtemp(path.join(os.tmpdir(), TEMP_GIT_INDEX_PREFIX));
const temporaryIndex = path.join(temporaryDirectory, 'index');
try {
const baseEnvironment = { ...process.env, GIT_INDEX_FILE: temporaryIndex } as NodeJS.ProcessEnv;

View file

@ -152,11 +152,13 @@ export class FileSystemWatcher {
*/
async initialize(): Promise<void> {
if (!this.watchPathBase) {
this.logger.log('[test-id-WATCH_FS_STABILIZED] Watcher has stabilized (no watch path)');
return;
}
// Check if file system watch is enabled for this workspace
if (this.workspaceConfig && !this.workspaceConfig.enableFileSystemWatch) {
this.logger.log('[test-id-WATCH_FS_STABILIZED] Watcher has stabilized (disabled by config)');
this.logger.log('FileSystemWatcher File system watching is disabled for this workspace');
return;
}
@ -412,6 +414,9 @@ export class FileSystemWatcher {
this.logger.log('[test-id-WATCH_FS_STABILIZED] Watcher has stabilized');
} catch (error) {
this.logger.alert('FileSystemWatcher Failed to initialize file watching:', error);
// Still log stabilized marker even if initialization failed
// This prevents tests from hanging waiting for the marker
this.logger.log('[test-id-WATCH_FS_STABILIZED] Watcher has stabilized (with errors)');
}
}

View file

@ -38,20 +38,24 @@ export class WatchFileSystemAdaptor extends FileSystemAdaptor {
}
private async initializeAsync(): Promise<void> {
this.logger.log('WatchFileSystemAdaptor initializeAsync starting');
try {
const workspaceId = this.workspaceID;
this.logger.log(`WatchFileSystemAdaptor loading workspace config for ${workspaceId}`);
if (workspaceId) {
const loadedWorkspaceData = await workspace.get(workspaceId);
if (!loadedWorkspaceData || typeof loadedWorkspaceData !== 'object' || !isWikiWorkspace(loadedWorkspaceData)) {
throw new Error('Invalid workspace data');
}
this.workspace = loadedWorkspaceData;
this.logger.log(`WatchFileSystemAdaptor workspace config loaded, enableFileSystemWatch=${this.workspace.enableFileSystemWatch}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.logger.log(`Failed to load workspace data: ${errorMessage}`);
}
this.logger.log('WatchFileSystemAdaptor creating FileSystemWatcher');
// Create and initialize the file watcher
this.watcher = new FileSystemWatcher({
wiki: this.wiki,
@ -61,7 +65,9 @@ export class WatchFileSystemAdaptor extends FileSystemAdaptor {
workspaceConfig: this.workspace,
});
this.logger.log('WatchFileSystemAdaptor calling watcher.initialize()');
await this.watcher.initialize();
this.logger.log('WatchFileSystemAdaptor initialization complete');
}
/**

View file

@ -1,61 +0,0 @@
/**
* High-performance tiddler comparison utilities
* Used to detect if a tiddler has actually changed to prevent unnecessary saves
*/
/**
* Fields that change automatically during save and should be excluded from comparison
*/
const EXCLUDED_FIELDS = new Set([
'modified', // Always updated on save
'revision', // Internal TiddlyWiki revision counter
'bag', // TiddlyWeb specific
'created', // May be auto-generated
]);
/**
* Fast comparison of two tiddlers to detect real content changes
* Strategy:
* 1. Quick length check on text field (most common change)
* 2. Compare field counts
* 3. Deep compare only if needed
*
* @param oldTiddler - Existing tiddler in wiki
* @param newTiddler - New tiddler from file
* @returns true if tiddlers are meaningfully different
*/
export function hasMeaningfulChanges(
oldTiddler: Record<string, unknown>,
newTiddler: Record<string, unknown>,
): boolean {
// Fast path: Compare text field length first (most common change)
const oldText = oldTiddler.text as string | undefined;
const newText = newTiddler.text as string | undefined;
if ((oldText?.length ?? 0) !== (newText?.length ?? 0)) {
return true; // Text length changed - definitely different
}
// Get field keys excluding auto-generated ones
const oldKeys = new Set(Object.keys(oldTiddler)).difference(EXCLUDED_FIELDS);
const newKeys = new Set(Object.keys(newTiddler)).difference(EXCLUDED_FIELDS);
// Quick check: Different number of fields
if (oldKeys.size !== newKeys.size) {
return true;
}
// Check if there are any different keys (fields added or removed)
if (oldKeys.difference(newKeys).size > 0) {
return true;
}
// Deep comparison: Check each field value (keys are the same at this point)
for (const key of oldKeys) {
if (oldTiddler[key] !== newTiddler[key]) {
return true; // Field value changed
}
}
return false; // No meaningful changes
}