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.
Files changed (2) hide show
  1. package/dist/cli.js +185 -11
  2. 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 create a new file: call write_file once. Do NOT list_files or read_file first.
232
- 2. 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.
233
- 3. When the task is done, stop calling tools and reply with ONE short sentence summarizing what you changed.
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
- { role: "system", content: SYSTEM_PROMPT },
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.3";
1009
+ var VERSION = "0.1.4";
836
1010
  var HELP = `
837
1011
  StackAI \u2014 AI coding agent in your terminal
838
1012
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackai",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "StackAI — AI coding agent in your terminal. Read, write, and edit code with AI.",
5
5
  "type": "module",
6
6
  "bin": {