@pikaa-ai/pikaa 0.3.13 → 0.3.15

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/index.js CHANGED
@@ -1119,17 +1119,139 @@ class ExecutionPolicyError extends GroupyError {
1119
1119
  }
1120
1120
  }
1121
1121
  // src/auth/store.ts
1122
- import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
1123
- import { resolve } from "path";
1122
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2, unlinkSync } from "fs";
1123
+ import { resolve as resolve2 } from "path";
1124
+
1125
+ // src/config/paths.ts
1126
+ import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
1127
+ import { resolve, join } from "path";
1124
1128
  import { homedir } from "os";
1129
+ function getPikaaHomeDir() {
1130
+ const envDir = process.env.PIKAA_HOME || process.env.GROUPY_HOME;
1131
+ if (envDir) {
1132
+ return resolve(envDir);
1133
+ }
1134
+ return resolve(homedir(), ".pikaa");
1135
+ }
1136
+ function getLegacyGroupyHomeDir() {
1137
+ if (process.env.GROUPY_HOME) {
1138
+ return resolve(process.env.GROUPY_HOME);
1139
+ }
1140
+ return resolve(homedir(), ".groupy");
1141
+ }
1142
+ var hasMigrated = false;
1143
+ function copyDirRecursiveSync(src, dest) {
1144
+ if (!existsSync(src))
1145
+ return;
1146
+ if (!existsSync(dest)) {
1147
+ mkdirSync(dest, { recursive: true });
1148
+ }
1149
+ const entries = readdirSync(src);
1150
+ for (const entry of entries) {
1151
+ const srcPath = join(src, entry);
1152
+ const destPath = join(dest, entry);
1153
+ const stat = statSync(srcPath);
1154
+ if (stat.isDirectory()) {
1155
+ copyDirRecursiveSync(srcPath, destPath);
1156
+ } else if (!existsSync(destPath)) {
1157
+ try {
1158
+ copyFileSync(srcPath, destPath);
1159
+ } catch {}
1160
+ }
1161
+ }
1162
+ }
1163
+ function ensurePikaaHomeMigrated(force = false) {
1164
+ const pikaaHome = getPikaaHomeDir();
1165
+ const legacyHome = getLegacyGroupyHomeDir();
1166
+ try {
1167
+ if (!existsSync(pikaaHome)) {
1168
+ mkdirSync(pikaaHome, { recursive: true });
1169
+ }
1170
+ } catch {}
1171
+ if (hasMigrated && !force) {
1172
+ return pikaaHome;
1173
+ }
1174
+ hasMigrated = true;
1175
+ try {
1176
+ if (existsSync(legacyHome) && legacyHome !== pikaaHome) {
1177
+ const legacyCreds = join(legacyHome, "credentials.json");
1178
+ const pikaaCreds = join(pikaaHome, "credentials.json");
1179
+ if (existsSync(legacyCreds) && !existsSync(pikaaCreds)) {
1180
+ copyFileSync(legacyCreds, pikaaCreds);
1181
+ }
1182
+ const legacyThreads = join(legacyHome, "groupy_threads.db");
1183
+ const pikaaThreads = join(pikaaHome, "pikaa_threads.db");
1184
+ if (existsSync(legacyThreads) && !existsSync(pikaaThreads)) {
1185
+ copyFileSync(legacyThreads, pikaaThreads);
1186
+ }
1187
+ const legacyRules = join(legacyHome, "groupy_rules.db");
1188
+ const pikaaRules = join(pikaaHome, "pikaa_rules.db");
1189
+ if (existsSync(legacyRules) && !existsSync(pikaaRules)) {
1190
+ copyFileSync(legacyRules, pikaaRules);
1191
+ }
1192
+ const legacyGraph = join(legacyHome, "agent_graph.db");
1193
+ const pikaaGraph = join(pikaaHome, "agent_graph.db");
1194
+ if (existsSync(legacyGraph) && !existsSync(pikaaGraph)) {
1195
+ copyFileSync(legacyGraph, pikaaGraph);
1196
+ }
1197
+ const legacyMemories = join(legacyHome, "memories.md");
1198
+ const pikaaMemories = join(pikaaHome, "memories.md");
1199
+ if (existsSync(legacyMemories) && !existsSync(pikaaMemories)) {
1200
+ copyFileSync(legacyMemories, pikaaMemories);
1201
+ }
1202
+ copyDirRecursiveSync(join(legacyHome, "skills"), join(pikaaHome, "skills"));
1203
+ copyDirRecursiveSync(join(legacyHome, "templates"), join(pikaaHome, "templates"));
1204
+ }
1205
+ } catch {}
1206
+ return pikaaHome;
1207
+ }
1208
+ function getCredentialsPath() {
1209
+ ensurePikaaHomeMigrated();
1210
+ return join(getPikaaHomeDir(), "credentials.json");
1211
+ }
1212
+ function getThreadsDbPath() {
1213
+ ensurePikaaHomeMigrated();
1214
+ return join(getPikaaHomeDir(), "pikaa_threads.db");
1215
+ }
1216
+ function getPrefixRulesDbPath() {
1217
+ ensurePikaaHomeMigrated();
1218
+ return join(getPikaaHomeDir(), "pikaa_rules.db");
1219
+ }
1220
+ function getAgentGraphDbPath() {
1221
+ ensurePikaaHomeMigrated();
1222
+ return join(getPikaaHomeDir(), "agent_graph.db");
1223
+ }
1224
+ function getGlobalSkillsDir() {
1225
+ ensurePikaaHomeMigrated();
1226
+ return join(getPikaaHomeDir(), "skills");
1227
+ }
1228
+ function getGlobalTemplatesDir() {
1229
+ ensurePikaaHomeMigrated();
1230
+ return join(getPikaaHomeDir(), "templates");
1231
+ }
1232
+ function getGlobalMemoriesPath() {
1233
+ ensurePikaaHomeMigrated();
1234
+ return join(getPikaaHomeDir(), "memories.md");
1235
+ }
1236
+ function getProjectsDir() {
1237
+ ensurePikaaHomeMigrated();
1238
+ const dir = join(getPikaaHomeDir(), "projects");
1239
+ if (!existsSync(dir)) {
1240
+ try {
1241
+ mkdirSync(dir, { recursive: true });
1242
+ } catch {}
1243
+ }
1244
+ return dir;
1245
+ }
1125
1246
 
1247
+ // src/auth/store.ts
1126
1248
  class CredentialsStore {
1127
1249
  filePath;
1128
1250
  constructor(customPath) {
1129
- this.filePath = customPath || resolve(homedir(), ".groupy", "credentials.json");
1251
+ this.filePath = customPath || getCredentialsPath();
1130
1252
  }
1131
1253
  load() {
1132
- if (!existsSync(this.filePath))
1254
+ if (!existsSync2(this.filePath))
1133
1255
  return null;
1134
1256
  try {
1135
1257
  const raw = readFileSync(this.filePath, "utf8");
@@ -1160,14 +1282,14 @@ class CredentialsStore {
1160
1282
  return creds?.user;
1161
1283
  }
1162
1284
  save(credentials) {
1163
- const dir = resolve(this.filePath, "..");
1164
- if (!existsSync(dir)) {
1165
- mkdirSync(dir, { recursive: true });
1285
+ const dir = resolve2(this.filePath, "..");
1286
+ if (!existsSync2(dir)) {
1287
+ mkdirSync2(dir, { recursive: true });
1166
1288
  }
1167
1289
  writeFileSync(this.filePath, JSON.stringify(credentials, null, 2), "utf8");
1168
1290
  }
1169
1291
  clear() {
1170
- if (existsSync(this.filePath)) {
1292
+ if (existsSync2(this.filePath)) {
1171
1293
  try {
1172
1294
  unlinkSync(this.filePath);
1173
1295
  return true;
@@ -1616,9 +1738,9 @@ class ToolRouter {
1616
1738
  }
1617
1739
  }
1618
1740
  // src/tools/handlers/apply-patch.ts
1619
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1620
- import { resolve as resolve2, dirname } from "path";
1621
- import { mkdirSync as mkdirSync2 } from "fs";
1741
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1742
+ import { resolve as resolve3, dirname as dirname2 } from "path";
1743
+ import { mkdirSync as mkdirSync3 } from "fs";
1622
1744
  var applyPatchTool = {
1623
1745
  name: "apply_patch",
1624
1746
  description: "Apply precise multi-line modifications to an existing file or create a new file. TargetContent must match the file content exactly.",
@@ -1645,7 +1767,7 @@ var applyPatchTool = {
1645
1767
  if (!rawPath) {
1646
1768
  return { output: "Error: 'path' parameter is required", isError: true };
1647
1769
  }
1648
- const filePath = resolve2(ctx.cwd, rawPath);
1770
+ const filePath = resolve3(ctx.cwd, rawPath);
1649
1771
  const targetContent = typeof args.targetContent === "string" ? args.targetContent : "";
1650
1772
  const replacementContent = String(args.replacementContent ?? "");
1651
1773
  if (ctx.execPolicy) {
@@ -1664,7 +1786,7 @@ var applyPatchTool = {
1664
1786
  }
1665
1787
  }
1666
1788
  }
1667
- if (!existsSync2(filePath)) {
1789
+ if (!existsSync3(filePath)) {
1668
1790
  if (targetContent) {
1669
1791
  return {
1670
1792
  output: `Error: Target file '${rawPath}' does not exist, but targetContent was provided.`,
@@ -1672,7 +1794,7 @@ var applyPatchTool = {
1672
1794
  };
1673
1795
  }
1674
1796
  try {
1675
- mkdirSync2(dirname(filePath), { recursive: true });
1797
+ mkdirSync3(dirname2(filePath), { recursive: true });
1676
1798
  writeFileSync2(filePath, replacementContent, "utf8");
1677
1799
  return { output: `Successfully created new file '${rawPath}'` };
1678
1800
  } catch (err) {
@@ -1879,7 +2001,7 @@ class WindowsSandbox {
1879
2001
  }
1880
2002
 
1881
2003
  // src/security/kernel/linux.ts
1882
- import { existsSync as existsSync3 } from "fs";
2004
+ import { existsSync as existsSync4 } from "fs";
1883
2005
 
1884
2006
  class LinuxSandbox {
1885
2007
  hasBwrap = false;
@@ -1890,7 +2012,7 @@ class LinuxSandbox {
1890
2012
  if (process.platform !== "linux") {
1891
2013
  return;
1892
2014
  }
1893
- this.hasBwrap = existsSync3("/usr/bin/bwrap") || existsSync3("/bin/bwrap") || existsSync3("/usr/local/bin/bwrap");
2015
+ this.hasBwrap = existsSync4("/usr/bin/bwrap") || existsSync4("/bin/bwrap") || existsSync4("/usr/local/bin/bwrap");
1894
2016
  }
1895
2017
  wrapCommand(cmd, profile) {
1896
2018
  if (!this.hasBwrap || profile.kind === "danger-unrestricted") {
@@ -1924,7 +2046,7 @@ class LinuxSandbox {
1924
2046
  }
1925
2047
 
1926
2048
  // src/security/kernel/macos.ts
1927
- import { existsSync as existsSync4 } from "fs";
2049
+ import { existsSync as existsSync5 } from "fs";
1928
2050
 
1929
2051
  class MacOSSandbox {
1930
2052
  hasSandboxExec = false;
@@ -1935,7 +2057,7 @@ class MacOSSandbox {
1935
2057
  if (process.platform !== "darwin") {
1936
2058
  return;
1937
2059
  }
1938
- this.hasSandboxExec = existsSync4("/usr/bin/sandbox-exec");
2060
+ this.hasSandboxExec = existsSync5("/usr/bin/sandbox-exec");
1939
2061
  }
1940
2062
  generateProfile(profile) {
1941
2063
  const rules = [
@@ -1974,7 +2096,7 @@ class MacOSSandbox {
1974
2096
  }
1975
2097
 
1976
2098
  // src/security/kernel/manager.ts
1977
- import { resolve as resolve3, normalize } from "path";
2099
+ import { resolve as resolve4, normalize } from "path";
1978
2100
 
1979
2101
  class KernelSandboxManager {
1980
2102
  windowsSandbox;
@@ -1996,10 +2118,10 @@ class KernelSandboxManager {
1996
2118
  };
1997
2119
  }
1998
2120
  buildDefaultProfile(cwd, allowNetwork = true) {
1999
- const normCwd = normalize(resolve3(cwd));
2121
+ const normCwd = normalize(resolve4(cwd));
2000
2122
  return {
2001
2123
  kind: "workspace-write",
2002
- readableRoots: [normCwd, resolve3(process.cwd())],
2124
+ readableRoots: [normCwd, resolve4(process.cwd())],
2003
2125
  writableRoots: [normCwd],
2004
2126
  allowNetwork,
2005
2127
  limits: {
@@ -2045,21 +2167,19 @@ var globalKernelSandbox = new KernelSandboxManager;
2045
2167
 
2046
2168
  // src/storage/prefix-rules-store.ts
2047
2169
  import { Database } from "bun:sqlite";
2048
- import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
2049
- import { dirname as dirname2, resolve as resolve4 } from "path";
2050
- import { homedir as homedir2 } from "os";
2051
-
2170
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2171
+ import { dirname as dirname3, resolve as resolve5 } from "path";
2052
2172
  class PrefixRulesStore {
2053
2173
  db;
2054
2174
  constructor(dbOrPath) {
2055
2175
  if (dbOrPath instanceof Database) {
2056
2176
  this.db = dbOrPath;
2057
2177
  } else {
2058
- const effectivePath = dbOrPath || resolve4(homedir2(), ".groupy", "groupy_rules.db");
2178
+ const effectivePath = dbOrPath || getPrefixRulesDbPath();
2059
2179
  if (effectivePath !== ":memory:") {
2060
- const dir = dirname2(effectivePath);
2061
- if (!existsSync5(dir)) {
2062
- mkdirSync3(dir, { recursive: true });
2180
+ const dir = dirname3(effectivePath);
2181
+ if (!existsSync6(dir)) {
2182
+ mkdirSync4(dir, { recursive: true });
2063
2183
  }
2064
2184
  }
2065
2185
  this.db = new Database(effectivePath);
@@ -2082,7 +2202,7 @@ class PrefixRulesStore {
2082
2202
  addRule(workspacePath, prefixTokens) {
2083
2203
  if (!prefixTokens || prefixTokens.length === 0)
2084
2204
  return;
2085
- const normalizedWs = workspacePath === "*" ? "*" : resolve4(workspacePath);
2205
+ const normalizedWs = workspacePath === "*" ? "*" : resolve5(workspacePath);
2086
2206
  const tokensJson = JSON.stringify(prefixTokens);
2087
2207
  const id = `${normalizedWs}:${tokensJson}`;
2088
2208
  const query = this.db.prepare(`
@@ -2099,7 +2219,7 @@ class PrefixRulesStore {
2099
2219
  isApproved(workspacePath, commandTokens) {
2100
2220
  if (!commandTokens || commandTokens.length === 0)
2101
2221
  return false;
2102
- const normalizedWs = resolve4(workspacePath);
2222
+ const normalizedWs = resolve5(workspacePath);
2103
2223
  const query = this.db.prepare(`
2104
2224
  SELECT prefix_tokens FROM approved_prefix_rules
2105
2225
  WHERE workspace_path = $ws OR workspace_path = '*'
@@ -2118,7 +2238,7 @@ class PrefixRulesStore {
2118
2238
  listRules(workspacePath) {
2119
2239
  let rows;
2120
2240
  if (workspacePath) {
2121
- const normalizedWs = workspacePath === "*" ? "*" : resolve4(workspacePath);
2241
+ const normalizedWs = workspacePath === "*" ? "*" : resolve5(workspacePath);
2122
2242
  const query = this.db.prepare(`
2123
2243
  SELECT prefix_tokens FROM approved_prefix_rules
2124
2244
  WHERE workspace_path = $ws OR workspace_path = '*'
@@ -2137,7 +2257,7 @@ class PrefixRulesStore {
2137
2257
  }).filter((r) => r.length > 0);
2138
2258
  }
2139
2259
  removeRule(workspacePath, prefixTokens) {
2140
- const normalizedWs = workspacePath === "*" ? "*" : resolve4(workspacePath);
2260
+ const normalizedWs = workspacePath === "*" ? "*" : resolve5(workspacePath);
2141
2261
  const tokensJson = JSON.stringify(prefixTokens);
2142
2262
  const id = `${normalizedWs}:${tokensJson}`;
2143
2263
  const query = this.db.prepare(`
@@ -2270,7 +2390,7 @@ function createShellTool(policy = new ExecPolicy) {
2270
2390
  } catch {}
2271
2391
  });
2272
2392
  }
2273
- const timeoutPromise = new Promise((resolve5) => setTimeout(() => resolve5({ isTimeout: true }), timeoutMs));
2393
+ const timeoutPromise = new Promise((resolve6) => setTimeout(() => resolve6({ isTimeout: true }), timeoutMs));
2274
2394
  const result = await Promise.race([
2275
2395
  proc.exited.then(async (code) => {
2276
2396
  const stdout = await new Response(proc.stdout).text();
@@ -2314,8 +2434,8 @@ ${result.stderr.trim()}`);
2314
2434
  }
2315
2435
  var shellTool = createShellTool();
2316
2436
  // src/tools/handlers/file-ops.ts
2317
- import { readdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync6, statSync, mkdirSync as mkdirSync4 } from "fs";
2318
- import { resolve as resolve5, dirname as dirname3 } from "path";
2437
+ import { readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync7, statSync as statSync2, mkdirSync as mkdirSync5 } from "fs";
2438
+ import { resolve as resolve6, dirname as dirname4 } from "path";
2319
2439
  var readFileTool = {
2320
2440
  name: "read_file",
2321
2441
  description: "Read the full text content of a file.",
@@ -2327,8 +2447,8 @@ var readFileTool = {
2327
2447
  required: ["path"]
2328
2448
  },
2329
2449
  async execute(args, ctx) {
2330
- const filePath = resolve5(ctx.cwd, String(args.path || ""));
2331
- if (!existsSync6(filePath)) {
2450
+ const filePath = resolve6(ctx.cwd, String(args.path || ""));
2451
+ if (!existsSync7(filePath)) {
2332
2452
  return { output: `Error: File not found: '${args.path}'`, isError: true };
2333
2453
  }
2334
2454
  try {
@@ -2349,15 +2469,15 @@ var listDirTool = {
2349
2469
  }
2350
2470
  },
2351
2471
  async execute(args, ctx) {
2352
- const dirPath = resolve5(ctx.cwd, String(args.path || "."));
2353
- if (!existsSync6(dirPath)) {
2472
+ const dirPath = resolve6(ctx.cwd, String(args.path || "."));
2473
+ if (!existsSync7(dirPath)) {
2354
2474
  return { output: `Error: Directory not found: '${args.path}'`, isError: true };
2355
2475
  }
2356
2476
  try {
2357
- const entries = readdirSync(dirPath);
2477
+ const entries = readdirSync2(dirPath);
2358
2478
  const formatted = entries.map((entry) => {
2359
- const full = resolve5(dirPath, entry);
2360
- const isDir = statSync(full).isDirectory();
2479
+ const full = resolve6(dirPath, entry);
2480
+ const isDir = statSync2(full).isDirectory();
2361
2481
  return `${isDir ? "[DIR]" : "[FILE]"} ${entry}`;
2362
2482
  });
2363
2483
  return { output: formatted.join(`
@@ -2380,7 +2500,7 @@ var writeFileTool = {
2380
2500
  },
2381
2501
  async execute(args, ctx) {
2382
2502
  const rawPath = String(args.path || "");
2383
- const filePath = resolve5(ctx.cwd, rawPath);
2503
+ const filePath = resolve6(ctx.cwd, rawPath);
2384
2504
  if (ctx.execPolicy) {
2385
2505
  const evalResult = ctx.execPolicy.shouldPromptFileEdit(rawPath);
2386
2506
  if (evalResult.isPlanBlocked || ctx.mode === "plan") {
@@ -2398,7 +2518,7 @@ var writeFileTool = {
2398
2518
  }
2399
2519
  }
2400
2520
  try {
2401
- mkdirSync4(dirname3(filePath), { recursive: true });
2521
+ mkdirSync5(dirname4(filePath), { recursive: true });
2402
2522
  writeFileSync3(filePath, String(args.content ?? ""), "utf8");
2403
2523
  return { output: `Successfully wrote to '${args.path}'` };
2404
2524
  } catch (err) {
@@ -2521,8 +2641,8 @@ var updatePlanTool = {
2521
2641
  }
2522
2642
  };
2523
2643
  // src/search/engine.ts
2524
- import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2, existsSync as existsSync7 } from "fs";
2525
- import { resolve as resolve6, relative, join, extname } from "path";
2644
+ import { readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync3, existsSync as existsSync8 } from "fs";
2645
+ import { resolve as resolve7, relative, join as join2, extname } from "path";
2526
2646
  var DEFAULT_IGNORE_DIRS = new Set([
2527
2647
  ".git",
2528
2648
  "node_modules",
@@ -2567,8 +2687,8 @@ var BINARY_EXTENSIONS = new Set([
2567
2687
 
2568
2688
  class FileSearchEngine {
2569
2689
  grep(cwd, options) {
2570
- const searchRoot = resolve6(cwd, options.path || ".");
2571
- if (!existsSync7(searchRoot)) {
2690
+ const searchRoot = resolve7(cwd, options.path || ".");
2691
+ if (!existsSync8(searchRoot)) {
2572
2692
  return { matches: [], totalMatches: 0, truncated: false };
2573
2693
  }
2574
2694
  const maxResults = options.maxResults || 50;
@@ -2617,8 +2737,8 @@ class FileSearchEngine {
2617
2737
  return { matches, totalMatches, truncated };
2618
2738
  }
2619
2739
  findFiles(cwd, options) {
2620
- const searchRoot = resolve6(cwd, options.path || ".");
2621
- if (!existsSync7(searchRoot))
2740
+ const searchRoot = resolve7(cwd, options.path || ".");
2741
+ if (!existsSync8(searchRoot))
2622
2742
  return [];
2623
2743
  const maxResults = options.maxResults || 100;
2624
2744
  const gitignoreRules = this.loadGitignoreRules(searchRoot);
@@ -2657,8 +2777,8 @@ class FileSearchEngine {
2657
2777
  }
2658
2778
  loadGitignoreRules(root) {
2659
2779
  const rules = new Set;
2660
- const gitignorePath = join(root, ".gitignore");
2661
- if (existsSync7(gitignorePath)) {
2780
+ const gitignorePath = join2(root, ".gitignore");
2781
+ if (existsSync8(gitignorePath)) {
2662
2782
  try {
2663
2783
  const lines = readFileSync4(gitignorePath, "utf8").split(`
2664
2784
  `);
@@ -2675,7 +2795,7 @@ class FileSearchEngine {
2675
2795
  collectFiles(dir, root, gitignoreRules, includePattern) {
2676
2796
  const results = [];
2677
2797
  try {
2678
- const stat = statSync2(dir);
2798
+ const stat = statSync3(dir);
2679
2799
  if (!stat.isDirectory()) {
2680
2800
  if (!this.isBinary(dir)) {
2681
2801
  results.push(dir);
@@ -2689,9 +2809,9 @@ class FileSearchEngine {
2689
2809
  while (queue.length > 0) {
2690
2810
  const currentDir = queue.shift();
2691
2811
  try {
2692
- const entries = readdirSync2(currentDir, { withFileTypes: true });
2812
+ const entries = readdirSync3(currentDir, { withFileTypes: true });
2693
2813
  for (const entry of entries) {
2694
- const fullPath = join(currentDir, entry.name);
2814
+ const fullPath = join2(currentDir, entry.name);
2695
2815
  const relToRoot = relative(root, fullPath).replace(/\\/g, "/");
2696
2816
  if (this.isIgnored(entry.name, relToRoot, gitignoreRules)) {
2697
2817
  continue;
@@ -3088,6 +3208,111 @@ ${loaded.instructions}`
3088
3208
  }
3089
3209
 
3090
3210
  // src/memories/tool.ts
3211
+ function createSaveMemoryTool(store) {
3212
+ return {
3213
+ name: "save_memory",
3214
+ description: "Save a persistent memory note to the project's Auto-Memory bank. Categories: 'user' (role, workflow style, tooling preferences), 'feedback' (user corrections, guidelines), 'project' (external context, environments, deadlines), 'reference' (links, issue trackers, dashboards). Do NOT save facts easily discovered in code or git history.",
3215
+ parameters: {
3216
+ type: "object",
3217
+ properties: {
3218
+ category: {
3219
+ type: "string",
3220
+ description: "Category of memory: 'user', 'feedback', 'project', or 'reference'.",
3221
+ enum: ["user", "feedback", "project", "reference"]
3222
+ },
3223
+ name: {
3224
+ type: "string",
3225
+ description: "Short, descriptive snake_case identifier for this memory topic (e.g. 'testing_strategy', 'preferred_framework', 'staging_api')."
3226
+ },
3227
+ description: {
3228
+ type: "string",
3229
+ description: "One-line summary to display in the MEMORY.md index (e.g. 'Prefers Vitest without database mocks')."
3230
+ },
3231
+ content: {
3232
+ type: "string",
3233
+ description: "Detailed description of the fact, preference, or learned correction."
3234
+ }
3235
+ },
3236
+ required: ["category", "name", "content"]
3237
+ },
3238
+ async execute(args, context) {
3239
+ const category = args.category || "project";
3240
+ const name = String(args.name || `topic_${Date.now()}`);
3241
+ const content = String(args.content || "").trim();
3242
+ const description = args.description ? String(args.description).trim() : undefined;
3243
+ if (!content) {
3244
+ return { output: "Error: memory content cannot be empty", isError: true };
3245
+ }
3246
+ const entry = store.saveTopicMemory({
3247
+ category,
3248
+ name,
3249
+ description,
3250
+ content,
3251
+ cwd: context.cwd
3252
+ });
3253
+ return {
3254
+ output: `\u2713 Saved Auto-Memory topic: [${entry.category}] "${entry.name}" -> ${entry.filePath}`
3255
+ };
3256
+ }
3257
+ };
3258
+ }
3259
+ function createReadMemoryTool(store) {
3260
+ return {
3261
+ name: "read_memory",
3262
+ description: "Read the full details of a specific Auto-Memory topic file recorded in the project's memory index.",
3263
+ parameters: {
3264
+ type: "object",
3265
+ properties: {
3266
+ topic: {
3267
+ type: "string",
3268
+ description: "Name or filename of the memory topic to read (e.g. 'testing_strategy' or 'feedback_testing.md')."
3269
+ }
3270
+ },
3271
+ required: ["topic"]
3272
+ },
3273
+ async execute(args, context) {
3274
+ const topic = String(args.topic || "").trim();
3275
+ if (!topic) {
3276
+ return { output: "Error: topic name is required", isError: true };
3277
+ }
3278
+ const memory = store.readTopicMemory(topic, context.cwd);
3279
+ if (!memory) {
3280
+ return {
3281
+ output: `No memory topic found matching '${topic}' in this project.`,
3282
+ isError: true
3283
+ };
3284
+ }
3285
+ return {
3286
+ output: `# Topic: ${memory.name} (${memory.type})
3287
+ Modified: ${memory.modified}
3288
+
3289
+ ${memory.content}`
3290
+ };
3291
+ }
3292
+ };
3293
+ }
3294
+ function createListMemoriesTool(store) {
3295
+ return {
3296
+ name: "list_memories",
3297
+ description: "List all persistent memory topics and index for the current project repository.",
3298
+ parameters: {
3299
+ type: "object",
3300
+ properties: {}
3301
+ },
3302
+ async execute(_args, context) {
3303
+ const topics = store.listProjectMemories(context.cwd);
3304
+ if (topics.length === 0) {
3305
+ return { output: "No persistent Auto-Memories have been recorded for this project yet." };
3306
+ }
3307
+ const lines = topics.map((t) => `\u2022 [${t.type}] **${t.name}**: ${t.description || t.content.split(`
3308
+ `)[0]} (file: ${t.filePath})`);
3309
+ return { output: `Project Auto-Memories (${topics.length} topics):
3310
+
3311
+ ${lines.join(`
3312
+ `)}` };
3313
+ }
3314
+ };
3315
+ }
3091
3316
  function createRememberTool(store) {
3092
3317
  return {
3093
3318
  name: "remember",
@@ -3098,16 +3323,15 @@ function createRememberTool(store) {
3098
3323
  category: {
3099
3324
  type: "string",
3100
3325
  description: "Category of the memory.",
3101
- enum: ["preference", "guideline", "architecture", "note"]
3326
+ enum: ["preference", "guideline", "architecture", "note", "user", "feedback", "project", "reference"]
3102
3327
  },
3103
3328
  content: {
3104
3329
  type: "string",
3105
3330
  description: "The concise rule, preference, or fact to remember permanently."
3106
3331
  },
3107
- scope: {
3332
+ name: {
3108
3333
  type: "string",
3109
- description: "'global' (applies to all projects) or 'workspace' (applies only to current repository). Defaults to 'global'.",
3110
- enum: ["global", "workspace"]
3334
+ description: "Optional topic name."
3111
3335
  }
3112
3336
  },
3113
3337
  required: ["category", "content"]
@@ -3115,22 +3339,30 @@ function createRememberTool(store) {
3115
3339
  async execute(args, context) {
3116
3340
  const category = args.category || "preference";
3117
3341
  const content = String(args.content || "");
3118
- const scope = args.scope || "global";
3342
+ const name = args.name ? String(args.name) : undefined;
3119
3343
  if (!content) {
3120
3344
  return { output: "Error: memory content cannot be empty", isError: true };
3121
3345
  }
3122
3346
  const entry = store.addMemory({
3123
3347
  category,
3124
3348
  content,
3125
- scope,
3349
+ name,
3126
3350
  cwd: context.cwd
3127
3351
  });
3128
3352
  return {
3129
- output: `Successfully saved to ${scope} memory bank: [${entry.category}] "${entry.content}"`
3353
+ output: `Successfully saved to Auto-Memory bank: [${entry.category}] "${entry.name || entry.content}"`
3130
3354
  };
3131
3355
  }
3132
3356
  };
3133
3357
  }
3358
+ function createAutoMemoryTools(store) {
3359
+ return [
3360
+ createSaveMemoryTool(store),
3361
+ createReadMemoryTool(store),
3362
+ createListMemoriesTool(store),
3363
+ createRememberTool(store)
3364
+ ];
3365
+ }
3134
3366
 
3135
3367
  // src/worktree/tools.ts
3136
3368
  function createWorktreeTools(manager) {
@@ -3265,7 +3497,9 @@ function createDefaultTools(options = {}) {
3265
3497
  router2.register(createSkillTool(options.skillsLoader));
3266
3498
  }
3267
3499
  if (options.memoryStore) {
3268
- router2.register(createRememberTool(options.memoryStore));
3500
+ for (const tool of createAutoMemoryTools(options.memoryStore)) {
3501
+ router2.register(tool);
3502
+ }
3269
3503
  }
3270
3504
  if (options.worktreeManager) {
3271
3505
  for (const tool of createWorktreeTools(options.worktreeManager)) {
@@ -3275,21 +3509,21 @@ function createDefaultTools(options = {}) {
3275
3509
  return router2;
3276
3510
  }
3277
3511
  // src/security/sandbox.ts
3278
- import { resolve as resolve7, normalize as normalize2, relative as relative2 } from "path";
3512
+ import { resolve as resolve8, normalize as normalize2, relative as relative2 } from "path";
3279
3513
  class PathSandbox {
3280
3514
  allowedRoots;
3281
3515
  constructor(workspaceRoots) {
3282
- this.allowedRoots = workspaceRoots.map((root) => normalize2(resolve7(root)));
3516
+ this.allowedRoots = workspaceRoots.map((root) => normalize2(resolve8(root)));
3283
3517
  }
3284
3518
  isPathAllowed(targetPath) {
3285
- const normalized = normalize2(resolve7(targetPath));
3519
+ const normalized = normalize2(resolve8(targetPath));
3286
3520
  return this.allowedRoots.some((root) => {
3287
3521
  const rel = relative2(root, normalized);
3288
- return !rel.startsWith("..") && !resolve7(root, rel).startsWith("..");
3522
+ return !rel.startsWith("..") && !resolve8(root, rel).startsWith("..");
3289
3523
  });
3290
3524
  }
3291
3525
  assertPathAllowed(targetPath) {
3292
- const normalized = normalize2(resolve7(targetPath));
3526
+ const normalized = normalize2(resolve8(targetPath));
3293
3527
  if (!this.isPathAllowed(normalized)) {
3294
3528
  throw new ExecutionPolicyError(`Path access denied: '${targetPath}' is outside the allowed workspace boundaries (${this.allowedRoots.join(", ")})`);
3295
3529
  }
@@ -3297,8 +3531,8 @@ class PathSandbox {
3297
3531
  }
3298
3532
  }
3299
3533
  // src/security/scanner.ts
3300
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
3301
- import { join as join2, relative as relative3, resolve as resolve8 } from "path";
3534
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
3535
+ import { join as join3, relative as relative3, resolve as resolve9 } from "path";
3302
3536
  var SECURITY_RULES = [
3303
3537
  {
3304
3538
  id: "SEC-001",
@@ -3430,27 +3664,27 @@ var IGNORED_FILES = new Set([
3430
3664
  ]);
3431
3665
  async function runSecurityScan(targetDir, options = {}) {
3432
3666
  const startTime = performance.now();
3433
- const root = resolve8(targetDir);
3667
+ const root = resolve9(targetDir);
3434
3668
  const isDirectTestDir = targetDir.includes("test") || Boolean(options.includeTests);
3435
3669
  const maxFiles = options.maxFiles || 2000;
3436
3670
  const findings = [];
3437
3671
  let scannedCount = 0;
3438
3672
  function walk(current) {
3439
- if (scannedCount >= maxFiles || !existsSync8(current))
3673
+ if (scannedCount >= maxFiles || !existsSync9(current))
3440
3674
  return;
3441
3675
  let entries;
3442
3676
  try {
3443
- entries = readdirSync3(current);
3677
+ entries = readdirSync4(current);
3444
3678
  } catch {
3445
3679
  return;
3446
3680
  }
3447
3681
  for (const entry of entries) {
3448
3682
  if (scannedCount >= maxFiles)
3449
3683
  break;
3450
- const fullPath = join2(current, entry);
3684
+ const fullPath = join3(current, entry);
3451
3685
  let stat;
3452
3686
  try {
3453
- stat = statSync3(fullPath);
3687
+ stat = statSync4(fullPath);
3454
3688
  } catch {
3455
3689
  continue;
3456
3690
  }
@@ -3571,14 +3805,13 @@ function formatWorldStatePrompt(state) {
3571
3805
  `);
3572
3806
  }
3573
3807
  // src/prompts/loader.ts
3574
- import { existsSync as existsSync9, readFileSync as readFileSync6 } from "fs";
3575
- import { resolve as resolve9, join as join3 } from "path";
3576
- import { homedir as homedir3 } from "os";
3577
-
3808
+ import { existsSync as existsSync10, readFileSync as readFileSync6 } from "fs";
3809
+ import { resolve as resolve10, join as join4 } from "path";
3810
+ import { homedir as homedir2 } from "os";
3578
3811
  class PromptTemplateLoader {
3579
3812
  builtInTemplatesDir;
3580
3813
  constructor(builtInDir) {
3581
- this.builtInTemplatesDir = builtInDir || resolve9(join3(import.meta.dir, "..", "..", "templates"));
3814
+ this.builtInTemplatesDir = builtInDir || resolve10(join4(import.meta.dir, "..", "..", "templates"));
3582
3815
  }
3583
3816
  loadTemplate(relativePath, variables = {}, cwd) {
3584
3817
  const rawContent = this.resolveTemplateContent(relativePath, cwd);
@@ -3587,21 +3820,27 @@ class PromptTemplateLoader {
3587
3820
  resolveTemplateContent(relativePath, cwd) {
3588
3821
  const normalizedRel = relativePath.replace(/^\/+/, "");
3589
3822
  if (cwd) {
3590
- const workspacePath = join3(cwd, ".agents", "templates", normalizedRel);
3591
- if (existsSync9(workspacePath)) {
3823
+ const workspacePath = join4(cwd, ".agents", "templates", normalizedRel);
3824
+ if (existsSync10(workspacePath)) {
3592
3825
  try {
3593
3826
  return readFileSync6(workspacePath, "utf-8");
3594
3827
  } catch {}
3595
3828
  }
3596
3829
  }
3597
- const globalPath = join3(homedir3(), ".groupy", "templates", normalizedRel);
3598
- if (existsSync9(globalPath)) {
3830
+ const globalPath = join4(getGlobalTemplatesDir(), normalizedRel);
3831
+ if (existsSync10(globalPath)) {
3599
3832
  try {
3600
3833
  return readFileSync6(globalPath, "utf-8");
3601
3834
  } catch {}
3602
3835
  }
3603
- const builtInPath = join3(this.builtInTemplatesDir, normalizedRel);
3604
- if (existsSync9(builtInPath)) {
3836
+ const legacyGlobalPath = join4(homedir2(), ".groupy", "templates", normalizedRel);
3837
+ if (existsSync10(legacyGlobalPath)) {
3838
+ try {
3839
+ return readFileSync6(legacyGlobalPath, "utf-8");
3840
+ } catch {}
3841
+ }
3842
+ const builtInPath = join4(this.builtInTemplatesDir, normalizedRel);
3843
+ if (existsSync10(builtInPath)) {
3605
3844
  try {
3606
3845
  return readFileSync6(builtInPath, "utf-8");
3607
3846
  } catch {}
@@ -3618,8 +3857,8 @@ class PromptTemplateLoader {
3618
3857
  var globalPromptLoader = new PromptTemplateLoader;
3619
3858
 
3620
3859
  // src/prompts/agents-md.ts
3621
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
3622
- import { resolve as resolve10, join as join4, dirname as dirname4 } from "path";
3860
+ import { existsSync as existsSync11, readFileSync as readFileSync7 } from "fs";
3861
+ import { resolve as resolve11, join as join5, dirname as dirname5 } from "path";
3623
3862
  var DEFAULT_AGENTS_MD_FILENAMES = [
3624
3863
  "AGENTS.override.md",
3625
3864
  "AGENTS.md",
@@ -3634,28 +3873,28 @@ var AGENTS_MD_SEPARATOR = `
3634
3873
 
3635
3874
  class AgentsMdLoader {
3636
3875
  findProjectRoot(startDir) {
3637
- let current = resolve10(startDir);
3876
+ let current = resolve11(startDir);
3638
3877
  while (true) {
3639
- if (existsSync10(join4(current, ".git"))) {
3878
+ if (existsSync11(join5(current, ".git"))) {
3640
3879
  return current;
3641
3880
  }
3642
- const parent = dirname4(current);
3881
+ const parent = dirname5(current);
3643
3882
  if (parent === current) {
3644
- return resolve10(startDir);
3883
+ return resolve11(startDir);
3645
3884
  }
3646
3885
  current = parent;
3647
3886
  }
3648
3887
  }
3649
3888
  collectDirectoryHierarchy(targetDir, rootDir) {
3650
3889
  const hierarchy = [];
3651
- let current = resolve10(targetDir);
3652
- const normalizedRoot = resolve10(rootDir);
3890
+ let current = resolve11(targetDir);
3891
+ const normalizedRoot = resolve11(rootDir);
3653
3892
  while (true) {
3654
3893
  hierarchy.unshift(current);
3655
3894
  if (current === normalizedRoot) {
3656
3895
  break;
3657
3896
  }
3658
- const parent = dirname4(current);
3897
+ const parent = dirname5(current);
3659
3898
  if (parent === current) {
3660
3899
  break;
3661
3900
  }
@@ -3670,8 +3909,8 @@ class AgentsMdLoader {
3670
3909
  const sourcePaths = [];
3671
3910
  for (const dir of dirHierarchy) {
3672
3911
  for (const filename of fallbackFilenames) {
3673
- const filePath = join4(dir, filename);
3674
- if (existsSync10(filePath)) {
3912
+ const filePath = join5(dir, filename);
3913
+ if (existsSync11(filePath)) {
3675
3914
  try {
3676
3915
  const content = readFileSync7(filePath, "utf-8").trim();
3677
3916
  if (content) {
@@ -4024,6 +4263,17 @@ async function runTurn(session, turnContext, input) {
4024
4263
  }
4025
4264
  continue;
4026
4265
  }
4266
+ if (!currentAgentText.trim() && toolCallRequests.length === 0) {
4267
+ if (iteration === 1 && iteration < turnContext.maxIterations) {
4268
+ session.addHistoryItem({
4269
+ id: `msg_nudge_${Date.now()}`,
4270
+ type: "user_message",
4271
+ content: "Please proceed with executing the task. Provide your complete analysis or call the required tools now.",
4272
+ createdAt: Date.now()
4273
+ });
4274
+ continue;
4275
+ }
4276
+ }
4027
4277
  break;
4028
4278
  }
4029
4279
  const totalContextTokens = estimateTotalTokens(session.getHistory()) + Math.ceil(effectiveSystemPrompt.length / 4);
@@ -4249,13 +4499,13 @@ class Session {
4249
4499
  type: "StatusChanged",
4250
4500
  status: "waiting_approval"
4251
4501
  });
4252
- return new Promise((resolve11) => {
4502
+ return new Promise((resolve12) => {
4253
4503
  this.pendingApprovals.set(params.approvalId, (approved) => {
4254
4504
  this.emitEvent({
4255
4505
  type: "StatusChanged",
4256
4506
  status: "running"
4257
4507
  });
4258
- resolve11(approved);
4508
+ resolve12(approved);
4259
4509
  });
4260
4510
  });
4261
4511
  }
@@ -4278,13 +4528,13 @@ class Session {
4278
4528
  type: "StatusChanged",
4279
4529
  status: "waiting_user_input"
4280
4530
  });
4281
- return new Promise((resolve11) => {
4531
+ return new Promise((resolve12) => {
4282
4532
  this.pendingUserQuestions.set(params.questionId, (answer) => {
4283
4533
  this.emitEvent({
4284
4534
  type: "StatusChanged",
4285
4535
  status: "running"
4286
4536
  });
4287
- resolve11(answer);
4537
+ resolve12(answer);
4288
4538
  });
4289
4539
  });
4290
4540
  }
@@ -4314,7 +4564,7 @@ class Session {
4314
4564
  return handleTurnInput(this, { text, images });
4315
4565
  }
4316
4566
  async promptAndWait(text, images, timeoutMs = 30000) {
4317
- return new Promise((resolve11, reject) => {
4567
+ return new Promise((resolve12, reject) => {
4318
4568
  const timer = setTimeout(() => {
4319
4569
  unsub();
4320
4570
  reject(new Error(`Turn timed out after ${timeoutMs}ms`));
@@ -4323,7 +4573,7 @@ class Session {
4323
4573
  if (event.msg.type === "TurnCompleted") {
4324
4574
  clearTimeout(timer);
4325
4575
  unsub();
4326
- resolve11();
4576
+ resolve12();
4327
4577
  } else if (event.msg.type === "Error") {
4328
4578
  clearTimeout(timer);
4329
4579
  unsub();
@@ -4342,8 +4592,8 @@ class Session {
4342
4592
  if (this.submissionQueue.length > 0) {
4343
4593
  yield this.submissionQueue.shift();
4344
4594
  } else {
4345
- const nextSub = await new Promise((resolve11) => {
4346
- this.submissionResolvers.push(resolve11);
4595
+ const nextSub = await new Promise((resolve12) => {
4596
+ this.submissionResolvers.push(resolve12);
4347
4597
  });
4348
4598
  yield nextSub;
4349
4599
  }
@@ -4607,13 +4857,13 @@ class StdioTransport {
4607
4857
  if (this.isClosed || !this.proc || !this.proc.stdin) {
4608
4858
  throw new GroupyError("MCP Stdio transport is closed");
4609
4859
  }
4610
- return new Promise((resolve11, reject) => {
4860
+ return new Promise((resolve12, reject) => {
4611
4861
  const timeoutMs = 30000;
4612
4862
  const timer = setTimeout(() => {
4613
4863
  this.pendingRequests.delete(request.id);
4614
4864
  reject(new GroupyError(`MCP request timed out after ${timeoutMs}ms (method: ${request.method})`));
4615
4865
  }, timeoutMs);
4616
- this.pendingRequests.set(request.id, { resolve: resolve11, reject, timer });
4866
+ this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
4617
4867
  try {
4618
4868
  const payload = JSON.stringify(request) + `
4619
4869
  `;
@@ -4746,12 +4996,12 @@ class SseTransport {
4746
4996
  if (!this.messageUrl) {
4747
4997
  this.messageUrl = this.endpointUrl;
4748
4998
  }
4749
- return new Promise((resolve11, reject) => {
4999
+ return new Promise((resolve12, reject) => {
4750
5000
  const timer = setTimeout(() => {
4751
5001
  this.pendingRequests.delete(request.id);
4752
5002
  reject(new GroupyError(`MCP SSE request timed out (method: ${request.method})`));
4753
5003
  }, 30000);
4754
- this.pendingRequests.set(request.id, { resolve: resolve11, reject, timer });
5004
+ this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
4755
5005
  fetch(this.messageUrl, {
4756
5006
  method: "POST",
4757
5007
  headers: {
@@ -4988,8 +5238,8 @@ class McpClient {
4988
5238
  }
4989
5239
  }
4990
5240
  // src/mcp/manager.ts
4991
- import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
4992
- import { resolve as resolve11, dirname as dirname5, join as join5 } from "path";
5241
+ import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync6 } from "fs";
5242
+ import { resolve as resolve12, dirname as dirname6, join as join6 } from "path";
4993
5243
  class McpManager {
4994
5244
  clients = new Map;
4995
5245
  serverConfigs = new Map;
@@ -5023,8 +5273,8 @@ class McpManager {
5023
5273
  }
5024
5274
  }
5025
5275
  async loadConfigFile(filePath) {
5026
- const fullPath = resolve11(filePath);
5027
- if (!existsSync11(fullPath))
5276
+ const fullPath = resolve12(filePath);
5277
+ if (!existsSync12(fullPath))
5028
5278
  return;
5029
5279
  this.loadedConfigFiles.add(fullPath);
5030
5280
  try {
@@ -5277,13 +5527,13 @@ class McpManager {
5277
5527
  `);
5278
5528
  }
5279
5529
  saveServerToConfigFile(filePath, name, config) {
5280
- const fullPath = resolve11(filePath);
5281
- const dir = dirname5(fullPath);
5282
- if (!existsSync11(dir)) {
5283
- mkdirSync5(dir, { recursive: true });
5530
+ const fullPath = resolve12(filePath);
5531
+ const dir = dirname6(fullPath);
5532
+ if (!existsSync12(dir)) {
5533
+ mkdirSync6(dir, { recursive: true });
5284
5534
  }
5285
5535
  let existing = { mcpServers: {} };
5286
- if (existsSync11(fullPath)) {
5536
+ if (existsSync12(fullPath)) {
5287
5537
  try {
5288
5538
  const content = readFileSync8(fullPath, "utf8");
5289
5539
  existing = JSON.parse(content);
@@ -5297,8 +5547,8 @@ class McpManager {
5297
5547
  this.loadedConfigFiles.add(fullPath);
5298
5548
  }
5299
5549
  removeServerFromConfigFile(filePath, name) {
5300
- const fullPath = resolve11(filePath);
5301
- if (!existsSync11(fullPath))
5550
+ const fullPath = resolve12(filePath);
5551
+ if (!existsSync12(fullPath))
5302
5552
  return false;
5303
5553
  try {
5304
5554
  const content = readFileSync8(fullPath, "utf8");
@@ -5328,11 +5578,11 @@ class McpManager {
5328
5578
  }
5329
5579
  }
5330
5580
  getDefaultConfigFile(cwd = process.cwd()) {
5331
- const workspaceConfig = join5(cwd, ".mcp.json");
5332
- if (existsSync11(workspaceConfig))
5581
+ const workspaceConfig = join6(cwd, ".mcp.json");
5582
+ if (existsSync12(workspaceConfig))
5333
5583
  return workspaceConfig;
5334
- const altConfig = join5(cwd, "mcp_config.json");
5335
- if (existsSync11(altConfig))
5584
+ const altConfig = join6(cwd, "mcp_config.json");
5585
+ if (existsSync12(altConfig))
5336
5586
  return altConfig;
5337
5587
  return workspaceConfig;
5338
5588
  }
@@ -5351,11 +5601,11 @@ class McpManager {
5351
5601
  }
5352
5602
  }
5353
5603
  // src/mcp/servers/chrome-devtools/index.ts
5354
- import { resolve as resolve13 } from "path";
5604
+ import { resolve as resolve14 } from "path";
5355
5605
 
5356
5606
  // src/mcp/servers/chrome-devtools/launcher.ts
5357
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync } from "fs";
5358
- import { join as join6 } from "path";
5607
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, rmSync } from "fs";
5608
+ import { join as join7 } from "path";
5359
5609
  import { tmpdir } from "os";
5360
5610
  class BrowserLauncher {
5361
5611
  proc = null;
@@ -5363,21 +5613,21 @@ class BrowserLauncher {
5363
5613
  wsDebuggerUrl = null;
5364
5614
  port = 0;
5365
5615
  static findBrowserExecutable() {
5366
- if (process.env.CHROME_PATH && existsSync12(process.env.CHROME_PATH)) {
5616
+ if (process.env.CHROME_PATH && existsSync13(process.env.CHROME_PATH)) {
5367
5617
  return process.env.CHROME_PATH;
5368
5618
  }
5369
5619
  if (process.platform === "win32") {
5370
5620
  const candidates = [
5371
5621
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
5372
5622
  "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
5373
- join6(process.env.LOCALAPPDATA || "", "Google\\Chrome\\Application\\chrome.exe"),
5623
+ join7(process.env.LOCALAPPDATA || "", "Google\\Chrome\\Application\\chrome.exe"),
5374
5624
  "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
5375
5625
  "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
5376
5626
  "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
5377
- join6(process.env.LOCALAPPDATA || "", "BraveSoftware\\Brave-Browser\\Application\\brave.exe")
5627
+ join7(process.env.LOCALAPPDATA || "", "BraveSoftware\\Brave-Browser\\Application\\brave.exe")
5378
5628
  ];
5379
5629
  for (const path of candidates) {
5380
- if (path && existsSync12(path))
5630
+ if (path && existsSync13(path))
5381
5631
  return path;
5382
5632
  }
5383
5633
  } else if (process.platform === "darwin") {
@@ -5388,7 +5638,7 @@ class BrowserLauncher {
5388
5638
  "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
5389
5639
  ];
5390
5640
  for (const path of candidates) {
5391
- if (existsSync12(path))
5641
+ if (existsSync13(path))
5392
5642
  return path;
5393
5643
  }
5394
5644
  } else {
@@ -5401,7 +5651,7 @@ class BrowserLauncher {
5401
5651
  "/usr/bin/microsoft-edge"
5402
5652
  ];
5403
5653
  for (const path of candidates) {
5404
- if (existsSync12(path))
5654
+ if (existsSync13(path))
5405
5655
  return path;
5406
5656
  }
5407
5657
  }
@@ -5413,8 +5663,8 @@ class BrowserLauncher {
5413
5663
  throw new Error("No supported browser (Google Chrome, Chromium, MS Edge, Brave) found on this machine. Please install Chrome or specify CHROME_PATH.");
5414
5664
  }
5415
5665
  this.port = options.port || 9200 + Math.floor(Math.random() * 500);
5416
- this.tempUserDataDir = options.userDataDir || join6(tmpdir(), `groupy_chrome_${Date.now()}_${Math.random().toString(36).slice(2)}`);
5417
- mkdirSync6(this.tempUserDataDir, { recursive: true });
5666
+ this.tempUserDataDir = options.userDataDir || join7(tmpdir(), `groupy_chrome_${Date.now()}_${Math.random().toString(36).slice(2)}`);
5667
+ mkdirSync7(this.tempUserDataDir, { recursive: true });
5418
5668
  const isHeadless = options.headless ?? true;
5419
5669
  const launchArgs = [
5420
5670
  executable,
@@ -5491,7 +5741,7 @@ class BrowserLauncher {
5491
5741
  }
5492
5742
  this.proc = null;
5493
5743
  }
5494
- if (this.tempUserDataDir && existsSync12(this.tempUserDataDir)) {
5744
+ if (this.tempUserDataDir && existsSync13(this.tempUserDataDir)) {
5495
5745
  try {
5496
5746
  rmSync(this.tempUserDataDir, { recursive: true, force: true });
5497
5747
  } catch {}
@@ -5513,7 +5763,7 @@ class CdpSession {
5513
5763
  this.wsUrl = wsUrl;
5514
5764
  }
5515
5765
  async connect() {
5516
- return new Promise((resolve12, reject) => {
5766
+ return new Promise((resolve13, reject) => {
5517
5767
  try {
5518
5768
  this.ws = new WebSocket(this.wsUrl);
5519
5769
  const onOpen = async () => {
@@ -5529,7 +5779,7 @@ class CdpSession {
5529
5779
  await this.send("Network.enable").catch(() => {});
5530
5780
  await this.send("Page.setLifecycleEventsEnabled", { enabled: true }).catch(() => {});
5531
5781
  this.setupEventHandlers();
5532
- resolve12();
5782
+ resolve13();
5533
5783
  } catch (err) {
5534
5784
  reject(err);
5535
5785
  }
@@ -5651,13 +5901,13 @@ class CdpSession {
5651
5901
  throw new Error("CDP WebSocket is not connected");
5652
5902
  }
5653
5903
  const id = this.nextId++;
5654
- return new Promise((resolve12, reject) => {
5904
+ return new Promise((resolve13, reject) => {
5655
5905
  const timeoutMs = 30000;
5656
5906
  const timer = setTimeout(() => {
5657
5907
  this.pending.delete(id);
5658
5908
  reject(new Error(`CDP command '${method}' timed out after ${timeoutMs}ms`));
5659
5909
  }, timeoutMs);
5660
- this.pending.set(id, { resolve: resolve12, reject, timer });
5910
+ this.pending.set(id, { resolve: resolve13, reject, timer });
5661
5911
  try {
5662
5912
  this.ws?.send(JSON.stringify({ id, method, params }));
5663
5913
  } catch (err) {
@@ -5852,8 +6102,8 @@ class DomSnapshotEngine {
5852
6102
  }
5853
6103
  }
5854
6104
  // src/mcp/servers/chrome-devtools/controller.ts
5855
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "fs";
5856
- import { dirname as dirname6, resolve as resolve12 } from "path";
6105
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync8 } from "fs";
6106
+ import { dirname as dirname7, resolve as resolve13 } from "path";
5857
6107
  class ChromeDevToolsController {
5858
6108
  launcher = new BrowserLauncher;
5859
6109
  sessions = new Map;
@@ -5981,12 +6231,12 @@ class ChromeDevToolsController {
5981
6231
  } else if (navType === "reload") {
5982
6232
  await cdp.send("Page.reload", { ignoreCache: params.ignoreCache });
5983
6233
  }
5984
- await new Promise((resolve13) => {
5985
- const timer = setTimeout(resolve13, params.timeout || 3000);
6234
+ await new Promise((resolve14) => {
6235
+ const timer = setTimeout(resolve14, params.timeout || 3000);
5986
6236
  const unsub = cdp.on("Page.loadEventFired", () => {
5987
6237
  clearTimeout(timer);
5988
6238
  unsub();
5989
- resolve13();
6239
+ resolve14();
5990
6240
  });
5991
6241
  });
5992
6242
  const evalRes = await cdp.send("Runtime.evaluate", {
@@ -6002,8 +6252,8 @@ class ChromeDevToolsController {
6002
6252
  const { cdp } = this.getSession(params.pageId);
6003
6253
  const snapshot = await DomSnapshotEngine.captureSnapshot(cdp, params.verbose);
6004
6254
  if (params.filePath) {
6005
- const fullPath = resolve12(params.filePath);
6006
- mkdirSync7(dirname6(fullPath), { recursive: true });
6255
+ const fullPath = resolve13(params.filePath);
6256
+ mkdirSync8(dirname7(fullPath), { recursive: true });
6007
6257
  writeFileSync5(fullPath, snapshot.textSnapshot, "utf8");
6008
6258
  return `Snapshot saved to ${params.filePath} (${snapshot.elementsCount} indexed elements)`;
6009
6259
  }
@@ -6031,8 +6281,8 @@ class ChromeDevToolsController {
6031
6281
  });
6032
6282
  const base64Data = res.data;
6033
6283
  if (params.filePath) {
6034
- const fullPath = resolve12(params.filePath);
6035
- mkdirSync7(dirname6(fullPath), { recursive: true });
6284
+ const fullPath = resolve13(params.filePath);
6285
+ mkdirSync8(dirname7(fullPath), { recursive: true });
6036
6286
  writeFileSync5(fullPath, Buffer.from(base64Data, "base64"));
6037
6287
  return { format, filePath: params.filePath };
6038
6288
  }
@@ -6176,8 +6426,8 @@ class ChromeDevToolsController {
6176
6426
  }
6177
6427
  const value = res.result?.value;
6178
6428
  if (params.filePath) {
6179
- const fullPath = resolve12(params.filePath);
6180
- mkdirSync7(dirname6(fullPath), { recursive: true });
6429
+ const fullPath = resolve13(params.filePath);
6430
+ mkdirSync8(dirname7(fullPath), { recursive: true });
6181
6431
  writeFileSync5(fullPath, JSON.stringify(value, null, 2), "utf8");
6182
6432
  return `Script output saved to ${params.filePath}`;
6183
6433
  }
@@ -6670,9 +6920,9 @@ if (false) {}
6670
6920
 
6671
6921
  // src/mcp/servers/chrome-devtools/index.ts
6672
6922
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/chrome-devtools";
6673
- var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve13(__dirname, "server.ts");
6923
+ var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve14(__dirname, "server.ts");
6674
6924
  // src/mcp/servers/web-search/index.ts
6675
- import { resolve as resolve14 } from "path";
6925
+ import { resolve as resolve15 } from "path";
6676
6926
 
6677
6927
  // src/mcp/servers/web-search/html-to-markdown.ts
6678
6928
  class HtmlToMarkdownConverter {
@@ -7324,14 +7574,14 @@ if (false) {}
7324
7574
 
7325
7575
  // src/mcp/servers/web-search/index.ts
7326
7576
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/web-search";
7327
- var WEB_SEARCH_MCP_SERVER_PATH = resolve14(__dirname, "server.ts");
7577
+ var WEB_SEARCH_MCP_SERVER_PATH = resolve15(__dirname, "server.ts");
7328
7578
  // src/mcp/servers/sqlite/index.ts
7329
- import { resolve as resolve16 } from "path";
7579
+ import { resolve as resolve17 } from "path";
7330
7580
 
7331
7581
  // src/mcp/servers/sqlite/db-engine.ts
7332
7582
  import { Database as Database2 } from "bun:sqlite";
7333
- import { resolve as resolve15, isAbsolute } from "path";
7334
- import { readdirSync as readdirSync4 } from "fs";
7583
+ import { resolve as resolve16, isAbsolute } from "path";
7584
+ import { readdirSync as readdirSync5 } from "fs";
7335
7585
 
7336
7586
  class SqliteEngine {
7337
7587
  connections = new Map;
@@ -7353,17 +7603,17 @@ class SqliteEngine {
7353
7603
  this.defaultDbPath = discovered;
7354
7604
  return discovered;
7355
7605
  }
7356
- const fallback = resolve15(process.cwd(), "dev.sqlite");
7606
+ const fallback = resolve16(process.cwd(), "dev.sqlite");
7357
7607
  this.defaultDbPath = fallback;
7358
7608
  return fallback;
7359
7609
  }
7360
- return isAbsolute(dbPath) ? dbPath : resolve15(process.cwd(), dbPath);
7610
+ return isAbsolute(dbPath) ? dbPath : resolve16(process.cwd(), dbPath);
7361
7611
  }
7362
7612
  autoDiscoverDatabase() {
7363
7613
  try {
7364
- const files = readdirSync4(process.cwd());
7614
+ const files = readdirSync5(process.cwd());
7365
7615
  const dbFile = files.find((f) => f.endsWith(".sqlite") || f.endsWith(".sqlite3") || f.endsWith(".db"));
7366
- return dbFile ? resolve15(process.cwd(), dbFile) : null;
7616
+ return dbFile ? resolve16(process.cwd(), dbFile) : null;
7367
7617
  } catch {
7368
7618
  return null;
7369
7619
  }
@@ -7822,7 +8072,7 @@ if (false) {}
7822
8072
 
7823
8073
  // src/mcp/servers/sqlite/index.ts
7824
8074
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
7825
- var SQLITE_MCP_SERVER_PATH = resolve16(__dirname, "server.ts");
8075
+ var SQLITE_MCP_SERVER_PATH = resolve17(__dirname, "server.ts");
7826
8076
  // src/agents/identity.ts
7827
8077
  import { generateKeyPairSync, sign, verify } from "crypto";
7828
8078
  function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
@@ -7865,8 +8115,8 @@ function verifyTaskAction(assertion, payload) {
7865
8115
  }
7866
8116
  }
7867
8117
  // src/agents/roles.ts
7868
- import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
7869
- import { resolve as resolve17, join as join7 } from "path";
8118
+ import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
8119
+ import { resolve as resolve18, join as join8 } from "path";
7870
8120
 
7871
8121
  class AgentRoleRegistry {
7872
8122
  roles = new Map;
@@ -7950,14 +8200,14 @@ class AgentRoleRegistry {
7950
8200
  return cycle === 0 ? base : `${base}_${cycle + 1}`;
7951
8201
  }
7952
8202
  loadRolesFromDir(dirPath) {
7953
- const fullPath = resolve17(dirPath);
7954
- if (!existsSync14(fullPath))
8203
+ const fullPath = resolve18(dirPath);
8204
+ if (!existsSync15(fullPath))
7955
8205
  return;
7956
- const entries = readdirSync5(fullPath);
8206
+ const entries = readdirSync6(fullPath);
7957
8207
  for (const entry of entries) {
7958
8208
  if (entry.endsWith(".json")) {
7959
8209
  try {
7960
- const content = readFileSync9(join7(fullPath, entry), "utf8");
8210
+ const content = readFileSync9(join8(fullPath, entry), "utf8");
7961
8211
  const parsed = JSON.parse(content);
7962
8212
  if (parsed.name && parsed.systemPrompt) {
7963
8213
  this.registerRole(parsed);
@@ -7986,21 +8236,19 @@ class AgentRoleRegistry {
7986
8236
  }
7987
8237
  // src/agents/graph-store.ts
7988
8238
  import { Database as Database3 } from "bun:sqlite";
7989
- import { resolve as resolve18 } from "path";
7990
- import { existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
7991
- import { homedir as homedir4 } from "os";
7992
-
8239
+ import { resolve as resolve19 } from "path";
8240
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
7993
8241
  class AgentGraphStore {
7994
8242
  db;
7995
8243
  constructor(dbPathOrDb) {
7996
8244
  if (dbPathOrDb instanceof Database3) {
7997
8245
  this.db = dbPathOrDb;
7998
8246
  } else {
7999
- const dbPath = dbPathOrDb || resolve18(homedir4(), ".groupy", "agent_graph.db");
8247
+ const dbPath = dbPathOrDb || getAgentGraphDbPath();
8000
8248
  if (dbPath !== ":memory:") {
8001
- const dir = resolve18(dbPath, "..");
8002
- if (!existsSync15(dir)) {
8003
- mkdirSync8(dir, { recursive: true });
8249
+ const dir = resolve19(dbPath, "..");
8250
+ if (!existsSync16(dir)) {
8251
+ mkdirSync9(dir, { recursive: true });
8004
8252
  }
8005
8253
  }
8006
8254
  this.db = new Database3(dbPath);
@@ -8122,8 +8370,8 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
8122
8370
  });
8123
8371
  let resolvePromise;
8124
8372
  let rejectPromise;
8125
- const taskPromise = new Promise((resolve19, reject) => {
8126
- resolvePromise = resolve19;
8373
+ const taskPromise = new Promise((resolve20, reject) => {
8374
+ resolvePromise = resolve20;
8127
8375
  rejectPromise = reject;
8128
8376
  });
8129
8377
  const handle = {
@@ -8422,17 +8670,16 @@ function registerMultiAgentTools(router2, spawner) {
8422
8670
  }
8423
8671
  // src/storage/sqlite-store.ts
8424
8672
  import { Database as Database4 } from "bun:sqlite";
8425
- import { existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
8426
- import { dirname as dirname7, resolve as resolve19 } from "path";
8427
- import { homedir as homedir5 } from "os";
8673
+ import { existsSync as existsSync17, mkdirSync as mkdirSync10 } from "fs";
8674
+ import { dirname as dirname8 } from "path";
8428
8675
  class SqliteThreadStore {
8429
8676
  db;
8430
8677
  constructor(dbPath) {
8431
8678
  const effectivePath = dbPath || this.getDefaultDbPath();
8432
8679
  if (effectivePath !== ":memory:") {
8433
- const dir = dirname7(effectivePath);
8434
- if (!existsSync16(dir)) {
8435
- mkdirSync9(dir, { recursive: true });
8680
+ const dir = dirname8(effectivePath);
8681
+ if (!existsSync17(dir)) {
8682
+ mkdirSync10(dir, { recursive: true });
8436
8683
  }
8437
8684
  }
8438
8685
  this.db = new Database4(effectivePath);
@@ -8441,7 +8688,7 @@ class SqliteThreadStore {
8441
8688
  this.initSchema();
8442
8689
  }
8443
8690
  getDefaultDbPath() {
8444
- return resolve19(homedir5(), ".groupy", "groupy_threads.db");
8691
+ return getThreadsDbPath();
8445
8692
  }
8446
8693
  initSchema() {
8447
8694
  this.db.exec(`
@@ -8664,9 +8911,9 @@ class SessionPersistenceManager {
8664
8911
  }
8665
8912
  }
8666
8913
  // src/skills/loader.ts
8667
- import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync10 } from "fs";
8668
- import { resolve as resolve20, join as join8 } from "path";
8669
- import { homedir as homedir6 } from "os";
8914
+ import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync10 } from "fs";
8915
+ import { resolve as resolve20, join as join9 } from "path";
8916
+ import { homedir as homedir3 } from "os";
8670
8917
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
8671
8918
 
8672
8919
  class SkillsLoader {
@@ -8736,16 +8983,16 @@ class SkillsLoader {
8736
8983
  resolve20(cwd, "skills")
8737
8984
  ];
8738
8985
  for (const cand of candidates) {
8739
- if (existsSync17(cand) && !roots.includes(cand)) {
8986
+ if (existsSync18(cand) && !roots.includes(cand)) {
8740
8987
  roots.push(cand);
8741
8988
  }
8742
8989
  }
8743
8990
  }
8744
8991
  if (this.includeGlobal) {
8745
- roots.push(resolve20(homedir6(), ".groupy", "skills"), resolve20(homedir6(), ".gemini", "config", "skills"));
8992
+ roots.push(getGlobalSkillsDir(), resolve20(homedir3(), ".gemini", "config", "skills"));
8746
8993
  }
8747
8994
  roots.push(...this.customRoots.map((r) => resolve20(r)));
8748
- return roots.filter((r) => existsSync17(r));
8995
+ return roots.filter((r) => existsSync18(r));
8749
8996
  }
8750
8997
  discoverSkills(cwd, options) {
8751
8998
  return this.listSkills(cwd, options);
@@ -8761,12 +9008,12 @@ class SkillsLoader {
8761
9008
  const discovered = new Map;
8762
9009
  for (const root of roots) {
8763
9010
  try {
8764
- const entries = readdirSync6(root, { withFileTypes: true });
9011
+ const entries = readdirSync7(root, { withFileTypes: true });
8765
9012
  for (const entry of entries) {
8766
9013
  if (entry.isDirectory()) {
8767
- const skillDir = join8(root, entry.name);
8768
- const skillFilePath = join8(skillDir, "SKILL.md");
8769
- if (existsSync17(skillFilePath)) {
9014
+ const skillDir = join9(root, entry.name);
9015
+ const skillFilePath = join9(skillDir, "SKILL.md");
9016
+ if (existsSync18(skillFilePath)) {
8770
9017
  const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
8771
9018
  if (meta && !discovered.has(meta.name)) {
8772
9019
  meta.enabled = !this.isSkillDisabled(meta.name);
@@ -8888,143 +9135,282 @@ When tackling complex specialized tasks that match any of these skills, autonomo
8888
9135
  }
8889
9136
  }
8890
9137
  // src/memories/store.ts
8891
- import { existsSync as existsSync18, readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync10 } from "fs";
8892
- import { resolve as resolve21, dirname as dirname8 } from "path";
8893
- import { homedir as homedir7 } from "os";
8894
-
9138
+ import { existsSync as existsSync19, readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync11, readdirSync as readdirSync8 } from "fs";
9139
+ import { resolve as resolve21, join as join10, basename, dirname as dirname9 } from "path";
9140
+ import { createHash } from "crypto";
8895
9141
  class MemoryStore {
8896
9142
  globalPath;
8897
9143
  customWorkspacePath;
8898
9144
  constructor(options = {}) {
8899
- this.globalPath = options.globalPath || resolve21(homedir7(), ".groupy", "memories.md");
9145
+ this.globalPath = options.globalPath || getGlobalMemoriesPath();
8900
9146
  this.customWorkspacePath = options.workspacePath;
8901
9147
  }
8902
- getWorkspacePath(cwd) {
8903
- return this.customWorkspacePath || resolve21(cwd, ".agents", "memories.md");
9148
+ findProjectRoot(cwd) {
9149
+ let current = resolve21(cwd);
9150
+ while (true) {
9151
+ if (existsSync19(join10(current, ".git"))) {
9152
+ return current;
9153
+ }
9154
+ const parent = dirname9(current);
9155
+ if (parent === current) {
9156
+ return resolve21(cwd);
9157
+ }
9158
+ current = parent;
9159
+ }
8904
9160
  }
8905
- addMemory(params) {
8906
- const scope = params.scope || "global";
8907
- const targetFile = scope === "global" ? this.globalPath : this.getWorkspacePath(params.cwd || process.cwd());
8908
- const dir = dirname8(targetFile);
8909
- if (!existsSync18(dir)) {
8910
- mkdirSync10(dir, { recursive: true });
8911
- }
8912
- const existingEntries = this.readMemoryFile(targetFile, scope);
8913
- const normalized = params.content.trim();
8914
- const duplicate = existingEntries.find((e) => e.category === params.category && e.content.toLowerCase() === normalized.toLowerCase());
8915
- if (duplicate) {
8916
- return duplicate;
8917
- }
8918
- const newEntry = {
8919
- id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
8920
- category: params.category,
8921
- content: normalized,
8922
- scope,
8923
- createdAt: Date.now()
8924
- };
8925
- existingEntries.push(newEntry);
8926
- this.writeMemoryFile(targetFile, existingEntries);
8927
- return newEntry;
9161
+ getProjectSlug(cwd) {
9162
+ const root = this.findProjectRoot(cwd);
9163
+ const folderName = basename(root).toLowerCase().replace(/[^a-z0-9_-]/g, "-") || "project";
9164
+ const hash = createHash("sha256").update(resolve21(root)).digest("hex").slice(0, 6);
9165
+ return `${folderName}-${hash}`;
8928
9166
  }
8929
- getAllMemories(cwd) {
8930
- const globalEntries = this.readMemoryFile(this.globalPath, "global");
8931
- const workspacePath = this.getWorkspacePath(cwd);
8932
- const workspaceEntries = existsSync18(workspacePath) ? this.readMemoryFile(workspacePath, "workspace") : [];
8933
- return [...globalEntries, ...workspaceEntries];
9167
+ getProjectMemoryDir(cwd) {
9168
+ if (this.customWorkspacePath) {
9169
+ const dir2 = resolve21(this.customWorkspacePath);
9170
+ if (!existsSync19(dir2)) {
9171
+ try {
9172
+ mkdirSync11(dir2, { recursive: true });
9173
+ } catch {}
9174
+ }
9175
+ return dir2;
9176
+ }
9177
+ const slug = this.getProjectSlug(cwd);
9178
+ const dir = join10(getProjectsDir(), slug, "memory");
9179
+ if (!existsSync19(dir)) {
9180
+ try {
9181
+ mkdirSync11(dir, { recursive: true });
9182
+ } catch {}
9183
+ }
9184
+ return dir;
9185
+ }
9186
+ getMemoryIndexPath(cwd) {
9187
+ return join10(this.getProjectMemoryDir(cwd), "MEMORY.md");
9188
+ }
9189
+ normalizeCategory(raw) {
9190
+ const cat = raw.toLowerCase().trim();
9191
+ if (cat === "user" || cat === "preference")
9192
+ return "user";
9193
+ if (cat === "feedback" || cat === "guideline")
9194
+ return "feedback";
9195
+ if (cat === "project" || cat === "architecture")
9196
+ return "project";
9197
+ if (cat === "reference" || cat === "note")
9198
+ return "reference";
9199
+ return "project";
9200
+ }
9201
+ saveTopicMemory(params) {
9202
+ const type = this.normalizeCategory(params.category);
9203
+ const sanitizedName = params.name.toLowerCase().trim().replace(/[^a-z0-9_-]/g, "_").replace(/^_+|_+$/g, "") || `note_${Date.now()}`;
9204
+ const memoryDir = this.getProjectMemoryDir(params.cwd);
9205
+ const fileName = `${type}_${sanitizedName}.md`;
9206
+ const filePath = join10(memoryDir, fileName);
9207
+ const nowIso = new Date().toISOString();
9208
+ const cleanContent = params.content.trim();
9209
+ const desc = (params.description || cleanContent.split(`
9210
+ `)[0] || sanitizedName).replace(/[\r\n]+/g, " ");
9211
+ const frontmatter = [
9212
+ "---",
9213
+ `type: ${type}`,
9214
+ `name: ${sanitizedName}`,
9215
+ `description: ${desc}`,
9216
+ `modified: ${nowIso}`,
9217
+ "---",
9218
+ "",
9219
+ `# ${sanitizedName.replace(/_/g, " ").toUpperCase()}`,
9220
+ "",
9221
+ cleanContent,
9222
+ ""
9223
+ ].join(`
9224
+ `);
9225
+ writeFileSync6(filePath, frontmatter, "utf8");
9226
+ this.syncMemoryIndex(params.cwd);
9227
+ return {
9228
+ id: `mem_${sanitizedName}`,
9229
+ category: type,
9230
+ name: sanitizedName,
9231
+ description: desc,
9232
+ content: cleanContent,
9233
+ scope: "project",
9234
+ createdAt: Date.now(),
9235
+ modifiedAt: Date.now(),
9236
+ filePath
9237
+ };
8934
9238
  }
8935
- readMemoryFile(filePath, scope) {
8936
- if (!existsSync18(filePath))
8937
- return [];
9239
+ readTopicMemory(topicNameOrFile, cwd) {
9240
+ const memoryDir = this.getProjectMemoryDir(cwd);
9241
+ let targetPath = join10(memoryDir, topicNameOrFile);
9242
+ if (!existsSync19(targetPath)) {
9243
+ if (!topicNameOrFile.endsWith(".md")) {
9244
+ targetPath = join10(memoryDir, `${topicNameOrFile}.md`);
9245
+ }
9246
+ }
9247
+ if (!existsSync19(targetPath)) {
9248
+ const files = readdirSync8(memoryDir);
9249
+ const match = files.find((f) => f.includes(topicNameOrFile));
9250
+ if (match) {
9251
+ targetPath = join10(memoryDir, match);
9252
+ } else {
9253
+ return null;
9254
+ }
9255
+ }
8938
9256
  try {
8939
- const content = readFileSync11(filePath, "utf8");
8940
- const lines = content.split(`
9257
+ const raw = readFileSync11(targetPath, "utf8");
9258
+ return this.parseTopicFile(raw, targetPath);
9259
+ } catch {
9260
+ return null;
9261
+ }
9262
+ }
9263
+ parseTopicFile(raw, filePath) {
9264
+ const lines = raw.split(`
8941
9265
  `);
8942
- const entries = [];
8943
- let currentCategory = "preference";
8944
- for (const line of lines) {
8945
- const trimmed = line.trim();
8946
- if (trimmed.startsWith("## Preferences") || trimmed.startsWith("## User Preferences")) {
8947
- currentCategory = "preference";
8948
- } else if (trimmed.startsWith("## Guidelines") || trimmed.startsWith("## Coding Guidelines")) {
8949
- currentCategory = "guideline";
8950
- } else if (trimmed.startsWith("## Architecture") || trimmed.startsWith("## Project Architecture")) {
8951
- currentCategory = "architecture";
8952
- } else if (trimmed.startsWith("## Notes") || trimmed.startsWith("## General Notes")) {
8953
- currentCategory = "note";
8954
- } else if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) {
8955
- const itemText = trimmed.slice(2).trim();
8956
- if (itemText) {
8957
- entries.push({
8958
- id: `mem_${entries.length + 1}`,
8959
- category: currentCategory,
8960
- content: itemText,
8961
- scope,
8962
- createdAt: Date.now()
8963
- });
8964
- }
9266
+ let inFm = false;
9267
+ let type = "project";
9268
+ let name = basename(filePath, ".md");
9269
+ let description;
9270
+ let modified = new Date().toISOString();
9271
+ const bodyLines = [];
9272
+ for (let i = 0;i < lines.length; i++) {
9273
+ const line = lines[i];
9274
+ if (i === 0 && line.trim() === "---") {
9275
+ inFm = true;
9276
+ continue;
9277
+ }
9278
+ if (inFm) {
9279
+ if (line.trim() === "---") {
9280
+ inFm = false;
9281
+ continue;
9282
+ }
9283
+ const colonIdx = line.indexOf(":");
9284
+ if (colonIdx !== -1) {
9285
+ const key = line.slice(0, colonIdx).trim();
9286
+ const val = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, "");
9287
+ if (key === "type")
9288
+ type = this.normalizeCategory(val);
9289
+ else if (key === "name")
9290
+ name = val;
9291
+ else if (key === "description")
9292
+ description = val;
9293
+ else if (key === "modified")
9294
+ modified = val;
8965
9295
  }
9296
+ } else {
9297
+ bodyLines.push(line);
8966
9298
  }
8967
- return entries;
8968
- } catch {
8969
- return [];
8970
9299
  }
8971
- }
8972
- writeMemoryFile(filePath, entries) {
8973
- const categories = {
8974
- preference: [],
8975
- guideline: [],
8976
- architecture: [],
8977
- note: []
9300
+ return {
9301
+ type,
9302
+ name,
9303
+ description,
9304
+ modified,
9305
+ content: bodyLines.join(`
9306
+ `).trim(),
9307
+ filePath
8978
9308
  };
8979
- for (const entry of entries) {
8980
- categories[entry.category].push(entry.content);
9309
+ }
9310
+ syncMemoryIndex(cwd) {
9311
+ const memoryDir = this.getProjectMemoryDir(cwd);
9312
+ const indexPath = join10(memoryDir, "MEMORY.md");
9313
+ const files = existsSync19(memoryDir) ? readdirSync8(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md") : [];
9314
+ const items2 = [];
9315
+ for (const f of files) {
9316
+ try {
9317
+ const full = join10(memoryDir, f);
9318
+ const parsed = this.parseTopicFile(readFileSync11(full, "utf8"), full);
9319
+ items2.push({
9320
+ type: parsed.type,
9321
+ name: parsed.name,
9322
+ desc: parsed.description || parsed.content.split(`
9323
+ `)[0] || parsed.name,
9324
+ file: f
9325
+ });
9326
+ } catch {}
8981
9327
  }
8982
- let markdown = `# Groupy Persistent Memories
8983
-
8984
- `;
8985
- if (categories.preference.length > 0) {
8986
- markdown += `## User Preferences
8987
- ${categories.preference.map((p) => `- ${p}`).join(`
8988
- `)}
8989
-
8990
- `;
9328
+ const indexLines = [
9329
+ "# Project Auto-Memory Index",
9330
+ "",
9331
+ "This index is loaded at session startup. Detailed topics can be retrieved via read_memory tool.",
9332
+ ""
9333
+ ];
9334
+ for (const item of items2) {
9335
+ indexLines.push(`- [${item.type}] **${item.name}**: ${item.desc} (topic: ${item.file})`);
8991
9336
  }
8992
- if (categories.guideline.length > 0) {
8993
- markdown += `## Coding Guidelines
8994
- ${categories.guideline.map((g) => `- ${g}`).join(`
8995
- `)}
8996
-
8997
- `;
9337
+ const boundedLines = indexLines.slice(0, 200);
9338
+ writeFileSync6(indexPath, boundedLines.join(`
9339
+ `) + `
9340
+ `, "utf8");
9341
+ }
9342
+ loadMemoryIndex(cwd) {
9343
+ const indexPath = this.getMemoryIndexPath(cwd);
9344
+ if (!existsSync19(indexPath))
9345
+ return "";
9346
+ try {
9347
+ const raw = readFileSync11(indexPath, "utf8");
9348
+ const byteLimit = 25 * 1024;
9349
+ const sliced = raw.length > byteLimit ? raw.slice(0, byteLimit) : raw;
9350
+ const lines = sliced.split(`
9351
+ `).slice(0, 200);
9352
+ return lines.join(`
9353
+ `).trim();
9354
+ } catch {
9355
+ return "";
8998
9356
  }
8999
- if (categories.architecture.length > 0) {
9000
- markdown += `## Project Architecture
9001
- ${categories.architecture.map((a) => `- ${a}`).join(`
9002
- `)}
9003
-
9004
- `;
9357
+ }
9358
+ listProjectMemories(cwd) {
9359
+ const memoryDir = this.getProjectMemoryDir(cwd);
9360
+ if (!existsSync19(memoryDir))
9361
+ return [];
9362
+ const files = readdirSync8(memoryDir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
9363
+ const list = [];
9364
+ for (const f of files) {
9365
+ try {
9366
+ const full = join10(memoryDir, f);
9367
+ list.push(this.parseTopicFile(readFileSync11(full, "utf8"), full));
9368
+ } catch {}
9005
9369
  }
9006
- if (categories.note.length > 0) {
9007
- markdown += `## General Notes
9008
- ${categories.note.map((n) => `- ${n}`).join(`
9009
- `)}
9010
-
9011
- `;
9370
+ return list;
9371
+ }
9372
+ addMemory(params) {
9373
+ const cwd = params.cwd || process.cwd();
9374
+ const type = this.normalizeCategory(params.category);
9375
+ const name = params.name || `${type}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
9376
+ const entry = this.saveTopicMemory({
9377
+ category: type,
9378
+ name,
9379
+ content: params.content,
9380
+ cwd
9381
+ });
9382
+ if (params.scope) {
9383
+ entry.scope = params.scope;
9012
9384
  }
9013
- writeFileSync6(filePath, markdown.trim() + `
9014
- `, "utf8");
9385
+ return entry;
9386
+ }
9387
+ getAllMemories(cwd) {
9388
+ const projectTopics = this.listProjectMemories(cwd).map((t) => ({
9389
+ id: `mem_${t.name}`,
9390
+ category: t.type,
9391
+ name: t.name,
9392
+ description: t.description,
9393
+ content: t.content,
9394
+ scope: t.type === "user" ? "global" : "workspace",
9395
+ createdAt: new Date(t.modified).getTime() || Date.now(),
9396
+ filePath: t.filePath
9397
+ }));
9398
+ return projectTopics;
9015
9399
  }
9016
9400
  formatMemoriesPrompt(cwd) {
9017
- const memories = this.getAllMemories(cwd);
9018
- if (memories.length === 0)
9401
+ const indexContent = this.loadMemoryIndex(cwd);
9402
+ if (!indexContent)
9019
9403
  return "";
9020
- const lines = memories.map((m) => `- [${m.category}] (${m.scope}): ${m.content}`);
9021
- return `
9022
- ## Persistent User Preferences & Memory Bank
9023
- <user_memories>
9024
- ${lines.join(`
9025
- `)}
9026
- </user_memories>
9027
- Strictly respect these learned user preferences and project architectural conventions.`;
9404
+ return [
9405
+ "",
9406
+ "## Project Auto-Memory (Persistent Learnings)",
9407
+ "<auto_memory>",
9408
+ indexContent,
9409
+ "</auto_memory>",
9410
+ "Apply these persistent project learnings, user preferences, and feedback across all tasks.",
9411
+ "If more context is needed for a specific topic, retrieve it using the `read_memory` tool."
9412
+ ].join(`
9413
+ `);
9028
9414
  }
9029
9415
  }
9030
9416
  // src/worktree/git.ts
@@ -9155,8 +9541,8 @@ async function removeWorktreeGit(repoRoot, worktreePath, deleteBranch = false) {
9155
9541
  return { success: true };
9156
9542
  }
9157
9543
  // src/worktree/manager.ts
9158
- import { resolve as resolve23, join as join9 } from "path";
9159
- import { existsSync as existsSync19, mkdirSync as mkdirSync11, writeFileSync as writeFileSync7, readFileSync as readFileSync12 } from "fs";
9544
+ import { resolve as resolve23, join as join11 } from "path";
9545
+ import { existsSync as existsSync20, mkdirSync as mkdirSync12, writeFileSync as writeFileSync7, readFileSync as readFileSync12 } from "fs";
9160
9546
  var DEFAULT_WORKTREE_KEEP_COUNT = 15;
9161
9547
 
9162
9548
  class WorktreeManager {
@@ -9180,15 +9566,15 @@ class WorktreeManager {
9180
9566
  const branchName = options.branch || `groupy/${taskId}`;
9181
9567
  const targetDir = options.worktreePath || (this.baseStorageDir ? resolve23(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve23(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
9182
9568
  const worktreeParent = resolve23(targetDir, "..");
9183
- if (!existsSync19(worktreeParent)) {
9184
- mkdirSync11(worktreeParent, { recursive: true });
9569
+ if (!existsSync20(worktreeParent)) {
9570
+ mkdirSync12(worktreeParent, { recursive: true });
9185
9571
  }
9186
9572
  const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
9187
9573
  const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
9188
9574
  if (!result.success) {
9189
9575
  throw new Error(`Failed to create git worktree: ${result.error}`);
9190
9576
  }
9191
- const metaPath = join9(targetDir, "groupy-thread.json");
9577
+ const metaPath = join11(targetDir, "groupy-thread.json");
9192
9578
  try {
9193
9579
  writeFileSync7(metaPath, JSON.stringify({
9194
9580
  version: 1,
@@ -9214,8 +9600,8 @@ class WorktreeManager {
9214
9600
  return [];
9215
9601
  const worktrees = await listWorktreesGit(repoRoot);
9216
9602
  return worktrees.map((wt) => {
9217
- const metaPath = join9(wt.path, "groupy-thread.json");
9218
- if (existsSync19(metaPath)) {
9603
+ const metaPath = join11(wt.path, "groupy-thread.json");
9604
+ if (existsSync20(metaPath)) {
9219
9605
  try {
9220
9606
  const raw = JSON.parse(readFileSync12(metaPath, "utf8"));
9221
9607
  return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
@@ -9293,7 +9679,7 @@ class WorktreeManager {
9293
9679
  }
9294
9680
  }
9295
9681
  // src/auth/oauth.ts
9296
- import { randomBytes, createHash } from "crypto";
9682
+ import { randomBytes, createHash as createHash2 } from "crypto";
9297
9683
  import { exec } from "child_process";
9298
9684
  class AuthClient {
9299
9685
  store;
@@ -9446,7 +9832,7 @@ class AuthClient {
9446
9832
  return randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9]/g, "").slice(0, 64);
9447
9833
  }
9448
9834
  generateCodeChallenge(verifier) {
9449
- return createHash("sha256").update(verifier).digest("base64url");
9835
+ return createHash2("sha256").update(verifier).digest("base64url");
9450
9836
  }
9451
9837
  }
9452
9838
  // src/ui/components/claude/claude-header.tsx
@@ -10996,11 +11382,15 @@ export {
10996
11382
  captureWorldState,
10997
11383
  compactHistory,
10998
11384
  createAgentIdentity,
11385
+ createAutoMemoryTools,
10999
11386
  createCodeModeTools,
11000
11387
  createDefaultTools,
11001
11388
  createFileSearchTools,
11389
+ createListMemoriesTool,
11002
11390
  createMultiAgentTools,
11391
+ createReadMemoryTool,
11003
11392
  createRememberTool,
11393
+ createSaveMemoryTool,
11004
11394
  createShellTool,
11005
11395
  createSkillTool,
11006
11396
  createWorktreeGit,