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/README.md +18 -1
- package/dist/cli/index.js +483 -72
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/static/app.js +143 -11
- package/dist/dashboard/static/index.html +5 -0
- package/dist/dashboard/static/style.css +197 -0
- package/dist/index.js +450 -70
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
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
|
-
|
|
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
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
|
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\`
|
|
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
|
-
|
|
757
|
-
-
|
|
758
|
-
-
|
|
759
|
-
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
- **
|
|
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:
|
|
1193
|
+
sendJson(res, { id: effectiveProjectId, name: effectiveProjectName });
|
|
977
1194
|
break;
|
|
978
1195
|
}
|
|
979
1196
|
case "/graph": {
|
|
980
|
-
const graph = await loadGraphJsonl(
|
|
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(
|
|
986
|
-
const observations2 = filterByProject(allObs,
|
|
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(
|
|
992
|
-
const allObs = await loadObservationsJson(
|
|
993
|
-
const observations2 = filterByProject(allObs,
|
|
994
|
-
const nextId2 = await loadIdCounter(
|
|
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(
|
|
1013
|
-
const observations2 = filterByProject(allObs,
|
|
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
|
|
1723
|
-
const name2 =
|
|
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
|
-
|
|
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
|
|
2403
|
-
import { homedir as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
3200
|
-
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
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) {
|