@hanhnd/agent-kit 1.0.28 → 1.0.29

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.
@@ -6,7 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
- var __commonJS = (cb, mod) => function __require() {
9
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
10
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
11
+ }) : x)(function(x) {
12
+ if (typeof require !== "undefined") return require.apply(this, arguments);
13
+ throw Error('Dynamic require of "' + x + '" is not supported');
14
+ });
15
+ var __commonJS = (cb, mod) => function __require2() {
10
16
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
17
  };
12
18
  var __export = (target, all) => {
@@ -3223,8 +3229,8 @@ var require_utils = __commonJS({
3223
3229
  }
3224
3230
  return ind;
3225
3231
  }
3226
- function removeDotSegments(path6) {
3227
- let input = path6;
3232
+ function removeDotSegments(path11) {
3233
+ let input = path11;
3228
3234
  const output = [];
3229
3235
  let nextSlash = -1;
3230
3236
  let len = 0;
@@ -3423,8 +3429,8 @@ var require_schemes = __commonJS({
3423
3429
  wsComponent.secure = void 0;
3424
3430
  }
3425
3431
  if (wsComponent.resourceName) {
3426
- const [path6, query] = wsComponent.resourceName.split("?");
3427
- wsComponent.path = path6 && path6 !== "/" ? path6 : void 0;
3432
+ const [path11, query] = wsComponent.resourceName.split("?");
3433
+ wsComponent.path = path11 && path11 !== "/" ? path11 : void 0;
3428
3434
  wsComponent.query = query;
3429
3435
  wsComponent.resourceName = void 0;
3430
3436
  }
@@ -3589,49 +3595,49 @@ var require_fast_uri = __commonJS({
3589
3595
  schemelessOptions.skipEscape = true;
3590
3596
  return serialize(resolved, schemelessOptions);
3591
3597
  }
3592
- function resolveComponent(base, relative2, options, skipNormalization) {
3598
+ function resolveComponent(base, relative3, options, skipNormalization) {
3593
3599
  const target = {};
3594
3600
  if (!skipNormalization) {
3595
3601
  base = parse3(serialize(base, options), options);
3596
- relative2 = parse3(serialize(relative2, options), options);
3602
+ relative3 = parse3(serialize(relative3, options), options);
3597
3603
  }
3598
3604
  options = options || {};
3599
- if (!options.tolerant && relative2.scheme) {
3600
- target.scheme = relative2.scheme;
3601
- target.userinfo = relative2.userinfo;
3602
- target.host = relative2.host;
3603
- target.port = relative2.port;
3604
- target.path = removeDotSegments(relative2.path || "");
3605
- target.query = relative2.query;
3605
+ if (!options.tolerant && relative3.scheme) {
3606
+ target.scheme = relative3.scheme;
3607
+ target.userinfo = relative3.userinfo;
3608
+ target.host = relative3.host;
3609
+ target.port = relative3.port;
3610
+ target.path = removeDotSegments(relative3.path || "");
3611
+ target.query = relative3.query;
3606
3612
  } else {
3607
- if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
3608
- target.userinfo = relative2.userinfo;
3609
- target.host = relative2.host;
3610
- target.port = relative2.port;
3611
- target.path = removeDotSegments(relative2.path || "");
3612
- target.query = relative2.query;
3613
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
3614
+ target.userinfo = relative3.userinfo;
3615
+ target.host = relative3.host;
3616
+ target.port = relative3.port;
3617
+ target.path = removeDotSegments(relative3.path || "");
3618
+ target.query = relative3.query;
3613
3619
  } else {
3614
- if (!relative2.path) {
3620
+ if (!relative3.path) {
3615
3621
  target.path = base.path;
3616
- if (relative2.query !== void 0) {
3617
- target.query = relative2.query;
3622
+ if (relative3.query !== void 0) {
3623
+ target.query = relative3.query;
3618
3624
  } else {
3619
3625
  target.query = base.query;
3620
3626
  }
3621
3627
  } else {
3622
- if (relative2.path[0] === "/") {
3623
- target.path = removeDotSegments(relative2.path);
3628
+ if (relative3.path[0] === "/") {
3629
+ target.path = removeDotSegments(relative3.path);
3624
3630
  } else {
3625
3631
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3626
- target.path = "/" + relative2.path;
3632
+ target.path = "/" + relative3.path;
3627
3633
  } else if (!base.path) {
3628
- target.path = relative2.path;
3634
+ target.path = relative3.path;
3629
3635
  } else {
3630
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
3636
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
3631
3637
  }
3632
3638
  target.path = removeDotSegments(target.path);
3633
3639
  }
3634
- target.query = relative2.query;
3640
+ target.query = relative3.query;
3635
3641
  }
3636
3642
  target.userinfo = base.userinfo;
3637
3643
  target.host = base.host;
@@ -3639,7 +3645,7 @@ var require_fast_uri = __commonJS({
3639
3645
  }
3640
3646
  target.scheme = base.scheme;
3641
3647
  }
3642
- target.fragment = relative2.fragment;
3648
+ target.fragment = relative3.fragment;
3643
3649
  return target;
3644
3650
  }
3645
3651
  function equal(uriA, uriB, options) {
@@ -6786,12 +6792,12 @@ var require_dist = __commonJS({
6786
6792
  throw new Error(`Unknown format "${name}"`);
6787
6793
  return f;
6788
6794
  };
6789
- function addFormats(ajv, list, fs4, exportName) {
6795
+ function addFormats(ajv, list, fs7, exportName) {
6790
6796
  var _a;
6791
6797
  var _b;
6792
6798
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6793
6799
  for (const f of list)
6794
- ajv.addFormat(f, fs4[f]);
6800
+ ajv.addFormat(f, fs7[f]);
6795
6801
  }
6796
6802
  module.exports = exports = formatsPlugin;
6797
6803
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -7277,8 +7283,8 @@ function getErrorMap() {
7277
7283
 
7278
7284
  // ../node_modules/zod/v3/helpers/parseUtil.js
7279
7285
  var makeIssue = (params) => {
7280
- const { data, path: path6, errorMaps, issueData } = params;
7281
- const fullPath = [...path6, ...issueData.path || []];
7286
+ const { data, path: path11, errorMaps, issueData } = params;
7287
+ const fullPath = [...path11, ...issueData.path || []];
7282
7288
  const fullIssue = {
7283
7289
  ...issueData,
7284
7290
  path: fullPath
@@ -7394,11 +7400,11 @@ var errorUtil;
7394
7400
 
7395
7401
  // ../node_modules/zod/v3/types.js
7396
7402
  var ParseInputLazyPath = class {
7397
- constructor(parent, value, path6, key) {
7403
+ constructor(parent, value, path11, key) {
7398
7404
  this._cachedPath = [];
7399
7405
  this.parent = parent;
7400
7406
  this.data = value;
7401
- this._path = path6;
7407
+ this._path = path11;
7402
7408
  this._key = key;
7403
7409
  }
7404
7410
  get path() {
@@ -11036,10 +11042,10 @@ function assignProp(target, prop, value) {
11036
11042
  configurable: true
11037
11043
  });
11038
11044
  }
11039
- function getElementAtPath(obj, path6) {
11040
- if (!path6)
11045
+ function getElementAtPath(obj, path11) {
11046
+ if (!path11)
11041
11047
  return obj;
11042
- return path6.reduce((acc, key) => acc?.[key], obj);
11048
+ return path11.reduce((acc, key) => acc?.[key], obj);
11043
11049
  }
11044
11050
  function promiseAllObject(promisesObj) {
11045
11051
  const keys = Object.keys(promisesObj);
@@ -11359,11 +11365,11 @@ function aborted(x, startIndex = 0) {
11359
11365
  }
11360
11366
  return false;
11361
11367
  }
11362
- function prefixIssues(path6, issues) {
11368
+ function prefixIssues(path11, issues) {
11363
11369
  return issues.map((iss) => {
11364
11370
  var _a;
11365
11371
  (_a = iss).path ?? (_a.path = []);
11366
- iss.path.unshift(path6);
11372
+ iss.path.unshift(path11);
11367
11373
  return iss;
11368
11374
  });
11369
11375
  }
@@ -20990,12 +20996,12 @@ var StdioServerTransport = class {
20990
20996
  };
20991
20997
 
20992
20998
  // src/tools/core.ts
20993
- import * as fs from "fs";
20994
- import * as path2 from "path";
20999
+ import * as fs2 from "fs";
21000
+ import * as path3 from "path";
20995
21001
 
20996
21002
  // src/utils/utils.ts
20997
21003
  function getWorkspaceRoot() {
20998
- return process.env.WORKSPACE_DIR || process.env.CODEX_WORKSPACE || process.env.CODEX_PROJECT_DIR || process.env.CLAUDE_WORKSPACE || process.env.GEMINI_WORKSPACE || process.env.INIT_CWD || process.env.PWD || process.cwd();
21004
+ return process.env.WORKSPACE_DIR || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.CLAUDE_WORKSPACE || process.env.CODEX_WORKSPACE || process.env.GEMINI_WORKSPACE || process.env.INIT_CWD || process.env.PWD || process.cwd();
20999
21005
  }
21000
21006
  function mcpText(text) {
21001
21007
  return { content: [{ type: "text", text }] };
@@ -21005,6 +21011,41 @@ function mcpJson(obj) {
21005
21011
  }
21006
21012
 
21007
21013
  // src/tools/config.ts
21014
+ import * as fs from "fs";
21015
+ import * as path from "path";
21016
+
21017
+ // src/memory/types.ts
21018
+ var DEFAULT_MEMORY_CONFIG = {
21019
+ enabled: false,
21020
+ wikiDir: "",
21021
+ topK: 5,
21022
+ chunkSize: 1500,
21023
+ overlapLines: 2,
21024
+ embeddingModel: "Xenova/bge-small-en-v1.5",
21025
+ vectorDimension: 384
21026
+ };
21027
+
21028
+ // src/tools/config.ts
21029
+ function resolveMemoryConfig(settings2, workspaceRoot2) {
21030
+ const override = settings2.memory ?? {};
21031
+ return {
21032
+ ...DEFAULT_MEMORY_CONFIG,
21033
+ ...override,
21034
+ enabled: override.enabled ?? DEFAULT_MEMORY_CONFIG.enabled,
21035
+ wikiDir: typeof override.wikiDir === "string" && override.wikiDir.length > 0 ? override.wikiDir : path.join(workspaceRoot2, ".agent-kit", "wiki")
21036
+ };
21037
+ }
21038
+ function loadProjectSettings(projectDir) {
21039
+ const settingsPath = path.join(projectDir, ".agent-kit", "settings.json");
21040
+ if (fs.existsSync(settingsPath)) {
21041
+ try {
21042
+ return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
21043
+ } catch (error2) {
21044
+ console.error(`[agent-kit] Warning: Failed to parse ${settingsPath}:`, error2);
21045
+ }
21046
+ }
21047
+ return {};
21048
+ }
21008
21049
  var FORBIDDEN_FILES = [
21009
21050
  ".env",
21010
21051
  ".bashrc",
@@ -21046,7 +21087,7 @@ var FORBIDDEN_DIRS = [
21046
21087
  ];
21047
21088
 
21048
21089
  // src/tools/handoffs.ts
21049
- import * as path from "path";
21090
+ import * as path2 from "path";
21050
21091
  var HANDOFF_TYPES = [
21051
21092
  "brainstorm",
21052
21093
  "clarification",
@@ -21089,11 +21130,11 @@ function resolveHandoffPath(input) {
21089
21130
  content: input.content,
21090
21131
  type: canonicalType
21091
21132
  });
21092
- const relativePath = path.join(".agent-kit", "handoffs", featureSlug, `${canonicalType}.md`);
21133
+ const relativePath = path2.join(".agent-kit", "handoffs", featureSlug, `${canonicalType}.md`);
21093
21134
  return {
21094
21135
  featureSlug,
21095
21136
  canonicalType,
21096
- filePath: path.join(input.workspaceRoot, relativePath),
21137
+ filePath: path2.join(input.workspaceRoot, relativePath),
21097
21138
  relativePath
21098
21139
  };
21099
21140
  }
@@ -21118,11 +21159,11 @@ function registerCoreTools(server2) {
21118
21159
  slug,
21119
21160
  content
21120
21161
  });
21121
- const handoffDir = path2.dirname(location.filePath);
21122
- if (!fs.existsSync(handoffDir)) {
21123
- fs.mkdirSync(handoffDir, { recursive: true });
21162
+ const handoffDir = path3.dirname(location.filePath);
21163
+ if (!fs2.existsSync(handoffDir)) {
21164
+ fs2.mkdirSync(handoffDir, { recursive: true });
21124
21165
  }
21125
- fs.writeFileSync(location.filePath, content, "utf8");
21166
+ fs2.writeFileSync(location.filePath, content, "utf8");
21126
21167
  return mcpText(`\u2705 Saved to: ${location.filePath}`);
21127
21168
  } catch (error2) {
21128
21169
  return mcpText(`Error saving handoff: ${error2}`);
@@ -21134,28 +21175,28 @@ function registerCoreTools(server2) {
21134
21175
  // src/tools/agent.ts
21135
21176
  import { spawn } from "child_process";
21136
21177
  import { randomUUID } from "crypto";
21137
- import * as fs2 from "fs";
21138
- import * as path4 from "path";
21178
+ import * as fs3 from "fs";
21179
+ import * as path5 from "path";
21139
21180
 
21140
21181
  // src/tools/security.ts
21141
21182
  import { execFileSync } from "child_process";
21142
- import * as path3 from "path";
21183
+ import * as path4 from "path";
21143
21184
  function sanitize(input) {
21144
21185
  return String(input).replace(/[;&|`$<>\\]/g, "").trim().slice(0, 500);
21145
21186
  }
21146
21187
  function validatePath(filePath, baseDir = process.cwd()) {
21147
- const resolved = path3.resolve(baseDir, filePath);
21148
- const root = path3.resolve(baseDir);
21149
- if (resolved !== root && !resolved.startsWith(root + path3.sep)) {
21188
+ const resolved = path4.resolve(baseDir, filePath);
21189
+ const root = path4.resolve(baseDir);
21190
+ if (resolved !== root && !resolved.startsWith(root + path4.sep)) {
21150
21191
  throw new Error(`Path traversal detected: ${filePath}`);
21151
21192
  }
21152
- const segments = resolved.split(path3.sep);
21193
+ const segments = resolved.split(path4.sep);
21153
21194
  for (const segment of segments) {
21154
21195
  if (FORBIDDEN_DIRS.some((d) => segment.toLowerCase() === d.toLowerCase())) {
21155
21196
  throw new Error(`Access to sensitive directory is FORBIDDEN: ${segment}`);
21156
21197
  }
21157
21198
  }
21158
- const fileName = path3.basename(resolved);
21199
+ const fileName = path4.basename(resolved);
21159
21200
  const isExactForbidden = FORBIDDEN_FILES.some((f) => fileName.toLowerCase() === f.toLowerCase());
21160
21201
  const isPatternForbidden = FORBIDDEN_PATTERNS.some((re) => re.test(fileName));
21161
21202
  if (isExactForbidden || isPatternForbidden) {
@@ -21206,7 +21247,7 @@ var INSTALL_HINTS = {
21206
21247
  claude: "Install Claude CLI: npm install -g @anthropic-ai/claude-code",
21207
21248
  codex: "Install Codex CLI: npm install -g @openai/codex"
21208
21249
  };
21209
- function agentArgs(agent, prompt, workspaceRoot) {
21250
+ function agentArgs(agent, prompt, workspaceRoot2) {
21210
21251
  switch (agent) {
21211
21252
  case "gemini":
21212
21253
  return ["-y", "-p", prompt];
@@ -21219,7 +21260,7 @@ function agentArgs(agent, prompt, workspaceRoot) {
21219
21260
  "--sandbox",
21220
21261
  "workspace-write",
21221
21262
  "--cd",
21222
- workspaceRoot,
21263
+ workspaceRoot2,
21223
21264
  "exec",
21224
21265
  prompt
21225
21266
  ];
@@ -21228,21 +21269,21 @@ function agentArgs(agent, prompt, workspaceRoot) {
21228
21269
  function taskSlug(task) {
21229
21270
  return task.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-").slice(0, 48);
21230
21271
  }
21231
- function initAgentLog(workspaceRoot, slug, agent) {
21232
- const logsDir = path4.join(workspaceRoot, ".agent-kit", "logs", "agents");
21233
- fs2.mkdirSync(logsDir, { recursive: true });
21272
+ function initAgentLog(workspaceRoot2, slug, agent) {
21273
+ const logsDir = path5.join(workspaceRoot2, ".agent-kit", "logs", "agents");
21274
+ fs3.mkdirSync(logsDir, { recursive: true });
21234
21275
  const now = (/* @__PURE__ */ new Date()).toISOString();
21235
- const logFile = path4.join(logsDir, `delegate-${now}-${slug}.log`);
21276
+ const logFile = path5.join(logsDir, `delegate-${now}-${slug}.log`);
21236
21277
  const header = `Agent: ${agent}
21237
21278
  Date: ${now}
21238
21279
  ${"\u2500".repeat(60)}
21239
21280
 
21240
21281
  `;
21241
- fs2.writeFileSync(logFile, header, "utf-8");
21242
- const logStream = fs2.createWriteStream(logFile, { flags: "a" });
21282
+ fs3.writeFileSync(logFile, header, "utf-8");
21283
+ const logStream = fs3.createWriteStream(logFile, { flags: "a" });
21243
21284
  logStream.on("error", () => {
21244
21285
  });
21245
- return { logPath: path4.relative(workspaceRoot, logFile), logStream };
21286
+ return { logPath: path5.relative(workspaceRoot2, logFile), logStream };
21246
21287
  }
21247
21288
  function registerAgentTools(server2) {
21248
21289
  server2.tool(
@@ -21255,17 +21296,17 @@ function registerAgentTools(server2) {
21255
21296
  )
21256
21297
  },
21257
21298
  async ({ agent, task }, extra) => {
21258
- const workspaceRoot = getWorkspaceRoot();
21299
+ const workspaceRoot2 = getWorkspaceRoot();
21259
21300
  let job;
21260
21301
  let logPath;
21261
21302
  let killChild;
21262
21303
  try {
21263
21304
  let prompt = task;
21264
- const resolvedTask = path4.resolve(workspaceRoot, task);
21265
- if (fs2.existsSync(resolvedTask) && fs2.statSync(resolvedTask).isFile()) {
21305
+ const resolvedTask = path5.resolve(workspaceRoot2, task);
21306
+ if (fs3.existsSync(resolvedTask) && fs3.statSync(resolvedTask).isFile()) {
21266
21307
  try {
21267
- validatePath(task, workspaceRoot);
21268
- prompt = fs2.readFileSync(resolvedTask, "utf-8");
21308
+ validatePath(task, workspaceRoot2);
21309
+ prompt = fs3.readFileSync(resolvedTask, "utf-8");
21269
21310
  } catch (err) {
21270
21311
  return mcpText(`Error reading task file: ${err}`);
21271
21312
  }
@@ -21278,17 +21319,17 @@ function registerAgentTools(server2) {
21278
21319
  error: `${agent} CLI is not installed. ${INSTALL_HINTS[agent]}`
21279
21320
  });
21280
21321
  }
21281
- const args = agentArgs(agent, prompt, workspaceRoot);
21282
- const { logPath: lp, logStream } = initAgentLog(workspaceRoot, taskSlug(task), agent);
21322
+ const args = agentArgs(agent, prompt, workspaceRoot2);
21323
+ const { logPath: lp, logStream } = initAgentLog(workspaceRoot2, taskSlug(task), agent);
21283
21324
  logPath = lp;
21284
21325
  const jobId = randomUUID();
21285
21326
  const child = spawn(agent, args, {
21286
- cwd: workspaceRoot,
21327
+ cwd: workspaceRoot2,
21287
21328
  env: {
21288
21329
  ...process.env,
21289
- CODEX_PROJECT_DIR: workspaceRoot,
21290
- CODEX_WORKSPACE: workspaceRoot,
21291
- GEMINI_WORKSPACE: workspaceRoot
21330
+ CODEX_PROJECT_DIR: workspaceRoot2,
21331
+ CODEX_WORKSPACE: workspaceRoot2,
21332
+ GEMINI_WORKSPACE: workspaceRoot2
21292
21333
  },
21293
21334
  detached: process.platform !== "win32"
21294
21335
  });
@@ -21366,10 +21407,10 @@ function registerAgentTools(server2) {
21366
21407
  import { writeFileSync as writeFileSync3 } from "fs";
21367
21408
 
21368
21409
  // src/utils/credentials.ts
21369
- import * as fs3 from "fs";
21410
+ import * as fs4 from "fs";
21370
21411
  import * as os from "os";
21371
- import * as path5 from "path";
21372
- var CREDENTIALS_FILE = path5.join(os.homedir(), ".claude", "credentials");
21412
+ import * as path6 from "path";
21413
+ var CREDENTIALS_FILE = path6.join(os.homedir(), ".claude", "credentials");
21373
21414
  var cachedCredentials = null;
21374
21415
  function parseIni(content) {
21375
21416
  const result = {};
@@ -21394,12 +21435,12 @@ function parseIni(content) {
21394
21435
  }
21395
21436
  function loadCredentials() {
21396
21437
  if (cachedCredentials !== null) return cachedCredentials;
21397
- if (!fs3.existsSync(CREDENTIALS_FILE)) {
21438
+ if (!fs4.existsSync(CREDENTIALS_FILE)) {
21398
21439
  cachedCredentials = {};
21399
21440
  return cachedCredentials;
21400
21441
  }
21401
21442
  try {
21402
- const stat = fs3.statSync(CREDENTIALS_FILE);
21443
+ const stat = fs4.statSync(CREDENTIALS_FILE);
21403
21444
  const mode = stat.mode & 511;
21404
21445
  if (mode !== 384) {
21405
21446
  process.stderr.write(
@@ -21409,7 +21450,7 @@ function loadCredentials() {
21409
21450
  }
21410
21451
  } catch {
21411
21452
  }
21412
- const content = fs3.readFileSync(CREDENTIALS_FILE, "utf-8");
21453
+ const content = fs4.readFileSync(CREDENTIALS_FILE, "utf-8");
21413
21454
  const ini = parseIni(content);
21414
21455
  const profile = process.env.KIT_PROFILE ?? "default";
21415
21456
  cachedCredentials = ini[profile] ?? {};
@@ -21742,6 +21783,714 @@ ${ticket.fields.labels?.join(", ") || "None"}`;
21742
21783
  );
21743
21784
  }
21744
21785
 
21786
+ // src/tools/memory.ts
21787
+ import * as path10 from "path";
21788
+
21789
+ // src/memory/embedder.ts
21790
+ import * as os2 from "os";
21791
+ import * as path7 from "path";
21792
+ var DEFAULT_CACHE_DIR = path7.join(os2.homedir(), ".cache", "fastembed");
21793
+ var EmbedderError = class extends Error {
21794
+ constructor(message, cause) {
21795
+ super(message);
21796
+ this.name = "EmbedderError";
21797
+ if (cause instanceof Error) this.cause = cause;
21798
+ }
21799
+ };
21800
+ var Embedder = class {
21801
+ modelName;
21802
+ pipeline = null;
21803
+ _dimension;
21804
+ constructor(modelName) {
21805
+ this.modelName = modelName;
21806
+ }
21807
+ async embed(texts) {
21808
+ if (texts.length === 0) return [];
21809
+ if (!this.pipeline) {
21810
+ await this.loadModel();
21811
+ }
21812
+ try {
21813
+ const results = await this.pipeline(texts);
21814
+ return results.map((vec) => {
21815
+ const arr = new Float32Array(vec);
21816
+ if (!this._dimension) this._dimension = arr.length;
21817
+ return arr;
21818
+ });
21819
+ } catch (cause) {
21820
+ throw new EmbedderError(`Embedding failed for batch of ${texts.length} texts`, cause);
21821
+ }
21822
+ }
21823
+ get dimension() {
21824
+ return this._dimension;
21825
+ }
21826
+ isReady() {
21827
+ return this.pipeline !== null;
21828
+ }
21829
+ async loadModel() {
21830
+ try {
21831
+ const { EmbeddingModel, FlagEmbedding } = await import("fastembed");
21832
+ const modelMap = {
21833
+ "Xenova/bge-small-en-v1.5": EmbeddingModel.BGESmallENV15,
21834
+ "Xenova/bge-base-en-v1.5": EmbeddingModel.BGEBaseENV15,
21835
+ "Xenova/all-MiniLM-L6-v2": EmbeddingModel.AllMiniLML6V2
21836
+ };
21837
+ const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
21838
+ const embedder = await FlagEmbedding.init({ model, cacheDir: DEFAULT_CACHE_DIR, showDownloadProgress: false });
21839
+ this.pipeline = async (texts) => {
21840
+ const result = [];
21841
+ for await (const batch of embedder.embed(texts, texts.length)) {
21842
+ for (const vec of batch) {
21843
+ result.push(Array.from(vec));
21844
+ }
21845
+ }
21846
+ return result;
21847
+ };
21848
+ } catch (cause) {
21849
+ throw new EmbedderError(`Failed to load embedding model: ${this.modelName}`, cause);
21850
+ }
21851
+ }
21852
+ };
21853
+
21854
+ // src/memory/indexer.ts
21855
+ import * as fs5 from "fs";
21856
+ import * as path8 from "path";
21857
+
21858
+ // src/memory/chunker.ts
21859
+ import { createHash } from "crypto";
21860
+ function computeChunkId(content) {
21861
+ return createHash("sha256").update(content).digest("hex").slice(0, 16);
21862
+ }
21863
+ function cleanContentForEmbedding(text) {
21864
+ return text.replace(/<!--[\s\S]*?-->/g, "");
21865
+ }
21866
+ var HEADING_RE = /^(#{1,6}) (.+)$/;
21867
+ var SENTENCE_END_RE = /([.!?。!?])\s+(?![0-9])/g;
21868
+ var URL_RE = /https?:\/\/\S+/g;
21869
+ function splitAtParagraph(text, maxLen) {
21870
+ const paragraphs = text.split(/\n\n+/);
21871
+ const parts = [];
21872
+ let current = "";
21873
+ for (const para of paragraphs) {
21874
+ const candidate = current ? `${current}
21875
+
21876
+ ${para}` : para;
21877
+ if (candidate.length <= maxLen) {
21878
+ current = candidate;
21879
+ } else {
21880
+ if (current) parts.push(current);
21881
+ current = para.length <= maxLen ? para : splitAtLine(para, maxLen).join("\n\n");
21882
+ }
21883
+ }
21884
+ if (current) parts.push(current);
21885
+ return parts.filter(Boolean);
21886
+ }
21887
+ function splitAtLine(text, maxLen) {
21888
+ const lines = text.split("\n");
21889
+ const parts = [];
21890
+ let current = "";
21891
+ for (const line of lines) {
21892
+ const candidate = current ? `${current}
21893
+ ${line}` : line;
21894
+ if (candidate.length <= maxLen) {
21895
+ current = candidate;
21896
+ } else {
21897
+ if (current) parts.push(current);
21898
+ current = line.length <= maxLen ? line : splitAtSentence(line, maxLen).join(" ");
21899
+ }
21900
+ }
21901
+ if (current) parts.push(current);
21902
+ return parts.filter(Boolean);
21903
+ }
21904
+ function splitAtSentence(text, maxLen) {
21905
+ const sanitized = text.replace(URL_RE, "[URL]");
21906
+ const sentences = [];
21907
+ let lastIndex = 0;
21908
+ for (const match of sanitized.matchAll(SENTENCE_END_RE)) {
21909
+ const end = (match.index ?? 0) + match[0].length;
21910
+ sentences.push(text.slice(lastIndex, end).trim());
21911
+ lastIndex = end;
21912
+ }
21913
+ if (lastIndex < text.length) {
21914
+ sentences.push(text.slice(lastIndex).trim());
21915
+ }
21916
+ if (sentences.length === 0) return [text.slice(0, maxLen)];
21917
+ const parts = [];
21918
+ let current = "";
21919
+ for (const sentence of sentences) {
21920
+ const candidate = current ? `${current} ${sentence}` : sentence;
21921
+ if (candidate.length <= maxLen) {
21922
+ current = candidate;
21923
+ } else {
21924
+ if (current) parts.push(current);
21925
+ current = sentence.slice(0, maxLen);
21926
+ }
21927
+ }
21928
+ if (current) parts.push(current);
21929
+ return parts.filter(Boolean);
21930
+ }
21931
+ function splitSection(text, maxLen) {
21932
+ if (text.length <= maxLen) return [text];
21933
+ const byPara = splitAtParagraph(text, maxLen);
21934
+ if (byPara.every((p) => p.length <= maxLen)) return byPara;
21935
+ return byPara.flatMap((p) => p.length <= maxLen ? [p] : splitAtLine(p, maxLen));
21936
+ }
21937
+ function chunkMarkdown(text, source, config2) {
21938
+ if (!text || !text.trim()) return [];
21939
+ const { chunkSize, overlapLines } = config2;
21940
+ const lines = text.split("\n");
21941
+ const sections = [];
21942
+ let current = { heading: "", headingLevel: 0, lines: [] };
21943
+ for (let i = 0; i < lines.length; i++) {
21944
+ const line = lines[i];
21945
+ const headingMatch = line.match(HEADING_RE);
21946
+ if (headingMatch) {
21947
+ if (current.lines.length > 0) sections.push(current);
21948
+ current = {
21949
+ heading: headingMatch[2].trim(),
21950
+ headingLevel: headingMatch[1].length,
21951
+ lines: [{ text: line, lineNum: i + 1 }]
21952
+ };
21953
+ } else {
21954
+ current.lines.push({ text: line, lineNum: i + 1 });
21955
+ }
21956
+ }
21957
+ if (current.lines.length > 0) sections.push(current);
21958
+ const chunks = [];
21959
+ let prevOverlapLines = [];
21960
+ for (const section of sections) {
21961
+ const sectionText = section.lines.map((l) => l.text).join("\n");
21962
+ if (!sectionText.trim()) continue;
21963
+ const fullText = prevOverlapLines.length > 0 ? `${prevOverlapLines.join("\n")}
21964
+ ${sectionText}` : sectionText;
21965
+ const parts = splitSection(fullText, chunkSize);
21966
+ for (let pi = 0; pi < parts.length; pi++) {
21967
+ const part = parts[pi];
21968
+ if (!part.trim()) continue;
21969
+ const partLines = part.split("\n");
21970
+ const firstLine = section.lines[0]?.lineNum ?? 1;
21971
+ const lastLine = section.lines[section.lines.length - 1]?.lineNum ?? firstLine;
21972
+ const content = cleanContentForEmbedding(part);
21973
+ chunks.push({
21974
+ id: computeChunkId(content),
21975
+ source,
21976
+ heading: section.heading,
21977
+ headingLevel: section.headingLevel,
21978
+ content,
21979
+ lineStart: firstLine,
21980
+ lineEnd: lastLine
21981
+ });
21982
+ if (pi === parts.length - 1) {
21983
+ prevOverlapLines = partLines.slice(-overlapLines);
21984
+ }
21985
+ }
21986
+ }
21987
+ return chunks;
21988
+ }
21989
+
21990
+ // src/memory/indexer.ts
21991
+ var LOCK_RETRY_MS = 50;
21992
+ var LOCK_TIMEOUT_MS = 500;
21993
+ async function acquireLock(lockPath) {
21994
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
21995
+ while (Date.now() < deadline) {
21996
+ try {
21997
+ fs5.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
21998
+ return true;
21999
+ } catch {
22000
+ await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
22001
+ }
22002
+ }
22003
+ return false;
22004
+ }
22005
+ function releaseLock(lockPath) {
22006
+ try {
22007
+ fs5.unlinkSync(lockPath);
22008
+ } catch {
22009
+ }
22010
+ }
22011
+ var MemoryIndexer = class {
22012
+ constructor(store, embedder, config2) {
22013
+ this.store = store;
22014
+ this.embedder = embedder;
22015
+ this.config = config2;
22016
+ }
22017
+ _ready = false;
22018
+ async indexFile(absolutePath) {
22019
+ return this.indexFileRelativeTo(absolutePath, this.config.wikiDir);
22020
+ }
22021
+ async indexFileRelativeTo(absolutePath, sourceRoot) {
22022
+ const source = path8.relative(sourceRoot, absolutePath);
22023
+ const existingHashes = this.store.hashesBySource(source);
22024
+ let text;
22025
+ try {
22026
+ text = fs5.readFileSync(absolutePath, "utf8");
22027
+ } catch (err) {
22028
+ console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
22029
+ return { indexed: 0, deleted: 0, skipped: existingHashes.size };
22030
+ }
22031
+ const allChunks = chunkMarkdown(text, source, this.config);
22032
+ const newHashes = new Set(allChunks.map((c) => c.id));
22033
+ const toIndex = allChunks.filter((c) => !existingHashes.has(c.id));
22034
+ const toDelete = [...existingHashes].filter((h) => !newHashes.has(h));
22035
+ let embeddings = [];
22036
+ if (toIndex.length > 0) {
22037
+ try {
22038
+ embeddings = await this.embedder.embed(toIndex.map((c) => c.content));
22039
+ } catch (err) {
22040
+ console.warn(`[memory-indexer] Embedding failed for ${absolutePath}:`, err);
22041
+ return { indexed: 0, deleted: 0, skipped: existingHashes.size };
22042
+ }
22043
+ }
22044
+ if (toIndex.length > 0) {
22045
+ this.store.upsert(toIndex, embeddings);
22046
+ }
22047
+ if (toDelete.length > 0) {
22048
+ this.store.deleteByIds(toDelete);
22049
+ }
22050
+ return {
22051
+ indexed: toIndex.length,
22052
+ deleted: toDelete.length,
22053
+ skipped: allChunks.length - toIndex.length
22054
+ };
22055
+ }
22056
+ async indexDirectory(rootDir, opts = {}) {
22057
+ const totals = { indexed: 0, deleted: 0, skipped: 0 };
22058
+ if (!fs5.existsSync(rootDir)) return totals;
22059
+ const relativeBase = opts.relativeBase ?? this.config.wikiDir;
22060
+ const excludeFiles = new Set(opts.excludeFiles ?? []);
22061
+ const files = [];
22062
+ const walk = (dirPath) => {
22063
+ let entries;
22064
+ try {
22065
+ entries = fs5.readdirSync(dirPath, { withFileTypes: true });
22066
+ } catch (err) {
22067
+ console.warn(`[memory-indexer] Cannot scan directory ${dirPath}:`, err);
22068
+ return;
22069
+ }
22070
+ for (const entry of entries) {
22071
+ const entryPath = path8.join(dirPath, entry.name);
22072
+ if (entry.isDirectory()) {
22073
+ walk(entryPath);
22074
+ continue;
22075
+ }
22076
+ if (entry.isFile() && /\.md$/i.test(entry.name) && !excludeFiles.has(entry.name)) {
22077
+ files.push(entryPath);
22078
+ }
22079
+ }
22080
+ };
22081
+ walk(rootDir);
22082
+ const currentSources = new Set(
22083
+ files.map((file) => path8.relative(relativeBase, file))
22084
+ );
22085
+ for (const stale of this.store.indexedSources()) {
22086
+ if (!currentSources.has(stale)) {
22087
+ this.store.deleteBySource(stale);
22088
+ totals.deleted += 1;
22089
+ }
22090
+ }
22091
+ for (const file of files) {
22092
+ const stats = await this.indexFileRelativeTo(file, relativeBase);
22093
+ totals.indexed += stats.indexed;
22094
+ totals.deleted += stats.deleted;
22095
+ totals.skipped += stats.skipped;
22096
+ }
22097
+ return totals;
22098
+ }
22099
+ async search(query, topK) {
22100
+ const fetchLimit = topK * 2;
22101
+ let denseResults = [];
22102
+ if (this.store.vecAvailable) {
22103
+ try {
22104
+ const queryEmbedding = await this.embedder.embed([query]);
22105
+ denseResults = this.store.searchDense(queryEmbedding[0], fetchLimit);
22106
+ } catch (err) {
22107
+ console.warn("[memory-indexer] Dense search embedding failed:", err);
22108
+ }
22109
+ }
22110
+ const bm25Results = this.store.searchBm25(query, fetchLimit);
22111
+ const k = 60;
22112
+ const scoreMap = /* @__PURE__ */ new Map();
22113
+ denseResults.forEach((r, rank) => {
22114
+ scoreMap.set(r.id, { dense: 1 / (k + rank + 1), bm25: 0 });
22115
+ });
22116
+ bm25Results.forEach((r, rank) => {
22117
+ const existing = scoreMap.get(r.id);
22118
+ if (existing) {
22119
+ existing.bm25 = 1 / (k + rank + 1);
22120
+ } else {
22121
+ scoreMap.set(r.id, { dense: 0, bm25: 1 / (k + rank + 1) });
22122
+ }
22123
+ });
22124
+ const hasDense = this.store.vecAvailable && denseResults.length > 0;
22125
+ const numRetrievers = hasDense ? 2 : 1;
22126
+ const maxScore = numRetrievers / 61;
22127
+ const ranked = [...scoreMap.entries()].map(([id, scores]) => ({
22128
+ id,
22129
+ totalScore: scores.dense + scores.bm25,
22130
+ hasDense: scores.dense > 0,
22131
+ hasBm25: scores.bm25 > 0
22132
+ })).sort((a, b) => b.totalScore - a.totalScore);
22133
+ const chunks = this.store.getChunksByIds(ranked.map((r) => r.id));
22134
+ const chunkMap = new Map(chunks.map((c) => [c.id, c]));
22135
+ const results = [];
22136
+ const seenSources = /* @__PURE__ */ new Set();
22137
+ for (const r of ranked) {
22138
+ if (results.length >= topK) break;
22139
+ const chunk = chunkMap.get(r.id);
22140
+ if (!chunk) continue;
22141
+ if (seenSources.has(chunk.source)) continue;
22142
+ seenSources.add(chunk.source);
22143
+ const normalizedScore = maxScore > 0 ? r.totalScore / maxScore : 0;
22144
+ const retriever = r.hasDense && r.hasBm25 ? "both" : r.hasDense ? "dense" : "bm25";
22145
+ let contentSource = "file";
22146
+ try {
22147
+ chunk.content = fs5.readFileSync(path8.join(this.config.wikiDir, chunk.source), "utf8");
22148
+ } catch {
22149
+ contentSource = "fallback";
22150
+ }
22151
+ results.push({ chunk, score: normalizedScore, retriever, contentSource });
22152
+ }
22153
+ return results;
22154
+ }
22155
+ async save(content) {
22156
+ const datePart = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22157
+ const rawDir = path8.join(this.config.wikiDir, "raw");
22158
+ const savePath = path8.join(rawDir, `conv_save_${datePart}.md`);
22159
+ const lockPath = `${savePath}.lock`;
22160
+ fs5.mkdirSync(rawDir, { recursive: true });
22161
+ const acquired = await acquireLock(lockPath);
22162
+ if (!acquired) {
22163
+ console.warn("[memory-indexer] Could not acquire write lock, writing anyway (fail-open)");
22164
+ }
22165
+ try {
22166
+ fs5.appendFileSync(savePath, `
22167
+ ${content}
22168
+ `, "utf8");
22169
+ } finally {
22170
+ if (acquired) releaseLock(lockPath);
22171
+ }
22172
+ return { indexed: 0, deleted: 0, skipped: 0 };
22173
+ }
22174
+ async startupIndex() {
22175
+ try {
22176
+ await this.indexDirectory(path8.join(this.config.wikiDir, "compiled"), {
22177
+ relativeBase: this.config.wikiDir,
22178
+ excludeFiles: ["index.md", "log.md"]
22179
+ });
22180
+ } catch (err) {
22181
+ console.warn("[memory-indexer] Startup indexing failed:", err);
22182
+ } finally {
22183
+ this._ready = true;
22184
+ }
22185
+ }
22186
+ get ready() {
22187
+ return this._ready;
22188
+ }
22189
+ };
22190
+
22191
+ // src/memory/store.ts
22192
+ import Database from "better-sqlite3";
22193
+ import * as fs6 from "fs";
22194
+ import * as path9 from "path";
22195
+ var StoreError = class extends Error {
22196
+ constructor(message, cause) {
22197
+ super(message);
22198
+ this.name = "StoreError";
22199
+ if (cause instanceof Error) this.cause = cause;
22200
+ }
22201
+ };
22202
+ var MemoryStore = class {
22203
+ constructor(dbPath, config2) {
22204
+ this.config = config2;
22205
+ fs6.mkdirSync(path9.dirname(dbPath), { recursive: true });
22206
+ this.db = this.openDatabase(dbPath);
22207
+ }
22208
+ db;
22209
+ _vecAvailable = false;
22210
+ openDatabase(dbPath) {
22211
+ try {
22212
+ const db = new Database(dbPath);
22213
+ db.pragma("journal_mode = WAL");
22214
+ db.pragma("busy_timeout = 5000");
22215
+ this.loadVecExtension(db);
22216
+ this.createSchema(db);
22217
+ return db;
22218
+ } catch (err) {
22219
+ console.error("[memory-store] DB open failed, recreating:", err);
22220
+ try {
22221
+ fs6.unlinkSync(dbPath);
22222
+ } catch {
22223
+ }
22224
+ const db = new Database(dbPath);
22225
+ db.pragma("journal_mode = WAL");
22226
+ db.pragma("busy_timeout = 5000");
22227
+ this.loadVecExtension(db);
22228
+ this.createSchema(db);
22229
+ return db;
22230
+ }
22231
+ }
22232
+ loadVecExtension(db) {
22233
+ try {
22234
+ const sqliteVec = __require("sqlite-vec");
22235
+ sqliteVec.load(db);
22236
+ this._vecAvailable = true;
22237
+ } catch (err) {
22238
+ console.warn("[memory-store] sqlite-vec unavailable, degrading to FTS5-only:", err);
22239
+ this._vecAvailable = false;
22240
+ }
22241
+ }
22242
+ createSchema(db) {
22243
+ db.exec(`
22244
+ CREATE TABLE IF NOT EXISTS memory_chunks (
22245
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
22246
+ id TEXT NOT NULL UNIQUE,
22247
+ source TEXT NOT NULL,
22248
+ heading TEXT NOT NULL DEFAULT '',
22249
+ heading_level INTEGER NOT NULL DEFAULT 0,
22250
+ content TEXT NOT NULL,
22251
+ line_start INTEGER NOT NULL,
22252
+ line_end INTEGER NOT NULL,
22253
+ indexed_at INTEGER NOT NULL
22254
+ );
22255
+
22256
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
22257
+ content,
22258
+ content='memory_chunks',
22259
+ content_rowid='rowid'
22260
+ );
22261
+
22262
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
22263
+ AFTER INSERT ON memory_chunks BEGIN
22264
+ INSERT INTO memory_fts(rowid, content) VALUES (new.rowid, new.content);
22265
+ END;
22266
+
22267
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
22268
+ AFTER DELETE ON memory_chunks BEGIN
22269
+ INSERT INTO memory_fts(memory_fts, rowid, content)
22270
+ VALUES ('delete', old.rowid, old.content);
22271
+ END;
22272
+ `);
22273
+ if (this._vecAvailable) {
22274
+ try {
22275
+ db.exec(`
22276
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
22277
+ embedding FLOAT[${this.config.vectorDimension}]
22278
+ );
22279
+ `);
22280
+ } catch (err) {
22281
+ console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
22282
+ this._vecAvailable = false;
22283
+ }
22284
+ }
22285
+ }
22286
+ upsert(chunks, embeddings) {
22287
+ const insertChunk = this.db.prepare(`
22288
+ INSERT OR REPLACE INTO memory_chunks
22289
+ (id, source, heading, heading_level, content, line_start, line_end, indexed_at)
22290
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
22291
+ `);
22292
+ let insertVec = null;
22293
+ if (this._vecAvailable) {
22294
+ insertVec = this.db.prepare(`INSERT OR REPLACE INTO memory_vec (rowid, embedding) VALUES (?, ?)`);
22295
+ }
22296
+ const upsertAll = this.db.transaction(() => {
22297
+ for (let i = 0; i < chunks.length; i++) {
22298
+ const chunk = chunks[i];
22299
+ try {
22300
+ this.db.prepare(`DELETE FROM memory_chunks WHERE id = ?`).run(chunk.id);
22301
+ const info = insertChunk.run(
22302
+ chunk.id,
22303
+ chunk.source,
22304
+ chunk.heading,
22305
+ chunk.headingLevel,
22306
+ chunk.content,
22307
+ chunk.lineStart,
22308
+ chunk.lineEnd,
22309
+ Date.now()
22310
+ );
22311
+ if (insertVec && embeddings[i] && info.lastInsertRowid) {
22312
+ const rowid = Number(info.lastInsertRowid);
22313
+ const emb = embeddings[i];
22314
+ const buf = Buffer.from(emb.buffer, emb.byteOffset, emb.byteLength);
22315
+ insertVec.run(rowid, buf);
22316
+ }
22317
+ } catch (err) {
22318
+ console.warn(`[memory-store] Failed to upsert chunk ${chunk.id}:`, err);
22319
+ }
22320
+ }
22321
+ });
22322
+ try {
22323
+ upsertAll();
22324
+ } catch (err) {
22325
+ throw new StoreError("Full batch upsert failed", err);
22326
+ }
22327
+ }
22328
+ hashesBySource(source) {
22329
+ const rows = this.db.prepare(`SELECT id FROM memory_chunks WHERE source = ?`).all(source);
22330
+ return new Set(rows.map((r) => r.id));
22331
+ }
22332
+ indexedSources() {
22333
+ const rows = this.db.prepare(`SELECT DISTINCT source FROM memory_chunks`).all();
22334
+ return rows.map((r) => r.source);
22335
+ }
22336
+ deleteBySource(source) {
22337
+ const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE source = ?`).all(source);
22338
+ const deleteAll = this.db.transaction(() => {
22339
+ if (this._vecAvailable && rows.length > 0) {
22340
+ const placeholders = rows.map(() => "?").join(", ");
22341
+ this.db.prepare(`DELETE FROM memory_vec WHERE rowid IN (${placeholders})`).run(...rows.map((r) => r.rowid));
22342
+ }
22343
+ this.db.prepare(`DELETE FROM memory_chunks WHERE source = ?`).run(source);
22344
+ });
22345
+ deleteAll();
22346
+ }
22347
+ deleteByIds(ids) {
22348
+ if (ids.length === 0) return;
22349
+ const placeholders = ids.map(() => "?").join(", ");
22350
+ const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE id IN (${placeholders})`).all(...ids);
22351
+ const deleteAll = this.db.transaction(() => {
22352
+ if (this._vecAvailable && rows.length > 0) {
22353
+ const rPlaceholders = rows.map(() => "?").join(", ");
22354
+ this.db.prepare(`DELETE FROM memory_vec WHERE rowid IN (${rPlaceholders})`).run(...rows.map((r) => r.rowid));
22355
+ }
22356
+ this.db.prepare(`DELETE FROM memory_chunks WHERE id IN (${placeholders})`).run(...ids);
22357
+ });
22358
+ deleteAll();
22359
+ }
22360
+ searchDense(embedding, limit) {
22361
+ if (!this._vecAvailable) return [];
22362
+ try {
22363
+ const buf = Buffer.from(embedding.buffer);
22364
+ const rows = this.db.prepare(
22365
+ `SELECT mc.id, mv.distance
22366
+ FROM memory_vec mv
22367
+ JOIN memory_chunks mc ON mc.rowid = mv.rowid
22368
+ WHERE mv.embedding MATCH ? AND k = ?
22369
+ ORDER BY mv.distance`
22370
+ ).all(buf, limit);
22371
+ return rows.map((r) => ({
22372
+ id: r.id,
22373
+ score: Math.max(0, 1 - r.distance)
22374
+ }));
22375
+ } catch (err) {
22376
+ console.warn("[memory-store] Dense search failed:", err);
22377
+ return [];
22378
+ }
22379
+ }
22380
+ searchBm25(query, limit) {
22381
+ if (!query.trim()) return [];
22382
+ const ftsQuery = query.trim().split(/\s+/).map((t) => t.replace(/['"*^():\-]/g, " ").trim()).filter(Boolean).join(" OR ");
22383
+ if (!ftsQuery) return [];
22384
+ try {
22385
+ const rows = this.db.prepare(
22386
+ `SELECT mc.id, rank
22387
+ FROM memory_fts
22388
+ JOIN memory_chunks mc ON mc.rowid = memory_fts.rowid
22389
+ WHERE memory_fts MATCH ?
22390
+ ORDER BY rank
22391
+ LIMIT ?`
22392
+ ).all(ftsQuery, limit);
22393
+ if (rows.length === 0) return [];
22394
+ const ranks = rows.map((r) => r.rank);
22395
+ const minRank = Math.min(...ranks);
22396
+ const maxRank = Math.max(...ranks);
22397
+ const range = maxRank - minRank;
22398
+ return rows.map((r) => ({
22399
+ id: r.id,
22400
+ score: range > 0 ? (maxRank - r.rank) / range : 1
22401
+ }));
22402
+ } catch (err) {
22403
+ console.warn("[memory-store] BM25 search failed:", err);
22404
+ return [];
22405
+ }
22406
+ }
22407
+ getChunksByIds(ids) {
22408
+ if (ids.length === 0) return [];
22409
+ const placeholders = ids.map(() => "?").join(", ");
22410
+ const rows = this.db.prepare(
22411
+ `SELECT id, source, heading, heading_level, content, line_start, line_end
22412
+ FROM memory_chunks WHERE id IN (${placeholders})`
22413
+ ).all(...ids);
22414
+ return rows.map((r) => ({
22415
+ id: r.id,
22416
+ source: r.source,
22417
+ heading: r.heading,
22418
+ headingLevel: r.heading_level,
22419
+ content: r.content,
22420
+ lineStart: r.line_start,
22421
+ lineEnd: r.line_end
22422
+ }));
22423
+ }
22424
+ get vecAvailable() {
22425
+ return this._vecAvailable;
22426
+ }
22427
+ close() {
22428
+ this.db.close();
22429
+ }
22430
+ };
22431
+
22432
+ // src/tools/memory.ts
22433
+ function registerMemoryToolHandlers(server2, indexer, store, config2) {
22434
+ server2.tool(
22435
+ "kit_memory_search",
22436
+ "Search persistent memory for context relevant to the current question. Returns semantically relevant results using hybrid dense+BM25+RRF search.",
22437
+ {
22438
+ query: external_exports.string().min(1).describe("Search query"),
22439
+ top_k: external_exports.number().int().positive().optional().describe("Number of results to return")
22440
+ },
22441
+ async ({ query, top_k }) => {
22442
+ try {
22443
+ const results = await indexer.search(query, top_k ?? config2.topK);
22444
+ if (results.length === 0) {
22445
+ const degradedNote2 = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
22446
+ return mcpText(`${degradedNote2}No memories found for query: "${query}"`);
22447
+ }
22448
+ const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
22449
+ const formatted = results.map((r) => {
22450
+ const displaySource = r.chunk.source.replace(/^compiled\//, "");
22451
+ const content = r.contentSource === "fallback" ? `\u26A0\uFE0F Source file unavailable \u2014 showing matched chunk only:
22452
+ ${r.chunk.content}` : r.chunk.content;
22453
+ return `### ${displaySource} (score: ${r.score.toFixed(3)})
22454
+ ${content}`;
22455
+ }).join("\n\n---\n\n");
22456
+ return mcpText(`${degradedNote}${formatted}`);
22457
+ } catch (err) {
22458
+ const message = err instanceof Error ? err.message : String(err);
22459
+ return mcpText(`kit_memory_search failed: ${message}`);
22460
+ }
22461
+ }
22462
+ );
22463
+ server2.tool(
22464
+ "kit_memory_save",
22465
+ "Save content to wiki/raw for inclusion after the next /wiki compile.",
22466
+ {
22467
+ content: external_exports.string().min(1).describe("Content to save to memory")
22468
+ },
22469
+ async ({ content }) => {
22470
+ try {
22471
+ await indexer.save(content);
22472
+ return mcpJson({
22473
+ saved: true,
22474
+ queued_for_compile: true,
22475
+ message: "Saved to wiki/raw \u2014 will be indexed after next /wiki compile"
22476
+ });
22477
+ } catch (err) {
22478
+ const message = err instanceof Error ? err.message : String(err);
22479
+ return mcpJson({ saved: false, error: message });
22480
+ }
22481
+ }
22482
+ );
22483
+ }
22484
+ function registerMemoryTools(server2, settings2, workspaceRoot2) {
22485
+ if (settings2.memory?.enabled !== true) return null;
22486
+ const config2 = resolveMemoryConfig(settings2, workspaceRoot2);
22487
+ const store = new MemoryStore(path10.join(config2.wikiDir, "index.db"), config2);
22488
+ const embedder = new Embedder(config2.embeddingModel);
22489
+ const indexer = new MemoryIndexer(store, embedder, config2);
22490
+ registerMemoryToolHandlers(server2, indexer, store, config2);
22491
+ return indexer;
22492
+ }
22493
+
21745
22494
  // src/kit-server.ts
21746
22495
  var server = new McpServer({
21747
22496
  name: "kit-agents",
@@ -21750,5 +22499,11 @@ var server = new McpServer({
21750
22499
  registerIntegrationTools(server);
21751
22500
  registerCoreTools(server);
21752
22501
  registerAgentTools(server);
22502
+ var workspaceRoot = getWorkspaceRoot();
22503
+ var settings = loadProjectSettings(workspaceRoot);
22504
+ var memoryIndexer = registerMemoryTools(server, settings, workspaceRoot);
21753
22505
  var transport = new StdioServerTransport();
21754
22506
  await server.connect(transport);
22507
+ if (memoryIndexer) {
22508
+ void memoryIndexer.startupIndex();
22509
+ }