Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions Packs/pai-hook-system/src/hooks/handlers/SystemIntegrity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
determineSignificance,
inferChangeType,
generateDescriptiveTitle,
readIntegrityState,
type FileChange,
} from '../lib/change-detection';
import type { ParsedTranscript } from '../../skills/CORE/Tools/TranscriptParser';
Expand Down Expand Up @@ -71,7 +72,7 @@ async function notifyIntegrityStart(): Promise<void> {
/**
* Update the integrity state file.
*/
function updateIntegrityState(changes: FileChange[]): void {
function updateIntegrityState(changes: FileChange[], lastParsedLine: number): void {
try {
if (!existsSync(STATE_DIR)) {
mkdirSync(STATE_DIR, { recursive: true });
Expand All @@ -81,10 +82,11 @@ function updateIntegrityState(changes: FileChange[]): void {
last_run: new Date().toISOString(),
last_changes_hash: hashChanges(changes),
cooldown_until: getCooldownEndTime(),
last_parsed_line: lastParsedLine,
};

writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
console.error('[SystemIntegrity] Updated state file');
console.error('[SystemIntegrity] Updated state file (parsed up to line ' + lastParsedLine + ')');
} catch (error) {
console.error('[SystemIntegrity] Failed to update state:', error);
}
Expand Down Expand Up @@ -168,9 +170,14 @@ export async function handleSystemIntegrity(
return;
}

// Parse changes from transcript
const changes = parseToolUseBlocks(hookInput.transcript_path);
console.error(`[SystemIntegrity] Found ${changes.length} file changes in transcript`);
// Get last parsed line from state for incremental parsing
const state = readIntegrityState();
const startLine = state?.last_parsed_line || 0;
console.error(`[SystemIntegrity] Incremental parsing from line ${startLine}`);

// Parse changes from transcript (only new lines since last check)
const { changes, lastLine } = parseToolUseBlocks(hookInput.transcript_path, startLine);
console.error(`[SystemIntegrity] Found ${changes.length} file changes in transcript (lines ${startLine}-${lastLine})`);

// Filter to only PAI system changes
const systemChanges = changes.filter(c => c.category !== null);
Expand Down Expand Up @@ -202,8 +209,8 @@ export async function handleSystemIntegrity(
console.error(` ... and ${systemChanges.length - 5} more`);
}

// Update state before spawning
updateIntegrityState(systemChanges);
// Update state before spawning (including last parsed line for incremental parsing)
updateIntegrityState(systemChanges, lastLine);

// Send voice notification (fire-and-forget)
notifyIntegrityStart().catch(() => {});
Expand Down
20 changes: 15 additions & 5 deletions Packs/pai-hook-system/src/hooks/lib/change-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface IntegrityState {
last_run: string;
last_changes_hash: string;
cooldown_until: string | null;
last_parsed_line?: number;
}

// ============================================================================
Expand Down Expand Up @@ -106,20 +107,29 @@ const STRUCTURAL_PATTERNS = [
/**
* Parse tool_use blocks from a transcript that modify files.
* Extracts Write, Edit, and MultiEdit operations.
*
* @param transcriptPath - Path to the transcript file
* @param startLine - Optional line number to start parsing from (0-indexed)
* @returns Object containing changes and the last line number parsed
*/
export function parseToolUseBlocks(transcriptPath: string): FileChange[] {
export function parseToolUseBlocks(
transcriptPath: string,
startLine: number = 0
): { changes: FileChange[]; lastLine: number } {
try {
if (!existsSync(transcriptPath)) {
console.error('[ChangeDetection] Transcript not found:', transcriptPath);
return [];
return { changes: [], lastLine: 0 };
}

const content = readFileSync(transcriptPath, 'utf-8');
const lines = content.trim().split('\n');
const changes: FileChange[] = [];
const seenPaths = new Set<string>();

for (const line of lines) {
// Only parse lines after startLine
for (let i = startLine; i < lines.length; i++) {
const line = lines[i];
if (!line.trim()) continue;

try {
Expand Down Expand Up @@ -168,10 +178,10 @@ export function parseToolUseBlocks(transcriptPath: string): FileChange[] {
}
}

return changes;
return { changes, lastLine: lines.length };
} catch (error) {
console.error('[ChangeDetection] Error parsing transcript:', error);
return [];
return { changes: [], lastLine: 0 };
}
}

Expand Down