@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.
Files changed (51) hide show
  1. package/README.md +20 -2
  2. package/dist/cli/index.mjs +479 -5
  3. package/dist/cli/index.mjs.map +1 -1
  4. package/dist/daemon/index.mjs +2 -2
  5. package/dist/{daemon-D9evGlgR.mjs → daemon-2ND5WO2j.mjs} +3 -3
  6. package/dist/{daemon-D9evGlgR.mjs.map → daemon-2ND5WO2j.mjs.map} +1 -1
  7. package/dist/{db-4lSqLFb8.mjs → db-BtuN768f.mjs} +9 -2
  8. package/dist/db-BtuN768f.mjs.map +1 -0
  9. package/dist/hooks/capture-all-events.mjs +19 -4
  10. package/dist/hooks/capture-all-events.mjs.map +4 -4
  11. package/dist/hooks/cleanup-session-files.mjs.map +2 -2
  12. package/dist/hooks/context-compression-hook.mjs +88 -18
  13. package/dist/hooks/context-compression-hook.mjs.map +3 -3
  14. package/dist/hooks/initialize-session.mjs +14 -8
  15. package/dist/hooks/initialize-session.mjs.map +3 -3
  16. package/dist/hooks/load-core-context.mjs +18 -2
  17. package/dist/hooks/load-core-context.mjs.map +4 -4
  18. package/dist/hooks/load-project-context.mjs +14 -8
  19. package/dist/hooks/load-project-context.mjs.map +3 -3
  20. package/dist/hooks/stop-hook.mjs +105 -8
  21. package/dist/hooks/stop-hook.mjs.map +3 -3
  22. package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
  23. package/dist/index.d.mts +2 -2
  24. package/dist/index.d.mts.map +1 -1
  25. package/dist/index.mjs +1 -1
  26. package/dist/mcp/index.mjs +1 -1
  27. package/dist/{vault-indexer-DXWs9pDn.mjs → vault-indexer-k-kUlaZ-.mjs} +41 -7
  28. package/dist/vault-indexer-k-kUlaZ-.mjs.map +1 -0
  29. package/package.json +1 -1
  30. package/src/hooks/ts/capture-all-events.ts +6 -0
  31. package/src/hooks/ts/lib/project-utils.ts +24 -5
  32. package/src/hooks/ts/pre-compact/context-compression-hook.ts +110 -16
  33. package/src/hooks/ts/session-start/initialize-session.ts +7 -1
  34. package/src/hooks/ts/session-start/load-core-context.ts +7 -0
  35. package/src/hooks/ts/session-start/load-project-context.ts +8 -1
  36. package/src/hooks/ts/stop/stop-hook.ts +28 -0
  37. package/templates/claude-md.template.md +7 -74
  38. package/templates/skills/CORE/Aesthetic.md +333 -0
  39. package/templates/skills/CORE/CONSTITUTION.md +1502 -0
  40. package/templates/skills/CORE/HistorySystem.md +427 -0
  41. package/templates/skills/CORE/HookSystem.md +1082 -0
  42. package/templates/skills/CORE/Prompting.md +509 -0
  43. package/templates/skills/CORE/ProsodyAgentTemplate.md +53 -0
  44. package/templates/skills/CORE/ProsodyGuide.md +416 -0
  45. package/templates/skills/CORE/SKILL.md +741 -0
  46. package/templates/skills/CORE/SkillSystem.md +213 -0
  47. package/templates/skills/CORE/TerminalTabs.md +119 -0
  48. package/templates/skills/CORE/VOICE.md +106 -0
  49. package/templates/skills/user/.gitkeep +0 -0
  50. package/dist/db-4lSqLFb8.mjs.map +0 -1
  51. 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 WhatsApp (Whazaa) is configured as an enabled MCP server.
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
- * No PAI dependency works for any Claude Code user with whazaa installed.
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
- * This hook now mirrors "pause session" behavior: the session note gets a
11
- * descriptive name, rich content, and TODO.md gets a continuation prompt.
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
- const state = formatSessionState(data, hookInput.cwd);
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 (data.workItems.length > 0) {
352
- addWorkToSessionNote(notePath, data.workItems, `Pre-Compact (~${tokenDisplay} tokens)`);
353
- console.error(`Added ${data.workItems.length} work item(s) to session note`);
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 (instead of "New Session")
357
- const title = deriveTitle(data);
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 (state && hookInput.session_id) {
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
- state,
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
- ## MANDATORY: End Session Command
638
+ ## Session Lifecycle
639
639
 
640
- **When user says "end session", execute this COMPLETE procedure automatically.**
640
+ **Session commands (pause, end, continue, go, cpp) are defined in the CORE skill.**
641
641
 
642
- ### The End Session Checklist
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
- ```bash
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. **End session = full cleanup** - notes, TODO, commit, push everywhere
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.