@tekmidian/pai 0.8.4 → 0.9.0

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.
Files changed (33) hide show
  1. package/ARCHITECTURE.md +121 -0
  2. package/FEATURE.md +5 -0
  3. package/README.md +54 -0
  4. package/dist/cli/index.mjs +11 -11
  5. package/dist/daemon/index.mjs +3 -3
  6. package/dist/{daemon-nXyhvdzz.mjs → daemon-VIFoKc_z.mjs} +31 -6
  7. package/dist/daemon-VIFoKc_z.mjs.map +1 -0
  8. package/dist/daemon-mcp/index.mjs +51 -0
  9. package/dist/daemon-mcp/index.mjs.map +1 -1
  10. package/dist/{factory-Ygqe_bVZ.mjs → factory-e0k1HWuc.mjs} +2 -2
  11. package/dist/{factory-Ygqe_bVZ.mjs.map → factory-e0k1HWuc.mjs.map} +1 -1
  12. package/dist/hooks/load-project-context.mjs +276 -89
  13. package/dist/hooks/load-project-context.mjs.map +4 -4
  14. package/dist/hooks/stop-hook.mjs +152 -2
  15. package/dist/hooks/stop-hook.mjs.map +3 -3
  16. package/dist/{postgres-CKf-EDtS.mjs → postgres-DvEPooLO.mjs} +45 -10
  17. package/dist/postgres-DvEPooLO.mjs.map +1 -0
  18. package/dist/query-feedback-Dv43XKHM.mjs +76 -0
  19. package/dist/query-feedback-Dv43XKHM.mjs.map +1 -0
  20. package/dist/tools-C4SBZHga.mjs +1731 -0
  21. package/dist/tools-C4SBZHga.mjs.map +1 -0
  22. package/dist/{vault-indexer-Bi2cRmn7.mjs → vault-indexer-B-aJpRZC.mjs} +3 -2
  23. package/dist/{vault-indexer-Bi2cRmn7.mjs.map → vault-indexer-B-aJpRZC.mjs.map} +1 -1
  24. package/dist/{zettelkasten-cdajbnPr.mjs → zettelkasten-DhBKZQHF.mjs} +358 -3
  25. package/dist/zettelkasten-DhBKZQHF.mjs.map +1 -0
  26. package/package.json +1 -1
  27. package/src/hooks/ts/session-start/load-project-context.ts +36 -0
  28. package/src/hooks/ts/stop/stop-hook.ts +203 -1
  29. package/dist/daemon-nXyhvdzz.mjs.map +0 -1
  30. package/dist/postgres-CKf-EDtS.mjs.map +0 -1
  31. package/dist/tools-DcaJlYDN.mjs +0 -869
  32. package/dist/tools-DcaJlYDN.mjs.map +0 -1
  33. package/dist/zettelkasten-cdajbnPr.mjs.map +0 -1
@@ -15,7 +15,7 @@ async function createStorageBackend(config) {
15
15
  }
16
16
  async function tryPostgres(config) {
17
17
  try {
18
- const { PostgresBackend } = await import("./postgres-CKf-EDtS.mjs");
18
+ const { PostgresBackend } = await import("./postgres-DvEPooLO.mjs");
19
19
  const pgConfig = config.postgres ?? {};
20
20
  await PostgresBackend.ensureDatabase(pgConfig);
21
21
  const backend = new PostgresBackend(pgConfig);
@@ -41,4 +41,4 @@ async function createSQLiteBackend() {
41
41
 
42
42
  //#endregion
43
43
  export { factory_exports as n, createStorageBackend as t };
44
- //# sourceMappingURL=factory-Ygqe_bVZ.mjs.map
44
+ //# sourceMappingURL=factory-e0k1HWuc.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"factory-Ygqe_bVZ.mjs","names":[],"sources":["../src/storage/factory.ts"],"sourcesContent":["/**\n * Storage backend factory.\n *\n * Reads the daemon config and returns the appropriate StorageBackend.\n * If Postgres is configured but unavailable, falls back to SQLite with\n * a warning log — the daemon never crashes due to a missing Postgres.\n */\n\nimport type { PaiDaemonConfig } from \"../daemon/config.js\";\nimport type { StorageBackend } from \"./interface.js\";\n\n/**\n * Create and return the configured StorageBackend.\n *\n * Auto-fallback behaviour:\n * - storageBackend = \"sqlite\" → SQLiteBackend always\n * - storageBackend = \"postgres\" → PostgresBackend if reachable, else SQLiteBackend\n */\nexport async function createStorageBackend(\n config: PaiDaemonConfig\n): Promise<StorageBackend> {\n if (config.storageBackend === \"postgres\") {\n return await tryPostgres(config);\n }\n\n // Default: SQLite\n return createSQLiteBackend();\n}\n\nasync function tryPostgres(config: PaiDaemonConfig): Promise<StorageBackend> {\n try {\n const { PostgresBackend } = await import(\"./postgres.js\");\n const pgConfig = config.postgres ?? {};\n\n // Ensure the per-user database exists and has the schema applied\n await PostgresBackend.ensureDatabase(pgConfig);\n\n const backend = new PostgresBackend(pgConfig);\n\n const err = await backend.testConnection();\n if (err) {\n process.stderr.write(\n `[pai-daemon] Postgres unavailable (${err}). Falling back to SQLite.\\n`\n );\n await backend.close();\n return createSQLiteBackend();\n }\n\n process.stderr.write(\"[pai-daemon] Connected to PostgreSQL backend.\\n\");\n return backend;\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[pai-daemon] Postgres init error (${msg}). Falling back to SQLite.\\n`\n );\n return createSQLiteBackend();\n }\n}\n\nasync function createSQLiteBackend(): Promise<StorageBackend> {\n const { openFederation } = await import(\"../memory/db.js\");\n const { SQLiteBackend } = await import(\"./sqlite.js\");\n const db = openFederation();\n return new SQLiteBackend(db);\n}\n"],"mappings":";;;;;;;;;;;AAkBA,eAAsB,qBACpB,QACyB;AACzB,KAAI,OAAO,mBAAmB,WAC5B,QAAO,MAAM,YAAY,OAAO;AAIlC,QAAO,qBAAqB;;AAG9B,eAAe,YAAY,QAAkD;AAC3E,KAAI;EACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,WAAW,OAAO,YAAY,EAAE;AAGtC,QAAM,gBAAgB,eAAe,SAAS;EAE9C,MAAM,UAAU,IAAI,gBAAgB,SAAS;EAE7C,MAAM,MAAM,MAAM,QAAQ,gBAAgB;AAC1C,MAAI,KAAK;AACP,WAAQ,OAAO,MACb,sCAAsC,IAAI,8BAC3C;AACD,SAAM,QAAQ,OAAO;AACrB,UAAO,qBAAqB;;AAG9B,UAAQ,OAAO,MAAM,kDAAkD;AACvE,SAAO;UACA,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MACb,qCAAqC,IAAI,8BAC1C;AACD,SAAO,qBAAqB;;;AAIhC,eAAe,sBAA+C;CAC5D,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,EAAE,kBAAkB,MAAM,OAAO;AAEvC,QAAO,IAAI,cADA,gBAAgB,CACC"}
1
+ {"version":3,"file":"factory-e0k1HWuc.mjs","names":[],"sources":["../src/storage/factory.ts"],"sourcesContent":["/**\n * Storage backend factory.\n *\n * Reads the daemon config and returns the appropriate StorageBackend.\n * If Postgres is configured but unavailable, falls back to SQLite with\n * a warning log — the daemon never crashes due to a missing Postgres.\n */\n\nimport type { PaiDaemonConfig } from \"../daemon/config.js\";\nimport type { StorageBackend } from \"./interface.js\";\n\n/**\n * Create and return the configured StorageBackend.\n *\n * Auto-fallback behaviour:\n * - storageBackend = \"sqlite\" → SQLiteBackend always\n * - storageBackend = \"postgres\" → PostgresBackend if reachable, else SQLiteBackend\n */\nexport async function createStorageBackend(\n config: PaiDaemonConfig\n): Promise<StorageBackend> {\n if (config.storageBackend === \"postgres\") {\n return await tryPostgres(config);\n }\n\n // Default: SQLite\n return createSQLiteBackend();\n}\n\nasync function tryPostgres(config: PaiDaemonConfig): Promise<StorageBackend> {\n try {\n const { PostgresBackend } = await import(\"./postgres.js\");\n const pgConfig = config.postgres ?? {};\n\n // Ensure the per-user database exists and has the schema applied\n await PostgresBackend.ensureDatabase(pgConfig);\n\n const backend = new PostgresBackend(pgConfig);\n\n const err = await backend.testConnection();\n if (err) {\n process.stderr.write(\n `[pai-daemon] Postgres unavailable (${err}). Falling back to SQLite.\\n`\n );\n await backend.close();\n return createSQLiteBackend();\n }\n\n process.stderr.write(\"[pai-daemon] Connected to PostgreSQL backend.\\n\");\n return backend;\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[pai-daemon] Postgres init error (${msg}). Falling back to SQLite.\\n`\n );\n return createSQLiteBackend();\n }\n}\n\nasync function createSQLiteBackend(): Promise<StorageBackend> {\n const { openFederation } = await import(\"../memory/db.js\");\n const { SQLiteBackend } = await import(\"./sqlite.js\");\n const db = openFederation();\n return new SQLiteBackend(db);\n}\n"],"mappings":";;;;;;;;;;;AAkBA,eAAsB,qBACpB,QACyB;AACzB,KAAI,OAAO,mBAAmB,WAC5B,QAAO,MAAM,YAAY,OAAO;AAIlC,QAAO,qBAAqB;;AAG9B,eAAe,YAAY,QAAkD;AAC3E,KAAI;EACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;EACzC,MAAM,WAAW,OAAO,YAAY,EAAE;AAGtC,QAAM,gBAAgB,eAAe,SAAS;EAE9C,MAAM,UAAU,IAAI,gBAAgB,SAAS;EAE7C,MAAM,MAAM,MAAM,QAAQ,gBAAgB;AAC1C,MAAI,KAAK;AACP,WAAQ,OAAO,MACb,sCAAsC,IAAI,8BAC3C;AACD,SAAM,QAAQ,OAAO;AACrB,UAAO,qBAAqB;;AAG9B,UAAQ,OAAO,MAAM,kDAAkD;AACvE,SAAO;UACA,GAAG;EACV,MAAM,MAAM,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;AACtD,UAAQ,OAAO,MACb,qCAAqC,IAAI,8BAC1C;AACD,SAAO,qBAAqB;;;AAIhC,eAAe,sBAA+C;CAC5D,MAAM,EAAE,mBAAmB,MAAM,OAAO;CACxC,MAAM,EAAE,kBAAkB,MAAM,OAAO;AAEvC,QAAO,IAAI,cADA,gBAAgB,CACC"}
@@ -1,28 +1,183 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/hooks/ts/session-start/load-project-context.ts
4
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync } from "fs";
5
- import { join as join5, basename as basename3, dirname, resolve as resolve2 } from "path";
6
- import { homedir as homedir3 } from "os";
4
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync } from "fs";
5
+ import { join as join6, basename as basename4, dirname, resolve as resolve2 } from "path";
6
+ import { homedir as homedir4 } from "os";
7
7
  import { execSync } from "child_process";
8
8
 
9
+ // src/memory/wakeup.ts
10
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
11
+ import { join, basename } from "node:path";
12
+ import { homedir } from "node:os";
13
+ var L1_TOKEN_BUDGET = 800;
14
+ var L1_CHAR_BUDGET = L1_TOKEN_BUDGET * 4;
15
+ var L1_MAX_NOTES = 10;
16
+ var EXTRACT_SECTIONS = [
17
+ "Work Done",
18
+ "Key Decisions",
19
+ "Next Steps",
20
+ "Checkpoint"
21
+ ];
22
+ var IDENTITY_FILE = join(homedir(), ".pai", "identity.txt");
23
+ function loadL0Identity() {
24
+ if (!existsSync(IDENTITY_FILE)) return "";
25
+ try {
26
+ return readFileSync(IDENTITY_FILE, "utf-8").trim();
27
+ } catch {
28
+ return "";
29
+ }
30
+ }
31
+ function findNotesDirForProject(rootPath) {
32
+ const localCandidates = [
33
+ join(rootPath, "Notes"),
34
+ join(rootPath, "notes"),
35
+ join(rootPath, ".claude", "Notes")
36
+ ];
37
+ for (const p of localCandidates) {
38
+ if (existsSync(p)) return p;
39
+ }
40
+ const encoded = rootPath.replace(/\//g, "-").replace(/\./g, "-").replace(/ /g, "-");
41
+ const centralNotes = join(
42
+ homedir(),
43
+ ".claude",
44
+ "projects",
45
+ encoded,
46
+ "Notes"
47
+ );
48
+ if (existsSync(centralNotes)) return centralNotes;
49
+ return null;
50
+ }
51
+ function findSessionNotes(notesDir) {
52
+ const result = [];
53
+ const scanDir = (dir) => {
54
+ if (!existsSync(dir)) return;
55
+ let entries;
56
+ try {
57
+ entries = readdirSync(dir, { withFileTypes: true }).map((e) => ({ name: e.name, isDir: e.isDirectory() }));
58
+ } catch {
59
+ return;
60
+ }
61
+ for (const entry of entries) {
62
+ const fullPath = join(dir, entry.name);
63
+ if (entry.isDir) {
64
+ scanDir(fullPath);
65
+ } else if (entry.name.match(/^\d{3,4}[\s_-].*\.md$/)) {
66
+ result.push(fullPath);
67
+ }
68
+ }
69
+ };
70
+ scanDir(notesDir);
71
+ result.sort((a, b) => {
72
+ const numA = parseInt(basename(a).match(/^(\d+)/)?.[1] ?? "0", 10);
73
+ const numB = parseInt(basename(b).match(/^(\d+)/)?.[1] ?? "0", 10);
74
+ return numB - numA;
75
+ });
76
+ return result;
77
+ }
78
+ function extractKeyLines(content, maxChars) {
79
+ const lines = content.split("\n");
80
+ const selected = [];
81
+ let inTargetSection = false;
82
+ let currentSection = "";
83
+ let charCount = 0;
84
+ for (const line of lines) {
85
+ const h2Match = line.match(/^## (.+)$/);
86
+ const h3Match = line.match(/^### (.+)$/);
87
+ if (h2Match) {
88
+ currentSection = h2Match[1];
89
+ inTargetSection = EXTRACT_SECTIONS.some(
90
+ (s) => currentSection.toLowerCase().includes(s.toLowerCase())
91
+ );
92
+ continue;
93
+ }
94
+ if (h3Match) {
95
+ if (inTargetSection) {
96
+ const label = `[${h3Match[1]}]`;
97
+ if (charCount + label.length < maxChars) {
98
+ selected.push(label);
99
+ charCount += label.length + 1;
100
+ }
101
+ }
102
+ continue;
103
+ }
104
+ if (!inTargetSection) continue;
105
+ const trimmed = line.trim();
106
+ if (!trimmed || trimmed.startsWith("<!--") || trimmed === "---") continue;
107
+ if (trimmed.startsWith("- ") || trimmed.startsWith("* ") || trimmed.match(/^\d+\./) || trimmed.startsWith("**")) {
108
+ if (charCount + trimmed.length + 1 > maxChars) break;
109
+ selected.push(trimmed);
110
+ charCount += trimmed.length + 1;
111
+ }
112
+ }
113
+ return selected.join("\n");
114
+ }
115
+ function buildL1EssentialStory(rootPath, tokenBudget = L1_TOKEN_BUDGET) {
116
+ const charBudget = tokenBudget * 4;
117
+ const notesDir = findNotesDirForProject(rootPath);
118
+ if (!notesDir) return "";
119
+ const noteFiles = findSessionNotes(notesDir).slice(0, L1_MAX_NOTES);
120
+ if (noteFiles.length === 0) return "";
121
+ const sections = [];
122
+ let remaining = charBudget;
123
+ for (const noteFile of noteFiles) {
124
+ if (remaining <= 50) break;
125
+ let content;
126
+ try {
127
+ content = readFileSync(noteFile, "utf-8");
128
+ } catch {
129
+ continue;
130
+ }
131
+ const name = basename(noteFile);
132
+ const titleMatch = name.match(/^\d+ - (\d{4}-\d{2}-\d{2}) - (.+)\.md$/);
133
+ const dateLabel = titleMatch ? titleMatch[1] : "";
134
+ const titleLabel = titleMatch ? titleMatch[2] : name.replace(/^\d+ - /, "").replace(/\.md$/, "");
135
+ const perNoteChars = Math.min(remaining, Math.floor(charBudget / noteFiles.length) + 200);
136
+ const extracted = extractKeyLines(content, perNoteChars);
137
+ if (!extracted) continue;
138
+ const noteBlock = `[${dateLabel} - ${titleLabel}]
139
+ ${extracted}`;
140
+ sections.push(noteBlock);
141
+ remaining -= noteBlock.length + 1;
142
+ }
143
+ if (sections.length === 0) return "";
144
+ return sections.join("\n\n");
145
+ }
146
+ function buildWakeupContext(rootPath, tokenBudget = L1_TOKEN_BUDGET) {
147
+ const identity = loadL0Identity();
148
+ const essentialStory = rootPath ? buildL1EssentialStory(rootPath, tokenBudget) : "";
149
+ if (!identity && !essentialStory) return "";
150
+ const parts = [];
151
+ if (identity) {
152
+ parts.push(`## L0 Identity
153
+
154
+ ${identity}`);
155
+ }
156
+ if (essentialStory) {
157
+ parts.push(`## L1 Essential Story
158
+
159
+ ${essentialStory}`);
160
+ }
161
+ return parts.join("\n\n");
162
+ }
163
+
9
164
  // src/hooks/ts/lib/project-utils/paths.ts
10
- import { existsSync as existsSync2, mkdirSync, readdirSync, renameSync } from "fs";
11
- import { join as join2, basename } from "path";
165
+ import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, renameSync } from "fs";
166
+ import { join as join3, basename as basename2 } from "path";
12
167
 
13
168
  // src/hooks/ts/lib/pai-paths.ts
14
- import { homedir } from "os";
15
- import { resolve, join } from "path";
16
- import { existsSync, readFileSync } from "fs";
169
+ import { homedir as homedir2 } from "os";
170
+ import { resolve, join as join2 } from "path";
171
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
17
172
  function loadEnvFile() {
18
173
  const possiblePaths = [
19
174
  resolve(process.env.PAI_DIR || "", ".env"),
20
- resolve(homedir(), ".claude", ".env")
175
+ resolve(homedir2(), ".claude", ".env")
21
176
  ];
22
177
  for (const envPath of possiblePaths) {
23
- if (existsSync(envPath)) {
178
+ if (existsSync2(envPath)) {
24
179
  try {
25
- const content = readFileSync(envPath, "utf-8");
180
+ const content = readFileSync2(envPath, "utf-8");
26
181
  for (const line of content.split("\n")) {
27
182
  const trimmed = line.trim();
28
183
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -33,8 +188,8 @@ function loadEnvFile() {
33
188
  if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
34
189
  value = value.slice(1, -1);
35
190
  }
36
- value = value.replace(/\$HOME/g, homedir());
37
- value = value.replace(/^~(?=\/|$)/, homedir());
191
+ value = value.replace(/\$HOME/g, homedir2());
192
+ value = value.replace(/^~(?=\/|$)/, homedir2());
38
193
  if (process.env[key] === void 0) {
39
194
  process.env[key] = value;
40
195
  }
@@ -47,19 +202,19 @@ function loadEnvFile() {
47
202
  }
48
203
  }
49
204
  loadEnvFile();
50
- var PAI_DIR = process.env.PAI_DIR ? resolve(process.env.PAI_DIR) : resolve(homedir(), ".claude");
51
- var HOOKS_DIR = join(PAI_DIR, "Hooks");
52
- var SKILLS_DIR = join(PAI_DIR, "Skills");
53
- var AGENTS_DIR = join(PAI_DIR, "Agents");
54
- var HISTORY_DIR = join(PAI_DIR, "History");
55
- var COMMANDS_DIR = join(PAI_DIR, "Commands");
205
+ var PAI_DIR = process.env.PAI_DIR ? resolve(process.env.PAI_DIR) : resolve(homedir2(), ".claude");
206
+ var HOOKS_DIR = join2(PAI_DIR, "Hooks");
207
+ var SKILLS_DIR = join2(PAI_DIR, "Skills");
208
+ var AGENTS_DIR = join2(PAI_DIR, "Agents");
209
+ var HISTORY_DIR = join2(PAI_DIR, "History");
210
+ var COMMANDS_DIR = join2(PAI_DIR, "Commands");
56
211
  function validatePAIStructure() {
57
- if (!existsSync(PAI_DIR)) {
212
+ if (!existsSync2(PAI_DIR)) {
58
213
  console.error(`PAI_DIR does not exist: ${PAI_DIR}`);
59
214
  console.error(` Expected ~/.claude or set PAI_DIR environment variable`);
60
215
  process.exit(1);
61
216
  }
62
- if (!existsSync(HOOKS_DIR)) {
217
+ if (!existsSync2(HOOKS_DIR)) {
63
218
  console.error(`PAI hooks directory not found: ${HOOKS_DIR}`);
64
219
  console.error(` Your PAI_DIR may be misconfigured`);
65
220
  console.error(` Current PAI_DIR: ${PAI_DIR}`);
@@ -69,7 +224,7 @@ function validatePAIStructure() {
69
224
  validatePAIStructure();
70
225
 
71
226
  // src/hooks/ts/lib/project-utils/paths.ts
72
- var PROJECTS_DIR = join2(PAI_DIR, "projects");
227
+ var PROJECTS_DIR = join3(PAI_DIR, "projects");
73
228
  var PROBE_CWD_PATTERNS = [
74
229
  "/CodexBar/ClaudeProbe",
75
230
  "/ClaudeProbe"
@@ -83,23 +238,23 @@ function encodePath(path) {
83
238
  }
84
239
  function getProjectDir(cwd) {
85
240
  const encoded = encodePath(cwd);
86
- return join2(PROJECTS_DIR, encoded);
241
+ return join3(PROJECTS_DIR, encoded);
87
242
  }
88
243
  function getNotesDir(cwd) {
89
- return join2(getProjectDir(cwd), "Notes");
244
+ return join3(getProjectDir(cwd), "Notes");
90
245
  }
91
246
  function findNotesDir(cwd) {
92
- const cwdBasename = basename(cwd).toLowerCase();
93
- if (cwdBasename === "notes" && existsSync2(cwd)) {
247
+ const cwdBasename = basename2(cwd).toLowerCase();
248
+ if (cwdBasename === "notes" && existsSync3(cwd)) {
94
249
  return { path: cwd, isLocal: true };
95
250
  }
96
251
  const localPaths = [
97
- join2(cwd, "Notes"),
98
- join2(cwd, "notes"),
99
- join2(cwd, ".claude", "Notes")
252
+ join3(cwd, "Notes"),
253
+ join3(cwd, "notes"),
254
+ join3(cwd, ".claude", "Notes")
100
255
  ];
101
256
  for (const path of localPaths) {
102
- if (existsSync2(path)) {
257
+ if (existsSync3(path)) {
103
258
  return { path, isLocal: true };
104
259
  }
105
260
  }
@@ -107,41 +262,41 @@ function findNotesDir(cwd) {
107
262
  }
108
263
  function findTodoPath(cwd) {
109
264
  const localPaths = [
110
- join2(cwd, "TODO.md"),
111
- join2(cwd, "notes", "TODO.md"),
112
- join2(cwd, "Notes", "TODO.md"),
113
- join2(cwd, ".claude", "TODO.md")
265
+ join3(cwd, "TODO.md"),
266
+ join3(cwd, "notes", "TODO.md"),
267
+ join3(cwd, "Notes", "TODO.md"),
268
+ join3(cwd, ".claude", "TODO.md")
114
269
  ];
115
270
  for (const path of localPaths) {
116
- if (existsSync2(path)) return path;
271
+ if (existsSync3(path)) return path;
117
272
  }
118
- return join2(getNotesDir(cwd), "TODO.md");
273
+ return join3(getNotesDir(cwd), "TODO.md");
119
274
  }
120
275
  function findAllClaudeMdPaths(cwd) {
121
276
  const foundPaths = [];
122
277
  const localPaths = [
123
- join2(cwd, ".claude", "CLAUDE.md"),
124
- join2(cwd, "CLAUDE.md"),
125
- join2(cwd, "Notes", "CLAUDE.md"),
126
- join2(cwd, "notes", "CLAUDE.md"),
127
- join2(cwd, "Prompts", "CLAUDE.md"),
128
- join2(cwd, "prompts", "CLAUDE.md")
278
+ join3(cwd, ".claude", "CLAUDE.md"),
279
+ join3(cwd, "CLAUDE.md"),
280
+ join3(cwd, "Notes", "CLAUDE.md"),
281
+ join3(cwd, "notes", "CLAUDE.md"),
282
+ join3(cwd, "Prompts", "CLAUDE.md"),
283
+ join3(cwd, "prompts", "CLAUDE.md")
129
284
  ];
130
285
  for (const path of localPaths) {
131
- if (existsSync2(path)) foundPaths.push(path);
286
+ if (existsSync3(path)) foundPaths.push(path);
132
287
  }
133
288
  return foundPaths;
134
289
  }
135
290
 
136
291
  // src/hooks/ts/lib/project-utils/notify.ts
137
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
138
- import { join as join3 } from "path";
139
- import { homedir as homedir2 } from "os";
292
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
293
+ import { join as join4 } from "path";
294
+ import { homedir as homedir3 } from "os";
140
295
  function isWhatsAppEnabled() {
141
296
  try {
142
- const settingsPath = join3(homedir2(), ".claude", "settings.json");
143
- if (!existsSync3(settingsPath)) return false;
144
- const settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
297
+ const settingsPath = join4(homedir3(), ".claude", "settings.json");
298
+ if (!existsSync4(settingsPath)) return false;
299
+ const settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
145
300
  const enabled = settings.enabledMcpjsonServers || [];
146
301
  return enabled.includes("aibroker") || enabled.includes("whazaa") || enabled.includes("telex");
147
302
  } catch {
@@ -186,21 +341,21 @@ async function sendNtfyNotification(message, retries = 2) {
186
341
  }
187
342
 
188
343
  // src/hooks/ts/lib/project-utils/session-notes.ts
189
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync, renameSync as renameSync2 } from "fs";
190
- import { join as join4, basename as basename2 } from "path";
344
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync, renameSync as renameSync2 } from "fs";
345
+ import { join as join5, basename as basename3 } from "path";
191
346
  function getMonthDir(notesDir) {
192
347
  const now = /* @__PURE__ */ new Date();
193
348
  const year = String(now.getFullYear());
194
349
  const month = String(now.getMonth() + 1).padStart(2, "0");
195
- const monthDir = join4(notesDir, year, month);
196
- if (!existsSync4(monthDir)) {
350
+ const monthDir = join5(notesDir, year, month);
351
+ if (!existsSync5(monthDir)) {
197
352
  mkdirSync2(monthDir, { recursive: true });
198
353
  }
199
354
  return monthDir;
200
355
  }
201
356
  function getNextNoteNumber(notesDir) {
202
357
  const monthDir = getMonthDir(notesDir);
203
- const files = readdirSync2(monthDir).filter((f) => f.match(/^\d{3,4}[\s_-]/)).sort();
358
+ const files = readdirSync3(monthDir).filter((f) => f.match(/^\d{3,4}[\s_-]/)).sort();
204
359
  if (files.length === 0) return "0001";
205
360
  let maxNumber = 0;
206
361
  for (const file of files) {
@@ -213,27 +368,27 @@ function getNextNoteNumber(notesDir) {
213
368
  return String(maxNumber + 1).padStart(4, "0");
214
369
  }
215
370
  function getCurrentNotePath(notesDir) {
216
- if (!existsSync4(notesDir)) return null;
371
+ if (!existsSync5(notesDir)) return null;
217
372
  const findLatestIn = (dir) => {
218
- if (!existsSync4(dir)) return null;
219
- const files = readdirSync2(dir).filter((f) => f.match(/^\d{3,4}[\s_-].*\.md$/)).sort((a, b) => {
373
+ if (!existsSync5(dir)) return null;
374
+ const files = readdirSync3(dir).filter((f) => f.match(/^\d{3,4}[\s_-].*\.md$/)).sort((a, b) => {
220
375
  const numA = parseInt(a.match(/^(\d+)/)?.[1] || "0", 10);
221
376
  const numB = parseInt(b.match(/^(\d+)/)?.[1] || "0", 10);
222
377
  return numA - numB;
223
378
  });
224
379
  if (files.length === 0) return null;
225
- return join4(dir, files[files.length - 1]);
380
+ return join5(dir, files[files.length - 1]);
226
381
  };
227
382
  const now = /* @__PURE__ */ new Date();
228
383
  const year = String(now.getFullYear());
229
384
  const month = String(now.getMonth() + 1).padStart(2, "0");
230
- const currentMonthDir = join4(notesDir, year, month);
385
+ const currentMonthDir = join5(notesDir, year, month);
231
386
  const found = findLatestIn(currentMonthDir);
232
387
  if (found) return found;
233
388
  const prevDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
234
389
  const prevYear = String(prevDate.getFullYear());
235
390
  const prevMonth = String(prevDate.getMonth() + 1).padStart(2, "0");
236
- const prevMonthDir = join4(notesDir, prevYear, prevMonth);
391
+ const prevMonthDir = join5(notesDir, prevYear, prevMonth);
237
392
  const prevFound = findLatestIn(prevMonthDir);
238
393
  if (prevFound) return prevFound;
239
394
  return findLatestIn(notesDir);
@@ -243,7 +398,7 @@ function createSessionNote(notesDir, description) {
243
398
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
244
399
  const monthDir = getMonthDir(notesDir);
245
400
  const filename = `${noteNumber} - ${date} - New Session.md`;
246
- const filepath = join4(monthDir, filename);
401
+ const filepath = join5(monthDir, filename);
247
402
  const content = `# Session ${noteNumber}: ${description}
248
403
 
249
404
  **Date:** ${date}
@@ -281,16 +436,16 @@ function findPaiBinary() {
281
436
  `${process.env.HOME}/.local/bin/pai`
282
437
  ];
283
438
  for (const p of fallbacks) {
284
- if (existsSync5(p)) return p;
439
+ if (existsSync6(p)) return p;
285
440
  }
286
441
  }
287
442
  return "pai";
288
443
  }
289
444
  function getRoutedNotesPath() {
290
- const routingFile = join5(PAI_DIR, "session-routing.json");
291
- if (!existsSync5(routingFile)) return null;
445
+ const routingFile = join6(PAI_DIR, "session-routing.json");
446
+ if (!existsSync6(routingFile)) return null;
292
447
  try {
293
- const routing = JSON.parse(readFileSync4(routingFile, "utf-8"));
448
+ const routing = JSON.parse(readFileSync5(routingFile, "utf-8"));
294
449
  const active = routing?.active_session;
295
450
  if (active?.notes_path) {
296
451
  return active.notes_path;
@@ -315,16 +470,16 @@ var PROJECT_SIGNALS = [
315
470
  "CMakeLists.txt",
316
471
  "tsconfig.json",
317
472
  "CLAUDE.md",
318
- join5("Notes", "PAI.md")
473
+ join6("Notes", "PAI.md")
319
474
  ];
320
475
  function hasProjectSignals(dir) {
321
476
  for (const signal of PROJECT_SIGNALS) {
322
- if (existsSync5(join5(dir, signal))) return true;
477
+ if (existsSync6(join6(dir, signal))) return true;
323
478
  }
324
479
  return false;
325
480
  }
326
481
  function isGuardedPath(dir) {
327
- const home = homedir3();
482
+ const home = homedir4();
328
483
  const resolved = resolve2(dir);
329
484
  if (resolved === home) return true;
330
485
  const parts = resolved.split("/").filter(Boolean);
@@ -355,9 +510,9 @@ async function main() {
355
510
  console.error("Could not parse hook input, using process.cwd()");
356
511
  }
357
512
  const cwd = hookInput?.cwd || process.cwd();
358
- let projectName = basename3(cwd);
513
+ let projectName = basename4(cwd);
359
514
  if (projectName.toLowerCase() === "notes") {
360
- projectName = basename3(dirname(cwd));
515
+ projectName = basename4(dirname(cwd));
361
516
  }
362
517
  console.error(`Working directory: ${cwd}`);
363
518
  console.error(`Project: ${projectName}`);
@@ -373,7 +528,7 @@ async function main() {
373
528
  for (const path of claudeMdPaths) {
374
529
  console.error(` - ${path}`);
375
530
  try {
376
- const content = readFileSync4(path, "utf-8");
531
+ const content = readFileSync5(path, "utf-8");
377
532
  claudeMdContents.push({ path, content });
378
533
  console.error(` Read ${content.length} chars`);
379
534
  } catch (error) {
@@ -388,7 +543,7 @@ async function main() {
388
543
  let notesDir;
389
544
  if (routedPath) {
390
545
  const { mkdirSync: mkdirSync3 } = await import("fs");
391
- if (!existsSync5(routedPath)) {
546
+ if (!existsSync6(routedPath)) {
392
547
  mkdirSync3(routedPath, { recursive: true });
393
548
  console.error(`Created routed Notes: ${routedPath}`);
394
549
  } else {
@@ -401,7 +556,7 @@ async function main() {
401
556
  notesDir = notesInfo.path;
402
557
  console.error(`Notes directory: ${notesDir} (local)`);
403
558
  } else {
404
- if (!existsSync5(notesInfo.path)) {
559
+ if (!existsSync6(notesInfo.path)) {
405
560
  const { mkdirSync: mkdirSync3 } = await import("fs");
406
561
  mkdirSync3(notesInfo.path, { recursive: true });
407
562
  console.error(`Created central Notes: ${notesInfo.path}`);
@@ -412,24 +567,24 @@ async function main() {
412
567
  }
413
568
  }
414
569
  const projectDir = getProjectDir(cwd);
415
- if (existsSync5(projectDir)) {
570
+ if (existsSync6(projectDir)) {
416
571
  try {
417
- const files = readdirSync3(projectDir);
572
+ const files = readdirSync4(projectDir);
418
573
  const jsonlFiles = files.filter((f) => f.endsWith(".jsonl")).map((f) => ({
419
574
  name: f,
420
- path: join5(projectDir, f),
421
- mtime: statSync(join5(projectDir, f)).mtime.getTime()
575
+ path: join6(projectDir, f),
576
+ mtime: statSync(join6(projectDir, f)).mtime.getTime()
422
577
  })).sort((a, b) => b.mtime - a.mtime);
423
578
  if (jsonlFiles.length > 1) {
424
579
  const { mkdirSync: mkdirSync3, renameSync: renameSync3 } = await import("fs");
425
- const sessionsDir = join5(projectDir, "sessions");
426
- if (!existsSync5(sessionsDir)) {
580
+ const sessionsDir = join6(projectDir, "sessions");
581
+ if (!existsSync6(sessionsDir)) {
427
582
  mkdirSync3(sessionsDir, { recursive: true });
428
583
  }
429
584
  for (let i = 1; i < jsonlFiles.length; i++) {
430
585
  const file = jsonlFiles[i];
431
- const destPath = join5(sessionsDir, file.name);
432
- if (!existsSync5(destPath)) {
586
+ const destPath = join6(sessionsDir, file.name);
587
+ if (!existsSync6(destPath)) {
433
588
  renameSync3(file.path, destPath);
434
589
  console.error(`Moved old session: ${file.name} \u2192 sessions/`);
435
590
  }
@@ -440,11 +595,11 @@ async function main() {
440
595
  }
441
596
  }
442
597
  const todoPath = findTodoPath(cwd);
443
- const hasTodo = existsSync5(todoPath);
598
+ const hasTodo = existsSync6(todoPath);
444
599
  if (hasTodo) {
445
600
  console.error(`TODO.md: ${todoPath}`);
446
601
  } else {
447
- const newTodoPath = join5(notesDir, "TODO.md");
602
+ const newTodoPath = join6(notesDir, "TODO.md");
448
603
  const { writeFileSync: writeFileSync2 } = await import("fs");
449
604
  writeFileSync2(newTodoPath, `# TODO
450
605
 
@@ -465,13 +620,13 @@ async function main() {
465
620
  const safeProjectName = typeof projectName === "string" && projectName.trim().length > 0 ? projectName.trim() : "Untitled Session";
466
621
  console.error("\nNo previous session notes found - creating new one");
467
622
  activeNotePath = createSessionNote(notesDir, String(safeProjectName));
468
- console.error(`Created: ${basename3(activeNotePath)}`);
623
+ console.error(`Created: ${basename4(activeNotePath)}`);
469
624
  } else {
470
625
  activeNotePath = currentNotePath;
471
626
  console.error(`
472
- Using existing session note: ${basename3(activeNotePath)}`);
627
+ Using existing session note: ${basename4(activeNotePath)}`);
473
628
  try {
474
- const content = readFileSync4(activeNotePath, "utf-8");
629
+ const content = readFileSync5(activeNotePath, "utf-8");
475
630
  const lines = content.split("\n").slice(0, 12);
476
631
  console.error("--- Current Note Preview ---");
477
632
  for (const line of lines) {
@@ -482,9 +637,9 @@ Using existing session note: ${basename3(activeNotePath)}`);
482
637
  }
483
638
  }
484
639
  }
485
- if (existsSync5(todoPath)) {
640
+ if (existsSync6(todoPath)) {
486
641
  try {
487
- const todoContent = readFileSync4(todoPath, "utf-8");
642
+ const todoContent = readFileSync5(todoPath, "utf-8");
488
643
  const todoLines = todoContent.split("\n").filter((l) => l.includes("[ ]")).slice(0, 5);
489
644
  if (todoLines.length > 0) {
490
645
  console.error("\nOpen TODOs:");
@@ -583,7 +738,7 @@ Working Directory: ${cwd}
583
738
  ${notesDir ? `Notes Directory: ${notesDir}${routedPath ? " (routed via pai route)" : ""}` : "Notes: disabled (no local Notes/ directory)"}
584
739
  ${hasTodo ? `TODO: ${todoPath}` : "TODO: not found"}
585
740
  ${claudeMdPaths.length > 0 ? `CLAUDE.md: ${claudeMdPaths.join(", ")}` : "No CLAUDE.md found"}
586
- ${activeNotePath ? `Active Note: ${basename3(activeNotePath)}` : ""}
741
+ ${activeNotePath ? `Active Note: ${basename4(activeNotePath)}` : ""}
587
742
  ${routedPath ? `
588
743
  Note Routing: ACTIVE (pai route is set - notes go to Obsidian vault)` : ""}
589
744
  ${paiProjectBlock ? `
@@ -611,6 +766,38 @@ THE ABOVE INSTRUCTIONS ARE MANDATORY. Follow them exactly.
611
766
  console.log(claudeMdReminder);
612
767
  console.error(`Injected CLAUDE.md content from: ${path}`);
613
768
  }
769
+ try {
770
+ let wakeupRootPath;
771
+ try {
772
+ const { execFileSync: efs } = await import("child_process");
773
+ const raw2 = efs(paiBin, ["project", "detect", "--json", cwd], {
774
+ encoding: "utf-8",
775
+ env: process.env
776
+ }).trim();
777
+ if (raw2) {
778
+ const det = JSON.parse(raw2);
779
+ if (det.root_path) wakeupRootPath = det.root_path;
780
+ }
781
+ } catch {
782
+ wakeupRootPath = cwd;
783
+ }
784
+ const wakeupBlock = buildWakeupContext(wakeupRootPath);
785
+ if (wakeupBlock) {
786
+ const wakeupReminder = `
787
+ <system-reminder>
788
+ WAKEUP CONTEXT
789
+
790
+ ${wakeupBlock}
791
+ </system-reminder>
792
+ `;
793
+ console.log(wakeupReminder);
794
+ console.error("Injected wake-up context (L0+L1)");
795
+ } else {
796
+ console.error("No wake-up context to inject (no identity file or session notes)");
797
+ }
798
+ } catch (wakeupError) {
799
+ console.error("Wake-up context injection failed:", wakeupError);
800
+ }
614
801
  console.error("\nProject context setup complete\n");
615
802
  process.exit(0);
616
803
  }