memorix 0.5.2 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -29,17 +29,138 @@ var init_esm_shims = __esm({
29
29
  });
30
30
 
31
31
  // src/store/persistence.ts
32
+ var persistence_exports = {};
33
+ __export(persistence_exports, {
34
+ getBaseDataDir: () => getBaseDataDir,
35
+ getDbFilePath: () => getDbFilePath,
36
+ getGraphFilePath: () => getGraphFilePath,
37
+ getProjectDataDir: () => getProjectDataDir,
38
+ hasExistingData: () => hasExistingData,
39
+ listProjectDirs: () => listProjectDirs,
40
+ loadGraphJsonl: () => loadGraphJsonl,
41
+ loadIdCounter: () => loadIdCounter,
42
+ loadObservationsJson: () => loadObservationsJson,
43
+ migrateGlobalData: () => migrateGlobalData,
44
+ saveGraphJsonl: () => saveGraphJsonl,
45
+ saveIdCounter: () => saveIdCounter,
46
+ saveObservationsJson: () => saveObservationsJson
47
+ });
32
48
  import { promises as fs } from "fs";
33
49
  import path2 from "path";
34
50
  import os from "os";
51
+ function sanitizeProjectId(projectId) {
52
+ return projectId.replace(/\//g, "--").replace(/[<>:"|?*\\]/g, "_");
53
+ }
35
54
  async function getProjectDataDir(projectId, baseDir) {
36
- const dataDir = baseDir ?? DEFAULT_DATA_DIR;
55
+ const base = baseDir ?? DEFAULT_DATA_DIR;
56
+ const dirName = sanitizeProjectId(projectId);
57
+ const dataDir = path2.join(base, dirName);
37
58
  await fs.mkdir(dataDir, { recursive: true });
38
59
  return dataDir;
39
60
  }
61
+ function getBaseDataDir(baseDir) {
62
+ return baseDir ?? DEFAULT_DATA_DIR;
63
+ }
64
+ async function listProjectDirs(baseDir) {
65
+ const base = baseDir ?? DEFAULT_DATA_DIR;
66
+ try {
67
+ const entries = await fs.readdir(base, { withFileTypes: true });
68
+ return entries.filter((e) => e.isDirectory()).map((e) => path2.join(base, e.name));
69
+ } catch {
70
+ return [];
71
+ }
72
+ }
73
+ async function migrateGlobalData(projectId, baseDir) {
74
+ const base = baseDir ?? DEFAULT_DATA_DIR;
75
+ const globalObsPath = path2.join(base, "observations.json");
76
+ const migratedObsPath = path2.join(base, "observations.json.migrated");
77
+ let sourceObsPath = null;
78
+ try {
79
+ await fs.access(globalObsPath);
80
+ sourceObsPath = globalObsPath;
81
+ } catch {
82
+ try {
83
+ await fs.access(migratedObsPath);
84
+ sourceObsPath = migratedObsPath;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ let globalObs = [];
90
+ try {
91
+ const data = await fs.readFile(sourceObsPath, "utf-8");
92
+ globalObs = JSON.parse(data);
93
+ if (!Array.isArray(globalObs) || globalObs.length === 0) return false;
94
+ } catch {
95
+ return false;
96
+ }
97
+ const projectDir2 = await getProjectDataDir(projectId, baseDir);
98
+ const projectObsPath = path2.join(projectDir2, "observations.json");
99
+ let projectObs = [];
100
+ try {
101
+ const data = await fs.readFile(projectObsPath, "utf-8");
102
+ projectObs = JSON.parse(data);
103
+ if (!Array.isArray(projectObs)) projectObs = [];
104
+ } catch {
105
+ }
106
+ if (projectObs.length >= globalObs.length) {
107
+ return false;
108
+ }
109
+ const existingIds = new Set(projectObs.map((o) => o.id));
110
+ const merged = [...projectObs];
111
+ for (const obs of globalObs) {
112
+ if (!existingIds.has(obs.id)) {
113
+ merged.push(obs);
114
+ }
115
+ }
116
+ merged.sort((a, b) => (a.id ?? 0) - (b.id ?? 0));
117
+ for (const obs of merged) {
118
+ obs.projectId = projectId;
119
+ }
120
+ await fs.writeFile(projectObsPath, JSON.stringify(merged, null, 2), "utf-8");
121
+ for (const file of ["graph.jsonl", "counter.json"]) {
122
+ const src = path2.join(base, file);
123
+ const srcMigrated = path2.join(base, file + ".migrated");
124
+ const dst = path2.join(projectDir2, file);
125
+ for (const source of [src, srcMigrated]) {
126
+ try {
127
+ await fs.access(source);
128
+ await fs.copyFile(source, dst);
129
+ break;
130
+ } catch {
131
+ }
132
+ }
133
+ }
134
+ const maxId = merged.reduce((max, o) => Math.max(max, o.id ?? 0), 0);
135
+ await fs.writeFile(
136
+ path2.join(projectDir2, "counter.json"),
137
+ JSON.stringify({ nextId: maxId + 1 }),
138
+ "utf-8"
139
+ );
140
+ for (const file of ["observations.json", "graph.jsonl", "counter.json"]) {
141
+ const src = path2.join(base, file);
142
+ try {
143
+ await fs.access(src);
144
+ await fs.rename(src, src + ".migrated");
145
+ } catch {
146
+ }
147
+ }
148
+ return true;
149
+ }
150
+ function getDbFilePath(projectDir2) {
151
+ return path2.join(projectDir2, "memorix.msp");
152
+ }
40
153
  function getGraphFilePath(projectDir2) {
41
154
  return path2.join(projectDir2, "graph.jsonl");
42
155
  }
156
+ async function hasExistingData(projectDir2) {
157
+ try {
158
+ await fs.access(getDbFilePath(projectDir2));
159
+ return true;
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
43
164
  async function saveGraphJsonl(projectDir2, entities, relations) {
44
165
  const lines = [
45
166
  ...entities.map(
@@ -645,6 +766,18 @@ async function detectInstalledAgents() {
645
766
  agents.push("kiro");
646
767
  } catch {
647
768
  }
769
+ const codexDir = path5.join(home, ".codex");
770
+ try {
771
+ await fs3.access(codexDir);
772
+ agents.push("codex");
773
+ } catch {
774
+ }
775
+ const antigravityDir = path5.join(home, ".gemini", "antigravity");
776
+ try {
777
+ await fs3.access(antigravityDir);
778
+ agents.push("antigravity");
779
+ } catch {
780
+ }
648
781
  return agents;
649
782
  }
650
783
  async function installHooks(agent, projectRoot, global = false) {
@@ -721,15 +854,34 @@ async function installAgentRules(agent, projectRoot) {
721
854
  case "copilot":
722
855
  rulesPath = path5.join(projectRoot, ".github", "copilot-instructions.md");
723
856
  break;
857
+ case "codex":
858
+ rulesPath = path5.join(projectRoot, "AGENTS.md");
859
+ break;
860
+ case "kiro":
861
+ rulesPath = path5.join(projectRoot, ".kiro", "rules", "memorix.md");
862
+ break;
724
863
  default:
725
- return;
864
+ rulesPath = path5.join(projectRoot, ".agent", "rules", "memorix.md");
865
+ break;
726
866
  }
727
867
  try {
728
868
  await fs3.mkdir(path5.dirname(rulesPath), { recursive: true });
729
- try {
730
- await fs3.access(rulesPath);
731
- } catch {
732
- await fs3.writeFile(rulesPath, rulesContent, "utf-8");
869
+ if (agent === "codex") {
870
+ try {
871
+ const existing = await fs3.readFile(rulesPath, "utf-8");
872
+ if (existing.includes("Memorix")) {
873
+ return;
874
+ }
875
+ await fs3.writeFile(rulesPath, existing + "\n\n" + rulesContent, "utf-8");
876
+ } catch {
877
+ await fs3.writeFile(rulesPath, rulesContent, "utf-8");
878
+ }
879
+ } else {
880
+ try {
881
+ await fs3.access(rulesPath);
882
+ } catch {
883
+ await fs3.writeFile(rulesPath, rulesContent, "utf-8");
884
+ }
733
885
  }
734
886
  } catch {
735
887
  }
@@ -743,22 +895,50 @@ You have access to Memorix memory tools. Follow these rules to maintain persiste
743
895
 
744
896
  At the **beginning of every conversation**, before responding to the user:
745
897
 
746
- 1. Call \`memorix_search\` with query related to the user's first message or the current project
747
- 2. If results are found, use them to understand the current project state, recent decisions, and pending tasks
748
- 3. Reference relevant memories naturally in your response
898
+ 1. Call \`memorix_search\` with a query related to the user's first message or the current project
899
+ 2. If results are found, use \`memorix_detail\` to fetch the most relevant ones
900
+ 3. Reference relevant memories naturally in your response \u2014 the user should feel you "remember" them
749
901
 
750
902
  This ensures you already know the project context without the user re-explaining.
751
903
 
752
904
  ## During Session \u2014 Capture Important Context
753
905
 
754
- Proactively call \`memorix_store\` when any of the following happen:
906
+ **Proactively** call \`memorix_store\` whenever any of the following happen:
907
+
908
+ ### Architecture & Decisions
909
+ - Technology choice, framework selection, or design pattern adopted
910
+ - Trade-off discussion with a clear conclusion
911
+ - API design, database schema, or project structure decisions
912
+
913
+ ### Bug Fixes & Problem Solving
914
+ - A bug is identified and resolved \u2014 store root cause + fix
915
+ - Workaround applied for a known issue
916
+ - Performance issue diagnosed and optimized
917
+
918
+ ### Gotchas & Pitfalls
919
+ - Something unexpected or tricky is discovered
920
+ - A common mistake is identified and corrected
921
+ - Platform-specific behavior that caused issues
755
922
 
756
- - **Architecture decision**: You or the user decide on a technology, pattern, or approach
757
- - **Bug fix**: A bug is identified and resolved \u2014 store the root cause and fix
758
- - **Gotcha/pitfall**: Something unexpected or tricky is discovered
759
- - **Configuration change**: Environment, port, path, or tooling changes
923
+ ### Configuration & Environment
924
+ - Environment variables, port numbers, paths changed
925
+ - Docker, nginx, Caddy, or reverse proxy config modified
926
+ - Package dependencies added, removed, or version-pinned
760
927
 
761
- Use appropriate types: \`decision\`, \`problem-solution\`, \`gotcha\`, \`what-changed\`, \`discovery\`.
928
+ ### Deployment & Operations
929
+ - Server deployment steps (Docker, VPS, cloud)
930
+ - DNS, SSL/TLS certificate, domain configuration
931
+ - CI/CD pipeline setup or changes
932
+ - Database migration or data transfer procedures
933
+ - Server topology (ports, services, reverse proxy chain)
934
+ - SSH keys, access credentials setup (store pattern, NOT secrets)
935
+
936
+ ### Project Milestones
937
+ - Feature completed or shipped
938
+ - Version released or published to npm/PyPI/etc.
939
+ - Repository made public, README updated, PR submitted
940
+
941
+ Use appropriate types: \`decision\`, \`problem-solution\`, \`gotcha\`, \`what-changed\`, \`discovery\`, \`how-it-works\`.
762
942
 
763
943
  ## Session End \u2014 Store Summary
764
944
 
@@ -766,18 +946,21 @@ When the conversation is ending or the user says goodbye:
766
946
 
767
947
  1. Call \`memorix_store\` with type \`session-request\` to record:
768
948
  - What was accomplished in this session
769
- - Current project state
949
+ - Current project state and any blockers
770
950
  - Pending tasks or next steps
771
- - Any unresolved issues
951
+ - Key files modified
772
952
 
773
- This creates a "handoff note" for the next session.
953
+ This creates a "handoff note" for the next session (or for another AI agent).
774
954
 
775
955
  ## Guidelines
776
956
 
777
- - **Don't store trivial information** (greetings, acknowledgments, simple file reads)
957
+ - **Don't store trivial information** (greetings, acknowledgments, simple file reads, ls/dir output)
778
958
  - **Do store anything you'd want to know if you lost all context**
779
- - **Use concise titles** and structured facts
959
+ - **Do store anything a different AI agent would need to continue this work**
960
+ - **Use concise titles** (~5-10 words) and structured facts
780
961
  - **Include file paths** in filesModified when relevant
962
+ - **Include related concepts** for better searchability
963
+ - **Prefer storing too much over too little** \u2014 the retention system will auto-decay stale memories
781
964
  `;
782
965
  }
783
966
  async function uninstallHooks(agent, projectRoot, global = false) {
@@ -802,7 +985,7 @@ async function uninstallHooks(agent, projectRoot, global = false) {
802
985
  }
803
986
  async function getHookStatus(projectRoot) {
804
987
  const results = [];
805
- const agents = ["claude", "copilot", "windsurf", "cursor", "kiro", "codex"];
988
+ const agents = ["claude", "copilot", "windsurf", "cursor", "kiro", "codex", "antigravity"];
806
989
  for (const agent of agents) {
807
990
  const projectPath = getProjectConfigPath(agent, projectRoot);
808
991
  const globalPath = getGlobalConfigPath(agent);
@@ -967,31 +1150,65 @@ function sendError(res, message, status = 500) {
967
1150
  function filterByProject(items, projectId) {
968
1151
  return items.filter((item) => item.projectId === projectId);
969
1152
  }
970
- async function handleApi(req, res, dataDir, projectId, projectName) {
1153
+ async function handleApi(req, res, dataDir, projectId, projectName, baseDir) {
971
1154
  const url = new URL(req.url || "/", `http://${req.headers.host}`);
972
1155
  const apiPath = url.pathname.replace("/api", "");
1156
+ const requestedProject = url.searchParams.get("project");
1157
+ let effectiveDataDir = dataDir;
1158
+ let effectiveProjectId = projectId;
1159
+ let effectiveProjectName = projectName;
1160
+ if (requestedProject && requestedProject !== projectId) {
1161
+ const sanitized = requestedProject.replace(/\//g, "--").replace(/[<>:"|?*\\]/g, "_");
1162
+ const candidateDir = path6.join(baseDir, sanitized);
1163
+ try {
1164
+ await fs4.access(candidateDir);
1165
+ effectiveDataDir = candidateDir;
1166
+ effectiveProjectId = requestedProject;
1167
+ effectiveProjectName = requestedProject.split("/").pop() || requestedProject;
1168
+ } catch {
1169
+ }
1170
+ }
973
1171
  try {
974
1172
  switch (apiPath) {
1173
+ case "/projects": {
1174
+ try {
1175
+ const entries = await fs4.readdir(baseDir, { withFileTypes: true });
1176
+ const projects = entries.filter((e) => e.isDirectory() && e.name.includes("--")).map((e) => {
1177
+ const dirName = e.name;
1178
+ const id = dirName.replace(/--/g, "/");
1179
+ return {
1180
+ id,
1181
+ name: id.split("/").pop() || id,
1182
+ dirName,
1183
+ isCurrent: id === projectId
1184
+ };
1185
+ });
1186
+ sendJson(res, projects);
1187
+ } catch {
1188
+ sendJson(res, []);
1189
+ }
1190
+ break;
1191
+ }
975
1192
  case "/project": {
976
- sendJson(res, { id: projectId, name: projectName });
1193
+ sendJson(res, { id: effectiveProjectId, name: effectiveProjectName });
977
1194
  break;
978
1195
  }
979
1196
  case "/graph": {
980
- const graph = await loadGraphJsonl(dataDir);
1197
+ const graph = await loadGraphJsonl(effectiveDataDir);
981
1198
  sendJson(res, graph);
982
1199
  break;
983
1200
  }
984
1201
  case "/observations": {
985
- const allObs = await loadObservationsJson(dataDir);
986
- const observations2 = filterByProject(allObs, projectId);
1202
+ const allObs = await loadObservationsJson(effectiveDataDir);
1203
+ const observations2 = filterByProject(allObs, effectiveProjectId);
987
1204
  sendJson(res, observations2);
988
1205
  break;
989
1206
  }
990
1207
  case "/stats": {
991
- const graph = await loadGraphJsonl(dataDir);
992
- const allObs = await loadObservationsJson(dataDir);
993
- const observations2 = filterByProject(allObs, projectId);
994
- const nextId2 = await loadIdCounter(dataDir);
1208
+ const graph = await loadGraphJsonl(effectiveDataDir);
1209
+ const allObs = await loadObservationsJson(effectiveDataDir);
1210
+ const observations2 = filterByProject(allObs, effectiveProjectId);
1211
+ const nextId2 = await loadIdCounter(effectiveDataDir);
995
1212
  const typeCounts = {};
996
1213
  for (const obs of observations2) {
997
1214
  const t = obs.type || "unknown";
@@ -1009,8 +1226,8 @@ async function handleApi(req, res, dataDir, projectId, projectName) {
1009
1226
  break;
1010
1227
  }
1011
1228
  case "/retention": {
1012
- const allObs = await loadObservationsJson(dataDir);
1013
- const observations2 = filterByProject(allObs, projectId);
1229
+ const allObs = await loadObservationsJson(effectiveDataDir);
1230
+ const observations2 = filterByProject(allObs, effectiveProjectId);
1014
1231
  const now = Date.now();
1015
1232
  const scored = observations2.map((obs) => {
1016
1233
  const age = now - new Date(obs.createdAt || now).getTime();
@@ -1044,8 +1261,42 @@ async function handleApi(req, res, dataDir, projectId, projectName) {
1044
1261
  });
1045
1262
  break;
1046
1263
  }
1047
- default:
1264
+ default: {
1265
+ const deleteMatch = apiPath.match(/^\/observations\/(\d+)$/);
1266
+ if (deleteMatch && req.method === "DELETE") {
1267
+ const obsId = parseInt(deleteMatch[1], 10);
1268
+ const allObs = await loadObservationsJson(effectiveDataDir);
1269
+ const idx = allObs.findIndex((o) => o.id === obsId);
1270
+ if (idx === -1) {
1271
+ sendError(res, "Observation not found", 404);
1272
+ } else {
1273
+ allObs.splice(idx, 1);
1274
+ await saveObservationsJson(effectiveDataDir, allObs);
1275
+ sendJson(res, { ok: true, deleted: obsId });
1276
+ }
1277
+ break;
1278
+ }
1279
+ if (apiPath === "/export") {
1280
+ const graph = await loadGraphJsonl(effectiveDataDir);
1281
+ const allObs = await loadObservationsJson(effectiveDataDir);
1282
+ const observations2 = filterByProject(allObs, effectiveProjectId);
1283
+ const nextId2 = await loadIdCounter(effectiveDataDir);
1284
+ const exportData = {
1285
+ project: { id: effectiveProjectId, name: effectiveProjectName },
1286
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1287
+ graph,
1288
+ observations: observations2,
1289
+ nextId: nextId2
1290
+ };
1291
+ res.writeHead(200, {
1292
+ "Content-Type": "application/json",
1293
+ "Content-Disposition": `attachment; filename="memorix-${effectiveProjectId.replace(/\//g, "-")}-export.json"`
1294
+ });
1295
+ res.end(JSON.stringify(exportData, null, 2));
1296
+ break;
1297
+ }
1048
1298
  sendError(res, "Not found", 404);
1299
+ }
1049
1300
  }
1050
1301
  } catch (err) {
1051
1302
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -1087,10 +1338,11 @@ function openBrowser(url) {
1087
1338
  }
1088
1339
  async function startDashboard(dataDir, port, staticDir, projectId, projectName, autoOpen = true) {
1089
1340
  const resolvedStaticDir = staticDir;
1341
+ const baseDir = getBaseDataDir();
1090
1342
  const server = createServer(async (req, res) => {
1091
1343
  const url = req.url || "/";
1092
1344
  if (url.startsWith("/api/")) {
1093
- await handleApi(req, res, dataDir, projectId, projectName);
1345
+ await handleApi(req, res, dataDir, projectId, projectName, baseDir);
1094
1346
  } else {
1095
1347
  await serveStatic(req, res, resolvedStaticDir);
1096
1348
  }
@@ -1719,12 +1971,14 @@ function detectProject(cwd) {
1719
1971
  const rootPath = getGitRoot(basePath) ?? findPackageRoot(basePath) ?? basePath;
1720
1972
  const gitRemote = getGitRemote(rootPath);
1721
1973
  if (gitRemote) {
1722
- const id = normalizeGitRemote(gitRemote);
1723
- const name2 = id.split("/").pop() ?? path3.basename(rootPath);
1724
- return { id, name: name2, gitRemote, rootPath };
1974
+ const id2 = normalizeGitRemote(gitRemote);
1975
+ const name2 = id2.split("/").pop() ?? path3.basename(rootPath);
1976
+ return { id: id2, name: name2, gitRemote, rootPath };
1725
1977
  }
1726
1978
  const name = path3.basename(rootPath);
1727
- return { id: name, name, rootPath };
1979
+ const id = `local/${name}`;
1980
+ console.error(`[memorix] Warning: no git remote found at ${rootPath}, using fallback projectId: ${id}`);
1981
+ return { id, name, rootPath };
1728
1982
  }
1729
1983
  function findPackageRoot(cwd) {
1730
1984
  let dir = path3.resolve(cwd);
@@ -2244,6 +2498,69 @@ var CopilotAdapter = class {
2244
2498
  }
2245
2499
  };
2246
2500
 
2501
+ // src/rules/adapters/kiro.ts
2502
+ init_esm_shims();
2503
+ import matter7 from "gray-matter";
2504
+ var KiroAdapter = class {
2505
+ source = "kiro";
2506
+ filePatterns = [
2507
+ ".kiro/steering/*.md",
2508
+ "AGENTS.md"
2509
+ ];
2510
+ parse(filePath, content) {
2511
+ if (filePath.includes(".kiro/steering/")) {
2512
+ return this.parseSteeringRule(filePath, content);
2513
+ }
2514
+ if (filePath.endsWith("AGENTS.md")) {
2515
+ return this.parseAgentsMd(filePath, content);
2516
+ }
2517
+ return [];
2518
+ }
2519
+ generate(rules) {
2520
+ return rules.map((rule, i) => {
2521
+ const fm = {};
2522
+ if (rule.description) fm.description = rule.description;
2523
+ const fileName = rule.id.replace(/^kiro:/, "").replace(/[^a-zA-Z0-9-_]/g, "-") || `rule-${i}`;
2524
+ const body = Object.keys(fm).length > 0 ? matter7.stringify(rule.content, fm) : rule.content;
2525
+ return {
2526
+ filePath: `.kiro/steering/${fileName}.md`,
2527
+ content: body
2528
+ };
2529
+ });
2530
+ }
2531
+ parseSteeringRule(filePath, content) {
2532
+ const { data, content: body } = matter7(content);
2533
+ const trimmed = body.trim();
2534
+ if (!trimmed) return [];
2535
+ const trigger = data.trigger;
2536
+ const alwaysApply = !trigger || trigger === "always";
2537
+ return [{
2538
+ id: generateRuleId("kiro", filePath),
2539
+ content: trimmed,
2540
+ description: data.description,
2541
+ source: "kiro",
2542
+ scope: alwaysApply ? "global" : "path-specific",
2543
+ paths: data.globs,
2544
+ alwaysApply,
2545
+ priority: alwaysApply ? 10 : 5,
2546
+ hash: hashContent(trimmed)
2547
+ }];
2548
+ }
2549
+ parseAgentsMd(filePath, content) {
2550
+ const trimmed = content.trim();
2551
+ if (!trimmed) return [];
2552
+ return [{
2553
+ id: generateRuleId("kiro", filePath),
2554
+ content: trimmed,
2555
+ source: "kiro",
2556
+ scope: "project",
2557
+ alwaysApply: true,
2558
+ priority: 10,
2559
+ hash: hashContent(trimmed)
2560
+ }];
2561
+ }
2562
+ };
2563
+
2247
2564
  // src/rules/syncer.ts
2248
2565
  var RulesSyncer = class {
2249
2566
  projectRoot;
@@ -2257,7 +2574,8 @@ var RulesSyncer = class {
2257
2574
  new CodexAdapter(),
2258
2575
  new WindsurfAdapter(),
2259
2576
  new AntigravityAdapter(),
2260
- new CopilotAdapter()
2577
+ new CopilotAdapter(),
2578
+ new KiroAdapter()
2261
2579
  ];
2262
2580
  for (const a of all) {
2263
2581
  this.adapters.set(a.source, a);
@@ -2399,8 +2717,8 @@ var RulesSyncer = class {
2399
2717
  // src/workspace/engine.ts
2400
2718
  init_esm_shims();
2401
2719
  import { readFileSync as readFileSync2, readdirSync, existsSync as existsSync4, cpSync, mkdirSync as mkdirSync2 } from "fs";
2402
- import { join as join8 } from "path";
2403
- import { homedir as homedir7 } from "os";
2720
+ import { join as join9 } from "path";
2721
+ import { homedir as homedir8 } from "os";
2404
2722
 
2405
2723
  // src/workspace/mcp-adapters/windsurf.ts
2406
2724
  init_esm_shims();
@@ -2834,9 +3152,55 @@ var AntigravityMCPAdapter = class {
2834
3152
  }
2835
3153
  };
2836
3154
 
3155
+ // src/workspace/mcp-adapters/kiro.ts
3156
+ init_esm_shims();
3157
+ import { homedir as homedir7 } from "os";
3158
+ import { join as join7 } from "path";
3159
+ var KiroMCPAdapter = class {
3160
+ source = "kiro";
3161
+ parse(content) {
3162
+ try {
3163
+ const config = JSON.parse(content);
3164
+ const servers = config.mcpServers ?? {};
3165
+ return Object.entries(servers).map(([name, entry]) => ({
3166
+ name,
3167
+ command: entry.command ?? "",
3168
+ args: entry.args ?? [],
3169
+ ...entry.env && Object.keys(entry.env).length > 0 ? { env: entry.env } : {},
3170
+ ...entry.url ? { url: entry.url } : {}
3171
+ }));
3172
+ } catch {
3173
+ return [];
3174
+ }
3175
+ }
3176
+ generate(servers) {
3177
+ const mcpServers = {};
3178
+ for (const s of servers) {
3179
+ const entry = {};
3180
+ if (s.url) {
3181
+ entry.url = s.url;
3182
+ } else {
3183
+ entry.command = s.command;
3184
+ entry.args = s.args;
3185
+ }
3186
+ if (s.env && Object.keys(s.env).length > 0) {
3187
+ entry.env = s.env;
3188
+ }
3189
+ mcpServers[s.name] = entry;
3190
+ }
3191
+ return JSON.stringify({ mcpServers }, null, 2);
3192
+ }
3193
+ getConfigPath(projectRoot) {
3194
+ if (projectRoot) {
3195
+ return join7(projectRoot, ".kiro", "settings", "mcp.json");
3196
+ }
3197
+ return join7(homedir7(), ".kiro", "settings", "mcp.json");
3198
+ }
3199
+ };
3200
+
2837
3201
  // src/workspace/workflow-sync.ts
2838
3202
  init_esm_shims();
2839
- import matter7 from "gray-matter";
3203
+ import matter8 from "gray-matter";
2840
3204
  var WorkflowSyncer = class {
2841
3205
  /**
2842
3206
  * Parse a Windsurf workflow markdown file into a WorkflowEntry.
@@ -2846,7 +3210,7 @@ var WorkflowSyncer = class {
2846
3210
  let description = "";
2847
3211
  let content = raw;
2848
3212
  try {
2849
- const parsed = matter7(raw);
3213
+ const parsed = matter8(raw);
2850
3214
  description = parsed.data?.description ?? "";
2851
3215
  content = parsed.content.trim();
2852
3216
  } catch {
@@ -2868,7 +3232,7 @@ var WorkflowSyncer = class {
2868
3232
  if (wf.description) {
2869
3233
  fm.description = wf.description;
2870
3234
  }
2871
- const content = matter7.stringify(wf.content, fm);
3235
+ const content = matter8.stringify(wf.content, fm);
2872
3236
  return {
2873
3237
  filePath: `.agents/skills/${safeName}/SKILL.md`,
2874
3238
  content
@@ -2885,7 +3249,7 @@ var WorkflowSyncer = class {
2885
3249
  }
2886
3250
  fm.globs = "";
2887
3251
  fm.alwaysApply = "false";
2888
- const content = matter7.stringify(wf.content, fm);
3252
+ const content = matter8.stringify(wf.content, fm);
2889
3253
  return {
2890
3254
  filePath: `.cursor/rules/${safeName}.mdc`,
2891
3255
  content
@@ -3067,7 +3431,8 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3067
3431
  ["codex", new CodexMCPAdapter()],
3068
3432
  ["claude-code", new ClaudeCodeMCPAdapter()],
3069
3433
  ["copilot", new CopilotMCPAdapter()],
3070
- ["antigravity", new AntigravityMCPAdapter()]
3434
+ ["antigravity", new AntigravityMCPAdapter()],
3435
+ ["kiro", new KiroMCPAdapter()]
3071
3436
  ]);
3072
3437
  this.workflowSyncer = new WorkflowSyncer();
3073
3438
  this.rulesSyncer = new RulesSyncer(projectRoot);
@@ -3085,7 +3450,8 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3085
3450
  codex: [],
3086
3451
  "claude-code": [],
3087
3452
  copilot: [],
3088
- antigravity: []
3453
+ antigravity: [],
3454
+ kiro: []
3089
3455
  };
3090
3456
  for (const [target, adapter] of this.adapters) {
3091
3457
  const configPath = adapter.getConfigPath(this.projectRoot);
@@ -3177,13 +3543,14 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3177
3543
  windsurf: [".windsurf/skills"],
3178
3544
  "claude-code": [".claude/skills"],
3179
3545
  copilot: [".github/skills", ".copilot/skills"],
3180
- antigravity: [".agent/skills", ".gemini/skills", ".gemini/antigravity/skills"]
3546
+ antigravity: [".agent/skills", ".gemini/skills", ".gemini/antigravity/skills"],
3547
+ kiro: [".kiro/skills"]
3181
3548
  };
3182
3549
  /** Get the target skills directory for an agent (null if agent has no skills support) */
3183
3550
  getTargetSkillsDir(target) {
3184
3551
  const dirs = _WorkspaceSyncEngine.SKILLS_DIRS[target];
3185
3552
  if (!dirs || dirs.length === 0) return null;
3186
- return join8(this.projectRoot, dirs[0]);
3553
+ return join9(this.projectRoot, dirs[0]);
3187
3554
  }
3188
3555
  /**
3189
3556
  * Scan all agent skills directories and collect unique skills.
@@ -3192,12 +3559,12 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3192
3559
  const skills = [];
3193
3560
  const conflicts = [];
3194
3561
  const seen = /* @__PURE__ */ new Map();
3195
- const home = homedir7();
3562
+ const home = homedir8();
3196
3563
  for (const [agent, dirs] of Object.entries(_WorkspaceSyncEngine.SKILLS_DIRS)) {
3197
3564
  for (const dir of dirs) {
3198
3565
  const paths = [
3199
- join8(this.projectRoot, dir),
3200
- join8(home, dir)
3566
+ join9(this.projectRoot, dir),
3567
+ join9(home, dir)
3201
3568
  ];
3202
3569
  for (const skillsRoot of paths) {
3203
3570
  if (!existsSync4(skillsRoot)) continue;
@@ -3205,7 +3572,7 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3205
3572
  const entries = readdirSync(skillsRoot, { withFileTypes: true });
3206
3573
  for (const entry of entries) {
3207
3574
  if (!entry.isDirectory()) continue;
3208
- const skillMd = join8(skillsRoot, entry.name, "SKILL.md");
3575
+ const skillMd = join9(skillsRoot, entry.name, "SKILL.md");
3209
3576
  if (!existsSync4(skillMd)) continue;
3210
3577
  let description = "";
3211
3578
  try {
@@ -3217,7 +3584,7 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3217
3584
  const newEntry = {
3218
3585
  name: entry.name,
3219
3586
  description,
3220
- sourcePath: join8(skillsRoot, entry.name),
3587
+ sourcePath: join9(skillsRoot, entry.name),
3221
3588
  sourceAgent: agent
3222
3589
  };
3223
3590
  const existing = seen.get(entry.name);
@@ -3254,7 +3621,7 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3254
3621
  }
3255
3622
  for (const skill of skills) {
3256
3623
  if (skill.sourceAgent === target) continue;
3257
- const dest = join8(targetDir, skill.name);
3624
+ const dest = join9(targetDir, skill.name);
3258
3625
  if (existsSync4(dest)) {
3259
3626
  skipped.push(`${skill.name} (already exists in ${target})`);
3260
3627
  continue;
@@ -3270,13 +3637,13 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3270
3637
  }
3271
3638
  scanWorkflows() {
3272
3639
  const workflows = [];
3273
- const wfDir = join8(this.projectRoot, ".windsurf", "workflows");
3640
+ const wfDir = join9(this.projectRoot, ".windsurf", "workflows");
3274
3641
  if (!existsSync4(wfDir)) return workflows;
3275
3642
  try {
3276
3643
  const files = readdirSync(wfDir).filter((f) => f.endsWith(".md"));
3277
3644
  for (const file of files) {
3278
3645
  try {
3279
- const content = readFileSync2(join8(wfDir, file), "utf-8");
3646
+ const content = readFileSync2(join9(wfDir, file), "utf-8");
3280
3647
  workflows.push(this.workflowSyncer.parseWindsurfWorkflow(file, content));
3281
3648
  } catch {
3282
3649
  }
@@ -3364,7 +3731,8 @@ var WorkspaceSyncEngine = class _WorkspaceSyncEngine {
3364
3731
  codex: "codex",
3365
3732
  windsurf: "windsurf",
3366
3733
  copilot: "copilot",
3367
- antigravity: "antigravity"
3734
+ antigravity: "antigravity",
3735
+ kiro: "kiro"
3368
3736
  };
3369
3737
  return map[target] ?? null;
3370
3738
  }
@@ -3384,6 +3752,14 @@ var OBSERVATION_TYPES = [
3384
3752
  ];
3385
3753
  async function createMemorixServer(cwd) {
3386
3754
  const project = detectProject(cwd);
3755
+ try {
3756
+ const { migrateGlobalData: migrateGlobalData2 } = await Promise.resolve().then(() => (init_persistence(), persistence_exports));
3757
+ const migrated = await migrateGlobalData2(project.id);
3758
+ if (migrated) {
3759
+ console.error(`[memorix] Migrated legacy data to project directory: ${project.id}`);
3760
+ }
3761
+ } catch {
3762
+ }
3387
3763
  const projectDir2 = await getProjectDataDir(project.id);
3388
3764
  const graphManager = new KnowledgeGraphManager(projectDir2);
3389
3765
  await graphManager.init();
@@ -3398,15 +3774,14 @@ async function createMemorixServer(cwd) {
3398
3774
  const { getHookStatus: getHookStatus2, installHooks: installHooks2, detectInstalledAgents: detectInstalledAgents2 } = await Promise.resolve().then(() => (init_installers(), installers_exports));
3399
3775
  const workDir = cwd ?? process.cwd();
3400
3776
  const statuses = await getHookStatus2(workDir);
3401
- const anyInstalled = statuses.some((s) => s.installed);
3402
- if (!anyInstalled) {
3403
- const agents = await detectInstalledAgents2();
3404
- for (const agent of agents) {
3405
- try {
3406
- const config = await installHooks2(agent, workDir);
3407
- console.error(`[memorix] Auto-installed hooks for ${agent} \u2192 ${config.configPath}`);
3408
- } catch {
3409
- }
3777
+ const installedAgents = new Set(statuses.filter((s) => s.installed).map((s) => s.agent));
3778
+ const detectedAgents = await detectInstalledAgents2();
3779
+ for (const agent of detectedAgents) {
3780
+ if (installedAgents.has(agent)) continue;
3781
+ try {
3782
+ const config = await installHooks2(agent, workDir);
3783
+ console.error(`[memorix] Auto-installed hooks for ${agent} \u2192 ${config.configPath}`);
3784
+ } catch {
3410
3785
  }
3411
3786
  }
3412
3787
  } catch {
@@ -3543,15 +3918,20 @@ Entity: ${entityName} | Type: ${type} | Project: ${project.id}${enrichment}`
3543
3918
  query: z.string().describe("Search query (natural language or keywords)"),
3544
3919
  limit: z.number().optional().describe("Max results (default: 20)"),
3545
3920
  type: z.enum(OBSERVATION_TYPES).optional().describe("Filter by observation type"),
3546
- maxTokens: z.number().optional().describe("Token budget \u2014 trim results to fit (0 = unlimited)")
3921
+ maxTokens: z.number().optional().describe("Token budget \u2014 trim results to fit (0 = unlimited)"),
3922
+ scope: z.enum(["project", "global"]).optional().describe(
3923
+ 'Search scope: "project" (default) only searches current project, "global" searches all projects'
3924
+ )
3547
3925
  }
3548
3926
  },
3549
- async ({ query, limit, type, maxTokens }) => {
3927
+ async ({ query, limit, type, maxTokens, scope }) => {
3550
3928
  const result = await compactSearch({
3551
3929
  query,
3552
3930
  limit,
3553
3931
  type,
3554
- maxTokens
3932
+ maxTokens,
3933
+ // Default to current project scope; 'global' removes the project filter
3934
+ projectId: scope === "global" ? void 0 : project.id
3555
3935
  });
3556
3936
  let text = result.formatted;
3557
3937
  if (!syncAdvisoryShown && syncAdvisory) {