@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 +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +77 -0
- package/dist/cli.js.map +1 -0
- package/dist/extractor.d.ts +27 -0
- package/dist/extractor.d.ts.map +1 -0
- package/dist/extractor.js +155 -0
- package/dist/extractor.js.map +1 -0
- package/dist/git.d.ts +7 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +66 -0
- package/dist/git.js.map +1 -0
- package/dist/index-writer.d.ts +29 -0
- package/dist/index-writer.d.ts.map +1 -0
- package/dist/index-writer.js +105 -0
- package/dist/index-writer.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/merger.d.ts +34 -0
- package/dist/merger.d.ts.map +1 -0
- package/dist/merger.js +241 -0
- package/dist/merger.js.map +1 -0
- package/dist/watcher.d.ts +7 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +87 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +35 -0
package/dist/cli.d.ts
ADDED
|
@@ -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
|
package/dist/cli.js.map
ADDED
|
@@ -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
|
package/dist/git.js.map
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|
package/dist/merger.d.ts
ADDED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,wCAoDC;AAUD,4CASC;AA4BD,8CA8CC;AA7LD,uCAAyB;AACzB,2CAA6B;AAE7B,MAAM,YAAY,GAAG,2BAA2B,CAAC;AACjD,MAAM,UAAU,GAAG,6BAA6B,CAAC;AACjD,MAAM,cAAc,GAAG,4CAA4C,CAAC;AAEpE,MAAM,cAAc,GAAG,uBAAuB,CAAC;AAE/C;;GAEG;AACH,SAAS,mBAAmB,CAAC,SAAmB,EAAE,SAAiB;IACjE,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpF,OAAO,CACL,GAAG,YAAY,mBAAmB,SAAS,SAAS;QACpD,GAAG,cAAc,MAAM;QACvB,GAAG,KAAK,MAAM;QACd,GAAG,UAAU,EAAE,CAChB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAAC,OAAe;IAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC/C,IAAI,QAAQ,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACrD,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/B,OAAO,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,cAAc,CAAC,QAAgB,EAAE,YAAsB;IACrE,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAE3C,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEtC,oDAAoD;IACpD,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,QAAQ,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,cAAc,GAAG,qBAAqB,CAAC,QAAQ,CAAC,CAAC;IAEvD,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,oDAAoD;QACpD,MAAM,OAAO,GAAG,mBAAmB,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC;QAC5E,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1D,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,GAAG,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3D,OAAO;IACT,CAAC;IAED,sDAAsD;IACtD,MAAM,qBAAqB,GAAG,cAAc;SACzC,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAE5C,gEAAgE;IAChE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAEtC,MAAM,gBAAgB,GAAG;QACvB,GAAG,qBAAqB;QACxB,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;KACjE,CAAC;IAEF,MAAM,cAAc,GAClB,GAAG,YAAY,mBAAmB,SAAS,SAAS;QACpD,GAAG,cAAc,MAAM;QACvB,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;QACpC,GAAG,UAAU,EAAE,CAAC;IAElB,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAC9B,IAAI,MAAM,CACR,GAAG,WAAW,CAAC,YAAY,CAAC,aAAa,WAAW,CAAC,UAAU,CAAC,EAAE,CACnE,EACD,cAAc,CACf,CAAC;IAEF,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACpD,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,UAAkB;IACjD,MAAM,IAAI,GAAG,WAAW,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;IACnD,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC;KAC7B,CAAC;IACF,kEAAkE;IAClE,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5D,OAAO,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC;QAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;QACpC,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,iBAAiB,CAAC,cAAsB;IACtD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC;IAE9D,oEAAoE;IACpE,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;QAC9C,+CAA+C;QAC/C,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;YAC3D,iDAAiD;YACjD,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;IAClC,CAAC;IAED,mFAAmF;IACnF,IAAI,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAC/C,MAAM,SAAS,GAAG,WAAW,GAAG,GAAG,CAAC;YAEpC,6DAA6D;YAC7D,gFAAgF;YAChF,MAAM,WAAW,GAAG;gBAClB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC;gBAC7B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,CAAC;gBAC5B,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC;gBAC3B,SAAS;aACV,CAAC;YAEF,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;gBAC/B,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxB,MAAM,KAAK,GAAG,yBAAyB,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;oBAC5D,IAAI,KAAK;wBAAE,OAAO,KAAK,CAAC;gBAC1B,CAAC;YACH,CAAC;YAED,8DAA8D;YAC9D,+DAA+D;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,yBAAyB,CAChC,WAAmB,EACnB,WAAmB,EACnB,KAAa;IAEb,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,wBAAwB;IAEpD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAErE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE;gBAAE,SAAS;YAEnC,MAAM,cAAc,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YAChD,MAAM,WAAW,GAAG,cAAc,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAExD,4EAA4E;YAC5E,IAAI,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;gBACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAEpD,yDAAyD;gBACzD,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;gBACtC,IAAI,OAAO,EAAE,CAAC;oBACZ,2CAA2C;oBAC3C,OAAO,OAAO,CAAC;gBACjB,CAAC;gBAED,iEAAiE;gBACjE,6DAA6D;gBAC7D,MAAM,KAAK,GAAG,yBAAyB,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;gBAC1E,IAAI,KAAK;oBAAE,OAAO,KAAK,CAAC;YAC1B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -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"}
|
package/dist/watcher.js
ADDED
|
@@ -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
|
+
}
|