@stan-chen/simple-cli 0.2.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.
Files changed (87) hide show
  1. package/README.md +287 -0
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.js +259 -0
  4. package/dist/commands/add.d.ts +9 -0
  5. package/dist/commands/add.js +50 -0
  6. package/dist/commands/git/commit.d.ts +12 -0
  7. package/dist/commands/git/commit.js +97 -0
  8. package/dist/commands/git/status.d.ts +6 -0
  9. package/dist/commands/git/status.js +42 -0
  10. package/dist/commands/index.d.ts +16 -0
  11. package/dist/commands/index.js +376 -0
  12. package/dist/commands/mcp/status.d.ts +6 -0
  13. package/dist/commands/mcp/status.js +31 -0
  14. package/dist/commands/swarm.d.ts +36 -0
  15. package/dist/commands/swarm.js +236 -0
  16. package/dist/commands.d.ts +32 -0
  17. package/dist/commands.js +427 -0
  18. package/dist/context.d.ts +116 -0
  19. package/dist/context.js +327 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +109 -0
  22. package/dist/lib/agent.d.ts +98 -0
  23. package/dist/lib/agent.js +281 -0
  24. package/dist/lib/editor.d.ts +74 -0
  25. package/dist/lib/editor.js +441 -0
  26. package/dist/lib/git.d.ts +164 -0
  27. package/dist/lib/git.js +351 -0
  28. package/dist/lib/ui.d.ts +159 -0
  29. package/dist/lib/ui.js +252 -0
  30. package/dist/mcp/client.d.ts +22 -0
  31. package/dist/mcp/client.js +81 -0
  32. package/dist/mcp/manager.d.ts +186 -0
  33. package/dist/mcp/manager.js +442 -0
  34. package/dist/prompts/provider.d.ts +22 -0
  35. package/dist/prompts/provider.js +78 -0
  36. package/dist/providers/index.d.ts +15 -0
  37. package/dist/providers/index.js +82 -0
  38. package/dist/providers/multi.d.ts +11 -0
  39. package/dist/providers/multi.js +28 -0
  40. package/dist/registry.d.ts +24 -0
  41. package/dist/registry.js +379 -0
  42. package/dist/repoMap.d.ts +5 -0
  43. package/dist/repoMap.js +79 -0
  44. package/dist/router.d.ts +41 -0
  45. package/dist/router.js +108 -0
  46. package/dist/skills.d.ts +25 -0
  47. package/dist/skills.js +288 -0
  48. package/dist/swarm/coordinator.d.ts +86 -0
  49. package/dist/swarm/coordinator.js +257 -0
  50. package/dist/swarm/index.d.ts +28 -0
  51. package/dist/swarm/index.js +29 -0
  52. package/dist/swarm/task.d.ts +104 -0
  53. package/dist/swarm/task.js +221 -0
  54. package/dist/swarm/types.d.ts +132 -0
  55. package/dist/swarm/types.js +37 -0
  56. package/dist/swarm/worker.d.ts +107 -0
  57. package/dist/swarm/worker.js +299 -0
  58. package/dist/tools/analyzeFile.d.ts +16 -0
  59. package/dist/tools/analyzeFile.js +43 -0
  60. package/dist/tools/git.d.ts +40 -0
  61. package/dist/tools/git.js +236 -0
  62. package/dist/tools/glob.d.ts +34 -0
  63. package/dist/tools/glob.js +165 -0
  64. package/dist/tools/grep.d.ts +53 -0
  65. package/dist/tools/grep.js +296 -0
  66. package/dist/tools/linter.d.ts +35 -0
  67. package/dist/tools/linter.js +349 -0
  68. package/dist/tools/listDir.d.ts +29 -0
  69. package/dist/tools/listDir.js +50 -0
  70. package/dist/tools/memory.d.ts +34 -0
  71. package/dist/tools/memory.js +215 -0
  72. package/dist/tools/readFiles.d.ts +25 -0
  73. package/dist/tools/readFiles.js +31 -0
  74. package/dist/tools/reloadTools.d.ts +11 -0
  75. package/dist/tools/reloadTools.js +22 -0
  76. package/dist/tools/runCommand.d.ts +32 -0
  77. package/dist/tools/runCommand.js +79 -0
  78. package/dist/tools/scraper.d.ts +31 -0
  79. package/dist/tools/scraper.js +211 -0
  80. package/dist/tools/writeFiles.d.ts +63 -0
  81. package/dist/tools/writeFiles.js +87 -0
  82. package/dist/ui/server.d.ts +5 -0
  83. package/dist/ui/server.js +74 -0
  84. package/dist/watcher.d.ts +35 -0
  85. package/dist/watcher.js +164 -0
  86. package/docs/assets/logo.jpeg +0 -0
  87. package/package.json +78 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Context Manager - Manages conversation context and file state
3
+ * Based on Aider's coder.py and GeminiCLI's context management
4
+ * Uses gpt-tokenizer for accurate token counting (with fallback)
5
+ */
6
+ import { type Skill } from './skills.js';
7
+ import { type Tool } from './registry.js';
8
+ export interface Message {
9
+ role: 'user' | 'assistant' | 'system';
10
+ content: string;
11
+ timestamp?: number;
12
+ }
13
+ export interface ContextState {
14
+ cwd: string;
15
+ activeFiles: Set<string>;
16
+ readOnlyFiles: Set<string>;
17
+ history: Message[];
18
+ skill: Skill;
19
+ tokenEstimate: number;
20
+ }
21
+ /**
22
+ * Synchronous token count (uses cached encoder or estimation)
23
+ */
24
+ declare function countTokens(text: string): number;
25
+ /**
26
+ * ContextManager class
27
+ * Manages the conversation context, file state, and system prompts
28
+ */
29
+ export declare class ContextManager {
30
+ private cwd;
31
+ private activeFiles;
32
+ private readOnlyFiles;
33
+ private history;
34
+ private skill;
35
+ private tools;
36
+ private repoMapCache;
37
+ private repoMapTimestamp;
38
+ constructor(cwd?: string);
39
+ /**
40
+ * Initialize the context manager
41
+ */
42
+ initialize(): Promise<void>;
43
+ /**
44
+ * Add a file to active context
45
+ */
46
+ addFile(path: string, readOnly?: boolean): boolean;
47
+ /**
48
+ * Remove a file from context
49
+ */
50
+ removeFile(path: string): boolean;
51
+ /**
52
+ * Add a message to history
53
+ */
54
+ addMessage(role: Message['role'], content: string): void;
55
+ /**
56
+ * Clear conversation history
57
+ */
58
+ clearHistory(): void;
59
+ /**
60
+ * Get all files in context
61
+ */
62
+ getFiles(): {
63
+ active: string[];
64
+ readOnly: string[];
65
+ };
66
+ /**
67
+ * Read all files in context
68
+ */
69
+ getFileContents(): Promise<Map<string, string>>;
70
+ /**
71
+ * Set the active skill
72
+ */
73
+ setSkill(skill: Skill): void;
74
+ /**
75
+ * Get the current skill
76
+ */
77
+ getSkill(): Skill;
78
+ /**
79
+ * Refresh the repository map
80
+ */
81
+ refreshRepoMap(): Promise<string>;
82
+ /**
83
+ * Build the system prompt
84
+ */
85
+ buildSystemPrompt(): Promise<string>;
86
+ /**
87
+ * Build messages for LLM
88
+ */
89
+ buildMessages(userMessage: string): Promise<Message[]>;
90
+ /**
91
+ * Estimate token count using accurate tokenizer
92
+ */
93
+ estimateTokenCount(): Promise<number>;
94
+ /**
95
+ * Get current state
96
+ */
97
+ getState(): ContextState;
98
+ /**
99
+ * Restore state
100
+ */
101
+ restoreState(state: Partial<ContextState>): void;
102
+ /**
103
+ * Get conversation history
104
+ */
105
+ getHistory(): Message[];
106
+ /**
107
+ * Get tools
108
+ */
109
+ getTools(): Map<string, Tool>;
110
+ /**
111
+ * Get working directory
112
+ */
113
+ getCwd(): string;
114
+ }
115
+ export declare function getContextManager(cwd?: string): ContextManager;
116
+ export { countTokens };
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Context Manager - Manages conversation context and file state
3
+ * Based on Aider's coder.py and GeminiCLI's context management
4
+ * Uses gpt-tokenizer for accurate token counting (with fallback)
5
+ */
6
+ import { readFile } from 'fs/promises';
7
+ import { existsSync } from 'fs';
8
+ import { relative, resolve } from 'path';
9
+ import { generateRepoMap } from './repoMap.js';
10
+ import { getActiveSkill } from './skills.js';
11
+ import { loadAllTools, getToolDefinitions } from './registry.js';
12
+ import { getPromptProvider } from './prompts/provider.js';
13
+ // Cached tokenizer (typed as any to handle missing module)
14
+ let tokenEncoder = null;
15
+ let tokenizerLoaded = false;
16
+ /**
17
+ * Load tokenizer lazily (handles case where gpt-tokenizer not installed)
18
+ */
19
+ async function loadTokenizer() {
20
+ if (tokenizerLoaded)
21
+ return;
22
+ tokenizerLoaded = true;
23
+ try {
24
+ const mod = await import('gpt-tokenizer');
25
+ tokenEncoder = mod.encode;
26
+ }
27
+ catch {
28
+ // gpt-tokenizer not installed, will use fallback
29
+ }
30
+ }
31
+ /**
32
+ * Count tokens using gpt-tokenizer (accurate for GPT models)
33
+ * Falls back to rough estimation if not available
34
+ */
35
+ async function countTokensAsync(text) {
36
+ await loadTokenizer();
37
+ if (tokenEncoder) {
38
+ try {
39
+ return tokenEncoder(text).length;
40
+ }
41
+ catch {
42
+ // Fall through to estimation
43
+ }
44
+ }
45
+ // Fallback: ~4 chars per token (rough but reasonable)
46
+ return Math.ceil(text.length / 4);
47
+ }
48
+ /**
49
+ * Synchronous token count (uses cached encoder or estimation)
50
+ */
51
+ function countTokens(text) {
52
+ if (tokenEncoder) {
53
+ try {
54
+ return tokenEncoder(text).length;
55
+ }
56
+ catch {
57
+ // Fall through
58
+ }
59
+ }
60
+ return Math.ceil(text.length / 4);
61
+ }
62
+ /**
63
+ * ContextManager class
64
+ * Manages the conversation context, file state, and system prompts
65
+ */
66
+ export class ContextManager {
67
+ cwd;
68
+ activeFiles = new Set();
69
+ readOnlyFiles = new Set();
70
+ history = [];
71
+ skill;
72
+ tools = new Map();
73
+ repoMapCache = '';
74
+ repoMapTimestamp = 0;
75
+ constructor(cwd) {
76
+ this.cwd = cwd || process.cwd();
77
+ this.skill = getActiveSkill();
78
+ }
79
+ /**
80
+ * Initialize the context manager
81
+ */
82
+ async initialize() {
83
+ // Load tools
84
+ this.tools = await loadAllTools();
85
+ // Generate initial repo map
86
+ await this.refreshRepoMap();
87
+ }
88
+ /**
89
+ * Add a file to active context
90
+ */
91
+ addFile(path, readOnly = false) {
92
+ const fullPath = resolve(this.cwd, path);
93
+ if (!existsSync(fullPath)) {
94
+ return false;
95
+ }
96
+ if (readOnly) {
97
+ this.readOnlyFiles.add(fullPath);
98
+ this.activeFiles.delete(fullPath);
99
+ }
100
+ else {
101
+ this.activeFiles.add(fullPath);
102
+ this.readOnlyFiles.delete(fullPath);
103
+ }
104
+ return true;
105
+ }
106
+ /**
107
+ * Remove a file from context
108
+ */
109
+ removeFile(path) {
110
+ const fullPath = resolve(this.cwd, path);
111
+ const wasActive = this.activeFiles.delete(fullPath);
112
+ const wasReadOnly = this.readOnlyFiles.delete(fullPath);
113
+ return wasActive || wasReadOnly;
114
+ }
115
+ /**
116
+ * Add a message to history
117
+ */
118
+ addMessage(role, content) {
119
+ this.history.push({
120
+ role,
121
+ content,
122
+ timestamp: Date.now(),
123
+ });
124
+ }
125
+ /**
126
+ * Clear conversation history
127
+ */
128
+ clearHistory() {
129
+ this.history = [];
130
+ }
131
+ /**
132
+ * Get all files in context
133
+ */
134
+ getFiles() {
135
+ return {
136
+ active: Array.from(this.activeFiles).map(f => relative(this.cwd, f)),
137
+ readOnly: Array.from(this.readOnlyFiles).map(f => relative(this.cwd, f)),
138
+ };
139
+ }
140
+ /**
141
+ * Read all files in context
142
+ */
143
+ async getFileContents() {
144
+ const contents = new Map();
145
+ for (const file of [...this.activeFiles, ...this.readOnlyFiles]) {
146
+ try {
147
+ const content = await readFile(file, 'utf-8');
148
+ const relPath = relative(this.cwd, file);
149
+ contents.set(relPath, content);
150
+ }
151
+ catch {
152
+ // Skip files that can't be read
153
+ }
154
+ }
155
+ return contents;
156
+ }
157
+ /**
158
+ * Set the active skill
159
+ */
160
+ setSkill(skill) {
161
+ this.skill = skill;
162
+ }
163
+ /**
164
+ * Get the current skill
165
+ */
166
+ getSkill() {
167
+ return this.skill;
168
+ }
169
+ /**
170
+ * Refresh the repository map
171
+ */
172
+ async refreshRepoMap() {
173
+ const now = Date.now();
174
+ // Cache for 30 seconds
175
+ if (this.repoMapCache && now - this.repoMapTimestamp < 30000) {
176
+ return this.repoMapCache;
177
+ }
178
+ this.repoMapCache = await generateRepoMap(this.cwd);
179
+ this.repoMapTimestamp = now;
180
+ return this.repoMapCache;
181
+ }
182
+ /**
183
+ * Build the system prompt
184
+ */
185
+ async buildSystemPrompt() {
186
+ const provider = getPromptProvider();
187
+ const systemPrompt = await provider.getSystemPrompt({
188
+ cwd: this.cwd,
189
+ skillPrompt: this.skill.systemPrompt
190
+ });
191
+ const parts = [systemPrompt];
192
+ // Tool definitions
193
+ const toolDefs = getToolDefinitions(this.tools);
194
+ parts.push('\n## Available Tools\n' + toolDefs);
195
+ // Active files
196
+ const files = this.getFiles();
197
+ if (files.active.length > 0 || files.readOnly.length > 0) {
198
+ parts.push('\n## Context Files');
199
+ if (files.active.length > 0) {
200
+ parts.push('\nEditable files:');
201
+ parts.push(files.active.map(f => `- ${f}`).join('\n'));
202
+ }
203
+ if (files.readOnly.length > 0) {
204
+ parts.push('\nRead-only files:');
205
+ parts.push(files.readOnly.map(f => `- ${f}`).join('\n'));
206
+ }
207
+ }
208
+ // Repository map (condensed)
209
+ const repoMap = await this.refreshRepoMap();
210
+ if (repoMap) {
211
+ const condensed = repoMap.split('\n').slice(0, 50).join('\n');
212
+ parts.push('\n## Repository Structure\n' + condensed);
213
+ if (repoMap.split('\n').length > 50) {
214
+ parts.push('... (truncated)');
215
+ }
216
+ }
217
+ return parts.join('\n');
218
+ }
219
+ /**
220
+ * Build messages for LLM
221
+ */
222
+ async buildMessages(userMessage) {
223
+ const messages = [];
224
+ // System prompt
225
+ const systemPrompt = await this.buildSystemPrompt();
226
+ messages.push({ role: 'system', content: systemPrompt });
227
+ // File contents as context
228
+ const fileContents = await this.getFileContents();
229
+ if (fileContents.size > 0) {
230
+ let fileContext = '## File Contents\n\n';
231
+ for (const [path, content] of fileContents) {
232
+ const truncated = content.length > 10000
233
+ ? content.slice(0, 10000) + '\n... (truncated)'
234
+ : content;
235
+ fileContext += `### ${path}\n\`\`\`\n${truncated}\n\`\`\`\n\n`;
236
+ }
237
+ messages.push({ role: 'user', content: fileContext });
238
+ messages.push({ role: 'assistant', content: 'I have read the files. How can I help?' });
239
+ }
240
+ // Conversation history
241
+ for (const msg of this.history) {
242
+ messages.push({ role: msg.role, content: msg.content });
243
+ }
244
+ // Current message
245
+ messages.push({ role: 'user', content: userMessage });
246
+ return messages;
247
+ }
248
+ /**
249
+ * Estimate token count using accurate tokenizer
250
+ */
251
+ async estimateTokenCount() {
252
+ // Ensure tokenizer is loaded
253
+ await loadTokenizer();
254
+ let total = 0;
255
+ // System prompt
256
+ const systemPrompt = await this.buildSystemPrompt();
257
+ total += countTokens(systemPrompt);
258
+ // File contents
259
+ const fileContents = await this.getFileContents();
260
+ for (const content of fileContents.values()) {
261
+ total += countTokens(content);
262
+ }
263
+ // History
264
+ for (const msg of this.history) {
265
+ total += countTokens(msg.content);
266
+ }
267
+ // Add overhead for message formatting (~4 tokens per message)
268
+ total += (this.history.length + 2) * 4;
269
+ return total;
270
+ }
271
+ /**
272
+ * Get current state
273
+ */
274
+ getState() {
275
+ return {
276
+ cwd: this.cwd,
277
+ activeFiles: this.activeFiles,
278
+ readOnlyFiles: this.readOnlyFiles,
279
+ history: this.history,
280
+ skill: this.skill,
281
+ tokenEstimate: 0, // Calculated async
282
+ };
283
+ }
284
+ /**
285
+ * Restore state
286
+ */
287
+ restoreState(state) {
288
+ if (state.cwd)
289
+ this.cwd = state.cwd;
290
+ if (state.activeFiles)
291
+ this.activeFiles = state.activeFiles;
292
+ if (state.readOnlyFiles)
293
+ this.readOnlyFiles = state.readOnlyFiles;
294
+ if (state.history)
295
+ this.history = state.history;
296
+ if (state.skill)
297
+ this.skill = state.skill;
298
+ }
299
+ /**
300
+ * Get conversation history
301
+ */
302
+ getHistory() {
303
+ return [...this.history];
304
+ }
305
+ /**
306
+ * Get tools
307
+ */
308
+ getTools() {
309
+ return this.tools;
310
+ }
311
+ /**
312
+ * Get working directory
313
+ */
314
+ getCwd() {
315
+ return this.cwd;
316
+ }
317
+ }
318
+ // Create a global context manager instance
319
+ let globalContext = null;
320
+ export function getContextManager(cwd) {
321
+ if (!globalContext) {
322
+ globalContext = new ContextManager(cwd);
323
+ }
324
+ return globalContext;
325
+ }
326
+ // Export token counting utility for use elsewhere
327
+ export { countTokens };
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Simple-CLI Orchestrator - Core loop: Observe -> Plan -> Verify -> Act -> Reflect
4
+ * Must remain under 150 lines. Supports MoE routing with --moe flag.
5
+ */
6
+ import 'dotenv/config';
package/dist/index.js ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Simple-CLI Orchestrator - Core loop: Observe -> Plan -> Verify -> Act -> Reflect
4
+ * Must remain under 150 lines. Supports MoE routing with --moe flag.
5
+ */
6
+ import 'dotenv/config';
7
+ import * as readline from 'readline';
8
+ import { generateRepoMap } from './repoMap.js';
9
+ import { loadTools } from './registry.js';
10
+ import { createProvider } from './providers/index.js';
11
+ import { createMultiProvider } from './providers/multi.js';
12
+ import { routeTask, loadTierConfig, formatRoutingDecision } from './router.js';
13
+ import { getPromptProvider } from './prompts/provider.js';
14
+ const YOLO_MODE = process.argv.includes('--yolo');
15
+ const MOE_MODE = process.argv.includes('--moe');
16
+ let tools;
17
+ let history = [];
18
+ const buildPrompt = async () => {
19
+ const repoMap = await generateRepoMap();
20
+ const toolDefs = Array.from(tools.values()).map(t => `- ${t.name}: ${t.description}`).join('\n');
21
+ const provider = getPromptProvider();
22
+ const systemPrompt = await provider.getSystemPrompt({ cwd: process.cwd() });
23
+ return `${systemPrompt}
24
+ ## Context\n${repoMap}
25
+ ## Tools\n${toolDefs}
26
+ ## Format: <thought>reasoning</thought> then {"tool": "name", "args": {}} or {"tool": "none", "message": "..."}`;
27
+ };
28
+ import { jsonrepair } from 'jsonrepair';
29
+ const parseResponse = (r) => {
30
+ const thought = r.match(/<thought>([\s\S]*?)<\/thought>/)?.[1]?.trim() || '';
31
+ const jsonMatch = r.match(/\{[\s\S]*"tool"[\s\S]*\}/);
32
+ let action = { tool: 'none', message: 'No action', args: {} };
33
+ if (jsonMatch) {
34
+ try {
35
+ const repaired = jsonrepair(jsonMatch[0]);
36
+ action = JSON.parse(repaired);
37
+ }
38
+ catch { /* use default */ }
39
+ }
40
+ return { thought, action };
41
+ };
42
+ const confirm = async (tool, args) => {
43
+ if (YOLO_MODE)
44
+ return true;
45
+ const t = tools.get(tool);
46
+ if (!t || t.permission === 'read')
47
+ return !!t;
48
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
49
+ return new Promise(res => {
50
+ rl.question(`[CONFIRM] ${tool}(${JSON.stringify(args)})? (y/n) `, a => { rl.close(); res(a.toLowerCase() === 'y'); });
51
+ });
52
+ };
53
+ const execTool = async (name, args) => {
54
+ const t = tools.get(name);
55
+ if (!t)
56
+ return `Error: Tool "${name}" not found`;
57
+ try {
58
+ const r = await t.execute(args);
59
+ return typeof r === 'string' ? r : JSON.stringify(r, null, 2);
60
+ }
61
+ catch (e) {
62
+ return `Error: ${e instanceof Error ? e.message : e}`;
63
+ }
64
+ };
65
+ const main = async () => {
66
+ console.log(`Simple-CLI v0.1.0 ${MOE_MODE ? '[MoE]' : ''} ${YOLO_MODE ? '[YOLO]' : '[Safe]'}`);
67
+ tools = await loadTools();
68
+ const systemPrompt = await buildPrompt();
69
+ const tierConfigs = MOE_MODE ? loadTierConfig() : null;
70
+ const multiProvider = tierConfigs ? createMultiProvider(tierConfigs) : null;
71
+ const singleProvider = !MOE_MODE ? createProvider() : null;
72
+ const generate = async (input) => {
73
+ if (MOE_MODE && multiProvider && tierConfigs) {
74
+ const routing = await routeTask(input, (p) => multiProvider.generateWithTier(1, p, [{ role: 'user', content: input }]));
75
+ console.log(formatRoutingDecision(routing, tierConfigs));
76
+ return multiProvider.generateWithTier(routing.tier, systemPrompt, history);
77
+ }
78
+ return singleProvider.generateResponse(systemPrompt, history);
79
+ };
80
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
81
+ const prompt = () => {
82
+ rl.question('\n> ', async (input) => {
83
+ if (input.toLowerCase() === 'exit') {
84
+ rl.close();
85
+ return;
86
+ }
87
+ history.push({ role: 'user', content: input });
88
+ const response = await generate(input);
89
+ const { thought, action } = parseResponse(response);
90
+ console.log(`\n[Thought] ${thought}`);
91
+ if (action.tool !== 'none') {
92
+ if (await confirm(action.tool, action.args || {})) {
93
+ const result = await execTool(action.tool, action.args || {});
94
+ console.log(`[Result] ${result}`);
95
+ history.push({ role: 'assistant', content: response }, { role: 'user', content: `Tool result: ${result}` });
96
+ }
97
+ else
98
+ console.log('[Skipped]');
99
+ }
100
+ else {
101
+ console.log(`[Response] ${action.message}`);
102
+ history.push({ role: 'assistant', content: response });
103
+ }
104
+ prompt();
105
+ });
106
+ };
107
+ prompt();
108
+ };
109
+ main().catch(console.error);
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Agent Core - Main agent loop with reflection and retry
3
+ * Implements Aider-style reasoning and error recovery
4
+ */
5
+ import { Message } from '../context.js';
6
+ import { EditBlock, EditResult } from './editor.js';
7
+ import { GitManager } from './git.js';
8
+ export interface AgentConfig {
9
+ maxReflections: number;
10
+ autoLint: boolean;
11
+ autoTest: boolean;
12
+ autoCommit: boolean;
13
+ testCommand?: string;
14
+ lintCommand?: string;
15
+ }
16
+ export interface ToolCall {
17
+ tool: string;
18
+ args: Record<string, unknown>;
19
+ }
20
+ export interface AgentResponse {
21
+ thought?: string;
22
+ action: ToolCall | {
23
+ tool: 'none';
24
+ message: string;
25
+ };
26
+ editBlocks?: EditBlock[];
27
+ }
28
+ export interface ReflectionContext {
29
+ attempt: number;
30
+ previousError: string;
31
+ previousResponse: string;
32
+ failedEdits: EditResult[];
33
+ }
34
+ /**
35
+ * Parse LLM response into structured format
36
+ */
37
+ export declare function parseResponse(response: string): AgentResponse;
38
+ /**
39
+ * Build reflection prompt for retry
40
+ */
41
+ export declare function buildReflectionPrompt(context: ReflectionContext): string;
42
+ /**
43
+ * Build lint error prompt
44
+ */
45
+ export declare function buildLintErrorPrompt(file: string, errors: string): string;
46
+ /**
47
+ * Build test failure prompt
48
+ */
49
+ export declare function buildTestFailurePrompt(output: string): string;
50
+ /**
51
+ * Agent class - Main orchestration logic
52
+ */
53
+ export declare class Agent {
54
+ private config;
55
+ private git;
56
+ private generateFn;
57
+ private executeTool;
58
+ private lintFn?;
59
+ private testFn?;
60
+ constructor(options: {
61
+ config: AgentConfig;
62
+ git: GitManager;
63
+ generateFn: (messages: Message[]) => Promise<string>;
64
+ executeTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
65
+ lintFn?: (file: string) => Promise<{
66
+ passed: boolean;
67
+ output: string;
68
+ }>;
69
+ testFn?: () => Promise<{
70
+ passed: boolean;
71
+ output: string;
72
+ }>;
73
+ });
74
+ /**
75
+ * Process a user message with reflection loop
76
+ */
77
+ process(userMessage: string, history: Message[], systemPrompt: string): Promise<{
78
+ response: AgentResponse;
79
+ editResults: EditResult[];
80
+ lintResults: Array<{
81
+ file: string;
82
+ passed: boolean;
83
+ output: string;
84
+ }>;
85
+ testResult?: {
86
+ passed: boolean;
87
+ output: string;
88
+ };
89
+ commitResult?: {
90
+ hash: string;
91
+ message: string;
92
+ };
93
+ }>;
94
+ }
95
+ /**
96
+ * Summarize conversation history to reduce tokens
97
+ */
98
+ export declare function summarizeHistory(history: Message[], generateFn: (messages: Message[]) => Promise<string>, maxMessages?: number): Promise<Message[]>;