thatgfsj-code 0.2.1 → 0.2.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/README.md +131 -30
  4. package/dist/core/ai-engine.d.ts +17 -0
  5. package/dist/core/ai-engine.d.ts.map +1 -1
  6. package/dist/core/ai-engine.js +23 -7
  7. package/dist/core/ai-engine.js.map +1 -1
  8. package/dist/index.js +18 -27
  9. package/dist/index.js.map +1 -1
  10. package/dist/repl/input.d.ts +8 -0
  11. package/dist/repl/input.d.ts.map +1 -1
  12. package/dist/repl/input.js +12 -0
  13. package/dist/repl/input.js.map +1 -1
  14. package/dist/repl/loop.d.ts +56 -0
  15. package/dist/repl/loop.d.ts.map +1 -1
  16. package/dist/repl/loop.js +333 -4
  17. package/dist/repl/loop.js.map +1 -1
  18. package/dist/repl/output.d.ts.map +1 -1
  19. package/dist/repl/output.js +3 -1
  20. package/dist/repl/output.js.map +1 -1
  21. package/dist/repl/welcome.js +1 -1
  22. package/dist/repl/welcome.js.map +1 -1
  23. package/docs/API_KEY_GUIDE.md +6 -0
  24. package/docs/FAQ.md +25 -3
  25. package/package.json +35 -3
  26. package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +0 -279
  27. package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +0 -564
  28. package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +0 -194
  29. package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +0 -24
  30. package/ROADMAP.md +0 -107
  31. package/src/agent/core.ts +0 -179
  32. package/src/agent/index.ts +0 -8
  33. package/src/agent/intent.ts +0 -181
  34. package/src/agent/streaming.ts +0 -132
  35. package/src/core/ai-engine.ts +0 -437
  36. package/src/core/cli.ts +0 -171
  37. package/src/core/config.ts +0 -147
  38. package/src/core/context-compactor.ts +0 -245
  39. package/src/core/hooks.ts +0 -196
  40. package/src/core/permissions.ts +0 -308
  41. package/src/core/session.ts +0 -165
  42. package/src/core/skills.ts +0 -208
  43. package/src/core/state.ts +0 -120
  44. package/src/core/subagent.ts +0 -195
  45. package/src/core/system-prompt.ts +0 -163
  46. package/src/core/tool-registry.ts +0 -157
  47. package/src/core/types.ts +0 -280
  48. package/src/index.ts +0 -544
  49. package/src/mcp/client.ts +0 -330
  50. package/src/repl/index.ts +0 -8
  51. package/src/repl/input.ts +0 -139
  52. package/src/repl/loop.ts +0 -280
  53. package/src/repl/output.ts +0 -222
  54. package/src/repl/welcome.ts +0 -296
  55. package/src/tools/file.ts +0 -117
  56. package/src/tools/git.ts +0 -132
  57. package/src/tools/index.ts +0 -48
  58. package/src/tools/search.ts +0 -263
  59. package/src/tools/shell.ts +0 -181
  60. package/src/utils/diff-preview.ts +0 -202
  61. package/src/utils/index.ts +0 -8
  62. package/src/utils/memory.ts +0 -223
  63. package/src/utils/project-context.ts +0 -207
  64. package/tsconfig.json +0 -19
@@ -1,263 +0,0 @@
1
- /**
2
- * Search Tool - Code search and file operations
3
- */
4
-
5
- import { Tool, ToolResult } from '../core/types.js';
6
- import { exec } from 'child_process';
7
- import { promisify } from 'util';
8
- import { readdirSync, statSync, readFileSync } from 'fs';
9
- import { join, relative } from 'path';
10
-
11
- const execAsync = promisify(exec);
12
-
13
- export class SearchTool implements Tool {
14
- name = 'search';
15
- description = 'Search and find: grep, find files, list directory tree';
16
-
17
- parameters = [
18
- { name: 'action', type: 'string', description: 'Action: grep, find, tree, files', required: true },
19
- { name: 'pattern', type: 'string', description: 'Search pattern or file pattern', required: false },
20
- { name: 'path', type: 'string', description: 'Directory to search in', required: false },
21
- { name: 'options', type: 'string', description: 'Additional options', required: false }
22
- ];
23
-
24
- async execute(params: Record<string, any>): Promise<ToolResult> {
25
- const { action, pattern, path, options } = params;
26
- const workDir = path || process.cwd();
27
-
28
- try {
29
- switch (action) {
30
- case 'grep':
31
- case 'search':
32
- return await this.grep(pattern || '', workDir, options || '');
33
-
34
- case 'find':
35
- return await this.find(pattern || '*', workDir);
36
-
37
- case 'tree':
38
- return await this.tree(workDir, parseInt(options) || 3);
39
-
40
- case 'files':
41
- return await this.listFiles(workDir, pattern || '*');
42
-
43
- default:
44
- return { success: false, error: `Unknown action: ${action}` };
45
- }
46
- } catch (error: any) {
47
- return { success: false, error: error.message };
48
- }
49
- }
50
-
51
- /**
52
- * Grep - search for pattern in files
53
- */
54
- private async grep(pattern: string, path: string, options: string): Promise<ToolResult> {
55
- if (!pattern) {
56
- return { success: false, error: 'Pattern required' };
57
- }
58
-
59
- // Build grep command
60
- let cmd = `grep -rn "${pattern}" "${path}"`;
61
-
62
- if (options?.includes('i')) cmd += ' -i'; // Case insensitive
63
- if (options?.includes('w')) cmd += ' -w'; // Whole word
64
- if (options?.includes('l')) cmd += ' -l'; // Files only
65
- if (options?.includes('n')) cmd += ' -n'; // Line numbers
66
-
67
- cmd += ' --color=never';
68
-
69
- try {
70
- const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
71
-
72
- if (!stdout && stderr) {
73
- return { success: false, error: stderr };
74
- }
75
-
76
- const lines = (stdout || '').split('\n').filter(l => l.trim());
77
-
78
- if (lines.length === 0) {
79
- return { success: true, output: 'No matches found' };
80
- }
81
-
82
- // Limit output
83
- const limited = lines.slice(0, 50);
84
- const output = limited.join('\n');
85
-
86
- return {
87
- success: true,
88
- output: lines.length > 50
89
- ? output + `\n... and ${lines.length - 50} more matches`
90
- : output
91
- };
92
-
93
- } catch (error: any) {
94
- if (error.killed) {
95
- return { success: false, error: 'Search timed out (>30s)' };
96
- }
97
- return { success: false, error: error.message };
98
- }
99
- }
100
-
101
- /**
102
- * Find - find files by pattern
103
- */
104
- private async find(pattern: string, path: string): Promise<ToolResult> {
105
- if (!pattern) {
106
- pattern = '*';
107
- }
108
-
109
- const results: string[] = [];
110
-
111
- const search = (dir: string, depth: number) => {
112
- if (depth > 5) return; // Max depth
113
-
114
- try {
115
- const items = readdirSync(dir);
116
-
117
- for (const item of items) {
118
- // Skip common ignored directories
119
- if (item === 'node_modules' || item === '.git' || item === 'dist' || item === 'build') {
120
- continue;
121
- }
122
-
123
- const fullPath = join(dir, item);
124
-
125
- try {
126
- const stat = statSync(fullPath);
127
-
128
- if (stat.isDirectory()) {
129
- // Check if matches pattern
130
- if (this.matchPattern(item, pattern)) {
131
- results.push(fullPath);
132
- }
133
- search(fullPath, depth + 1);
134
- } else if (stat.isFile()) {
135
- if (this.matchPattern(item, pattern)) {
136
- results.push(fullPath);
137
- }
138
- }
139
- } catch {
140
- // Skip inaccessible files
141
- }
142
- }
143
- } catch {
144
- // Skip inaccessible directories
145
- }
146
- };
147
-
148
- search(path, 0);
149
-
150
- if (results.length === 0) {
151
- return { success: true, output: 'No files found' };
152
- }
153
-
154
- const limited = results.slice(0, 100);
155
- const output = limited.join('\n');
156
-
157
- return {
158
- success: true,
159
- output: results.length > 100
160
- ? output + `\n... and ${results.length - 100} more files`
161
- : output
162
- };
163
- }
164
-
165
- /**
166
- * Tree - show directory structure
167
- */
168
- private async tree(path: string, maxDepth: number): Promise<ToolResult> {
169
- const lines: string[] = [];
170
-
171
- const walk = (dir: string, prefix: string, depth: number) => {
172
- if (depth > maxDepth) return;
173
-
174
- try {
175
- const items = readdirSync(dir).filter(i =>
176
- !i.startsWith('.') && i !== 'node_modules' && i !== 'dist'
177
- );
178
-
179
- for (let i = 0; i < items.length; i++) {
180
- const item = items[i];
181
- const fullPath = join(dir, item);
182
- const isLast = i === items.length - 1;
183
- const connector = isLast ? '└── ' : '├── ';
184
-
185
- lines.push(prefix + connector + item);
186
-
187
- try {
188
- const stat = statSync(fullPath);
189
- if (stat.isDirectory()) {
190
- const newPrefix = prefix + (isLast ? ' ' : '│ ');
191
- walk(fullPath, newPrefix, depth + 1);
192
- }
193
- } catch {
194
- // Skip
195
- }
196
- }
197
- } catch {
198
- // Skip inaccessible
199
- }
200
- };
201
-
202
- lines.push(relative(process.cwd(), path) || '.');
203
- walk(path, '', 0);
204
-
205
- return { success: true, output: lines.join('\n') };
206
- }
207
-
208
- /**
209
- * List files in directory
210
- */
211
- private async listFiles(path: string, pattern: string): Promise<ToolResult> {
212
- const files: string[] = [];
213
-
214
- const scan = (dir: string) => {
215
- try {
216
- const items = readdirSync(dir);
217
-
218
- for (const item of items) {
219
- if (item === 'node_modules' || item === '.git') continue;
220
-
221
- const fullPath = join(dir, item);
222
-
223
- try {
224
- const stat = statSync(fullPath);
225
- if (stat.isFile() && this.matchPattern(item, pattern)) {
226
- files.push(relative(process.cwd(), fullPath));
227
- }
228
- } catch {
229
- // Skip
230
- }
231
- }
232
- } catch {
233
- // Skip
234
- }
235
- };
236
-
237
- scan(path);
238
-
239
- if (files.length === 0) {
240
- return { success: true, output: 'No files found' };
241
- }
242
-
243
- return { success: true, output: files.join('\n') };
244
- }
245
-
246
- /**
247
- * Simple glob-like pattern matching
248
- */
249
- private matchPattern(filename: string, pattern: string): boolean {
250
- if (pattern === '*') return true;
251
-
252
- // Convert glob to regex
253
- const regex = new RegExp(
254
- '^' + pattern
255
- .replace(/\./g, '\\.')
256
- .replace(/\*/g, '.*')
257
- .replace(/\?/g, '.') + '$',
258
- 'i'
259
- );
260
-
261
- return regex.test(filename);
262
- }
263
- }
@@ -1,181 +0,0 @@
1
- /**
2
- * Shell Tool - Execute shell commands with security checks
3
- */
4
-
5
- import { Tool, ToolResult, ToolContext } from '../core/types.js';
6
- import { PermissionChecker } from '../core/permissions.js';
7
- import { exec, spawn } from 'child_process';
8
- import { promisify } from 'util';
9
-
10
- // Shared permission checker instance
11
- let permissionChecker: PermissionChecker | null = null;
12
-
13
- export function setPermissionChecker(checker: PermissionChecker) {
14
- permissionChecker = checker;
15
- }
16
-
17
- const execAsync = promisify(exec);
18
-
19
- // Dangerous command patterns
20
- const DANGEROUS_PATTERNS = [
21
- /^rm\s+-rf\s+\//i, // rm -rf /
22
- /^del\s+\/f\s+\/s\s+\/q/i, // del /f /s /q
23
- /^format\s+[a-z]:/i, // format c:
24
- /^mkfs/i, // mkfs
25
- /^dd\s+if=/i, // dd if=
26
- /^shred/i, // shred
27
- /^cat\s+\/dev\/null\s*>/i, // cat /dev/null >
28
- />\s*\/dev\/sda/i, // Write to disk
29
- /^rm\s+-rf\s+\$HOME/i, // rm -rf $HOME
30
- /^rm\s+-rf\s+%USERPROFILE%/i // Windows user profile
31
- ];
32
-
33
- // Commands that need confirmation
34
- const CONFIRM_REQUIRED = [
35
- 'rm -rf',
36
- 'rmdir',
37
- 'del /s /q',
38
- 'format',
39
- 'mkfs',
40
- 'dd',
41
- 'shutdown',
42
- 'restart',
43
- 'init 0',
44
- 'init 6',
45
- 'poweroff',
46
- 'reboot'
47
- ];
48
-
49
- export class ShellTool implements Tool {
50
- name = 'shell';
51
- description = 'Execute shell commands and scripts. Use with caution - some commands require user confirmation.';
52
-
53
- /** S02: Input schema for shell command */
54
- inputSchema = {
55
- type: 'object' as const,
56
- properties: {
57
- command: { type: 'string', description: 'Shell command to execute' },
58
- cwd: { type: 'string', description: 'Working directory' },
59
- timeout: { type: 'number', description: 'Timeout in seconds', default: 30 }
60
- },
61
- required: ['command']
62
- };
63
-
64
- /** S02: Tool metadata — permission and category info */
65
- metadata = {
66
- permissions: ['execute', 'write', 'network'] as ('read' | 'write' | 'execute' | 'network')[],
67
- tags: ['shell', 'system', 'dangerous'],
68
- maxDuration: 120000, // 2 min max
69
- version: '1.0.0'
70
- };
71
-
72
- parameters = [
73
- { name: 'command', type: 'string', description: 'Shell command to execute', required: true },
74
- { name: 'cwd', type: 'string', description: 'Working directory', required: false },
75
- { name: 'timeout', type: 'number', description: 'Timeout in seconds', required: false }
76
- ];
77
-
78
- /**
79
- * Check if command is dangerous
80
- */
81
- private isDangerous(command: string): boolean {
82
- return DANGEROUS_PATTERNS.some(pattern => pattern.test(command.trim()));
83
- }
84
-
85
- /**
86
- * Check if command needs confirmation
87
- */
88
- private needsConfirmation(command: string): boolean {
89
- const lower = command.toLowerCase();
90
- return CONFIRM_REQUIRED.some(cmd => lower.includes(cmd.toLowerCase()));
91
- }
92
-
93
- /**
94
- * Validate command before execution
95
- */
96
- private validateCommand(command: string): string | null {
97
- if (!command || typeof command !== 'string') {
98
- return 'Command is required and must be a string';
99
- }
100
-
101
- const trimmed = command.trim();
102
-
103
- // Check for dangerous commands
104
- if (this.isDangerous(trimmed)) {
105
- return `Dangerous command blocked: ${trimmed.substring(0, 50)}...`;
106
- }
107
-
108
- // Check command length
109
- if (trimmed.length > 10000) {
110
- return 'Command too long (max 10000 characters)';
111
- }
112
-
113
- return null;
114
- }
115
-
116
- async execute(params: Record<string, any>, ctx?: ToolContext): Promise<ToolResult> {
117
- const { command, cwd, timeout = 30 } = params;
118
-
119
- // Validate command
120
- const validationError = this.validateCommand(command);
121
- if (validationError) {
122
- return { success: false, error: validationError };
123
- }
124
-
125
- // S03: 6-stage permission check
126
- if (permissionChecker) {
127
- const result = await permissionChecker.check('shell', params, ctx || {});
128
- if (!result.allowed) {
129
- return { success: false, error: `Permission denied: ${result.reason}` };
130
- }
131
- if (result.requiresConfirmation) {
132
- // Ask user for confirmation
133
- if (ctx?.confirmAction) {
134
- const confirmed = await ctx.confirmAction(
135
- `⚠️ Confirm command:\n ${command}\n\n[y] Yes [n] No`
136
- );
137
- if (!confirmed) {
138
- return { success: false, error: 'Command cancelled by user' };
139
- }
140
- }
141
- }
142
- } else if (ctx?.confirmAction && this.needsConfirmation(command)) {
143
- // Fallback confirmation
144
- const confirmed = await ctx.confirmAction(`Execute this command?\n ${command}\n\nType 'y' to confirm or 'n' to cancel:`);
145
- if (!confirmed) {
146
- return { success: false, error: 'Command cancelled by user' };
147
- }
148
- }
149
-
150
- try {
151
- const options: any = {
152
- timeout: timeout * 1000,
153
- maxBuffer: 10 * 1024 * 1024 // 10MB
154
- };
155
-
156
- if (cwd) {
157
- options.cwd = cwd;
158
- }
159
-
160
- const { stdout, stderr } = await execAsync(command, options);
161
- const stdoutStr = stdout?.toString() || '';
162
- const stderrStr = stderr?.toString() || '';
163
-
164
- // Combine output, prioritize stdout
165
- const output = stdoutStr + (stderrStr ? `\n[stderr]: ${stderrStr}` : '');
166
-
167
- return { success: true, output: output.trim() || '(command executed successfully with no output)' };
168
-
169
- } catch (error: any) {
170
- // Handle timeout
171
- if (error.killed) {
172
- return { success: false, error: 'Command timed out' };
173
- }
174
-
175
- return {
176
- success: false,
177
- error: error.message || 'Command execution failed'
178
- };
179
- }
180
- }
181
- }
@@ -1,202 +0,0 @@
1
- /**
2
- * Diff Preview - Show file changes before modification
3
- * Simple implementation without external dependencies
4
- */
5
-
6
- import { readFileSync, existsSync } from 'fs';
7
-
8
- export interface DiffResult {
9
- hasChanges: boolean;
10
- oldContent?: string;
11
- newContent?: string;
12
- diff?: string;
13
- preview?: string;
14
- }
15
-
16
- export class DiffPreview {
17
- /**
18
- * Compare old and new content and generate diff
19
- */
20
- static compare(oldContent: string, newContent: string): DiffResult {
21
- const oldLines = oldContent.split('\n');
22
- const newLines = newContent.split('\n');
23
-
24
- const diff = this.computeDiff(oldLines, newLines);
25
-
26
- if (diff.length === 0) {
27
- return { hasChanges: false };
28
- }
29
-
30
- return {
31
- hasChanges: true,
32
- oldContent,
33
- newContent,
34
- diff: this.formatDiff(diff),
35
- preview: this.formatPreview(diff)
36
- };
37
- }
38
-
39
- /**
40
- * Compute simple line-by-line diff
41
- */
42
- private static computeDiff(oldLines: string[], newLines: string[]): DiffChunk[] {
43
- const result: DiffChunk[] = [];
44
-
45
- // Simple LCS-based diff
46
- const lcs = this.longestCommonSubsequence(oldLines, newLines);
47
-
48
- let oldIdx = 0;
49
- let newIdx = 0;
50
- let lcsIdx = 0;
51
-
52
- while (oldIdx < oldLines.length || newIdx < newLines.length) {
53
- if (lcsIdx < lcs.length) {
54
- // Output removed lines from old
55
- while (oldIdx < oldLines.length && oldLines[oldIdx] !== lcs[lcsIdx]) {
56
- result.push({ type: 'removed', content: oldLines[oldIdx] });
57
- oldIdx++;
58
- }
59
-
60
- // Output added lines from new
61
- while (newIdx < newLines.length && newLines[newIdx] !== lcs[lcsIdx]) {
62
- result.push({ type: 'added', content: newLines[newIdx] });
63
- newIdx++;
64
- }
65
-
66
- // Output common line
67
- if (oldIdx < oldLines.length && newIdx < newLines.length) {
68
- result.push({ type: 'unchanged', content: oldLines[oldIdx] });
69
- oldIdx++;
70
- newIdx++;
71
- lcsIdx++;
72
- }
73
- } else {
74
- // Remaining lines
75
- while (oldIdx < oldLines.length) {
76
- result.push({ type: 'removed', content: oldLines[oldIdx] });
77
- oldIdx++;
78
- }
79
- while (newIdx < newLines.length) {
80
- result.push({ type: 'added', content: newLines[newIdx] });
81
- newIdx++;
82
- }
83
- }
84
- }
85
-
86
- return result;
87
- }
88
-
89
- /**
90
- * Find longest common subsequence
91
- */
92
- private static longestCommonSubsequence(a: string[], b: string[]): string[] {
93
- const m = a.length;
94
- const n = b.length;
95
- const dp: number[][] = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
96
-
97
- for (let i = 1; i <= m; i++) {
98
- for (let j = 1; j <= n; j++) {
99
- if (a[i - 1] === b[j - 1]) {
100
- dp[i][j] = dp[i - 1][j - 1] + 1;
101
- } else {
102
- dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
103
- }
104
- }
105
- }
106
-
107
- // Backtrack to find LCS
108
- const lcs: string[] = [];
109
- let i = m, j = n;
110
- while (i > 0 && j > 0) {
111
- if (a[i - 1] === b[j - 1]) {
112
- lcs.unshift(a[i - 1]);
113
- i--;
114
- j--;
115
- } else if (dp[i - 1][j] > dp[i][j - 1]) {
116
- i--;
117
- } else {
118
- j--;
119
- }
120
- }
121
-
122
- return lcs;
123
- }
124
-
125
- /**
126
- * Show diff between file and proposed changes
127
- */
128
- static diffFile(filePath: string, newContent: string): DiffResult {
129
- let oldContent = '';
130
-
131
- if (existsSync(filePath)) {
132
- try {
133
- oldContent = readFileSync(filePath, 'utf-8');
134
- } catch {
135
- // File exists but can't read
136
- }
137
- }
138
-
139
- return this.compare(oldContent, newContent);
140
- }
141
-
142
- /**
143
- * Format diff for display with colors
144
- */
145
- private static formatDiff(diff: DiffChunk[]): string {
146
- const lines: string[] = [];
147
- const green = '\x1b[32m';
148
- const red = '\x1b[31m';
149
- const gray = '\x1b[90m';
150
- const reset = '\x1b[0m';
151
-
152
- for (const chunk of diff) {
153
- if (chunk.type === 'added') {
154
- lines.push(`${green}+ ${chunk.content}${reset}`);
155
- } else if (chunk.type === 'removed') {
156
- lines.push(`${red}- ${chunk.content}${reset}`);
157
- } else {
158
- lines.push(`${gray} ${chunk.content}${reset}`);
159
- }
160
- }
161
-
162
- return lines.join('\n');
163
- }
164
-
165
- /**
166
- * Format a compact preview
167
- */
168
- private static formatPreview(diff: DiffChunk[]): string {
169
- let added = 0;
170
- let removed = 0;
171
-
172
- for (const chunk of diff) {
173
- if (chunk.type === 'added') added++;
174
- if (chunk.type === 'removed') removed++;
175
- }
176
-
177
- const parts: string[] = [];
178
- if (added > 0) parts.push(`+${added}`);
179
- if (removed > 0) parts.push(`-${removed}`);
180
-
181
- return parts.join(' ') || 'No changes';
182
- }
183
-
184
- /**
185
- * Check if changes are significant
186
- */
187
- static isSignificant(oldContent: string, newContent: string, threshold: number = 0.1): boolean {
188
- if (!oldContent && !newContent) return false;
189
- if (!oldContent || !newContent) return true;
190
-
191
- const oldLines = oldContent.split('\n').length;
192
- const newLines = newContent.split('\n').length;
193
-
194
- const changeRatio = Math.abs(newLines - oldLines) / oldLines;
195
- return changeRatio > threshold;
196
- }
197
- }
198
-
199
- interface DiffChunk {
200
- type: 'added' | 'removed' | 'unchanged';
201
- content: string;
202
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * Utils Module
3
- * Utility functions for diff, project context, and memory
4
- */
5
-
6
- export { DiffPreview, type DiffResult } from './diff-preview.js';
7
- export { ProjectContext, type ProjectInfo, type PackageJson } from './project-context.js';
8
- export { SessionMemory, type MemoryEntry } from './memory.js';