@sean.holung/minicode 0.1.0

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 (54) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +241 -0
  3. package/dist/src/agent/agent.js +209 -0
  4. package/dist/src/agent/config.js +151 -0
  5. package/dist/src/agent/types.js +1 -0
  6. package/dist/src/index.js +138 -0
  7. package/dist/src/indexer/cache.js +121 -0
  8. package/dist/src/indexer/code-map.js +92 -0
  9. package/dist/src/indexer/plugin-loader.js +78 -0
  10. package/dist/src/indexer/plugins/typescript.js +327 -0
  11. package/dist/src/indexer/project-index.js +145 -0
  12. package/dist/src/indexer/types.js +1 -0
  13. package/dist/src/model/client.js +374 -0
  14. package/dist/src/prompt/system-prompt.js +91 -0
  15. package/dist/src/safety/guardrails.js +55 -0
  16. package/dist/src/session/session.js +95 -0
  17. package/dist/src/tools/edit-file.js +73 -0
  18. package/dist/src/tools/find-references.js +52 -0
  19. package/dist/src/tools/get-dependencies.js +56 -0
  20. package/dist/src/tools/helpers.js +42 -0
  21. package/dist/src/tools/list-files.js +63 -0
  22. package/dist/src/tools/read-file.js +79 -0
  23. package/dist/src/tools/read-symbol.js +96 -0
  24. package/dist/src/tools/registry.js +68 -0
  25. package/dist/src/tools/run-command.js +92 -0
  26. package/dist/src/tools/search-code-map.js +72 -0
  27. package/dist/src/tools/search.js +153 -0
  28. package/dist/src/tools/write-file.js +44 -0
  29. package/dist/src/ui/app.js +31 -0
  30. package/dist/src/ui/cli-ink.js +168 -0
  31. package/dist/src/ui/components/activity-pane.js +35 -0
  32. package/dist/src/ui/components/header-bar.js +6 -0
  33. package/dist/src/ui/components/input-composer.js +46 -0
  34. package/dist/src/ui/components/tool-timeline-item.js +37 -0
  35. package/dist/src/ui/events.js +1 -0
  36. package/dist/src/ui/state/ui-store.js +89 -0
  37. package/dist/src/ui/theme.js +23 -0
  38. package/dist/tests/agent.test.js +130 -0
  39. package/dist/tests/cache.test.js +37 -0
  40. package/dist/tests/config.test.js +37 -0
  41. package/dist/tests/dependency-graph.test.js +27 -0
  42. package/dist/tests/file-tools.test.js +73 -0
  43. package/dist/tests/find-references.test.js +30 -0
  44. package/dist/tests/get-dependencies.test.js +35 -0
  45. package/dist/tests/guardrails.test.js +18 -0
  46. package/dist/tests/indexer.test.js +201 -0
  47. package/dist/tests/model-client-openai.test.js +84 -0
  48. package/dist/tests/read-symbol.test.js +83 -0
  49. package/dist/tests/search-code-map.test.js +30 -0
  50. package/dist/tests/session.test.js +37 -0
  51. package/dist/tests/system-prompt.test.js +82 -0
  52. package/dist/tests/test-utils.js +18 -0
  53. package/dist/tests/tool-registry.test.js +41 -0
  54. package/package.json +43 -0
@@ -0,0 +1,327 @@
1
+ import path from "node:path";
2
+ import ts from "typescript";
3
+ const EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
4
+ function getLine(sourceFile, position) {
5
+ const { line } = sourceFile.getLineAndCharacterOfPosition(position);
6
+ return line + 1;
7
+ }
8
+ function extractSignature(node, sourceFile) {
9
+ const fullText = node.getText(sourceFile);
10
+ const sourceText = sourceFile.getText();
11
+ const getSigEnd = () => {
12
+ if (ts.isFunctionDeclaration(node) ||
13
+ ts.isMethodDeclaration(node) ||
14
+ ts.isConstructorDeclaration(node)) {
15
+ return node.body?.getStart(sourceFile) ?? null;
16
+ }
17
+ if (ts.isArrowFunction(node) && ts.isBlock(node.body)) {
18
+ return node.body.getStart(sourceFile);
19
+ }
20
+ if (ts.isClassDeclaration(node)) {
21
+ const text = sourceText.slice(node.getStart(), node.getEnd());
22
+ const braceIdx = text.indexOf("{");
23
+ return braceIdx >= 0 ? node.getStart() + braceIdx : null;
24
+ }
25
+ return null;
26
+ };
27
+ const sigEnd = getSigEnd();
28
+ if (sigEnd !== null) {
29
+ const sig = sourceText.slice(node.getStart(), sigEnd).trim();
30
+ return sig.endsWith(")") ? sig : sig;
31
+ }
32
+ return fullText;
33
+ }
34
+ function isExported(node) {
35
+ if (ts.canHaveModifiers(node)) {
36
+ const mods = ts.getModifiers(node);
37
+ if (mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) {
38
+ return true;
39
+ }
40
+ }
41
+ return false;
42
+ }
43
+ function extractJSDoc(node, sourceFile) {
44
+ const nodeWithJSDoc = node;
45
+ const jsDoc = nodeWithJSDoc.jsDoc?.[0];
46
+ if (!jsDoc)
47
+ return undefined;
48
+ const fullText = jsDoc.getText(sourceFile);
49
+ const cleaned = fullText
50
+ .replace(/^\s*\/\*\*/, "")
51
+ .replace(/\*\/\s*$/, "")
52
+ .replace(/^\s*\*\s?/gm, "")
53
+ .trim();
54
+ return cleaned.length > 0 ? cleaned : undefined;
55
+ }
56
+ function createPlugin() {
57
+ return {
58
+ name: "typescript",
59
+ extensions: EXTENSIONS,
60
+ canIndex(filePath) {
61
+ const lower = filePath.toLowerCase();
62
+ return EXTENSIONS.some((ext) => lower.endsWith(ext));
63
+ },
64
+ indexFile(filePath, content) {
65
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
66
+ const symbols = [];
67
+ let currentClass = null;
68
+ function visit(node) {
69
+ if (ts.isFunctionDeclaration(node) && node.name) {
70
+ const name = node.name.getText(sourceFile);
71
+ const qualifiedName = currentClass ? `${currentClass}.${name}` : name;
72
+ const doc = extractJSDoc(node, sourceFile);
73
+ symbols.push({
74
+ name,
75
+ qualifiedName,
76
+ kind: "function",
77
+ filePath,
78
+ startLine: getLine(sourceFile, node.getStart(sourceFile)),
79
+ endLine: getLine(sourceFile, node.getEnd()),
80
+ signature: extractSignature(node, sourceFile),
81
+ exported: isExported(node),
82
+ dependencies: [],
83
+ ...(doc && { docComment: doc }),
84
+ });
85
+ return;
86
+ }
87
+ if (ts.isClassDeclaration(node) && node.name) {
88
+ const name = node.name.getText(sourceFile);
89
+ const prevClass = currentClass;
90
+ currentClass = name;
91
+ const doc = extractJSDoc(node, sourceFile);
92
+ symbols.push({
93
+ name,
94
+ qualifiedName: name,
95
+ kind: "class",
96
+ filePath,
97
+ startLine: getLine(sourceFile, node.getStart(sourceFile)),
98
+ endLine: getLine(sourceFile, node.getEnd()),
99
+ signature: extractSignature(node, sourceFile),
100
+ exported: isExported(node),
101
+ dependencies: [],
102
+ ...(doc && { docComment: doc }),
103
+ });
104
+ ts.forEachChild(node, visit);
105
+ currentClass = prevClass;
106
+ return;
107
+ }
108
+ if (ts.isConstructorDeclaration(node)) {
109
+ const qualifiedName = currentClass
110
+ ? `${currentClass}.constructor`
111
+ : "constructor";
112
+ const doc = extractJSDoc(node, sourceFile);
113
+ symbols.push({
114
+ name: "constructor",
115
+ qualifiedName,
116
+ kind: "method",
117
+ filePath,
118
+ startLine: getLine(sourceFile, node.getStart(sourceFile)),
119
+ endLine: getLine(sourceFile, node.getEnd()),
120
+ signature: extractSignature(node, sourceFile),
121
+ exported: false,
122
+ dependencies: [],
123
+ ...(doc && { docComment: doc }),
124
+ });
125
+ return;
126
+ }
127
+ if (ts.isMethodDeclaration(node) && node.name) {
128
+ const name = ts.isComputedPropertyName(node.name)
129
+ ? "[computed]"
130
+ : node.name.getText(sourceFile);
131
+ const qualifiedName = currentClass ? `${currentClass}.${name}` : name;
132
+ const doc = extractJSDoc(node, sourceFile);
133
+ symbols.push({
134
+ name,
135
+ qualifiedName,
136
+ kind: "method",
137
+ filePath,
138
+ startLine: getLine(sourceFile, node.getStart(sourceFile)),
139
+ endLine: getLine(sourceFile, node.getEnd()),
140
+ signature: extractSignature(node, sourceFile),
141
+ exported: false,
142
+ dependencies: [],
143
+ ...(doc && { docComment: doc }),
144
+ });
145
+ return;
146
+ }
147
+ if (ts.isInterfaceDeclaration(node) && node.name) {
148
+ const name = node.name.getText(sourceFile);
149
+ const doc = extractJSDoc(node, sourceFile);
150
+ symbols.push({
151
+ name,
152
+ qualifiedName: name,
153
+ kind: "interface",
154
+ filePath,
155
+ startLine: getLine(sourceFile, node.getStart(sourceFile)),
156
+ endLine: getLine(sourceFile, node.getEnd()),
157
+ signature: node.getText(sourceFile).split("\n")[0] ?? `interface ${name}`,
158
+ exported: isExported(node),
159
+ dependencies: [],
160
+ ...(doc && { docComment: doc }),
161
+ });
162
+ return;
163
+ }
164
+ if (ts.isTypeAliasDeclaration(node) && node.name) {
165
+ const name = node.name.getText(sourceFile);
166
+ const doc = extractJSDoc(node, sourceFile);
167
+ symbols.push({
168
+ name,
169
+ qualifiedName: name,
170
+ kind: "type",
171
+ filePath,
172
+ startLine: getLine(sourceFile, node.getStart(sourceFile)),
173
+ endLine: getLine(sourceFile, node.getEnd()),
174
+ signature: node.getText(sourceFile).split("\n")[0] ?? `type ${name}`,
175
+ exported: isExported(node),
176
+ dependencies: [],
177
+ ...(doc && { docComment: doc }),
178
+ });
179
+ return;
180
+ }
181
+ if (ts.isVariableStatement(node)) {
182
+ const exported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
183
+ const doc = extractJSDoc(node, sourceFile);
184
+ for (const decl of node.declarationList.declarations) {
185
+ const init = decl.initializer;
186
+ if (ts.isIdentifier(decl.name) &&
187
+ init &&
188
+ (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
189
+ const name = decl.name.getText(sourceFile);
190
+ symbols.push({
191
+ name,
192
+ qualifiedName: name,
193
+ kind: "function",
194
+ filePath,
195
+ startLine: getLine(sourceFile, decl.getStart(sourceFile)),
196
+ endLine: getLine(sourceFile, decl.getEnd()),
197
+ signature: extractSignature(decl, sourceFile),
198
+ exported,
199
+ dependencies: [],
200
+ ...(doc && { docComment: doc }),
201
+ });
202
+ }
203
+ }
204
+ return;
205
+ }
206
+ ts.forEachChild(node, visit);
207
+ }
208
+ visit(sourceFile);
209
+ return symbols;
210
+ },
211
+ resolveDependencies(symbols, projectFiles) {
212
+ const symbolSet = new Set(symbols.map((s) => s.qualifiedName));
213
+ const edges = [];
214
+ const rootDir = "/project";
215
+ function addEdge(from, to, kind) {
216
+ if (symbolSet.has(to)) {
217
+ edges.push({ from, to, kind });
218
+ }
219
+ }
220
+ function collectTypeRefs(node, from) {
221
+ if (ts.isTypeReferenceNode(node)) {
222
+ const name = node.typeName.getText();
223
+ addEdge(from, name, "references");
224
+ if (ts.isQualifiedName(node.typeName)) {
225
+ const left = node.typeName.left;
226
+ if (ts.isIdentifier(left)) {
227
+ addEdge(from, left.getText(), "references");
228
+ }
229
+ }
230
+ }
231
+ ts.forEachChild(node, (n) => collectTypeRefs(n, from));
232
+ }
233
+ function collectCalls(node, from) {
234
+ if (ts.isCallExpression(node)) {
235
+ const expr = node.expression;
236
+ if (ts.isIdentifier(expr)) {
237
+ addEdge(from, expr.getText(), "calls");
238
+ }
239
+ else if (ts.isNewExpression(expr) && expr.expression) {
240
+ if (ts.isIdentifier(expr.expression)) {
241
+ addEdge(from, expr.expression.getText(), "calls");
242
+ }
243
+ }
244
+ }
245
+ ts.forEachChild(node, (n) => collectCalls(n, from));
246
+ }
247
+ for (const [filePath, content] of projectFiles) {
248
+ const fullPath = path.join(rootDir, filePath);
249
+ const sourceFile = ts.createSourceFile(fullPath, content, ts.ScriptTarget.Latest, true);
250
+ let currentClass = null;
251
+ function visit(node) {
252
+ if (ts.isClassDeclaration(node) && node.name) {
253
+ const name = node.name.getText(sourceFile);
254
+ const prevClass = currentClass;
255
+ currentClass = name;
256
+ if (symbolSet.has(name)) {
257
+ for (const clause of node.heritageClauses ?? []) {
258
+ for (const type of clause.types) {
259
+ const expr = type.expression;
260
+ const target = ts.isIdentifier(expr)
261
+ ? expr.getText()
262
+ : ts.isPropertyAccessExpression(expr)
263
+ ? expr.expression.getText()
264
+ : expr.getText();
265
+ const kind = clause.token === ts.SyntaxKind.ExtendsKeyword
266
+ ? "extends"
267
+ : "implements";
268
+ addEdge(name, target, kind);
269
+ }
270
+ }
271
+ }
272
+ ts.forEachChild(node, visit);
273
+ currentClass = prevClass;
274
+ return;
275
+ }
276
+ if (ts.isConstructorDeclaration(node)) {
277
+ const from = currentClass ? `${currentClass}.constructor` : "constructor";
278
+ if (symbolSet.has(from)) {
279
+ collectTypeRefs(node, from);
280
+ collectCalls(node, from);
281
+ }
282
+ return;
283
+ }
284
+ if (ts.isMethodDeclaration(node) && node.name) {
285
+ const name = ts.isComputedPropertyName(node.name)
286
+ ? "[computed]"
287
+ : node.name.getText(sourceFile);
288
+ const from = currentClass ? `${currentClass}.${name}` : name;
289
+ if (symbolSet.has(from)) {
290
+ collectTypeRefs(node, from);
291
+ collectCalls(node, from);
292
+ }
293
+ return;
294
+ }
295
+ if (ts.isFunctionDeclaration(node) && node.name) {
296
+ const name = node.name.getText(sourceFile);
297
+ const from = currentClass ? `${currentClass}.${name}` : name;
298
+ if (symbolSet.has(from)) {
299
+ collectTypeRefs(node, from);
300
+ collectCalls(node, from);
301
+ }
302
+ return;
303
+ }
304
+ if (ts.isVariableStatement(node)) {
305
+ for (const decl of node.declarationList.declarations) {
306
+ const init = decl.initializer;
307
+ if (ts.isIdentifier(decl.name) &&
308
+ init &&
309
+ (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
310
+ const name = decl.name.getText(sourceFile);
311
+ if (symbolSet.has(name)) {
312
+ collectTypeRefs(decl, name);
313
+ collectCalls(decl, name);
314
+ }
315
+ }
316
+ }
317
+ return;
318
+ }
319
+ ts.forEachChild(node, visit);
320
+ }
321
+ visit(sourceFile);
322
+ }
323
+ return edges;
324
+ },
325
+ };
326
+ }
327
+ export const typescriptPlugin = createPlugin();
@@ -0,0 +1,145 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { generateCodeMap } from "./code-map.js";
4
+ import { getPluginForFile, loadPlugins } from "./plugin-loader.js";
5
+ const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build", "coverage"]);
6
+ async function collectSourceFiles(dir, root, files) {
7
+ const entries = await readdir(dir, { withFileTypes: true });
8
+ for (const entry of entries) {
9
+ const fullPath = path.join(dir, entry.name);
10
+ if (entry.isDirectory()) {
11
+ if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
12
+ await collectSourceFiles(fullPath, root, files);
13
+ }
14
+ continue;
15
+ }
16
+ if (entry.isFile()) {
17
+ const ext = path.extname(entry.name).toLowerCase();
18
+ if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
19
+ files.push(path.relative(root, fullPath));
20
+ }
21
+ }
22
+ }
23
+ }
24
+ export function createProjectIndex(symbols, files, dependencyEdges, plugins, projectFiles, workspaceRoot) {
25
+ return {
26
+ symbols,
27
+ files,
28
+ dependencyEdges,
29
+ plugins,
30
+ projectFiles,
31
+ workspaceRoot,
32
+ getSymbol(name) {
33
+ const direct = symbols.get(name);
34
+ if (direct)
35
+ return direct;
36
+ for (const sym of symbols.values()) {
37
+ if (sym.name === name || sym.qualifiedName === name)
38
+ return sym;
39
+ }
40
+ return undefined;
41
+ },
42
+ getSymbolsInFile(filePath) {
43
+ return files.get(filePath) ?? [];
44
+ },
45
+ getDependencyCone(symbolName, depth = 2) {
46
+ const target = symbols.get(symbolName) ?? [...symbols.values()].find((s) => s.name === symbolName || s.qualifiedName === symbolName);
47
+ if (!target)
48
+ return [];
49
+ const result = new Map();
50
+ result.set(target.qualifiedName, target);
51
+ let frontier = new Set([target.qualifiedName]);
52
+ for (let d = 0; d < depth; d += 1) {
53
+ const next = new Set();
54
+ for (const from of frontier) {
55
+ for (const edge of dependencyEdges) {
56
+ if (edge.from === from) {
57
+ const dep = symbols.get(edge.to) ?? [...symbols.values()].find((s) => s.qualifiedName === edge.to || s.name === edge.to);
58
+ if (dep && !result.has(dep.qualifiedName)) {
59
+ result.set(dep.qualifiedName, dep);
60
+ next.add(dep.qualifiedName);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ frontier = next;
66
+ }
67
+ return [...result.values()];
68
+ },
69
+ getCodeMap(tokenBudget) {
70
+ return generateCodeMap(files, tokenBudget, dependencyEdges);
71
+ },
72
+ reindexFile(filePath, content) {
73
+ const relPath = path.isAbsolute(filePath)
74
+ ? path.relative(workspaceRoot, filePath)
75
+ : path.normalize(filePath);
76
+ const plugin = getPluginForFile(relPath, plugins);
77
+ if (!plugin)
78
+ return;
79
+ const oldSymbols = files.get(relPath) ?? [];
80
+ for (const sym of oldSymbols) {
81
+ symbols.delete(sym.qualifiedName);
82
+ }
83
+ files.delete(relPath);
84
+ projectFiles.set(relPath, content);
85
+ const extracted = plugin.indexFile(relPath, content);
86
+ for (const sym of extracted) {
87
+ symbols.set(sym.qualifiedName, sym);
88
+ const existing = files.get(relPath) ?? [];
89
+ existing.push(sym);
90
+ files.set(relPath, existing);
91
+ }
92
+ for (const p of plugins) {
93
+ if (p.resolveDependencies) {
94
+ const allSymbols = [...symbols.values()];
95
+ const edges = p.resolveDependencies(allSymbols, projectFiles);
96
+ dependencyEdges.splice(0, dependencyEdges.length, ...edges);
97
+ break;
98
+ }
99
+ }
100
+ },
101
+ };
102
+ }
103
+ /**
104
+ * Build a project index by scanning the workspace and running all matching plugins.
105
+ */
106
+ export async function buildProjectIndex(workspaceRoot) {
107
+ const plugins = await loadPlugins(workspaceRoot);
108
+ const root = path.resolve(workspaceRoot);
109
+ const sourceFiles = [];
110
+ await collectSourceFiles(root, root, sourceFiles);
111
+ const symbols = new Map();
112
+ const files = new Map();
113
+ const projectFiles = new Map();
114
+ for (const relPath of sourceFiles) {
115
+ const plugin = getPluginForFile(relPath, plugins);
116
+ if (!plugin)
117
+ continue;
118
+ const absPath = path.join(root, relPath);
119
+ let content;
120
+ try {
121
+ content = await readFile(absPath, "utf8");
122
+ }
123
+ catch {
124
+ continue;
125
+ }
126
+ projectFiles.set(relPath, content);
127
+ const extracted = plugin.indexFile(relPath, content);
128
+ for (const sym of extracted) {
129
+ symbols.set(sym.qualifiedName, sym);
130
+ const existing = files.get(relPath) ?? [];
131
+ existing.push(sym);
132
+ files.set(relPath, existing);
133
+ }
134
+ }
135
+ let dependencyEdges = [];
136
+ for (const plugin of plugins) {
137
+ if (plugin.resolveDependencies) {
138
+ const allSymbols = [...symbols.values()];
139
+ const edges = plugin.resolveDependencies(allSymbols, projectFiles);
140
+ dependencyEdges = edges;
141
+ break;
142
+ }
143
+ }
144
+ return createProjectIndex(symbols, files, dependencyEdges, plugins, projectFiles, root);
145
+ }
@@ -0,0 +1 @@
1
+ export {};