@sunilp-org/jam-cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +304 -52
- package/dist/commands/ask.d.ts +4 -0
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +202 -10
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/commit.d.ts +12 -0
- package/dist/commands/commit.d.ts.map +1 -0
- package/dist/commands/commit.js +135 -0
- package/dist/commands/commit.js.map +1 -0
- package/dist/commands/context.d.ts +12 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +52 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/review.d.ts +25 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +117 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +199 -197
- package/dist/commands/run.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +3 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/index.js +63 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/base.d.ts +26 -0
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +17 -1
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/groq.d.ts +16 -0
- package/dist/providers/groq.d.ts.map +1 -0
- package/dist/providers/groq.js +23 -0
- package/dist/providers/groq.js.map +1 -0
- package/dist/providers/ollama.d.ts +6 -1
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +77 -4
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.d.ts +18 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +229 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/tools/all-tools.d.ts +18 -0
- package/dist/tools/all-tools.d.ts.map +1 -0
- package/dist/tools/all-tools.js +95 -0
- package/dist/tools/all-tools.js.map +1 -0
- package/dist/tools/apply_patch.js +1 -1
- package/dist/tools/apply_patch.js.map +1 -1
- package/dist/tools/context-tools.d.ts +14 -0
- package/dist/tools/context-tools.d.ts.map +1 -0
- package/dist/tools/context-tools.js +63 -0
- package/dist/tools/context-tools.js.map +1 -0
- package/dist/tools/git_diff.js +1 -1
- package/dist/tools/git_diff.js.map +1 -1
- package/dist/tools/git_status.js +1 -1
- package/dist/tools/git_status.js.map +1 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +2 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run_command.d.ts +8 -3
- package/dist/tools/run_command.d.ts.map +1 -1
- package/dist/tools/run_command.js +90 -3
- package/dist/tools/run_command.js.map +1 -1
- package/dist/ui/chat.d.ts.map +1 -1
- package/dist/ui/chat.js +173 -1
- package/dist/ui/chat.js.map +1 -1
- package/dist/ui/logo.d.ts.map +1 -1
- package/dist/ui/logo.js +5 -1
- package/dist/ui/logo.js.map +1 -1
- package/dist/utils/agent.d.ts +130 -0
- package/dist/utils/agent.d.ts.map +1 -0
- package/dist/utils/agent.js +449 -0
- package/dist/utils/agent.js.map +1 -0
- package/dist/utils/cache.d.ts +30 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +62 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/context.d.ts +38 -0
- package/dist/utils/context.d.ts.map +1 -0
- package/dist/utils/context.js +383 -0
- package/dist/utils/context.js.map +1 -0
- package/dist/utils/critic.d.ts +31 -0
- package/dist/utils/critic.d.ts.map +1 -0
- package/dist/utils/critic.js +126 -0
- package/dist/utils/critic.js.map +1 -0
- package/dist/utils/index-builder.d.ts +53 -0
- package/dist/utils/index-builder.d.ts.map +1 -0
- package/dist/utils/index-builder.js +241 -0
- package/dist/utils/index-builder.js.map +1 -0
- package/dist/utils/memory.d.ts +104 -0
- package/dist/utils/memory.d.ts.map +1 -0
- package/dist/utils/memory.js +215 -0
- package/dist/utils/memory.js.map +1 -0
- package/dist/utils/past-sessions.d.ts +31 -0
- package/dist/utils/past-sessions.d.ts.map +1 -0
- package/dist/utils/past-sessions.js +126 -0
- package/dist/utils/past-sessions.js.map +1 -0
- package/dist/utils/tokens.d.ts +53 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +138 -0
- package/dist/utils/tokens.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol index builder — lightweight file→symbol mapping for faster retrieval.
|
|
3
|
+
*
|
|
4
|
+
* Scans source files for exported symbols (functions, classes, interfaces,
|
|
5
|
+
* types, constants) and builds an index file at `.jam/symbol-index.json`.
|
|
6
|
+
*
|
|
7
|
+
* The index is used by the planner and enrichment stages to suggest specific
|
|
8
|
+
* files to the model based on symbol names, reducing wasted search rounds.
|
|
9
|
+
*/
|
|
10
|
+
import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
|
|
11
|
+
import { join, extname, relative } from 'node:path';
|
|
12
|
+
// ── File scanning ─────────────────────────────────────────────────────────────
|
|
13
|
+
const IGNORED_DIRS = new Set([
|
|
14
|
+
'node_modules', 'dist', 'build', '.git', '.jam', 'coverage',
|
|
15
|
+
'__pycache__', '.next', '.nuxt', 'target', 'out', '.venv', 'venv',
|
|
16
|
+
]);
|
|
17
|
+
const CODE_EXTENSIONS = new Set([
|
|
18
|
+
'.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java',
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* Recursively collect all source files in a workspace.
|
|
22
|
+
*/
|
|
23
|
+
async function collectSourceFiles(dir, rootDir, files = [], maxFiles = 500) {
|
|
24
|
+
if (files.length >= maxFiles)
|
|
25
|
+
return files;
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return files;
|
|
32
|
+
}
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (files.length >= maxFiles)
|
|
35
|
+
break;
|
|
36
|
+
const name = String(entry.name);
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
if (!IGNORED_DIRS.has(name) && !name.startsWith('.')) {
|
|
39
|
+
await collectSourceFiles(join(dir, name), rootDir, files, maxFiles);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (CODE_EXTENSIONS.has(extname(name))) {
|
|
43
|
+
files.push(join(dir, name));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return files;
|
|
47
|
+
}
|
|
48
|
+
// ── Symbol extraction (TypeScript / JavaScript) ───────────────────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Extract exported symbols from a TypeScript/JavaScript file using regex.
|
|
51
|
+
* This is intentionally simple — no AST parsing needed for an index.
|
|
52
|
+
*/
|
|
53
|
+
function extractTsSymbols(content, filePath) {
|
|
54
|
+
const symbols = [];
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
const line = lines[i];
|
|
58
|
+
const lineNum = i + 1;
|
|
59
|
+
// export function foo(
|
|
60
|
+
const funcMatch = line.match(/export\s+(?:async\s+)?function\s+(\w+)/);
|
|
61
|
+
if (funcMatch) {
|
|
62
|
+
symbols.push({ name: funcMatch[1], kind: 'function', file: filePath, line: lineNum });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// export class Foo
|
|
66
|
+
const classMatch = line.match(/export\s+(?:abstract\s+)?class\s+(\w+)/);
|
|
67
|
+
if (classMatch) {
|
|
68
|
+
symbols.push({ name: classMatch[1], kind: 'class', file: filePath, line: lineNum });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// export interface Foo
|
|
72
|
+
const ifaceMatch = line.match(/export\s+interface\s+(\w+)/);
|
|
73
|
+
if (ifaceMatch) {
|
|
74
|
+
symbols.push({ name: ifaceMatch[1], kind: 'interface', file: filePath, line: lineNum });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// export type Foo =
|
|
78
|
+
const typeMatch = line.match(/export\s+type\s+(\w+)/);
|
|
79
|
+
if (typeMatch) {
|
|
80
|
+
symbols.push({ name: typeMatch[1], kind: 'type', file: filePath, line: lineNum });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// export enum Foo
|
|
84
|
+
const enumMatch = line.match(/export\s+enum\s+(\w+)/);
|
|
85
|
+
if (enumMatch) {
|
|
86
|
+
symbols.push({ name: enumMatch[1], kind: 'enum', file: filePath, line: lineNum });
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
// export const FOO = / export const foo =
|
|
90
|
+
const constMatch = line.match(/export\s+const\s+(\w+)/);
|
|
91
|
+
if (constMatch) {
|
|
92
|
+
symbols.push({ name: constMatch[1], kind: 'const', file: filePath, line: lineNum });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Non-exported but significant: class Foo
|
|
96
|
+
const plainClassMatch = line.match(/^(?:abstract\s+)?class\s+(\w+)/);
|
|
97
|
+
if (plainClassMatch && !line.includes('export')) {
|
|
98
|
+
symbols.push({ name: plainClassMatch[1], kind: 'class', file: filePath, line: lineNum });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return symbols;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Extract symbols from a Python file.
|
|
106
|
+
*/
|
|
107
|
+
function extractPySymbols(content, filePath) {
|
|
108
|
+
const symbols = [];
|
|
109
|
+
const lines = content.split('\n');
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const line = lines[i];
|
|
112
|
+
const lineNum = i + 1;
|
|
113
|
+
// def foo( — top-level only (no leading whitespace)
|
|
114
|
+
const funcMatch = line.match(/^(?:async\s+)?def\s+(\w+)/);
|
|
115
|
+
if (funcMatch) {
|
|
116
|
+
symbols.push({ name: funcMatch[1], kind: 'function', file: filePath, line: lineNum });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// class Foo
|
|
120
|
+
const classMatch = line.match(/^class\s+(\w+)/);
|
|
121
|
+
if (classMatch) {
|
|
122
|
+
symbols.push({ name: classMatch[1], kind: 'class', file: filePath, line: lineNum });
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return symbols;
|
|
127
|
+
}
|
|
128
|
+
// ── Index builder ─────────────────────────────────────────────────────────────
|
|
129
|
+
const INDEX_DIR = '.jam';
|
|
130
|
+
const INDEX_FILE = 'symbol-index.json';
|
|
131
|
+
/**
|
|
132
|
+
* Build a symbol index for the workspace.
|
|
133
|
+
* Scans all source files and extracts exported symbols.
|
|
134
|
+
*/
|
|
135
|
+
export async function buildSymbolIndex(workspaceRoot) {
|
|
136
|
+
const files = await collectSourceFiles(workspaceRoot, workspaceRoot);
|
|
137
|
+
const allSymbols = [];
|
|
138
|
+
for (const absPath of files) {
|
|
139
|
+
let content;
|
|
140
|
+
try {
|
|
141
|
+
content = await readFile(absPath, 'utf-8');
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const relPath = relative(workspaceRoot, absPath);
|
|
147
|
+
const ext = extname(absPath);
|
|
148
|
+
let symbols;
|
|
149
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
150
|
+
symbols = extractTsSymbols(content, relPath);
|
|
151
|
+
}
|
|
152
|
+
else if (ext === '.py') {
|
|
153
|
+
symbols = extractPySymbols(content, relPath);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Skip unsupported languages for now
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
allSymbols.push(...symbols);
|
|
160
|
+
}
|
|
161
|
+
const index = {
|
|
162
|
+
builtAt: new Date().toISOString(),
|
|
163
|
+
filesScanned: files.length,
|
|
164
|
+
totalSymbols: allSymbols.length,
|
|
165
|
+
symbols: allSymbols,
|
|
166
|
+
};
|
|
167
|
+
// Write index to disk
|
|
168
|
+
const indexDir = join(workspaceRoot, INDEX_DIR);
|
|
169
|
+
await mkdir(indexDir, { recursive: true });
|
|
170
|
+
await writeFile(join(indexDir, INDEX_FILE), JSON.stringify(index, null, 2), 'utf-8');
|
|
171
|
+
return index;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Load the cached symbol index from disk.
|
|
175
|
+
* Returns null if no index exists or it's too stale.
|
|
176
|
+
*/
|
|
177
|
+
export async function loadSymbolIndex(workspaceRoot, maxAge = 30 * 60 * 1000) {
|
|
178
|
+
const indexPath = join(workspaceRoot, INDEX_DIR, INDEX_FILE);
|
|
179
|
+
try {
|
|
180
|
+
const fileStat = await stat(indexPath);
|
|
181
|
+
const age = Date.now() - fileStat.mtimeMs;
|
|
182
|
+
if (age > maxAge)
|
|
183
|
+
return null; // Too stale
|
|
184
|
+
const raw = await readFile(indexPath, 'utf-8');
|
|
185
|
+
return JSON.parse(raw);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Load or build the symbol index (builds if missing/stale).
|
|
193
|
+
*/
|
|
194
|
+
export async function getOrBuildIndex(workspaceRoot) {
|
|
195
|
+
const cached = await loadSymbolIndex(workspaceRoot);
|
|
196
|
+
if (cached)
|
|
197
|
+
return cached;
|
|
198
|
+
return buildSymbolIndex(workspaceRoot);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Search the symbol index for symbols matching a query.
|
|
202
|
+
* Returns matching symbols sorted by relevance.
|
|
203
|
+
*/
|
|
204
|
+
export function searchSymbols(index, query, maxResults = 20) {
|
|
205
|
+
const lower = query.toLowerCase();
|
|
206
|
+
const terms = lower.split(/\s+/).filter(t => t.length > 1);
|
|
207
|
+
if (terms.length === 0)
|
|
208
|
+
return [];
|
|
209
|
+
// Score each symbol
|
|
210
|
+
const scored = index.symbols.map(sym => {
|
|
211
|
+
const nameLower = sym.name.toLowerCase();
|
|
212
|
+
let score = 0;
|
|
213
|
+
for (const term of terms) {
|
|
214
|
+
if (nameLower === term)
|
|
215
|
+
score += 10; // Exact match
|
|
216
|
+
else if (nameLower.includes(term))
|
|
217
|
+
score += 5; // Partial match
|
|
218
|
+
else if (sym.file.toLowerCase().includes(term))
|
|
219
|
+
score += 2; // File match
|
|
220
|
+
}
|
|
221
|
+
return { sym, score };
|
|
222
|
+
});
|
|
223
|
+
return scored
|
|
224
|
+
.filter(s => s.score > 0)
|
|
225
|
+
.sort((a, b) => b.score - a.score)
|
|
226
|
+
.slice(0, maxResults)
|
|
227
|
+
.map(s => s.sym);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Format symbol search results for injection into context.
|
|
231
|
+
*/
|
|
232
|
+
export function formatSymbolResults(symbols) {
|
|
233
|
+
if (symbols.length === 0)
|
|
234
|
+
return '';
|
|
235
|
+
const lines = ['**Relevant symbols found in the codebase:**', ''];
|
|
236
|
+
for (const sym of symbols) {
|
|
237
|
+
lines.push(`- \`${sym.name}\` (${sym.kind}) → \`${sym.file}:${sym.line}\``);
|
|
238
|
+
}
|
|
239
|
+
return lines.join('\n');
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=index-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-builder.js","sourceRoot":"","sources":["../../src/utils/index-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working memory management for the agentic loop.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - **Context compaction** — summarize old tool results when approaching the
|
|
6
|
+
* context window limit.
|
|
7
|
+
* - **Scratchpad** — periodic "what have I learned" prompts that create a
|
|
8
|
+
* running working memory the model can reference.
|
|
9
|
+
* - **Tool result capping** — truncate oversized tool outputs before injection.
|
|
10
|
+
*
|
|
11
|
+
* The core idea: instead of feeding the LLM an ever-growing message array,
|
|
12
|
+
* we periodically compress old rounds into a compact summary and carry only
|
|
13
|
+
* recent rounds + the summary forward.
|
|
14
|
+
*/
|
|
15
|
+
import type { Message } from '../providers/base.js';
|
|
16
|
+
import type { ProviderAdapter } from '../providers/base.js';
|
|
17
|
+
/** After this many tool rounds, inject a scratchpad prompt. */
|
|
18
|
+
export declare const SCRATCHPAD_INTERVAL = 3;
|
|
19
|
+
/** Max token budget for a single tool result injection. */
|
|
20
|
+
export declare const MAX_TOOL_RESULT_TOKENS = 1500;
|
|
21
|
+
/**
|
|
22
|
+
* Cap a tool output to prevent oversized injections into the message array.
|
|
23
|
+
* Call this BEFORE pushing the tool result into `messages`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function capToolResult(toolName: string, output: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Check if it's time to inject a scratchpad prompt.
|
|
28
|
+
* Returns true every SCRATCHPAD_INTERVAL tool rounds.
|
|
29
|
+
*/
|
|
30
|
+
export declare function shouldInjectScratchpad(toolRound: number): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Build the scratchpad injection message.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildScratchpadPrompt(): Message;
|
|
35
|
+
/**
|
|
36
|
+
* Check whether the message array needs compaction.
|
|
37
|
+
*/
|
|
38
|
+
export declare function needsCompaction(messages: Message[], systemPrompt: string | undefined, model?: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Compact the message array by summarizing old tool rounds.
|
|
41
|
+
*
|
|
42
|
+
* Strategy:
|
|
43
|
+
* 1. Keep the first message (original user query) and the last N messages (recent context).
|
|
44
|
+
* 2. Summarize everything in between via a cheap LLM call.
|
|
45
|
+
* 3. Replace the middle messages with a single summary message.
|
|
46
|
+
*
|
|
47
|
+
* Falls back to a simple truncation if the LLM call fails.
|
|
48
|
+
*/
|
|
49
|
+
export declare function compactMessages(messages: Message[], provider: ProviderAdapter, options: {
|
|
50
|
+
model?: string;
|
|
51
|
+
keepRecent?: number;
|
|
52
|
+
}): Promise<Message[]>;
|
|
53
|
+
/**
|
|
54
|
+
* WorkingMemory manages the conversation's context budget throughout
|
|
55
|
+
* the agentic loop. Commands use it like:
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* const memory = new WorkingMemory(provider, model, systemPrompt);
|
|
59
|
+
* // In the tool loop:
|
|
60
|
+
* memory.addToolResult(toolName, output, messages);
|
|
61
|
+
* if (memory.shouldScratchpad(round)) messages.push(memory.scratchpadPrompt());
|
|
62
|
+
* if (memory.shouldCompact(messages)) messages = await memory.compact(messages);
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare class WorkingMemory {
|
|
66
|
+
private provider;
|
|
67
|
+
private model?;
|
|
68
|
+
private systemPrompt?;
|
|
69
|
+
private readFiles;
|
|
70
|
+
private searchQueries;
|
|
71
|
+
private factsLearned;
|
|
72
|
+
constructor(provider: ProviderAdapter, model?: string, systemPrompt?: string);
|
|
73
|
+
/**
|
|
74
|
+
* Process and cap a tool result before injection into messages.
|
|
75
|
+
* Also tracks which files/searches have been done.
|
|
76
|
+
*/
|
|
77
|
+
processToolResult(toolName: string, args: Record<string, unknown>, output: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* Check if we should inject a scratchpad prompt at this round.
|
|
80
|
+
*/
|
|
81
|
+
shouldScratchpad(round: number): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Build the scratchpad prompt.
|
|
84
|
+
*/
|
|
85
|
+
scratchpadPrompt(): Message;
|
|
86
|
+
/**
|
|
87
|
+
* Check if messages need compaction.
|
|
88
|
+
*/
|
|
89
|
+
shouldCompact(messages: Message[]): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Compact the messages array.
|
|
92
|
+
*/
|
|
93
|
+
compact(messages: Message[]): Promise<Message[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Get a summary of what has been accessed (for diagnostics / JAM.md updates).
|
|
96
|
+
*/
|
|
97
|
+
getAccessLog(): {
|
|
98
|
+
readFiles: string[];
|
|
99
|
+
searchQueries: string[];
|
|
100
|
+
};
|
|
101
|
+
/** Reset for a new turn (multi-turn chat). */
|
|
102
|
+
reset(): void;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/utils/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAQ5D,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAKrC,2DAA2D;AAC3D,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAO3C;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtE;AAcD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAID;;GAEG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,OAAO,EAAE,EACnB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAGT;AAgBD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,OAAO,EAAE,EACnB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,OAAO,CAAC,OAAO,EAAE,CAAC,CAuDpB;AAID;;;;;;;;;;;GAWG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,KAAK,CAAC,CAAS;IACvB,OAAO,CAAC,YAAY,CAAC,CAAS;IAC9B,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,aAAa,CAA0B;IAC/C,OAAO,CAAC,YAAY,CAAgB;gBAExB,QAAQ,EAAE,eAAe,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM;IAM5E;;;OAGG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAa1F;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIxC;;OAEG;IACH,gBAAgB,IAAI,OAAO;IAI3B;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO;IAI3C;;OAEG;IACG,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAItD;;OAEG;IACH,YAAY,IAAI;QAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QAAC,aAAa,EAAE,MAAM,EAAE,CAAA;KAAE;IAOhE,8CAA8C;IAC9C,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working memory management for the agentic loop.
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - **Context compaction** — summarize old tool results when approaching the
|
|
6
|
+
* context window limit.
|
|
7
|
+
* - **Scratchpad** — periodic "what have I learned" prompts that create a
|
|
8
|
+
* running working memory the model can reference.
|
|
9
|
+
* - **Tool result capping** — truncate oversized tool outputs before injection.
|
|
10
|
+
*
|
|
11
|
+
* The core idea: instead of feeding the LLM an ever-growing message array,
|
|
12
|
+
* we periodically compress old rounds into a compact summary and carry only
|
|
13
|
+
* recent rounds + the summary forward.
|
|
14
|
+
*/
|
|
15
|
+
import { checkBudget, truncateToolOutput, } from './tokens.js';
|
|
16
|
+
// ── Configuration ─────────────────────────────────────────────────────────────
|
|
17
|
+
/** After this many tool rounds, inject a scratchpad prompt. */
|
|
18
|
+
export const SCRATCHPAD_INTERVAL = 3;
|
|
19
|
+
/** When messages exceed this fraction of the context budget, compact. */
|
|
20
|
+
const COMPACTION_THRESHOLD = 0.70;
|
|
21
|
+
/** Max token budget for a single tool result injection. */
|
|
22
|
+
export const MAX_TOOL_RESULT_TOKENS = 1500;
|
|
23
|
+
/** Max token budget for compacted summary. */
|
|
24
|
+
const _MAX_SUMMARY_TOKENS = 800;
|
|
25
|
+
// ── Tool result capping (P0-2) ────────────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Cap a tool output to prevent oversized injections into the message array.
|
|
28
|
+
* Call this BEFORE pushing the tool result into `messages`.
|
|
29
|
+
*/
|
|
30
|
+
export function capToolResult(toolName, output) {
|
|
31
|
+
return truncateToolOutput(toolName, output, MAX_TOOL_RESULT_TOKENS);
|
|
32
|
+
}
|
|
33
|
+
// ── Scratchpad (P1-4) ────────────────────────────────────────────────────────
|
|
34
|
+
const SCRATCHPAD_PROMPT = `[WORKING MEMORY CHECKPOINT]
|
|
35
|
+
|
|
36
|
+
Pause and organize what you have learned so far. Write a brief, structured note:
|
|
37
|
+
|
|
38
|
+
1. **Files examined**: List every file path you have read or searched.
|
|
39
|
+
2. **Key findings**: List the most important facts you found (function names, patterns, locations).
|
|
40
|
+
3. **Still needed**: What information do you still need to answer the user's question?
|
|
41
|
+
|
|
42
|
+
Keep this under 200 words. This note will stay in your context as working memory.`;
|
|
43
|
+
/**
|
|
44
|
+
* Check if it's time to inject a scratchpad prompt.
|
|
45
|
+
* Returns true every SCRATCHPAD_INTERVAL tool rounds.
|
|
46
|
+
*/
|
|
47
|
+
export function shouldInjectScratchpad(toolRound) {
|
|
48
|
+
return toolRound > 0 && toolRound % SCRATCHPAD_INTERVAL === 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the scratchpad injection message.
|
|
52
|
+
*/
|
|
53
|
+
export function buildScratchpadPrompt() {
|
|
54
|
+
return { role: 'user', content: SCRATCHPAD_PROMPT };
|
|
55
|
+
}
|
|
56
|
+
// ── Context compaction / summarization (P0-3) ─────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Check whether the message array needs compaction.
|
|
59
|
+
*/
|
|
60
|
+
export function needsCompaction(messages, systemPrompt, model) {
|
|
61
|
+
const { budget, currentTokens } = checkBudget(messages, systemPrompt, model);
|
|
62
|
+
return currentTokens > budget * COMPACTION_THRESHOLD;
|
|
63
|
+
}
|
|
64
|
+
const SUMMARIZER_SYSTEM_PROMPT = `You are a context summarizer for a code assistant. You will receive a conversation between a user and an assistant that includes tool calls and their results.
|
|
65
|
+
|
|
66
|
+
Your job is to produce a COMPACT summary of all the information gathered so far. Include:
|
|
67
|
+
1. Files that were examined (paths only)
|
|
68
|
+
2. Key code facts discovered (function names, class names, patterns, important line numbers)
|
|
69
|
+
3. Any errors or dead-ends encountered
|
|
70
|
+
|
|
71
|
+
Rules:
|
|
72
|
+
- Be extremely concise — bullet points only
|
|
73
|
+
- Include file paths and line numbers where relevant
|
|
74
|
+
- Do NOT include opinions or analysis — just facts
|
|
75
|
+
- Do NOT include the full contents of files — just what was found
|
|
76
|
+
- Stay under 300 words`;
|
|
77
|
+
/**
|
|
78
|
+
* Compact the message array by summarizing old tool rounds.
|
|
79
|
+
*
|
|
80
|
+
* Strategy:
|
|
81
|
+
* 1. Keep the first message (original user query) and the last N messages (recent context).
|
|
82
|
+
* 2. Summarize everything in between via a cheap LLM call.
|
|
83
|
+
* 3. Replace the middle messages with a single summary message.
|
|
84
|
+
*
|
|
85
|
+
* Falls back to a simple truncation if the LLM call fails.
|
|
86
|
+
*/
|
|
87
|
+
export async function compactMessages(messages, provider, options) {
|
|
88
|
+
const keepRecent = options.keepRecent ?? 6; // Keep last 6 messages (3 rounds)
|
|
89
|
+
// Not enough messages to compact
|
|
90
|
+
if (messages.length <= keepRecent + 2)
|
|
91
|
+
return messages;
|
|
92
|
+
const firstMessage = messages[0];
|
|
93
|
+
const middleMessages = messages.slice(1, -keepRecent);
|
|
94
|
+
const recentMessages = messages.slice(-keepRecent);
|
|
95
|
+
// Build a condensed representation of middle messages for the summarizer
|
|
96
|
+
const middleText = middleMessages
|
|
97
|
+
.map((m) => `[${m.role}] ${m.content.slice(0, 500)}${m.content.length > 500 ? '…' : ''}`)
|
|
98
|
+
.join('\n---\n');
|
|
99
|
+
// Try to summarize via LLM
|
|
100
|
+
try {
|
|
101
|
+
const summaryRequest = {
|
|
102
|
+
messages: [{
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: `Summarize the following conversation context:\n\n${middleText}`,
|
|
105
|
+
}],
|
|
106
|
+
model: options.model,
|
|
107
|
+
temperature: 0.1, // Very focused
|
|
108
|
+
maxTokens: 500,
|
|
109
|
+
systemPrompt: SUMMARIZER_SYSTEM_PROMPT,
|
|
110
|
+
};
|
|
111
|
+
let summary = '';
|
|
112
|
+
const stream = provider.streamCompletion(summaryRequest);
|
|
113
|
+
for await (const chunk of stream) {
|
|
114
|
+
if (!chunk.done)
|
|
115
|
+
summary += chunk.delta;
|
|
116
|
+
}
|
|
117
|
+
const trimmedSummary = summary.trim();
|
|
118
|
+
if (trimmedSummary.length > 20) {
|
|
119
|
+
// Successfully summarized — replace middle with summary
|
|
120
|
+
const summaryMessage = {
|
|
121
|
+
role: 'user',
|
|
122
|
+
content: `[CONTEXT SUMMARY — earlier tool results compressed]\n\n${trimmedSummary}\n\n[End of summary — recent context follows]`,
|
|
123
|
+
};
|
|
124
|
+
return [firstMessage, summaryMessage, ...recentMessages];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Summarization failed — fall back to simple truncation
|
|
129
|
+
}
|
|
130
|
+
// Fallback: just keep first + truncated middle + recent
|
|
131
|
+
const fallbackSummary = {
|
|
132
|
+
role: 'user',
|
|
133
|
+
content: `[CONTEXT NOTE: ${middleMessages.length} earlier messages were compressed to save context space. Key info may need to be re-discovered if not in recent messages.]`,
|
|
134
|
+
};
|
|
135
|
+
return [firstMessage, fallbackSummary, ...recentMessages];
|
|
136
|
+
}
|
|
137
|
+
// ── Combined memory manager ───────────────────────────────────────────────────
|
|
138
|
+
/**
|
|
139
|
+
* WorkingMemory manages the conversation's context budget throughout
|
|
140
|
+
* the agentic loop. Commands use it like:
|
|
141
|
+
*
|
|
142
|
+
* ```ts
|
|
143
|
+
* const memory = new WorkingMemory(provider, model, systemPrompt);
|
|
144
|
+
* // In the tool loop:
|
|
145
|
+
* memory.addToolResult(toolName, output, messages);
|
|
146
|
+
* if (memory.shouldScratchpad(round)) messages.push(memory.scratchpadPrompt());
|
|
147
|
+
* if (memory.shouldCompact(messages)) messages = await memory.compact(messages);
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export class WorkingMemory {
|
|
151
|
+
provider;
|
|
152
|
+
model;
|
|
153
|
+
systemPrompt;
|
|
154
|
+
readFiles = new Set();
|
|
155
|
+
searchQueries = new Set();
|
|
156
|
+
factsLearned = [];
|
|
157
|
+
constructor(provider, model, systemPrompt) {
|
|
158
|
+
this.provider = provider;
|
|
159
|
+
this.model = model;
|
|
160
|
+
this.systemPrompt = systemPrompt;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Process and cap a tool result before injection into messages.
|
|
164
|
+
* Also tracks which files/searches have been done.
|
|
165
|
+
*/
|
|
166
|
+
processToolResult(toolName, args, output) {
|
|
167
|
+
// Track what we've accessed
|
|
168
|
+
if (toolName === 'read_file' && args['path']) {
|
|
169
|
+
this.readFiles.add(String(args['path']));
|
|
170
|
+
}
|
|
171
|
+
if (toolName === 'search_text' && args['query']) {
|
|
172
|
+
this.searchQueries.add(String(args['query']));
|
|
173
|
+
}
|
|
174
|
+
// Cap the output
|
|
175
|
+
return capToolResult(toolName, output);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Check if we should inject a scratchpad prompt at this round.
|
|
179
|
+
*/
|
|
180
|
+
shouldScratchpad(round) {
|
|
181
|
+
return shouldInjectScratchpad(round);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Build the scratchpad prompt.
|
|
185
|
+
*/
|
|
186
|
+
scratchpadPrompt() {
|
|
187
|
+
return buildScratchpadPrompt();
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check if messages need compaction.
|
|
191
|
+
*/
|
|
192
|
+
shouldCompact(messages) {
|
|
193
|
+
return needsCompaction(messages, this.systemPrompt, this.model);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Compact the messages array.
|
|
197
|
+
*/
|
|
198
|
+
async compact(messages) {
|
|
199
|
+
return compactMessages(messages, this.provider, { model: this.model });
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get a summary of what has been accessed (for diagnostics / JAM.md updates).
|
|
203
|
+
*/
|
|
204
|
+
getAccessLog() {
|
|
205
|
+
return {
|
|
206
|
+
readFiles: [...this.readFiles],
|
|
207
|
+
searchQueries: [...this.searchQueries],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** Reset for a new turn (multi-turn chat). */
|
|
211
|
+
reset() {
|
|
212
|
+
// Don't reset readFiles/searchQueries — they're session-level
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/utils/memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Past session search — find relevant Q&A from previous sessions.
|
|
3
|
+
*
|
|
4
|
+
* Uses a simple TF-IDF-like keyword overlap to find past conversations
|
|
5
|
+
* that are relevant to the current question. No vector embeddings needed —
|
|
6
|
+
* this is a pragmatic approach for local-first CLI tools.
|
|
7
|
+
*/
|
|
8
|
+
export interface PastExchange {
|
|
9
|
+
/** The user's question from the past session. */
|
|
10
|
+
question: string;
|
|
11
|
+
/** The assistant's answer from the past session. */
|
|
12
|
+
answer: string;
|
|
13
|
+
/** The session name / id. */
|
|
14
|
+
sessionId: string;
|
|
15
|
+
/** Relevance score (0–1). */
|
|
16
|
+
score: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Search past sessions for Q&A exchanges relevant to the current question.
|
|
20
|
+
*
|
|
21
|
+
* @param question The user's current question.
|
|
22
|
+
* @param workspaceRoot Current workspace root (to scope sessions).
|
|
23
|
+
* @param maxResults Maximum exchanges to return.
|
|
24
|
+
* @param minScore Minimum relevance score to include.
|
|
25
|
+
*/
|
|
26
|
+
export declare function searchPastSessions(question: string, workspaceRoot: string, maxResults?: number, minScore?: number): Promise<PastExchange[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Format past exchanges as context to inject into the system prompt or messages.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatPastExchanges(exchanges: PastExchange[]): string;
|
|
31
|
+
//# sourceMappingURL=past-sessions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"past-sessions.d.ts","sourceRoot":"","sources":["../../src/utils/past-sessions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;CACf;AAyCD;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,UAAU,GAAE,MAAU,EACtB,QAAQ,GAAE,MAAa,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CAwDzB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CAqBrE"}
|