@stan-chen/simple-cli 0.2.3 → 0.2.5
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 +62 -63
- package/dist/anyllm.py +62 -0
- package/dist/builtins.d.ts +726 -0
- package/dist/builtins.js +481 -0
- package/dist/cli.d.ts +0 -4
- package/dist/cli.js +34 -493
- package/dist/engine.d.ts +33 -0
- package/dist/engine.js +138 -0
- package/dist/learnings.d.ts +15 -0
- package/dist/learnings.js +54 -0
- package/dist/llm.d.ts +18 -0
- package/dist/llm.js +73 -0
- package/dist/mcp.d.ts +132 -0
- package/dist/mcp.js +43 -0
- package/dist/skills.d.ts +5 -16
- package/dist/skills.js +91 -253
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +15 -0
- package/package.json +10 -6
- package/dist/claw/jit.d.ts +0 -5
- package/dist/claw/jit.js +0 -138
- package/dist/claw/management.d.ts +0 -3
- package/dist/claw/management.js +0 -107
- package/dist/commands/add.d.ts +0 -9
- package/dist/commands/add.js +0 -50
- package/dist/commands/git/commit.d.ts +0 -12
- package/dist/commands/git/commit.js +0 -98
- package/dist/commands/git/status.d.ts +0 -6
- package/dist/commands/git/status.js +0 -42
- package/dist/commands/index.d.ts +0 -16
- package/dist/commands/index.js +0 -377
- package/dist/commands/mcp/status.d.ts +0 -6
- package/dist/commands/mcp/status.js +0 -31
- package/dist/commands/swarm.d.ts +0 -36
- package/dist/commands/swarm.js +0 -236
- package/dist/commands.d.ts +0 -32
- package/dist/commands.js +0 -427
- package/dist/context.d.ts +0 -116
- package/dist/context.js +0 -337
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -109
- package/dist/lib/agent.d.ts +0 -99
- package/dist/lib/agent.js +0 -313
- package/dist/lib/editor.d.ts +0 -74
- package/dist/lib/editor.js +0 -441
- package/dist/lib/git.d.ts +0 -164
- package/dist/lib/git.js +0 -356
- package/dist/lib/shim.d.ts +0 -4
- package/dist/lib/shim.js +0 -30
- package/dist/lib/ui.d.ts +0 -159
- package/dist/lib/ui.js +0 -277
- package/dist/mcp/client.d.ts +0 -22
- package/dist/mcp/client.js +0 -81
- package/dist/mcp/manager.d.ts +0 -186
- package/dist/mcp/manager.js +0 -446
- package/dist/prompts/provider.d.ts +0 -22
- package/dist/prompts/provider.js +0 -79
- package/dist/providers/index.d.ts +0 -31
- package/dist/providers/index.js +0 -93
- package/dist/providers/multi.d.ts +0 -12
- package/dist/providers/multi.js +0 -28
- package/dist/registry.d.ts +0 -29
- package/dist/registry.js +0 -443
- package/dist/repoMap.d.ts +0 -5
- package/dist/repoMap.js +0 -79
- package/dist/router.d.ts +0 -41
- package/dist/router.js +0 -118
- package/dist/swarm/coordinator.d.ts +0 -86
- package/dist/swarm/coordinator.js +0 -257
- package/dist/swarm/index.d.ts +0 -28
- package/dist/swarm/index.js +0 -29
- package/dist/swarm/task.d.ts +0 -104
- package/dist/swarm/task.js +0 -221
- package/dist/swarm/types.d.ts +0 -132
- package/dist/swarm/types.js +0 -37
- package/dist/swarm/worker.d.ts +0 -109
- package/dist/swarm/worker.js +0 -369
- package/dist/tools/analyzeFile.d.ts +0 -16
- package/dist/tools/analyzeFile.js +0 -43
- package/dist/tools/analyze_file.d.ts +0 -16
- package/dist/tools/analyze_file.js +0 -43
- package/dist/tools/clawBrain.d.ts +0 -23
- package/dist/tools/clawBrain.js +0 -136
- package/dist/tools/claw_brain.d.ts +0 -23
- package/dist/tools/claw_brain.js +0 -139
- package/dist/tools/deleteFile.d.ts +0 -19
- package/dist/tools/deleteFile.js +0 -36
- package/dist/tools/delete_file.d.ts +0 -19
- package/dist/tools/delete_file.js +0 -36
- package/dist/tools/fileOps.d.ts +0 -22
- package/dist/tools/fileOps.js +0 -43
- package/dist/tools/file_ops.d.ts +0 -22
- package/dist/tools/file_ops.js +0 -43
- package/dist/tools/git.d.ts +0 -40
- package/dist/tools/git.js +0 -236
- package/dist/tools/glob.d.ts +0 -34
- package/dist/tools/glob.js +0 -165
- package/dist/tools/grep.d.ts +0 -53
- package/dist/tools/grep.js +0 -296
- package/dist/tools/linter.d.ts +0 -35
- package/dist/tools/linter.js +0 -407
- package/dist/tools/listDir.d.ts +0 -29
- package/dist/tools/listDir.js +0 -50
- package/dist/tools/list_dir.d.ts +0 -29
- package/dist/tools/list_dir.js +0 -50
- package/dist/tools/memory.d.ts +0 -34
- package/dist/tools/memory.js +0 -215
- package/dist/tools/organizer.d.ts +0 -1
- package/dist/tools/organizer.js +0 -65
- package/dist/tools/readFiles.d.ts +0 -25
- package/dist/tools/readFiles.js +0 -31
- package/dist/tools/read_files.d.ts +0 -25
- package/dist/tools/read_files.js +0 -31
- package/dist/tools/reloadTools.d.ts +0 -11
- package/dist/tools/reloadTools.js +0 -22
- package/dist/tools/reload_tools.d.ts +0 -11
- package/dist/tools/reload_tools.js +0 -22
- package/dist/tools/runCommand.d.ts +0 -32
- package/dist/tools/runCommand.js +0 -79
- package/dist/tools/run_command.d.ts +0 -32
- package/dist/tools/run_command.js +0 -103
- package/dist/tools/scheduler.d.ts +0 -25
- package/dist/tools/scheduler.js +0 -65
- package/dist/tools/scraper.d.ts +0 -31
- package/dist/tools/scraper.js +0 -211
- package/dist/tools/writeFiles.d.ts +0 -63
- package/dist/tools/writeFiles.js +0 -87
- package/dist/tools/write_files.d.ts +0 -84
- package/dist/tools/write_files.js +0 -91
- package/dist/tools/write_to_file.d.ts +0 -15
- package/dist/tools/write_to_file.js +0 -21
- package/dist/ui/server.d.ts +0 -5
- package/dist/ui/server.js +0 -74
- package/dist/watcher.d.ts +0 -35
- package/dist/watcher.js +0 -164
- /package/{docs/assets → assets}/logo.jpeg +0 -0
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: writeFiles
|
|
3
|
-
* Write or update files with search/replace support (Aider-style)
|
|
4
|
-
*/
|
|
5
|
-
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
6
|
-
import { dirname, resolve } from 'path';
|
|
7
|
-
import { z } from 'zod';
|
|
8
|
-
export const name = 'write_files';
|
|
9
|
-
export const description = 'Write or modify files. ALWAYS provide an array of objects in the "files" parameter, even for a single file. Each object must have "path" and either "content" (for full write) or "searchReplace" (for edits).';
|
|
10
|
-
export const permission = 'write';
|
|
11
|
-
const SearchReplaceSchema = z.object({
|
|
12
|
-
search: z.string().describe('Text to search for'),
|
|
13
|
-
replace: z.string().describe('Text to replace with')
|
|
14
|
-
});
|
|
15
|
-
const FileWriteSchema = z.object({
|
|
16
|
-
path: z.string().describe('File path to write'),
|
|
17
|
-
content: z.string().optional().describe('Full content to write (for new files or full rewrites)'),
|
|
18
|
-
searchReplace: z.union([
|
|
19
|
-
z.array(SearchReplaceSchema),
|
|
20
|
-
SearchReplaceSchema.transform(val => [val])
|
|
21
|
-
]).optional().describe('Array of search/replace operations for targeted edits')
|
|
22
|
-
});
|
|
23
|
-
export const schema = z.object({
|
|
24
|
-
files: z.array(FileWriteSchema).describe('Array of files to write/modify')
|
|
25
|
-
});
|
|
26
|
-
export const execute = async (args) => {
|
|
27
|
-
const parsed = schema.parse(args);
|
|
28
|
-
const results = [];
|
|
29
|
-
for (const file of parsed.files) {
|
|
30
|
-
try {
|
|
31
|
-
// Ensure directory exists
|
|
32
|
-
await mkdir(dirname(file.path), { recursive: true });
|
|
33
|
-
if (file.content !== undefined) {
|
|
34
|
-
// Full file write
|
|
35
|
-
await writeFile(file.path, file.content, 'utf-8');
|
|
36
|
-
const absPath = resolve(file.path);
|
|
37
|
-
results.push({
|
|
38
|
-
path: file.path,
|
|
39
|
-
success: true,
|
|
40
|
-
message: `File written successfully to ${absPath}`
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
else if (file.searchReplace && file.searchReplace.length > 0) {
|
|
44
|
-
// Search/replace operations
|
|
45
|
-
let content = await readFile(file.path, 'utf-8');
|
|
46
|
-
let changesApplied = 0;
|
|
47
|
-
const absPath = resolve(file.path);
|
|
48
|
-
for (const { search, replace } of file.searchReplace) {
|
|
49
|
-
if (content.includes(search)) {
|
|
50
|
-
// Apply replacement to all occurrences
|
|
51
|
-
const newContent = content.replace(search, replace);
|
|
52
|
-
if (newContent !== content) {
|
|
53
|
-
content = newContent;
|
|
54
|
-
changesApplied++;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
if (changesApplied > 0) {
|
|
59
|
-
await writeFile(file.path, content, 'utf-8');
|
|
60
|
-
results.push({
|
|
61
|
-
path: file.path,
|
|
62
|
-
success: true,
|
|
63
|
-
message: `Applied ${changesApplied} search/replace operation(s) to ${absPath}`
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
results.push({
|
|
68
|
-
path: file.path,
|
|
69
|
-
success: false,
|
|
70
|
-
message: `No matching search patterns found in ${absPath}`
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
results.push({
|
|
76
|
-
path: file.path,
|
|
77
|
-
success: false,
|
|
78
|
-
message: 'No content or searchReplace provided'
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
results.push({
|
|
84
|
-
path: file.path,
|
|
85
|
-
success: false,
|
|
86
|
-
message: error instanceof Error ? error.message : 'Unknown error'
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return results;
|
|
91
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
export declare const name = "write_to_file";
|
|
3
|
-
export declare const description = "Write content to a single file. Overwrites if it exists.";
|
|
4
|
-
export declare const permission: "write";
|
|
5
|
-
export declare const schema: z.ZodObject<{
|
|
6
|
-
path: z.ZodString;
|
|
7
|
-
content: z.ZodString;
|
|
8
|
-
}, "strip", z.ZodTypeAny, {
|
|
9
|
-
path: string;
|
|
10
|
-
content: string;
|
|
11
|
-
}, {
|
|
12
|
-
path: string;
|
|
13
|
-
content: string;
|
|
14
|
-
}>;
|
|
15
|
-
export declare const execute: (args: Record<string, unknown>) => Promise<string>;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tool: write_to_file
|
|
3
|
-
* Simplified file write for single files
|
|
4
|
-
*/
|
|
5
|
-
import { writeFile, mkdir } from 'fs/promises';
|
|
6
|
-
import { dirname, resolve } from 'path';
|
|
7
|
-
import { z } from 'zod';
|
|
8
|
-
export const name = 'write_to_file';
|
|
9
|
-
export const description = 'Write content to a single file. Overwrites if it exists.';
|
|
10
|
-
export const permission = 'write';
|
|
11
|
-
export const schema = z.object({
|
|
12
|
-
path: z.string().describe('File path to write'),
|
|
13
|
-
content: z.string().describe('Content to write')
|
|
14
|
-
});
|
|
15
|
-
export const execute = async (args) => {
|
|
16
|
-
const { path, content } = schema.parse(args);
|
|
17
|
-
const absPath = resolve(path);
|
|
18
|
-
await mkdir(dirname(absPath), { recursive: true });
|
|
19
|
-
await writeFile(absPath, content, 'utf-8');
|
|
20
|
-
return `Successfully wrote to ${path}`;
|
|
21
|
-
};
|
package/dist/ui/server.d.ts
DELETED
package/dist/ui/server.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import http from 'http';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import { getContextManager } from '../context.js';
|
|
6
|
-
import { createProvider } from '../providers/index.js';
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
export async function startServer(options) {
|
|
9
|
-
const { port, host } = options;
|
|
10
|
-
// Initialize Simple-CLI core
|
|
11
|
-
const ctx = getContextManager();
|
|
12
|
-
await ctx.initialize();
|
|
13
|
-
const provider = createProvider();
|
|
14
|
-
const server = http.createServer(async (req, res) => {
|
|
15
|
-
// Basic router
|
|
16
|
-
if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
|
|
17
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
18
|
-
res.end(fs.readFileSync(path.join(__dirname, 'index.html')));
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (req.method === 'POST' && req.url === '/api/chat') {
|
|
22
|
-
let body = '';
|
|
23
|
-
req.on('data', chunk => { body += chunk; });
|
|
24
|
-
req.on('end', async () => {
|
|
25
|
-
try {
|
|
26
|
-
const { message } = JSON.parse(body);
|
|
27
|
-
if (!message) {
|
|
28
|
-
res.writeHead(400);
|
|
29
|
-
res.end(JSON.stringify({ error: 'Message required' }));
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
// Add message to context
|
|
33
|
-
ctx.addMessage('user', message);
|
|
34
|
-
// Get system prompt and history
|
|
35
|
-
const systemPrompt = await ctx.buildSystemPrompt();
|
|
36
|
-
const history = ctx.getHistory();
|
|
37
|
-
// Generate response
|
|
38
|
-
const response = await provider.generateResponse(systemPrompt, history.map(m => ({ role: m.role, content: m.content })));
|
|
39
|
-
// Parse response (minimalist version of cli.ts logic)
|
|
40
|
-
const thought = response.match(/<thought>([\s\S]*?)<\/thought>/)?.[1]?.trim();
|
|
41
|
-
const jsonMatch = response.match(/\{[\s\S]*"tool"[\s\S]*\}/);
|
|
42
|
-
let action = { tool: 'none', message: 'No action', args: {} };
|
|
43
|
-
if (jsonMatch) {
|
|
44
|
-
try {
|
|
45
|
-
action = JSON.parse(jsonMatch[0]);
|
|
46
|
-
}
|
|
47
|
-
catch { /* ignore */ }
|
|
48
|
-
}
|
|
49
|
-
const messageText = action.message || response.replace(/<thought>[\s\S]*?<\/thought>/, '').replace(/\{[\s\S]*"tool"[\s\S]*\}/, '').trim();
|
|
50
|
-
// Update context
|
|
51
|
-
ctx.addMessage('assistant', response);
|
|
52
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
53
|
-
res.end(JSON.stringify({
|
|
54
|
-
message: messageText,
|
|
55
|
-
thought,
|
|
56
|
-
action
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
res.writeHead(500);
|
|
61
|
-
res.end(JSON.stringify({ error: error.message }));
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
// 404
|
|
67
|
-
res.writeHead(404);
|
|
68
|
-
res.end('Not Found');
|
|
69
|
-
});
|
|
70
|
-
server.listen(port, host, () => {
|
|
71
|
-
console.log(`\n🚀 Simple-CLI UI running at http://${host}:${port}`);
|
|
72
|
-
console.log(` Press Ctrl+C to stop\n`);
|
|
73
|
-
});
|
|
74
|
-
}
|
package/dist/watcher.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Watcher - Watch files for changes and AI comments
|
|
3
|
-
* Uses chokidar for robust cross-platform watching
|
|
4
|
-
*/
|
|
5
|
-
import { EventEmitter } from 'events';
|
|
6
|
-
export interface WatcherOptions {
|
|
7
|
-
root: string;
|
|
8
|
-
ignorePatterns?: RegExp[];
|
|
9
|
-
onFileChange?: (filePath: string, type: 'add' | 'change' | 'unlink') => void;
|
|
10
|
-
onAIComment?: (filePath: string, comments: AIComment[]) => void;
|
|
11
|
-
verbose?: boolean;
|
|
12
|
-
}
|
|
13
|
-
export interface AIComment {
|
|
14
|
-
line: number;
|
|
15
|
-
text: string;
|
|
16
|
-
action: 'request' | 'question' | 'note';
|
|
17
|
-
}
|
|
18
|
-
export declare class FileWatcher extends EventEmitter {
|
|
19
|
-
private options;
|
|
20
|
-
private watcher;
|
|
21
|
-
private watchedFiles;
|
|
22
|
-
private ignorePatterns;
|
|
23
|
-
private verbose;
|
|
24
|
-
constructor(options: WatcherOptions);
|
|
25
|
-
start(): void;
|
|
26
|
-
stop(): void;
|
|
27
|
-
private shouldIgnore;
|
|
28
|
-
private handleChange;
|
|
29
|
-
private handleUnlink;
|
|
30
|
-
private extractAIComments;
|
|
31
|
-
hasActionableComments(): boolean;
|
|
32
|
-
getActionableCommentsPrompt(): string;
|
|
33
|
-
private log;
|
|
34
|
-
}
|
|
35
|
-
export declare function createFileWatcher(options: WatcherOptions): FileWatcher;
|
package/dist/watcher.js
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File Watcher - Watch files for changes and AI comments
|
|
3
|
-
* Uses chokidar for robust cross-platform watching
|
|
4
|
-
*/
|
|
5
|
-
import * as chokidar from 'chokidar';
|
|
6
|
-
import { readFile } from 'fs/promises';
|
|
7
|
-
import { relative } from 'path';
|
|
8
|
-
import { EventEmitter } from 'events';
|
|
9
|
-
// AI comment pattern (matches: # ai!, // ai?, -- ai, etc.)
|
|
10
|
-
const AI_COMMENT_PATTERN = /(?:#|\/\/|--|;+)\s*(ai\b.*|ai\b.*|.*\bai[?!]?)\s*$/i;
|
|
11
|
-
// Default ignore patterns
|
|
12
|
-
const IGNORE_PATTERNS = [
|
|
13
|
-
/node_modules/,
|
|
14
|
-
/\.git/,
|
|
15
|
-
/dist\//,
|
|
16
|
-
/build\//,
|
|
17
|
-
/\.next/,
|
|
18
|
-
/\.nuxt/,
|
|
19
|
-
/__pycache__/,
|
|
20
|
-
/\.pytest_cache/,
|
|
21
|
-
/\.venv/,
|
|
22
|
-
/venv/,
|
|
23
|
-
/\.env$/,
|
|
24
|
-
/\.DS_Store/,
|
|
25
|
-
/\.swp$/,
|
|
26
|
-
/\.swo$/,
|
|
27
|
-
/\.bak$/,
|
|
28
|
-
/~$/,
|
|
29
|
-
/\.tmp$/,
|
|
30
|
-
/\.log$/,
|
|
31
|
-
];
|
|
32
|
-
const MAX_FILE_SIZE = 1024 * 1024; // 1MB
|
|
33
|
-
export class FileWatcher extends EventEmitter {
|
|
34
|
-
options;
|
|
35
|
-
watcher = null;
|
|
36
|
-
watchedFiles = new Map();
|
|
37
|
-
ignorePatterns;
|
|
38
|
-
verbose;
|
|
39
|
-
constructor(options) {
|
|
40
|
-
super();
|
|
41
|
-
this.options = options;
|
|
42
|
-
this.ignorePatterns = [...IGNORE_PATTERNS, ...(options.ignorePatterns || [])];
|
|
43
|
-
this.verbose = options.verbose || false;
|
|
44
|
-
if (options.onFileChange)
|
|
45
|
-
this.on('file-change', options.onFileChange);
|
|
46
|
-
if (options.onAIComment)
|
|
47
|
-
this.on('ai-comment', options.onAIComment);
|
|
48
|
-
}
|
|
49
|
-
start() {
|
|
50
|
-
if (this.watcher)
|
|
51
|
-
return;
|
|
52
|
-
this.log(`Starting chokidar watcher on ${this.options.root}`);
|
|
53
|
-
this.watcher = chokidar.watch(this.options.root, {
|
|
54
|
-
ignored: (path) => this.shouldIgnore(path),
|
|
55
|
-
persistent: true,
|
|
56
|
-
ignoreInitial: false,
|
|
57
|
-
awaitWriteFinish: {
|
|
58
|
-
stabilityThreshold: 300,
|
|
59
|
-
pollInterval: 100
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
this.watcher
|
|
63
|
-
.on('add', (path) => this.handleChange(path, 'add'))
|
|
64
|
-
.on('change', (path) => this.handleChange(path, 'change'))
|
|
65
|
-
.on('unlink', (path) => this.handleUnlink(path));
|
|
66
|
-
}
|
|
67
|
-
stop() {
|
|
68
|
-
if (this.watcher) {
|
|
69
|
-
this.watcher.close();
|
|
70
|
-
this.watcher = null;
|
|
71
|
-
this.log('Watcher stopped');
|
|
72
|
-
}
|
|
73
|
-
this.watchedFiles.clear();
|
|
74
|
-
}
|
|
75
|
-
shouldIgnore(path) {
|
|
76
|
-
const relPath = relative(this.options.root, path);
|
|
77
|
-
// Always include root
|
|
78
|
-
if (path === this.options.root)
|
|
79
|
-
return false;
|
|
80
|
-
return this.ignorePatterns.some(pattern => pattern.test(relPath));
|
|
81
|
-
}
|
|
82
|
-
async handleChange(path, type) {
|
|
83
|
-
const relPath = relative(this.options.root, path);
|
|
84
|
-
// Skip large files (chokidar might have already stat-ed, but we want to be safe before reading)
|
|
85
|
-
// Note: chokidar doesn't expose stat size in event easily without another stat call or 'add' stats arg
|
|
86
|
-
// For simplicity, we just read.
|
|
87
|
-
try {
|
|
88
|
-
// ... Check size logic could go here if critical
|
|
89
|
-
const comments = await this.extractAIComments(path);
|
|
90
|
-
const existing = this.watchedFiles.get(path);
|
|
91
|
-
const hasNewComments = comments.length > 0 && (!existing || JSON.stringify(comments) !== JSON.stringify(existing.aiComments));
|
|
92
|
-
this.watchedFiles.set(path, { path, aiComments: comments });
|
|
93
|
-
this.emit('file-change', relPath, type);
|
|
94
|
-
if (hasNewComments) {
|
|
95
|
-
this.emit('ai-comment', relPath, comments);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch (e) {
|
|
99
|
-
this.log(`Error processing ${relPath}: ${e}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
handleUnlink(path) {
|
|
103
|
-
const relPath = relative(this.options.root, path);
|
|
104
|
-
this.watchedFiles.delete(path);
|
|
105
|
-
this.emit('file-change', relPath, 'unlink');
|
|
106
|
-
}
|
|
107
|
-
async extractAIComments(filePath) {
|
|
108
|
-
try {
|
|
109
|
-
const content = await readFile(filePath, 'utf-8');
|
|
110
|
-
if (content.length > MAX_FILE_SIZE)
|
|
111
|
-
return [];
|
|
112
|
-
const lines = content.split('\n');
|
|
113
|
-
const comments = [];
|
|
114
|
-
for (let i = 0; i < lines.length; i++) {
|
|
115
|
-
const match = AI_COMMENT_PATTERN.exec(lines[i]);
|
|
116
|
-
if (match) {
|
|
117
|
-
const text = match[1].trim();
|
|
118
|
-
let action = 'note';
|
|
119
|
-
const lower = text.toLowerCase();
|
|
120
|
-
if (lower.endsWith('!') || lower.startsWith('ai!'))
|
|
121
|
-
action = 'request';
|
|
122
|
-
else if (lower.endsWith('?') || lower.startsWith('ai?'))
|
|
123
|
-
action = 'question';
|
|
124
|
-
comments.push({ line: i + 1, text, action });
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return comments;
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
return [];
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
hasActionableComments() {
|
|
134
|
-
for (const [_, file] of this.watchedFiles) {
|
|
135
|
-
if (file.aiComments.some(c => c.action === 'request' || c.action === 'question')) {
|
|
136
|
-
return true;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
getActionableCommentsPrompt() {
|
|
142
|
-
const parts = [];
|
|
143
|
-
for (const [path, file] of this.watchedFiles) {
|
|
144
|
-
const actionable = file.aiComments.filter(c => c.action === 'request' || c.action === 'question');
|
|
145
|
-
if (actionable.length > 0) {
|
|
146
|
-
const relPath = relative(this.options.root, path);
|
|
147
|
-
parts.push(`\n${relPath}:`);
|
|
148
|
-
for (const c of actionable) {
|
|
149
|
-
parts.push(` Line ${c.line} [${c.action === 'request' ? '!' : '?'}]: ${c.text}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return parts.length > 0 ? `The following AI comments were found in the codebase:${parts.join('\n')}` : '';
|
|
154
|
-
}
|
|
155
|
-
log(msg) {
|
|
156
|
-
if (this.verbose)
|
|
157
|
-
console.log(`[Watcher] ${msg}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
export function createFileWatcher(options) {
|
|
161
|
-
const w = new FileWatcher(options);
|
|
162
|
-
w.start();
|
|
163
|
-
return w;
|
|
164
|
-
}
|
|
File without changes
|