@tekmidian/pai 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/cleanup-session-files.mjs.map +1 -1
- package/dist/hooks/context-compression-hook.mjs +132 -15
- package/dist/hooks/context-compression-hook.mjs.map +3 -3
- package/dist/hooks/initialize-session.mjs.map +1 -1
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/stop-hook.mjs.map +2 -2
- package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
- package/package.json +1 -1
- package/src/hooks/ts/lib/project-utils.ts +38 -4
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +30 -16
|
@@ -437,8 +437,24 @@ export function createSessionNote(notesDir: string, description: string): string
|
|
|
437
437
|
*/
|
|
438
438
|
export function appendCheckpoint(notePath: string, checkpoint: string): void {
|
|
439
439
|
if (!existsSync(notePath)) {
|
|
440
|
-
|
|
441
|
-
|
|
440
|
+
// Note vanished (cloud sync, cleanup, etc.) — recreate it
|
|
441
|
+
console.error(`Note file not found, recreating: ${notePath}`);
|
|
442
|
+
try {
|
|
443
|
+
const parentDir = join(notePath, '..');
|
|
444
|
+
if (!existsSync(parentDir)) {
|
|
445
|
+
mkdirSync(parentDir, { recursive: true });
|
|
446
|
+
}
|
|
447
|
+
const noteFilename = basename(notePath);
|
|
448
|
+
const numberMatch = noteFilename.match(/^(\d+)/);
|
|
449
|
+
const noteNumber = numberMatch ? numberMatch[1] : '0000';
|
|
450
|
+
const date = new Date().toISOString().split('T')[0];
|
|
451
|
+
const content = `# Session ${noteNumber}: Recovered\n\n**Date:** ${date}\n**Status:** In Progress\n\n---\n\n## Work Done\n\n<!-- PAI will add completed work here during session -->\n\n---\n\n## Next Steps\n\n<!-- To be filled at session end -->\n\n---\n\n**Tags:** #Session\n`;
|
|
452
|
+
writeFileSync(notePath, content);
|
|
453
|
+
console.error(`Recreated session note: ${noteFilename}`);
|
|
454
|
+
} catch (err) {
|
|
455
|
+
console.error(`Failed to recreate note: ${err}`);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
442
458
|
}
|
|
443
459
|
|
|
444
460
|
const content = readFileSync(notePath, 'utf-8');
|
|
@@ -891,6 +907,7 @@ ${sessionSummary ? `**Session Summary:** ${sessionSummary}\n\n` : ''}${backlogSe
|
|
|
891
907
|
/**
|
|
892
908
|
* Add a checkpoint entry to TODO.md (without replacing tasks)
|
|
893
909
|
* Ensures only ONE timestamp line at the end
|
|
910
|
+
* Works regardless of TODO.md structure — appends if no known section found
|
|
894
911
|
*/
|
|
895
912
|
export function addTodoCheckpoint(cwd: string, checkpoint: string): void {
|
|
896
913
|
const todoPath = ensureTodoMd(cwd);
|
|
@@ -899,11 +916,28 @@ export function addTodoCheckpoint(cwd: string, checkpoint: string): void {
|
|
|
899
916
|
// Remove ALL existing timestamp lines and trailing separators
|
|
900
917
|
content = content.replace(/(\n---\s*)*(\n\*Last updated:.*\*\s*)+$/g, '');
|
|
901
918
|
|
|
902
|
-
|
|
919
|
+
const checkpointText = `\n**Checkpoint (${new Date().toISOString()}):** ${checkpoint}\n\n`;
|
|
920
|
+
|
|
921
|
+
// Try to insert before Backlog section
|
|
903
922
|
const backlogIndex = content.indexOf('## Backlog');
|
|
904
923
|
if (backlogIndex !== -1) {
|
|
905
|
-
const checkpointText = `\n**Checkpoint (${new Date().toISOString()}):** ${checkpoint}\n\n`;
|
|
906
924
|
content = content.substring(0, backlogIndex) + checkpointText + content.substring(backlogIndex);
|
|
925
|
+
} else {
|
|
926
|
+
// No Backlog section — try before Continue section, or just append
|
|
927
|
+
const continueIndex = content.indexOf('## Continue');
|
|
928
|
+
if (continueIndex !== -1) {
|
|
929
|
+
// Insert after the Continue section (find the next ## or ---)
|
|
930
|
+
const afterContinue = content.indexOf('\n---', continueIndex);
|
|
931
|
+
if (afterContinue !== -1) {
|
|
932
|
+
const insertAt = afterContinue + 4; // after \n---
|
|
933
|
+
content = content.substring(0, insertAt) + '\n' + checkpointText + content.substring(insertAt);
|
|
934
|
+
} else {
|
|
935
|
+
content = content.trimEnd() + '\n' + checkpointText;
|
|
936
|
+
}
|
|
937
|
+
} else {
|
|
938
|
+
// No known section — just append before the end
|
|
939
|
+
content = content.trimEnd() + '\n' + checkpointText;
|
|
940
|
+
}
|
|
907
941
|
}
|
|
908
942
|
|
|
909
943
|
// Add exactly ONE timestamp at the end
|
|
@@ -18,6 +18,7 @@ import { tmpdir } from 'os';
|
|
|
18
18
|
import {
|
|
19
19
|
sendNtfyNotification,
|
|
20
20
|
getCurrentNotePath,
|
|
21
|
+
createSessionNote,
|
|
21
22
|
appendCheckpoint,
|
|
22
23
|
addWorkToSessionNote,
|
|
23
24
|
findNotesDir,
|
|
@@ -297,24 +298,37 @@ async function main() {
|
|
|
297
298
|
const notesInfo = hookInput.cwd
|
|
298
299
|
? findNotesDir(hookInput.cwd)
|
|
299
300
|
: { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
301
|
+
let notePath = getCurrentNotePath(notesInfo.path);
|
|
302
|
+
|
|
303
|
+
// If no note found, or the latest note is completed, create a new one
|
|
304
|
+
if (!notePath) {
|
|
305
|
+
console.error('No session note found — creating one for checkpoint');
|
|
306
|
+
notePath = createSessionNote(notesInfo.path, 'Recovered Session');
|
|
307
|
+
} else {
|
|
308
|
+
// Check if the found note is already completed — don't write to completed notes
|
|
309
|
+
try {
|
|
310
|
+
const noteContent = readFileSync(notePath, 'utf-8');
|
|
311
|
+
if (noteContent.includes('**Status:** Completed') || noteContent.includes('**Completed:**')) {
|
|
312
|
+
console.error(`Latest note is completed (${basename(notePath)}) — creating new one`);
|
|
313
|
+
notePath = createSessionNote(notesInfo.path, 'Continued Session');
|
|
314
|
+
}
|
|
315
|
+
} catch { /* proceed with existing note */ }
|
|
316
|
+
}
|
|
315
317
|
|
|
316
|
-
|
|
318
|
+
// 1. Write rich checkpoint with full session state
|
|
319
|
+
const checkpointBody = state
|
|
320
|
+
? `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.\n\n${state}`
|
|
321
|
+
: `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.`;
|
|
322
|
+
appendCheckpoint(notePath, checkpointBody);
|
|
323
|
+
|
|
324
|
+
// 2. Write work items to "Work Done" section (same as stop-hook)
|
|
325
|
+
const workItems = extractWorkFromTranscript(hookInput.transcript_path);
|
|
326
|
+
if (workItems.length > 0) {
|
|
327
|
+
addWorkToSessionNote(notePath, workItems, `Pre-Compact (~${tokenDisplay} tokens)`);
|
|
328
|
+
console.error(`Added ${workItems.length} work item(s) to session note`);
|
|
317
329
|
}
|
|
330
|
+
|
|
331
|
+
console.error(`Rich checkpoint saved: ${basename(notePath)}`);
|
|
318
332
|
} catch (noteError) {
|
|
319
333
|
console.error(`Could not save checkpoint: ${noteError}`);
|
|
320
334
|
}
|