@tekmidian/pai 0.5.5 → 0.5.7
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/README.md +20 -2
- package/dist/cli/index.mjs +479 -5
- package/dist/cli/index.mjs.map +1 -1
- package/dist/daemon/index.mjs +2 -2
- package/dist/{daemon-D9evGlgR.mjs → daemon-2ND5WO2j.mjs} +3 -3
- package/dist/{daemon-D9evGlgR.mjs.map → daemon-2ND5WO2j.mjs.map} +1 -1
- package/dist/{db-4lSqLFb8.mjs → db-BtuN768f.mjs} +9 -2
- package/dist/db-BtuN768f.mjs.map +1 -0
- package/dist/hooks/capture-all-events.mjs +19 -4
- package/dist/hooks/capture-all-events.mjs.map +4 -4
- package/dist/hooks/cleanup-session-files.mjs.map +2 -2
- package/dist/hooks/context-compression-hook.mjs +88 -18
- package/dist/hooks/context-compression-hook.mjs.map +3 -3
- package/dist/hooks/initialize-session.mjs +14 -8
- package/dist/hooks/initialize-session.mjs.map +3 -3
- package/dist/hooks/load-core-context.mjs +18 -2
- package/dist/hooks/load-core-context.mjs.map +4 -4
- package/dist/hooks/load-project-context.mjs +14 -8
- package/dist/hooks/load-project-context.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +105 -8
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.mjs +1 -1
- package/dist/{vault-indexer-DXWs9pDn.mjs → vault-indexer-k-kUlaZ-.mjs} +41 -7
- package/dist/vault-indexer-k-kUlaZ-.mjs.map +1 -0
- package/package.json +1 -1
- package/src/hooks/ts/capture-all-events.ts +6 -0
- package/src/hooks/ts/lib/project-utils.ts +24 -5
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +110 -16
- package/src/hooks/ts/session-start/initialize-session.ts +7 -1
- package/src/hooks/ts/session-start/load-core-context.ts +7 -0
- package/src/hooks/ts/session-start/load-project-context.ts +8 -1
- package/src/hooks/ts/stop/stop-hook.ts +28 -0
- package/templates/claude-md.template.md +7 -74
- package/templates/skills/CORE/Aesthetic.md +333 -0
- package/templates/skills/CORE/CONSTITUTION.md +1502 -0
- package/templates/skills/CORE/HistorySystem.md +427 -0
- package/templates/skills/CORE/HookSystem.md +1082 -0
- package/templates/skills/CORE/Prompting.md +509 -0
- package/templates/skills/CORE/ProsodyAgentTemplate.md +53 -0
- package/templates/skills/CORE/ProsodyGuide.md +416 -0
- package/templates/skills/CORE/SKILL.md +741 -0
- package/templates/skills/CORE/SkillSystem.md +213 -0
- package/templates/skills/CORE/TerminalTabs.md +119 -0
- package/templates/skills/CORE/VOICE.md +106 -0
- package/templates/skills/user/.gitkeep +0 -0
- package/dist/db-4lSqLFb8.mjs.map +0 -1
- package/dist/vault-indexer-DXWs9pDn.mjs.map +0 -1
|
@@ -10,10 +10,29 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, renameSync } from 'fs';
|
|
12
12
|
import { join, basename } from 'path';
|
|
13
|
+
import { homedir } from 'os';
|
|
13
14
|
|
|
14
15
|
// Import from pai-paths which handles .env loading and path resolution
|
|
15
16
|
import { PAI_DIR } from './pai-paths.js';
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Directories known to be automated health-check / probe sessions.
|
|
20
|
+
* Hooks should exit early for these to avoid registry clutter and wasted work.
|
|
21
|
+
*/
|
|
22
|
+
const PROBE_CWD_PATTERNS = [
|
|
23
|
+
'/CodexBar/ClaudeProbe',
|
|
24
|
+
'/ClaudeProbe',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if the current working directory belongs to a probe/health-check session.
|
|
29
|
+
* Returns true if hooks should skip this session entirely.
|
|
30
|
+
*/
|
|
31
|
+
export function isProbeSession(cwd?: string): boolean {
|
|
32
|
+
const dir = cwd || process.cwd();
|
|
33
|
+
return PROBE_CWD_PATTERNS.some(pattern => dir.includes(pattern));
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
// Re-export PAI_DIR for consumers
|
|
18
37
|
export { PAI_DIR };
|
|
19
38
|
export const PROJECTS_DIR = join(PAI_DIR, 'projects');
|
|
@@ -96,20 +115,20 @@ export function getSessionsDirFromProjectDir(projectDir: string): string {
|
|
|
96
115
|
}
|
|
97
116
|
|
|
98
117
|
/**
|
|
99
|
-
* Check if
|
|
118
|
+
* Check if a messaging MCP server (AIBroker, Whazaa, or Telex) is configured.
|
|
100
119
|
*
|
|
101
120
|
* Uses standard Claude Code config at ~/.claude/settings.json.
|
|
102
|
-
*
|
|
121
|
+
* When any messaging server is active, the AI handles notifications via MCP
|
|
122
|
+
* and ntfy is skipped to avoid duplicates.
|
|
103
123
|
*/
|
|
104
124
|
export function isWhatsAppEnabled(): boolean {
|
|
105
125
|
try {
|
|
106
|
-
const { homedir } = require('os');
|
|
107
126
|
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
108
127
|
if (!existsSync(settingsPath)) return false;
|
|
109
128
|
|
|
110
129
|
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
111
130
|
const enabled: string[] = settings.enabledMcpjsonServers || [];
|
|
112
|
-
return enabled.includes('whazaa');
|
|
131
|
+
return enabled.includes('aibroker') || enabled.includes('whazaa') || enabled.includes('telex');
|
|
113
132
|
} catch {
|
|
114
133
|
return false;
|
|
115
134
|
}
|
|
@@ -967,7 +986,7 @@ export function updateTodoContinue(
|
|
|
967
986
|
const now = new Date().toISOString();
|
|
968
987
|
const stateLines = state
|
|
969
988
|
? state.split('\n').filter(l => l.trim()).slice(0, 10).map(l => `> ${l}`).join('\n')
|
|
970
|
-
: `> Check the latest session note for details.`;
|
|
989
|
+
: `> Working directory: ${cwd}. Check the latest session note for details.`;
|
|
971
990
|
|
|
972
991
|
const continueSection = `## Continue
|
|
973
992
|
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
* 2. Update TODO.md with a proper ## Continue section for the next session
|
|
8
8
|
* 3. Save session state to temp file for post-compact injection via SessionStart(compact)
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Uses a CUMULATIVE state file (.compact-state.json) that persists across
|
|
11
|
+
* compactions. This ensures that even after multiple compactions (where the
|
|
12
|
+
* transcript becomes thin), we still have rich data for titles, summaries,
|
|
13
|
+
* and work items from earlier in the session.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
import { readFileSync, writeFileSync } from 'fs';
|
|
16
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
15
17
|
import { basename, dirname, join } from 'path';
|
|
16
18
|
import { tmpdir } from 'os';
|
|
17
19
|
import {
|
|
@@ -24,6 +26,7 @@ import {
|
|
|
24
26
|
renameSessionNote,
|
|
25
27
|
updateTodoContinue,
|
|
26
28
|
calculateSessionTokens,
|
|
29
|
+
isProbeSession,
|
|
27
30
|
WorkItem,
|
|
28
31
|
} from '../lib/project-utils';
|
|
29
32
|
|
|
@@ -276,11 +279,75 @@ function deriveTitle(data: TranscriptData): string {
|
|
|
276
279
|
.substring(0, 60);
|
|
277
280
|
}
|
|
278
281
|
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Cumulative state — persists across compactions in .compact-state.json
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
const CUMULATIVE_STATE_FILE = '.compact-state.json';
|
|
287
|
+
|
|
288
|
+
function loadCumulativeState(notesDir: string): TranscriptData | null {
|
|
289
|
+
try {
|
|
290
|
+
const filePath = join(notesDir, CUMULATIVE_STATE_FILE);
|
|
291
|
+
if (!existsSync(filePath)) return null;
|
|
292
|
+
const raw = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
293
|
+
return {
|
|
294
|
+
userMessages: raw.userMessages || [],
|
|
295
|
+
summaries: raw.summaries || [],
|
|
296
|
+
captures: raw.captures || [],
|
|
297
|
+
lastCompleted: raw.lastCompleted || '',
|
|
298
|
+
filesModified: raw.filesModified || [],
|
|
299
|
+
workItems: raw.workItems || [],
|
|
300
|
+
};
|
|
301
|
+
} catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function mergeTranscriptData(accumulated: TranscriptData | null, current: TranscriptData): TranscriptData {
|
|
307
|
+
if (!accumulated) return current;
|
|
308
|
+
|
|
309
|
+
const mergeArrays = (a: string[], b: string[]): string[] => {
|
|
310
|
+
const seen = new Set(a);
|
|
311
|
+
return [...a, ...b.filter(x => !seen.has(x))];
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const seenTitles = new Set(accumulated.workItems.map(w => w.title));
|
|
315
|
+
const newWorkItems = current.workItems.filter(w => !seenTitles.has(w.title));
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
userMessages: mergeArrays(accumulated.userMessages, current.userMessages).slice(-20),
|
|
319
|
+
summaries: mergeArrays(accumulated.summaries, current.summaries),
|
|
320
|
+
captures: mergeArrays(accumulated.captures, current.captures),
|
|
321
|
+
lastCompleted: current.lastCompleted || accumulated.lastCompleted,
|
|
322
|
+
filesModified: mergeArrays(accumulated.filesModified, current.filesModified),
|
|
323
|
+
workItems: [...accumulated.workItems, ...newWorkItems],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function saveCumulativeState(notesDir: string, data: TranscriptData, notePath: string | null): void {
|
|
328
|
+
try {
|
|
329
|
+
const filePath = join(notesDir, CUMULATIVE_STATE_FILE);
|
|
330
|
+
writeFileSync(filePath, JSON.stringify({
|
|
331
|
+
...data,
|
|
332
|
+
notePath,
|
|
333
|
+
lastUpdated: new Date().toISOString(),
|
|
334
|
+
}, null, 2));
|
|
335
|
+
console.error(`Cumulative state saved (${data.workItems.length} work items, ${data.filesModified.length} files)`);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
console.error(`Failed to save cumulative state: ${err}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
279
341
|
// ---------------------------------------------------------------------------
|
|
280
342
|
// Main
|
|
281
343
|
// ---------------------------------------------------------------------------
|
|
282
344
|
|
|
283
345
|
async function main() {
|
|
346
|
+
// Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)
|
|
347
|
+
if (isProbeSession()) {
|
|
348
|
+
process.exit(0);
|
|
349
|
+
}
|
|
350
|
+
|
|
284
351
|
let hookInput: HookInput | null = null;
|
|
285
352
|
|
|
286
353
|
try {
|
|
@@ -311,10 +378,28 @@ async function main() {
|
|
|
311
378
|
: String(tokenCount);
|
|
312
379
|
|
|
313
380
|
// -----------------------------------------------------------------
|
|
314
|
-
// Single-pass transcript parsing
|
|
381
|
+
// Single-pass transcript parsing + cumulative state merge
|
|
315
382
|
// -----------------------------------------------------------------
|
|
316
383
|
const data = parseTranscript(hookInput.transcript_path);
|
|
317
|
-
|
|
384
|
+
|
|
385
|
+
// Find notes directory early — needed for cumulative state
|
|
386
|
+
let notesInfo: { path: string; isLocal: boolean };
|
|
387
|
+
try {
|
|
388
|
+
notesInfo = hookInput.cwd
|
|
389
|
+
? findNotesDir(hookInput.cwd)
|
|
390
|
+
: { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };
|
|
391
|
+
} catch {
|
|
392
|
+
notesInfo = { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Load accumulated state from previous compactions and merge
|
|
396
|
+
const accumulated = loadCumulativeState(notesInfo.path);
|
|
397
|
+
const merged = mergeTranscriptData(accumulated, data);
|
|
398
|
+
const state = formatSessionState(merged, hookInput.cwd);
|
|
399
|
+
|
|
400
|
+
if (accumulated) {
|
|
401
|
+
console.error(`Loaded cumulative state: ${accumulated.workItems.length} work items, ${accumulated.filesModified.length} files from previous compaction(s)`);
|
|
402
|
+
}
|
|
318
403
|
|
|
319
404
|
// -----------------------------------------------------------------
|
|
320
405
|
// Persist session state to numbered session note (like "pause session")
|
|
@@ -322,9 +407,6 @@ async function main() {
|
|
|
322
407
|
let notePath: string | null = null;
|
|
323
408
|
|
|
324
409
|
try {
|
|
325
|
-
const notesInfo = hookInput.cwd
|
|
326
|
-
? findNotesDir(hookInput.cwd)
|
|
327
|
-
: { path: join(dirname(hookInput.transcript_path), 'Notes'), isLocal: false };
|
|
328
410
|
notePath = getCurrentNotePath(notesInfo.path);
|
|
329
411
|
|
|
330
412
|
// If no note found, or the latest note is completed, create a new one
|
|
@@ -347,14 +429,14 @@ async function main() {
|
|
|
347
429
|
: `Context compression triggered at ~${tokenDisplay} tokens with ${stats.messageCount} messages.`;
|
|
348
430
|
appendCheckpoint(notePath, checkpointBody);
|
|
349
431
|
|
|
350
|
-
// 2. Write work items to "Work Done" section
|
|
351
|
-
if (
|
|
352
|
-
addWorkToSessionNote(notePath,
|
|
353
|
-
console.error(`Added ${
|
|
432
|
+
// 2. Write work items to "Work Done" section (uses merged cumulative data)
|
|
433
|
+
if (merged.workItems.length > 0) {
|
|
434
|
+
addWorkToSessionNote(notePath, merged.workItems, `Pre-Compact (~${tokenDisplay} tokens)`);
|
|
435
|
+
console.error(`Added ${merged.workItems.length} work item(s) to session note`);
|
|
354
436
|
}
|
|
355
437
|
|
|
356
|
-
// 3. Rename session note with a meaningful title (
|
|
357
|
-
const title = deriveTitle(
|
|
438
|
+
// 3. Rename session note with a meaningful title (uses merged data for richer titles)
|
|
439
|
+
const title = deriveTitle(merged);
|
|
358
440
|
if (title) {
|
|
359
441
|
const newPath = renameSessionNote(notePath, title);
|
|
360
442
|
if (newPath !== notePath) {
|
|
@@ -377,6 +459,9 @@ async function main() {
|
|
|
377
459
|
console.error(`Could not save checkpoint: ${noteError}`);
|
|
378
460
|
}
|
|
379
461
|
|
|
462
|
+
// Save cumulative state for next compaction
|
|
463
|
+
saveCumulativeState(notesInfo.path, merged, notePath);
|
|
464
|
+
|
|
380
465
|
// -----------------------------------------------------------------
|
|
381
466
|
// Update TODO.md with proper ## Continue section (like "pause session")
|
|
382
467
|
// -----------------------------------------------------------------
|
|
@@ -397,13 +482,22 @@ async function main() {
|
|
|
397
482
|
// Instead, we write the injection payload to a temp file keyed by
|
|
398
483
|
// session_id. The SessionStart(compact) hook reads it and outputs
|
|
399
484
|
// to stdout, which IS injected into the post-compaction context.
|
|
485
|
+
//
|
|
486
|
+
// Always fires (even with thin state) — includes note path so the AI
|
|
487
|
+
// can enrich the session note post-compaction using its own context.
|
|
400
488
|
// -----------------------------------------------------------------------
|
|
401
|
-
if (
|
|
489
|
+
if (hookInput.session_id) {
|
|
490
|
+
const stateText = state || `Working directory: ${hookInput.cwd || 'unknown'}`;
|
|
491
|
+
const noteInfo = notePath
|
|
492
|
+
? `\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.`
|
|
493
|
+
: '';
|
|
494
|
+
|
|
402
495
|
const injection = [
|
|
403
496
|
'<system-reminder>',
|
|
404
497
|
`SESSION STATE RECOVERED AFTER COMPACTION (${compactType}, ~${tokenDisplay} tokens)`,
|
|
405
498
|
'',
|
|
406
|
-
|
|
499
|
+
stateText,
|
|
500
|
+
noteInfo,
|
|
407
501
|
'',
|
|
408
502
|
'IMPORTANT: This session state was captured before context compaction.',
|
|
409
503
|
'Use it to maintain continuity. Continue the conversation from where',
|
|
@@ -24,7 +24,7 @@ import { existsSync, statSync, readFileSync, writeFileSync } from 'fs';
|
|
|
24
24
|
import { join } from 'path';
|
|
25
25
|
import { tmpdir } from 'os';
|
|
26
26
|
import { PAI_DIR } from '../lib/pai-paths';
|
|
27
|
-
import { sendNtfyNotification, isWhatsAppEnabled } from '../lib/project-utils';
|
|
27
|
+
import { sendNtfyNotification, isWhatsAppEnabled, isProbeSession } from '../lib/project-utils';
|
|
28
28
|
|
|
29
29
|
// Debounce duration in milliseconds (prevents duplicate SessionStart events)
|
|
30
30
|
const DEBOUNCE_MS = 2000;
|
|
@@ -111,6 +111,12 @@ async function main() {
|
|
|
111
111
|
process.exit(0);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)
|
|
115
|
+
if (isProbeSession()) {
|
|
116
|
+
console.error('Probe session detected - skipping session initialization');
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
114
120
|
// Check debounce to prevent duplicate notifications
|
|
115
121
|
// (IDE extension can fire multiple SessionStart events)
|
|
116
122
|
if (shouldDebounce()) {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { readFileSync, existsSync } from 'fs';
|
|
29
29
|
import { join } from 'path';
|
|
30
30
|
import { PAI_DIR, SKILLS_DIR } from '../lib/pai-paths';
|
|
31
|
+
import { isProbeSession } from '../lib/project-utils';
|
|
31
32
|
|
|
32
33
|
async function main() {
|
|
33
34
|
try {
|
|
@@ -42,6 +43,12 @@ async function main() {
|
|
|
42
43
|
process.exit(0);
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)
|
|
47
|
+
if (isProbeSession()) {
|
|
48
|
+
console.error('Probe session detected - skipping CORE context loading');
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
// Get CORE skill path using PAI paths library
|
|
46
53
|
const coreSkillPath = join(SKILLS_DIR, 'CORE/SKILL.md');
|
|
47
54
|
|
|
@@ -27,7 +27,8 @@ import {
|
|
|
27
27
|
createSessionNote,
|
|
28
28
|
findTodoPath,
|
|
29
29
|
findAllClaudeMdPaths,
|
|
30
|
-
sendNtfyNotification
|
|
30
|
+
sendNtfyNotification,
|
|
31
|
+
isProbeSession
|
|
31
32
|
} from '../lib/project-utils';
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -80,6 +81,12 @@ interface HookInput {
|
|
|
80
81
|
async function main() {
|
|
81
82
|
console.error('\nload-project-context.ts starting...');
|
|
82
83
|
|
|
84
|
+
// Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)
|
|
85
|
+
if (isProbeSession()) {
|
|
86
|
+
console.error('Probe session detected - skipping project context loading');
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
83
90
|
// Read hook input from stdin
|
|
84
91
|
let hookInput: HookInput | null = null;
|
|
85
92
|
try {
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
moveSessionFilesToSessionsDir,
|
|
10
10
|
addWorkToSessionNote,
|
|
11
11
|
findNotesDir,
|
|
12
|
+
isProbeSession,
|
|
13
|
+
updateTodoContinue,
|
|
12
14
|
WorkItem
|
|
13
15
|
} from '../lib/project-utils';
|
|
14
16
|
|
|
@@ -207,6 +209,11 @@ function contentToText(content: any): string {
|
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
async function main() {
|
|
212
|
+
// Skip probe/health-check sessions (e.g. CodexBar ClaudeProbe)
|
|
213
|
+
if (isProbeSession()) {
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
210
217
|
const timestamp = new Date().toISOString();
|
|
211
218
|
console.error(`\nSTOP-HOOK TRIGGERED AT ${timestamp}`);
|
|
212
219
|
|
|
@@ -385,6 +392,27 @@ async function main() {
|
|
|
385
392
|
const summary = message || 'Session completed.';
|
|
386
393
|
finalizeSessionNote(currentNotePath, summary);
|
|
387
394
|
console.error(`Session note finalized: ${basename(currentNotePath)}`);
|
|
395
|
+
|
|
396
|
+
// Update TODO.md ## Continue section so next session has context
|
|
397
|
+
try {
|
|
398
|
+
const stateLines: string[] = [];
|
|
399
|
+
stateLines.push(`Working directory: ${cwd}`);
|
|
400
|
+
if (workItems.length > 0) {
|
|
401
|
+
stateLines.push('');
|
|
402
|
+
stateLines.push('Work completed:');
|
|
403
|
+
for (const item of workItems.slice(0, 5)) {
|
|
404
|
+
stateLines.push(`- ${item.title}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (message) {
|
|
408
|
+
stateLines.push('');
|
|
409
|
+
stateLines.push(`Last completed: ${message}`);
|
|
410
|
+
}
|
|
411
|
+
const state = stateLines.join('\n');
|
|
412
|
+
updateTodoContinue(cwd, basename(currentNotePath), state, 'session-end');
|
|
413
|
+
} catch (todoError) {
|
|
414
|
+
console.error(`Could not update TODO.md: ${todoError}`);
|
|
415
|
+
}
|
|
388
416
|
}
|
|
389
417
|
} catch (noteError) {
|
|
390
418
|
console.error(`Could not finalize session note: ${noteError}`);
|
|
@@ -635,82 +635,13 @@ IMPORTANT:
|
|
|
635
635
|
|
|
636
636
|
---
|
|
637
637
|
|
|
638
|
-
##
|
|
638
|
+
## Session Lifecycle
|
|
639
639
|
|
|
640
|
-
**
|
|
640
|
+
**Session commands (pause, end, continue, go, cpp) are defined in the CORE skill.**
|
|
641
641
|
|
|
642
|
-
|
|
642
|
+
The CORE skill auto-loads at session start and contains the full session lifecycle: pause checkpoints, end session with commit/push, continuation protocol, and session note naming rules.
|
|
643
643
|
|
|
644
|
-
|
|
645
|
-
# 1. Update session notes with summary of work done
|
|
646
|
-
# 2. Update TODO.md (mark completed, add discovered tasks)
|
|
647
|
-
# 3. Rename session note if it has placeholder name
|
|
648
|
-
# 4. Commit and push in Obsidian/Notes project
|
|
649
|
-
# 5. Commit and push in related code folder (if any)
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
### Automatic Execution
|
|
653
|
-
|
|
654
|
-
When user says "end session" (or "end", "done", "finish session"):
|
|
655
|
-
|
|
656
|
-
**Step 1: Summarize and Update Session Note**
|
|
657
|
-
- Write summary of what was accomplished
|
|
658
|
-
- List any blockers or open questions
|
|
659
|
-
- Ensure session note has meaningful name (NOT "New Session" or project name)
|
|
660
|
-
|
|
661
|
-
**Step 2: Update TODO.md**
|
|
662
|
-
- Mark completed tasks with `[x]`
|
|
663
|
-
- Keep in-progress tasks with `[ ]`
|
|
664
|
-
- Add any newly discovered tasks
|
|
665
|
-
|
|
666
|
-
**Step 3: Commit and Push Obsidian Project**
|
|
667
|
-
```bash
|
|
668
|
-
cd [OBSIDIAN_PROJECT_DIR]
|
|
669
|
-
git add .
|
|
670
|
-
git status # Check what's being committed
|
|
671
|
-
git commit -m "docs: Session NNNN complete - [brief description]"
|
|
672
|
-
git push
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
**Step 4: Commit and Push Code Repository (if applicable)**
|
|
676
|
-
```bash
|
|
677
|
-
cd [CODE_PROJECT_DIR]
|
|
678
|
-
git add .
|
|
679
|
-
git status
|
|
680
|
-
git commit -m "feat/fix/refactor: [description of changes]"
|
|
681
|
-
git push
|
|
682
|
-
```
|
|
683
|
-
|
|
684
|
-
**Step 5: Confirm Completion**
|
|
685
|
-
Report back:
|
|
686
|
-
- Session note filename (should be descriptive)
|
|
687
|
-
- Commits made (both repos if applicable)
|
|
688
|
-
- Any uncommitted changes that were skipped
|
|
689
|
-
- Next session starting point
|
|
690
|
-
|
|
691
|
-
### Project-Code Directory Mapping
|
|
692
|
-
|
|
693
|
-
Add your project mappings to `~/.config/pai/project-mappings.json`:
|
|
694
|
-
|
|
695
|
-
```json
|
|
696
|
-
{
|
|
697
|
-
"mappings": [
|
|
698
|
-
{ "obsidian": "My Project", "code_dir": "~/path/to/code" }
|
|
699
|
-
]
|
|
700
|
-
}
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
PAI reads this file during `end session` to know which code repositories to commit alongside their Obsidian project notes. Run `pai setup` to configure your mappings interactively.
|
|
704
|
-
|
|
705
|
-
For personal preferences (notification channels, voice settings, agent defaults), see `~/.config/pai/agent-prefs.md`.
|
|
706
|
-
|
|
707
|
-
### Important Rules
|
|
708
|
-
|
|
709
|
-
- **NEVER skip the code repo commit** if code changes were made
|
|
710
|
-
- **ALWAYS rename placeholder session notes** before committing
|
|
711
|
-
- **ALWAYS use clean commit messages** (no AI signatures)
|
|
712
|
-
- **CHECK for uncommitted changes** in both locations
|
|
713
|
-
- **REPORT what was pushed** so user knows the state
|
|
644
|
+
**Key commands:** "pause session", "end session", "go"/"continue", "cpp" (commit-push-publish).
|
|
714
645
|
|
|
715
646
|
---
|
|
716
647
|
|
|
@@ -722,7 +653,7 @@ For personal preferences (notification channels, voice settings, agent defaults)
|
|
|
722
653
|
4. **Always spotcheck** - verification is mandatory
|
|
723
654
|
5. **Never search home** - use specific subdirectories
|
|
724
655
|
6. **Autonomous swarm mode** - spawn orchestrator, let agents do the work, no questions
|
|
725
|
-
7. **
|
|
656
|
+
7. **Session lifecycle in CORE skill** - pause, end, continue, cpp commands
|
|
726
657
|
8. **Plan mode for 3+ steps** - write specs, evaluate approaches, execute with verification
|
|
727
658
|
9. **Verify before done** - prove it works, don't just claim it
|
|
728
659
|
10. **Learn from corrections** - update tasks/lessons.md after every user correction
|
|
@@ -730,4 +661,6 @@ For personal preferences (notification channels, voice settings, agent defaults)
|
|
|
730
661
|
12. **Fix bugs autonomously** - investigate, resolve, verify without hand-holding
|
|
731
662
|
13. **Task management** - plan to tasks/todo.md first, track progress, document results
|
|
732
663
|
|
|
664
|
+
**CLAUDE.md + CORE skill = complete PAI configuration.** CLAUDE.md covers agent architecture and engineering standards. CORE skill covers identity, session lifecycle, notifications, and compaction resilience.
|
|
665
|
+
|
|
733
666
|
This is constitutional. Violations waste time, money, and context.
|