@pikaa-ai/pikaa 0.3.14 → 0.3.16

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) {
@@ -4260,13 +4499,13 @@ class Session {
4260
4499
  type: "StatusChanged",
4261
4500
  status: "waiting_approval"
4262
4501
  });
4263
- return new Promise((resolve11) => {
4502
+ return new Promise((resolve12) => {
4264
4503
  this.pendingApprovals.set(params.approvalId, (approved) => {
4265
4504
  this.emitEvent({
4266
4505
  type: "StatusChanged",
4267
4506
  status: "running"
4268
4507
  });
4269
- resolve11(approved);
4508
+ resolve12(approved);
4270
4509
  });
4271
4510
  });
4272
4511
  }
@@ -4289,13 +4528,13 @@ class Session {
4289
4528
  type: "StatusChanged",
4290
4529
  status: "waiting_user_input"
4291
4530
  });
4292
- return new Promise((resolve11) => {
4531
+ return new Promise((resolve12) => {
4293
4532
  this.pendingUserQuestions.set(params.questionId, (answer) => {
4294
4533
  this.emitEvent({
4295
4534
  type: "StatusChanged",
4296
4535
  status: "running"
4297
4536
  });
4298
- resolve11(answer);
4537
+ resolve12(answer);
4299
4538
  });
4300
4539
  });
4301
4540
  }
@@ -4325,7 +4564,7 @@ class Session {
4325
4564
  return handleTurnInput(this, { text, images });
4326
4565
  }
4327
4566
  async promptAndWait(text, images, timeoutMs = 30000) {
4328
- return new Promise((resolve11, reject) => {
4567
+ return new Promise((resolve12, reject) => {
4329
4568
  const timer = setTimeout(() => {
4330
4569
  unsub();
4331
4570
  reject(new Error(`Turn timed out after ${timeoutMs}ms`));
@@ -4334,7 +4573,7 @@ class Session {
4334
4573
  if (event.msg.type === "TurnCompleted") {
4335
4574
  clearTimeout(timer);
4336
4575
  unsub();
4337
- resolve11();
4576
+ resolve12();
4338
4577
  } else if (event.msg.type === "Error") {
4339
4578
  clearTimeout(timer);
4340
4579
  unsub();
@@ -4353,8 +4592,8 @@ class Session {
4353
4592
  if (this.submissionQueue.length > 0) {
4354
4593
  yield this.submissionQueue.shift();
4355
4594
  } else {
4356
- const nextSub = await new Promise((resolve11) => {
4357
- this.submissionResolvers.push(resolve11);
4595
+ const nextSub = await new Promise((resolve12) => {
4596
+ this.submissionResolvers.push(resolve12);
4358
4597
  });
4359
4598
  yield nextSub;
4360
4599
  }
@@ -4618,13 +4857,13 @@ class StdioTransport {
4618
4857
  if (this.isClosed || !this.proc || !this.proc.stdin) {
4619
4858
  throw new GroupyError("MCP Stdio transport is closed");
4620
4859
  }
4621
- return new Promise((resolve11, reject) => {
4860
+ return new Promise((resolve12, reject) => {
4622
4861
  const timeoutMs = 30000;
4623
4862
  const timer = setTimeout(() => {
4624
4863
  this.pendingRequests.delete(request.id);
4625
4864
  reject(new GroupyError(`MCP request timed out after ${timeoutMs}ms (method: ${request.method})`));
4626
4865
  }, timeoutMs);
4627
- this.pendingRequests.set(request.id, { resolve: resolve11, reject, timer });
4866
+ this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
4628
4867
  try {
4629
4868
  const payload = JSON.stringify(request) + `
4630
4869
  `;
@@ -4757,12 +4996,12 @@ class SseTransport {
4757
4996
  if (!this.messageUrl) {
4758
4997
  this.messageUrl = this.endpointUrl;
4759
4998
  }
4760
- return new Promise((resolve11, reject) => {
4999
+ return new Promise((resolve12, reject) => {
4761
5000
  const timer = setTimeout(() => {
4762
5001
  this.pendingRequests.delete(request.id);
4763
5002
  reject(new GroupyError(`MCP SSE request timed out (method: ${request.method})`));
4764
5003
  }, 30000);
4765
- this.pendingRequests.set(request.id, { resolve: resolve11, reject, timer });
5004
+ this.pendingRequests.set(request.id, { resolve: resolve12, reject, timer });
4766
5005
  fetch(this.messageUrl, {
4767
5006
  method: "POST",
4768
5007
  headers: {
@@ -4999,8 +5238,8 @@ class McpClient {
4999
5238
  }
5000
5239
  }
5001
5240
  // src/mcp/manager.ts
5002
- import { existsSync as existsSync11, readFileSync as readFileSync8, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "fs";
5003
- 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";
5004
5243
  class McpManager {
5005
5244
  clients = new Map;
5006
5245
  serverConfigs = new Map;
@@ -5034,8 +5273,8 @@ class McpManager {
5034
5273
  }
5035
5274
  }
5036
5275
  async loadConfigFile(filePath) {
5037
- const fullPath = resolve11(filePath);
5038
- if (!existsSync11(fullPath))
5276
+ const fullPath = resolve12(filePath);
5277
+ if (!existsSync12(fullPath))
5039
5278
  return;
5040
5279
  this.loadedConfigFiles.add(fullPath);
5041
5280
  try {
@@ -5288,13 +5527,13 @@ class McpManager {
5288
5527
  `);
5289
5528
  }
5290
5529
  saveServerToConfigFile(filePath, name, config) {
5291
- const fullPath = resolve11(filePath);
5292
- const dir = dirname5(fullPath);
5293
- if (!existsSync11(dir)) {
5294
- mkdirSync5(dir, { recursive: true });
5530
+ const fullPath = resolve12(filePath);
5531
+ const dir = dirname6(fullPath);
5532
+ if (!existsSync12(dir)) {
5533
+ mkdirSync6(dir, { recursive: true });
5295
5534
  }
5296
5535
  let existing = { mcpServers: {} };
5297
- if (existsSync11(fullPath)) {
5536
+ if (existsSync12(fullPath)) {
5298
5537
  try {
5299
5538
  const content = readFileSync8(fullPath, "utf8");
5300
5539
  existing = JSON.parse(content);
@@ -5308,8 +5547,8 @@ class McpManager {
5308
5547
  this.loadedConfigFiles.add(fullPath);
5309
5548
  }
5310
5549
  removeServerFromConfigFile(filePath, name) {
5311
- const fullPath = resolve11(filePath);
5312
- if (!existsSync11(fullPath))
5550
+ const fullPath = resolve12(filePath);
5551
+ if (!existsSync12(fullPath))
5313
5552
  return false;
5314
5553
  try {
5315
5554
  const content = readFileSync8(fullPath, "utf8");
@@ -5339,11 +5578,11 @@ class McpManager {
5339
5578
  }
5340
5579
  }
5341
5580
  getDefaultConfigFile(cwd = process.cwd()) {
5342
- const workspaceConfig = join5(cwd, ".mcp.json");
5343
- if (existsSync11(workspaceConfig))
5581
+ const workspaceConfig = join6(cwd, ".mcp.json");
5582
+ if (existsSync12(workspaceConfig))
5344
5583
  return workspaceConfig;
5345
- const altConfig = join5(cwd, "mcp_config.json");
5346
- if (existsSync11(altConfig))
5584
+ const altConfig = join6(cwd, "mcp_config.json");
5585
+ if (existsSync12(altConfig))
5347
5586
  return altConfig;
5348
5587
  return workspaceConfig;
5349
5588
  }
@@ -5362,11 +5601,11 @@ class McpManager {
5362
5601
  }
5363
5602
  }
5364
5603
  // src/mcp/servers/chrome-devtools/index.ts
5365
- import { resolve as resolve13 } from "path";
5604
+ import { resolve as resolve14 } from "path";
5366
5605
 
5367
5606
  // src/mcp/servers/chrome-devtools/launcher.ts
5368
- import { existsSync as existsSync12, mkdirSync as mkdirSync6, rmSync } from "fs";
5369
- import { join as join6 } from "path";
5607
+ import { existsSync as existsSync13, mkdirSync as mkdirSync7, rmSync } from "fs";
5608
+ import { join as join7 } from "path";
5370
5609
  import { tmpdir } from "os";
5371
5610
  class BrowserLauncher {
5372
5611
  proc = null;
@@ -5374,21 +5613,21 @@ class BrowserLauncher {
5374
5613
  wsDebuggerUrl = null;
5375
5614
  port = 0;
5376
5615
  static findBrowserExecutable() {
5377
- if (process.env.CHROME_PATH && existsSync12(process.env.CHROME_PATH)) {
5616
+ if (process.env.CHROME_PATH && existsSync13(process.env.CHROME_PATH)) {
5378
5617
  return process.env.CHROME_PATH;
5379
5618
  }
5380
5619
  if (process.platform === "win32") {
5381
5620
  const candidates = [
5382
5621
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
5383
5622
  "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
5384
- join6(process.env.LOCALAPPDATA || "", "Google\\Chrome\\Application\\chrome.exe"),
5623
+ join7(process.env.LOCALAPPDATA || "", "Google\\Chrome\\Application\\chrome.exe"),
5385
5624
  "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
5386
5625
  "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
5387
5626
  "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
5388
- join6(process.env.LOCALAPPDATA || "", "BraveSoftware\\Brave-Browser\\Application\\brave.exe")
5627
+ join7(process.env.LOCALAPPDATA || "", "BraveSoftware\\Brave-Browser\\Application\\brave.exe")
5389
5628
  ];
5390
5629
  for (const path of candidates) {
5391
- if (path && existsSync12(path))
5630
+ if (path && existsSync13(path))
5392
5631
  return path;
5393
5632
  }
5394
5633
  } else if (process.platform === "darwin") {
@@ -5399,7 +5638,7 @@ class BrowserLauncher {
5399
5638
  "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
5400
5639
  ];
5401
5640
  for (const path of candidates) {
5402
- if (existsSync12(path))
5641
+ if (existsSync13(path))
5403
5642
  return path;
5404
5643
  }
5405
5644
  } else {
@@ -5412,7 +5651,7 @@ class BrowserLauncher {
5412
5651
  "/usr/bin/microsoft-edge"
5413
5652
  ];
5414
5653
  for (const path of candidates) {
5415
- if (existsSync12(path))
5654
+ if (existsSync13(path))
5416
5655
  return path;
5417
5656
  }
5418
5657
  }
@@ -5424,8 +5663,8 @@ class BrowserLauncher {
5424
5663
  throw new Error("No supported browser (Google Chrome, Chromium, MS Edge, Brave) found on this machine. Please install Chrome or specify CHROME_PATH.");
5425
5664
  }
5426
5665
  this.port = options.port || 9200 + Math.floor(Math.random() * 500);
5427
- this.tempUserDataDir = options.userDataDir || join6(tmpdir(), `groupy_chrome_${Date.now()}_${Math.random().toString(36).slice(2)}`);
5428
- 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 });
5429
5668
  const isHeadless = options.headless ?? true;
5430
5669
  const launchArgs = [
5431
5670
  executable,
@@ -5502,7 +5741,7 @@ class BrowserLauncher {
5502
5741
  }
5503
5742
  this.proc = null;
5504
5743
  }
5505
- if (this.tempUserDataDir && existsSync12(this.tempUserDataDir)) {
5744
+ if (this.tempUserDataDir && existsSync13(this.tempUserDataDir)) {
5506
5745
  try {
5507
5746
  rmSync(this.tempUserDataDir, { recursive: true, force: true });
5508
5747
  } catch {}
@@ -5524,7 +5763,7 @@ class CdpSession {
5524
5763
  this.wsUrl = wsUrl;
5525
5764
  }
5526
5765
  async connect() {
5527
- return new Promise((resolve12, reject) => {
5766
+ return new Promise((resolve13, reject) => {
5528
5767
  try {
5529
5768
  this.ws = new WebSocket(this.wsUrl);
5530
5769
  const onOpen = async () => {
@@ -5540,7 +5779,7 @@ class CdpSession {
5540
5779
  await this.send("Network.enable").catch(() => {});
5541
5780
  await this.send("Page.setLifecycleEventsEnabled", { enabled: true }).catch(() => {});
5542
5781
  this.setupEventHandlers();
5543
- resolve12();
5782
+ resolve13();
5544
5783
  } catch (err) {
5545
5784
  reject(err);
5546
5785
  }
@@ -5662,13 +5901,13 @@ class CdpSession {
5662
5901
  throw new Error("CDP WebSocket is not connected");
5663
5902
  }
5664
5903
  const id = this.nextId++;
5665
- return new Promise((resolve12, reject) => {
5904
+ return new Promise((resolve13, reject) => {
5666
5905
  const timeoutMs = 30000;
5667
5906
  const timer = setTimeout(() => {
5668
5907
  this.pending.delete(id);
5669
5908
  reject(new Error(`CDP command '${method}' timed out after ${timeoutMs}ms`));
5670
5909
  }, timeoutMs);
5671
- this.pending.set(id, { resolve: resolve12, reject, timer });
5910
+ this.pending.set(id, { resolve: resolve13, reject, timer });
5672
5911
  try {
5673
5912
  this.ws?.send(JSON.stringify({ id, method, params }));
5674
5913
  } catch (err) {
@@ -5863,8 +6102,8 @@ class DomSnapshotEngine {
5863
6102
  }
5864
6103
  }
5865
6104
  // src/mcp/servers/chrome-devtools/controller.ts
5866
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync7 } from "fs";
5867
- 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";
5868
6107
  class ChromeDevToolsController {
5869
6108
  launcher = new BrowserLauncher;
5870
6109
  sessions = new Map;
@@ -5992,12 +6231,12 @@ class ChromeDevToolsController {
5992
6231
  } else if (navType === "reload") {
5993
6232
  await cdp.send("Page.reload", { ignoreCache: params.ignoreCache });
5994
6233
  }
5995
- await new Promise((resolve13) => {
5996
- const timer = setTimeout(resolve13, params.timeout || 3000);
6234
+ await new Promise((resolve14) => {
6235
+ const timer = setTimeout(resolve14, params.timeout || 3000);
5997
6236
  const unsub = cdp.on("Page.loadEventFired", () => {
5998
6237
  clearTimeout(timer);
5999
6238
  unsub();
6000
- resolve13();
6239
+ resolve14();
6001
6240
  });
6002
6241
  });
6003
6242
  const evalRes = await cdp.send("Runtime.evaluate", {
@@ -6013,8 +6252,8 @@ class ChromeDevToolsController {
6013
6252
  const { cdp } = this.getSession(params.pageId);
6014
6253
  const snapshot = await DomSnapshotEngine.captureSnapshot(cdp, params.verbose);
6015
6254
  if (params.filePath) {
6016
- const fullPath = resolve12(params.filePath);
6017
- mkdirSync7(dirname6(fullPath), { recursive: true });
6255
+ const fullPath = resolve13(params.filePath);
6256
+ mkdirSync8(dirname7(fullPath), { recursive: true });
6018
6257
  writeFileSync5(fullPath, snapshot.textSnapshot, "utf8");
6019
6258
  return `Snapshot saved to ${params.filePath} (${snapshot.elementsCount} indexed elements)`;
6020
6259
  }
@@ -6042,8 +6281,8 @@ class ChromeDevToolsController {
6042
6281
  });
6043
6282
  const base64Data = res.data;
6044
6283
  if (params.filePath) {
6045
- const fullPath = resolve12(params.filePath);
6046
- mkdirSync7(dirname6(fullPath), { recursive: true });
6284
+ const fullPath = resolve13(params.filePath);
6285
+ mkdirSync8(dirname7(fullPath), { recursive: true });
6047
6286
  writeFileSync5(fullPath, Buffer.from(base64Data, "base64"));
6048
6287
  return { format, filePath: params.filePath };
6049
6288
  }
@@ -6187,8 +6426,8 @@ class ChromeDevToolsController {
6187
6426
  }
6188
6427
  const value = res.result?.value;
6189
6428
  if (params.filePath) {
6190
- const fullPath = resolve12(params.filePath);
6191
- mkdirSync7(dirname6(fullPath), { recursive: true });
6429
+ const fullPath = resolve13(params.filePath);
6430
+ mkdirSync8(dirname7(fullPath), { recursive: true });
6192
6431
  writeFileSync5(fullPath, JSON.stringify(value, null, 2), "utf8");
6193
6432
  return `Script output saved to ${params.filePath}`;
6194
6433
  }
@@ -6681,9 +6920,9 @@ if (false) {}
6681
6920
 
6682
6921
  // src/mcp/servers/chrome-devtools/index.ts
6683
6922
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/chrome-devtools";
6684
- var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve13(__dirname, "server.ts");
6923
+ var CHROME_DEVTOOLS_MCP_SERVER_PATH = resolve14(__dirname, "server.ts");
6685
6924
  // src/mcp/servers/web-search/index.ts
6686
- import { resolve as resolve14 } from "path";
6925
+ import { resolve as resolve15 } from "path";
6687
6926
 
6688
6927
  // src/mcp/servers/web-search/html-to-markdown.ts
6689
6928
  class HtmlToMarkdownConverter {
@@ -7335,14 +7574,14 @@ if (false) {}
7335
7574
 
7336
7575
  // src/mcp/servers/web-search/index.ts
7337
7576
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/web-search";
7338
- var WEB_SEARCH_MCP_SERVER_PATH = resolve14(__dirname, "server.ts");
7577
+ var WEB_SEARCH_MCP_SERVER_PATH = resolve15(__dirname, "server.ts");
7339
7578
  // src/mcp/servers/sqlite/index.ts
7340
- import { resolve as resolve16 } from "path";
7579
+ import { resolve as resolve17 } from "path";
7341
7580
 
7342
7581
  // src/mcp/servers/sqlite/db-engine.ts
7343
7582
  import { Database as Database2 } from "bun:sqlite";
7344
- import { resolve as resolve15, isAbsolute } from "path";
7345
- import { readdirSync as readdirSync4 } from "fs";
7583
+ import { resolve as resolve16, isAbsolute } from "path";
7584
+ import { readdirSync as readdirSync5 } from "fs";
7346
7585
 
7347
7586
  class SqliteEngine {
7348
7587
  connections = new Map;
@@ -7364,17 +7603,17 @@ class SqliteEngine {
7364
7603
  this.defaultDbPath = discovered;
7365
7604
  return discovered;
7366
7605
  }
7367
- const fallback = resolve15(process.cwd(), "dev.sqlite");
7606
+ const fallback = resolve16(process.cwd(), "dev.sqlite");
7368
7607
  this.defaultDbPath = fallback;
7369
7608
  return fallback;
7370
7609
  }
7371
- return isAbsolute(dbPath) ? dbPath : resolve15(process.cwd(), dbPath);
7610
+ return isAbsolute(dbPath) ? dbPath : resolve16(process.cwd(), dbPath);
7372
7611
  }
7373
7612
  autoDiscoverDatabase() {
7374
7613
  try {
7375
- const files = readdirSync4(process.cwd());
7614
+ const files = readdirSync5(process.cwd());
7376
7615
  const dbFile = files.find((f) => f.endsWith(".sqlite") || f.endsWith(".sqlite3") || f.endsWith(".db"));
7377
- return dbFile ? resolve15(process.cwd(), dbFile) : null;
7616
+ return dbFile ? resolve16(process.cwd(), dbFile) : null;
7378
7617
  } catch {
7379
7618
  return null;
7380
7619
  }
@@ -7833,7 +8072,7 @@ if (false) {}
7833
8072
 
7834
8073
  // src/mcp/servers/sqlite/index.ts
7835
8074
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/mcp/servers/sqlite";
7836
- var SQLITE_MCP_SERVER_PATH = resolve16(__dirname, "server.ts");
8075
+ var SQLITE_MCP_SERVER_PATH = resolve17(__dirname, "server.ts");
7837
8076
  // src/agents/identity.ts
7838
8077
  import { generateKeyPairSync, sign, verify } from "crypto";
7839
8078
  function createAgentIdentity(parentId, harnessId = "groupy-harness-v1") {
@@ -7876,8 +8115,8 @@ function verifyTaskAction(assertion, payload) {
7876
8115
  }
7877
8116
  }
7878
8117
  // src/agents/roles.ts
7879
- import { existsSync as existsSync14, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
7880
- 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";
7881
8120
 
7882
8121
  class AgentRoleRegistry {
7883
8122
  roles = new Map;
@@ -7961,14 +8200,14 @@ class AgentRoleRegistry {
7961
8200
  return cycle === 0 ? base : `${base}_${cycle + 1}`;
7962
8201
  }
7963
8202
  loadRolesFromDir(dirPath) {
7964
- const fullPath = resolve17(dirPath);
7965
- if (!existsSync14(fullPath))
8203
+ const fullPath = resolve18(dirPath);
8204
+ if (!existsSync15(fullPath))
7966
8205
  return;
7967
- const entries = readdirSync5(fullPath);
8206
+ const entries = readdirSync6(fullPath);
7968
8207
  for (const entry of entries) {
7969
8208
  if (entry.endsWith(".json")) {
7970
8209
  try {
7971
- const content = readFileSync9(join7(fullPath, entry), "utf8");
8210
+ const content = readFileSync9(join8(fullPath, entry), "utf8");
7972
8211
  const parsed = JSON.parse(content);
7973
8212
  if (parsed.name && parsed.systemPrompt) {
7974
8213
  this.registerRole(parsed);
@@ -7997,21 +8236,19 @@ class AgentRoleRegistry {
7997
8236
  }
7998
8237
  // src/agents/graph-store.ts
7999
8238
  import { Database as Database3 } from "bun:sqlite";
8000
- import { resolve as resolve18 } from "path";
8001
- import { existsSync as existsSync15, mkdirSync as mkdirSync8 } from "fs";
8002
- import { homedir as homedir4 } from "os";
8003
-
8239
+ import { resolve as resolve19 } from "path";
8240
+ import { existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
8004
8241
  class AgentGraphStore {
8005
8242
  db;
8006
8243
  constructor(dbPathOrDb) {
8007
8244
  if (dbPathOrDb instanceof Database3) {
8008
8245
  this.db = dbPathOrDb;
8009
8246
  } else {
8010
- const dbPath = dbPathOrDb || resolve18(homedir4(), ".groupy", "agent_graph.db");
8247
+ const dbPath = dbPathOrDb || getAgentGraphDbPath();
8011
8248
  if (dbPath !== ":memory:") {
8012
- const dir = resolve18(dbPath, "..");
8013
- if (!existsSync15(dir)) {
8014
- mkdirSync8(dir, { recursive: true });
8249
+ const dir = resolve19(dbPath, "..");
8250
+ if (!existsSync16(dir)) {
8251
+ mkdirSync9(dir, { recursive: true });
8015
8252
  }
8016
8253
  }
8017
8254
  this.db = new Database3(dbPath);
@@ -8133,8 +8370,8 @@ Your nickname is ${nickname}. Your assigned task is: '${params.taskName}'. Focus
8133
8370
  });
8134
8371
  let resolvePromise;
8135
8372
  let rejectPromise;
8136
- const taskPromise = new Promise((resolve19, reject) => {
8137
- resolvePromise = resolve19;
8373
+ const taskPromise = new Promise((resolve20, reject) => {
8374
+ resolvePromise = resolve20;
8138
8375
  rejectPromise = reject;
8139
8376
  });
8140
8377
  const handle = {
@@ -8433,17 +8670,16 @@ function registerMultiAgentTools(router2, spawner) {
8433
8670
  }
8434
8671
  // src/storage/sqlite-store.ts
8435
8672
  import { Database as Database4 } from "bun:sqlite";
8436
- import { existsSync as existsSync16, mkdirSync as mkdirSync9 } from "fs";
8437
- import { dirname as dirname7, resolve as resolve19 } from "path";
8438
- import { homedir as homedir5 } from "os";
8673
+ import { existsSync as existsSync17, mkdirSync as mkdirSync10 } from "fs";
8674
+ import { dirname as dirname8 } from "path";
8439
8675
  class SqliteThreadStore {
8440
8676
  db;
8441
8677
  constructor(dbPath) {
8442
8678
  const effectivePath = dbPath || this.getDefaultDbPath();
8443
8679
  if (effectivePath !== ":memory:") {
8444
- const dir = dirname7(effectivePath);
8445
- if (!existsSync16(dir)) {
8446
- mkdirSync9(dir, { recursive: true });
8680
+ const dir = dirname8(effectivePath);
8681
+ if (!existsSync17(dir)) {
8682
+ mkdirSync10(dir, { recursive: true });
8447
8683
  }
8448
8684
  }
8449
8685
  this.db = new Database4(effectivePath);
@@ -8452,7 +8688,7 @@ class SqliteThreadStore {
8452
8688
  this.initSchema();
8453
8689
  }
8454
8690
  getDefaultDbPath() {
8455
- return resolve19(homedir5(), ".groupy", "groupy_threads.db");
8691
+ return getThreadsDbPath();
8456
8692
  }
8457
8693
  initSchema() {
8458
8694
  this.db.exec(`
@@ -8675,9 +8911,9 @@ class SessionPersistenceManager {
8675
8911
  }
8676
8912
  }
8677
8913
  // src/skills/loader.ts
8678
- import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync10 } from "fs";
8679
- import { resolve as resolve20, join as join8 } from "path";
8680
- 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";
8681
8917
  var __dirname = "/home/runner/work/agent-cli/agent-cli/src/skills";
8682
8918
 
8683
8919
  class SkillsLoader {
@@ -8747,16 +8983,16 @@ class SkillsLoader {
8747
8983
  resolve20(cwd, "skills")
8748
8984
  ];
8749
8985
  for (const cand of candidates) {
8750
- if (existsSync17(cand) && !roots.includes(cand)) {
8986
+ if (existsSync18(cand) && !roots.includes(cand)) {
8751
8987
  roots.push(cand);
8752
8988
  }
8753
8989
  }
8754
8990
  }
8755
8991
  if (this.includeGlobal) {
8756
- roots.push(resolve20(homedir6(), ".groupy", "skills"), resolve20(homedir6(), ".gemini", "config", "skills"));
8992
+ roots.push(getGlobalSkillsDir(), resolve20(homedir3(), ".gemini", "config", "skills"));
8757
8993
  }
8758
8994
  roots.push(...this.customRoots.map((r) => resolve20(r)));
8759
- return roots.filter((r) => existsSync17(r));
8995
+ return roots.filter((r) => existsSync18(r));
8760
8996
  }
8761
8997
  discoverSkills(cwd, options) {
8762
8998
  return this.listSkills(cwd, options);
@@ -8772,12 +9008,12 @@ class SkillsLoader {
8772
9008
  const discovered = new Map;
8773
9009
  for (const root of roots) {
8774
9010
  try {
8775
- const entries = readdirSync6(root, { withFileTypes: true });
9011
+ const entries = readdirSync7(root, { withFileTypes: true });
8776
9012
  for (const entry of entries) {
8777
9013
  if (entry.isDirectory()) {
8778
- const skillDir = join8(root, entry.name);
8779
- const skillFilePath = join8(skillDir, "SKILL.md");
8780
- if (existsSync17(skillFilePath)) {
9014
+ const skillDir = join9(root, entry.name);
9015
+ const skillFilePath = join9(skillDir, "SKILL.md");
9016
+ if (existsSync18(skillFilePath)) {
8781
9017
  const meta = this.parseSkillFrontmatter(skillFilePath, entry.name, root, cwd);
8782
9018
  if (meta && !discovered.has(meta.name)) {
8783
9019
  meta.enabled = !this.isSkillDisabled(meta.name);
@@ -8899,143 +9135,282 @@ When tackling complex specialized tasks that match any of these skills, autonomo
8899
9135
  }
8900
9136
  }
8901
9137
  // src/memories/store.ts
8902
- import { existsSync as existsSync18, readFileSync as readFileSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync10 } from "fs";
8903
- import { resolve as resolve21, dirname as dirname8 } from "path";
8904
- import { homedir as homedir7 } from "os";
8905
-
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";
8906
9141
  class MemoryStore {
8907
9142
  globalPath;
8908
9143
  customWorkspacePath;
8909
9144
  constructor(options = {}) {
8910
- this.globalPath = options.globalPath || resolve21(homedir7(), ".groupy", "memories.md");
9145
+ this.globalPath = options.globalPath || getGlobalMemoriesPath();
8911
9146
  this.customWorkspacePath = options.workspacePath;
8912
9147
  }
8913
- getWorkspacePath(cwd) {
8914
- 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
+ }
8915
9160
  }
8916
- addMemory(params) {
8917
- const scope = params.scope || "global";
8918
- const targetFile = scope === "global" ? this.globalPath : this.getWorkspacePath(params.cwd || process.cwd());
8919
- const dir = dirname8(targetFile);
8920
- if (!existsSync18(dir)) {
8921
- mkdirSync10(dir, { recursive: true });
8922
- }
8923
- const existingEntries = this.readMemoryFile(targetFile, scope);
8924
- const normalized = params.content.trim();
8925
- const duplicate = existingEntries.find((e) => e.category === params.category && e.content.toLowerCase() === normalized.toLowerCase());
8926
- if (duplicate) {
8927
- return duplicate;
8928
- }
8929
- const newEntry = {
8930
- id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
8931
- category: params.category,
8932
- content: normalized,
8933
- scope,
8934
- createdAt: Date.now()
8935
- };
8936
- existingEntries.push(newEntry);
8937
- this.writeMemoryFile(targetFile, existingEntries);
8938
- 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}`;
8939
9166
  }
8940
- getAllMemories(cwd) {
8941
- const globalEntries = this.readMemoryFile(this.globalPath, "global");
8942
- const workspacePath = this.getWorkspacePath(cwd);
8943
- const workspaceEntries = existsSync18(workspacePath) ? this.readMemoryFile(workspacePath, "workspace") : [];
8944
- 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
+ };
8945
9238
  }
8946
- readMemoryFile(filePath, scope) {
8947
- if (!existsSync18(filePath))
8948
- 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
+ }
8949
9256
  try {
8950
- const content = readFileSync11(filePath, "utf8");
8951
- 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(`
8952
9265
  `);
8953
- const entries = [];
8954
- let currentCategory = "preference";
8955
- for (const line of lines) {
8956
- const trimmed = line.trim();
8957
- if (trimmed.startsWith("## Preferences") || trimmed.startsWith("## User Preferences")) {
8958
- currentCategory = "preference";
8959
- } else if (trimmed.startsWith("## Guidelines") || trimmed.startsWith("## Coding Guidelines")) {
8960
- currentCategory = "guideline";
8961
- } else if (trimmed.startsWith("## Architecture") || trimmed.startsWith("## Project Architecture")) {
8962
- currentCategory = "architecture";
8963
- } else if (trimmed.startsWith("## Notes") || trimmed.startsWith("## General Notes")) {
8964
- currentCategory = "note";
8965
- } else if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) {
8966
- const itemText = trimmed.slice(2).trim();
8967
- if (itemText) {
8968
- entries.push({
8969
- id: `mem_${entries.length + 1}`,
8970
- category: currentCategory,
8971
- content: itemText,
8972
- scope,
8973
- createdAt: Date.now()
8974
- });
8975
- }
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;
8976
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;
9295
+ }
9296
+ } else {
9297
+ bodyLines.push(line);
8977
9298
  }
8978
- return entries;
8979
- } catch {
8980
- return [];
8981
9299
  }
8982
- }
8983
- writeMemoryFile(filePath, entries) {
8984
- const categories = {
8985
- preference: [],
8986
- guideline: [],
8987
- architecture: [],
8988
- note: []
9300
+ return {
9301
+ type,
9302
+ name,
9303
+ description,
9304
+ modified,
9305
+ content: bodyLines.join(`
9306
+ `).trim(),
9307
+ filePath
8989
9308
  };
8990
- for (const entry of entries) {
8991
- 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 {}
8992
9327
  }
8993
- let markdown = `# Groupy Persistent Memories
8994
-
8995
- `;
8996
- if (categories.preference.length > 0) {
8997
- markdown += `## User Preferences
8998
- ${categories.preference.map((p) => `- ${p}`).join(`
8999
- `)}
9000
-
9001
- `;
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})`);
9002
9336
  }
9003
- if (categories.guideline.length > 0) {
9004
- markdown += `## Coding Guidelines
9005
- ${categories.guideline.map((g) => `- ${g}`).join(`
9006
- `)}
9007
-
9008
- `;
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 "";
9009
9356
  }
9010
- if (categories.architecture.length > 0) {
9011
- markdown += `## Project Architecture
9012
- ${categories.architecture.map((a) => `- ${a}`).join(`
9013
- `)}
9014
-
9015
- `;
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 {}
9016
9369
  }
9017
- if (categories.note.length > 0) {
9018
- markdown += `## General Notes
9019
- ${categories.note.map((n) => `- ${n}`).join(`
9020
- `)}
9021
-
9022
- `;
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;
9023
9384
  }
9024
- writeFileSync6(filePath, markdown.trim() + `
9025
- `, "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;
9026
9399
  }
9027
9400
  formatMemoriesPrompt(cwd) {
9028
- const memories = this.getAllMemories(cwd);
9029
- if (memories.length === 0)
9401
+ const indexContent = this.loadMemoryIndex(cwd);
9402
+ if (!indexContent)
9030
9403
  return "";
9031
- const lines = memories.map((m) => `- [${m.category}] (${m.scope}): ${m.content}`);
9032
- return `
9033
- ## Persistent User Preferences & Memory Bank
9034
- <user_memories>
9035
- ${lines.join(`
9036
- `)}
9037
- </user_memories>
9038
- 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
+ `);
9039
9414
  }
9040
9415
  }
9041
9416
  // src/worktree/git.ts
@@ -9166,8 +9541,8 @@ async function removeWorktreeGit(repoRoot, worktreePath, deleteBranch = false) {
9166
9541
  return { success: true };
9167
9542
  }
9168
9543
  // src/worktree/manager.ts
9169
- import { resolve as resolve23, join as join9 } from "path";
9170
- 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";
9171
9546
  var DEFAULT_WORKTREE_KEEP_COUNT = 15;
9172
9547
 
9173
9548
  class WorktreeManager {
@@ -9191,15 +9566,15 @@ class WorktreeManager {
9191
9566
  const branchName = options.branch || `groupy/${taskId}`;
9192
9567
  const targetDir = options.worktreePath || (this.baseStorageDir ? resolve23(this.baseStorageDir, branchName.replace(/\//g, "_")) : resolve23(repoRoot, ".groupy", "worktrees", branchName.replace(/\//g, "_")));
9193
9568
  const worktreeParent = resolve23(targetDir, "..");
9194
- if (!existsSync19(worktreeParent)) {
9195
- mkdirSync11(worktreeParent, { recursive: true });
9569
+ if (!existsSync20(worktreeParent)) {
9570
+ mkdirSync12(worktreeParent, { recursive: true });
9196
9571
  }
9197
9572
  const baseBranch = options.baseBranch || await getCurrentBranch(repoRoot);
9198
9573
  const result = await createWorktreeGit(repoRoot, targetDir, branchName, baseBranch);
9199
9574
  if (!result.success) {
9200
9575
  throw new Error(`Failed to create git worktree: ${result.error}`);
9201
9576
  }
9202
- const metaPath = join9(targetDir, "groupy-thread.json");
9577
+ const metaPath = join11(targetDir, "groupy-thread.json");
9203
9578
  try {
9204
9579
  writeFileSync7(metaPath, JSON.stringify({
9205
9580
  version: 1,
@@ -9225,8 +9600,8 @@ class WorktreeManager {
9225
9600
  return [];
9226
9601
  const worktrees = await listWorktreesGit(repoRoot);
9227
9602
  return worktrees.map((wt) => {
9228
- const metaPath = join9(wt.path, "groupy-thread.json");
9229
- if (existsSync19(metaPath)) {
9603
+ const metaPath = join11(wt.path, "groupy-thread.json");
9604
+ if (existsSync20(metaPath)) {
9230
9605
  try {
9231
9606
  const raw = JSON.parse(readFileSync12(metaPath, "utf8"));
9232
9607
  return { ...wt, threadId: raw.ownerThreadId || raw.threadId };
@@ -9304,7 +9679,7 @@ class WorktreeManager {
9304
9679
  }
9305
9680
  }
9306
9681
  // src/auth/oauth.ts
9307
- import { randomBytes, createHash } from "crypto";
9682
+ import { randomBytes, createHash as createHash2 } from "crypto";
9308
9683
  import { exec } from "child_process";
9309
9684
  class AuthClient {
9310
9685
  store;
@@ -9457,7 +9832,7 @@ class AuthClient {
9457
9832
  return randomBytes(32).toString("base64url").replace(/[^a-zA-Z0-9]/g, "").slice(0, 64);
9458
9833
  }
9459
9834
  generateCodeChallenge(verifier) {
9460
- return createHash("sha256").update(verifier).digest("base64url");
9835
+ return createHash2("sha256").update(verifier).digest("base64url");
9461
9836
  }
9462
9837
  }
9463
9838
  // src/ui/components/claude/claude-header.tsx
@@ -11007,11 +11382,15 @@ export {
11007
11382
  captureWorldState,
11008
11383
  compactHistory,
11009
11384
  createAgentIdentity,
11385
+ createAutoMemoryTools,
11010
11386
  createCodeModeTools,
11011
11387
  createDefaultTools,
11012
11388
  createFileSearchTools,
11389
+ createListMemoriesTool,
11013
11390
  createMultiAgentTools,
11391
+ createReadMemoryTool,
11014
11392
  createRememberTool,
11393
+ createSaveMemoryTool,
11015
11394
  createShellTool,
11016
11395
  createSkillTool,
11017
11396
  createWorktreeGit,