@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.
@@ -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
- console.error(`Note file not found: ${notePath}`);
441
- return;
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
- // Add checkpoint before Backlog section
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
- const currentNotePath = getCurrentNotePath(notesInfo.path);
301
-
302
- if (currentNotePath) {
303
- // 1. Write rich checkpoint with full session state
304
- const checkpointBody = state
305
- ? `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.\n\n${state}`
306
- : `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.`;
307
- appendCheckpoint(currentNotePath, checkpointBody);
308
-
309
- // 2. Write work items to "Work Done" section (same as stop-hook)
310
- const workItems = extractWorkFromTranscript(hookInput.transcript_path);
311
- if (workItems.length > 0) {
312
- addWorkToSessionNote(currentNotePath, workItems, `Pre-Compact (~${tokenDisplay} tokens)`);
313
- console.error(`Added ${workItems.length} work item(s) to session note`);
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
- console.error(`Rich checkpoint saved: ${basename(currentNotePath)}`);
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
  }