stackai 0.1.3 → 0.1.4
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/dist/cli.js +185 -11
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -173,6 +173,40 @@ function toLLMError(err) {
|
|
|
173
173
|
// ../core/dist/file-agent.js
|
|
174
174
|
import { promises as fs } from "fs";
|
|
175
175
|
import path from "path";
|
|
176
|
+
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
177
|
+
"node_modules",
|
|
178
|
+
".git",
|
|
179
|
+
"dist",
|
|
180
|
+
".next",
|
|
181
|
+
".turbo",
|
|
182
|
+
"build",
|
|
183
|
+
"out",
|
|
184
|
+
"coverage",
|
|
185
|
+
".cache"
|
|
186
|
+
]);
|
|
187
|
+
function globToRegExp(glob) {
|
|
188
|
+
let re = "";
|
|
189
|
+
for (let i = 0; i < glob.length; i++) {
|
|
190
|
+
const c = glob[i];
|
|
191
|
+
if (c === "*") {
|
|
192
|
+
if (glob[i + 1] === "*") {
|
|
193
|
+
re += ".*";
|
|
194
|
+
i++;
|
|
195
|
+
if (glob[i + 1] === "/")
|
|
196
|
+
i++;
|
|
197
|
+
} else {
|
|
198
|
+
re += "[^/]*";
|
|
199
|
+
}
|
|
200
|
+
} else if (c === "?") {
|
|
201
|
+
re += "[^/]";
|
|
202
|
+
} else if (".+^${}()|[]\\".includes(c)) {
|
|
203
|
+
re += `\\${c}`;
|
|
204
|
+
} else {
|
|
205
|
+
re += c;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return new RegExp(`^${re}$`);
|
|
209
|
+
}
|
|
176
210
|
var FileAgent = class {
|
|
177
211
|
root;
|
|
178
212
|
constructor(cwd) {
|
|
@@ -220,6 +254,70 @@ var FileAgent = class {
|
|
|
220
254
|
async createDir(relPath) {
|
|
221
255
|
await fs.mkdir(this.resolve(relPath), { recursive: true });
|
|
222
256
|
}
|
|
257
|
+
/** Recursively yield every file path under `dir`, skipping ignored dirs. */
|
|
258
|
+
async *walkFiles(dir) {
|
|
259
|
+
let entries;
|
|
260
|
+
try {
|
|
261
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
262
|
+
} catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
for (const e of entries) {
|
|
266
|
+
if (e.isDirectory()) {
|
|
267
|
+
if (IGNORE_DIRS.has(e.name))
|
|
268
|
+
continue;
|
|
269
|
+
yield* this.walkFiles(path.join(dir, e.name));
|
|
270
|
+
} else {
|
|
271
|
+
yield path.join(dir, e.name);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
rel(abs) {
|
|
276
|
+
return path.relative(this.root, abs).split(path.sep).join("/");
|
|
277
|
+
}
|
|
278
|
+
/** Search file contents (regex). Returns `path:line: text` matches. */
|
|
279
|
+
async grep(pattern, maxResults = 80) {
|
|
280
|
+
let re;
|
|
281
|
+
try {
|
|
282
|
+
re = new RegExp(pattern);
|
|
283
|
+
} catch {
|
|
284
|
+
re = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
285
|
+
}
|
|
286
|
+
const out = [];
|
|
287
|
+
for await (const abs of this.walkFiles(this.root)) {
|
|
288
|
+
if (out.length >= maxResults)
|
|
289
|
+
break;
|
|
290
|
+
let content;
|
|
291
|
+
try {
|
|
292
|
+
content = await fs.readFile(abs, "utf8");
|
|
293
|
+
} catch {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const lines = content.split("\n");
|
|
297
|
+
for (let i = 0; i < lines.length; i++) {
|
|
298
|
+
if (re.test(lines[i])) {
|
|
299
|
+
out.push(`${this.rel(abs)}:${i + 1}: ${lines[i].trim().slice(0, 160)}`);
|
|
300
|
+
if (out.length >= maxResults)
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
/** Find files whose relative path matches a glob pattern. */
|
|
308
|
+
async glob(pattern, maxResults = 200) {
|
|
309
|
+
const re = globToRegExp(pattern);
|
|
310
|
+
const out = [];
|
|
311
|
+
for await (const abs of this.walkFiles(this.root)) {
|
|
312
|
+
const rel = this.rel(abs);
|
|
313
|
+
if (re.test(rel)) {
|
|
314
|
+
out.push(rel);
|
|
315
|
+
if (out.length >= maxResults)
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return out.sort((a, b) => a.localeCompare(b));
|
|
320
|
+
}
|
|
223
321
|
};
|
|
224
322
|
|
|
225
323
|
// ../core/dist/system-prompt.js
|
|
@@ -227,10 +325,14 @@ var SYSTEM_PROMPT = `You are StackAI, an autonomous coding agent operating insid
|
|
|
227
325
|
|
|
228
326
|
You can read, write, and edit files by calling the provided tools. Be DECISIVE and make the FEWEST tool calls possible \u2014 every call is a slow round-trip.
|
|
229
327
|
|
|
328
|
+
Tools: read_file, write_file, edit_file, list_files, create_dir, search_files (regex content search), find_files (glob).
|
|
329
|
+
|
|
230
330
|
How to work:
|
|
231
|
-
1. To
|
|
232
|
-
2. To
|
|
233
|
-
3.
|
|
331
|
+
1. To explore an unfamiliar project, use find_files (e.g. "src/**/*.ts") and search_files (regex) instead of reading everything.
|
|
332
|
+
2. To create a new file: call write_file once. Do NOT list_files or read_file first.
|
|
333
|
+
3. To change an existing file: call read_file ONCE to see its contents, then make ONE edit_file (or one write_file) call to apply the change.
|
|
334
|
+
4. If project instructions were provided (from STACKAI.md / AGENTS.md), follow them.
|
|
335
|
+
5. When the task is done, stop calling tools and reply with ONE short sentence summarizing what you changed.
|
|
234
336
|
|
|
235
337
|
Hard rules:
|
|
236
338
|
- Make each distinct tool call AT MOST ONCE. Never repeat the same read or edit.
|
|
@@ -307,6 +409,30 @@ var TOOLS = [
|
|
|
307
409
|
required: ["path"]
|
|
308
410
|
}
|
|
309
411
|
}
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
type: "function",
|
|
415
|
+
function: {
|
|
416
|
+
name: "search_files",
|
|
417
|
+
description: "Search file contents across the project with a regular expression. Returns matching `path:line: text`.",
|
|
418
|
+
parameters: {
|
|
419
|
+
type: "object",
|
|
420
|
+
properties: { pattern: { type: "string" } },
|
|
421
|
+
required: ["pattern"]
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
type: "function",
|
|
427
|
+
function: {
|
|
428
|
+
name: "find_files",
|
|
429
|
+
description: "Find files by glob pattern (e.g. `src/**/*.ts`, `*.json`). Returns matching paths.",
|
|
430
|
+
parameters: {
|
|
431
|
+
type: "object",
|
|
432
|
+
properties: { pattern: { type: "string" } },
|
|
433
|
+
required: ["pattern"]
|
|
434
|
+
}
|
|
435
|
+
}
|
|
310
436
|
}
|
|
311
437
|
];
|
|
312
438
|
var AgentRunner = class {
|
|
@@ -318,16 +444,40 @@ var AgentRunner = class {
|
|
|
318
444
|
}
|
|
319
445
|
async run({ prompt, cwd, onStep }) {
|
|
320
446
|
const files = new FileAgent(cwd);
|
|
321
|
-
const messages =
|
|
322
|
-
|
|
323
|
-
{ role: "user", content: prompt }
|
|
324
|
-
];
|
|
447
|
+
const messages = await this.systemMessages(files);
|
|
448
|
+
messages.push({ role: "user", content: prompt });
|
|
325
449
|
return this.runLoop(messages, files, onStep);
|
|
326
450
|
}
|
|
327
451
|
/** Start a stateful chat session that retains history across prompts. */
|
|
328
452
|
session(cwd) {
|
|
329
453
|
return new AgentSession(this, cwd);
|
|
330
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Build the leading system messages: the base system prompt plus any
|
|
457
|
+
* project context found in STACKAI.md / AGENTS.md at the project root.
|
|
458
|
+
* @internal
|
|
459
|
+
*/
|
|
460
|
+
async systemMessages(files) {
|
|
461
|
+
const messages = [
|
|
462
|
+
{ role: "system", content: SYSTEM_PROMPT }
|
|
463
|
+
];
|
|
464
|
+
for (const name of ["STACKAI.md", "AGENTS.md"]) {
|
|
465
|
+
try {
|
|
466
|
+
const ctx = (await files.readFile(name)).trim();
|
|
467
|
+
if (ctx) {
|
|
468
|
+
messages.push({
|
|
469
|
+
role: "system",
|
|
470
|
+
content: `Project instructions from ${name}:
|
|
471
|
+
|
|
472
|
+
${ctx}`
|
|
473
|
+
});
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return messages;
|
|
480
|
+
}
|
|
331
481
|
/**
|
|
332
482
|
* Run the tool-call loop over an existing message history (mutated in place).
|
|
333
483
|
* Shared by one-shot run() and the interactive AgentSession.
|
|
@@ -409,6 +559,14 @@ var AgentRunner = class {
|
|
|
409
559
|
case "create_dir":
|
|
410
560
|
await files.createDir(str(args.path));
|
|
411
561
|
return `Created ${str(args.path)}`;
|
|
562
|
+
case "search_files": {
|
|
563
|
+
const matches = await files.grep(str(args.pattern));
|
|
564
|
+
return matches.join("\n") || "(no matches)";
|
|
565
|
+
}
|
|
566
|
+
case "find_files": {
|
|
567
|
+
const found = await files.glob(str(args.pattern));
|
|
568
|
+
return found.join("\n") || "(no files matched)";
|
|
569
|
+
}
|
|
412
570
|
default:
|
|
413
571
|
return `Error: unknown tool ${name}`;
|
|
414
572
|
}
|
|
@@ -422,16 +580,19 @@ function str(value) {
|
|
|
422
580
|
}
|
|
423
581
|
var AgentSession = class {
|
|
424
582
|
runner;
|
|
425
|
-
messages = [
|
|
426
|
-
{ role: "system", content: SYSTEM_PROMPT }
|
|
427
|
-
];
|
|
583
|
+
messages = [];
|
|
428
584
|
files;
|
|
585
|
+
initialized = false;
|
|
429
586
|
constructor(runner, cwd) {
|
|
430
587
|
this.runner = runner;
|
|
431
588
|
this.files = new FileAgent(cwd);
|
|
432
589
|
}
|
|
433
590
|
/** Send a user message; the agent acts with full prior context. */
|
|
434
591
|
async send(prompt, onStep) {
|
|
592
|
+
if (!this.initialized) {
|
|
593
|
+
this.messages = await this.runner.systemMessages(this.files);
|
|
594
|
+
this.initialized = true;
|
|
595
|
+
}
|
|
435
596
|
this.messages.push({ role: "user", content: prompt });
|
|
436
597
|
return this.runner.runLoop(this.messages, this.files, onStep);
|
|
437
598
|
}
|
|
@@ -552,6 +713,14 @@ function toolEntries(step) {
|
|
|
552
713
|
return [{ kind: "tool", color: ACCENT, label: `List(${path3 || "."})` }];
|
|
553
714
|
case "create_dir":
|
|
554
715
|
return [{ kind: "tool", color: ACCENT, label: `Create(${path3}/)` }];
|
|
716
|
+
case "search_files":
|
|
717
|
+
return [
|
|
718
|
+
{ kind: "tool", color: ACCENT, label: `Search(${String(args.pattern ?? "")})` }
|
|
719
|
+
];
|
|
720
|
+
case "find_files":
|
|
721
|
+
return [
|
|
722
|
+
{ kind: "tool", color: ACCENT, label: `Find(${String(args.pattern ?? "")})` }
|
|
723
|
+
];
|
|
555
724
|
default:
|
|
556
725
|
return [{ kind: "tool", color: ACCENT, label: step.name }];
|
|
557
726
|
}
|
|
@@ -581,6 +750,11 @@ function applyStep(entries, step) {
|
|
|
581
750
|
const n = step.detail ? splitLines(step.detail).length : 0;
|
|
582
751
|
return [...entries, { kind: "sub", color: "gray", text: `read ${n} lines` }];
|
|
583
752
|
}
|
|
753
|
+
if (step.name === "search_files" || step.name === "find_files") {
|
|
754
|
+
const empty = /^\(no /.test(step.detail);
|
|
755
|
+
const n = empty ? 0 : splitLines(step.detail).length;
|
|
756
|
+
return [...entries, { kind: "sub", color: "gray", text: `${n} results` }];
|
|
757
|
+
}
|
|
584
758
|
}
|
|
585
759
|
return entries;
|
|
586
760
|
}
|
|
@@ -832,7 +1006,7 @@ function LoginView() {
|
|
|
832
1006
|
|
|
833
1007
|
// src/cli.tsx
|
|
834
1008
|
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
835
|
-
var VERSION = "0.1.
|
|
1009
|
+
var VERSION = "0.1.4";
|
|
836
1010
|
var HELP = `
|
|
837
1011
|
StackAI \u2014 AI coding agent in your terminal
|
|
838
1012
|
|