@tekmidian/pai 0.3.1 → 0.4.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.
- package/dist/cli/index.mjs +352 -33
- package/dist/cli/index.mjs.map +1 -1
- package/dist/hooks/capture-all-events.mjs +238 -0
- package/dist/hooks/capture-all-events.mjs.map +7 -0
- package/dist/hooks/capture-session-summary.mjs +198 -0
- package/dist/hooks/capture-session-summary.mjs.map +7 -0
- package/dist/hooks/capture-tool-output.mjs +105 -0
- package/dist/hooks/capture-tool-output.mjs.map +7 -0
- package/dist/hooks/cleanup-session-files.mjs +129 -0
- package/dist/hooks/cleanup-session-files.mjs.map +7 -0
- package/dist/hooks/context-compression-hook.mjs +283 -0
- package/dist/hooks/context-compression-hook.mjs.map +7 -0
- package/dist/hooks/initialize-session.mjs +206 -0
- package/dist/hooks/initialize-session.mjs.map +7 -0
- package/dist/hooks/load-core-context.mjs +110 -0
- package/dist/hooks/load-core-context.mjs.map +7 -0
- package/dist/hooks/load-project-context.mjs +548 -0
- package/dist/hooks/load-project-context.mjs.map +7 -0
- package/dist/hooks/security-validator.mjs +159 -0
- package/dist/hooks/security-validator.mjs.map +7 -0
- package/dist/hooks/stop-hook.mjs +625 -0
- package/dist/hooks/stop-hook.mjs.map +7 -0
- package/dist/hooks/subagent-stop-hook.mjs +152 -0
- package/dist/hooks/subagent-stop-hook.mjs.map +7 -0
- package/dist/hooks/sync-todo-to-md.mjs +322 -0
- package/dist/hooks/sync-todo-to-md.mjs.map +7 -0
- package/dist/hooks/update-tab-on-action.mjs +90 -0
- package/dist/hooks/update-tab-on-action.mjs.map +7 -0
- package/dist/hooks/update-tab-titles.mjs +55 -0
- package/dist/hooks/update-tab-titles.mjs.map +7 -0
- package/package.json +4 -2
- package/scripts/build-hooks.mjs +51 -0
- package/src/hooks/pre-compact.sh +4 -0
- package/src/hooks/session-stop.sh +4 -0
- package/src/hooks/ts/capture-all-events.ts +179 -0
- package/src/hooks/ts/lib/detect-environment.ts +53 -0
- package/src/hooks/ts/lib/metadata-extraction.ts +144 -0
- package/src/hooks/ts/lib/pai-paths.ts +124 -0
- package/src/hooks/ts/lib/project-utils.ts +914 -0
- package/src/hooks/ts/post-tool-use/capture-tool-output.ts +78 -0
- package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +230 -0
- package/src/hooks/ts/post-tool-use/update-tab-on-action.ts +145 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +155 -0
- package/src/hooks/ts/pre-tool-use/security-validator.ts +258 -0
- package/src/hooks/ts/session-end/capture-session-summary.ts +185 -0
- package/src/hooks/ts/session-start/initialize-session.ts +155 -0
- package/src/hooks/ts/session-start/load-core-context.ts +104 -0
- package/src/hooks/ts/session-start/load-project-context.ts +394 -0
- package/src/hooks/ts/stop/stop-hook.ts +407 -0
- package/src/hooks/ts/subagent-stop/subagent-stop-hook.ts +212 -0
- package/src/hooks/ts/user-prompt/cleanup-session-files.ts +45 -0
- package/src/hooks/ts/user-prompt/update-tab-titles.ts +88 -0
- package/tab-color-command.sh +24 -0
- package/templates/ai-steering-rules.template.md +58 -0
- package/templates/pai-skill.template.md +24 -0
- package/templates/skills/createskill-skill.template.md +78 -0
- package/templates/skills/history-system.template.md +371 -0
- package/templates/skills/hook-system.template.md +913 -0
- package/templates/skills/sessions-skill.template.md +102 -0
- package/templates/skills/skill-system.template.md +214 -0
- package/templates/skills/terminal-tabs.template.md +120 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* load-project-context.ts
|
|
5
|
+
*
|
|
6
|
+
* SessionStart hook that sets up project context:
|
|
7
|
+
* - Checks for CLAUDE.md in various locations (Claude Code handles loading)
|
|
8
|
+
* - Sets up Notes/ directory in ~/.claude/projects/{encoded-path}/
|
|
9
|
+
* - Ensures TODO.md exists
|
|
10
|
+
* - Sends ntfy.sh notification (mandatory)
|
|
11
|
+
* - Displays session continuity info (like session-init.sh)
|
|
12
|
+
*
|
|
13
|
+
* This hook complements Claude Code's native CLAUDE.md loading by:
|
|
14
|
+
* - Setting up the Notes infrastructure
|
|
15
|
+
* - Showing the latest session note for continuity
|
|
16
|
+
* - Sending ntfy.sh notifications
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
20
|
+
import { join, basename, dirname } from 'path';
|
|
21
|
+
import { execSync } from 'child_process';
|
|
22
|
+
import {
|
|
23
|
+
PAI_DIR,
|
|
24
|
+
findNotesDir,
|
|
25
|
+
getProjectDir,
|
|
26
|
+
getCurrentNotePath,
|
|
27
|
+
createSessionNote,
|
|
28
|
+
findTodoPath,
|
|
29
|
+
findAllClaudeMdPaths,
|
|
30
|
+
sendNtfyNotification
|
|
31
|
+
} from '../lib/project-utils';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Find the pai CLI binary path dynamically.
|
|
35
|
+
* Tries `which pai` first, then common fallback locations.
|
|
36
|
+
*/
|
|
37
|
+
function findPaiBinary(): string {
|
|
38
|
+
try {
|
|
39
|
+
return execSync('which pai', { encoding: 'utf-8' }).trim();
|
|
40
|
+
} catch {
|
|
41
|
+
// Fallback locations in order of preference
|
|
42
|
+
const fallbacks = [
|
|
43
|
+
'/usr/local/bin/pai',
|
|
44
|
+
'/opt/homebrew/bin/pai',
|
|
45
|
+
`${process.env.HOME}/.local/bin/pai`,
|
|
46
|
+
];
|
|
47
|
+
for (const p of fallbacks) {
|
|
48
|
+
if (existsSync(p)) return p;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return 'pai'; // Last resort: rely on PATH at runtime
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Check session-routing.json for an active route.
|
|
56
|
+
* Returns the routed Notes path if set, or null to use default behavior.
|
|
57
|
+
*/
|
|
58
|
+
function getRoutedNotesPath(): string | null {
|
|
59
|
+
const routingFile = join(PAI_DIR, 'session-routing.json');
|
|
60
|
+
if (!existsSync(routingFile)) return null;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const routing = JSON.parse(readFileSync(routingFile, 'utf-8'));
|
|
64
|
+
const active = routing?.active_session;
|
|
65
|
+
if (active?.notes_path) {
|
|
66
|
+
return active.notes_path;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Ignore parse errors
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface HookInput {
|
|
75
|
+
session_id: string;
|
|
76
|
+
cwd: string;
|
|
77
|
+
hook_event_name: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
console.error('\nload-project-context.ts starting...');
|
|
82
|
+
|
|
83
|
+
// Read hook input from stdin
|
|
84
|
+
let hookInput: HookInput | null = null;
|
|
85
|
+
try {
|
|
86
|
+
const chunks: Buffer[] = [];
|
|
87
|
+
for await (const chunk of process.stdin) {
|
|
88
|
+
chunks.push(chunk);
|
|
89
|
+
}
|
|
90
|
+
const input = Buffer.concat(chunks).toString('utf-8');
|
|
91
|
+
if (input.trim()) {
|
|
92
|
+
hookInput = JSON.parse(input);
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('Could not parse hook input, using process.cwd()');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Get current working directory
|
|
99
|
+
const cwd = hookInput?.cwd || process.cwd();
|
|
100
|
+
|
|
101
|
+
// Determine meaningful project name
|
|
102
|
+
// If cwd is a Notes directory, use parent directory name instead
|
|
103
|
+
let projectName = basename(cwd);
|
|
104
|
+
if (projectName.toLowerCase() === 'notes') {
|
|
105
|
+
projectName = basename(dirname(cwd));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.error(`Working directory: ${cwd}`);
|
|
109
|
+
console.error(`Project: ${projectName}`);
|
|
110
|
+
|
|
111
|
+
// Check if this is a subagent session - skip for subagents
|
|
112
|
+
const isSubagent = process.env.CLAUDE_AGENT_TYPE !== undefined ||
|
|
113
|
+
(process.env.CLAUDE_PROJECT_DIR || '').includes('/.claude/agents/');
|
|
114
|
+
|
|
115
|
+
if (isSubagent) {
|
|
116
|
+
console.error('Subagent session - skipping project context setup');
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 1. Find and READ all CLAUDE.md files - inject them into context
|
|
121
|
+
// This ensures Claude actually processes the instructions, not just sees them in headers
|
|
122
|
+
const claudeMdPaths = findAllClaudeMdPaths(cwd);
|
|
123
|
+
const claudeMdContents: { path: string; content: string }[] = [];
|
|
124
|
+
|
|
125
|
+
if (claudeMdPaths.length > 0) {
|
|
126
|
+
console.error(`Found ${claudeMdPaths.length} CLAUDE.md file(s):`);
|
|
127
|
+
for (const path of claudeMdPaths) {
|
|
128
|
+
console.error(` - ${path}`);
|
|
129
|
+
try {
|
|
130
|
+
const content = readFileSync(path, 'utf-8');
|
|
131
|
+
claudeMdContents.push({ path, content });
|
|
132
|
+
console.error(` Read ${content.length} chars`);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error(` Could not read: ${error}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
console.error('No CLAUDE.md found in project');
|
|
139
|
+
console.error(' Consider creating one at ./CLAUDE.md or ./.claude/CLAUDE.md');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 2. Find or create Notes directory
|
|
143
|
+
// Priority:
|
|
144
|
+
// 1. Active session routing (pai route <project>) → routed Obsidian path
|
|
145
|
+
// 2. Local Notes/ in cwd → use it (git-trackable, e.g. symlink to Obsidian)
|
|
146
|
+
// 3. Central ~/.claude/projects/.../Notes/ → fallback
|
|
147
|
+
const routedPath = getRoutedNotesPath();
|
|
148
|
+
let notesDir: string;
|
|
149
|
+
|
|
150
|
+
if (routedPath) {
|
|
151
|
+
// Routing is active - use the configured Obsidian Notes path
|
|
152
|
+
const { mkdirSync } = await import('fs');
|
|
153
|
+
if (!existsSync(routedPath)) {
|
|
154
|
+
mkdirSync(routedPath, { recursive: true });
|
|
155
|
+
console.error(`Created routed Notes: ${routedPath}`);
|
|
156
|
+
} else {
|
|
157
|
+
console.error(`Notes directory: ${routedPath} (routed via pai route)`);
|
|
158
|
+
}
|
|
159
|
+
notesDir = routedPath;
|
|
160
|
+
} else {
|
|
161
|
+
const notesInfo = findNotesDir(cwd);
|
|
162
|
+
|
|
163
|
+
if (notesInfo.isLocal) {
|
|
164
|
+
notesDir = notesInfo.path;
|
|
165
|
+
console.error(`Notes directory: ${notesDir} (local)`);
|
|
166
|
+
} else {
|
|
167
|
+
// Create central Notes directory
|
|
168
|
+
if (!existsSync(notesInfo.path)) {
|
|
169
|
+
const { mkdirSync } = await import('fs');
|
|
170
|
+
mkdirSync(notesInfo.path, { recursive: true });
|
|
171
|
+
console.error(`Created central Notes: ${notesInfo.path}`);
|
|
172
|
+
} else {
|
|
173
|
+
console.error(`Notes directory: ${notesInfo.path} (central)`);
|
|
174
|
+
}
|
|
175
|
+
notesDir = notesInfo.path;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 3. Cleanup old .jsonl files from project root (move to sessions/)
|
|
180
|
+
// Keep the newest one for potential resume, move older ones to sessions/
|
|
181
|
+
const projectDir = getProjectDir(cwd);
|
|
182
|
+
if (existsSync(projectDir)) {
|
|
183
|
+
try {
|
|
184
|
+
const files = readdirSync(projectDir);
|
|
185
|
+
const jsonlFiles = files
|
|
186
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
187
|
+
.map(f => ({
|
|
188
|
+
name: f,
|
|
189
|
+
path: join(projectDir, f),
|
|
190
|
+
mtime: statSync(join(projectDir, f)).mtime.getTime()
|
|
191
|
+
}))
|
|
192
|
+
.sort((a, b) => b.mtime - a.mtime); // newest first
|
|
193
|
+
|
|
194
|
+
if (jsonlFiles.length > 1) {
|
|
195
|
+
const { mkdirSync, renameSync } = await import('fs');
|
|
196
|
+
const sessionsDir = join(projectDir, 'sessions');
|
|
197
|
+
if (!existsSync(sessionsDir)) {
|
|
198
|
+
mkdirSync(sessionsDir, { recursive: true });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Move all except the newest
|
|
202
|
+
for (let i = 1; i < jsonlFiles.length; i++) {
|
|
203
|
+
const file = jsonlFiles[i];
|
|
204
|
+
const destPath = join(sessionsDir, file.name);
|
|
205
|
+
if (!existsSync(destPath)) {
|
|
206
|
+
renameSync(file.path, destPath);
|
|
207
|
+
console.error(`Moved old session: ${file.name} → sessions/`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(`Could not cleanup old .jsonl files: ${error}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 4. Find or create TODO.md
|
|
217
|
+
const todoPath = findTodoPath(cwd);
|
|
218
|
+
const hasTodo = existsSync(todoPath);
|
|
219
|
+
if (hasTodo) {
|
|
220
|
+
console.error(`TODO.md: ${todoPath}`);
|
|
221
|
+
} else {
|
|
222
|
+
// Create TODO.md in the Notes directory
|
|
223
|
+
const newTodoPath = join(notesDir, 'TODO.md');
|
|
224
|
+
const { writeFileSync } = await import('fs');
|
|
225
|
+
writeFileSync(newTodoPath, `# TODO\n\n## Offen\n\n- [ ] \n\n---\n\n*Created: ${new Date().toISOString()}*\n`);
|
|
226
|
+
console.error(`Created TODO.md: ${newTodoPath}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 5. Check for existing note or create new one
|
|
230
|
+
let activeNotePath: string | null = null;
|
|
231
|
+
|
|
232
|
+
if (notesDir) { // notesDir is always set now (local or central)
|
|
233
|
+
const currentNotePath = getCurrentNotePath(notesDir);
|
|
234
|
+
|
|
235
|
+
// Determine if we need a new note
|
|
236
|
+
let needsNewNote = false;
|
|
237
|
+
if (!currentNotePath) {
|
|
238
|
+
needsNewNote = true;
|
|
239
|
+
console.error('\nNo previous session notes found - creating new one');
|
|
240
|
+
} else {
|
|
241
|
+
// Check if the existing note is completed
|
|
242
|
+
try {
|
|
243
|
+
const content = readFileSync(currentNotePath, 'utf-8');
|
|
244
|
+
if (content.includes('**Status:** Completed') || content.includes('**Completed:**')) {
|
|
245
|
+
needsNewNote = true;
|
|
246
|
+
console.error(`\nPrevious note completed - creating new one`);
|
|
247
|
+
const summaryMatch = content.match(/## Next Steps\n\n([^\n]+)/);
|
|
248
|
+
if (summaryMatch) {
|
|
249
|
+
console.error(` Previous: ${summaryMatch[1].substring(0, 60)}...`);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
console.error(`\nContinuing session note: ${basename(currentNotePath)}`);
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
needsNewNote = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Create new note if needed
|
|
260
|
+
if (needsNewNote) {
|
|
261
|
+
activeNotePath = createSessionNote(notesDir, projectName);
|
|
262
|
+
console.error(`Created: ${basename(activeNotePath)}`);
|
|
263
|
+
} else {
|
|
264
|
+
activeNotePath = currentNotePath!;
|
|
265
|
+
// Show preview of current note
|
|
266
|
+
try {
|
|
267
|
+
const content = readFileSync(activeNotePath, 'utf-8');
|
|
268
|
+
const lines = content.split('\n').slice(0, 12);
|
|
269
|
+
console.error('--- Current Note Preview ---');
|
|
270
|
+
for (const line of lines) {
|
|
271
|
+
console.error(line);
|
|
272
|
+
}
|
|
273
|
+
console.error('--- End Preview ---\n');
|
|
274
|
+
} catch {
|
|
275
|
+
// Ignore read errors
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// 6. Show TODO.md preview
|
|
281
|
+
if (existsSync(todoPath)) {
|
|
282
|
+
try {
|
|
283
|
+
const todoContent = readFileSync(todoPath, 'utf-8');
|
|
284
|
+
const todoLines = todoContent.split('\n').filter(l => l.includes('[ ]')).slice(0, 5);
|
|
285
|
+
if (todoLines.length > 0) {
|
|
286
|
+
console.error('\nOpen TODOs:');
|
|
287
|
+
for (const line of todoLines) {
|
|
288
|
+
console.error(` ${line.trim()}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} catch {
|
|
292
|
+
// Ignore read errors
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 7. Send ntfy.sh notification (MANDATORY)
|
|
297
|
+
await sendNtfyNotification(`Session started in ${projectName}`);
|
|
298
|
+
|
|
299
|
+
// 7.5. Run pai project detect to identify the registered PAI project
|
|
300
|
+
const paiBin = findPaiBinary();
|
|
301
|
+
let paiProjectBlock = '';
|
|
302
|
+
try {
|
|
303
|
+
const { execFileSync } = await import('child_process');
|
|
304
|
+
const raw = execFileSync(paiBin, ['project', 'detect', '--json', cwd], {
|
|
305
|
+
encoding: 'utf-8',
|
|
306
|
+
env: process.env,
|
|
307
|
+
}).trim();
|
|
308
|
+
|
|
309
|
+
if (raw) {
|
|
310
|
+
const detected = JSON.parse(raw) as {
|
|
311
|
+
slug?: string;
|
|
312
|
+
display_name?: string;
|
|
313
|
+
root_path?: string;
|
|
314
|
+
match_type?: string;
|
|
315
|
+
relative_path?: string | null;
|
|
316
|
+
session_count?: number;
|
|
317
|
+
status?: string;
|
|
318
|
+
error?: string;
|
|
319
|
+
cwd?: string;
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
if (detected.error === 'no_match') {
|
|
323
|
+
paiProjectBlock = `PAI Project Registry: No registered project matches this directory.
|
|
324
|
+
Run "pai project add ." to register this project, or use /route to tag the session.`;
|
|
325
|
+
console.error('PAI detect: no match for', cwd);
|
|
326
|
+
} else if (detected.slug) {
|
|
327
|
+
const name = detected.display_name || detected.slug;
|
|
328
|
+
const nameSlug = ` (slug: ${detected.slug})`;
|
|
329
|
+
const matchDesc = detected.match_type === 'exact'
|
|
330
|
+
? 'exact'
|
|
331
|
+
: `parent (+${detected.relative_path ?? ''})`;
|
|
332
|
+
const statusFlag = detected.status && detected.status !== 'active'
|
|
333
|
+
? ` [${detected.status.toUpperCase()}]`
|
|
334
|
+
: '';
|
|
335
|
+
paiProjectBlock = `PAI Project Registry: ${name}${statusFlag}${nameSlug}
|
|
336
|
+
Match: ${matchDesc} | Sessions: ${detected.session_count ?? 0}${detected.status && detected.status !== 'active' ? `\nWARNING: Project status is "${detected.status}". Run: pai project health --fix` : ''}`;
|
|
337
|
+
console.error(`PAI detect: matched "${detected.slug}" (${detected.match_type})`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} catch (e) {
|
|
341
|
+
// Non-fatal — don't break session start if pai is unavailable
|
|
342
|
+
console.error('pai project detect failed:', e);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// 8. Output system reminder with session info
|
|
346
|
+
const reminder = `
|
|
347
|
+
<system-reminder>
|
|
348
|
+
PROJECT CONTEXT LOADED
|
|
349
|
+
|
|
350
|
+
Project: ${projectName}
|
|
351
|
+
Working Directory: ${cwd}
|
|
352
|
+
${notesDir ? `Notes Directory: ${notesDir}${routedPath ? ' (routed via pai route)' : ''}` : 'Notes: disabled (no local Notes/ directory)'}
|
|
353
|
+
${hasTodo ? `TODO: ${todoPath}` : 'TODO: not found'}
|
|
354
|
+
${claudeMdPaths.length > 0 ? `CLAUDE.md: ${claudeMdPaths.join(', ')}` : 'No CLAUDE.md found'}
|
|
355
|
+
${activeNotePath ? `Active Note: ${basename(activeNotePath)}` : ''}
|
|
356
|
+
${routedPath ? `\nNote Routing: ACTIVE (pai route is set - notes go to Obsidian vault)` : ''}
|
|
357
|
+
${paiProjectBlock ? `\n${paiProjectBlock}` : ''}
|
|
358
|
+
Session Commands:
|
|
359
|
+
- "pause session" → Save checkpoint, update TODO, exit (no compact)
|
|
360
|
+
- "end session" → Finalize note, commit if needed, start fresh next time
|
|
361
|
+
- "pai route clear" → Clear note routing (in a new session)
|
|
362
|
+
</system-reminder>
|
|
363
|
+
`;
|
|
364
|
+
|
|
365
|
+
// Output to stdout for Claude to receive
|
|
366
|
+
console.log(reminder);
|
|
367
|
+
|
|
368
|
+
// 9. INJECT CLAUDE.md contents as system-reminders
|
|
369
|
+
// This ensures Claude actually reads and processes the instructions
|
|
370
|
+
for (const { path, content } of claudeMdContents) {
|
|
371
|
+
const claudeMdReminder = `
|
|
372
|
+
<system-reminder>
|
|
373
|
+
LOCAL CLAUDE.md LOADED (MANDATORY - READ AND FOLLOW)
|
|
374
|
+
|
|
375
|
+
Source: ${path}
|
|
376
|
+
|
|
377
|
+
${content}
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
THE ABOVE INSTRUCTIONS ARE MANDATORY. Follow them exactly.
|
|
381
|
+
</system-reminder>
|
|
382
|
+
`;
|
|
383
|
+
console.log(claudeMdReminder);
|
|
384
|
+
console.error(`Injected CLAUDE.md content from: ${path}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
console.error('\nProject context setup complete\n');
|
|
388
|
+
process.exit(0);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
main().catch(error => {
|
|
392
|
+
console.error('load-project-context.ts error:', error);
|
|
393
|
+
process.exit(0); // Don't block session start
|
|
394
|
+
});
|