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.
- package/CHANGELOG.md +131 -0
- package/DEVELOPMENT.md +286 -0
- package/README.md +131 -30
- package/dist/core/ai-engine.d.ts +17 -0
- package/dist/core/ai-engine.d.ts.map +1 -1
- package/dist/core/ai-engine.js +23 -7
- package/dist/core/ai-engine.js.map +1 -1
- package/dist/index.js +18 -27
- package/dist/index.js.map +1 -1
- package/dist/repl/input.d.ts +8 -0
- package/dist/repl/input.d.ts.map +1 -1
- package/dist/repl/input.js +12 -0
- package/dist/repl/input.js.map +1 -1
- package/dist/repl/loop.d.ts +56 -0
- package/dist/repl/loop.d.ts.map +1 -1
- package/dist/repl/loop.js +333 -4
- package/dist/repl/loop.js.map +1 -1
- package/dist/repl/output.d.ts.map +1 -1
- package/dist/repl/output.js +3 -1
- package/dist/repl/output.js.map +1 -1
- package/dist/repl/welcome.js +1 -1
- package/dist/repl/welcome.js.map +1 -1
- package/docs/API_KEY_GUIDE.md +6 -0
- package/docs/FAQ.md +25 -3
- package/package.json +35 -3
- package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +0 -279
- package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +0 -564
- package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +0 -194
- package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +0 -24
- package/ROADMAP.md +0 -107
- package/src/agent/core.ts +0 -179
- package/src/agent/index.ts +0 -8
- package/src/agent/intent.ts +0 -181
- package/src/agent/streaming.ts +0 -132
- package/src/core/ai-engine.ts +0 -437
- package/src/core/cli.ts +0 -171
- package/src/core/config.ts +0 -147
- package/src/core/context-compactor.ts +0 -245
- package/src/core/hooks.ts +0 -196
- package/src/core/permissions.ts +0 -308
- package/src/core/session.ts +0 -165
- package/src/core/skills.ts +0 -208
- package/src/core/state.ts +0 -120
- package/src/core/subagent.ts +0 -195
- package/src/core/system-prompt.ts +0 -163
- package/src/core/tool-registry.ts +0 -157
- package/src/core/types.ts +0 -280
- package/src/index.ts +0 -544
- package/src/mcp/client.ts +0 -330
- package/src/repl/index.ts +0 -8
- package/src/repl/input.ts +0 -139
- package/src/repl/loop.ts +0 -280
- package/src/repl/output.ts +0 -222
- package/src/repl/welcome.ts +0 -296
- package/src/tools/file.ts +0 -117
- package/src/tools/git.ts +0 -132
- package/src/tools/index.ts +0 -48
- package/src/tools/search.ts +0 -263
- package/src/tools/shell.ts +0 -181
- package/src/utils/diff-preview.ts +0 -202
- package/src/utils/index.ts +0 -8
- package/src/utils/memory.ts +0 -223
- package/src/utils/project-context.ts +0 -207
- package/tsconfig.json +0 -19
package/src/utils/memory.ts
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Memory - Remember modifications during conversation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
6
|
-
import { join, dirname } from 'path';
|
|
7
|
-
import { homedir } from 'os';
|
|
8
|
-
|
|
9
|
-
export interface MemoryEntry {
|
|
10
|
-
id: string;
|
|
11
|
-
timestamp: number;
|
|
12
|
-
type: 'file_read' | 'file_write' | 'file_edit' | 'command' | 'git' | 'note';
|
|
13
|
-
description: string;
|
|
14
|
-
details: Record<string, any>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class SessionMemory {
|
|
18
|
-
private entries: MemoryEntry[] = [];
|
|
19
|
-
private sessionId: string;
|
|
20
|
-
private memoryFile?: string;
|
|
21
|
-
|
|
22
|
-
constructor(sessionId?: string) {
|
|
23
|
-
this.sessionId = sessionId || this.generateId();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Add an entry to memory
|
|
28
|
-
*/
|
|
29
|
-
add(entry: Omit<MemoryEntry, 'id' | 'timestamp'>): void {
|
|
30
|
-
this.entries.push({
|
|
31
|
-
...entry,
|
|
32
|
-
id: this.generateId(),
|
|
33
|
-
timestamp: Date.now()
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Record file read
|
|
39
|
-
*/
|
|
40
|
-
recordRead(path: string, lines?: number): void {
|
|
41
|
-
this.add({
|
|
42
|
-
type: 'file_read',
|
|
43
|
-
description: `Read ${path}`,
|
|
44
|
-
details: { path, lines }
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Record file write
|
|
50
|
-
*/
|
|
51
|
-
recordWrite(path: string, content?: string): void {
|
|
52
|
-
this.add({
|
|
53
|
-
type: 'file_write',
|
|
54
|
-
description: `Wrote ${path}`,
|
|
55
|
-
details: { path, size: content?.length || 0 }
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Record file edit
|
|
61
|
-
*/
|
|
62
|
-
recordEdit(path: string, operation: string): void {
|
|
63
|
-
this.add({
|
|
64
|
-
type: 'file_edit',
|
|
65
|
-
description: `Edited ${path}: ${operation}`,
|
|
66
|
-
details: { path, operation }
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Record command execution
|
|
72
|
-
*/
|
|
73
|
-
recordCommand(command: string, output?: string): void {
|
|
74
|
-
this.add({
|
|
75
|
-
type: 'command',
|
|
76
|
-
description: `Executed: ${command}`,
|
|
77
|
-
details: { command, output: output?.slice(0, 200) }
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Record git operation
|
|
83
|
-
*/
|
|
84
|
-
recordGit(operation: string, result: string): void {
|
|
85
|
-
this.add({
|
|
86
|
-
type: 'git',
|
|
87
|
-
description: `Git ${operation}`,
|
|
88
|
-
details: { operation, result: result.slice(0, 200) }
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Record a note
|
|
94
|
-
*/
|
|
95
|
-
recordNote(note: string): void {
|
|
96
|
-
this.add({
|
|
97
|
-
type: 'note',
|
|
98
|
-
description: note,
|
|
99
|
-
details: {}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get all entries
|
|
105
|
-
*/
|
|
106
|
-
getEntries(): MemoryEntry[] {
|
|
107
|
-
return [...this.entries];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Get recent entries
|
|
112
|
-
*/
|
|
113
|
-
getRecent(count: number = 10): MemoryEntry[] {
|
|
114
|
-
return this.entries.slice(-count);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get entries by type
|
|
119
|
-
*/
|
|
120
|
-
getByType(type: MemoryEntry['type']): MemoryEntry[] {
|
|
121
|
-
return this.entries.filter(e => e.type === type);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Search entries
|
|
126
|
-
*/
|
|
127
|
-
search(query: string): MemoryEntry[] {
|
|
128
|
-
const lower = query.toLowerCase();
|
|
129
|
-
return this.entries.filter(e =>
|
|
130
|
-
e.description.toLowerCase().includes(lower) ||
|
|
131
|
-
JSON.stringify(e.details).toLowerCase().includes(lower)
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Get summary
|
|
137
|
-
*/
|
|
138
|
-
getSummary(): string {
|
|
139
|
-
if (this.entries.length === 0) {
|
|
140
|
-
return 'No actions recorded yet.';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const types = this.entries.reduce((acc, e) => {
|
|
144
|
-
acc[e.type] = (acc[e.type] || 0) + 1;
|
|
145
|
-
return acc;
|
|
146
|
-
}, {} as Record<string, number>);
|
|
147
|
-
|
|
148
|
-
const lines = ['📋 Session Summary:'];
|
|
149
|
-
|
|
150
|
-
for (const [type, count] of Object.entries(types)) {
|
|
151
|
-
const emoji = {
|
|
152
|
-
'file_read': '📖',
|
|
153
|
-
'file_write': '✍️',
|
|
154
|
-
'file_edit': '📝',
|
|
155
|
-
'command': '⚡',
|
|
156
|
-
'git': '📊',
|
|
157
|
-
'note': '📌'
|
|
158
|
-
}[type] || '•';
|
|
159
|
-
|
|
160
|
-
lines.push(` ${emoji} ${type}: ${count}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return lines.join('\n');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Export to file
|
|
168
|
-
*/
|
|
169
|
-
save(): void {
|
|
170
|
-
if (!this.memoryFile) {
|
|
171
|
-
const homeDir = homedir();
|
|
172
|
-
const memDir = join(homeDir, '.thatgfsj', 'memory');
|
|
173
|
-
|
|
174
|
-
if (!existsSync(memDir)) {
|
|
175
|
-
mkdirSync(memDir, { recursive: true });
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
this.memoryFile = join(memDir, `${this.sessionId}.json`);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
writeFileSync(this.memoryFile, JSON.stringify({
|
|
182
|
-
sessionId: this.sessionId,
|
|
183
|
-
entries: this.entries,
|
|
184
|
-
savedAt: Date.now()
|
|
185
|
-
}, null, 2));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Load from file
|
|
190
|
-
*/
|
|
191
|
-
load(sessionId: string): boolean {
|
|
192
|
-
const homeDir = homedir();
|
|
193
|
-
const memoryFile = join(homeDir, '.thatgfsj', 'memory', `${sessionId}.json`);
|
|
194
|
-
|
|
195
|
-
if (!existsSync(memoryFile)) {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
const data = JSON.parse(readFileSync(memoryFile, 'utf-8'));
|
|
201
|
-
this.entries = data.entries || [];
|
|
202
|
-
this.sessionId = data.sessionId;
|
|
203
|
-
this.memoryFile = memoryFile;
|
|
204
|
-
return true;
|
|
205
|
-
} catch {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Clear memory
|
|
212
|
-
*/
|
|
213
|
-
clear(): void {
|
|
214
|
-
this.entries = [];
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Generate unique ID
|
|
219
|
-
*/
|
|
220
|
-
private generateId(): string {
|
|
221
|
-
return `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project Context - Auto-detect and read project files
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
6
|
-
import { join, relative } from 'path';
|
|
7
|
-
|
|
8
|
-
export interface ProjectInfo {
|
|
9
|
-
root: string;
|
|
10
|
-
name: string;
|
|
11
|
-
language: string;
|
|
12
|
-
buildTool?: string;
|
|
13
|
-
gitIgnore: string[];
|
|
14
|
-
files: string[];
|
|
15
|
-
hasGit: boolean;
|
|
16
|
-
packageJson?: PackageJson;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface PackageJson {
|
|
20
|
-
name: string;
|
|
21
|
-
version: string;
|
|
22
|
-
description?: string;
|
|
23
|
-
scripts?: Record<string, string>;
|
|
24
|
-
dependencies?: Record<string, string>;
|
|
25
|
-
devDependencies?: Record<string, string>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export class ProjectContext {
|
|
29
|
-
private root: string;
|
|
30
|
-
|
|
31
|
-
constructor(root: string = process.cwd()) {
|
|
32
|
-
this.root = root;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Detect project type and gather context
|
|
37
|
-
*/
|
|
38
|
-
async detect(): Promise<ProjectInfo> {
|
|
39
|
-
const info: ProjectInfo = {
|
|
40
|
-
root: this.root,
|
|
41
|
-
name: this.getProjectName(),
|
|
42
|
-
language: this.detectLanguage(),
|
|
43
|
-
buildTool: this.detectBuildTool(),
|
|
44
|
-
gitIgnore: this.readGitIgnore(),
|
|
45
|
-
files: this.listFiles(),
|
|
46
|
-
hasGit: existsSync(join(this.root, '.git'))
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const pkg = this.readPackageJson();
|
|
50
|
-
if (pkg) {
|
|
51
|
-
info.packageJson = pkg;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return info;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Get project name
|
|
59
|
-
*/
|
|
60
|
-
private getProjectName(): string {
|
|
61
|
-
const pkg = this.readPackageJson();
|
|
62
|
-
if (pkg?.name) {
|
|
63
|
-
return pkg.name;
|
|
64
|
-
}
|
|
65
|
-
return this.root.split(/[\\/]/).pop() || 'unknown';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Detect primary language
|
|
70
|
-
*/
|
|
71
|
-
private detectLanguage(): string {
|
|
72
|
-
const langCounts: Record<string, number> = {};
|
|
73
|
-
|
|
74
|
-
const scan = (dir: string, depth: number = 0) => {
|
|
75
|
-
if (depth > 3) return;
|
|
76
|
-
try {
|
|
77
|
-
const items = readdirSync(dir);
|
|
78
|
-
for (const item of items) {
|
|
79
|
-
if (['node_modules', '.git', 'dist'].includes(item)) continue;
|
|
80
|
-
const fullPath = join(dir, item);
|
|
81
|
-
try {
|
|
82
|
-
const stat = statSync(fullPath);
|
|
83
|
-
if (stat.isDirectory()) {
|
|
84
|
-
scan(fullPath, depth + 1);
|
|
85
|
-
} else if (stat.isFile()) {
|
|
86
|
-
const ext = item.split('.').pop()?.toLowerCase();
|
|
87
|
-
if (ext) langCounts[ext] = (langCounts[ext] || 0) + 1;
|
|
88
|
-
}
|
|
89
|
-
} catch { /* skip */ }
|
|
90
|
-
}
|
|
91
|
-
} catch { /* skip */ }
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
scan(this.root);
|
|
95
|
-
|
|
96
|
-
const extToLang: Record<string, string> = {
|
|
97
|
-
'ts': 'TypeScript', 'tsx': 'TypeScript',
|
|
98
|
-
'js': 'JavaScript', 'jsx': 'JavaScript', 'mjs': 'JavaScript',
|
|
99
|
-
'py': 'Python', 'java': 'Java', 'go': 'Go', 'rs': 'Rust',
|
|
100
|
-
'c': 'C/C++', 'cpp': 'C/C++', 'h': 'C/C++', 'hpp': 'C/C++',
|
|
101
|
-
'cs': 'C#', 'rb': 'Ruby', 'php': 'PHP',
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
for (const [ext, count] of Object.entries(langCounts)) {
|
|
105
|
-
const lang = extToLang[ext];
|
|
106
|
-
if (lang && count > 0) return lang;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return 'Unknown';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Detect build tool
|
|
114
|
-
*/
|
|
115
|
-
private detectBuildTool(): string | undefined {
|
|
116
|
-
if (existsSync(join(this.root, 'package.json'))) return 'npm/yarn/pnpm';
|
|
117
|
-
if (existsSync(join(this.root, 'Cargo.toml'))) return 'Cargo';
|
|
118
|
-
if (existsSync(join(this.root, 'go.mod'))) return 'Go';
|
|
119
|
-
if (existsSync(join(this.root, 'pom.xml'))) return 'Maven';
|
|
120
|
-
if (existsSync(join(this.root, 'build.gradle'))) return 'Gradle';
|
|
121
|
-
if (existsSync(join(this.root, 'Makefile'))) return 'Make';
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Read .gitignore
|
|
127
|
-
*/
|
|
128
|
-
private readGitIgnore(): string[] {
|
|
129
|
-
const path = join(this.root, '.gitignore');
|
|
130
|
-
if (!existsSync(path)) return [];
|
|
131
|
-
try {
|
|
132
|
-
return readFileSync(path, 'utf-8').split('\n')
|
|
133
|
-
.map(l => l.trim()).filter(l => l && !l.startsWith('#'));
|
|
134
|
-
} catch { return []; }
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Read package.json
|
|
139
|
-
*/
|
|
140
|
-
private readPackageJson(): PackageJson | null {
|
|
141
|
-
const path = join(this.root, 'package.json');
|
|
142
|
-
if (!existsSync(path)) return null;
|
|
143
|
-
try {
|
|
144
|
-
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
145
|
-
} catch { return null; }
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* List project files
|
|
150
|
-
*/
|
|
151
|
-
private listFiles(maxFiles: number = 100): string[] {
|
|
152
|
-
const files: string[] = [];
|
|
153
|
-
const gitIgnore = this.readGitIgnore();
|
|
154
|
-
|
|
155
|
-
const scan = (dir: string, depth: number = 0) => {
|
|
156
|
-
if (depth > 4 || files.length >= maxFiles) return;
|
|
157
|
-
try {
|
|
158
|
-
const items = readdirSync(dir);
|
|
159
|
-
for (const item of items) {
|
|
160
|
-
if (files.length >= maxFiles) break;
|
|
161
|
-
if (this.isIgnored(item, gitIgnore)) continue;
|
|
162
|
-
const fullPath = join(dir, item);
|
|
163
|
-
const relPath = relative(this.root, fullPath);
|
|
164
|
-
try {
|
|
165
|
-
const stat = statSync(fullPath);
|
|
166
|
-
if (stat.isDirectory()) {
|
|
167
|
-
files.push(relPath + '/');
|
|
168
|
-
scan(fullPath, depth + 1);
|
|
169
|
-
} else if (stat.isFile()) {
|
|
170
|
-
files.push(relPath);
|
|
171
|
-
}
|
|
172
|
-
} catch { /* skip */ }
|
|
173
|
-
}
|
|
174
|
-
} catch { /* skip */ }
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
scan(this.root);
|
|
178
|
-
return files;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Check if ignored
|
|
183
|
-
*/
|
|
184
|
-
private isIgnored(name: string, gitIgnore: string[]): boolean {
|
|
185
|
-
const common = ['node_modules', '.git', 'dist', 'build', '__pycache__', '.env'];
|
|
186
|
-
if (common.includes(name)) return true;
|
|
187
|
-
return gitIgnore.includes(name);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get context summary
|
|
192
|
-
*/
|
|
193
|
-
async getContextSummary(): Promise<string> {
|
|
194
|
-
const info = await this.detect();
|
|
195
|
-
const lines = [`📁 Project: ${info.name}`, `💻 Language: ${info.language}`];
|
|
196
|
-
if (info.buildTool) lines.push(`🔧 Build: ${info.buildTool}`);
|
|
197
|
-
if (info.hasGit) lines.push(`📊 Git: Yes`);
|
|
198
|
-
if (info.packageJson) {
|
|
199
|
-
lines.push(`📦 Package: ${info.packageJson.name}@${info.packageJson.version}`);
|
|
200
|
-
if (info.packageJson.scripts) {
|
|
201
|
-
lines.push(` Scripts: ${Object.keys(info.packageJson.scripts).slice(0, 5).join(', ')}`);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
lines.push(`📁 Files: ${info.files.length} files scanned`);
|
|
205
|
-
return lines.join('\n');
|
|
206
|
-
}
|
|
207
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": true,
|
|
15
|
-
"sourceMap": true
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|