@tekmidian/pai 0.5.3 → 0.5.5
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 +284 -124
- 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 +90 -4
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +205 -146
|
@@ -150,6 +150,32 @@ async function sendNtfyNotification(message, retries = 2) {
|
|
|
150
150
|
console.error("ntfy.sh notification failed after all retries");
|
|
151
151
|
return false;
|
|
152
152
|
}
|
|
153
|
+
function getMonthDir(notesDir) {
|
|
154
|
+
const now = /* @__PURE__ */ new Date();
|
|
155
|
+
const year = String(now.getFullYear());
|
|
156
|
+
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
157
|
+
const monthDir = join2(notesDir, year, month);
|
|
158
|
+
if (!existsSync2(monthDir)) {
|
|
159
|
+
mkdirSync(monthDir, { recursive: true });
|
|
160
|
+
}
|
|
161
|
+
return monthDir;
|
|
162
|
+
}
|
|
163
|
+
function getNextNoteNumber(notesDir) {
|
|
164
|
+
const monthDir = getMonthDir(notesDir);
|
|
165
|
+
const files = readdirSync(monthDir).filter((f) => f.match(/^\d{3,4}[\s_-]/)).filter((f) => f.endsWith(".md")).sort();
|
|
166
|
+
if (files.length === 0) {
|
|
167
|
+
return "0001";
|
|
168
|
+
}
|
|
169
|
+
let maxNumber = 0;
|
|
170
|
+
for (const file of files) {
|
|
171
|
+
const digitMatch = file.match(/^(\d+)/);
|
|
172
|
+
if (digitMatch) {
|
|
173
|
+
const num = parseInt(digitMatch[1], 10);
|
|
174
|
+
if (num > maxNumber) maxNumber = num;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return String(maxNumber + 1).padStart(4, "0");
|
|
178
|
+
}
|
|
153
179
|
function getCurrentNotePath(notesDir) {
|
|
154
180
|
if (!existsSync2(notesDir)) {
|
|
155
181
|
return null;
|
|
@@ -178,10 +204,77 @@ function getCurrentNotePath(notesDir) {
|
|
|
178
204
|
if (prevFound) return prevFound;
|
|
179
205
|
return findLatestIn(notesDir);
|
|
180
206
|
}
|
|
207
|
+
function createSessionNote(notesDir, description) {
|
|
208
|
+
const noteNumber = getNextNoteNumber(notesDir);
|
|
209
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
210
|
+
const safeDescription = "New Session";
|
|
211
|
+
const monthDir = getMonthDir(notesDir);
|
|
212
|
+
const filename = `${noteNumber} - ${date} - ${safeDescription}.md`;
|
|
213
|
+
const filepath = join2(monthDir, filename);
|
|
214
|
+
const content = `# Session ${noteNumber}: ${description}
|
|
215
|
+
|
|
216
|
+
**Date:** ${date}
|
|
217
|
+
**Status:** In Progress
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Work Done
|
|
222
|
+
|
|
223
|
+
<!-- PAI will add completed work here during session -->
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Next Steps
|
|
228
|
+
|
|
229
|
+
<!-- To be filled at session end -->
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
**Tags:** #Session
|
|
234
|
+
`;
|
|
235
|
+
writeFileSync(filepath, content);
|
|
236
|
+
console.error(`Created session note: ${filename}`);
|
|
237
|
+
return filepath;
|
|
238
|
+
}
|
|
181
239
|
function appendCheckpoint(notePath, checkpoint) {
|
|
182
240
|
if (!existsSync2(notePath)) {
|
|
183
|
-
console.error(`Note file not found: ${notePath}`);
|
|
184
|
-
|
|
241
|
+
console.error(`Note file not found, recreating: ${notePath}`);
|
|
242
|
+
try {
|
|
243
|
+
const parentDir = join2(notePath, "..");
|
|
244
|
+
if (!existsSync2(parentDir)) {
|
|
245
|
+
mkdirSync(parentDir, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
const noteFilename = basename(notePath);
|
|
248
|
+
const numberMatch = noteFilename.match(/^(\d+)/);
|
|
249
|
+
const noteNumber = numberMatch ? numberMatch[1] : "0000";
|
|
250
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
251
|
+
const content2 = `# Session ${noteNumber}: Recovered
|
|
252
|
+
|
|
253
|
+
**Date:** ${date}
|
|
254
|
+
**Status:** In Progress
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Work Done
|
|
259
|
+
|
|
260
|
+
<!-- PAI will add completed work here during session -->
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Next Steps
|
|
265
|
+
|
|
266
|
+
<!-- To be filled at session end -->
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
**Tags:** #Session
|
|
271
|
+
`;
|
|
272
|
+
writeFileSync(notePath, content2);
|
|
273
|
+
console.error(`Recreated session note: ${noteFilename}`);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.error(`Failed to recreate note: ${err}`);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
185
278
|
}
|
|
186
279
|
const content = readFileSync2(notePath, "utf-8");
|
|
187
280
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -237,6 +330,35 @@ function addWorkToSessionNote(notePath, workItems, sectionTitle) {
|
|
|
237
330
|
writeFileSync(notePath, content);
|
|
238
331
|
console.error(`Added ${workItems.length} work item(s) to: ${basename(notePath)}`);
|
|
239
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
|
+
}
|
|
240
362
|
function calculateSessionTokens(jsonlPath) {
|
|
241
363
|
if (!existsSync2(jsonlPath)) {
|
|
242
364
|
return 0;
|
|
@@ -304,26 +426,38 @@ function ensureTodoMd(cwd) {
|
|
|
304
426
|
}
|
|
305
427
|
return todoPath;
|
|
306
428
|
}
|
|
307
|
-
function
|
|
429
|
+
function updateTodoContinue(cwd, noteFilename, state, tokenDisplay) {
|
|
308
430
|
const todoPath = ensureTodoMd(cwd);
|
|
309
431
|
let content = readFileSync2(todoPath, "utf-8");
|
|
310
|
-
content = content.replace(
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
---
|
|
315
443
|
|
|
316
444
|
`;
|
|
317
|
-
|
|
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);
|
|
449
|
+
} else {
|
|
450
|
+
content = continueSection + content;
|
|
318
451
|
}
|
|
452
|
+
content = content.replace(/(\n---\s*)*(\n\*Last updated:.*\*\s*)+$/g, "");
|
|
319
453
|
content = content.trimEnd() + `
|
|
320
454
|
|
|
321
455
|
---
|
|
322
456
|
|
|
323
|
-
*Last updated: ${
|
|
457
|
+
*Last updated: ${now}*
|
|
324
458
|
`;
|
|
325
459
|
writeFileSync(todoPath, content);
|
|
326
|
-
console.error(
|
|
460
|
+
console.error("TODO.md ## Continue section updated");
|
|
327
461
|
}
|
|
328
462
|
|
|
329
463
|
// src/hooks/ts/pre-compact/context-compression-hook.ts
|
|
@@ -360,15 +494,19 @@ function getTranscriptStats(transcriptPath) {
|
|
|
360
494
|
return { messageCount: 0, isLarge: false };
|
|
361
495
|
}
|
|
362
496
|
}
|
|
363
|
-
function
|
|
497
|
+
function parseTranscript(transcriptPath) {
|
|
498
|
+
const data = {
|
|
499
|
+
userMessages: [],
|
|
500
|
+
summaries: [],
|
|
501
|
+
captures: [],
|
|
502
|
+
lastCompleted: "",
|
|
503
|
+
filesModified: [],
|
|
504
|
+
workItems: []
|
|
505
|
+
};
|
|
364
506
|
try {
|
|
365
507
|
const raw = readFileSync3(transcriptPath, "utf-8");
|
|
366
508
|
const lines = raw.trim().split("\n");
|
|
367
|
-
const
|
|
368
|
-
const summaries = [];
|
|
369
|
-
const captures = [];
|
|
370
|
-
let lastCompleted = "";
|
|
371
|
-
const filesModified = /* @__PURE__ */ new Set();
|
|
509
|
+
const seenSummaries = /* @__PURE__ */ new Set();
|
|
372
510
|
for (const line of lines) {
|
|
373
511
|
if (!line.trim()) continue;
|
|
374
512
|
let entry;
|
|
@@ -379,124 +517,117 @@ function extractSessionState(transcriptPath, cwd) {
|
|
|
379
517
|
}
|
|
380
518
|
if (entry.type === "user" && entry.message?.content) {
|
|
381
519
|
const text = contentToText(entry.message.content).slice(0, 300);
|
|
382
|
-
if (text) userMessages.push(text);
|
|
520
|
+
if (text) data.userMessages.push(text);
|
|
383
521
|
}
|
|
384
522
|
if (entry.type === "assistant" && entry.message?.content) {
|
|
385
523
|
const text = contentToText(entry.message.content);
|
|
386
524
|
const summaryMatch = text.match(/SUMMARY:\s*(.+?)(?:\n|$)/i);
|
|
387
525
|
if (summaryMatch) {
|
|
388
526
|
const s = summaryMatch[1].trim();
|
|
389
|
-
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
|
+
}
|
|
390
540
|
}
|
|
391
541
|
const captureMatch = text.match(/CAPTURE:\s*(.+?)(?:\n|$)/i);
|
|
392
542
|
if (captureMatch) {
|
|
393
543
|
const c = captureMatch[1].trim();
|
|
394
|
-
if (c.length > 5 && !captures.includes(c)) captures.push(c);
|
|
544
|
+
if (c.length > 5 && !data.captures.includes(c)) data.captures.push(c);
|
|
395
545
|
}
|
|
396
546
|
const completedMatch = text.match(/COMPLETED:\s*(.+?)(?:\n|$)/i);
|
|
397
547
|
if (completedMatch) {
|
|
398
|
-
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
|
+
}
|
|
399
553
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
+
}
|
|
407
563
|
}
|
|
408
564
|
}
|
|
409
565
|
}
|
|
410
566
|
}
|
|
411
567
|
}
|
|
412
|
-
const parts = [];
|
|
413
|
-
if (cwd) {
|
|
414
|
-
parts.push(`Working directory: ${cwd}`);
|
|
415
|
-
}
|
|
416
|
-
const recentUser = userMessages.slice(-3);
|
|
417
|
-
if (recentUser.length > 0) {
|
|
418
|
-
parts.push("\nRecent user requests:");
|
|
419
|
-
for (const msg of recentUser) {
|
|
420
|
-
const firstLine = msg.split("\n")[0].slice(0, 200);
|
|
421
|
-
parts.push(`- ${firstLine}`);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
const recentSummaries = summaries.slice(-3);
|
|
425
|
-
if (recentSummaries.length > 0) {
|
|
426
|
-
parts.push("\nWork summaries:");
|
|
427
|
-
for (const s of recentSummaries) {
|
|
428
|
-
parts.push(`- ${s.slice(0, 150)}`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
const recentCaptures = captures.slice(-5);
|
|
432
|
-
if (recentCaptures.length > 0) {
|
|
433
|
-
parts.push("\nCaptured context:");
|
|
434
|
-
for (const c of recentCaptures) {
|
|
435
|
-
parts.push(`- ${c.slice(0, 150)}`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
const files = Array.from(filesModified).slice(-10);
|
|
439
|
-
if (files.length > 0) {
|
|
440
|
-
parts.push("\nFiles modified this session:");
|
|
441
|
-
for (const f of files) {
|
|
442
|
-
parts.push(`- ${f}`);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (lastCompleted) {
|
|
446
|
-
parts.push(`
|
|
447
|
-
Last completed: ${lastCompleted.slice(0, 150)}`);
|
|
448
|
-
}
|
|
449
|
-
const result = parts.join("\n");
|
|
450
|
-
return result.length > 50 ? result : null;
|
|
451
568
|
} catch (err) {
|
|
452
|
-
console.error(`
|
|
453
|
-
return null;
|
|
569
|
+
console.error(`parseTranscript error: ${err}`);
|
|
454
570
|
}
|
|
571
|
+
return data;
|
|
455
572
|
}
|
|
456
|
-
function
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
for (const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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)}`);
|
|
581
|
+
}
|
|
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;
|
|
494
619
|
}
|
|
495
620
|
}
|
|
496
|
-
return workItems;
|
|
497
|
-
} catch {
|
|
498
|
-
return [];
|
|
499
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);
|
|
500
631
|
}
|
|
501
632
|
async function main() {
|
|
502
633
|
let hookInput = null;
|
|
@@ -523,30 +654,59 @@ async function main() {
|
|
|
523
654
|
const stats = getTranscriptStats(hookInput.transcript_path);
|
|
524
655
|
tokenCount = calculateSessionTokens(hookInput.transcript_path);
|
|
525
656
|
const tokenDisplay = tokenCount > 1e3 ? `${Math.round(tokenCount / 1e3)}k` : String(tokenCount);
|
|
526
|
-
const
|
|
657
|
+
const data = parseTranscript(hookInput.transcript_path);
|
|
658
|
+
const state = formatSessionState(data, hookInput.cwd);
|
|
659
|
+
let notePath = null;
|
|
527
660
|
try {
|
|
528
661
|
const notesInfo = hookInput.cwd ? findNotesDir(hookInput.cwd) : { path: join3(dirname(hookInput.transcript_path), "Notes"), isLocal: false };
|
|
529
|
-
|
|
530
|
-
if (
|
|
531
|
-
|
|
662
|
+
notePath = getCurrentNotePath(notesInfo.path);
|
|
663
|
+
if (!notePath) {
|
|
664
|
+
console.error("No session note found \u2014 creating one for checkpoint");
|
|
665
|
+
notePath = createSessionNote(notesInfo.path, "Recovered Session");
|
|
666
|
+
} else {
|
|
667
|
+
try {
|
|
668
|
+
const noteContent = readFileSync3(notePath, "utf-8");
|
|
669
|
+
if (noteContent.includes("**Status:** Completed") || noteContent.includes("**Completed:**")) {
|
|
670
|
+
console.error(`Latest note is completed (${basename2(notePath)}) \u2014 creating new one`);
|
|
671
|
+
notePath = createSessionNote(notesInfo.path, "Continued Session");
|
|
672
|
+
}
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const checkpointBody = state ? `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.
|
|
532
677
|
|
|
533
678
|
${state}` : `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.`;
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
679
|
+
appendCheckpoint(notePath, checkpointBody);
|
|
680
|
+
if (data.workItems.length > 0) {
|
|
681
|
+
addWorkToSessionNote(notePath, data.workItems, `Pre-Compact (~${tokenDisplay} tokens)`);
|
|
682
|
+
console.error(`Added ${data.workItems.length} work item(s) to session note`);
|
|
683
|
+
}
|
|
684
|
+
const title = deriveTitle(data);
|
|
685
|
+
if (title) {
|
|
686
|
+
const newPath = renameSessionNote(notePath, title);
|
|
687
|
+
if (newPath !== notePath) {
|
|
688
|
+
try {
|
|
689
|
+
let noteContent = readFileSync3(newPath, "utf-8");
|
|
690
|
+
noteContent = noteContent.replace(
|
|
691
|
+
/^(# Session \d+:)\s*.*$/m,
|
|
692
|
+
`$1 ${title}`
|
|
693
|
+
);
|
|
694
|
+
writeFileSync2(newPath, noteContent);
|
|
695
|
+
console.error(`Updated note H1 to match rename`);
|
|
696
|
+
} catch {
|
|
697
|
+
}
|
|
698
|
+
notePath = newPath;
|
|
539
699
|
}
|
|
540
|
-
console.error(`Rich checkpoint saved: ${basename2(currentNotePath)}`);
|
|
541
700
|
}
|
|
701
|
+
console.error(`Rich checkpoint saved: ${basename2(notePath)}`);
|
|
542
702
|
} catch (noteError) {
|
|
543
703
|
console.error(`Could not save checkpoint: ${noteError}`);
|
|
544
704
|
}
|
|
545
|
-
if (hookInput.cwd &&
|
|
705
|
+
if (hookInput.cwd && notePath) {
|
|
546
706
|
try {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
console.error("TODO.md
|
|
707
|
+
const noteFilename = basename2(notePath);
|
|
708
|
+
updateTodoContinue(hookInput.cwd, noteFilename, state, tokenDisplay);
|
|
709
|
+
console.error("TODO.md ## Continue section updated");
|
|
550
710
|
} catch (todoError) {
|
|
551
711
|
console.error(`Could not update TODO.md: ${todoError}`);
|
|
552
712
|
}
|