@tekmidian/pai 0.5.4 → 0.5.6
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 +240 -132
- 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 +1 -1
- package/dist/hooks/stop-hook.mjs.map +1 -1
- package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
- package/package.json +1 -1
- package/src/hooks/ts/lib/project-utils.ts +52 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +273 -140
|
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
// src/hooks/ts/pre-compact/context-compression-hook.ts
|
|
10
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
10
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
11
11
|
import { basename as basename2, dirname, join as join3 } from "path";
|
|
12
12
|
import { tmpdir } from "os";
|
|
13
13
|
|
|
@@ -330,6 +330,35 @@ function addWorkToSessionNote(notePath, workItems, sectionTitle) {
|
|
|
330
330
|
writeFileSync(notePath, content);
|
|
331
331
|
console.error(`Added ${workItems.length} work item(s) to: ${basename(notePath)}`);
|
|
332
332
|
}
|
|
333
|
+
function renameSessionNote(notePath, meaningfulName) {
|
|
334
|
+
if (!meaningfulName || !existsSync2(notePath)) {
|
|
335
|
+
return notePath;
|
|
336
|
+
}
|
|
337
|
+
const dir = join2(notePath, "..");
|
|
338
|
+
const oldFilename = basename(notePath);
|
|
339
|
+
const correctMatch = oldFilename.match(/^(\d{3,4}) - (\d{4}-\d{2}-\d{2}) - .*\.md$/);
|
|
340
|
+
const legacyMatch = oldFilename.match(/^(\d{3,4})_(\d{4}-\d{2}-\d{2})_.*\.md$/);
|
|
341
|
+
const match = correctMatch || legacyMatch;
|
|
342
|
+
if (!match) {
|
|
343
|
+
return notePath;
|
|
344
|
+
}
|
|
345
|
+
const [, noteNumber, date] = match;
|
|
346
|
+
const titleCaseName = meaningfulName.split(/[\s_-]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ").trim();
|
|
347
|
+
const paddedNumber = noteNumber.padStart(4, "0");
|
|
348
|
+
const newFilename = `${paddedNumber} - ${date} - ${titleCaseName}.md`;
|
|
349
|
+
const newPath = join2(dir, newFilename);
|
|
350
|
+
if (newFilename === oldFilename) {
|
|
351
|
+
return notePath;
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
renameSync(notePath, newPath);
|
|
355
|
+
console.error(`Renamed note: ${oldFilename} \u2192 ${newFilename}`);
|
|
356
|
+
return newPath;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error(`Could not rename note: ${error}`);
|
|
359
|
+
return notePath;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
333
362
|
function calculateSessionTokens(jsonlPath) {
|
|
334
363
|
if (!existsSync2(jsonlPath)) {
|
|
335
364
|
return 0;
|
|
@@ -397,39 +426,38 @@ function ensureTodoMd(cwd) {
|
|
|
397
426
|
}
|
|
398
427
|
return todoPath;
|
|
399
428
|
}
|
|
400
|
-
function
|
|
429
|
+
function updateTodoContinue(cwd, noteFilename, state, tokenDisplay) {
|
|
401
430
|
const todoPath = ensureTodoMd(cwd);
|
|
402
431
|
let content = readFileSync2(todoPath, "utf-8");
|
|
403
|
-
content = content.replace(
|
|
404
|
-
const
|
|
405
|
-
|
|
432
|
+
content = content.replace(/## Continue\n[\s\S]*?\n---\n+/, "");
|
|
433
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
434
|
+
const stateLines = state ? state.split("\n").filter((l) => l.trim()).slice(0, 10).map((l) => `> ${l}`).join("\n") : `> Check the latest session note for details.`;
|
|
435
|
+
const continueSection = `## Continue
|
|
436
|
+
|
|
437
|
+
> **Last session:** ${noteFilename.replace(".md", "")}
|
|
438
|
+
> **Paused at:** ${now}
|
|
439
|
+
>
|
|
440
|
+
${stateLines}
|
|
441
|
+
|
|
442
|
+
---
|
|
406
443
|
|
|
407
444
|
`;
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
445
|
+
content = content.replace(/^\s+/, "");
|
|
446
|
+
const titleMatch = content.match(/^(# [^\n]+\n+)/);
|
|
447
|
+
if (titleMatch) {
|
|
448
|
+
content = titleMatch[1] + continueSection + content.substring(titleMatch[0].length);
|
|
411
449
|
} else {
|
|
412
|
-
|
|
413
|
-
if (continueIndex !== -1) {
|
|
414
|
-
const afterContinue = content.indexOf("\n---", continueIndex);
|
|
415
|
-
if (afterContinue !== -1) {
|
|
416
|
-
const insertAt = afterContinue + 4;
|
|
417
|
-
content = content.substring(0, insertAt) + "\n" + checkpointText + content.substring(insertAt);
|
|
418
|
-
} else {
|
|
419
|
-
content = content.trimEnd() + "\n" + checkpointText;
|
|
420
|
-
}
|
|
421
|
-
} else {
|
|
422
|
-
content = content.trimEnd() + "\n" + checkpointText;
|
|
423
|
-
}
|
|
450
|
+
content = continueSection + content;
|
|
424
451
|
}
|
|
452
|
+
content = content.replace(/(\n---\s*)*(\n\*Last updated:.*\*\s*)+$/g, "");
|
|
425
453
|
content = content.trimEnd() + `
|
|
426
454
|
|
|
427
455
|
---
|
|
428
456
|
|
|
429
|
-
*Last updated: ${
|
|
457
|
+
*Last updated: ${now}*
|
|
430
458
|
`;
|
|
431
459
|
writeFileSync(todoPath, content);
|
|
432
|
-
console.error(
|
|
460
|
+
console.error("TODO.md ## Continue section updated");
|
|
433
461
|
}
|
|
434
462
|
|
|
435
463
|
// src/hooks/ts/pre-compact/context-compression-hook.ts
|
|
@@ -466,15 +494,19 @@ function getTranscriptStats(transcriptPath) {
|
|
|
466
494
|
return { messageCount: 0, isLarge: false };
|
|
467
495
|
}
|
|
468
496
|
}
|
|
469
|
-
function
|
|
497
|
+
function parseTranscript(transcriptPath) {
|
|
498
|
+
const data = {
|
|
499
|
+
userMessages: [],
|
|
500
|
+
summaries: [],
|
|
501
|
+
captures: [],
|
|
502
|
+
lastCompleted: "",
|
|
503
|
+
filesModified: [],
|
|
504
|
+
workItems: []
|
|
505
|
+
};
|
|
470
506
|
try {
|
|
471
507
|
const raw = readFileSync3(transcriptPath, "utf-8");
|
|
472
508
|
const lines = raw.trim().split("\n");
|
|
473
|
-
const
|
|
474
|
-
const summaries = [];
|
|
475
|
-
const captures = [];
|
|
476
|
-
let lastCompleted = "";
|
|
477
|
-
const filesModified = /* @__PURE__ */ new Set();
|
|
509
|
+
const seenSummaries = /* @__PURE__ */ new Set();
|
|
478
510
|
for (const line of lines) {
|
|
479
511
|
if (!line.trim()) continue;
|
|
480
512
|
let entry;
|
|
@@ -485,123 +517,164 @@ function extractSessionState(transcriptPath, cwd) {
|
|
|
485
517
|
}
|
|
486
518
|
if (entry.type === "user" && entry.message?.content) {
|
|
487
519
|
const text = contentToText(entry.message.content).slice(0, 300);
|
|
488
|
-
if (text) userMessages.push(text);
|
|
520
|
+
if (text) data.userMessages.push(text);
|
|
489
521
|
}
|
|
490
522
|
if (entry.type === "assistant" && entry.message?.content) {
|
|
491
523
|
const text = contentToText(entry.message.content);
|
|
492
524
|
const summaryMatch = text.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
|
|
493
525
|
if (summaryMatch) {
|
|
494
526
|
const s = summaryMatch[1].trim();
|
|
495
|
-
if (s.length > 5 && !summaries.includes(s))
|
|
527
|
+
if (s.length > 5 && !data.summaries.includes(s)) {
|
|
528
|
+
data.summaries.push(s);
|
|
529
|
+
if (!seenSummaries.has(s)) {
|
|
530
|
+
seenSummaries.add(s);
|
|
531
|
+
const details = [];
|
|
532
|
+
const actionsMatch = text.match(/ACTIONS:\s*(.+?)(?=\n[A-Z]+:|$)/is);
|
|
533
|
+
if (actionsMatch) {
|
|
534
|
+
const actionLines = actionsMatch[1].split("\n").map((l) => l.replace(/^[-*•]\s*/, "").replace(/^\d+\.\s*/, "").trim()).filter((l) => l.length > 3 && l.length < 100);
|
|
535
|
+
details.push(...actionLines.slice(0, 3));
|
|
536
|
+
}
|
|
537
|
+
data.workItems.push({ title: s, details: details.length > 0 ? details : void 0, completed: true });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
496
540
|
}
|
|
497
541
|
const captureMatch = text.match(/CAPTURE:\s*(.+?)(?:\n|$)/i);
|
|
498
542
|
if (captureMatch) {
|
|
499
543
|
const c = captureMatch[1].trim();
|
|
500
|
-
if (c.length > 5 && !captures.includes(c)) captures.push(c);
|
|
544
|
+
if (c.length > 5 && !data.captures.includes(c)) data.captures.push(c);
|
|
501
545
|
}
|
|
502
546
|
const completedMatch = text.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
503
547
|
if (completedMatch) {
|
|
504
|
-
lastCompleted = completedMatch[1].trim().replace(/\*+/g, "");
|
|
548
|
+
data.lastCompleted = completedMatch[1].trim().replace(/\*+/g, "");
|
|
549
|
+
if (data.workItems.length === 0 && !seenSummaries.has(data.lastCompleted) && data.lastCompleted.length > 5) {
|
|
550
|
+
seenSummaries.add(data.lastCompleted);
|
|
551
|
+
data.workItems.push({ title: data.lastCompleted, completed: true });
|
|
552
|
+
}
|
|
505
553
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
554
|
+
if (Array.isArray(entry.message.content)) {
|
|
555
|
+
for (const block of entry.message.content) {
|
|
556
|
+
if (block.type === "tool_use") {
|
|
557
|
+
const tool = block.name;
|
|
558
|
+
if ((tool === "Edit" || tool === "Write") && block.input?.file_path) {
|
|
559
|
+
if (!data.filesModified.includes(block.input.file_path)) {
|
|
560
|
+
data.filesModified.push(block.input.file_path);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
513
563
|
}
|
|
514
564
|
}
|
|
515
565
|
}
|
|
516
566
|
}
|
|
517
567
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
if (recentSummaries.length > 0) {
|
|
532
|
-
parts.push("\nWork summaries:");
|
|
533
|
-
for (const s of recentSummaries) {
|
|
534
|
-
parts.push(`- ${s.slice(0, 150)}`);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
const recentCaptures = captures.slice(-5);
|
|
538
|
-
if (recentCaptures.length > 0) {
|
|
539
|
-
parts.push("\nCaptured context:");
|
|
540
|
-
for (const c of recentCaptures) {
|
|
541
|
-
parts.push(`- ${c.slice(0, 150)}`);
|
|
542
|
-
}
|
|
568
|
+
} catch (err) {
|
|
569
|
+
console.error(`parseTranscript error: ${err}`);
|
|
570
|
+
}
|
|
571
|
+
return data;
|
|
572
|
+
}
|
|
573
|
+
function formatSessionState(data, cwd) {
|
|
574
|
+
const parts = [];
|
|
575
|
+
if (cwd) parts.push(`Working directory: ${cwd}`);
|
|
576
|
+
const recentUser = data.userMessages.slice(-3);
|
|
577
|
+
if (recentUser.length > 0) {
|
|
578
|
+
parts.push("\nRecent user requests:");
|
|
579
|
+
for (const msg of recentUser) {
|
|
580
|
+
parts.push(`- ${msg.split("\n")[0].slice(0, 200)}`);
|
|
543
581
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
582
|
+
}
|
|
583
|
+
const recentSummaries = data.summaries.slice(-3);
|
|
584
|
+
if (recentSummaries.length > 0) {
|
|
585
|
+
parts.push("\nWork summaries:");
|
|
586
|
+
for (const s of recentSummaries) parts.push(`- ${s.slice(0, 150)}`);
|
|
587
|
+
}
|
|
588
|
+
const recentCaptures = data.captures.slice(-5);
|
|
589
|
+
if (recentCaptures.length > 0) {
|
|
590
|
+
parts.push("\nCaptured context:");
|
|
591
|
+
for (const c of recentCaptures) parts.push(`- ${c.slice(0, 150)}`);
|
|
592
|
+
}
|
|
593
|
+
const files = data.filesModified.slice(-10);
|
|
594
|
+
if (files.length > 0) {
|
|
595
|
+
parts.push("\nFiles modified this session:");
|
|
596
|
+
for (const f of files) parts.push(`- ${f}`);
|
|
597
|
+
}
|
|
598
|
+
if (data.lastCompleted) {
|
|
599
|
+
parts.push(`
|
|
600
|
+
Last completed: ${data.lastCompleted.slice(0, 150)}`);
|
|
601
|
+
}
|
|
602
|
+
const result = parts.join("\n");
|
|
603
|
+
return result.length > 50 ? result : null;
|
|
604
|
+
}
|
|
605
|
+
function deriveTitle(data) {
|
|
606
|
+
let title = "";
|
|
607
|
+
if (data.workItems.length > 0) {
|
|
608
|
+
title = data.workItems[data.workItems.length - 1].title;
|
|
609
|
+
} else if (data.summaries.length > 0) {
|
|
610
|
+
title = data.summaries[data.summaries.length - 1];
|
|
611
|
+
} else if (data.lastCompleted && data.lastCompleted.length > 5) {
|
|
612
|
+
title = data.lastCompleted;
|
|
613
|
+
} else if (data.userMessages.length > 0) {
|
|
614
|
+
for (let i = data.userMessages.length - 1; i >= 0; i--) {
|
|
615
|
+
const msg = data.userMessages[i].split("\n")[0].trim();
|
|
616
|
+
if (msg.length > 10 && msg.length < 80 && !msg.toLowerCase().startsWith("yes") && !msg.toLowerCase().startsWith("ok")) {
|
|
617
|
+
title = msg;
|
|
618
|
+
break;
|
|
549
619
|
}
|
|
550
620
|
}
|
|
551
|
-
if (lastCompleted) {
|
|
552
|
-
parts.push(`
|
|
553
|
-
Last completed: ${lastCompleted.slice(0, 150)}`);
|
|
554
|
-
}
|
|
555
|
-
const result = parts.join("\n");
|
|
556
|
-
return result.length > 50 ? result : null;
|
|
557
|
-
} catch (err) {
|
|
558
|
-
console.error(`extractSessionState error: ${err}`);
|
|
559
|
-
return null;
|
|
560
621
|
}
|
|
622
|
+
if (!title && data.filesModified.length > 0) {
|
|
623
|
+
const basenames = data.filesModified.slice(-5).map((f) => {
|
|
624
|
+
const b = basename2(f);
|
|
625
|
+
return b.replace(/\.[^.]+$/, "");
|
|
626
|
+
});
|
|
627
|
+
const unique = [...new Set(basenames)];
|
|
628
|
+
title = unique.length <= 3 ? `Updated ${unique.join(", ")}` : `Modified ${data.filesModified.length} files`;
|
|
629
|
+
}
|
|
630
|
+
return title.replace(/[^\w\s-]/g, " ").replace(/\s+/g, " ").trim().substring(0, 60);
|
|
561
631
|
}
|
|
562
|
-
|
|
632
|
+
var CUMULATIVE_STATE_FILE = ".compact-state.json";
|
|
633
|
+
function loadCumulativeState(notesDir) {
|
|
563
634
|
try {
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
if (entry.type === "assistant" && entry.message?.content) {
|
|
577
|
-
const text = contentToText(entry.message.content);
|
|
578
|
-
const summaryMatch = text.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
|
|
579
|
-
if (summaryMatch) {
|
|
580
|
-
const summary = summaryMatch[1].trim();
|
|
581
|
-
if (summary && !seenSummaries.has(summary) && summary.length > 5) {
|
|
582
|
-
seenSummaries.add(summary);
|
|
583
|
-
const details = [];
|
|
584
|
-
const actionsMatch = text.match(/ACTIONS:\s*(.+?)(?=\n[A-Z]+:|$)/is);
|
|
585
|
-
if (actionsMatch) {
|
|
586
|
-
const actionLines = actionsMatch[1].split("\n").map((l) => l.replace(/^[-*•]\s*/, "").replace(/^\d+\.\s*/, "").trim()).filter((l) => l.length > 3 && l.length < 100);
|
|
587
|
-
details.push(...actionLines.slice(0, 3));
|
|
588
|
-
}
|
|
589
|
-
workItems.push({ title: summary, details: details.length > 0 ? details : void 0, completed: true });
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
const completedMatch = text.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
593
|
-
if (completedMatch && workItems.length === 0) {
|
|
594
|
-
const completed = completedMatch[1].trim().replace(/\*+/g, "");
|
|
595
|
-
if (completed && !seenSummaries.has(completed) && completed.length > 5) {
|
|
596
|
-
seenSummaries.add(completed);
|
|
597
|
-
workItems.push({ title: completed, completed: true });
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return workItems;
|
|
635
|
+
const filePath = join3(notesDir, CUMULATIVE_STATE_FILE);
|
|
636
|
+
if (!existsSync3(filePath)) return null;
|
|
637
|
+
const raw = JSON.parse(readFileSync3(filePath, "utf-8"));
|
|
638
|
+
return {
|
|
639
|
+
userMessages: raw.userMessages || [],
|
|
640
|
+
summaries: raw.summaries || [],
|
|
641
|
+
captures: raw.captures || [],
|
|
642
|
+
lastCompleted: raw.lastCompleted || "",
|
|
643
|
+
filesModified: raw.filesModified || [],
|
|
644
|
+
workItems: raw.workItems || []
|
|
645
|
+
};
|
|
603
646
|
} catch {
|
|
604
|
-
return
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function mergeTranscriptData(accumulated, current) {
|
|
651
|
+
if (!accumulated) return current;
|
|
652
|
+
const mergeArrays = (a, b) => {
|
|
653
|
+
const seen = new Set(a);
|
|
654
|
+
return [...a, ...b.filter((x) => !seen.has(x))];
|
|
655
|
+
};
|
|
656
|
+
const seenTitles = new Set(accumulated.workItems.map((w) => w.title));
|
|
657
|
+
const newWorkItems = current.workItems.filter((w) => !seenTitles.has(w.title));
|
|
658
|
+
return {
|
|
659
|
+
userMessages: mergeArrays(accumulated.userMessages, current.userMessages).slice(-20),
|
|
660
|
+
summaries: mergeArrays(accumulated.summaries, current.summaries),
|
|
661
|
+
captures: mergeArrays(accumulated.captures, current.captures),
|
|
662
|
+
lastCompleted: current.lastCompleted || accumulated.lastCompleted,
|
|
663
|
+
filesModified: mergeArrays(accumulated.filesModified, current.filesModified),
|
|
664
|
+
workItems: [...accumulated.workItems, ...newWorkItems]
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function saveCumulativeState(notesDir, data, notePath) {
|
|
668
|
+
try {
|
|
669
|
+
const filePath = join3(notesDir, CUMULATIVE_STATE_FILE);
|
|
670
|
+
writeFileSync2(filePath, JSON.stringify({
|
|
671
|
+
...data,
|
|
672
|
+
notePath,
|
|
673
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
674
|
+
}, null, 2));
|
|
675
|
+
console.error(`Cumulative state saved (${data.workItems.length} work items, ${data.filesModified.length} files)`);
|
|
676
|
+
} catch (err) {
|
|
677
|
+
console.error(`Failed to save cumulative state: ${err}`);
|
|
605
678
|
}
|
|
606
679
|
}
|
|
607
680
|
async function main() {
|
|
@@ -629,10 +702,22 @@ async function main() {
|
|
|
629
702
|
const stats = getTranscriptStats(hookInput.transcript_path);
|
|
630
703
|
tokenCount = calculateSessionTokens(hookInput.transcript_path);
|
|
631
704
|
const tokenDisplay = tokenCount > 1e3 ? `${Math.round(tokenCount / 1e3)}k` : String(tokenCount);
|
|
632
|
-
const
|
|
705
|
+
const data = parseTranscript(hookInput.transcript_path);
|
|
706
|
+
let notesInfo;
|
|
707
|
+
try {
|
|
708
|
+
notesInfo = hookInput.cwd ? findNotesDir(hookInput.cwd) : { path: join3(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
|
|
709
|
+
} catch {
|
|
710
|
+
notesInfo = { path: join3(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
|
|
711
|
+
}
|
|
712
|
+
const accumulated = loadCumulativeState(notesInfo.path);
|
|
713
|
+
const merged = mergeTranscriptData(accumulated, data);
|
|
714
|
+
const state = formatSessionState(merged, hookInput.cwd);
|
|
715
|
+
if (accumulated) {
|
|
716
|
+
console.error(`Loaded cumulative state: ${accumulated.workItems.length} work items, ${accumulated.filesModified.length} files from previous compaction(s)`);
|
|
717
|
+
}
|
|
718
|
+
let notePath = null;
|
|
633
719
|
try {
|
|
634
|
-
|
|
635
|
-
let notePath = getCurrentNotePath(notesInfo.path);
|
|
720
|
+
notePath = getCurrentNotePath(notesInfo.path);
|
|
636
721
|
if (!notePath) {
|
|
637
722
|
console.error("No session note found \u2014 creating one for checkpoint");
|
|
638
723
|
notePath = createSessionNote(notesInfo.path, "Recovered Session");
|
|
@@ -650,30 +735,53 @@ async function main() {
|
|
|
650
735
|
|
|
651
736
|
${state}` : `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.`;
|
|
652
737
|
appendCheckpoint(notePath, checkpointBody);
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
738
|
+
if (merged.workItems.length > 0) {
|
|
739
|
+
addWorkToSessionNote(notePath, merged.workItems, `Pre-Compact (~${tokenDisplay} tokens)`);
|
|
740
|
+
console.error(`Added ${merged.workItems.length} work item(s) to session note`);
|
|
741
|
+
}
|
|
742
|
+
const title = deriveTitle(merged);
|
|
743
|
+
if (title) {
|
|
744
|
+
const newPath = renameSessionNote(notePath, title);
|
|
745
|
+
if (newPath !== notePath) {
|
|
746
|
+
try {
|
|
747
|
+
let noteContent = readFileSync3(newPath, "utf-8");
|
|
748
|
+
noteContent = noteContent.replace(
|
|
749
|
+
/^(# Session \d+:)\s*.*$/m,
|
|
750
|
+
`$1 ${title}`
|
|
751
|
+
);
|
|
752
|
+
writeFileSync2(newPath, noteContent);
|
|
753
|
+
console.error(`Updated note H1 to match rename`);
|
|
754
|
+
} catch {
|
|
755
|
+
}
|
|
756
|
+
notePath = newPath;
|
|
757
|
+
}
|
|
657
758
|
}
|
|
658
759
|
console.error(`Rich checkpoint saved: ${basename2(notePath)}`);
|
|
659
760
|
} catch (noteError) {
|
|
660
761
|
console.error(`Could not save checkpoint: ${noteError}`);
|
|
661
762
|
}
|
|
662
|
-
|
|
763
|
+
saveCumulativeState(notesInfo.path, merged, notePath);
|
|
764
|
+
if (hookInput.cwd && notePath) {
|
|
663
765
|
try {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
console.error("TODO.md
|
|
766
|
+
const noteFilename = basename2(notePath);
|
|
767
|
+
updateTodoContinue(hookInput.cwd, noteFilename, state, tokenDisplay);
|
|
768
|
+
console.error("TODO.md ## Continue section updated");
|
|
667
769
|
} catch (todoError) {
|
|
668
770
|
console.error(`Could not update TODO.md: ${todoError}`);
|
|
669
771
|
}
|
|
670
772
|
}
|
|
671
|
-
if (
|
|
773
|
+
if (hookInput.session_id) {
|
|
774
|
+
const stateText = state || `Working directory: ${hookInput.cwd || "unknown"}`;
|
|
775
|
+
const noteInfo = notePath ? `
|
|
776
|
+
SESSION NOTE: ${notePath}
|
|
777
|
+
If this note still has a generic title (e.g. "New Session", "Context Compression"),
|
|
778
|
+
rename it based on actual work done and add a rich summary.` : "";
|
|
672
779
|
const injection = [
|
|
673
780
|
"<system-reminder>",
|
|
674
781
|
`SESSION STATE RECOVERED AFTER COMPACTION (${compactType}, ~${tokenDisplay} tokens)`,
|
|
675
782
|
"",
|
|
676
|
-
|
|
783
|
+
stateText,
|
|
784
|
+
noteInfo,
|
|
677
785
|
"",
|
|
678
786
|
"IMPORTANT: This session state was captured before context compaction.",
|
|
679
787
|
"Use it to maintain continuity. Continue the conversation from where",
|