@tekmidian/pai 0.7.1 → 0.7.3
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/cli/index.mjs +1 -1
- package/dist/daemon/index.mjs +1 -1
- package/dist/{daemon-DJoesjez.mjs → daemon-D8ZxcFhU.mjs} +615 -5
- package/dist/daemon-D8ZxcFhU.mjs.map +1 -0
- package/dist/hooks/context-compression-hook.mjs +140 -22
- package/dist/hooks/context-compression-hook.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +74 -4
- package/dist/hooks/load-project-context.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +70 -1
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
- package/dist/skills/Reconstruct/SKILL.md +232 -0
- package/package.json +1 -1
- package/plugins/productivity/plugin.json +1 -1
- package/plugins/productivity/skills/Reconstruct/SKILL.md +232 -0
- package/src/hooks/ts/lib/project-utils/index.ts +1 -0
- package/src/hooks/ts/lib/project-utils/session-notes.ts +24 -2
- package/src/hooks/ts/lib/project-utils.ts +1 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +159 -37
- package/src/hooks/ts/session-start/load-project-context.ts +101 -3
- package/src/hooks/ts/stop/stop-hook.ts +66 -0
- package/dist/daemon-DJoesjez.mjs.map +0 -1
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
5
5
|
import { basename as basename3, dirname, join as join6 } from "path";
|
|
6
6
|
import { tmpdir } from "os";
|
|
7
|
+
import { connect } from "net";
|
|
8
|
+
import { randomUUID } from "crypto";
|
|
7
9
|
|
|
8
10
|
// src/hooks/ts/lib/project-utils/paths.ts
|
|
9
11
|
import { existsSync as existsSync2, mkdirSync, readdirSync, renameSync } from "fs";
|
|
@@ -338,6 +340,33 @@ function addWorkToSessionNote(notePath, workItems, sectionTitle) {
|
|
|
338
340
|
writeFileSync(notePath, content);
|
|
339
341
|
console.error(`Added ${workItems.length} work item(s) to: ${basename2(notePath)}`);
|
|
340
342
|
}
|
|
343
|
+
function isMeaningfulTitle(text) {
|
|
344
|
+
return !isMeaninglessCandidate(text);
|
|
345
|
+
}
|
|
346
|
+
function isMeaninglessCandidate(text) {
|
|
347
|
+
const t = text.trim();
|
|
348
|
+
if (!t) return true;
|
|
349
|
+
if (t.length < 5) return true;
|
|
350
|
+
if (t.startsWith("/") || t.startsWith("~")) return true;
|
|
351
|
+
if (t.startsWith("#!")) return true;
|
|
352
|
+
if (t.includes("[object Object]")) return true;
|
|
353
|
+
if (/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/.test(t)) return true;
|
|
354
|
+
if (/^\d{1,2}:\d{2}(:\d{2})?(\s*(AM|PM))?$/i.test(t)) return true;
|
|
355
|
+
if (/^<[a-z-]+[\s/>]/i.test(t)) return true;
|
|
356
|
+
if (/^[0-9a-f]{10,}$/i.test(t)) return true;
|
|
357
|
+
if (/^Exit code \d+/i.test(t)) return true;
|
|
358
|
+
if (/^Error:/i.test(t)) return true;
|
|
359
|
+
if (/^This session is being continued/i.test(t)) return true;
|
|
360
|
+
if (/^\(Bash completed/i.test(t)) return true;
|
|
361
|
+
if (/^Task Notification$/i.test(t)) return true;
|
|
362
|
+
if (/^New Session$/i.test(t)) return true;
|
|
363
|
+
if (/^Recovered Session$/i.test(t)) return true;
|
|
364
|
+
if (/^Continued Session$/i.test(t)) return true;
|
|
365
|
+
if (/^Untitled Session$/i.test(t)) return true;
|
|
366
|
+
if (/^Context Compression$/i.test(t)) return true;
|
|
367
|
+
if (/^[A-Fa-f0-9]{8,}\s+Output$/i.test(t)) return true;
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
341
370
|
function renameSessionNote(notePath, meaningfulName) {
|
|
342
371
|
if (!meaningfulName || !existsSync4(notePath)) return notePath;
|
|
343
372
|
const dir = join4(notePath, "..");
|
|
@@ -452,6 +481,8 @@ ${stateLines}
|
|
|
452
481
|
}
|
|
453
482
|
|
|
454
483
|
// src/hooks/ts/pre-compact/context-compression-hook.ts
|
|
484
|
+
var DAEMON_SOCKET = process.env.PAI_SOCKET ?? "/tmp/pai.sock";
|
|
485
|
+
var DAEMON_TIMEOUT_MS = 3e3;
|
|
455
486
|
function contentToText(content) {
|
|
456
487
|
if (typeof content === "string") return content;
|
|
457
488
|
if (Array.isArray(content)) {
|
|
@@ -594,31 +625,39 @@ Last completed: ${data.lastCompleted.slice(0, 150)}`);
|
|
|
594
625
|
return result.length > 50 ? result : null;
|
|
595
626
|
}
|
|
596
627
|
function deriveTitle(data) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
628
|
+
const candidates = [];
|
|
629
|
+
for (let i = data.workItems.length - 1; i >= 0; i--) {
|
|
630
|
+
candidates.push(data.workItems[i].title);
|
|
631
|
+
}
|
|
632
|
+
for (let i = data.summaries.length - 1; i >= 0; i--) {
|
|
633
|
+
candidates.push(data.summaries[i]);
|
|
634
|
+
}
|
|
635
|
+
if (data.lastCompleted && data.lastCompleted.length > 5) {
|
|
636
|
+
candidates.push(data.lastCompleted);
|
|
637
|
+
}
|
|
638
|
+
for (const msg of data.userMessages) {
|
|
639
|
+
const line = msg.split("\n")[0].trim();
|
|
640
|
+
if (line.length > 10 && line.length < 80 && !line.toLowerCase().startsWith("yes") && !line.toLowerCase().startsWith("ok")) {
|
|
641
|
+
candidates.push(line);
|
|
611
642
|
}
|
|
612
643
|
}
|
|
613
|
-
if (
|
|
644
|
+
if (data.filesModified.length > 0) {
|
|
614
645
|
const basenames = data.filesModified.slice(-5).map((f) => {
|
|
615
646
|
const b = basename3(f);
|
|
616
647
|
return b.replace(/\.[^.]+$/, "");
|
|
617
648
|
});
|
|
618
649
|
const unique = [...new Set(basenames)];
|
|
619
|
-
|
|
650
|
+
candidates.push(
|
|
651
|
+
unique.length <= 3 ? `Updated ${unique.join(", ")}` : `Modified ${data.filesModified.length} files`
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
for (const raw of candidates) {
|
|
655
|
+
const cleaned = raw.replace(/[^\w\s-]/g, " ").replace(/\s+/g, " ").trim().substring(0, 60);
|
|
656
|
+
if (cleaned.length >= 5 && isMeaningfulTitle(cleaned)) {
|
|
657
|
+
return cleaned;
|
|
658
|
+
}
|
|
620
659
|
}
|
|
621
|
-
return
|
|
660
|
+
return "";
|
|
622
661
|
}
|
|
623
662
|
var CUMULATIVE_STATE_FILE = ".compact-state.json";
|
|
624
663
|
function loadCumulativeState(notesDir) {
|
|
@@ -668,6 +707,73 @@ function saveCumulativeState(notesDir, data, notePath) {
|
|
|
668
707
|
console.error(`Failed to save cumulative state: ${err}`);
|
|
669
708
|
}
|
|
670
709
|
}
|
|
710
|
+
function enqueueSessionSummary(payload) {
|
|
711
|
+
return new Promise((resolve2) => {
|
|
712
|
+
let done = false;
|
|
713
|
+
let buffer = "";
|
|
714
|
+
let timer = null;
|
|
715
|
+
function finish(ok) {
|
|
716
|
+
if (done) return;
|
|
717
|
+
done = true;
|
|
718
|
+
if (timer !== null) {
|
|
719
|
+
clearTimeout(timer);
|
|
720
|
+
timer = null;
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
client.destroy();
|
|
724
|
+
} catch {
|
|
725
|
+
}
|
|
726
|
+
resolve2(ok);
|
|
727
|
+
}
|
|
728
|
+
const client = connect(DAEMON_SOCKET, () => {
|
|
729
|
+
const msg = JSON.stringify({
|
|
730
|
+
id: randomUUID(),
|
|
731
|
+
method: "work_queue_enqueue",
|
|
732
|
+
params: {
|
|
733
|
+
type: "session-summary",
|
|
734
|
+
priority: 4,
|
|
735
|
+
// lower priority than session-end (2)
|
|
736
|
+
payload: {
|
|
737
|
+
cwd: payload.cwd,
|
|
738
|
+
sessionId: payload.sessionId,
|
|
739
|
+
transcriptPath: payload.transcriptPath
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}) + "\n";
|
|
743
|
+
client.write(msg);
|
|
744
|
+
});
|
|
745
|
+
client.on("data", (chunk) => {
|
|
746
|
+
buffer += chunk.toString();
|
|
747
|
+
const nl = buffer.indexOf("\n");
|
|
748
|
+
if (nl === -1) return;
|
|
749
|
+
const line = buffer.slice(0, nl);
|
|
750
|
+
try {
|
|
751
|
+
const response = JSON.parse(line);
|
|
752
|
+
if (response.ok) {
|
|
753
|
+
console.error(`PRE-COMPACT: Session summary enqueued with daemon (id=${response.result?.id}).`);
|
|
754
|
+
finish(true);
|
|
755
|
+
} else {
|
|
756
|
+
console.error(`PRE-COMPACT: Daemon rejected session-summary: ${response.error}`);
|
|
757
|
+
finish(false);
|
|
758
|
+
}
|
|
759
|
+
} catch {
|
|
760
|
+
finish(false);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
client.on("error", (e) => {
|
|
764
|
+
if (e.code === "ENOENT" || e.code === "ECONNREFUSED") {
|
|
765
|
+
console.error("PRE-COMPACT: Daemon not running \u2014 skipping session summary.");
|
|
766
|
+
} else {
|
|
767
|
+
console.error(`PRE-COMPACT: Daemon socket error: ${e.message}`);
|
|
768
|
+
}
|
|
769
|
+
finish(false);
|
|
770
|
+
});
|
|
771
|
+
timer = setTimeout(() => {
|
|
772
|
+
console.error("PRE-COMPACT: Daemon IPC timed out \u2014 skipping session summary.");
|
|
773
|
+
finish(false);
|
|
774
|
+
}, DAEMON_TIMEOUT_MS);
|
|
775
|
+
});
|
|
776
|
+
}
|
|
671
777
|
async function main() {
|
|
672
778
|
if (isProbeSession()) {
|
|
673
779
|
process.exit(0);
|
|
@@ -714,13 +820,14 @@ async function main() {
|
|
|
714
820
|
notePath = getCurrentNotePath(notesInfo.path);
|
|
715
821
|
if (!notePath) {
|
|
716
822
|
console.error("No session note found \u2014 creating one for checkpoint");
|
|
717
|
-
notePath = createSessionNote(notesInfo.path, "
|
|
823
|
+
notePath = createSessionNote(notesInfo.path, "Untitled Session");
|
|
718
824
|
} else {
|
|
719
825
|
try {
|
|
720
|
-
|
|
721
|
-
if (noteContent.includes("**Status:** Completed")
|
|
722
|
-
|
|
723
|
-
notePath
|
|
826
|
+
let noteContent = readFileSync6(notePath, "utf-8");
|
|
827
|
+
if (noteContent.includes("**Status:** Completed")) {
|
|
828
|
+
noteContent = noteContent.replace("**Status:** Completed", "**Status:** In Progress");
|
|
829
|
+
writeFileSync3(notePath, noteContent);
|
|
830
|
+
console.error(`Reopened completed note for continued session: ${basename3(notePath)}`);
|
|
724
831
|
}
|
|
725
832
|
} catch {
|
|
726
833
|
}
|
|
@@ -792,6 +899,17 @@ rename it based on actual work done and add a rich summary.` : "";
|
|
|
792
899
|
}
|
|
793
900
|
}
|
|
794
901
|
}
|
|
902
|
+
if (hookInput?.cwd) {
|
|
903
|
+
try {
|
|
904
|
+
await enqueueSessionSummary({
|
|
905
|
+
cwd: hookInput.cwd,
|
|
906
|
+
sessionId: hookInput.session_id,
|
|
907
|
+
transcriptPath: hookInput.transcript_path
|
|
908
|
+
});
|
|
909
|
+
} catch (err) {
|
|
910
|
+
console.error(`Could not enqueue session-summary: ${err}`);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
795
913
|
const ntfyMessage = tokenCount > 0 ? `Auto-pause: ~${Math.round(tokenCount / 1e3)}k tokens` : "Context compressing";
|
|
796
914
|
await sendNtfyNotification(ntfyMessage);
|
|
797
915
|
process.exit(0);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/hooks/ts/pre-compact/context-compression-hook.ts", "../../src/hooks/ts/lib/project-utils/paths.ts", "../../src/hooks/ts/lib/pai-paths.ts", "../../src/hooks/ts/lib/project-utils/notify.ts", "../../src/hooks/ts/lib/project-utils/session-notes.ts", "../../src/hooks/ts/lib/project-utils/tokens.ts", "../../src/hooks/ts/lib/project-utils/todo.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n/**\n * PreCompact Hook - Triggered before context compression\n *\n * Three critical jobs:\n * 1. Save rich checkpoint to session note \u2014 work items, state, meaningful rename\n * 2. Update TODO.md with a proper ## Continue section for the next session\n * 3. Save session state to temp file for post-compact injection via SessionStart(compact)\n *\n * Uses a CUMULATIVE state file (.compact-state.json) that persists across\n * compactions. This ensures that even after multiple compactions (where the\n * transcript becomes thin), we still have rich data for titles, summaries,\n * and work items from earlier in the session.\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { basename, dirname, join } from 'path';\nimport { tmpdir } from 'os';\nimport {\n sendNtfyNotification,\n getCurrentNotePath,\n createSessionNote,\n appendCheckpoint,\n addWorkToSessionNote,\n findNotesDir,\n renameSessionNote,\n updateTodoContinue,\n calculateSessionTokens,\n isProbeSession,\n WorkItem,\n} from '../lib/project-utils';\n\ninterface HookInput {\n session_id: string;\n transcript_path: string;\n cwd?: string;\n hook_event_name: string;\n compact_type?: string;\n trigger?: string;\n}\n\n/** Structured data extracted from a transcript in a single pass. */\ninterface TranscriptData {\n userMessages: string[];\n summaries: string[];\n captures: string[];\n lastCompleted: string;\n filesModified: string[];\n workItems: WorkItem[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Turn Claude content (string or content block array) into plain text. */\nfunction contentToText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .map((c) => {\n if (typeof c === 'string') return c;\n if (c?.text) return c.text;\n if (c?.content) return String(c.content);\n return '';\n })\n .join(' ')\n .trim();\n }\n return '';\n}\n\nfunction getTranscriptStats(transcriptPath: string): { messageCount: number; isLarge: boolean } {\n try {\n const content = readFileSync(transcriptPath, 'utf-8');\n const lines = content.trim().split('\\n');\n let userMessages = 0;\n let assistantMessages = 0;\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line);\n if (entry.type === 'user') userMessages++;\n else if (entry.type === 'assistant') assistantMessages++;\n } catch { /* skip */ }\n }\n const totalMessages = userMessages + assistantMessages;\n return { messageCount: totalMessages, isLarge: totalMessages > 50 };\n } catch {\n return { messageCount: 0, isLarge: false };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Unified transcript parser \u2014 single pass extracts everything\n// ---------------------------------------------------------------------------\n\nfunction parseTranscript(transcriptPath: string): TranscriptData {\n const data: TranscriptData = {\n userMessages: [],\n summaries: [],\n captures: [],\n lastCompleted: '',\n filesModified: [],\n workItems: [],\n };\n\n try {\n const raw = readFileSync(transcriptPath, 'utf-8');\n const lines = raw.trim().split('\\n');\n const seenSummaries = new Set<string>();\n\n for (const line of lines) {\n if (!line.trim()) continue;\n let entry: any;\n try { entry = JSON.parse(line); } catch { continue; }\n\n // --- User messages ---\n if (entry.type === 'user' && entry.message?.content) {\n const text = contentToText(entry.message.content).slice(0, 300);\n if (text) data.userMessages.push(text);\n }\n\n // --- Assistant content ---\n if (entry.type === 'assistant' && entry.message?.content) {\n const text = contentToText(entry.message.content);\n\n // Summaries \u2192 also create work items\n const summaryMatch = text.match(/SUMMARY:\\s*(.+?)(?:\\n|$)/i);\n if (summaryMatch) {\n const s = summaryMatch[1].trim();\n if (s.length > 5 && !data.summaries.includes(s)) {\n data.summaries.push(s);\n if (!seenSummaries.has(s)) {\n seenSummaries.add(s);\n const details: string[] = [];\n const actionsMatch = text.match(/ACTIONS:\\s*(.+?)(?=\\n[A-Z]+:|$)/is);\n if (actionsMatch) {\n const actionLines = actionsMatch[1].split('\\n')\n .map(l => l.replace(/^[-*\u2022]\\s*/, '').replace(/^\\d+\\.\\s*/, '').trim())\n .filter(l => l.length > 3 && l.length < 100);\n details.push(...actionLines.slice(0, 3));\n }\n data.workItems.push({ title: s, details: details.length > 0 ? details : undefined, completed: true });\n }\n }\n }\n\n // Captures\n const captureMatch = text.match(/CAPTURE:\\s*(.+?)(?:\\n|$)/i);\n if (captureMatch) {\n const c = captureMatch[1].trim();\n if (c.length > 5 && !data.captures.includes(c)) data.captures.push(c);\n }\n\n // Completed\n const completedMatch = text.match(/COMPLETED:\\s*(.+?)(?:\\n|$)/i);\n if (completedMatch) {\n data.lastCompleted = completedMatch[1].trim().replace(/\\*+/g, '');\n if (data.workItems.length === 0 && !seenSummaries.has(data.lastCompleted) && data.lastCompleted.length > 5) {\n seenSummaries.add(data.lastCompleted);\n data.workItems.push({ title: data.lastCompleted, completed: true });\n }\n }\n\n // File modifications (from tool_use blocks)\n if (Array.isArray(entry.message.content)) {\n for (const block of entry.message.content) {\n if (block.type === 'tool_use') {\n const tool = block.name;\n if ((tool === 'Edit' || tool === 'Write') && block.input?.file_path) {\n if (!data.filesModified.includes(block.input.file_path)) {\n data.filesModified.push(block.input.file_path);\n }\n }\n }\n }\n }\n }\n }\n } catch (err) {\n console.error(`parseTranscript error: ${err}`);\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Format session state as human-readable string\n// ---------------------------------------------------------------------------\n\nfunction formatSessionState(data: TranscriptData, cwd?: string): string | null {\n const parts: string[] = [];\n\n if (cwd) parts.push(`Working directory: ${cwd}`);\n\n const recentUser = data.userMessages.slice(-3);\n if (recentUser.length > 0) {\n parts.push('\\nRecent user requests:');\n for (const msg of recentUser) {\n parts.push(`- ${msg.split('\\n')[0].slice(0, 200)}`);\n }\n }\n\n const recentSummaries = data.summaries.slice(-3);\n if (recentSummaries.length > 0) {\n parts.push('\\nWork summaries:');\n for (const s of recentSummaries) parts.push(`- ${s.slice(0, 150)}`);\n }\n\n const recentCaptures = data.captures.slice(-5);\n if (recentCaptures.length > 0) {\n parts.push('\\nCaptured context:');\n for (const c of recentCaptures) parts.push(`- ${c.slice(0, 150)}`);\n }\n\n const files = data.filesModified.slice(-10);\n if (files.length > 0) {\n parts.push('\\nFiles modified this session:');\n for (const f of files) parts.push(`- ${f}`);\n }\n\n if (data.lastCompleted) {\n parts.push(`\\nLast completed: ${data.lastCompleted.slice(0, 150)}`);\n }\n\n const result = parts.join('\\n');\n return result.length > 50 ? result : null;\n}\n\n// ---------------------------------------------------------------------------\n// Derive a meaningful title for the session note\n// ---------------------------------------------------------------------------\n\nfunction deriveTitle(data: TranscriptData): string {\n let title = '';\n\n // 1. Last work item title (most descriptive of what was accomplished)\n if (data.workItems.length > 0) {\n title = data.workItems[data.workItems.length - 1].title;\n }\n // 2. Last summary\n else if (data.summaries.length > 0) {\n title = data.summaries[data.summaries.length - 1];\n }\n // 3. Last completed marker\n else if (data.lastCompleted && data.lastCompleted.length > 5) {\n title = data.lastCompleted;\n }\n // 4. Last substantive user message\n else if (data.userMessages.length > 0) {\n for (let i = data.userMessages.length - 1; i >= 0; i--) {\n const msg = data.userMessages[i].split('\\n')[0].trim();\n if (msg.length > 10 && msg.length < 80 &&\n !msg.toLowerCase().startsWith('yes') &&\n !msg.toLowerCase().startsWith('ok')) {\n title = msg;\n break;\n }\n }\n }\n // 5. Derive from files modified\n if (!title && data.filesModified.length > 0) {\n const basenames = data.filesModified.slice(-5).map(f => {\n const b = basename(f);\n return b.replace(/\\.[^.]+$/, '');\n });\n const unique = [...new Set(basenames)];\n title = unique.length <= 3\n ? `Updated ${unique.join(', ')}`\n : `Modified ${data.filesModified.length} files`;\n }\n\n // Clean up for filename use\n return title\n .replace(/[^\\w\\s-]/g, ' ') // Remove special chars\n .replace(/\\s+/g, ' ') // Normalize whitespace\n .trim()\n .substring(0, 60);\n}\n\n// ---------------------------------------------------------------------------\n// Cumulative state \u2014 persists across compactions in .compact-state.json\n// ---------------------------------------------------------------------------\n\nconst CUMULATIVE_STATE_FILE = '.compact-state.json';\n\nfunction loadCumulativeState(notesDir: string): TranscriptData | null {\n try {\n const filePath = join(notesDir, CUMULATIVE_STATE_FILE);\n if (!existsSync(filePath)) return null;\n const raw = JSON.parse(readFileSync(filePath, 'utf-8'));\n return {\n userMessages: raw.userMessages || [],\n summaries: raw.summaries || [],\n captures: raw.captures || [],\n lastCompleted: raw.lastCompleted || '',\n filesModified: raw.filesModified || [],\n workItems: raw.workItems || [],\n };\n } catch {\n return null;\n }\n}\n\nfunction mergeTranscriptData(accumulated: TranscriptData | null, current: TranscriptData): TranscriptData {\n if (!accumulated) return current;\n\n const mergeArrays = (a: string[], b: string[]): string[] => {\n const seen = new Set(a);\n return [...a, ...b.filter(x => !seen.has(x))];\n };\n\n const seenTitles = new Set(accumulated.workItems.map(w => w.title));\n const newWorkItems = current.workItems.filter(w => !seenTitles.has(w.title));\n\n return {\n userMessages: mergeArrays(accumulated.userMessages, current.userMessages).slice(-20),\n summaries: mergeArrays(accumulated.summaries, current.summaries),\n captures: mergeArrays(accumulated.captures, current.captures),\n lastCompleted: current.lastCompleted || accumulated.lastCompleted,\n filesModified: mergeArrays(accumulated.filesModified, current.filesModified),\n workItems: [...accumulated.workItems, ...newWorkItems],\n };\n}\n\nfunction saveCumulativeState(notesDir: string, data: TranscriptData, notePath: string | null): void {\n try {\n const filePath = join(notesDir, CUMULATIVE_STATE_FILE);\n writeFileSync(filePath, JSON.stringify({\n ...data,\n notePath,\n lastUpdated: new Date().toISOString(),\n }, null, 2));\n console.error(`Cumulative state saved (${data.workItems.length} work items, ${data.filesModified.length} files)`);\n } catch (err) {\n console.error(`Failed to save cumulative state: ${err}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nasync function main() {\n // Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)\n if (isProbeSession()) {\n process.exit(0);\n }\n\n let hookInput: HookInput | null = null;\n\n try {\n const decoder = new TextDecoder();\n let input = '';\n const timeoutPromise = new Promise<void>((resolve) => { setTimeout(resolve, 500); });\n const readPromise = (async () => {\n for await (const chunk of process.stdin) {\n input += decoder.decode(chunk, { stream: true });\n }\n })();\n await Promise.race([readPromise, timeoutPromise]);\n if (input.trim()) {\n hookInput = JSON.parse(input) as HookInput;\n }\n } catch {\n // Silently handle input errors\n }\n\n const compactType = hookInput?.compact_type || hookInput?.trigger || 'auto';\n let tokenCount = 0;\n\n if (hookInput?.transcript_path) {\n const stats = getTranscriptStats(hookInput.transcript_path);\n tokenCount = calculateSessionTokens(hookInput.transcript_path);\n const tokenDisplay = tokenCount > 1000\n ? `${Math.round(tokenCount / 1000)}k`\n : String(tokenCount);\n\n // -----------------------------------------------------------------\n // Single-pass transcript parsing + cumulative state merge\n // -----------------------------------------------------------------\n const data = parseTranscript(hookInput.transcript_path);\n\n // Find notes directory early \u2014 needed for cumulative state\n let notesInfo: { path: string; isLocal: boolean };\n try {\n notesInfo = hookInput.cwd\n ? findNotesDir(hookInput.cwd)\n : { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };\n } catch {\n notesInfo = { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };\n }\n\n // Load accumulated state from previous compactions and merge\n const accumulated = loadCumulativeState(notesInfo.path);\n const merged = mergeTranscriptData(accumulated, data);\n const state = formatSessionState(merged, hookInput.cwd);\n\n if (accumulated) {\n console.error(`Loaded cumulative state: ${accumulated.workItems.length} work items, ${accumulated.filesModified.length} files from previous compaction(s)`);\n }\n\n // -----------------------------------------------------------------\n // Persist session state to numbered session note (like \"pause session\")\n // -----------------------------------------------------------------\n let notePath: string | null = null;\n\n try {\n notePath = getCurrentNotePath(notesInfo.path);\n\n // If no note found, or the latest note is completed, create a new one\n if (!notePath) {\n console.error('No session note found \u2014 creating one for checkpoint');\n notePath = createSessionNote(notesInfo.path, 'Recovered Session');\n } else {\n try {\n const noteContent = readFileSync(notePath, 'utf-8');\n if (noteContent.includes('**Status:** Completed') || noteContent.includes('**Completed:**')) {\n console.error(`Latest note is completed (${basename(notePath)}) \u2014 creating new one`);\n notePath = createSessionNote(notesInfo.path, 'Continued Session');\n }\n } catch { /* proceed with existing note */ }\n }\n\n // 1. Write rich checkpoint with full session state\n const checkpointBody = state\n ? `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.\\n\\n${state}`\n : `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.`;\n appendCheckpoint(notePath, checkpointBody);\n\n // 2. Write work items to \"Work Done\" section (uses merged cumulative data)\n if (merged.workItems.length > 0) {\n addWorkToSessionNote(notePath, merged.workItems, `Pre-Compact (~${tokenDisplay} tokens)`);\n console.error(`Added ${merged.workItems.length} work item(s) to session note`);\n }\n\n // 3. Rename session note with a meaningful title (uses merged data for richer titles)\n const title = deriveTitle(merged);\n if (title) {\n const newPath = renameSessionNote(notePath, title);\n if (newPath !== notePath) {\n // Update H1 title inside the note to match\n try {\n let noteContent = readFileSync(newPath, 'utf-8');\n noteContent = noteContent.replace(\n /^(# Session \\d+:)\\s*.*$/m,\n `$1 ${title}`\n );\n writeFileSync(newPath, noteContent);\n console.error(`Updated note H1 to match rename`);\n } catch { /* ignore */ }\n notePath = newPath;\n }\n }\n\n console.error(`Rich checkpoint saved: ${basename(notePath)}`);\n } catch (noteError) {\n console.error(`Could not save checkpoint: ${noteError}`);\n }\n\n // Save cumulative state for next compaction\n saveCumulativeState(notesInfo.path, merged, notePath);\n\n // -----------------------------------------------------------------\n // Update TODO.md with proper ## Continue section (like \"pause session\")\n // -----------------------------------------------------------------\n if (hookInput.cwd && notePath) {\n try {\n const noteFilename = basename(notePath);\n updateTodoContinue(hookInput.cwd, noteFilename, state, tokenDisplay);\n console.error('TODO.md ## Continue section updated');\n } catch (todoError) {\n console.error(`Could not update TODO.md: ${todoError}`);\n }\n }\n\n // -----------------------------------------------------------------------\n // Save session state to temp file for post-compact injection.\n //\n // PreCompact hooks have NO stdout support (Claude Code ignores it).\n // Instead, we write the injection payload to a temp file keyed by\n // session_id. The SessionStart(compact) hook reads it and outputs\n // to stdout, which IS injected into the post-compaction context.\n //\n // Always fires (even with thin state) \u2014 includes note path so the AI\n // can enrich the session note post-compaction using its own context.\n // -----------------------------------------------------------------------\n if (hookInput.session_id) {\n const stateText = state || `Working directory: ${hookInput.cwd || 'unknown'}`;\n const noteInfo = notePath\n ? `\\nSESSION NOTE: ${notePath}\\nIf this note still has a generic title (e.g. \"New Session\", \"Context Compression\"),\\nrename it based on actual work done and add a rich summary.`\n : '';\n\n const injection = [\n '<system-reminder>',\n `SESSION STATE RECOVERED AFTER COMPACTION (${compactType}, ~${tokenDisplay} tokens)`,\n '',\n stateText,\n noteInfo,\n '',\n 'IMPORTANT: This session state was captured before context compaction.',\n 'Use it to maintain continuity. Continue the conversation from where',\n 'it left off without asking the user to repeat themselves.',\n 'Continue with the last task that you were asked to work on.',\n '</system-reminder>',\n ].join('\\n');\n\n try {\n const stateFile = join(tmpdir(), `pai-compact-state-${hookInput.session_id}.txt`);\n writeFileSync(stateFile, injection, 'utf-8');\n console.error(`Session state saved to ${stateFile} (${injection.length} chars)`);\n } catch (err) {\n console.error(`Failed to save state file: ${err}`);\n }\n }\n }\n\n // Send ntfy.sh notification\n const ntfyMessage = tokenCount > 0\n ? `Auto-pause: ~${Math.round(tokenCount / 1000)}k tokens`\n : 'Context compressing';\n await sendNtfyNotification(ntfyMessage);\n\n process.exit(0);\n}\n\nmain().catch(() => {\n process.exit(0);\n});\n", "/**\n * Path utilities \u2014 encoding, Notes/Sessions directory discovery and creation.\n */\n\nimport { existsSync, mkdirSync, readdirSync, renameSync } from 'fs';\nimport { join, basename } from 'path';\nimport { PAI_DIR } from '../pai-paths.js';\n\n// Re-export PAI_DIR for consumers\nexport { PAI_DIR };\nexport const PROJECTS_DIR = join(PAI_DIR, 'projects');\n\n/**\n * Directories known to be automated health-check / probe sessions.\n * Hooks should exit early for these to avoid registry clutter and wasted work.\n */\nconst PROBE_CWD_PATTERNS = [\n '/CodexBar/ClaudeProbe',\n '/ClaudeProbe',\n];\n\n/**\n * Check if the current working directory belongs to a probe/health-check session.\n * Returns true if hooks should skip this session entirely.\n */\nexport function isProbeSession(cwd?: string): boolean {\n const dir = cwd || process.cwd();\n return PROBE_CWD_PATTERNS.some(pattern => dir.includes(pattern));\n}\n\n/**\n * Encode a path the same way Claude Code does:\n * - Replace / with -\n * - Replace . with -\n * - Replace space with -\n */\nexport function encodePath(path: string): string {\n return path\n .replace(/\\//g, '-')\n .replace(/\\./g, '-')\n .replace(/ /g, '-');\n}\n\n/** Get the project directory for a given working directory. */\nexport function getProjectDir(cwd: string): string {\n const encoded = encodePath(cwd);\n return join(PROJECTS_DIR, encoded);\n}\n\n/** Get the Notes directory for a project (central location). */\nexport function getNotesDir(cwd: string): string {\n return join(getProjectDir(cwd), 'Notes');\n}\n\n/**\n * Find Notes directory \u2014 checks local first, falls back to central.\n * Does NOT create the directory.\n */\nexport function findNotesDir(cwd: string): { path: string; isLocal: boolean } {\n const cwdBasename = basename(cwd).toLowerCase();\n if (cwdBasename === 'notes' && existsSync(cwd)) {\n return { path: cwd, isLocal: true };\n }\n\n const localPaths = [\n join(cwd, 'Notes'),\n join(cwd, 'notes'),\n join(cwd, '.claude', 'Notes'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) {\n return { path, isLocal: true };\n }\n }\n\n return { path: getNotesDir(cwd), isLocal: false };\n}\n\n/** Get the sessions/ directory for a project (stores .jsonl transcripts). */\nexport function getSessionsDir(cwd: string): string {\n return join(getProjectDir(cwd), 'sessions');\n}\n\n/** Get the sessions/ directory from a project directory path. */\nexport function getSessionsDirFromProjectDir(projectDir: string): string {\n return join(projectDir, 'sessions');\n}\n\n// ---------------------------------------------------------------------------\n// Directory creation helpers\n// ---------------------------------------------------------------------------\n\n/** Ensure the Notes directory exists for a project. @deprecated Use ensureNotesDirSmart() */\nexport function ensureNotesDir(cwd: string): string {\n const notesDir = getNotesDir(cwd);\n if (!existsSync(notesDir)) {\n mkdirSync(notesDir, { recursive: true });\n console.error(`Created Notes directory: ${notesDir}`);\n }\n return notesDir;\n}\n\n/**\n * Smart Notes directory handling:\n * - If local Notes/ exists \u2192 use it (don't create anything new)\n * - If no local Notes/ \u2192 ensure central exists and use that\n */\nexport function ensureNotesDirSmart(cwd: string): { path: string; isLocal: boolean } {\n const found = findNotesDir(cwd);\n if (found.isLocal) return found;\n if (!existsSync(found.path)) {\n mkdirSync(found.path, { recursive: true });\n console.error(`Created central Notes directory: ${found.path}`);\n }\n return found;\n}\n\n/** Ensure the sessions/ directory exists for a project. */\nexport function ensureSessionsDir(cwd: string): string {\n const sessionsDir = getSessionsDir(cwd);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/** Ensure the sessions/ directory exists (from project dir path). */\nexport function ensureSessionsDirFromProjectDir(projectDir: string): string {\n const sessionsDir = getSessionsDirFromProjectDir(projectDir);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/**\n * Move all .jsonl session files from project root to sessions/ subdirectory.\n * Returns the number of files moved.\n */\nexport function moveSessionFilesToSessionsDir(\n projectDir: string,\n excludeFile?: string,\n silent = false\n): number {\n const sessionsDir = ensureSessionsDirFromProjectDir(projectDir);\n\n if (!existsSync(projectDir)) return 0;\n\n const files = readdirSync(projectDir);\n let movedCount = 0;\n\n for (const file of files) {\n if (file.endsWith('.jsonl') && file !== excludeFile) {\n const sourcePath = join(projectDir, file);\n const destPath = join(sessionsDir, file);\n try {\n renameSync(sourcePath, destPath);\n if (!silent) console.error(`Moved ${file} \u2192 sessions/`);\n movedCount++;\n } catch (error) {\n if (!silent) console.error(`Could not move ${file}: ${error}`);\n }\n }\n }\n\n return movedCount;\n}\n\n// ---------------------------------------------------------------------------\n// CLAUDE.md / TODO.md discovery\n// ---------------------------------------------------------------------------\n\n/** Find TODO.md \u2014 check local first, fallback to central. */\nexport function findTodoPath(cwd: string): string {\n const localPaths = [\n join(cwd, 'TODO.md'),\n join(cwd, 'notes', 'TODO.md'),\n join(cwd, 'Notes', 'TODO.md'),\n join(cwd, '.claude', 'TODO.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) return path;\n }\n\n return join(getNotesDir(cwd), 'TODO.md');\n}\n\n/** Find CLAUDE.md \u2014 returns the FIRST found path. */\nexport function findClaudeMdPath(cwd: string): string | null {\n const paths = findAllClaudeMdPaths(cwd);\n return paths.length > 0 ? paths[0] : null;\n}\n\n/**\n * Find ALL CLAUDE.md files in local locations in priority order.\n */\nexport function findAllClaudeMdPaths(cwd: string): string[] {\n const foundPaths: string[] = [];\n\n const localPaths = [\n join(cwd, '.claude', 'CLAUDE.md'),\n join(cwd, 'CLAUDE.md'),\n join(cwd, 'Notes', 'CLAUDE.md'),\n join(cwd, 'notes', 'CLAUDE.md'),\n join(cwd, 'Prompts', 'CLAUDE.md'),\n join(cwd, 'prompts', 'CLAUDE.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) foundPaths.push(path);\n }\n\n return foundPaths;\n}\n", "/**\n * PAI Path Resolution - Single Source of Truth\n *\n * This module provides consistent path resolution across all PAI hooks.\n * It handles PAI_DIR detection whether set explicitly or defaulting to ~/.claude\n *\n * ALSO loads .env file from PAI_DIR so all hooks get environment variables\n * without relying on Claude Code's settings.json injection.\n *\n * Usage in hooks:\n * import { PAI_DIR, HOOKS_DIR, SKILLS_DIR } from './lib/pai-paths';\n */\n\nimport { homedir } from 'os';\nimport { resolve, join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\n\n/**\n * Load .env file and inject into process.env\n * Must run BEFORE PAI_DIR resolution so .env can set PAI_DIR if needed\n */\nfunction loadEnvFile(): void {\n // Check common locations for .env\n const possiblePaths = [\n resolve(process.env.PAI_DIR || '', '.env'),\n resolve(homedir(), '.claude', '.env'),\n ];\n\n for (const envPath of possiblePaths) {\n if (existsSync(envPath)) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n // Skip comments and empty lines\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex > 0) {\n const key = trimmed.substring(0, eqIndex).trim();\n let value = trimmed.substring(eqIndex + 1).trim();\n\n // Remove surrounding quotes if present\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n\n // Expand $HOME and ~ in values\n value = value.replace(/\\$HOME/g, homedir());\n value = value.replace(/^~(?=\\/|$)/, homedir());\n\n // Only set if not already defined (env vars take precedence)\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\n }\n // Found and loaded, don't check other paths\n break;\n } catch {\n // Silently continue if .env can't be read\n }\n }\n }\n}\n\n// Load .env FIRST, before any other initialization\nloadEnvFile();\n\n/**\n * Smart PAI_DIR detection with fallback\n * Priority:\n * 1. PAI_DIR environment variable (if set)\n * 2. ~/.claude (standard location)\n */\nexport const PAI_DIR = process.env.PAI_DIR\n ? resolve(process.env.PAI_DIR)\n : resolve(homedir(), '.claude');\n\n/**\n * Common PAI directories\n */\nexport const HOOKS_DIR = join(PAI_DIR, 'Hooks');\nexport const SKILLS_DIR = join(PAI_DIR, 'Skills');\nexport const AGENTS_DIR = join(PAI_DIR, 'Agents');\nexport const HISTORY_DIR = join(PAI_DIR, 'History');\nexport const COMMANDS_DIR = join(PAI_DIR, 'Commands');\n\n/**\n * Validate PAI directory structure on first import\n * This fails fast with a clear error if PAI is misconfigured\n */\nfunction validatePAIStructure(): void {\n if (!existsSync(PAI_DIR)) {\n console.error(`PAI_DIR does not exist: ${PAI_DIR}`);\n console.error(` Expected ~/.claude or set PAI_DIR environment variable`);\n process.exit(1);\n }\n\n if (!existsSync(HOOKS_DIR)) {\n console.error(`PAI hooks directory not found: ${HOOKS_DIR}`);\n console.error(` Your PAI_DIR may be misconfigured`);\n console.error(` Current PAI_DIR: ${PAI_DIR}`);\n process.exit(1);\n }\n}\n\n// Run validation on module import\n// This ensures any hook that imports this module will fail fast if paths are wrong\nvalidatePAIStructure();\n\n/**\n * Helper to get history file path with date-based organization\n */\nexport function getHistoryFilePath(subdir: string, filename: string): string {\n const now = new Date();\n const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone;\n const localDate = new Date(now.toLocaleString('en-US', { timeZone: tz }));\n const year = localDate.getFullYear();\n const month = String(localDate.getMonth() + 1).padStart(2, '0');\n\n return join(HISTORY_DIR, subdir, `${year}-${month}`, filename);\n}\n", "/**\n * Push notification helpers \u2014 WhatsApp-aware with ntfy.sh fallback.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n/**\n * Check if a messaging MCP server (AIBroker, Whazaa, or Telex) is configured.\n * When any messaging server is active, the AI handles notifications via MCP\n * and ntfy is skipped to avoid duplicates.\n */\nexport function isWhatsAppEnabled(): boolean {\n try {\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const enabled: string[] = settings.enabledMcpjsonServers || [];\n return enabled.includes('aibroker') || enabled.includes('whazaa') || enabled.includes('telex');\n } catch {\n return false;\n }\n}\n\n/**\n * Send push notification \u2014 WhatsApp-aware with ntfy fallback.\n *\n * When WhatsApp (Whazaa) is enabled in MCP config, ntfy is SKIPPED\n * because the AI sends WhatsApp messages directly via MCP.\n * When WhatsApp is NOT configured, ntfy fires as the fallback channel.\n */\nexport async function sendNtfyNotification(message: string, retries = 2): Promise<boolean> {\n if (isWhatsAppEnabled()) {\n console.error(`WhatsApp (Whazaa) enabled in MCP config \u2014 skipping ntfy`);\n return true;\n }\n\n const topic = process.env.NTFY_TOPIC;\n\n if (!topic) {\n console.error('NTFY_TOPIC not set and WhatsApp not active \u2014 notifications disabled');\n return false;\n }\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const response = await fetch(`https://ntfy.sh/${topic}`, {\n method: 'POST',\n body: message,\n headers: {\n 'Title': 'Claude Code',\n 'Priority': 'default',\n },\n });\n\n if (response.ok) {\n console.error(`ntfy.sh notification sent (WhatsApp inactive): \"${message}\"`);\n return true;\n } else {\n console.error(`ntfy.sh attempt ${attempt + 1} failed: ${response.status}`);\n }\n } catch (error) {\n console.error(`ntfy.sh attempt ${attempt + 1} error: ${error}`);\n }\n\n if (attempt < retries) {\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n }\n\n console.error('ntfy.sh notification failed after all retries');\n return false;\n}\n", "/**\n * Session note creation, editing, checkpointing, renaming, and finalization.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, renameSync } from 'fs';\nimport { join, basename } from 'path';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Get or create the YYYY/MM subdirectory for the current month inside notesDir. */\nfunction getMonthDir(notesDir: string): string {\n const now = new Date();\n const year = String(now.getFullYear());\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const monthDir = join(notesDir, year, month);\n if (!existsSync(monthDir)) {\n mkdirSync(monthDir, { recursive: true });\n }\n return monthDir;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Get the next note number (4-digit format: 0001, 0002, etc.).\n * Numbers are scoped per YYYY/MM directory.\n */\nexport function getNextNoteNumber(notesDir: string): string {\n const monthDir = getMonthDir(notesDir);\n\n const files = readdirSync(monthDir)\n .filter(f => f.match(/^\\d{3,4}[\\s_-]/))\n .sort();\n\n if (files.length === 0) return '0001';\n\n let maxNumber = 0;\n for (const file of files) {\n const digitMatch = file.match(/^(\\d+)/);\n if (digitMatch) {\n const num = parseInt(digitMatch[1], 10);\n if (num > maxNumber) maxNumber = num;\n }\n }\n\n return String(maxNumber + 1).padStart(4, '0');\n}\n\n/**\n * Get the current (latest) note file path, or null if none exists.\n * Searches current month \u2192 previous month \u2192 flat notesDir (legacy).\n */\nexport function getCurrentNotePath(notesDir: string): string | null {\n if (!existsSync(notesDir)) return null;\n\n const findLatestIn = (dir: string): string | null => {\n if (!existsSync(dir)) return null;\n const files = readdirSync(dir)\n .filter(f => f.match(/^\\d{3,4}[\\s_-].*\\.md$/))\n .sort((a, b) => {\n const numA = parseInt(a.match(/^(\\d+)/)?.[1] || '0', 10);\n const numB = parseInt(b.match(/^(\\d+)/)?.[1] || '0', 10);\n return numA - numB;\n });\n if (files.length === 0) return null;\n return join(dir, files[files.length - 1]);\n };\n\n const now = new Date();\n const year = String(now.getFullYear());\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const currentMonthDir = join(notesDir, year, month);\n const found = findLatestIn(currentMonthDir);\n if (found) return found;\n\n const prevDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n const prevYear = String(prevDate.getFullYear());\n const prevMonth = String(prevDate.getMonth() + 1).padStart(2, '0');\n const prevMonthDir = join(notesDir, prevYear, prevMonth);\n const prevFound = findLatestIn(prevMonthDir);\n if (prevFound) return prevFound;\n\n return findLatestIn(notesDir);\n}\n\n/**\n * Create a new session note.\n * Format: \"NNNN - YYYY-MM-DD - New Session.md\" filed into YYYY/MM subdirectory.\n * Claude MUST rename at session end with a meaningful description.\n */\nexport function createSessionNote(notesDir: string, description: string): string {\n const noteNumber = getNextNoteNumber(notesDir);\n const date = new Date().toISOString().split('T')[0];\n const monthDir = getMonthDir(notesDir);\n const filename = `${noteNumber} - ${date} - New Session.md`;\n const filepath = join(monthDir, filename);\n\n const content = `# Session ${noteNumber}: ${description}\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`;\n\n writeFileSync(filepath, content);\n console.error(`Created session note: ${filename}`);\n\n return filepath;\n}\n\n/** Append a checkpoint to the current session note. */\nexport function appendCheckpoint(notePath: string, checkpoint: string): void {\n if (!existsSync(notePath)) {\n console.error(`Note file not found, recreating: ${notePath}`);\n try {\n const parentDir = join(notePath, '..');\n if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });\n const noteFilename = basename(notePath);\n const numberMatch = noteFilename.match(/^(\\d+)/);\n const noteNumber = numberMatch ? numberMatch[1] : '0000';\n const date = new Date().toISOString().split('T')[0];\n 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`;\n writeFileSync(notePath, content);\n console.error(`Recreated session note: ${noteFilename}`);\n } catch (err) {\n console.error(`Failed to recreate note: ${err}`);\n return;\n }\n }\n\n const content = readFileSync(notePath, 'utf-8');\n const timestamp = new Date().toISOString();\n const checkpointText = `\\n### Checkpoint ${timestamp}\\n\\n${checkpoint}\\n`;\n\n const nextStepsIndex = content.indexOf('## Next Steps');\n const newContent = nextStepsIndex !== -1\n ? content.substring(0, nextStepsIndex) + checkpointText + content.substring(nextStepsIndex)\n : content + checkpointText;\n\n writeFileSync(notePath, newContent);\n console.error(`Checkpoint added to: ${basename(notePath)}`);\n}\n\n/** Work item for session notes. */\nexport interface WorkItem {\n title: string;\n details?: string[];\n completed?: boolean;\n}\n\n/** Add work items to the \"Work Done\" section of a session note. */\nexport function addWorkToSessionNote(notePath: string, workItems: WorkItem[], sectionTitle?: string): void {\n if (!existsSync(notePath)) {\n console.error(`Note file not found: ${notePath}`);\n return;\n }\n\n let content = readFileSync(notePath, 'utf-8');\n\n let workText = '';\n if (sectionTitle) workText += `\\n### ${sectionTitle}\\n\\n`;\n\n for (const item of workItems) {\n const checkbox = item.completed !== false ? '[x]' : '[ ]';\n workText += `- ${checkbox} **${item.title}**\\n`;\n if (item.details && item.details.length > 0) {\n for (const detail of item.details) {\n workText += ` - ${detail}\\n`;\n }\n }\n }\n\n const workDoneMatch = content.match(/## Work Done\\n\\n(<!-- .*? -->)?/);\n if (workDoneMatch) {\n const insertPoint = content.indexOf(workDoneMatch[0]) + workDoneMatch[0].length;\n content = content.substring(0, insertPoint) + workText + content.substring(insertPoint);\n } else {\n const nextStepsIndex = content.indexOf('## Next Steps');\n if (nextStepsIndex !== -1) {\n content = content.substring(0, nextStepsIndex) + workText + '\\n' + content.substring(nextStepsIndex);\n }\n }\n\n writeFileSync(notePath, content);\n console.error(`Added ${workItems.length} work item(s) to: ${basename(notePath)}`);\n}\n\n/** Sanitize a string for use in a filename. */\nexport function sanitizeForFilename(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .substring(0, 50);\n}\n\n/**\n * Return true if the candidate string should be rejected as a meaningful name.\n * Rejects file paths, shebangs, timestamps, and \"[object Object]\" artifacts.\n */\nfunction isMeaninglessCandidate(text: string): boolean {\n const t = text.trim();\n if (!t) return true;\n if (t.startsWith('/')) return true; // file path\n if (t.startsWith('#!')) return true; // shebang\n if (t.includes('[object Object]')) return true; // serialization artifact\n if (/^\\d{4}-\\d{2}-\\d{2}(T[\\d:.Z+-]+)?$/.test(t)) return true; // ISO timestamp\n if (/^\\d{1,2}:\\d{2}(:\\d{2})?(\\s*(AM|PM))?$/i.test(t)) return true; // time-only\n return false;\n}\n\n/**\n * Extract a meaningful name from session note content and summary.\n * Looks at Work Done section headers, bold text, and summary.\n */\nexport function extractMeaningfulName(noteContent: string, summary: string): string {\n const workDoneMatch = noteContent.match(/## Work Done\\n\\n([\\s\\S]*?)(?=\\n---|\\n## Next)/);\n\n if (workDoneMatch) {\n const workDoneSection = workDoneMatch[1];\n\n const subheadings = workDoneSection.match(/### ([^\\n]+)/g);\n if (subheadings && subheadings.length > 0) {\n const firstHeading = subheadings[0].replace('### ', '').trim();\n if (!isMeaninglessCandidate(firstHeading) && firstHeading.length > 5 && firstHeading.length < 60) {\n return sanitizeForFilename(firstHeading);\n }\n }\n\n const boldMatches = workDoneSection.match(/\\*\\*([^*]+)\\*\\*/g);\n if (boldMatches && boldMatches.length > 0) {\n const firstBold = boldMatches[0].replace(/\\*\\*/g, '').trim();\n if (!isMeaninglessCandidate(firstBold) && firstBold.length > 3 && firstBold.length < 50) {\n return sanitizeForFilename(firstBold);\n }\n }\n\n const numberedItems = workDoneSection.match(/^\\d+\\.\\s+\\*\\*([^*]+)\\*\\*/m);\n if (numberedItems && !isMeaninglessCandidate(numberedItems[1])) {\n return sanitizeForFilename(numberedItems[1]);\n }\n }\n\n if (summary && summary.length > 5 && summary !== 'Session completed.' && !isMeaninglessCandidate(summary)) {\n const cleanSummary = summary\n .replace(/[^\\w\\s-]/g, ' ')\n .trim()\n .split(/\\s+/)\n .slice(0, 5)\n .join(' ');\n if (cleanSummary.length > 3 && !isMeaninglessCandidate(cleanSummary)) {\n return sanitizeForFilename(cleanSummary);\n }\n }\n\n return '';\n}\n\n/**\n * Rename a session note with a meaningful name.\n * Always uses \"NNNN - YYYY-MM-DD - Description.md\" format.\n * Returns the new path, or original path if rename fails.\n */\nexport function renameSessionNote(notePath: string, meaningfulName: string): string {\n if (!meaningfulName || !existsSync(notePath)) return notePath;\n\n const dir = join(notePath, '..');\n const oldFilename = basename(notePath);\n\n const correctMatch = oldFilename.match(/^(\\d{3,4}) - (\\d{4}-\\d{2}-\\d{2}) - .*\\.md$/);\n const legacyMatch = oldFilename.match(/^(\\d{3,4})_(\\d{4}-\\d{2}-\\d{2})_.*\\.md$/);\n const match = correctMatch || legacyMatch;\n if (!match) return notePath;\n\n const [, noteNumber, date] = match;\n\n const titleCaseName = meaningfulName\n .split(/[\\s_-]+/)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n .trim();\n\n const paddedNumber = noteNumber.padStart(4, '0');\n const newFilename = `${paddedNumber} - ${date} - ${titleCaseName}.md`;\n const newPath = join(dir, newFilename);\n\n if (newFilename === oldFilename) return notePath;\n\n try {\n renameSync(notePath, newPath);\n console.error(`Renamed note: ${oldFilename} \u2192 ${newFilename}`);\n return newPath;\n } catch (error) {\n console.error(`Could not rename note: ${error}`);\n return notePath;\n }\n}\n\n/** Update the session note's H1 title and rename the file. */\nexport function updateSessionNoteTitle(notePath: string, newTitle: string): void {\n if (!existsSync(notePath)) {\n console.error(`Note file not found: ${notePath}`);\n return;\n }\n\n let content = readFileSync(notePath, 'utf-8');\n content = content.replace(/^# Session \\d+:.*$/m, (match) => {\n const sessionNum = match.match(/Session (\\d+)/)?.[1] || '';\n return `# Session ${sessionNum}: ${newTitle}`;\n });\n writeFileSync(notePath, content);\n renameSessionNote(notePath, sanitizeForFilename(newTitle));\n}\n\n/**\n * Finalize session note \u2014 mark as complete, add summary, rename with meaningful name.\n * IDEMPOTENT: subsequent calls are no-ops if already finalized.\n * Returns the final path (may be renamed).\n */\nexport function finalizeSessionNote(notePath: string, summary: string): string {\n if (!existsSync(notePath)) {\n console.error(`Note file not found: ${notePath}`);\n return notePath;\n }\n\n let content = readFileSync(notePath, 'utf-8');\n\n if (content.includes('**Status:** Completed')) {\n console.error(`Note already finalized: ${basename(notePath)}`);\n return notePath;\n }\n\n content = content.replace('**Status:** In Progress', '**Status:** Completed');\n\n if (!content.includes('**Completed:**')) {\n const completionTime = new Date().toISOString();\n content = content.replace(\n '---\\n\\n## Work Done',\n `**Completed:** ${completionTime}\\n\\n---\\n\\n## Work Done`\n );\n }\n\n const nextStepsMatch = content.match(/## Next Steps\\n\\n(<!-- .*? -->)/);\n if (nextStepsMatch) {\n content = content.replace(\n nextStepsMatch[0],\n `## Next Steps\\n\\n${summary || 'Session completed.'}`\n );\n }\n\n writeFileSync(notePath, content);\n console.error(`Session note finalized: ${basename(notePath)}`);\n\n const meaningfulName = extractMeaningfulName(content, summary);\n if (meaningfulName) {\n return renameSessionNote(notePath, meaningfulName);\n }\n\n return notePath;\n}\n", "/**\n * Session token counting from .jsonl transcript files.\n */\n\nimport { existsSync, readFileSync } from 'fs';\n\n/**\n * Calculate total tokens from a session .jsonl file.\n * Sums input, output, cache_creation, and cache_read tokens.\n */\nexport function calculateSessionTokens(jsonlPath: string): number {\n if (!existsSync(jsonlPath)) return 0;\n\n try {\n const content = readFileSync(jsonlPath, 'utf-8');\n const lines = content.trim().split('\\n');\n let totalTokens = 0;\n\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n if (entry.message?.usage) {\n const usage = entry.message.usage;\n totalTokens += (usage.input_tokens || 0);\n totalTokens += (usage.output_tokens || 0);\n totalTokens += (usage.cache_creation_input_tokens || 0);\n totalTokens += (usage.cache_read_input_tokens || 0);\n }\n } catch {\n // Skip invalid JSON lines\n }\n }\n\n return totalTokens;\n } catch (error) {\n console.error(`Error calculating tokens: ${error}`);\n return 0;\n }\n}\n", "/**\n * TODO.md management \u2014 creation, task updates, checkpoints, and Continue section.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { findTodoPath } from './paths.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Task item for TODO.md. */\nexport interface TodoItem {\n content: string;\n completed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure TODO.md exists. Creates it with default structure if missing.\n * Returns the path to the TODO.md file.\n */\nexport function ensureTodoMd(cwd: string): string {\n const todoPath = findTodoPath(cwd);\n\n if (!existsSync(todoPath)) {\n const parentDir = join(todoPath, '..');\n if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });\n\n const content = `# TODO\n\n## Current Session\n\n- [ ] (Tasks will be tracked here)\n\n## Backlog\n\n- [ ] (Future tasks)\n\n---\n\n*Last updated: ${new Date().toISOString()}*\n`;\n\n writeFileSync(todoPath, content);\n console.error(`Created TODO.md: ${todoPath}`);\n }\n\n return todoPath;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Update TODO.md with current session tasks.\n * Preserves the Backlog section and ensures exactly ONE timestamp at the end.\n */\nexport function updateTodoMd(cwd: string, tasks: TodoItem[], sessionSummary?: string): void {\n const todoPath = ensureTodoMd(cwd);\n const content = readFileSync(todoPath, 'utf-8');\n\n const backlogMatch = content.match(/## Backlog[\\s\\S]*?(?=\\n---|\\n\\*Last updated|$)/);\n const backlogSection = backlogMatch\n ? backlogMatch[0].trim()\n : '## Backlog\\n\\n- [ ] (Future tasks)';\n\n const taskLines = tasks.length > 0\n ? tasks.map(t => `- [${t.completed ? 'x' : ' '}] ${t.content}`).join('\\n')\n : '- [ ] (No active tasks)';\n\n const newContent = `# TODO\n\n## Current Session\n\n${taskLines}\n\n${sessionSummary ? `**Session Summary:** ${sessionSummary}\\n\\n` : ''}${backlogSection}\n\n---\n\n*Last updated: ${new Date().toISOString()}*\n`;\n\n writeFileSync(todoPath, newContent);\n console.error(`Updated TODO.md: ${todoPath}`);\n}\n\n/**\n * Add a checkpoint entry to TODO.md (without replacing tasks).\n * Ensures exactly ONE timestamp line at the end.\n */\nexport function addTodoCheckpoint(cwd: string, checkpoint: string): void {\n const todoPath = ensureTodoMd(cwd);\n let content = readFileSync(todoPath, 'utf-8');\n\n // Remove ALL existing timestamp lines and trailing separators\n content = content.replace(/(\\n---\\s*)*(\\n\\*Last updated:.*\\*\\s*)+$/g, '');\n\n const checkpointText = `\\n**Checkpoint (${new Date().toISOString()}):** ${checkpoint}\\n\\n`;\n\n const backlogIndex = content.indexOf('## Backlog');\n if (backlogIndex !== -1) {\n content = content.substring(0, backlogIndex) + checkpointText + content.substring(backlogIndex);\n } else {\n const continueIndex = content.indexOf('## Continue');\n if (continueIndex !== -1) {\n const afterContinue = content.indexOf('\\n---', continueIndex);\n if (afterContinue !== -1) {\n const insertAt = afterContinue + 4;\n content = content.substring(0, insertAt) + '\\n' + checkpointText + content.substring(insertAt);\n } else {\n content = content.trimEnd() + '\\n' + checkpointText;\n }\n } else {\n content = content.trimEnd() + '\\n' + checkpointText;\n }\n }\n\n content = content.trimEnd() + `\\n\\n---\\n\\n*Last updated: ${new Date().toISOString()}*\\n`;\n\n writeFileSync(todoPath, content);\n console.error(`Checkpoint added to TODO.md`);\n}\n\n/**\n * Update the ## Continue section at the top of TODO.md.\n * Mirrors \"pause session\" behavior \u2014 gives the next session a starting point.\n * Replaces any existing ## Continue section.\n */\nexport function updateTodoContinue(\n cwd: string,\n noteFilename: string,\n state: string | null,\n tokenDisplay: string\n): void {\n const todoPath = ensureTodoMd(cwd);\n let content = readFileSync(todoPath, 'utf-8');\n\n // Remove existing ## Continue section\n content = content.replace(/## Continue\\n[\\s\\S]*?\\n---\\n+/, '');\n\n const now = new Date().toISOString();\n const stateLines = state\n ? state.split('\\n').filter(l => l.trim()).slice(0, 10).map(l => `> ${l}`).join('\\n')\n : `> Working directory: ${cwd}. Check the latest session note for details.`;\n\n const continueSection = `## Continue\n\n> **Last session:** ${noteFilename.replace('.md', '')}\n> **Paused at:** ${now}\n>\n${stateLines}\n\n---\n\n`;\n\n content = content.replace(/^\\s+/, '');\n\n const titleMatch = content.match(/^(# [^\\n]+\\n+)/);\n if (titleMatch) {\n content = titleMatch[1] + continueSection + content.substring(titleMatch[0].length);\n } else {\n content = continueSection + content;\n }\n\n content = content.replace(/(\\n---\\s*)*(\\n\\*Last updated:.*\\*\\s*)+$/g, '');\n content = content.trimEnd() + `\\n\\n---\\n\\n*Last updated: ${now}*\\n`;\n\n writeFileSync(todoPath, content);\n console.error('TODO.md ## Continue section updated');\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n/**\n * PreCompact Hook - Triggered before context compression\n *\n * Three critical jobs:\n * 1. Save rich checkpoint to session note \u2014 work items, state, meaningful rename\n * 2. Update TODO.md with a proper ## Continue section for the next session\n * 3. Save session state to temp file for post-compact injection via SessionStart(compact)\n *\n * Uses a CUMULATIVE state file (.compact-state.json) that persists across\n * compactions. This ensures that even after multiple compactions (where the\n * transcript becomes thin), we still have rich data for titles, summaries,\n * and work items from earlier in the session.\n */\n\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { basename, dirname, join } from 'path';\nimport { tmpdir } from 'os';\nimport { connect } from 'net';\nimport { randomUUID } from 'crypto';\nimport {\n sendNtfyNotification,\n getCurrentNotePath,\n createSessionNote,\n appendCheckpoint,\n addWorkToSessionNote,\n isMeaningfulTitle,\n findNotesDir,\n renameSessionNote,\n updateTodoContinue,\n calculateSessionTokens,\n isProbeSession,\n WorkItem,\n} from '../lib/project-utils';\n\ninterface HookInput {\n session_id: string;\n transcript_path: string;\n cwd?: string;\n hook_event_name: string;\n compact_type?: string;\n trigger?: string;\n}\n\nconst DAEMON_SOCKET = process.env.PAI_SOCKET ?? '/tmp/pai.sock';\nconst DAEMON_TIMEOUT_MS = 3_000;\n\n/** Structured data extracted from a transcript in a single pass. */\ninterface TranscriptData {\n userMessages: string[];\n summaries: string[];\n captures: string[];\n lastCompleted: string;\n filesModified: string[];\n workItems: WorkItem[];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Turn Claude content (string or content block array) into plain text. */\nfunction contentToText(content: unknown): string {\n if (typeof content === 'string') return content;\n if (Array.isArray(content)) {\n return content\n .map((c) => {\n if (typeof c === 'string') return c;\n if (c?.text) return c.text;\n if (c?.content) return String(c.content);\n return '';\n })\n .join(' ')\n .trim();\n }\n return '';\n}\n\nfunction getTranscriptStats(transcriptPath: string): { messageCount: number; isLarge: boolean } {\n try {\n const content = readFileSync(transcriptPath, 'utf-8');\n const lines = content.trim().split('\\n');\n let userMessages = 0;\n let assistantMessages = 0;\n for (const line of lines) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line);\n if (entry.type === 'user') userMessages++;\n else if (entry.type === 'assistant') assistantMessages++;\n } catch { /* skip */ }\n }\n const totalMessages = userMessages + assistantMessages;\n return { messageCount: totalMessages, isLarge: totalMessages > 50 };\n } catch {\n return { messageCount: 0, isLarge: false };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Unified transcript parser \u2014 single pass extracts everything\n// ---------------------------------------------------------------------------\n\nfunction parseTranscript(transcriptPath: string): TranscriptData {\n const data: TranscriptData = {\n userMessages: [],\n summaries: [],\n captures: [],\n lastCompleted: '',\n filesModified: [],\n workItems: [],\n };\n\n try {\n const raw = readFileSync(transcriptPath, 'utf-8');\n const lines = raw.trim().split('\\n');\n const seenSummaries = new Set<string>();\n\n for (const line of lines) {\n if (!line.trim()) continue;\n let entry: any;\n try { entry = JSON.parse(line); } catch { continue; }\n\n // --- User messages ---\n if (entry.type === 'user' && entry.message?.content) {\n const text = contentToText(entry.message.content).slice(0, 300);\n if (text) data.userMessages.push(text);\n }\n\n // --- Assistant content ---\n if (entry.type === 'assistant' && entry.message?.content) {\n const text = contentToText(entry.message.content);\n\n // Summaries \u2192 also create work items\n const summaryMatch = text.match(/SUMMARY:\\s*(.+?)(?:\\n|$)/i);\n if (summaryMatch) {\n const s = summaryMatch[1].trim();\n if (s.length > 5 && !data.summaries.includes(s)) {\n data.summaries.push(s);\n if (!seenSummaries.has(s)) {\n seenSummaries.add(s);\n const details: string[] = [];\n const actionsMatch = text.match(/ACTIONS:\\s*(.+?)(?=\\n[A-Z]+:|$)/is);\n if (actionsMatch) {\n const actionLines = actionsMatch[1].split('\\n')\n .map(l => l.replace(/^[-*\u2022]\\s*/, '').replace(/^\\d+\\.\\s*/, '').trim())\n .filter(l => l.length > 3 && l.length < 100);\n details.push(...actionLines.slice(0, 3));\n }\n data.workItems.push({ title: s, details: details.length > 0 ? details : undefined, completed: true });\n }\n }\n }\n\n // Captures\n const captureMatch = text.match(/CAPTURE:\\s*(.+?)(?:\\n|$)/i);\n if (captureMatch) {\n const c = captureMatch[1].trim();\n if (c.length > 5 && !data.captures.includes(c)) data.captures.push(c);\n }\n\n // Completed\n const completedMatch = text.match(/COMPLETED:\\s*(.+?)(?:\\n|$)/i);\n if (completedMatch) {\n data.lastCompleted = completedMatch[1].trim().replace(/\\*+/g, '');\n if (data.workItems.length === 0 && !seenSummaries.has(data.lastCompleted) && data.lastCompleted.length > 5) {\n seenSummaries.add(data.lastCompleted);\n data.workItems.push({ title: data.lastCompleted, completed: true });\n }\n }\n\n // File modifications (from tool_use blocks)\n if (Array.isArray(entry.message.content)) {\n for (const block of entry.message.content) {\n if (block.type === 'tool_use') {\n const tool = block.name;\n if ((tool === 'Edit' || tool === 'Write') && block.input?.file_path) {\n if (!data.filesModified.includes(block.input.file_path)) {\n data.filesModified.push(block.input.file_path);\n }\n }\n }\n }\n }\n }\n }\n } catch (err) {\n console.error(`parseTranscript error: ${err}`);\n }\n\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Format session state as human-readable string\n// ---------------------------------------------------------------------------\n\nfunction formatSessionState(data: TranscriptData, cwd?: string): string | null {\n const parts: string[] = [];\n\n if (cwd) parts.push(`Working directory: ${cwd}`);\n\n const recentUser = data.userMessages.slice(-3);\n if (recentUser.length > 0) {\n parts.push('\\nRecent user requests:');\n for (const msg of recentUser) {\n parts.push(`- ${msg.split('\\n')[0].slice(0, 200)}`);\n }\n }\n\n const recentSummaries = data.summaries.slice(-3);\n if (recentSummaries.length > 0) {\n parts.push('\\nWork summaries:');\n for (const s of recentSummaries) parts.push(`- ${s.slice(0, 150)}`);\n }\n\n const recentCaptures = data.captures.slice(-5);\n if (recentCaptures.length > 0) {\n parts.push('\\nCaptured context:');\n for (const c of recentCaptures) parts.push(`- ${c.slice(0, 150)}`);\n }\n\n const files = data.filesModified.slice(-10);\n if (files.length > 0) {\n parts.push('\\nFiles modified this session:');\n for (const f of files) parts.push(`- ${f}`);\n }\n\n if (data.lastCompleted) {\n parts.push(`\\nLast completed: ${data.lastCompleted.slice(0, 150)}`);\n }\n\n const result = parts.join('\\n');\n return result.length > 50 ? result : null;\n}\n\n// ---------------------------------------------------------------------------\n// Derive a meaningful title for the session note\n// ---------------------------------------------------------------------------\n\nfunction deriveTitle(data: TranscriptData): string {\n // Collect candidates in priority order, then pick the first meaningful one.\n const candidates: string[] = [];\n\n // 1. Work item titles (most descriptive of what was accomplished)\n for (let i = data.workItems.length - 1; i >= 0; i--) {\n candidates.push(data.workItems[i].title);\n }\n\n // 2. Summaries\n for (let i = data.summaries.length - 1; i >= 0; i--) {\n candidates.push(data.summaries[i]);\n }\n\n // 3. Last completed marker\n if (data.lastCompleted && data.lastCompleted.length > 5) {\n candidates.push(data.lastCompleted);\n }\n\n // 4. User messages (FIRST meaningful one, not last \u2014 first is more likely\n // to describe the session's purpose; last is often system noise)\n for (const msg of data.userMessages) {\n const line = msg.split('\\n')[0].trim();\n if (line.length > 10 && line.length < 80 &&\n !line.toLowerCase().startsWith('yes') &&\n !line.toLowerCase().startsWith('ok')) {\n candidates.push(line);\n }\n }\n\n // 5. Derive from files modified (fallback)\n if (data.filesModified.length > 0) {\n const basenames = data.filesModified.slice(-5).map(f => {\n const b = basename(f);\n return b.replace(/\\.[^.]+$/, '');\n });\n const unique = [...new Set(basenames)];\n candidates.push(\n unique.length <= 3\n ? `Updated ${unique.join(', ')}`\n : `Modified ${data.filesModified.length} files`\n );\n }\n\n // Pick the first candidate that passes the meaningfulness filter\n for (const raw of candidates) {\n const cleaned = raw\n .replace(/[^\\w\\s-]/g, ' ') // Remove special chars\n .replace(/\\s+/g, ' ') // Normalize whitespace\n .trim()\n .substring(0, 60);\n if (cleaned.length >= 5 && isMeaningfulTitle(cleaned)) {\n return cleaned;\n }\n }\n\n // All candidates were garbage \u2014 return empty (caller will not rename)\n return '';\n}\n\n// ---------------------------------------------------------------------------\n// Cumulative state \u2014 persists across compactions in .compact-state.json\n// ---------------------------------------------------------------------------\n\nconst CUMULATIVE_STATE_FILE = '.compact-state.json';\n\nfunction loadCumulativeState(notesDir: string): TranscriptData | null {\n try {\n const filePath = join(notesDir, CUMULATIVE_STATE_FILE);\n if (!existsSync(filePath)) return null;\n const raw = JSON.parse(readFileSync(filePath, 'utf-8'));\n return {\n userMessages: raw.userMessages || [],\n summaries: raw.summaries || [],\n captures: raw.captures || [],\n lastCompleted: raw.lastCompleted || '',\n filesModified: raw.filesModified || [],\n workItems: raw.workItems || [],\n };\n } catch {\n return null;\n }\n}\n\nfunction mergeTranscriptData(accumulated: TranscriptData | null, current: TranscriptData): TranscriptData {\n if (!accumulated) return current;\n\n const mergeArrays = (a: string[], b: string[]): string[] => {\n const seen = new Set(a);\n return [...a, ...b.filter(x => !seen.has(x))];\n };\n\n const seenTitles = new Set(accumulated.workItems.map(w => w.title));\n const newWorkItems = current.workItems.filter(w => !seenTitles.has(w.title));\n\n return {\n userMessages: mergeArrays(accumulated.userMessages, current.userMessages).slice(-20),\n summaries: mergeArrays(accumulated.summaries, current.summaries),\n captures: mergeArrays(accumulated.captures, current.captures),\n lastCompleted: current.lastCompleted || accumulated.lastCompleted,\n filesModified: mergeArrays(accumulated.filesModified, current.filesModified),\n workItems: [...accumulated.workItems, ...newWorkItems],\n };\n}\n\nfunction saveCumulativeState(notesDir: string, data: TranscriptData, notePath: string | null): void {\n try {\n const filePath = join(notesDir, CUMULATIVE_STATE_FILE);\n writeFileSync(filePath, JSON.stringify({\n ...data,\n notePath,\n lastUpdated: new Date().toISOString(),\n }, null, 2));\n console.error(`Cumulative state saved (${data.workItems.length} work items, ${data.filesModified.length} files)`);\n } catch (err) {\n console.error(`Failed to save cumulative state: ${err}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Daemon IPC \u2014 enqueue session-summary work item\n// ---------------------------------------------------------------------------\n\n/**\n * Send a session-summary work item to the daemon via IPC.\n * Returns true on success, false if the daemon is unreachable.\n * Times out after DAEMON_TIMEOUT_MS to avoid blocking the hook.\n */\nfunction enqueueSessionSummary(payload: {\n cwd: string;\n sessionId?: string;\n transcriptPath?: string;\n}): Promise<boolean> {\n return new Promise((resolve) => {\n let done = false;\n let buffer = '';\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(ok: boolean): void {\n if (done) return;\n done = true;\n if (timer !== null) { clearTimeout(timer); timer = null; }\n try { client.destroy(); } catch { /* ignore */ }\n resolve(ok);\n }\n\n const client = connect(DAEMON_SOCKET, () => {\n const msg = JSON.stringify({\n id: randomUUID(),\n method: 'work_queue_enqueue',\n params: {\n type: 'session-summary',\n priority: 4, // lower priority than session-end (2)\n payload: {\n cwd: payload.cwd,\n sessionId: payload.sessionId,\n transcriptPath: payload.transcriptPath,\n },\n },\n }) + '\\n';\n client.write(msg);\n });\n\n client.on('data', (chunk: Buffer) => {\n buffer += chunk.toString();\n const nl = buffer.indexOf('\\n');\n if (nl === -1) return;\n const line = buffer.slice(0, nl);\n try {\n const response = JSON.parse(line) as { ok: boolean; error?: string; result?: { id: string } };\n if (response.ok) {\n console.error(`PRE-COMPACT: Session summary enqueued with daemon (id=${response.result?.id}).`);\n finish(true);\n } else {\n console.error(`PRE-COMPACT: Daemon rejected session-summary: ${response.error}`);\n finish(false);\n }\n } catch {\n finish(false);\n }\n });\n\n client.on('error', (e: NodeJS.ErrnoException) => {\n if (e.code === 'ENOENT' || e.code === 'ECONNREFUSED') {\n console.error('PRE-COMPACT: Daemon not running \u2014 skipping session summary.');\n } else {\n console.error(`PRE-COMPACT: Daemon socket error: ${e.message}`);\n }\n finish(false);\n });\n\n timer = setTimeout(() => {\n console.error('PRE-COMPACT: Daemon IPC timed out \u2014 skipping session summary.');\n finish(false);\n }, DAEMON_TIMEOUT_MS);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Main\n// ---------------------------------------------------------------------------\n\nasync function main() {\n // Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)\n if (isProbeSession()) {\n process.exit(0);\n }\n\n let hookInput: HookInput | null = null;\n\n try {\n const decoder = new TextDecoder();\n let input = '';\n const timeoutPromise = new Promise<void>((resolve) => { setTimeout(resolve, 500); });\n const readPromise = (async () => {\n for await (const chunk of process.stdin) {\n input += decoder.decode(chunk, { stream: true });\n }\n })();\n await Promise.race([readPromise, timeoutPromise]);\n if (input.trim()) {\n hookInput = JSON.parse(input) as HookInput;\n }\n } catch {\n // Silently handle input errors\n }\n\n const compactType = hookInput?.compact_type || hookInput?.trigger || 'auto';\n let tokenCount = 0;\n\n if (hookInput?.transcript_path) {\n const stats = getTranscriptStats(hookInput.transcript_path);\n tokenCount = calculateSessionTokens(hookInput.transcript_path);\n const tokenDisplay = tokenCount > 1000\n ? `${Math.round(tokenCount / 1000)}k`\n : String(tokenCount);\n\n // -----------------------------------------------------------------\n // Single-pass transcript parsing + cumulative state merge\n // -----------------------------------------------------------------\n const data = parseTranscript(hookInput.transcript_path);\n\n // Find notes directory early \u2014 needed for cumulative state\n let notesInfo: { path: string; isLocal: boolean };\n try {\n notesInfo = hookInput.cwd\n ? findNotesDir(hookInput.cwd)\n : { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };\n } catch {\n notesInfo = { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };\n }\n\n // Load accumulated state from previous compactions and merge\n const accumulated = loadCumulativeState(notesInfo.path);\n const merged = mergeTranscriptData(accumulated, data);\n const state = formatSessionState(merged, hookInput.cwd);\n\n if (accumulated) {\n console.error(`Loaded cumulative state: ${accumulated.workItems.length} work items, ${accumulated.filesModified.length} files from previous compaction(s)`);\n }\n\n // -----------------------------------------------------------------\n // Persist session state to numbered session note.\n // RULE: ONE note per session. NEVER create a new note during compaction.\n // If the latest note is completed, reopen it (remove completed status)\n // rather than creating a duplicate.\n // -----------------------------------------------------------------\n let notePath: string | null = null;\n\n try {\n notePath = getCurrentNotePath(notesInfo.path);\n\n if (!notePath) {\n // Truly no note exists at all \u2014 create one (first compaction of a session\n // that started before PAI was installed, or corrupted notes dir)\n console.error('No session note found \u2014 creating one for checkpoint');\n notePath = createSessionNote(notesInfo.path, 'Untitled Session');\n } else {\n // If the latest note is completed, reopen it for this session's checkpoints\n // instead of creating a duplicate. This handles the case where session-stop\n // finalized a note but the session continued (e.g., user said \"end session\"\n // but kept working).\n try {\n let noteContent = readFileSync(notePath, 'utf-8');\n if (noteContent.includes('**Status:** Completed')) {\n noteContent = noteContent.replace('**Status:** Completed', '**Status:** In Progress');\n writeFileSync(notePath, noteContent);\n console.error(`Reopened completed note for continued session: ${basename(notePath)}`);\n }\n } catch { /* proceed with existing note */ }\n }\n\n // 1. Write rich checkpoint with full session state\n const checkpointBody = state\n ? `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.\\n\\n${state}`\n : `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.`;\n appendCheckpoint(notePath, checkpointBody);\n\n // 2. Write work items to \"Work Done\" section (uses merged cumulative data)\n if (merged.workItems.length > 0) {\n addWorkToSessionNote(notePath, merged.workItems, `Pre-Compact (~${tokenDisplay} tokens)`);\n console.error(`Added ${merged.workItems.length} work item(s) to session note`);\n }\n\n // 3. Rename session note with a meaningful title (uses merged data for richer titles)\n const title = deriveTitle(merged);\n if (title) {\n const newPath = renameSessionNote(notePath, title);\n if (newPath !== notePath) {\n // Update H1 title inside the note to match\n try {\n let noteContent = readFileSync(newPath, 'utf-8');\n noteContent = noteContent.replace(\n /^(# Session \\d+:)\\s*.*$/m,\n `$1 ${title}`\n );\n writeFileSync(newPath, noteContent);\n console.error(`Updated note H1 to match rename`);\n } catch { /* ignore */ }\n notePath = newPath;\n }\n }\n\n console.error(`Rich checkpoint saved: ${basename(notePath)}`);\n } catch (noteError) {\n console.error(`Could not save checkpoint: ${noteError}`);\n }\n\n // Save cumulative state for next compaction\n saveCumulativeState(notesInfo.path, merged, notePath);\n\n // -----------------------------------------------------------------\n // Update TODO.md with proper ## Continue section (like \"pause session\")\n // -----------------------------------------------------------------\n if (hookInput.cwd && notePath) {\n try {\n const noteFilename = basename(notePath);\n updateTodoContinue(hookInput.cwd, noteFilename, state, tokenDisplay);\n console.error('TODO.md ## Continue section updated');\n } catch (todoError) {\n console.error(`Could not update TODO.md: ${todoError}`);\n }\n }\n\n // -----------------------------------------------------------------------\n // Save session state to temp file for post-compact injection.\n //\n // PreCompact hooks have NO stdout support (Claude Code ignores it).\n // Instead, we write the injection payload to a temp file keyed by\n // session_id. The SessionStart(compact) hook reads it and outputs\n // to stdout, which IS injected into the post-compaction context.\n //\n // Always fires (even with thin state) \u2014 includes note path so the AI\n // can enrich the session note post-compaction using its own context.\n // -----------------------------------------------------------------------\n if (hookInput.session_id) {\n const stateText = state || `Working directory: ${hookInput.cwd || 'unknown'}`;\n const noteInfo = notePath\n ? `\\nSESSION NOTE: ${notePath}\\nIf this note still has a generic title (e.g. \"New Session\", \"Context Compression\"),\\nrename it based on actual work done and add a rich summary.`\n : '';\n\n const injection = [\n '<system-reminder>',\n `SESSION STATE RECOVERED AFTER COMPACTION (${compactType}, ~${tokenDisplay} tokens)`,\n '',\n stateText,\n noteInfo,\n '',\n 'IMPORTANT: This session state was captured before context compaction.',\n 'Use it to maintain continuity. Continue the conversation from where',\n 'it left off without asking the user to repeat themselves.',\n 'Continue with the last task that you were asked to work on.',\n '</system-reminder>',\n ].join('\\n');\n\n try {\n const stateFile = join(tmpdir(), `pai-compact-state-${hookInput.session_id}.txt`);\n writeFileSync(stateFile, injection, 'utf-8');\n console.error(`Session state saved to ${stateFile} (${injection.length} chars)`);\n } catch (err) {\n console.error(`Failed to save state file: ${err}`);\n }\n }\n }\n\n // -----------------------------------------------------------------------\n // Enqueue session-summary work item with daemon for AI-powered note generation\n // -----------------------------------------------------------------------\n if (hookInput?.cwd) {\n try {\n await enqueueSessionSummary({\n cwd: hookInput.cwd,\n sessionId: hookInput.session_id,\n transcriptPath: hookInput.transcript_path,\n });\n } catch (err) {\n console.error(`Could not enqueue session-summary: ${err}`);\n }\n }\n\n // Send ntfy.sh notification\n const ntfyMessage = tokenCount > 0\n ? `Auto-pause: ~${Math.round(tokenCount / 1000)}k tokens`\n : 'Context compressing';\n await sendNtfyNotification(ntfyMessage);\n\n process.exit(0);\n}\n\nmain().catch(() => {\n process.exit(0);\n});\n", "/**\n * Path utilities \u2014 encoding, Notes/Sessions directory discovery and creation.\n */\n\nimport { existsSync, mkdirSync, readdirSync, renameSync } from 'fs';\nimport { join, basename } from 'path';\nimport { PAI_DIR } from '../pai-paths.js';\n\n// Re-export PAI_DIR for consumers\nexport { PAI_DIR };\nexport const PROJECTS_DIR = join(PAI_DIR, 'projects');\n\n/**\n * Directories known to be automated health-check / probe sessions.\n * Hooks should exit early for these to avoid registry clutter and wasted work.\n */\nconst PROBE_CWD_PATTERNS = [\n '/CodexBar/ClaudeProbe',\n '/ClaudeProbe',\n];\n\n/**\n * Check if the current working directory belongs to a probe/health-check session.\n * Returns true if hooks should skip this session entirely.\n */\nexport function isProbeSession(cwd?: string): boolean {\n const dir = cwd || process.cwd();\n return PROBE_CWD_PATTERNS.some(pattern => dir.includes(pattern));\n}\n\n/**\n * Encode a path the same way Claude Code does:\n * - Replace / with -\n * - Replace . with -\n * - Replace space with -\n */\nexport function encodePath(path: string): string {\n return path\n .replace(/\\//g, '-')\n .replace(/\\./g, '-')\n .replace(/ /g, '-');\n}\n\n/** Get the project directory for a given working directory. */\nexport function getProjectDir(cwd: string): string {\n const encoded = encodePath(cwd);\n return join(PROJECTS_DIR, encoded);\n}\n\n/** Get the Notes directory for a project (central location). */\nexport function getNotesDir(cwd: string): string {\n return join(getProjectDir(cwd), 'Notes');\n}\n\n/**\n * Find Notes directory \u2014 checks local first, falls back to central.\n * Does NOT create the directory.\n */\nexport function findNotesDir(cwd: string): { path: string; isLocal: boolean } {\n const cwdBasename = basename(cwd).toLowerCase();\n if (cwdBasename === 'notes' && existsSync(cwd)) {\n return { path: cwd, isLocal: true };\n }\n\n const localPaths = [\n join(cwd, 'Notes'),\n join(cwd, 'notes'),\n join(cwd, '.claude', 'Notes'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) {\n return { path, isLocal: true };\n }\n }\n\n return { path: getNotesDir(cwd), isLocal: false };\n}\n\n/** Get the sessions/ directory for a project (stores .jsonl transcripts). */\nexport function getSessionsDir(cwd: string): string {\n return join(getProjectDir(cwd), 'sessions');\n}\n\n/** Get the sessions/ directory from a project directory path. */\nexport function getSessionsDirFromProjectDir(projectDir: string): string {\n return join(projectDir, 'sessions');\n}\n\n// ---------------------------------------------------------------------------\n// Directory creation helpers\n// ---------------------------------------------------------------------------\n\n/** Ensure the Notes directory exists for a project. @deprecated Use ensureNotesDirSmart() */\nexport function ensureNotesDir(cwd: string): string {\n const notesDir = getNotesDir(cwd);\n if (!existsSync(notesDir)) {\n mkdirSync(notesDir, { recursive: true });\n console.error(`Created Notes directory: ${notesDir}`);\n }\n return notesDir;\n}\n\n/**\n * Smart Notes directory handling:\n * - If local Notes/ exists \u2192 use it (don't create anything new)\n * - If no local Notes/ \u2192 ensure central exists and use that\n */\nexport function ensureNotesDirSmart(cwd: string): { path: string; isLocal: boolean } {\n const found = findNotesDir(cwd);\n if (found.isLocal) return found;\n if (!existsSync(found.path)) {\n mkdirSync(found.path, { recursive: true });\n console.error(`Created central Notes directory: ${found.path}`);\n }\n return found;\n}\n\n/** Ensure the sessions/ directory exists for a project. */\nexport function ensureSessionsDir(cwd: string): string {\n const sessionsDir = getSessionsDir(cwd);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/** Ensure the sessions/ directory exists (from project dir path). */\nexport function ensureSessionsDirFromProjectDir(projectDir: string): string {\n const sessionsDir = getSessionsDirFromProjectDir(projectDir);\n if (!existsSync(sessionsDir)) {\n mkdirSync(sessionsDir, { recursive: true });\n console.error(`Created sessions directory: ${sessionsDir}`);\n }\n return sessionsDir;\n}\n\n/**\n * Move all .jsonl session files from project root to sessions/ subdirectory.\n * Returns the number of files moved.\n */\nexport function moveSessionFilesToSessionsDir(\n projectDir: string,\n excludeFile?: string,\n silent = false\n): number {\n const sessionsDir = ensureSessionsDirFromProjectDir(projectDir);\n\n if (!existsSync(projectDir)) return 0;\n\n const files = readdirSync(projectDir);\n let movedCount = 0;\n\n for (const file of files) {\n if (file.endsWith('.jsonl') && file !== excludeFile) {\n const sourcePath = join(projectDir, file);\n const destPath = join(sessionsDir, file);\n try {\n renameSync(sourcePath, destPath);\n if (!silent) console.error(`Moved ${file} \u2192 sessions/`);\n movedCount++;\n } catch (error) {\n if (!silent) console.error(`Could not move ${file}: ${error}`);\n }\n }\n }\n\n return movedCount;\n}\n\n// ---------------------------------------------------------------------------\n// CLAUDE.md / TODO.md discovery\n// ---------------------------------------------------------------------------\n\n/** Find TODO.md \u2014 check local first, fallback to central. */\nexport function findTodoPath(cwd: string): string {\n const localPaths = [\n join(cwd, 'TODO.md'),\n join(cwd, 'notes', 'TODO.md'),\n join(cwd, 'Notes', 'TODO.md'),\n join(cwd, '.claude', 'TODO.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) return path;\n }\n\n return join(getNotesDir(cwd), 'TODO.md');\n}\n\n/** Find CLAUDE.md \u2014 returns the FIRST found path. */\nexport function findClaudeMdPath(cwd: string): string | null {\n const paths = findAllClaudeMdPaths(cwd);\n return paths.length > 0 ? paths[0] : null;\n}\n\n/**\n * Find ALL CLAUDE.md files in local locations in priority order.\n */\nexport function findAllClaudeMdPaths(cwd: string): string[] {\n const foundPaths: string[] = [];\n\n const localPaths = [\n join(cwd, '.claude', 'CLAUDE.md'),\n join(cwd, 'CLAUDE.md'),\n join(cwd, 'Notes', 'CLAUDE.md'),\n join(cwd, 'notes', 'CLAUDE.md'),\n join(cwd, 'Prompts', 'CLAUDE.md'),\n join(cwd, 'prompts', 'CLAUDE.md'),\n ];\n\n for (const path of localPaths) {\n if (existsSync(path)) foundPaths.push(path);\n }\n\n return foundPaths;\n}\n", "/**\n * PAI Path Resolution - Single Source of Truth\n *\n * This module provides consistent path resolution across all PAI hooks.\n * It handles PAI_DIR detection whether set explicitly or defaulting to ~/.claude\n *\n * ALSO loads .env file from PAI_DIR so all hooks get environment variables\n * without relying on Claude Code's settings.json injection.\n *\n * Usage in hooks:\n * import { PAI_DIR, HOOKS_DIR, SKILLS_DIR } from './lib/pai-paths';\n */\n\nimport { homedir } from 'os';\nimport { resolve, join } from 'path';\nimport { existsSync, readFileSync } from 'fs';\n\n/**\n * Load .env file and inject into process.env\n * Must run BEFORE PAI_DIR resolution so .env can set PAI_DIR if needed\n */\nfunction loadEnvFile(): void {\n // Check common locations for .env\n const possiblePaths = [\n resolve(process.env.PAI_DIR || '', '.env'),\n resolve(homedir(), '.claude', '.env'),\n ];\n\n for (const envPath of possiblePaths) {\n if (existsSync(envPath)) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n for (const line of content.split('\\n')) {\n const trimmed = line.trim();\n // Skip comments and empty lines\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n const eqIndex = trimmed.indexOf('=');\n if (eqIndex > 0) {\n const key = trimmed.substring(0, eqIndex).trim();\n let value = trimmed.substring(eqIndex + 1).trim();\n\n // Remove surrounding quotes if present\n if ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n value = value.slice(1, -1);\n }\n\n // Expand $HOME and ~ in values\n value = value.replace(/\\$HOME/g, homedir());\n value = value.replace(/^~(?=\\/|$)/, homedir());\n\n // Only set if not already defined (env vars take precedence)\n if (process.env[key] === undefined) {\n process.env[key] = value;\n }\n }\n }\n // Found and loaded, don't check other paths\n break;\n } catch {\n // Silently continue if .env can't be read\n }\n }\n }\n}\n\n// Load .env FIRST, before any other initialization\nloadEnvFile();\n\n/**\n * Smart PAI_DIR detection with fallback\n * Priority:\n * 1. PAI_DIR environment variable (if set)\n * 2. ~/.claude (standard location)\n */\nexport const PAI_DIR = process.env.PAI_DIR\n ? resolve(process.env.PAI_DIR)\n : resolve(homedir(), '.claude');\n\n/**\n * Common PAI directories\n */\nexport const HOOKS_DIR = join(PAI_DIR, 'Hooks');\nexport const SKILLS_DIR = join(PAI_DIR, 'Skills');\nexport const AGENTS_DIR = join(PAI_DIR, 'Agents');\nexport const HISTORY_DIR = join(PAI_DIR, 'History');\nexport const COMMANDS_DIR = join(PAI_DIR, 'Commands');\n\n/**\n * Validate PAI directory structure on first import\n * This fails fast with a clear error if PAI is misconfigured\n */\nfunction validatePAIStructure(): void {\n if (!existsSync(PAI_DIR)) {\n console.error(`PAI_DIR does not exist: ${PAI_DIR}`);\n console.error(` Expected ~/.claude or set PAI_DIR environment variable`);\n process.exit(1);\n }\n\n if (!existsSync(HOOKS_DIR)) {\n console.error(`PAI hooks directory not found: ${HOOKS_DIR}`);\n console.error(` Your PAI_DIR may be misconfigured`);\n console.error(` Current PAI_DIR: ${PAI_DIR}`);\n process.exit(1);\n }\n}\n\n// Run validation on module import\n// This ensures any hook that imports this module will fail fast if paths are wrong\nvalidatePAIStructure();\n\n/**\n * Helper to get history file path with date-based organization\n */\nexport function getHistoryFilePath(subdir: string, filename: string): string {\n const now = new Date();\n const tz = process.env.TIME_ZONE || Intl.DateTimeFormat().resolvedOptions().timeZone;\n const localDate = new Date(now.toLocaleString('en-US', { timeZone: tz }));\n const year = localDate.getFullYear();\n const month = String(localDate.getMonth() + 1).padStart(2, '0');\n\n return join(HISTORY_DIR, subdir, `${year}-${month}`, filename);\n}\n", "/**\n * Push notification helpers \u2014 WhatsApp-aware with ntfy.sh fallback.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\n\n/**\n * Check if a messaging MCP server (AIBroker, Whazaa, or Telex) is configured.\n * When any messaging server is active, the AI handles notifications via MCP\n * and ntfy is skipped to avoid duplicates.\n */\nexport function isWhatsAppEnabled(): boolean {\n try {\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n if (!existsSync(settingsPath)) return false;\n\n const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n const enabled: string[] = settings.enabledMcpjsonServers || [];\n return enabled.includes('aibroker') || enabled.includes('whazaa') || enabled.includes('telex');\n } catch {\n return false;\n }\n}\n\n/**\n * Send push notification \u2014 WhatsApp-aware with ntfy fallback.\n *\n * When WhatsApp (Whazaa) is enabled in MCP config, ntfy is SKIPPED\n * because the AI sends WhatsApp messages directly via MCP.\n * When WhatsApp is NOT configured, ntfy fires as the fallback channel.\n */\nexport async function sendNtfyNotification(message: string, retries = 2): Promise<boolean> {\n if (isWhatsAppEnabled()) {\n console.error(`WhatsApp (Whazaa) enabled in MCP config \u2014 skipping ntfy`);\n return true;\n }\n\n const topic = process.env.NTFY_TOPIC;\n\n if (!topic) {\n console.error('NTFY_TOPIC not set and WhatsApp not active \u2014 notifications disabled');\n return false;\n }\n\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const response = await fetch(`https://ntfy.sh/${topic}`, {\n method: 'POST',\n body: message,\n headers: {\n 'Title': 'Claude Code',\n 'Priority': 'default',\n },\n });\n\n if (response.ok) {\n console.error(`ntfy.sh notification sent (WhatsApp inactive): \"${message}\"`);\n return true;\n } else {\n console.error(`ntfy.sh attempt ${attempt + 1} failed: ${response.status}`);\n }\n } catch (error) {\n console.error(`ntfy.sh attempt ${attempt + 1} error: ${error}`);\n }\n\n if (attempt < retries) {\n await new Promise(resolve => setTimeout(resolve, 1000));\n }\n }\n\n console.error('ntfy.sh notification failed after all retries');\n return false;\n}\n", "/**\n * Session note creation, editing, checkpointing, renaming, and finalization.\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, renameSync } from 'fs';\nimport { join, basename } from 'path';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\n/** Get or create the YYYY/MM subdirectory for the current month inside notesDir. */\nfunction getMonthDir(notesDir: string): string {\n const now = new Date();\n const year = String(now.getFullYear());\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const monthDir = join(notesDir, year, month);\n if (!existsSync(monthDir)) {\n mkdirSync(monthDir, { recursive: true });\n }\n return monthDir;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Get the next note number (4-digit format: 0001, 0002, etc.).\n * Numbers are scoped per YYYY/MM directory.\n */\nexport function getNextNoteNumber(notesDir: string): string {\n const monthDir = getMonthDir(notesDir);\n\n const files = readdirSync(monthDir)\n .filter(f => f.match(/^\\d{3,4}[\\s_-]/))\n .sort();\n\n if (files.length === 0) return '0001';\n\n let maxNumber = 0;\n for (const file of files) {\n const digitMatch = file.match(/^(\\d+)/);\n if (digitMatch) {\n const num = parseInt(digitMatch[1], 10);\n if (num > maxNumber) maxNumber = num;\n }\n }\n\n return String(maxNumber + 1).padStart(4, '0');\n}\n\n/**\n * Get the current (latest) note file path, or null if none exists.\n * Searches current month \u2192 previous month \u2192 flat notesDir (legacy).\n */\nexport function getCurrentNotePath(notesDir: string): string | null {\n if (!existsSync(notesDir)) return null;\n\n const findLatestIn = (dir: string): string | null => {\n if (!existsSync(dir)) return null;\n const files = readdirSync(dir)\n .filter(f => f.match(/^\\d{3,4}[\\s_-].*\\.md$/))\n .sort((a, b) => {\n const numA = parseInt(a.match(/^(\\d+)/)?.[1] || '0', 10);\n const numB = parseInt(b.match(/^(\\d+)/)?.[1] || '0', 10);\n return numA - numB;\n });\n if (files.length === 0) return null;\n return join(dir, files[files.length - 1]);\n };\n\n const now = new Date();\n const year = String(now.getFullYear());\n const month = String(now.getMonth() + 1).padStart(2, '0');\n const currentMonthDir = join(notesDir, year, month);\n const found = findLatestIn(currentMonthDir);\n if (found) return found;\n\n const prevDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);\n const prevYear = String(prevDate.getFullYear());\n const prevMonth = String(prevDate.getMonth() + 1).padStart(2, '0');\n const prevMonthDir = join(notesDir, prevYear, prevMonth);\n const prevFound = findLatestIn(prevMonthDir);\n if (prevFound) return prevFound;\n\n return findLatestIn(notesDir);\n}\n\n/**\n * Create a new session note.\n * Format: \"NNNN - YYYY-MM-DD - New Session.md\" filed into YYYY/MM subdirectory.\n * Claude MUST rename at session end with a meaningful description.\n */\nexport function createSessionNote(notesDir: string, description: string): string {\n const noteNumber = getNextNoteNumber(notesDir);\n const date = new Date().toISOString().split('T')[0];\n const monthDir = getMonthDir(notesDir);\n const filename = `${noteNumber} - ${date} - New Session.md`;\n const filepath = join(monthDir, filename);\n\n const content = `# Session ${noteNumber}: ${description}\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`;\n\n writeFileSync(filepath, content);\n console.error(`Created session note: ${filename}`);\n\n return filepath;\n}\n\n/** Append a checkpoint to the current session note. */\nexport function appendCheckpoint(notePath: string, checkpoint: string): void {\n if (!existsSync(notePath)) {\n console.error(`Note file not found, recreating: ${notePath}`);\n try {\n const parentDir = join(notePath, '..');\n if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });\n const noteFilename = basename(notePath);\n const numberMatch = noteFilename.match(/^(\\d+)/);\n const noteNumber = numberMatch ? numberMatch[1] : '0000';\n const date = new Date().toISOString().split('T')[0];\n 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`;\n writeFileSync(notePath, content);\n console.error(`Recreated session note: ${noteFilename}`);\n } catch (err) {\n console.error(`Failed to recreate note: ${err}`);\n return;\n }\n }\n\n const content = readFileSync(notePath, 'utf-8');\n const timestamp = new Date().toISOString();\n const checkpointText = `\\n### Checkpoint ${timestamp}\\n\\n${checkpoint}\\n`;\n\n const nextStepsIndex = content.indexOf('## Next Steps');\n const newContent = nextStepsIndex !== -1\n ? content.substring(0, nextStepsIndex) + checkpointText + content.substring(nextStepsIndex)\n : content + checkpointText;\n\n writeFileSync(notePath, newContent);\n console.error(`Checkpoint added to: ${basename(notePath)}`);\n}\n\n/** Work item for session notes. */\nexport interface WorkItem {\n title: string;\n details?: string[];\n completed?: boolean;\n}\n\n/** Add work items to the \"Work Done\" section of a session note. */\nexport function addWorkToSessionNote(notePath: string, workItems: WorkItem[], sectionTitle?: string): void {\n if (!existsSync(notePath)) {\n console.error(`Note file not found: ${notePath}`);\n return;\n }\n\n let content = readFileSync(notePath, 'utf-8');\n\n let workText = '';\n if (sectionTitle) workText += `\\n### ${sectionTitle}\\n\\n`;\n\n for (const item of workItems) {\n const checkbox = item.completed !== false ? '[x]' : '[ ]';\n workText += `- ${checkbox} **${item.title}**\\n`;\n if (item.details && item.details.length > 0) {\n for (const detail of item.details) {\n workText += ` - ${detail}\\n`;\n }\n }\n }\n\n const workDoneMatch = content.match(/## Work Done\\n\\n(<!-- .*? -->)?/);\n if (workDoneMatch) {\n const insertPoint = content.indexOf(workDoneMatch[0]) + workDoneMatch[0].length;\n content = content.substring(0, insertPoint) + workText + content.substring(insertPoint);\n } else {\n const nextStepsIndex = content.indexOf('## Next Steps');\n if (nextStepsIndex !== -1) {\n content = content.substring(0, nextStepsIndex) + workText + '\\n' + content.substring(nextStepsIndex);\n }\n }\n\n writeFileSync(notePath, content);\n console.error(`Added ${workItems.length} work item(s) to: ${basename(notePath)}`);\n}\n\n/**\n * Check if a candidate title is meaningless / garbage.\n * Public wrapper around the internal filter for use by other hooks.\n */\nexport function isMeaningfulTitle(text: string): boolean {\n return !isMeaninglessCandidate(text);\n}\n\n/** Sanitize a string for use in a filename. */\nexport function sanitizeForFilename(str: string): string {\n return str\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .substring(0, 50);\n}\n\n/**\n * Return true if the candidate string should be rejected as a meaningful name.\n * Rejects file paths, shebangs, timestamps, system noise, XML tags, hashes, etc.\n */\nfunction isMeaninglessCandidate(text: string): boolean {\n const t = text.trim();\n if (!t) return true;\n if (t.length < 5) return true; // too short to be meaningful\n if (t.startsWith('/') || t.startsWith('~')) return true; // file path\n if (t.startsWith('#!')) return true; // shebang\n if (t.includes('[object Object]')) return true; // serialization artifact\n if (/^\\d{4}-\\d{2}-\\d{2}(T[\\d:.Z+-]+)?$/.test(t)) return true; // ISO timestamp\n if (/^\\d{1,2}:\\d{2}(:\\d{2})?(\\s*(AM|PM))?$/i.test(t)) return true; // time-only\n if (/^<[a-z-]+[\\s/>]/i.test(t)) return true; // XML/HTML tags (<task-notification>, etc.)\n if (/^[0-9a-f]{10,}$/i.test(t)) return true; // hex hash strings\n if (/^Exit code \\d+/i.test(t)) return true; // exit code messages\n if (/^Error:/i.test(t)) return true; // error messages\n if (/^This session is being continued/i.test(t)) return true; // continuation boilerplate\n if (/^\\(Bash completed/i.test(t)) return true; // bash output noise\n if (/^Task Notification$/i.test(t)) return true; // literal \"Task Notification\"\n if (/^New Session$/i.test(t)) return true; // placeholder title\n if (/^Recovered Session$/i.test(t)) return true; // placeholder title\n if (/^Continued Session$/i.test(t)) return true; // placeholder title\n if (/^Untitled Session$/i.test(t)) return true; // placeholder title\n if (/^Context Compression$/i.test(t)) return true; // compression artifact\n if (/^[A-Fa-f0-9]{8,}\\s+Output$/i.test(t)) return true; // hash + \"Output\" pattern\n return false;\n}\n\n/**\n * Extract a meaningful name from session note content and summary.\n * Looks at Work Done section headers, bold text, and summary.\n */\nexport function extractMeaningfulName(noteContent: string, summary: string): string {\n const workDoneMatch = noteContent.match(/## Work Done\\n\\n([\\s\\S]*?)(?=\\n---|\\n## Next)/);\n\n if (workDoneMatch) {\n const workDoneSection = workDoneMatch[1];\n\n const subheadings = workDoneSection.match(/### ([^\\n]+)/g);\n if (subheadings && subheadings.length > 0) {\n const firstHeading = subheadings[0].replace('### ', '').trim();\n if (!isMeaninglessCandidate(firstHeading) && firstHeading.length > 5 && firstHeading.length < 60) {\n return sanitizeForFilename(firstHeading);\n }\n }\n\n const boldMatches = workDoneSection.match(/\\*\\*([^*]+)\\*\\*/g);\n if (boldMatches && boldMatches.length > 0) {\n const firstBold = boldMatches[0].replace(/\\*\\*/g, '').trim();\n if (!isMeaninglessCandidate(firstBold) && firstBold.length > 3 && firstBold.length < 50) {\n return sanitizeForFilename(firstBold);\n }\n }\n\n const numberedItems = workDoneSection.match(/^\\d+\\.\\s+\\*\\*([^*]+)\\*\\*/m);\n if (numberedItems && !isMeaninglessCandidate(numberedItems[1])) {\n return sanitizeForFilename(numberedItems[1]);\n }\n }\n\n if (summary && summary.length > 5 && summary !== 'Session completed.' && !isMeaninglessCandidate(summary)) {\n const cleanSummary = summary\n .replace(/[^\\w\\s-]/g, ' ')\n .trim()\n .split(/\\s+/)\n .slice(0, 5)\n .join(' ');\n if (cleanSummary.length > 3 && !isMeaninglessCandidate(cleanSummary)) {\n return sanitizeForFilename(cleanSummary);\n }\n }\n\n return '';\n}\n\n/**\n * Rename a session note with a meaningful name.\n * Always uses \"NNNN - YYYY-MM-DD - Description.md\" format.\n * Returns the new path, or original path if rename fails.\n */\nexport function renameSessionNote(notePath: string, meaningfulName: string): string {\n if (!meaningfulName || !existsSync(notePath)) return notePath;\n\n const dir = join(notePath, '..');\n const oldFilename = basename(notePath);\n\n const correctMatch = oldFilename.match(/^(\\d{3,4}) - (\\d{4}-\\d{2}-\\d{2}) - .*\\.md$/);\n const legacyMatch = oldFilename.match(/^(\\d{3,4})_(\\d{4}-\\d{2}-\\d{2})_.*\\.md$/);\n const match = correctMatch || legacyMatch;\n if (!match) return notePath;\n\n const [, noteNumber, date] = match;\n\n const titleCaseName = meaningfulName\n .split(/[\\s_-]+/)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n .trim();\n\n const paddedNumber = noteNumber.padStart(4, '0');\n const newFilename = `${paddedNumber} - ${date} - ${titleCaseName}.md`;\n const newPath = join(dir, newFilename);\n\n if (newFilename === oldFilename) return notePath;\n\n try {\n renameSync(notePath, newPath);\n console.error(`Renamed note: ${oldFilename} \u2192 ${newFilename}`);\n return newPath;\n } catch (error) {\n console.error(`Could not rename note: ${error}`);\n return notePath;\n }\n}\n\n/** Update the session note's H1 title and rename the file. */\nexport function updateSessionNoteTitle(notePath: string, newTitle: string): void {\n if (!existsSync(notePath)) {\n console.error(`Note file not found: ${notePath}`);\n return;\n }\n\n let content = readFileSync(notePath, 'utf-8');\n content = content.replace(/^# Session \\d+:.*$/m, (match) => {\n const sessionNum = match.match(/Session (\\d+)/)?.[1] || '';\n return `# Session ${sessionNum}: ${newTitle}`;\n });\n writeFileSync(notePath, content);\n renameSessionNote(notePath, sanitizeForFilename(newTitle));\n}\n\n/**\n * Finalize session note \u2014 mark as complete, add summary, rename with meaningful name.\n * IDEMPOTENT: subsequent calls are no-ops if already finalized.\n * Returns the final path (may be renamed).\n */\nexport function finalizeSessionNote(notePath: string, summary: string): string {\n if (!existsSync(notePath)) {\n console.error(`Note file not found: ${notePath}`);\n return notePath;\n }\n\n let content = readFileSync(notePath, 'utf-8');\n\n if (content.includes('**Status:** Completed')) {\n console.error(`Note already finalized: ${basename(notePath)}`);\n return notePath;\n }\n\n content = content.replace('**Status:** In Progress', '**Status:** Completed');\n\n if (!content.includes('**Completed:**')) {\n const completionTime = new Date().toISOString();\n content = content.replace(\n '---\\n\\n## Work Done',\n `**Completed:** ${completionTime}\\n\\n---\\n\\n## Work Done`\n );\n }\n\n const nextStepsMatch = content.match(/## Next Steps\\n\\n(<!-- .*? -->)/);\n if (nextStepsMatch) {\n content = content.replace(\n nextStepsMatch[0],\n `## Next Steps\\n\\n${summary || 'Session completed.'}`\n );\n }\n\n writeFileSync(notePath, content);\n console.error(`Session note finalized: ${basename(notePath)}`);\n\n const meaningfulName = extractMeaningfulName(content, summary);\n if (meaningfulName) {\n return renameSessionNote(notePath, meaningfulName);\n }\n\n return notePath;\n}\n", "/**\n * Session token counting from .jsonl transcript files.\n */\n\nimport { existsSync, readFileSync } from 'fs';\n\n/**\n * Calculate total tokens from a session .jsonl file.\n * Sums input, output, cache_creation, and cache_read tokens.\n */\nexport function calculateSessionTokens(jsonlPath: string): number {\n if (!existsSync(jsonlPath)) return 0;\n\n try {\n const content = readFileSync(jsonlPath, 'utf-8');\n const lines = content.trim().split('\\n');\n let totalTokens = 0;\n\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n if (entry.message?.usage) {\n const usage = entry.message.usage;\n totalTokens += (usage.input_tokens || 0);\n totalTokens += (usage.output_tokens || 0);\n totalTokens += (usage.cache_creation_input_tokens || 0);\n totalTokens += (usage.cache_read_input_tokens || 0);\n }\n } catch {\n // Skip invalid JSON lines\n }\n }\n\n return totalTokens;\n } catch (error) {\n console.error(`Error calculating tokens: ${error}`);\n return 0;\n }\n}\n", "/**\n * TODO.md management \u2014 creation, task updates, checkpoints, and Continue section.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { findTodoPath } from './paths.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** Task item for TODO.md. */\nexport interface TodoItem {\n content: string;\n completed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Ensure TODO.md exists. Creates it with default structure if missing.\n * Returns the path to the TODO.md file.\n */\nexport function ensureTodoMd(cwd: string): string {\n const todoPath = findTodoPath(cwd);\n\n if (!existsSync(todoPath)) {\n const parentDir = join(todoPath, '..');\n if (!existsSync(parentDir)) mkdirSync(parentDir, { recursive: true });\n\n const content = `# TODO\n\n## Current Session\n\n- [ ] (Tasks will be tracked here)\n\n## Backlog\n\n- [ ] (Future tasks)\n\n---\n\n*Last updated: ${new Date().toISOString()}*\n`;\n\n writeFileSync(todoPath, content);\n console.error(`Created TODO.md: ${todoPath}`);\n }\n\n return todoPath;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Update TODO.md with current session tasks.\n * Preserves the Backlog section and ensures exactly ONE timestamp at the end.\n */\nexport function updateTodoMd(cwd: string, tasks: TodoItem[], sessionSummary?: string): void {\n const todoPath = ensureTodoMd(cwd);\n const content = readFileSync(todoPath, 'utf-8');\n\n const backlogMatch = content.match(/## Backlog[\\s\\S]*?(?=\\n---|\\n\\*Last updated|$)/);\n const backlogSection = backlogMatch\n ? backlogMatch[0].trim()\n : '## Backlog\\n\\n- [ ] (Future tasks)';\n\n const taskLines = tasks.length > 0\n ? tasks.map(t => `- [${t.completed ? 'x' : ' '}] ${t.content}`).join('\\n')\n : '- [ ] (No active tasks)';\n\n const newContent = `# TODO\n\n## Current Session\n\n${taskLines}\n\n${sessionSummary ? `**Session Summary:** ${sessionSummary}\\n\\n` : ''}${backlogSection}\n\n---\n\n*Last updated: ${new Date().toISOString()}*\n`;\n\n writeFileSync(todoPath, newContent);\n console.error(`Updated TODO.md: ${todoPath}`);\n}\n\n/**\n * Add a checkpoint entry to TODO.md (without replacing tasks).\n * Ensures exactly ONE timestamp line at the end.\n */\nexport function addTodoCheckpoint(cwd: string, checkpoint: string): void {\n const todoPath = ensureTodoMd(cwd);\n let content = readFileSync(todoPath, 'utf-8');\n\n // Remove ALL existing timestamp lines and trailing separators\n content = content.replace(/(\\n---\\s*)*(\\n\\*Last updated:.*\\*\\s*)+$/g, '');\n\n const checkpointText = `\\n**Checkpoint (${new Date().toISOString()}):** ${checkpoint}\\n\\n`;\n\n const backlogIndex = content.indexOf('## Backlog');\n if (backlogIndex !== -1) {\n content = content.substring(0, backlogIndex) + checkpointText + content.substring(backlogIndex);\n } else {\n const continueIndex = content.indexOf('## Continue');\n if (continueIndex !== -1) {\n const afterContinue = content.indexOf('\\n---', continueIndex);\n if (afterContinue !== -1) {\n const insertAt = afterContinue + 4;\n content = content.substring(0, insertAt) + '\\n' + checkpointText + content.substring(insertAt);\n } else {\n content = content.trimEnd() + '\\n' + checkpointText;\n }\n } else {\n content = content.trimEnd() + '\\n' + checkpointText;\n }\n }\n\n content = content.trimEnd() + `\\n\\n---\\n\\n*Last updated: ${new Date().toISOString()}*\\n`;\n\n writeFileSync(todoPath, content);\n console.error(`Checkpoint added to TODO.md`);\n}\n\n/**\n * Update the ## Continue section at the top of TODO.md.\n * Mirrors \"pause session\" behavior \u2014 gives the next session a starting point.\n * Replaces any existing ## Continue section.\n */\nexport function updateTodoContinue(\n cwd: string,\n noteFilename: string,\n state: string | null,\n tokenDisplay: string\n): void {\n const todoPath = ensureTodoMd(cwd);\n let content = readFileSync(todoPath, 'utf-8');\n\n // Remove existing ## Continue section\n content = content.replace(/## Continue\\n[\\s\\S]*?\\n---\\n+/, '');\n\n const now = new Date().toISOString();\n const stateLines = state\n ? state.split('\\n').filter(l => l.trim()).slice(0, 10).map(l => `> ${l}`).join('\\n')\n : `> Working directory: ${cwd}. Check the latest session note for details.`;\n\n const continueSection = `## Continue\n\n> **Last session:** ${noteFilename.replace('.md', '')}\n> **Paused at:** ${now}\n>\n${stateLines}\n\n---\n\n`;\n\n content = content.replace(/^\\s+/, '');\n\n const titleMatch = content.match(/^(# [^\\n]+\\n+)/);\n if (titleMatch) {\n content = titleMatch[1] + continueSection + content.substring(titleMatch[0].length);\n } else {\n content = continueSection + content;\n }\n\n content = content.replace(/(\\n---\\s*)*(\\n\\*Last updated:.*\\*\\s*)+$/g, '');\n content = content.trimEnd() + `\\n\\n---\\n\\n*Last updated: ${now}*\\n`;\n\n writeFileSync(todoPath, content);\n console.error('TODO.md ## Continue section updated');\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["existsSync", "readFileSync", "writeFileSync", "basename", "join", "existsSync", "join", "join", "existsSync", "join", "existsSync", "existsSync", "readFileSync", "join", "homedir", "resolve", "existsSync", "mkdirSync", "readdirSync", "readFileSync", "renameSync", "join", "basename", "content", "existsSync", "join", "basename", "renameSync", "existsSync", "readFileSync", "existsSync", "mkdirSync", "readFileSync", "writeFileSync", "join", "existsSync", "join", "mkdirSync", "writeFileSync", "readFileSync", "writeFileSync", "readFileSync", "basename", "join", "existsSync", "writeFileSync", "resolve"]
|
|
7
7
|
}
|