@sunilp-org/jam-cli 0.1.0 → 0.1.2
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 +431 -63
- 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/config.d.ts.map +1 -1
- package/dist/commands/config.js +2 -1
- package/dist/commands/config.js.map +1 -1
- 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 +39 -3
- 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/embedded.d.ts +20 -0
- package/dist/providers/embedded.d.ts.map +1 -0
- package/dist/providers/embedded.js +302 -0
- package/dist/providers/embedded.js.map +1 -0
- package/dist/providers/factory.d.ts.map +1 -1
- package/dist/providers/factory.js +25 -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 +6 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Critic agent — evaluates the main agent's answer for quality and relevance.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the naive keyword-matching `checkAnswerRelevance` with a real
|
|
5
|
+
* LLM-based evaluation. The critic runs as a separate, cheap LLM call
|
|
6
|
+
* with a focused system prompt.
|
|
7
|
+
*/
|
|
8
|
+
// ── Critic prompt ─────────────────────────────────────────────────────────────
|
|
9
|
+
const CRITIC_SYSTEM_PROMPT = `You are a strict answer quality evaluator for a code assistant.
|
|
10
|
+
|
|
11
|
+
You will receive:
|
|
12
|
+
1. The user's ORIGINAL QUESTION
|
|
13
|
+
2. The assistant's PROPOSED ANSWER
|
|
14
|
+
|
|
15
|
+
Evaluate the answer on these criteria:
|
|
16
|
+
- RELEVANCE: Does the answer address the user's specific question?
|
|
17
|
+
- SPECIFICITY: Does the answer reference specific files, line numbers, or code?
|
|
18
|
+
- ACCURACY: Does the answer appear technically correct (no obvious hallucinations)?
|
|
19
|
+
- COMPLETENESS: Does the answer cover the main aspects of the question?
|
|
20
|
+
- FORMAT: Is the answer in clean Markdown (not raw JSON, not gibberish)?
|
|
21
|
+
|
|
22
|
+
Respond in EXACTLY this format (no extra text):
|
|
23
|
+
PASS or FAIL
|
|
24
|
+
CONFIDENCE: 0.0-1.0
|
|
25
|
+
REASON: one sentence explanation
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
PASS
|
|
29
|
+
CONFIDENCE: 0.9
|
|
30
|
+
REASON: Answer correctly identifies the provider factory pattern in src/providers/factory.ts with specific line references.
|
|
31
|
+
|
|
32
|
+
FAIL
|
|
33
|
+
CONFIDENCE: 0.8
|
|
34
|
+
REASON: Answer describes the context.ts file but the user asked about LLM providers — completely off-topic.`;
|
|
35
|
+
// ── Critic execution ──────────────────────────────────────────────────────────
|
|
36
|
+
/**
|
|
37
|
+
* Run the critic agent to evaluate an answer.
|
|
38
|
+
*
|
|
39
|
+
* Uses a low temperature and short max tokens for fast, focused evaluation.
|
|
40
|
+
* Falls back to PASS if the critic call fails (non-blocking).
|
|
41
|
+
*/
|
|
42
|
+
export async function criticEvaluate(provider, question, answer, options = {}) {
|
|
43
|
+
// Skip critic for very short answers (handled by basic validation)
|
|
44
|
+
if (answer.trim().length < 30) {
|
|
45
|
+
return { pass: false, reason: 'Answer is too short to be useful.', confidence: 1.0 };
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const request = {
|
|
49
|
+
messages: [{
|
|
50
|
+
role: 'user',
|
|
51
|
+
content: [
|
|
52
|
+
'## Original Question',
|
|
53
|
+
'',
|
|
54
|
+
question,
|
|
55
|
+
'',
|
|
56
|
+
'## Proposed Answer',
|
|
57
|
+
'',
|
|
58
|
+
answer.slice(0, 3000), // Cap answer length sent to critic
|
|
59
|
+
].join('\n'),
|
|
60
|
+
}],
|
|
61
|
+
model: options.model,
|
|
62
|
+
temperature: 0.1,
|
|
63
|
+
maxTokens: 150,
|
|
64
|
+
systemPrompt: CRITIC_SYSTEM_PROMPT,
|
|
65
|
+
};
|
|
66
|
+
let response = '';
|
|
67
|
+
const stream = provider.streamCompletion(request);
|
|
68
|
+
for await (const chunk of stream) {
|
|
69
|
+
if (!chunk.done)
|
|
70
|
+
response += chunk.delta;
|
|
71
|
+
}
|
|
72
|
+
return parseCriticResponse(response.trim());
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Critic failure is non-fatal — default to pass
|
|
76
|
+
return { pass: true, reason: 'Critic evaluation failed, defaulting to pass.', confidence: 0.0 };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Parse the critic's structured response.
|
|
81
|
+
*/
|
|
82
|
+
function parseCriticResponse(text) {
|
|
83
|
+
const lines = text.split('\n').map(l => l.trim()).filter(Boolean);
|
|
84
|
+
if (lines.length === 0) {
|
|
85
|
+
return { pass: true, reason: 'Empty critic response.', confidence: 0.0 };
|
|
86
|
+
}
|
|
87
|
+
const firstLine = lines[0].toUpperCase();
|
|
88
|
+
const pass = firstLine.includes('PASS');
|
|
89
|
+
// Extract confidence
|
|
90
|
+
let confidence = 0.5;
|
|
91
|
+
const confLine = lines.find(l => l.toUpperCase().startsWith('CONFIDENCE'));
|
|
92
|
+
if (confLine) {
|
|
93
|
+
const match = confLine.match(/[\d.]+/);
|
|
94
|
+
if (match)
|
|
95
|
+
confidence = Math.min(1, Math.max(0, parseFloat(match[0])));
|
|
96
|
+
}
|
|
97
|
+
// Extract reason
|
|
98
|
+
let reason = 'No reason provided.';
|
|
99
|
+
const reasonLine = lines.find(l => l.toUpperCase().startsWith('REASON'));
|
|
100
|
+
if (reasonLine) {
|
|
101
|
+
reason = reasonLine.replace(/^REASON:\s*/i, '').trim();
|
|
102
|
+
}
|
|
103
|
+
return { pass, reason, confidence };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build a correction message from the critic's feedback.
|
|
107
|
+
* More specific than the generic buildCorrectionMessage.
|
|
108
|
+
*/
|
|
109
|
+
export function buildCriticCorrection(verdict, originalQuestion) {
|
|
110
|
+
return [
|
|
111
|
+
`[CRITIC FEEDBACK: Your answer was rejected.]`,
|
|
112
|
+
'',
|
|
113
|
+
`Reason: ${verdict.reason}`,
|
|
114
|
+
'',
|
|
115
|
+
`The user's original question was: "${originalQuestion}"`,
|
|
116
|
+
'',
|
|
117
|
+
'Provide a NEW answer that:',
|
|
118
|
+
'1. Directly addresses the question above',
|
|
119
|
+
'2. References specific file paths, line numbers, and code snippets',
|
|
120
|
+
'3. Is formatted in clean Markdown',
|
|
121
|
+
'4. Does NOT repeat your previous answer',
|
|
122
|
+
'',
|
|
123
|
+
'If you need more information, use tools to search and read code first.',
|
|
124
|
+
].join('\n');
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=critic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"critic.js","sourceRoot":"","sources":["../../src/utils/critic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
export interface SymbolEntry {
|
|
11
|
+
/** Symbol name (function, class, interface, variable name). */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Kind of symbol. */
|
|
14
|
+
kind: 'function' | 'class' | 'interface' | 'type' | 'const' | 'enum' | 'variable';
|
|
15
|
+
/** File path relative to workspace root. */
|
|
16
|
+
file: string;
|
|
17
|
+
/** 1-based line number where the symbol is defined. */
|
|
18
|
+
line: number;
|
|
19
|
+
}
|
|
20
|
+
export interface SymbolIndex {
|
|
21
|
+
/** When the index was built (ISO timestamp). */
|
|
22
|
+
builtAt: string;
|
|
23
|
+
/** Number of files scanned. */
|
|
24
|
+
filesScanned: number;
|
|
25
|
+
/** Total symbols found. */
|
|
26
|
+
totalSymbols: number;
|
|
27
|
+
/** All symbol entries. */
|
|
28
|
+
symbols: SymbolEntry[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a symbol index for the workspace.
|
|
32
|
+
* Scans all source files and extracts exported symbols.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildSymbolIndex(workspaceRoot: string): Promise<SymbolIndex>;
|
|
35
|
+
/**
|
|
36
|
+
* Load the cached symbol index from disk.
|
|
37
|
+
* Returns null if no index exists or it's too stale.
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadSymbolIndex(workspaceRoot: string, maxAge?: number): Promise<SymbolIndex | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Load or build the symbol index (builds if missing/stale).
|
|
42
|
+
*/
|
|
43
|
+
export declare function getOrBuildIndex(workspaceRoot: string): Promise<SymbolIndex>;
|
|
44
|
+
/**
|
|
45
|
+
* Search the symbol index for symbols matching a query.
|
|
46
|
+
* Returns matching symbols sorted by relevance.
|
|
47
|
+
*/
|
|
48
|
+
export declare function searchSymbols(index: SymbolIndex, query: string, maxResults?: number): SymbolEntry[];
|
|
49
|
+
/**
|
|
50
|
+
* Format symbol search results for injection into context.
|
|
51
|
+
*/
|
|
52
|
+
export declare function formatSymbolResults(symbols: SymbolEntry[]): string;
|
|
53
|
+
//# sourceMappingURL=index-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-builder.d.ts","sourceRoot":"","sources":["../../src/utils/index-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,sBAAsB;IACtB,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC;IAClF,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB;AAoJD;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA6ClF;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,MAAuB,GAC9B,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAa7B;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAIjF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,MAAW,GACtB,WAAW,EAAE,CAyBf;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,MAAM,CAUlE"}
|
|
@@ -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"}
|