@jeanzorzetti/context-keeper 0.1.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.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ require("dotenv/config");
5
+ const index_js_1 = require("./index.js");
6
+ const extractor_js_1 = require("./extractor.js");
7
+ const merger_js_1 = require("./merger.js");
8
+ const [, , command, ...args] = process.argv;
9
+ async function main() {
10
+ switch (command) {
11
+ case 'start': {
12
+ const autoCommit = args.includes('--auto-commit');
13
+ console.log('[cli] Starting Context Keeper daemon...');
14
+ const stop = (0, index_js_1.startDaemon)({ autoCommit });
15
+ process.on('SIGINT', () => {
16
+ console.log('\n[cli] Shutting down...');
17
+ stop();
18
+ process.exit(0);
19
+ });
20
+ process.on('SIGTERM', () => {
21
+ stop();
22
+ process.exit(0);
23
+ });
24
+ break;
25
+ }
26
+ case 'extract': {
27
+ const filePath = args[0];
28
+ if (!filePath) {
29
+ console.error('Usage: context-keeper extract <path-to-session.jsonl>');
30
+ process.exit(1);
31
+ }
32
+ if (!process.env.GROQ_API_KEY) {
33
+ console.error('GROQ_API_KEY is not set.');
34
+ process.exit(1);
35
+ }
36
+ console.log(`[cli] Extracting decisions from: ${filePath}`);
37
+ const decisions = await (0, extractor_js_1.processTranscript)(filePath);
38
+ if (decisions.length === 0) {
39
+ console.log('[cli] No architectural decisions found.');
40
+ return;
41
+ }
42
+ console.log(`[cli] Found ${decisions.length} decision(s):`);
43
+ decisions.forEach((d, i) => console.log(` ${i + 1}. ${d}`));
44
+ const projectDir = (0, merger_js_1.resolveProjectDir)(filePath);
45
+ if (projectDir) {
46
+ const contextFiles = (0, merger_js_1.findContextFiles)(projectDir);
47
+ for (const cf of contextFiles) {
48
+ (0, merger_js_1.mergeDecisions)(cf, decisions);
49
+ console.log(`[cli] Updated ${cf}`);
50
+ }
51
+ }
52
+ else {
53
+ console.warn('[cli] Could not resolve project dir — skipping file update.');
54
+ }
55
+ break;
56
+ }
57
+ case 'status': {
58
+ console.log('[cli] Context Keeper status: daemon not running in this process.');
59
+ console.log('[cli] Use `context-keeper start` to launch the daemon.');
60
+ break;
61
+ }
62
+ default: {
63
+ console.log(`Context Keeper v0.1.0
64
+
65
+ Usage:
66
+ context-keeper start [--auto-commit] Start the daemon
67
+ context-keeper extract <path> Manually extract decisions from a session
68
+ context-keeper status Show daemon status
69
+ `);
70
+ }
71
+ }
72
+ }
73
+ main().catch((err) => {
74
+ console.error('[cli] Fatal error:', err);
75
+ process.exit(1);
76
+ });
77
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ export interface JsonlMessage {
2
+ role: string;
3
+ content: string | Array<{
4
+ type: string;
5
+ text?: string;
6
+ }>;
7
+ timestamp?: string;
8
+ }
9
+ /**
10
+ * Reads a .jsonl file and returns its messages.
11
+ */
12
+ export declare function readTranscript(filePath: string): JsonlMessage[];
13
+ /**
14
+ * Converts messages array to a plain-text transcript, capped at MAX_CHARS.
15
+ * Prioritises recent messages (sliding window from the end).
16
+ */
17
+ export declare function formatTranscript(messages: JsonlMessage[]): string;
18
+ /**
19
+ * Calls Groq to extract architectural decisions from a transcript.
20
+ * Returns an array of decision strings (may be empty).
21
+ */
22
+ export declare function extractDecisions(transcript: string): Promise<string[]>;
23
+ /**
24
+ * Full pipeline: read JSONL → format → extract decisions via Groq.
25
+ */
26
+ export declare function processTranscript(filePath: string): Promise<string[]>;
27
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE,CAqB/D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM,CA4BjE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA+B5E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ3E"}
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.readTranscript = readTranscript;
40
+ exports.formatTranscript = formatTranscript;
41
+ exports.extractDecisions = extractDecisions;
42
+ exports.processTranscript = processTranscript;
43
+ const fs = __importStar(require("fs"));
44
+ const groq_sdk_1 = __importDefault(require("groq-sdk"));
45
+ const MAX_TOKENS = 8000;
46
+ const CHARS_PER_TOKEN = 4; // rough estimate
47
+ const MAX_CHARS = MAX_TOKENS * CHARS_PER_TOKEN;
48
+ const SYSTEM_PROMPT = `You are an architectural decision extractor. Given a coding session transcript,
49
+ extract only concrete architectural decisions made (technology choices, patterns adopted,
50
+ constraints identified). Output as a JSON array of strings. Each string: one decision,
51
+ max 100 chars, past tense, format "chose X over Y because Z" or "decided to X because Y".
52
+ Ignore questions, debugging steps, and implementation details. Max 10 decisions per session.`;
53
+ /**
54
+ * Reads a .jsonl file and returns its messages.
55
+ */
56
+ function readTranscript(filePath) {
57
+ const raw = fs.readFileSync(filePath, 'utf-8');
58
+ const messages = [];
59
+ for (const line of raw.split('\n')) {
60
+ const trimmed = line.trim();
61
+ if (!trimmed)
62
+ continue;
63
+ try {
64
+ const parsed = JSON.parse(trimmed);
65
+ // Claude Code JSONL can wrap messages inside a top-level object
66
+ if (parsed.message) {
67
+ messages.push(parsed.message);
68
+ }
69
+ else if (parsed.role) {
70
+ messages.push(parsed);
71
+ }
72
+ }
73
+ catch {
74
+ // skip malformed lines
75
+ }
76
+ }
77
+ return messages;
78
+ }
79
+ /**
80
+ * Converts messages array to a plain-text transcript, capped at MAX_CHARS.
81
+ * Prioritises recent messages (sliding window from the end).
82
+ */
83
+ function formatTranscript(messages) {
84
+ const lines = [];
85
+ for (const msg of messages) {
86
+ const role = msg.role ?? 'unknown';
87
+ let text;
88
+ if (typeof msg.content === 'string') {
89
+ text = msg.content;
90
+ }
91
+ else if (Array.isArray(msg.content)) {
92
+ text = msg.content
93
+ .filter((b) => b.type === 'text' && b.text)
94
+ .map((b) => b.text)
95
+ .join('\n');
96
+ }
97
+ else {
98
+ text = '';
99
+ }
100
+ if (text.trim()) {
101
+ lines.push(`[${role}]: ${text.trim()}`);
102
+ }
103
+ }
104
+ const full = lines.join('\n\n');
105
+ if (full.length <= MAX_CHARS)
106
+ return full;
107
+ // Sliding window: take the last MAX_CHARS characters
108
+ return '...(truncated)\n\n' + full.slice(full.length - MAX_CHARS);
109
+ }
110
+ /**
111
+ * Calls Groq to extract architectural decisions from a transcript.
112
+ * Returns an array of decision strings (may be empty).
113
+ */
114
+ async function extractDecisions(transcript) {
115
+ const groq = new groq_sdk_1.default({ apiKey: process.env.GROQ_API_KEY });
116
+ const completion = await groq.chat.completions.create({
117
+ model: 'llama-3.3-70b-versatile',
118
+ messages: [
119
+ { role: 'system', content: SYSTEM_PROMPT },
120
+ { role: 'user', content: `Transcript:\n\n${transcript}` },
121
+ ],
122
+ temperature: 0.1,
123
+ max_tokens: 1024,
124
+ });
125
+ const raw = completion.choices[0]?.message?.content ?? '[]';
126
+ try {
127
+ // Strip markdown code fences if present
128
+ const cleaned = raw.replace(/```(?:json)?\n?/g, '').replace(/```/g, '').trim();
129
+ const parsed = JSON.parse(cleaned);
130
+ if (Array.isArray(parsed)) {
131
+ return parsed
132
+ .filter((item) => typeof item === 'string')
133
+ .map((s) => s.trim())
134
+ .filter(Boolean)
135
+ .slice(0, 10);
136
+ }
137
+ }
138
+ catch {
139
+ // Groq returned something unexpected — return empty
140
+ }
141
+ return [];
142
+ }
143
+ /**
144
+ * Full pipeline: read JSONL → format → extract decisions via Groq.
145
+ */
146
+ async function processTranscript(filePath) {
147
+ const messages = readTranscript(filePath);
148
+ if (messages.length === 0)
149
+ return [];
150
+ const transcript = formatTranscript(messages);
151
+ if (!transcript.trim())
152
+ return [];
153
+ return extractDecisions(transcript);
154
+ }
155
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"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"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Commits the given files in the repository that contains them.
3
+ * No-op if the files have no staged/unstaged changes or if the directory
4
+ * is not a git repository.
5
+ */
6
+ export declare function commitContextFiles(filePaths: string[]): Promise<void>;
7
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB3E"}
package/dist/git.js ADDED
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.commitContextFiles = commitContextFiles;
40
+ const simple_git_1 = __importDefault(require("simple-git"));
41
+ const path = __importStar(require("path"));
42
+ /**
43
+ * Commits the given files in the repository that contains them.
44
+ * No-op if the files have no staged/unstaged changes or if the directory
45
+ * is not a git repository.
46
+ */
47
+ async function commitContextFiles(filePaths) {
48
+ if (filePaths.length === 0)
49
+ return;
50
+ // Use the directory of the first file as the git repo root candidate
51
+ const repoDir = path.dirname(filePaths[0]);
52
+ const git = (0, simple_git_1.default)(repoDir);
53
+ const isRepo = await git.checkIsRepo().catch(() => false);
54
+ if (!isRepo)
55
+ return;
56
+ // Stage only the context files that actually changed
57
+ await git.add(filePaths);
58
+ const status = await git.status();
59
+ const staged = status.staged;
60
+ if (staged.length === 0)
61
+ return;
62
+ await git.commit('chore: update context-keeper decisions', filePaths, {
63
+ '--no-verify': null,
64
+ });
65
+ }
66
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,gDAoBC;AA5BD,4DAAmC;AACnC,2CAA6B;AAE7B;;;;GAIG;AACI,KAAK,UAAU,kBAAkB,CAAC,SAAmB;IAC1D,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEnC,qEAAqE;IACrE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAA,oBAAS,EAAC,OAAO,CAAC,CAAC;IAE/B,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM;QAAE,OAAO;IAEpB,qDAAqD;IACrD,MAAM,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAEzB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEhC,MAAM,GAAG,CAAC,MAAM,CAAC,wCAAwC,EAAE,SAAS,EAAE;QACpE,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,29 @@
1
+ export interface DecisionEntry {
2
+ /** Unique ID for the decision (cuid-style, generated by daemon). */
3
+ id: string;
4
+ /** Absolute path to the project directory. */
5
+ projectPath: string;
6
+ /** Human-readable project name (basename of projectPath). */
7
+ projectName: string;
8
+ /** Basename of the source .jsonl file (session identifier). */
9
+ sessionId: string;
10
+ /** The decision text, max ~100 chars. */
11
+ text: string;
12
+ /** ISO 8601 timestamp when the decision was captured. */
13
+ capturedAt: string;
14
+ }
15
+ export interface DecisionIndex {
16
+ /** Schema version — bump if shape changes to allow migrations. */
17
+ version: 1;
18
+ /** All captured decisions, sorted descending by capturedAt (newest first). */
19
+ decisions: DecisionEntry[];
20
+ }
21
+ /**
22
+ * Appends new decisions to ~/.context-keeper/index.json.
23
+ * Deduplicates by (projectPath, sessionId, text) to avoid re-adding on retry.
24
+ * Entries are kept sorted newest-first.
25
+ */
26
+ export declare function appendToIndex(projectPath: string, sessionId: string, decisions: string[]): void;
27
+ /** Returns the path to the index file (useful for tests/CLI). */
28
+ export declare function getIndexPath(): string;
29
+ //# sourceMappingURL=index-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-writer.d.ts","sourceRoot":"","sources":["../src/index-writer.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,EAAE,EAAE,MAAM,CAAC;IACX,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,CAAC;IACpB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,OAAO,EAAE,CAAC,CAAC;IACX,8EAA8E;IAC9E,SAAS,EAAE,aAAa,EAAE,CAAC;CAC5B;AAkCD;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EAAE,GAClB,IAAI,CA8BN;AAED,iEAAiE;AACjE,wBAAgB,YAAY,IAAI,MAAM,CAErC"}
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.appendToIndex = appendToIndex;
37
+ exports.getIndexPath = getIndexPath;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const os = __importStar(require("os"));
41
+ const crypto = __importStar(require("crypto"));
42
+ // ---------------------------------------------------------------------------
43
+ const INDEX_DIR = path.join(os.homedir(), '.context-keeper');
44
+ const INDEX_PATH = path.join(INDEX_DIR, 'index.json');
45
+ const SCHEMA_VERSION = 1;
46
+ function readIndex() {
47
+ if (!fs.existsSync(INDEX_PATH)) {
48
+ return { version: 1, decisions: [] };
49
+ }
50
+ try {
51
+ const raw = fs.readFileSync(INDEX_PATH, 'utf-8');
52
+ const parsed = JSON.parse(raw);
53
+ if (!parsed.version || !Array.isArray(parsed.decisions)) {
54
+ return { version: 1, decisions: [] };
55
+ }
56
+ return parsed;
57
+ }
58
+ catch {
59
+ return { version: 1, decisions: [] };
60
+ }
61
+ }
62
+ function writeIndex(index) {
63
+ fs.mkdirSync(INDEX_DIR, { recursive: true });
64
+ fs.writeFileSync(INDEX_PATH, JSON.stringify(index, null, 2) + '\n', 'utf-8');
65
+ }
66
+ /** Simple collision-resistant ID (no external deps). */
67
+ function generateId() {
68
+ return crypto.randomBytes(12).toString('base64url');
69
+ }
70
+ /**
71
+ * Appends new decisions to ~/.context-keeper/index.json.
72
+ * Deduplicates by (projectPath, sessionId, text) to avoid re-adding on retry.
73
+ * Entries are kept sorted newest-first.
74
+ */
75
+ function appendToIndex(projectPath, sessionId, decisions) {
76
+ if (decisions.length === 0)
77
+ return;
78
+ const index = readIndex();
79
+ const capturedAt = new Date().toISOString();
80
+ const projectName = path.basename(projectPath);
81
+ // Build a dedup key set for existing entries in this session
82
+ const existingKeys = new Set(index.decisions
83
+ .filter((e) => e.projectPath === projectPath && e.sessionId === sessionId)
84
+ .map((e) => e.text));
85
+ const newEntries = decisions
86
+ .filter((text) => !existingKeys.has(text))
87
+ .map((text) => ({
88
+ id: generateId(),
89
+ projectPath,
90
+ projectName,
91
+ sessionId,
92
+ text,
93
+ capturedAt,
94
+ }));
95
+ if (newEntries.length === 0)
96
+ return;
97
+ index.decisions = [...newEntries, ...index.decisions];
98
+ index.version = 1;
99
+ writeIndex(index);
100
+ }
101
+ /** Returns the path to the index file (useful for tests/CLI). */
102
+ function getIndexPath() {
103
+ return INDEX_PATH;
104
+ }
105
+ //# sourceMappingURL=index-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-writer.js","sourceRoot":"","sources":["../src/index-writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import 'dotenv/config';
2
+ export interface DaemonOptions {
3
+ autoCommit?: boolean;
4
+ }
5
+ /**
6
+ * Starts the Context Keeper daemon.
7
+ * Returns a cleanup function to stop the watcher.
8
+ */
9
+ export declare function startDaemon(options?: DaemonOptions): () => void;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AAQvB,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,IAAI,CA6DnE"}
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.startDaemon = startDaemon;
37
+ require("dotenv/config");
38
+ const path = __importStar(require("path"));
39
+ const watcher_js_1 = require("./watcher.js");
40
+ const extractor_js_1 = require("./extractor.js");
41
+ const merger_js_1 = require("./merger.js");
42
+ const git_js_1 = require("./git.js");
43
+ const index_writer_js_1 = require("./index-writer.js");
44
+ /**
45
+ * Starts the Context Keeper daemon.
46
+ * Returns a cleanup function to stop the watcher.
47
+ */
48
+ function startDaemon(options = {}) {
49
+ const { autoCommit = false } = options;
50
+ if (!process.env.GROQ_API_KEY) {
51
+ console.error('[daemon] GROQ_API_KEY is not set. Extraction will fail.');
52
+ }
53
+ const stop = (0, watcher_js_1.startWatcher)(async (filePath) => {
54
+ console.log(`[daemon] Session ended: ${filePath}`);
55
+ const projectDir = (0, merger_js_1.resolveProjectDir)(filePath);
56
+ if (!projectDir) {
57
+ console.warn(`[daemon] Could not resolve project dir from: ${filePath}`);
58
+ return;
59
+ }
60
+ let decisions;
61
+ try {
62
+ decisions = await (0, extractor_js_1.processTranscript)(filePath);
63
+ }
64
+ catch (err) {
65
+ console.error(`[daemon] Extraction failed for ${filePath}:`, err);
66
+ return;
67
+ }
68
+ if (decisions.length === 0) {
69
+ console.log(`[daemon] No decisions extracted from ${filePath}`);
70
+ return;
71
+ }
72
+ console.log(`[daemon] Extracted ${decisions.length} decision(s):`, decisions);
73
+ // Write to global index (~/.context-keeper/index.json) for MCP server consumption
74
+ const sessionId = path.basename(filePath);
75
+ try {
76
+ (0, index_writer_js_1.appendToIndex)(projectDir, sessionId, decisions);
77
+ console.log(`[daemon] Updated index for session ${sessionId}`);
78
+ }
79
+ catch (err) {
80
+ console.error('[daemon] Failed to update index:', err);
81
+ }
82
+ const contextFiles = (0, merger_js_1.findContextFiles)(projectDir);
83
+ for (const cf of contextFiles) {
84
+ try {
85
+ (0, merger_js_1.mergeDecisions)(cf, decisions);
86
+ console.log(`[daemon] Updated ${cf}`);
87
+ }
88
+ catch (err) {
89
+ console.error(`[daemon] Failed to merge into ${cf}:`, err);
90
+ }
91
+ }
92
+ if (autoCommit) {
93
+ try {
94
+ await (0, git_js_1.commitContextFiles)(contextFiles);
95
+ console.log('[daemon] Committed context files');
96
+ }
97
+ catch (err) {
98
+ console.error('[daemon] Git commit failed:', err);
99
+ }
100
+ }
101
+ });
102
+ return stop;
103
+ }
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgBA,kCA6DC;AA7ED,yBAAuB;AACvB,2CAA6B;AAC7B,6CAA4C;AAC5C,iDAAmD;AACnD,2CAAkF;AAClF,qCAA8C;AAC9C,uDAAkD;AAMlD;;;GAGG;AACH,SAAgB,WAAW,CAAC,UAAyB,EAAE;IACrD,MAAM,EAAE,UAAU,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAEvC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;QAC9B,OAAO,CAAC,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,IAAI,GAAG,IAAA,yBAAY,EAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC3C,OAAO,CAAC,GAAG,CAAC,2BAA2B,QAAQ,EAAE,CAAC,CAAC;QAEnD,MAAM,UAAU,GAAG,IAAA,6BAAiB,EAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,gDAAgD,QAAQ,EAAE,CAAC,CAAC;YACzE,OAAO;QACT,CAAC;QAED,IAAI,SAAmB,CAAC;QACxB,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,IAAA,gCAAiB,EAAC,QAAQ,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,kCAAkC,QAAQ,GAAG,EAAE,GAAG,CAAC,CAAC;YAClE,OAAO;QACT,CAAC;QAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,GAAG,CAAC,wCAAwC,QAAQ,EAAE,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,sBAAsB,SAAS,CAAC,MAAM,eAAe,EAAE,SAAS,CAAC,CAAC;QAE9E,kFAAkF;QAClF,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,IAAA,+BAAa,EAAC,UAAU,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,sCAAsC,SAAS,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,CAAC;QACzD,CAAC;QAED,MAAM,YAAY,GAAG,IAAA,4BAAgB,EAAC,UAAU,CAAC,CAAC;QAClD,KAAK,MAAM,EAAE,IAAI,YAAY,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,IAAA,0BAAc,EAAC,EAAE,EAAE,SAAS,CAAC,CAAC;gBAC9B,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;YACxC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACf,IAAI,CAAC;gBACH,MAAM,IAAA,2BAAkB,EAAC,YAAY,CAAC,CAAC;gBACvC,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;YAClD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Merges new decisions into the managed section.
3
+ *
4
+ * Rules (from spec §6.4):
5
+ * 1. First run: append section to end of existing CLAUDE.md.
6
+ * 2. Subsequent updates: replace only the content between markers.
7
+ * 3. File doesn't exist: create with minimal header + section.
8
+ * 4. Manual edits inside markers: additive merge (new decisions appended, nothing deleted).
9
+ * 5. AGENTS.md: same behaviour.
10
+ */
11
+ export declare function mergeDecisions(filePath: string, newDecisions: string[]): void;
12
+ /**
13
+ * Finds CLAUDE.md and AGENTS.md starting from projectDir upward (git root detection).
14
+ * Returns all found paths (may be empty).
15
+ */
16
+ export declare function findContextFiles(projectDir: string): string[];
17
+ /**
18
+ * Resolves the project directory from a transcript file path.
19
+ *
20
+ * Claude Code stores transcripts at:
21
+ * ~/.claude/projects/<encoded-project-path>/<session-id>.jsonl
22
+ *
23
+ * The directory name is encoded differently on Unix vs Windows:
24
+ * - Unix/Linux/Mac: URL-encoded with %2F for path separators
25
+ * - Windows: dash-encoded, where single dashes replace both path separators (\) AND spaces
26
+ * Example: c--users-jeanz-onedrive-desktop-roi-labs-context-keeper
27
+ * Maps to: C:\Users\jeanz\OneDrive\Desktop\ROI Labs\context-keeper
28
+ *
29
+ * NOTE: Windows encoding is lossy and cannot be reliably decoded without filesystem context.
30
+ * Strategy: Use filesystem walk + git root discovery to find the actual project directory,
31
+ * ensuring projectName (via path.basename) is always correct and readable.
32
+ */
33
+ export declare function resolveProjectDir(transcriptPath: string): string | null;
34
+ //# sourceMappingURL=merger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merger.d.ts","sourceRoot":"","sources":["../src/merger.ts"],"names":[],"mappings":"AAkCA;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CAoD7E;AAMD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAS7D;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8CvE"}
package/dist/merger.js ADDED
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.mergeDecisions = mergeDecisions;
37
+ exports.findContextFiles = findContextFiles;
38
+ exports.resolveProjectDir = resolveProjectDir;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const MARKER_START = '<!-- context-keeper:start';
42
+ const MARKER_END = '<!-- context-keeper:end -->';
43
+ const SECTION_HEADER = '## Architectural Decisions (auto-captured)';
44
+ const MINIMAL_HEADER = `# Project Context\n\n`;
45
+ /**
46
+ * Builds the full managed section string (including markers).
47
+ */
48
+ function buildManagedSection(decisions, timestamp) {
49
+ const lines = decisions.map((d) => `- [${timestamp.slice(0, 10)}] ${d}`).join('\n');
50
+ return (`${MARKER_START} (last updated: ${timestamp}) -->\n` +
51
+ `${SECTION_HEADER}\n\n` +
52
+ `${lines}\n\n` +
53
+ `${MARKER_END}`);
54
+ }
55
+ /**
56
+ * Reads the current content between markers (if any).
57
+ * Returns null if no markers found.
58
+ */
59
+ function extractManagedSection(content) {
60
+ const startIdx = content.indexOf(MARKER_START);
61
+ if (startIdx === -1)
62
+ return null;
63
+ const endIdx = content.indexOf(MARKER_END, startIdx);
64
+ if (endIdx === -1)
65
+ return null;
66
+ return content.slice(startIdx, endIdx + MARKER_END.length);
67
+ }
68
+ /**
69
+ * Merges new decisions into the managed section.
70
+ *
71
+ * Rules (from spec §6.4):
72
+ * 1. First run: append section to end of existing CLAUDE.md.
73
+ * 2. Subsequent updates: replace only the content between markers.
74
+ * 3. File doesn't exist: create with minimal header + section.
75
+ * 4. Manual edits inside markers: additive merge (new decisions appended, nothing deleted).
76
+ * 5. AGENTS.md: same behaviour.
77
+ */
78
+ function mergeDecisions(filePath, newDecisions) {
79
+ const timestamp = new Date().toISOString();
80
+ if (newDecisions.length === 0)
81
+ return;
82
+ // Read existing file (or empty string if not found)
83
+ let existing = '';
84
+ if (fs.existsSync(filePath)) {
85
+ existing = fs.readFileSync(filePath, 'utf-8');
86
+ }
87
+ const currentSection = extractManagedSection(existing);
88
+ if (currentSection === null) {
89
+ // First run or markers not present — append section
90
+ const section = buildManagedSection(newDecisions, timestamp);
91
+ const base = existing.trim() ? existing.trimEnd() + '\n\n' : MINIMAL_HEADER;
92
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
93
+ fs.writeFileSync(filePath, base + section + '\n', 'utf-8');
94
+ return;
95
+ }
96
+ // Extract existing decisions to enable additive merge
97
+ const existingDecisionLines = currentSection
98
+ .split('\n')
99
+ .filter((line) => line.startsWith('- ['));
100
+ // Deduplicate: only add decisions not already present (by text)
101
+ const existingTexts = new Set(existingDecisionLines.map((l) => l.replace(/^- \[\d{4}-\d{2}-\d{2}\] /, '')));
102
+ const deduplicated = newDecisions.filter((d) => !existingTexts.has(d));
103
+ if (deduplicated.length === 0)
104
+ return;
105
+ const allDecisionLines = [
106
+ ...existingDecisionLines,
107
+ ...deduplicated.map((d) => `- [${timestamp.slice(0, 10)}] ${d}`),
108
+ ];
109
+ const updatedSection = `${MARKER_START} (last updated: ${timestamp}) -->\n` +
110
+ `${SECTION_HEADER}\n\n` +
111
+ `${allDecisionLines.join('\n')}\n\n` +
112
+ `${MARKER_END}`;
113
+ const updated = existing.replace(new RegExp(`${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}`), updatedSection);
114
+ fs.writeFileSync(filePath, updated, 'utf-8');
115
+ }
116
+ function escapeRegex(str) {
117
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
118
+ }
119
+ /**
120
+ * Finds CLAUDE.md and AGENTS.md starting from projectDir upward (git root detection).
121
+ * Returns all found paths (may be empty).
122
+ */
123
+ function findContextFiles(projectDir) {
124
+ const root = findGitRoot(projectDir) ?? projectDir;
125
+ const candidates = [
126
+ path.join(root, 'CLAUDE.md'),
127
+ path.join(root, 'AGENTS.md'),
128
+ ];
129
+ // Return all that exist, or [CLAUDE.md path] as default to create
130
+ const existing = candidates.filter((p) => fs.existsSync(p));
131
+ return existing.length > 0 ? existing : [path.join(root, 'CLAUDE.md')];
132
+ }
133
+ function findGitRoot(dir) {
134
+ let current = path.resolve(dir);
135
+ while (true) {
136
+ if (fs.existsSync(path.join(current, '.git')))
137
+ return current;
138
+ const parent = path.dirname(current);
139
+ if (parent === current)
140
+ return null;
141
+ current = parent;
142
+ }
143
+ }
144
+ /**
145
+ * Resolves the project directory from a transcript file path.
146
+ *
147
+ * Claude Code stores transcripts at:
148
+ * ~/.claude/projects/<encoded-project-path>/<session-id>.jsonl
149
+ *
150
+ * The directory name is encoded differently on Unix vs Windows:
151
+ * - Unix/Linux/Mac: URL-encoded with %2F for path separators
152
+ * - Windows: dash-encoded, where single dashes replace both path separators (\) AND spaces
153
+ * Example: c--users-jeanz-onedrive-desktop-roi-labs-context-keeper
154
+ * Maps to: C:\Users\jeanz\OneDrive\Desktop\ROI Labs\context-keeper
155
+ *
156
+ * NOTE: Windows encoding is lossy and cannot be reliably decoded without filesystem context.
157
+ * Strategy: Use filesystem walk + git root discovery to find the actual project directory,
158
+ * ensuring projectName (via path.basename) is always correct and readable.
159
+ */
160
+ function resolveProjectDir(transcriptPath) {
161
+ const parentDir = path.basename(path.dirname(transcriptPath));
162
+ // Try percent-decoding first (Unix/Linux format: %2F for separator)
163
+ try {
164
+ const decoded = decodeURIComponent(parentDir);
165
+ // Check if it looks like a valid absolute path
166
+ if (decoded.startsWith('/') || decoded.match(/^[a-zA-Z]:/)) {
167
+ // For Unix paths, return as-is (already decoded)
168
+ return decoded;
169
+ }
170
+ }
171
+ catch {
172
+ // Fall through to dash-decoding
173
+ }
174
+ // Try dash-decoding for Windows (lossy format: c--users-path-with-spaces-encoding)
175
+ if (parentDir.match(/^[a-z]--/)) {
176
+ try {
177
+ const driveLetter = parentDir[0].toUpperCase();
178
+ const driveRoot = driveLetter + ':';
179
+ // Search for the real directory by walking from common roots
180
+ // This allows us to derive the correct projectName from the git root's basename
181
+ const searchRoots = [
182
+ path.join(driveRoot, 'Users'),
183
+ path.join(driveRoot, 'Temp'),
184
+ path.join(driveRoot, 'Dev'),
185
+ driveRoot,
186
+ ];
187
+ for (const root of searchRoots) {
188
+ if (fs.existsSync(root)) {
189
+ const found = findProjectRootByEncoding(root, parentDir, 0);
190
+ if (found)
191
+ return found;
192
+ }
193
+ }
194
+ // If filesystem search fails, return null (unable to resolve)
195
+ // Better to fail safely than return an unverified encoded path
196
+ return null;
197
+ }
198
+ catch {
199
+ return null;
200
+ }
201
+ }
202
+ return null;
203
+ }
204
+ /**
205
+ * Searches for a real directory that matches the encoded path pattern,
206
+ * then climbs to find its git root (project root).
207
+ * Returns the git root path, ensuring projectName via path.basename() is correct.
208
+ */
209
+ function findProjectRootByEncoding(currentPath, encodedPath, depth) {
210
+ if (depth > 6)
211
+ return null; // Limit recursion depth
212
+ try {
213
+ const entries = fs.readdirSync(currentPath, { withFileTypes: true });
214
+ for (const entry of entries) {
215
+ if (!entry.isDirectory())
216
+ continue;
217
+ const normalizedName = entry.name.toLowerCase();
218
+ const encodedName = normalizedName.replace(/\s+/g, '-');
219
+ // Check if this directory's encoded name appears in the transcript encoding
220
+ if (encodedPath.includes(encodedName)) {
221
+ const fullPath = path.join(currentPath, entry.name);
222
+ // Found a matching directory — now climb to its git root
223
+ const gitRoot = findGitRoot(fullPath);
224
+ if (gitRoot) {
225
+ // Verify this is a real project (has .git)
226
+ return gitRoot;
227
+ }
228
+ // If no git root found but we matched, continue searching deeper
229
+ // (might be an intermediate directory, not the project root)
230
+ const found = findProjectRootByEncoding(fullPath, encodedPath, depth + 1);
231
+ if (found)
232
+ return found;
233
+ }
234
+ }
235
+ }
236
+ catch {
237
+ // Skip on permission errors or other I/O issues
238
+ }
239
+ return null;
240
+ }
241
+ //# sourceMappingURL=merger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merger.js","sourceRoot":"","sources":["../src/merger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export type SessionEndedCallback = (filePath: string) => Promise<void>;
2
+ /**
3
+ * Watches ~/.claude/projects/**\/*.jsonl for session end events.
4
+ * A session is considered ended when a file stops being modified for 5 minutes.
5
+ */
6
+ export declare function startWatcher(onSessionEnded: SessionEndedCallback): () => void;
7
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE;;;GAGG;AACH,wBAAgB,YAAY,CAAC,cAAc,EAAE,oBAAoB,GAAG,MAAM,IAAI,CA2C7E"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.startWatcher = startWatcher;
40
+ const chokidar_1 = __importDefault(require("chokidar"));
41
+ const os = __importStar(require("os"));
42
+ const path = __importStar(require("path"));
43
+ const DEBOUNCE_MS = 5 * 60 * 1000; // 5 minutes
44
+ /**
45
+ * Watches ~/.claude/projects/**\/*.jsonl for session end events.
46
+ * A session is considered ended when a file stops being modified for 5 minutes.
47
+ */
48
+ function startWatcher(onSessionEnded) {
49
+ const watchDir = path.join(os.homedir(), '.claude', 'projects');
50
+ const timers = new Map();
51
+ const watcher = chokidar_1.default.watch(`${watchDir}/**/*.jsonl`, {
52
+ persistent: true,
53
+ ignoreInitial: true,
54
+ awaitWriteFinish: {
55
+ stabilityThreshold: 2000,
56
+ pollInterval: 500,
57
+ },
58
+ });
59
+ function scheduleSessionEnd(filePath) {
60
+ const existing = timers.get(filePath);
61
+ if (existing)
62
+ clearTimeout(existing);
63
+ const timer = setTimeout(async () => {
64
+ timers.delete(filePath);
65
+ try {
66
+ await onSessionEnded(filePath);
67
+ }
68
+ catch (err) {
69
+ console.error(`[watcher] Error processing session for ${filePath}:`, err);
70
+ }
71
+ }, DEBOUNCE_MS);
72
+ timers.set(filePath, timer);
73
+ }
74
+ watcher.on('add', scheduleSessionEnd);
75
+ watcher.on('change', scheduleSessionEnd);
76
+ watcher.on('error', (err) => {
77
+ console.error('[watcher] Filesystem error:', err);
78
+ });
79
+ console.log(`[watcher] Watching ${watchDir}/**/*.jsonl (5-min debounce)`);
80
+ return () => {
81
+ for (const timer of timers.values())
82
+ clearTimeout(timer);
83
+ timers.clear();
84
+ watcher.close();
85
+ };
86
+ }
87
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,oCA2CC;AAvDD,wDAAgC;AAChC,uCAAyB;AACzB,2CAA6B;AAE7B,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,YAAY;AAI/C;;;GAGG;AACH,SAAgB,YAAY,CAAC,cAAoC;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAyC,CAAC;IAEhE,MAAM,OAAO,GAAG,kBAAQ,CAAC,KAAK,CAAC,GAAG,QAAQ,aAAa,EAAE;QACvD,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI;QACnB,gBAAgB,EAAE;YAChB,kBAAkB,EAAE,IAAI;YACxB,YAAY,EAAE,GAAG;SAClB;KACF,CAAC,CAAC;IAEH,SAAS,kBAAkB,CAAC,QAAgB;QAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtC,IAAI,QAAQ;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QAErC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,IAAI,EAAE;YAClC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACxB,IAAI,CAAC;gBACH,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,0CAA0C,QAAQ,GAAG,EAAE,GAAG,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC,EAAE,WAAW,CAAC,CAAC;QAEhB,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;IACtC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;IAEzC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC1B,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,sBAAsB,QAAQ,8BAA8B,CAAC,CAAC;IAE1E,OAAO,GAAG,EAAE;QACV,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE;YAAE,YAAY,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@jeanzorzetti/context-keeper",
3
+ "version": "0.1.0",
4
+ "description": "Context Keeper daemon — watches Claude Code transcripts, extracts decisions via Groq, updates CLAUDE.md",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "context-keeper": "dist/cli.js"
8
+ },
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "dev": "tsc --watch",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
21
+ "dependencies": {
22
+ "chokidar": "^3.6.0",
23
+ "groq-sdk": "^0.7.0",
24
+ "dotenv": "^16.4.5",
25
+ "simple-git": "^3.24.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^20.14.0",
29
+ "typescript": "^5.4.5",
30
+ "vitest": "^1.6.0"
31
+ },
32
+ "engines": {
33
+ "node": ">=20"
34
+ }
35
+ }