@cryer/star-cli 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.
@@ -433,6 +433,12 @@ function matchAllowRule(rule, req) {
433
433
  return target !== void 0 && globMatch(rule.pattern, target);
434
434
  }
435
435
  function isAllowedByRules(rules, req) {
436
+ return matchesAnyRule(rules, req);
437
+ }
438
+ function isDeniedByRules(rules, req) {
439
+ return matchesAnyRule(rules, req);
440
+ }
441
+ function matchesAnyRule(rules, req) {
436
442
  return rules.some((raw) => {
437
443
  const rule = parseAllowRule(raw);
438
444
  return rule !== null && matchAllowRule(rule, req);
@@ -484,7 +490,7 @@ function isSensitivePath(p) {
484
490
  }
485
491
  return base === ".env" || base.startsWith(".env.") || base === "id_rsa" || base.endsWith(".pem");
486
492
  }
487
- function checkPermission(mode, req, ctx, allowRules = []) {
493
+ function checkPermission(mode, req, ctx, allowRules = [], denyRules = []) {
488
494
  if (mode === "yolo") {
489
495
  return "allow";
490
496
  }
@@ -504,6 +510,9 @@ function checkPermission(mode, req, ctx, allowRules = []) {
504
510
  return "deny";
505
511
  }
506
512
  }
513
+ if (isDeniedByRules(denyRules, req)) {
514
+ return "deny";
515
+ }
507
516
  if (mode === "auto") {
508
517
  return "allow";
509
518
  }
@@ -745,6 +754,9 @@ function globalConfigPath() {
745
754
  function sessionsDir() {
746
755
  return path4.join(starHome(), "sessions");
747
756
  }
757
+ function userMemoryPath() {
758
+ return path4.join(starHome(), "MEMORY.md");
759
+ }
748
760
  function projectConfigPath(cwd) {
749
761
  return path4.join(cwd, ".star", "config.toml");
750
762
  }
@@ -1197,10 +1209,11 @@ import path9 from "path";
1197
1209
  import { z as z4 } from "zod";
1198
1210
 
1199
1211
  // src/tools/fs/util.ts
1212
+ import { readFileSync as readFileSync3, statSync as statSync3 } from "fs";
1200
1213
  import { readdir, stat } from "fs/promises";
1201
1214
  import path8 from "path";
1202
1215
  var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist"]);
1203
- async function walkFiles(root, skipDirs = SKIP_DIRS) {
1216
+ async function walkFiles(root, skipDirs = SKIP_DIRS, options = {}) {
1204
1217
  const out = [];
1205
1218
  async function walk(dir, relBase) {
1206
1219
  const entries = await readdir(dir, { withFileTypes: true }).catch(() => null);
@@ -1211,10 +1224,14 @@ async function walkFiles(root, skipDirs = SKIP_DIRS) {
1211
1224
  const abs = path8.join(dir, entry.name);
1212
1225
  const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
1213
1226
  if (entry.isDirectory()) {
1214
- if (!skipDirs.has(entry.name)) {
1215
- await walk(abs, rel);
1227
+ if (skipDirs.has(entry.name) || options.ignore?.(abs, true)) {
1228
+ continue;
1216
1229
  }
1230
+ await walk(abs, rel);
1217
1231
  } else if (entry.isFile()) {
1232
+ if (options.ignore?.(abs, false)) {
1233
+ continue;
1234
+ }
1218
1235
  try {
1219
1236
  const st = await stat(abs);
1220
1237
  out.push({ abs, rel, mtimeMs: st.mtimeMs });
@@ -1263,6 +1280,69 @@ function matchesGlob(pattern, relPath) {
1263
1280
  }
1264
1281
  return globToRegExp(normalized).test(relPath);
1265
1282
  }
1283
+ var IGNORE_FILE = ".starignore";
1284
+ var ignoreCache = /* @__PURE__ */ new Map();
1285
+ function parseIgnorePatterns(body) {
1286
+ const out = [];
1287
+ for (const raw of body.split("\n")) {
1288
+ const line = raw.trim();
1289
+ if (!line || line.startsWith("#")) {
1290
+ continue;
1291
+ }
1292
+ if (line.endsWith("/")) {
1293
+ const pattern = line.slice(0, -1);
1294
+ if (pattern) {
1295
+ out.push({ pattern, dirOnly: true });
1296
+ }
1297
+ } else {
1298
+ out.push({ pattern: line, dirOnly: false });
1299
+ }
1300
+ }
1301
+ return out;
1302
+ }
1303
+ function loadIgnorePatterns(dir) {
1304
+ const file = path8.join(dir, IGNORE_FILE);
1305
+ let mtimeMs;
1306
+ try {
1307
+ mtimeMs = statSync3(file).mtimeMs;
1308
+ } catch {
1309
+ return [];
1310
+ }
1311
+ const hit = ignoreCache.get(file);
1312
+ if (hit && hit.mtimeMs === mtimeMs) {
1313
+ return hit.patterns;
1314
+ }
1315
+ let patterns = [];
1316
+ try {
1317
+ patterns = parseIgnorePatterns(readFileSync3(file, "utf8"));
1318
+ } catch {
1319
+ patterns = [];
1320
+ }
1321
+ ignoreCache.set(file, { mtimeMs, patterns });
1322
+ return patterns;
1323
+ }
1324
+ function createIgnorePredicate(cwd) {
1325
+ const patterns = loadIgnorePatterns(cwd);
1326
+ if (patterns.length === 0) {
1327
+ return void 0;
1328
+ }
1329
+ const root = path8.resolve(cwd);
1330
+ return (absPath, isDir) => {
1331
+ const rel = path8.relative(root, absPath).split(path8.sep).join("/");
1332
+ if (!rel || rel.startsWith("..")) {
1333
+ return false;
1334
+ }
1335
+ for (const { pattern, dirOnly } of patterns) {
1336
+ if (dirOnly && !isDir) {
1337
+ continue;
1338
+ }
1339
+ if (matchesGlob(pattern, rel)) {
1340
+ return true;
1341
+ }
1342
+ }
1343
+ return false;
1344
+ };
1345
+ }
1266
1346
  function isSensitivePath2(filePath) {
1267
1347
  const base = path8.basename(filePath);
1268
1348
  if (base === ".env.example" || base === ".env.sample" || base === ".env.template") {
@@ -1292,7 +1372,7 @@ var globTool = {
1292
1372
  parameters: schema3,
1293
1373
  async execute(args, ctx) {
1294
1374
  const root = path9.resolve(ctx.cwd, args.path ?? ".");
1295
- const files = await walkFiles(root);
1375
+ const files = await walkFiles(root, void 0, { ignore: createIgnorePredicate(ctx.cwd) });
1296
1376
  const matched = files.filter((f) => matchesGlob(args.pattern, f.rel)).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, MAX_RESULTS);
1297
1377
  if (matched.length === 0) {
1298
1378
  return { content: `No files matched pattern: ${args.pattern}` };
@@ -1331,7 +1411,7 @@ var grepTool = {
1331
1411
  if (st.isFile()) {
1332
1412
  files = [{ abs: root, rel: path10.basename(root), mtimeMs: st.mtimeMs }];
1333
1413
  } else {
1334
- files = await walkFiles(root);
1414
+ files = await walkFiles(root, void 0, { ignore: createIgnorePredicate(ctx.cwd) });
1335
1415
  }
1336
1416
  } catch {
1337
1417
  return { content: `Path not found: ${args.path ?? "."}`, isError: true };
@@ -1959,7 +2039,7 @@ function createSubagentTool(deps) {
1959
2039
  description: z12.string().optional().describe("Short label for the subtask, shown in the result header.")
1960
2040
  }),
1961
2041
  async execute(args, ctx) {
1962
- const { AgentLoop: AgentLoop2 } = await import("./loop-R3RQCNFT.js");
2042
+ const { AgentLoop: AgentLoop2 } = await import("./loop-ADVWZA62.js");
1963
2043
  const child = new AgentLoop2({
1964
2044
  model: deps.model,
1965
2045
  registry: createDefaultRegistry(),
@@ -2016,6 +2096,64 @@ ${report || "(subagent produced no text output)"}` };
2016
2096
  };
2017
2097
  }
2018
2098
 
2099
+ // src/agent/user-memory.ts
2100
+ import { appendFileSync, mkdirSync, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
2101
+ import path14 from "path";
2102
+ import { z as z13 } from "zod";
2103
+ var USER_MEMORY_MAX_CHARS = 32 * 1024;
2104
+ var REMEMBER_TEXT_MAX_CHARS = 500;
2105
+ var cache3 = /* @__PURE__ */ new Map();
2106
+ function readUserMemory(home = starHome()) {
2107
+ const file = path14.join(home, "MEMORY.md");
2108
+ let mtimeMs;
2109
+ try {
2110
+ mtimeMs = statSync4(file).mtimeMs;
2111
+ } catch {
2112
+ return null;
2113
+ }
2114
+ const hit = cache3.get(file);
2115
+ if (hit && hit.mtimeMs === mtimeMs) return hit.block;
2116
+ let block = null;
2117
+ try {
2118
+ let body = readFileSync4(file, "utf8").trim();
2119
+ if (body) {
2120
+ if (body.length > USER_MEMORY_MAX_CHARS) {
2121
+ body = `${body.slice(0, USER_MEMORY_MAX_CHARS)}
2122
+
2123
+ [MEMORY.md truncated to ${USER_MEMORY_MAX_CHARS} characters]`;
2124
+ }
2125
+ block = [
2126
+ "# User memory (MEMORY.md)",
2127
+ "The following notes were saved by the user across sessions. Respect them unless they conflict with the current request.",
2128
+ body
2129
+ ].join("\n");
2130
+ }
2131
+ } catch {
2132
+ block = null;
2133
+ }
2134
+ cache3.set(file, { mtimeMs, block });
2135
+ return block;
2136
+ }
2137
+ function appendUserMemory(text, home = starHome()) {
2138
+ mkdirSync(home, { recursive: true });
2139
+ appendFileSync(path14.join(home, "MEMORY.md"), `- ${text}
2140
+ `, "utf8");
2141
+ }
2142
+ function createRememberTool(deps = {}) {
2143
+ return {
2144
+ name: "remember",
2145
+ description: `Save a note to the user's long-term memory (${userMemoryPath()}), injected into every future session. ONLY use when the user explicitly asks to remember or save something. Never use autonomously.`,
2146
+ permission: "write",
2147
+ parameters: z13.object({
2148
+ text: z13.string().max(REMEMBER_TEXT_MAX_CHARS).describe("The note to save, as a single concise sentence.")
2149
+ }),
2150
+ async execute(args) {
2151
+ appendUserMemory(args.text, deps.home);
2152
+ return { content: `Saved to user memory: ${args.text}` };
2153
+ }
2154
+ };
2155
+ }
2156
+
2019
2157
  // src/agent/loop.ts
2020
2158
  var PLAN_MODE_PROMPT = "\n\nYou are currently in PLAN MODE. Research the task using only the read-only tools available to you, then present a concrete, step-by-step implementation plan as your final answer. You must not modify files, run shell commands, or otherwise change the system \u2014 write/exec tools are unavailable in this mode. Do not ask the user to run commands for you; note manual steps in the plan instead. The user will review your plan and approve it before any execution begins.";
2021
2159
  var AgentLoop = class {
@@ -2041,6 +2179,7 @@ var AgentLoop = class {
2041
2179
  this.bindSnapshotHooks(opts.sessionStore);
2042
2180
  }
2043
2181
  opts.registry?.register(createSkillTool({ cwd: opts.cwd }));
2182
+ opts.registry?.register(createRememberTool());
2044
2183
  const depth = opts.subagentDepth ?? 0;
2045
2184
  if (opts.registry && depth < MAX_SUBAGENT_DEPTH) {
2046
2185
  opts.registry.register(
@@ -2285,8 +2424,9 @@ var AgentLoop = class {
2285
2424
  // /model switch) replaced the history with one that has none. Git context
2286
2425
  // (branch, dirty count, recent commits) is refreshed here too, so the model
2287
2426
  // always sees the current repo state; any git failure is silently skipped.
2288
- // Project memory (AGENTS.md in the cwd) is appended as a delimited block;
2289
- // readProjectMemory caches by mtime, so this stays cheap per turn. The
2427
+ // Project memory (AGENTS.md in the cwd) and user memory (~/.star-cli/
2428
+ // MEMORY.md) are appended as delimited blocks; both cache by mtime, so this
2429
+ // stays cheap per turn. The
2290
2430
  // skills listing is refreshed here as well, so skills added mid-session
2291
2431
  // show up in the next turn's system prompt.
2292
2432
  syncSystemMessage() {
@@ -2297,6 +2437,8 @@ var AgentLoop = class {
2297
2437
  if (plan) parts.push(PLAN_MODE_PROMPT.trim());
2298
2438
  const memory = readProjectMemory(this.opts.cwd);
2299
2439
  if (memory) parts.push(memory);
2440
+ const userMemory = readUserMemory();
2441
+ if (userMemory) parts.push(userMemory);
2300
2442
  const skills = discoverSkills(this.opts.cwd);
2301
2443
  if (skills.length > 0) parts.push(formatSkillsBlock(skills));
2302
2444
  const git = getGitSummary(this.opts.cwd);
@@ -2378,7 +2520,8 @@ ${summary}`
2378
2520
  config.permissionMode,
2379
2521
  { toolName: call.name, args: call.args, level: tool.permission },
2380
2522
  { cwd },
2381
- config.permissions.allow
2523
+ config.permissions.allow,
2524
+ config.permissions.deny
2382
2525
  );
2383
2526
  if (decision === "deny") {
2384
2527
  return { content: `Permission denied for tool "${call.name}".`, isError: true };
@@ -2450,12 +2593,14 @@ export {
2450
2593
  starHome,
2451
2594
  globalConfigPath,
2452
2595
  sessionsDir,
2596
+ userMemoryPath,
2453
2597
  projectConfigPath,
2454
2598
  discoverSkills,
2455
2599
  defaultTaskManager,
2456
2600
  resolveShell,
2457
2601
  bashTool,
2458
2602
  SKIP_DIRS,
2603
+ createIgnorePredicate,
2459
2604
  isSensitivePath2 as isSensitivePath,
2460
2605
  formatTaskList,
2461
2606
  formatTaskStarted,
@@ -2463,7 +2608,8 @@ export {
2463
2608
  TodoStore,
2464
2609
  formatTodos,
2465
2610
  createDefaultRegistry,
2611
+ appendUserMemory,
2466
2612
  PLAN_MODE_PROMPT,
2467
2613
  AgentLoop
2468
2614
  };
2469
- //# sourceMappingURL=chunk-MJ2O7NWI.js.map
2615
+ //# sourceMappingURL=chunk-GN236FD4.js.map