@hanhnd/agent-kit 1.0.33 → 1.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cli/commands/cli-digest-pending.test.js +19 -10
  2. package/dist/cli/commands/memory.js +44 -4
  3. package/dist/cli.js +307 -129
  4. package/dist/core/config/index.d.ts +3 -0
  5. package/dist/core/config/index.js +20 -0
  6. package/dist/core/handoffs/index.d.ts +8 -7
  7. package/dist/core/handoffs/index.js +12 -4
  8. package/dist/core/handoffs/index.test.d.ts +1 -0
  9. package/dist/core/handoffs/index.test.js +88 -0
  10. package/dist/entrypoints/server.js +1 -3
  11. package/dist/mcp/agent.d.ts +1 -1
  12. package/dist/mcp/agent.js +3 -3
  13. package/dist/mcp/core.d.ts +7 -0
  14. package/dist/mcp/core.js +39 -25
  15. package/dist/mcp/core.test.d.ts +1 -0
  16. package/dist/mcp/core.test.js +98 -0
  17. package/dist/mcp/memory.d.ts +2 -1
  18. package/dist/mcp/memory.js +45 -18
  19. package/dist/server.js +302 -178
  20. package/dist/services/digest/constants.d.ts +3 -3
  21. package/dist/services/digest/constants.js +3 -3
  22. package/dist/services/digest/digest-background.test.js +19 -8
  23. package/dist/services/digest/digest-init.test.js +13 -2
  24. package/dist/services/digest/digest-pending.test.js +13 -2
  25. package/dist/services/digest/digest-processor.test.js +66 -0
  26. package/dist/services/digest/files.js +3 -3
  27. package/dist/services/digest/model-registry.js +2 -2
  28. package/dist/services/digest/processor.js +43 -13
  29. package/dist/services/digest/providers/llama-local.js +17 -28
  30. package/dist/services/digest/types.d.ts +3 -1
  31. package/dist/services/digest/types.js +1 -1
  32. package/dist/services/memory/chunker.d.ts +5 -2
  33. package/dist/services/memory/chunker.js +3 -1
  34. package/dist/services/memory/chunker.test.js +21 -11
  35. package/dist/services/memory/constants.js +1 -1
  36. package/dist/services/memory/embedder.js +1 -1
  37. package/dist/services/memory/index.d.ts +1 -1
  38. package/dist/services/memory/indexer.d.ts +2 -1
  39. package/dist/services/memory/indexer.js +24 -8
  40. package/dist/services/memory/indexer.test.js +38 -2
  41. package/dist/services/memory/memory.test.js +172 -15
  42. package/dist/services/memory/store.d.ts +6 -1
  43. package/dist/services/memory/store.js +87 -47
  44. package/dist/services/memory/store.test.js +123 -3
  45. package/dist/services/memory/types.d.ts +9 -1
  46. package/dist/services/memory/types.js +2 -1
  47. package/dist/utils/paths.d.ts +2 -0
  48. package/dist/utils/paths.js +2 -1
  49. package/dist/utils/utils.d.ts +1 -0
  50. package/dist/utils/utils.js +3 -3
  51. package/package.json +2 -2
package/dist/server.js CHANGED
@@ -1,18 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from 'module'; const require = createRequire(import.meta.url);
3
2
  var __create = Object.create;
4
3
  var __defProp = Object.defineProperty;
5
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
6
  var __getProtoOf = Object.getPrototypeOf;
8
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
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() {
8
+ var __commonJS = (cb, mod) => function __require() {
16
9
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
10
  };
18
11
  var __export = (target, all) => {
@@ -6792,12 +6785,12 @@ var require_dist = __commonJS({
6792
6785
  throw new Error(`Unknown format "${name}"`);
6793
6786
  return f;
6794
6787
  };
6795
- function addFormats(ajv, list, fs11, exportName) {
6788
+ function addFormats(ajv, list, fs12, exportName) {
6796
6789
  var _a;
6797
6790
  var _b;
6798
6791
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6799
6792
  for (const f of list)
6800
- ajv.addFormat(f, fs11[f]);
6793
+ ajv.addFormat(f, fs12[f]);
6801
6794
  }
6802
6795
  module.exports = exports = formatsPlugin;
6803
6796
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -35889,7 +35882,7 @@ import * as path4 from "path";
35889
35882
 
35890
35883
  // src/utils/utils.ts
35891
35884
  function getWorkspaceRoot() {
35892
- 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();
35885
+ return process.env.WORKSPACE_DIR || process.env.CLAUDE_PROJECT_DIR || process.env.CODEX_PROJECT_DIR || process.env.GEMINI_PROJECT_DIR || process.env.INIT_CWD || process.env.PWD || process.cwd();
35893
35886
  }
35894
35887
  function mcpText(text) {
35895
35888
  return { content: [{ type: "text", text }] };
@@ -35897,11 +35890,18 @@ function mcpText(text) {
35897
35890
  function mcpJson(obj) {
35898
35891
  return mcpText(JSON.stringify(obj, null, 2));
35899
35892
  }
35893
+ function formatError2(err) {
35894
+ return err instanceof Error ? err.message : String(err);
35895
+ }
35900
35896
 
35901
35897
  // src/core/config/index.ts
35902
35898
  import * as fs2 from "fs";
35899
+ import * as os from "os";
35903
35900
  import * as path2 from "path";
35904
35901
 
35902
+ // src/services/memory/types.ts
35903
+ var SOURCE_TYPES = ["digest", "concept", "entity", "preference", "wiki"];
35904
+
35905
35905
  // src/services/memory/constants.ts
35906
35906
  var DEFAULT_MEMORY_CONFIG = {
35907
35907
  enabled: false,
@@ -35909,7 +35909,7 @@ var DEFAULT_MEMORY_CONFIG = {
35909
35909
  topK: 5,
35910
35910
  chunkSize: 1500,
35911
35911
  overlapLines: 2,
35912
- embeddingModel: "small" /* SMALL */,
35912
+ embeddingModel: "base" /* BASE */,
35913
35913
  vectorDimension: 384
35914
35914
  };
35915
35915
  var LOCK_RETRY_MS = 50;
@@ -35983,8 +35983,8 @@ function tryAcquireProcessLock(lockPath) {
35983
35983
  }
35984
35984
 
35985
35985
  // src/core/config/index.ts
35986
- function resolveMemoryConfig(settings2, workspaceRoot2) {
35987
- const override = settings2.memory ?? {};
35986
+ function resolveMemoryConfig(settings, workspaceRoot2) {
35987
+ const override = settings.memory ?? {};
35988
35988
  return {
35989
35989
  ...DEFAULT_MEMORY_CONFIG,
35990
35990
  ...override,
@@ -35992,24 +35992,26 @@ function resolveMemoryConfig(settings2, workspaceRoot2) {
35992
35992
  wikiDir: typeof override.wikiDir === "string" && override.wikiDir.length > 0 ? override.wikiDir : path2.join(workspaceRoot2, ".agent-kit", "wiki")
35993
35993
  };
35994
35994
  }
35995
- function loadProjectSettings(projectDir) {
35996
- const settingsPath = path2.join(projectDir, ".agent-kit", "settings.json");
35997
- if (fs2.existsSync(settingsPath)) {
35995
+ function getGlobalSettingsFile() {
35996
+ return path2.join(os.homedir(), ".agent-kit", "settings.json");
35997
+ }
35998
+ function loadGlobalSettings() {
35999
+ const file = getGlobalSettingsFile();
36000
+ if (fs2.existsSync(file)) {
35998
36001
  try {
35999
- return JSON.parse(fs2.readFileSync(settingsPath, "utf-8"));
36002
+ return JSON.parse(fs2.readFileSync(file, "utf-8"));
36000
36003
  } catch (error2) {
36001
- console.error(`[agent-kit] Warning: Failed to parse ${settingsPath}:`, error2);
36004
+ console.error(`[agent-kit] Warning: Failed to parse ${file}:`, error2);
36002
36005
  }
36003
36006
  }
36004
36007
  return {};
36005
36008
  }
36006
- function writeProjectSettings(workspaceRoot2, patch) {
36007
- const settingsPath = path2.join(workspaceRoot2, ".agent-kit", "settings.json");
36008
- const current = loadProjectSettings(workspaceRoot2);
36009
- atomicWriteJsonFile(settingsPath, { ...current, ...patch });
36009
+ function writeGlobalSettings(patch) {
36010
+ const current = loadGlobalSettings();
36011
+ atomicWriteJsonFile(getGlobalSettingsFile(), { ...current, ...patch });
36010
36012
  }
36011
- function resolveConversationDigestConfig(settings2) {
36012
- const digest = settings2.memory?.conversationDigest;
36013
+ function resolveConversationDigestConfig(settings) {
36014
+ const digest = settings.memory?.conversationDigest;
36013
36015
  if (!digest?.initialized || typeof digest.modelId !== "string" || typeof digest.initializedAt !== "string") {
36014
36016
  return void 0;
36015
36017
  }
@@ -36062,7 +36064,8 @@ var HANDOFF_TYPES = [
36062
36064
  "ticket",
36063
36065
  "research",
36064
36066
  "scenario",
36065
- "investigation"
36067
+ "investigation",
36068
+ "code"
36066
36069
  ];
36067
36070
  var TICKET_ID_PATTERN = /\b[A-Z][A-Z0-9]+-\d+\b/;
36068
36071
  function findTicketId(value) {
@@ -36090,52 +36093,72 @@ function deriveFeatureSlug(input) {
36090
36093
  if (contentTicketSlug) return contentTicketSlug;
36091
36094
  return sanitizeFeatureSlug(contentSlugCandidate(input.content)) || "untitled-handoff";
36092
36095
  }
36093
- function resolveHandoffPath(input) {
36096
+ function resolveHandoffFolder(input) {
36094
36097
  const canonicalType = normalizeHandoffType(input.type);
36095
36098
  const featureSlug = deriveFeatureSlug({
36096
36099
  requestedSlug: input.slug,
36097
- content: input.content,
36100
+ content: input.primaryContent,
36098
36101
  type: canonicalType
36099
36102
  });
36100
- const relativePath = path3.join(".agent-kit", "handoffs", featureSlug, `${canonicalType}.md`);
36103
+ const relativePath = path3.join(".agent-kit", "handoffs", featureSlug, canonicalType);
36101
36104
  return {
36102
36105
  featureSlug,
36103
36106
  canonicalType,
36104
- filePath: path3.join(input.workspaceRoot, relativePath),
36107
+ folderPath: path3.join(input.workspaceRoot, relativePath),
36105
36108
  relativePath
36106
36109
  };
36107
36110
  }
36111
+ function validateHandoffFilename(name) {
36112
+ const normalized = name.trim().replace(/^["']|["']$/g, "");
36113
+ if (!/^[A-Za-z0-9_-]+\.md$/.test(normalized)) {
36114
+ throw new Error(`Unsafe handoff filename: ${name}`);
36115
+ }
36116
+ return normalized;
36117
+ }
36108
36118
 
36109
36119
  // src/mcp/core.ts
36120
+ function handleSaveHandoff(args, workspaceRoot2) {
36121
+ try {
36122
+ if (Object.keys(args.files).length === 0) {
36123
+ return mcpText("Error saving handoff: files cannot be empty");
36124
+ }
36125
+ for (const name of Object.keys(args.files)) {
36126
+ try {
36127
+ validateHandoffFilename(name);
36128
+ } catch (err) {
36129
+ return mcpText(`Error saving handoff: ${err instanceof Error ? err.message : err}`);
36130
+ }
36131
+ }
36132
+ const primaryContent = args.files["README.md"] ?? Object.values(args.files)[0];
36133
+ const location = resolveHandoffFolder({
36134
+ workspaceRoot: workspaceRoot2,
36135
+ type: args.type,
36136
+ slug: args.slug,
36137
+ primaryContent
36138
+ });
36139
+ fs3.mkdirSync(location.folderPath, { recursive: true });
36140
+ for (const [name, content] of Object.entries(args.files)) {
36141
+ fs3.writeFileSync(path4.join(location.folderPath, name), content, "utf8");
36142
+ }
36143
+ return mcpText(`\u2705 Saved to: ${location.folderPath}`);
36144
+ } catch (error2) {
36145
+ return mcpText(`Error saving handoff: ${error2 instanceof Error ? error2.message : error2}`);
36146
+ }
36147
+ }
36110
36148
  function registerCoreTools(server2) {
36111
36149
  server2.tool(
36112
36150
  "kit_save_handoff",
36113
- `Save a handoff artifact to .agent-kit/handoffs/. Returns the saved file path to use in next-step instructions. Do NOT append version numbers (v2, v3, etc.) to the slug.`,
36151
+ `Save a handoff artifact to .agent-kit/handoffs/. Returns the saved folder path to use in next-step instructions. Do NOT append version numbers (v2, v3, etc.) to the slug.`,
36114
36152
  {
36115
36153
  type: external_exports.enum(HANDOFF_TYPES).describe("Handoff type"),
36116
- content: external_exports.string().describe("Full markdown content to save"),
36154
+ files: external_exports.record(external_exports.string(), external_exports.string()).describe(
36155
+ "Markdown files to write into the handoff folder. May include one or many files per call; repeated calls with the same type and slug add or overwrite the provided files. Filenames must match /^[A-Za-z0-9_-]+\\.md$/."
36156
+ ),
36117
36157
  slug: external_exports.string().describe(
36118
- 'Short identifier for the filename, e.g. "user-auth" or "PROJ-123". Do NOT append version numbers (v2, v3, etc.).'
36158
+ 'Short identifier for the folder, e.g. "user-auth" or "PROJ-123". Do NOT append version numbers (v2, v3, etc.).'
36119
36159
  )
36120
36160
  },
36121
- async ({ type, content, slug }) => {
36122
- try {
36123
- const location = resolveHandoffPath({
36124
- workspaceRoot: getWorkspaceRoot(),
36125
- type,
36126
- slug,
36127
- content
36128
- });
36129
- const handoffDir = path4.dirname(location.filePath);
36130
- if (!fs3.existsSync(handoffDir)) {
36131
- fs3.mkdirSync(handoffDir, { recursive: true });
36132
- }
36133
- fs3.writeFileSync(location.filePath, content, "utf8");
36134
- return mcpText(`\u2705 Saved to: ${location.filePath}`);
36135
- } catch (error2) {
36136
- return mcpText(`Error saving handoff: ${error2}`);
36137
- }
36138
- }
36161
+ async (args) => handleSaveHandoff(args, getWorkspaceRoot())
36139
36162
  );
36140
36163
  }
36141
36164
 
@@ -36245,10 +36268,10 @@ ${"\u2500".repeat(60)}
36245
36268
  function registerAgentTools(server2) {
36246
36269
  server2.tool(
36247
36270
  "kit_trigger_agent",
36248
- "!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff file (.agent-kit/handoffs/feature-slug/plan.md).",
36271
+ "!Important: Trigger only when user explicitly asks to delegate a task to an external agent CLI (gemini, claude, or codex). The task can be a direct message or a path to a handoff folder (.agent-kit/handoffs/feature-slug/plan).",
36249
36272
  {
36250
36273
  agent: external_exports.enum(["gemini", "claude", "codex"]).describe('The agent CLI to invoke: "gemini", "claude", or "codex"'),
36251
- task: external_exports.string().describe('Task message or path to a handoff file (e.g., ".agent-kit/handoffs/feature-slug/plan.md")')
36274
+ task: external_exports.string().describe('Task message or path to a handoff folder (e.g., ".agent-kit/handoffs/feature-slug/plan")')
36252
36275
  },
36253
36276
  async ({ agent, task }, extra) => {
36254
36277
  const workspaceRoot2 = getWorkspaceRoot();
@@ -36362,10 +36385,11 @@ import { writeFileSync as writeFileSync4 } from "fs";
36362
36385
  import * as fs5 from "fs";
36363
36386
 
36364
36387
  // src/utils/paths.ts
36365
- import * as os from "os";
36388
+ import * as os2 from "os";
36366
36389
  import * as path7 from "path";
36367
- var AGENT_KIT_HOME = path7.join(os.homedir(), ".agent-kit");
36390
+ var AGENT_KIT_HOME = path7.join(os2.homedir(), ".agent-kit");
36368
36391
  var CREDENTIALS_FILE = path7.join(AGENT_KIT_HOME, "credentials");
36392
+ var GLOBAL_SETTINGS_FILE = path7.join(AGENT_KIT_HOME, "settings.json");
36369
36393
  var MODEL_CACHE_DIR = path7.join(AGENT_KIT_HOME, "cache", "models");
36370
36394
  var FASTEMBED_CACHE_DIR = path7.join(MODEL_CACHE_DIR, "fastembed");
36371
36395
 
@@ -36737,6 +36761,7 @@ ${ticket.fields.labels?.join(", ") || "None"}`;
36737
36761
  }
36738
36762
 
36739
36763
  // src/mcp/memory.ts
36764
+ import * as fs11 from "fs";
36740
36765
  import * as path14 from "path";
36741
36766
 
36742
36767
  // src/services/memory/embedder.ts
@@ -36786,7 +36811,7 @@ var Embedder = class {
36786
36811
  const { EmbeddingModel, FlagEmbedding } = await import("fastembed");
36787
36812
  const modelMap = {
36788
36813
  ["tiny" /* TINY */]: EmbeddingModel.AllMiniLML6V2,
36789
- ["small" /* SMALL */]: EmbeddingModel.BGESmallENV15,
36814
+ ["base" /* BASE */]: EmbeddingModel.BGESmallENV15,
36790
36815
  ["large" /* LARGE */]: EmbeddingModel.BGEBaseENV15
36791
36816
  };
36792
36817
  const model = modelMap[this.modelName] ?? EmbeddingModel.BGESmallENV15;
@@ -36898,7 +36923,7 @@ function splitSection(text, maxLen) {
36898
36923
  if (byPara.every((p) => p.length <= maxLen)) return byPara;
36899
36924
  return byPara.flatMap((p) => p.length <= maxLen ? [p] : splitAtLine(p, maxLen));
36900
36925
  }
36901
- function chunkMarkdown(text, source, config2) {
36926
+ function chunkMarkdown(text, source, config2, meta) {
36902
36927
  if (!text || !text.trim()) return [];
36903
36928
  const { chunkSize, overlapLines } = config2;
36904
36929
  const lines = text.split("\n");
@@ -36937,11 +36962,13 @@ ${sectionText}` : sectionText;
36937
36962
  chunks.push({
36938
36963
  id: computeChunkId(content),
36939
36964
  source,
36965
+ sourceType: meta.sourceType,
36940
36966
  heading: section.heading,
36941
36967
  headingLevel: section.headingLevel,
36942
36968
  content,
36943
36969
  lineStart: firstLine,
36944
- lineEnd: lastLine
36970
+ lineEnd: lastLine,
36971
+ fileMtimeAt: meta.fileMtimeAt
36945
36972
  });
36946
36973
  if (pi === parts.length - 1) {
36947
36974
  prevOverlapLines = partLines.slice(-overlapLines);
@@ -36952,6 +36979,13 @@ ${sectionText}` : sectionText;
36952
36979
  }
36953
36980
 
36954
36981
  // src/services/memory/indexer.ts
36982
+ function deriveSourceType(source) {
36983
+ if (source.startsWith("compiled/provisional/conversation-digests/")) return "digest";
36984
+ if (source.startsWith("compiled/concepts/")) return "concept";
36985
+ if (source.startsWith("compiled/entities/")) return "entity";
36986
+ if (source === "compiled/preferences.md") return "preference";
36987
+ return "wiki";
36988
+ }
36955
36989
  var MemoryIndexer = class {
36956
36990
  constructor(store, embedder, config2) {
36957
36991
  this.store = store;
@@ -36972,7 +37006,15 @@ var MemoryIndexer = class {
36972
37006
  console.warn(`[memory-indexer] Cannot read file ${absolutePath}:`, err);
36973
37007
  return { indexed: 0, deleted: 0, skipped: existingChunkIds.size };
36974
37008
  }
36975
- const allChunks = chunkMarkdown(text, source, this.config);
37009
+ let fileMtimeAt;
37010
+ try {
37011
+ fileMtimeAt = fs6.statSync(absolutePath).mtimeMs;
37012
+ } catch (err) {
37013
+ console.warn(`[memory-indexer] Cannot stat file ${absolutePath}:`, err);
37014
+ fileMtimeAt = Date.now();
37015
+ }
37016
+ const sourceType = deriveSourceType(source);
37017
+ const allChunks = chunkMarkdown(text, source, this.config, { sourceType, fileMtimeAt });
36976
37018
  const currentChunkIds = new Set(allChunks.map((chunk) => chunk.id));
36977
37019
  const toIndex = allChunks.filter((chunk) => !existingChunkIds.has(chunk.id));
36978
37020
  const toDelete = [...existingChunkIds].filter((id) => !currentChunkIds.has(id));
@@ -37059,12 +37101,9 @@ var MemoryIndexer = class {
37059
37101
  scoreMap.set(r.id, { dense: 1 / (RRF_K + rank + 1), bm25: 0 });
37060
37102
  });
37061
37103
  bm25Results.forEach((r, rank) => {
37062
- const existing = scoreMap.get(r.id);
37063
- if (existing) {
37064
- existing.bm25 = 1 / (RRF_K + rank + 1);
37065
- } else {
37066
- scoreMap.set(r.id, { dense: 0, bm25: 1 / (RRF_K + rank + 1) });
37067
- }
37104
+ const entry = scoreMap.get(r.id) ?? { dense: 0, bm25: 0 };
37105
+ entry.bm25 = 1 / (RRF_K + rank + 1);
37106
+ scoreMap.set(r.id, entry);
37068
37107
  });
37069
37108
  const hasDense = this.store.vecAvailable && denseResults.length > 0;
37070
37109
  const numRetrievers = hasDense ? 2 : 1;
@@ -37138,7 +37177,10 @@ ${content}
37138
37177
  var import_stopword = __toESM(require_stopword_cjs(), 1);
37139
37178
  import Database from "better-sqlite3";
37140
37179
  import * as fs7 from "fs";
37180
+ import { createRequire } from "module";
37141
37181
  import * as path9 from "path";
37182
+ var require2 = createRequire(import.meta.url);
37183
+ var SCHEMA_VERSION = 2;
37142
37184
  var StoreError = class extends Error {
37143
37185
  constructor(message, cause) {
37144
37186
  super(message);
@@ -37178,7 +37220,7 @@ var MemoryStore = class {
37178
37220
  }
37179
37221
  loadVecExtension(db) {
37180
37222
  try {
37181
- const sqliteVec = __require("sqlite-vec");
37223
+ const sqliteVec = require2("sqlite-vec");
37182
37224
  sqliteVec.load(db);
37183
37225
  this._vecAvailable = true;
37184
37226
  } catch (err) {
@@ -37187,57 +37229,77 @@ var MemoryStore = class {
37187
37229
  }
37188
37230
  }
37189
37231
  createSchema(db) {
37190
- db.exec(`
37191
- CREATE TABLE IF NOT EXISTS memory_chunks (
37192
- rowid INTEGER PRIMARY KEY AUTOINCREMENT,
37193
- id TEXT NOT NULL UNIQUE,
37194
- source TEXT NOT NULL,
37195
- heading TEXT NOT NULL DEFAULT '',
37196
- heading_level INTEGER NOT NULL DEFAULT 0,
37197
- content TEXT NOT NULL,
37198
- line_start INTEGER NOT NULL,
37199
- line_end INTEGER NOT NULL,
37200
- indexed_at INTEGER NOT NULL
37201
- );
37232
+ try {
37233
+ const current = Number(db.pragma("user_version", { simple: true }) ?? 0);
37234
+ if (current < SCHEMA_VERSION) {
37235
+ db.exec(`
37236
+ DROP TRIGGER IF EXISTS memory_chunks_ai;
37237
+ DROP TRIGGER IF EXISTS memory_chunks_ad;
37238
+ DROP TABLE IF EXISTS memory_fts;
37239
+ DROP TABLE IF EXISTS memory_vec;
37240
+ DROP TABLE IF EXISTS memory_chunks;
37241
+ `);
37242
+ db.pragma(`user_version = ${SCHEMA_VERSION}`);
37243
+ }
37244
+ db.exec(`
37245
+ CREATE TABLE IF NOT EXISTS memory_chunks (
37246
+ rowid INTEGER PRIMARY KEY AUTOINCREMENT,
37247
+ id TEXT NOT NULL UNIQUE,
37248
+ source TEXT NOT NULL,
37249
+ source_type TEXT NOT NULL DEFAULT 'wiki',
37250
+ heading TEXT NOT NULL DEFAULT '',
37251
+ heading_level INTEGER NOT NULL DEFAULT 0,
37252
+ content TEXT NOT NULL,
37253
+ line_start INTEGER NOT NULL,
37254
+ line_end INTEGER NOT NULL,
37255
+ indexed_at INTEGER NOT NULL,
37256
+ file_mtime_at INTEGER NOT NULL DEFAULT 0
37257
+ );
37202
37258
 
37203
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
37204
- source,
37205
- heading,
37206
- content,
37207
- content='memory_chunks',
37208
- content_rowid='rowid'
37209
- );
37259
+ CREATE INDEX IF NOT EXISTS idx_memory_chunks_type_mtime
37260
+ ON memory_chunks (source_type, file_mtime_at DESC);
37210
37261
 
37211
- CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
37212
- AFTER INSERT ON memory_chunks BEGIN
37213
- INSERT INTO memory_fts(rowid, source, heading, content)
37214
- VALUES (new.rowid, new.source, new.heading, new.content);
37215
- END;
37262
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(
37263
+ source,
37264
+ heading,
37265
+ content,
37266
+ content='memory_chunks',
37267
+ content_rowid='rowid'
37268
+ );
37216
37269
 
37217
- CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
37218
- AFTER DELETE ON memory_chunks BEGIN
37219
- INSERT INTO memory_fts(memory_fts, rowid, source, heading, content)
37220
- VALUES ('delete', old.rowid, old.source, old.heading, old.content);
37221
- END;
37222
- `);
37223
- if (this._vecAvailable) {
37224
- try {
37225
- db.exec(`
37226
- CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
37227
- embedding FLOAT[${this.config.vectorDimension}]
37228
- );
37229
- `);
37230
- } catch (err) {
37231
- console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
37232
- this._vecAvailable = false;
37270
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ai
37271
+ AFTER INSERT ON memory_chunks BEGIN
37272
+ INSERT INTO memory_fts(rowid, source, heading, content)
37273
+ VALUES (new.rowid, new.source, new.heading, new.content);
37274
+ END;
37275
+
37276
+ CREATE TRIGGER IF NOT EXISTS memory_chunks_ad
37277
+ AFTER DELETE ON memory_chunks BEGIN
37278
+ INSERT INTO memory_fts(memory_fts, rowid, source, heading, content)
37279
+ VALUES ('delete', old.rowid, old.source, old.heading, old.content);
37280
+ END;
37281
+ `);
37282
+ if (this._vecAvailable) {
37283
+ try {
37284
+ db.exec(`
37285
+ CREATE VIRTUAL TABLE IF NOT EXISTS memory_vec USING vec0(
37286
+ embedding FLOAT[${this.config.vectorDimension}]
37287
+ );
37288
+ `);
37289
+ } catch (err) {
37290
+ console.warn("[memory-store] Failed to create memory_vec, disabling vector search:", err);
37291
+ this._vecAvailable = false;
37292
+ }
37233
37293
  }
37294
+ } catch (err) {
37295
+ throw new StoreError("schema migration failed", err);
37234
37296
  }
37235
37297
  }
37236
37298
  upsert(chunks, embeddings) {
37237
37299
  const insertChunk = this.db.prepare(`
37238
37300
  INSERT OR REPLACE INTO memory_chunks
37239
- (id, source, heading, heading_level, content, line_start, line_end, indexed_at)
37240
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
37301
+ (id, source, source_type, heading, heading_level, content, line_start, line_end, indexed_at, file_mtime_at)
37302
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
37241
37303
  `);
37242
37304
  let insertVec = null;
37243
37305
  if (this._vecAvailable) {
@@ -37251,15 +37313,17 @@ var MemoryStore = class {
37251
37313
  const info = insertChunk.run(
37252
37314
  chunk.id,
37253
37315
  chunk.source,
37316
+ chunk.sourceType,
37254
37317
  chunk.heading,
37255
37318
  chunk.headingLevel,
37256
37319
  chunk.content,
37257
37320
  chunk.lineStart,
37258
37321
  chunk.lineEnd,
37259
- Date.now()
37322
+ Date.now(),
37323
+ chunk.fileMtimeAt
37260
37324
  );
37261
37325
  if (insertVec && embeddings[i] && info.lastInsertRowid) {
37262
- const rowid = Number(info.lastInsertRowid);
37326
+ const rowid = typeof info.lastInsertRowid === "bigint" ? info.lastInsertRowid : BigInt(info.lastInsertRowid);
37263
37327
  const emb = embeddings[i];
37264
37328
  const buf = Buffer.from(emb.buffer, emb.byteOffset, emb.byteLength);
37265
37329
  insertVec.run(rowid, buf);
@@ -37283,6 +37347,20 @@ var MemoryStore = class {
37283
37347
  const rows = this.db.prepare(`SELECT DISTINCT source FROM memory_chunks`).all();
37284
37348
  return rows.map((r) => r.source);
37285
37349
  }
37350
+ getRecentSources(options) {
37351
+ const limit = Math.min(Math.max(1, options.limit || 5), 50);
37352
+ const whereClause = options.sourceType ? " WHERE source_type = ?" : "";
37353
+ const params = options.sourceType ? [options.sourceType, limit] : [limit];
37354
+ try {
37355
+ const rows = this.db.prepare(
37356
+ `SELECT source, MAX(file_mtime_at) AS t FROM memory_chunks${whereClause} GROUP BY source ORDER BY t DESC LIMIT ?`
37357
+ ).all(...params);
37358
+ return rows.map((row) => ({ source: row.source, fileMtimeAt: row.t }));
37359
+ } catch (err) {
37360
+ console.warn("[memory-store] Recent source lookup failed:", err);
37361
+ return [];
37362
+ }
37363
+ }
37286
37364
  deleteBySource(source) {
37287
37365
  const rows = this.db.prepare(`SELECT rowid FROM memory_chunks WHERE source = ?`).all(source);
37288
37366
  const deleteAll = this.db.transaction(() => {
@@ -37359,17 +37437,19 @@ var MemoryStore = class {
37359
37437
  if (ids.length === 0) return [];
37360
37438
  const placeholders = ids.map(() => "?").join(", ");
37361
37439
  const rows = this.db.prepare(
37362
- `SELECT id, source, heading, heading_level, content, line_start, line_end
37440
+ `SELECT id, source, source_type, heading, heading_level, content, line_start, line_end, file_mtime_at
37363
37441
  FROM memory_chunks WHERE id IN (${placeholders})`
37364
37442
  ).all(...ids);
37365
37443
  return rows.map((r) => ({
37366
37444
  id: r.id,
37367
37445
  source: r.source,
37446
+ sourceType: r.source_type,
37368
37447
  heading: r.heading,
37369
37448
  headingLevel: r.heading_level,
37370
37449
  content: r.content,
37371
37450
  lineStart: r.line_start,
37372
- lineEnd: r.line_end
37451
+ lineEnd: r.line_end,
37452
+ fileMtimeAt: r.file_mtime_at
37373
37453
  }));
37374
37454
  }
37375
37455
  get vecAvailable() {
@@ -37386,8 +37466,8 @@ import * as path13 from "node:path";
37386
37466
 
37387
37467
  // src/services/digest/constants.ts
37388
37468
  import * as path10 from "node:path";
37389
- var DEFAULT_DIGEST_MODEL_ID = "default" /* DEFAULT */;
37390
- var DEFAULT_DIGEST_MAX_INPUT_CHARS = 3e3;
37469
+ var DEFAULT_DIGEST_MODEL_ID = "base" /* BASE */;
37470
+ var DEFAULT_DIGEST_MAX_INPUT_CHARS = 1e4;
37391
37471
  var DEFAULT_DIGEST_TIMEOUT_MS = 12e4;
37392
37472
  var KIT_DIR = ".agent-kit";
37393
37473
  var WIKI_DIR = path10.join(KIT_DIR, "wiki");
@@ -37397,7 +37477,7 @@ var DIGEST_WORKER_DIR = path10.join(WIKI_DIR, "digest");
37397
37477
  var DIGEST_LOCKFILE_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.lock");
37398
37478
  var DIGEST_WORKER_STATUS_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.status.json");
37399
37479
  var DIGEST_WORKER_LOG_REL_PATH = path10.join(DIGEST_WORKER_DIR, "digest-worker.log");
37400
- var LLAMA_CONTEXT_SIZE = 4096;
37480
+ var LLAMA_CONTEXT_SIZE = 8192;
37401
37481
  var LLAMA_MAX_GENERATED_TOKENS = 512;
37402
37482
  var LLAMA_TEMPERATURE = 0.1;
37403
37483
 
@@ -37430,9 +37510,9 @@ function writeProvisionalDigestFile(outDir, input, markdown) {
37430
37510
  return markdownPath;
37431
37511
  }
37432
37512
  function writeConversationDigestSettings(workspaceRoot2, digest) {
37433
- const current = loadProjectSettings(workspaceRoot2);
37513
+ const current = loadGlobalSettings();
37434
37514
  const memory = current.memory ?? {};
37435
- writeProjectSettings(workspaceRoot2, {
37515
+ writeGlobalSettings({
37436
37516
  memory: {
37437
37517
  ...memory,
37438
37518
  enabled: memory.enabled ?? true,
@@ -37457,8 +37537,8 @@ var DIGEST_MODEL_REGISTRY = {
37457
37537
  sourceUrl: "https://huggingface.co/bartowski/Qwen2.5-0.5B-Instruct-GGUF",
37458
37538
  enabled: true
37459
37539
  },
37460
- ["default" /* DEFAULT */]: {
37461
- id: "default" /* DEFAULT */,
37540
+ ["base" /* BASE */]: {
37541
+ id: "base" /* BASE */,
37462
37542
  ggufUri: "hf:bartowski/Qwen2.5-1.5B-Instruct-GGUF/Qwen2.5-1.5B-Instruct-Q4_K_M.gguf",
37463
37543
  approxSizeBytes: 1e9,
37464
37544
  license: "Apache-2.0",
@@ -37535,39 +37615,19 @@ function buildPrompt(input, maxInputChars) {
37535
37615
  {
37536
37616
  role: "system",
37537
37617
  content: [
37538
- "# ROLE",
37539
- "You write short project-memory wiki pages from developer transcripts.",
37540
- "Your output is temporary recall to prevent blind steps.",
37541
- "",
37542
- "# RULES",
37543
- "1. Read the end of the export FIRST to discover the ultimate resolution.",
37544
- "2. What the user says LAST ALWAYS overrides earlier agreements.",
37545
- "3. Capture absolute engineering conclusions, never conversational pleasantries.",
37546
- "4. Return Markdown only. Do not wrap output in code blocks (```markdown ... ```)."
37618
+ "You are a technical summarizer.",
37619
+ "Focus ONLY on the final decisions made at the end of the transcript.",
37620
+ "Never invent or guess details.",
37621
+ "Keep the output concise and strictly factual."
37547
37622
  ].join("\n")
37548
37623
  },
37549
37624
  {
37550
37625
  role: "user",
37551
37626
  content: [
37552
- "Generate a high-level project memory page. STRICTLY adhere to this architectural layout:",
37553
- "",
37554
- "<layout>",
37555
- "# Digest: " + titleFromSource(input.sourcePath),
37556
- "- **Ultimate Core Resolution**: [Describe the final working state of the feature and what problem it solves in 1-2 sentences. No conversational filler.]",
37557
- "- **Architectural Changes**: [List 3-4 high-level engineering decisions made (e.g., shifts in hook timing, data isolation, atomic locks). Focus on systemic changes, NOT individual files.]",
37558
- "- **Component State**: [Briefly state how the Plugin side and the MCP/CLI side now interact based on the final decision.]",
37559
- "- **Considered & Rejected**: [List concepts proposed but discarded (e.g., initial hooks location, mocking strategies). Omit if empty.]",
37560
- "</layout>",
37561
- "",
37562
- "# CRITICAL RULES:",
37563
- "- DO NOT list individual file paths, line numbers, or specific test cases (EXCLUDE lists of files).",
37564
- "- Focus entirely on the SYSTEM DESIGN and ARCHITECTURE that the next engineer needs to know.",
37565
- "- Keep output under 300 tokens. Stop immediately after the last valid section.",
37566
- "",
37567
- "Source path: " + input.sourcePath,
37568
- '<conversation_export format="memory-kit">',
37627
+ "Transcript:",
37569
37628
  conversationExport,
37570
- "</conversation_export>"
37629
+ "",
37630
+ "Task: Write a brief summary paragraph of the final agreed-upon decisions in the transcript above. Then, provide a simple bulleted list of the specific technical changes or outcomes."
37571
37631
  ].join("\n")
37572
37632
  }
37573
37633
  ];
@@ -37630,7 +37690,18 @@ async function createLlamaLocalDigestProvider(modelId) {
37630
37690
  options.timeoutMs,
37631
37691
  () => new LlamaLocalDigestProviderError(`Llama provider timed out after ${options.timeoutMs}ms`)
37632
37692
  );
37633
- return sanitizeConversationDigestMarkdown(response);
37693
+ const metadata = `## Digest: ${titleFromSource(input.sourcePath)}
37694
+
37695
+ | Key | Value |
37696
+ | ------ | ----- |
37697
+ | **Source** | ${input.sourcePath} |
37698
+ | **Generated** | ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} |
37699
+ | **Model** | ${modelId} |
37700
+ `;
37701
+ const sanitized = sanitizeConversationDigestMarkdown(response);
37702
+ return `${metadata}
37703
+
37704
+ ${sanitized}`;
37634
37705
  } finally {
37635
37706
  await context.dispose();
37636
37707
  }
@@ -37664,19 +37735,25 @@ function discoverPendingConversationFiles(workspaceRoot2) {
37664
37735
  }
37665
37736
  });
37666
37737
  }
37667
- async function indexProvisionalDigestFile(workspaceRoot2, markdownPath) {
37738
+ async function indexProvisionalDigestFile(workspaceRoot2, markdownPath, indexer) {
37739
+ let store;
37668
37740
  try {
37669
- const settings2 = loadProjectSettings(workspaceRoot2);
37670
- const config2 = resolveMemoryConfig(settings2, workspaceRoot2);
37671
- const store = new MemoryStore(path13.join(config2.wikiDir, "index.db"), config2);
37672
- const embedder = { embed: async (_texts) => [], initialize: async () => {
37673
- } };
37674
- const indexer = new MemoryIndexer(store, embedder, config2);
37675
- await indexer.indexFile(markdownPath);
37741
+ if (indexer) {
37742
+ await indexer.indexFile(markdownPath);
37743
+ return { indexed: true };
37744
+ }
37745
+ const settings = loadGlobalSettings();
37746
+ const config2 = resolveMemoryConfig(settings, workspaceRoot2);
37747
+ if (config2.enabled !== true) return { indexed: false };
37748
+ store = new MemoryStore(path13.join(config2.wikiDir, "index.db"), config2);
37749
+ const embedder = new Embedder(config2.embeddingModel);
37750
+ await embedder.initialize();
37751
+ await new MemoryIndexer(store, embedder, config2).indexFile(markdownPath);
37676
37752
  return { indexed: true };
37677
37753
  } catch (err) {
37678
- const message = err instanceof Error ? err.message : String(err);
37679
- return { indexed: false, error: message };
37754
+ return { indexed: false, error: err instanceof Error ? err.message : String(err) };
37755
+ } finally {
37756
+ store?.close();
37680
37757
  }
37681
37758
  }
37682
37759
  async function digestConversationFile(options) {
@@ -37684,7 +37761,7 @@ async function digestConversationFile(options) {
37684
37761
  const input = readConversationDigestInput(options.workspaceRoot, options.inputPath);
37685
37762
  const existingPath = findExistingProvisionalDigest(outDir, input);
37686
37763
  if (existingPath) {
37687
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath);
37764
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, existingPath, options.indexer);
37688
37765
  return {
37689
37766
  markdown: existingPath,
37690
37767
  status: "provisional",
@@ -37701,7 +37778,7 @@ async function digestConversationFile(options) {
37701
37778
  timeoutMs: options.timeoutMs ?? DEFAULT_DIGEST_TIMEOUT_MS
37702
37779
  });
37703
37780
  const markdownPath = writeProvisionalDigestFile(outDir, input, markdown);
37704
- const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath);
37781
+ const indexResult = await indexProvisionalDigestFile(options.workspaceRoot, markdownPath, options.indexer);
37705
37782
  return {
37706
37783
  markdown: markdownPath,
37707
37784
  status: "provisional",
@@ -37718,7 +37795,7 @@ async function digestPendingConversations({
37718
37795
  digestFn = digestConversationFile,
37719
37796
  onProgress
37720
37797
  }) {
37721
- const digestConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot2));
37798
+ const digestConfig = resolveConversationDigestConfig(loadGlobalSettings());
37722
37799
  if (!digestConfig || digestConfig.enabled === false) {
37723
37800
  return { ok: true, initialized: false, action: "noop", reason: "not-initialized" };
37724
37801
  }
@@ -37726,11 +37803,21 @@ async function digestPendingConversations({
37726
37803
  if (!tryAcquireProcessLock(lockPath)) {
37727
37804
  return { ok: true, initialized: true, action: "noop", reason: "locked" };
37728
37805
  }
37806
+ let closeIndexer;
37729
37807
  try {
37730
37808
  const candidates = discoverPendingConversationFiles(workspaceRoot2);
37731
37809
  if (candidates.length === 0) {
37732
37810
  return { ok: true, initialized: true, action: "noop", reason: "no-pending" };
37733
37811
  }
37812
+ let indexer;
37813
+ const memoryConfig = resolveMemoryConfig(loadGlobalSettings(), workspaceRoot2);
37814
+ if (memoryConfig.enabled === true) {
37815
+ const store = new MemoryStore(path13.join(memoryConfig.wikiDir, "index.db"), memoryConfig);
37816
+ const embedder = new Embedder(memoryConfig.embeddingModel);
37817
+ indexer = new MemoryIndexer(store, embedder, memoryConfig);
37818
+ closeIndexer = () => store.close();
37819
+ await embedder.initialize();
37820
+ }
37734
37821
  let count = 0;
37735
37822
  let skipped = 0;
37736
37823
  let errors = 0;
@@ -37739,7 +37826,8 @@ async function digestPendingConversations({
37739
37826
  const result = await digestFn({
37740
37827
  workspaceRoot: workspaceRoot2,
37741
37828
  inputPath: filePath,
37742
- modelId: digestConfig.modelId
37829
+ modelId: digestConfig.modelId,
37830
+ indexer
37743
37831
  });
37744
37832
  if (result.skipped) {
37745
37833
  skipped++;
@@ -37756,9 +37844,17 @@ async function digestPendingConversations({
37756
37844
  return { ok: true, initialized: true, action: "digested", count, skipped, errors };
37757
37845
  } catch (err) {
37758
37846
  const message = err instanceof Error ? err.message : String(err);
37847
+ console.warn("[digest] Pending conversation digest failed:", err);
37759
37848
  return { ok: false, initialized: true, action: "error", error: message };
37760
37849
  } finally {
37761
37850
  releaseLock(lockPath);
37851
+ if (closeIndexer) {
37852
+ try {
37853
+ closeIndexer();
37854
+ } catch (err) {
37855
+ console.warn("[digest] Failed to close memory indexer store:", err);
37856
+ }
37857
+ }
37762
37858
  }
37763
37859
  }
37764
37860
  async function initializeConversationDigestModel(input) {
@@ -37800,19 +37896,18 @@ async function initializeConversationDigestModel(input) {
37800
37896
  function registerMemoryToolHandlers(server2, indexer, store, config2, workspaceRoot2 = process.cwd()) {
37801
37897
  server2.tool(
37802
37898
  "kit_memory_search",
37803
- "Search persistent memory for context relevant to the current question. Returns semantically relevant results using hybrid dense+BM25+RRF search.",
37899
+ "Search persistent memory for factual or semantic matches (decisions, concepts, entities). Extract concise keywords only, removing stopwords and filler words from the user request before querying. Use kit_memory_recent for temporal or recency questions.",
37804
37900
  {
37805
- query: external_exports.string().min(1).describe("Search query"),
37901
+ query: external_exports.string().min(1).describe("Keyword-only search query with stopwords removed"),
37806
37902
  top_k: external_exports.number().int().positive().optional().describe("Number of results to return")
37807
37903
  },
37808
37904
  async ({ query, top_k }) => {
37809
37905
  try {
37810
37906
  const results = await indexer.search(query, top_k ?? config2.topK);
37907
+ const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
37811
37908
  if (results.length === 0) {
37812
- const degradedNote2 = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
37813
- return mcpText(`${degradedNote2}No memories found for query: "${query}"`);
37909
+ return mcpText(`${degradedNote}No memories found for query: "${query}"`);
37814
37910
  }
37815
- const degradedNote = !store.vecAvailable ? "\u26A0\uFE0F Vector search unavailable \u2014 showing keyword-only results.\n\n" : "";
37816
37911
  const formatted = results.map((r) => {
37817
37912
  const displaySource = r.chunk.source.replace(/^compiled\//, "");
37818
37913
  const content = r.contentSource === "fallback" ? `\u26A0\uFE0F Source file unavailable \u2014 showing matched chunk only:
@@ -37822,8 +37917,36 @@ ${content}`;
37822
37917
  }).join("\n\n---\n\n");
37823
37918
  return mcpText(`${degradedNote}${formatted}`);
37824
37919
  } catch (err) {
37825
- const message = err instanceof Error ? err.message : String(err);
37826
- return mcpText(`kit_memory_search failed: ${message}`);
37920
+ return mcpText(`kit_memory_search failed: ${formatError2(err)}`);
37921
+ }
37922
+ }
37923
+ );
37924
+ server2.tool(
37925
+ "kit_memory_recent",
37926
+ 'Return the N most recently updated memory sources. Optionally filter by source type. Use for temporal questions like "what did we do last session" or "recent".',
37927
+ {
37928
+ n: external_exports.number().int().positive().max(50).optional().describe("How many recent sources to return (default 5)"),
37929
+ source_type: external_exports.enum(SOURCE_TYPES).optional().describe("Optional source type filter")
37930
+ },
37931
+ async ({ n, source_type }) => {
37932
+ try {
37933
+ const rows = store.getRecentSources({ limit: n ?? 5, sourceType: source_type });
37934
+ if (rows.length === 0) return mcpText("No recent sources found.");
37935
+ const blocks = [];
37936
+ for (const row of rows) {
37937
+ try {
37938
+ const content = fs11.readFileSync(path14.join(config2.wikiDir, row.source), "utf8");
37939
+ const displaySource = row.source.replace(/^compiled\//, "");
37940
+ blocks.push(`### ${displaySource}
37941
+ ${content}`);
37942
+ } catch (err) {
37943
+ console.warn(`[memory] Skipping unavailable recent source ${row.source}:`, err);
37944
+ }
37945
+ }
37946
+ if (blocks.length === 0) return mcpText("No recent sources found.");
37947
+ return mcpText(blocks.join("\n\n---\n\n"));
37948
+ } catch (err) {
37949
+ return mcpText(`kit_memory_recent failed: ${formatError2(err)}`);
37827
37950
  }
37828
37951
  }
37829
37952
  );
@@ -37842,8 +37965,10 @@ ${content}`;
37842
37965
  message: "Saved to wiki/raw \u2014 will be indexed after next /wiki compile"
37843
37966
  });
37844
37967
  } catch (err) {
37845
- const message = err instanceof Error ? err.message : String(err);
37846
- return mcpJson({ saved: false, error: message });
37968
+ return mcpJson({
37969
+ saved: false,
37970
+ error: formatError2(err)
37971
+ });
37847
37972
  }
37848
37973
  }
37849
37974
  );
@@ -37856,7 +37981,7 @@ ${content}`;
37856
37981
  },
37857
37982
  async ({ model_id, enabled }) => {
37858
37983
  try {
37859
- const existingConfig = resolveConversationDigestConfig(loadProjectSettings(workspaceRoot2));
37984
+ const existingConfig = resolveConversationDigestConfig(loadGlobalSettings());
37860
37985
  const result = await initializeConversationDigestModel({
37861
37986
  workspaceRoot: workspaceRoot2,
37862
37987
  modelId: model_id ?? existingConfig?.modelId ?? DEFAULT_DIGEST_MODEL_ID,
@@ -37870,15 +37995,15 @@ ${content}`;
37870
37995
  error: result.error
37871
37996
  });
37872
37997
  } catch (err) {
37873
- const message = err instanceof Error ? err.message : String(err);
37874
- return mcpJson({ initialized: false, error: message });
37998
+ return mcpJson({ initialized: false, error: formatError2(err) });
37875
37999
  }
37876
38000
  }
37877
38001
  );
37878
38002
  }
37879
- function registerMemoryTools(server2, settings2, workspaceRoot2, overrides) {
37880
- if (settings2.memory?.enabled !== true) return null;
37881
- const config2 = overrides?.config ?? resolveMemoryConfig(settings2, workspaceRoot2);
38003
+ function registerMemoryTools(server2, workspaceRoot2, overrides) {
38004
+ const settings = overrides?.settings ?? loadGlobalSettings();
38005
+ if (settings.memory?.enabled !== true) return null;
38006
+ const config2 = overrides?.config ?? resolveMemoryConfig(settings, workspaceRoot2);
37882
38007
  const store = overrides?.store ?? new MemoryStore(path14.join(config2.wikiDir, "index.db"), config2);
37883
38008
  const indexer = overrides?.indexer ?? (() => {
37884
38009
  const embedder = new Embedder(config2.embeddingModel);
@@ -37897,8 +38022,7 @@ registerIntegrationTools(server);
37897
38022
  registerCoreTools(server);
37898
38023
  registerAgentTools(server);
37899
38024
  var workspaceRoot = getWorkspaceRoot();
37900
- var settings = loadProjectSettings(workspaceRoot);
37901
- var memoryIndexer = registerMemoryTools(server, settings, workspaceRoot);
38025
+ var memoryIndexer = registerMemoryTools(server, workspaceRoot);
37902
38026
  var transport = new StdioServerTransport();
37903
38027
  await server.connect(transport);
37904
38028
  if (memoryIndexer) {