remem-mcp 0.6.8 → 0.7.3
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 +67 -68
- package/dist/{chunk-X4ECGLYH.js → chunk-4WCDAEPP.js} +897 -826
- package/dist/chunk-4WCDAEPP.js.map +1 -0
- package/dist/chunk-ITMR6THY.js +859 -0
- package/dist/chunk-ITMR6THY.js.map +1 -0
- package/dist/{chunk-XK4Y53J2.js → chunk-ONQHLUGM.js} +1 -8
- package/dist/{chunk-XK4Y53J2.js.map → chunk-ONQHLUGM.js.map} +1 -1
- package/dist/chunk-XJGDYQ34.js +22 -0
- package/dist/chunk-XJGDYQ34.js.map +1 -0
- package/dist/{chunk-FA324IBN.js → chunk-YU7WBJU5.js} +24 -19
- package/dist/chunk-YU7WBJU5.js.map +1 -0
- package/dist/{engine-LVKGZHBY.js → engine-S47HHGAO.js} +4 -3
- package/dist/index.js +1817 -383
- package/dist/index.js.map +1 -1
- package/dist/{resolver-AVK7XKX7.js → resolver-UHU4MBQX.js} +53 -20
- package/dist/resolver-UHU4MBQX.js.map +1 -0
- package/dist/sdk.js +8 -5
- package/dist/server-5PWQTRGZ.js +517 -0
- package/dist/server-5PWQTRGZ.js.map +1 -0
- package/dist/storage/schema.sql +112 -3
- package/package.json +1 -1
- package/scripts/verify-codegraph-stats.mjs +122 -0
- package/skills/remem-mcp/SKILL.md +44 -3
- package/dist/chunk-FA324IBN.js.map +0 -1
- package/dist/chunk-X4ECGLYH.js.map +0 -1
- package/dist/resolver-AVK7XKX7.js.map +0 -1
- /package/dist/{engine-LVKGZHBY.js.map → engine-S47HHGAO.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,45 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
LocalEmbedder,
|
|
4
3
|
Memory,
|
|
5
|
-
SQLiteBackend,
|
|
6
4
|
classifyGlobalContent,
|
|
7
5
|
findOutdatedPages,
|
|
8
6
|
getWikiPage,
|
|
9
7
|
ingestDirectory,
|
|
10
8
|
ingestFile,
|
|
11
|
-
init_local,
|
|
12
|
-
local_exports,
|
|
13
9
|
redact,
|
|
14
|
-
searchWiki
|
|
15
|
-
|
|
16
|
-
} from "./chunk-X4ECGLYH.js";
|
|
10
|
+
searchWiki
|
|
11
|
+
} from "./chunk-ITMR6THY.js";
|
|
17
12
|
import {
|
|
18
13
|
engine_exports,
|
|
19
14
|
findCallees,
|
|
20
15
|
findCallers,
|
|
21
|
-
generateId,
|
|
22
16
|
impactAnalysis,
|
|
23
17
|
indexDirectory,
|
|
24
18
|
indexFile,
|
|
25
19
|
init_engine,
|
|
26
|
-
init_ulid,
|
|
27
20
|
listSymbols,
|
|
28
21
|
searchSymbols
|
|
29
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-YU7WBJU5.js";
|
|
23
|
+
import {
|
|
24
|
+
LocalEmbedder,
|
|
25
|
+
SQLiteBackend,
|
|
26
|
+
init_local,
|
|
27
|
+
local_exports,
|
|
28
|
+
stripQueryProperNouns
|
|
29
|
+
} from "./chunk-4WCDAEPP.js";
|
|
30
|
+
import {
|
|
31
|
+
generateId,
|
|
32
|
+
init_ulid
|
|
33
|
+
} from "./chunk-XJGDYQ34.js";
|
|
30
34
|
import {
|
|
31
35
|
__dirname,
|
|
32
|
-
__require,
|
|
33
36
|
__toCommonJS,
|
|
34
37
|
init_esm_shims
|
|
35
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-ONQHLUGM.js";
|
|
36
39
|
|
|
37
40
|
// src/index.ts
|
|
38
41
|
init_esm_shims();
|
|
39
42
|
import { createHash as createHash6 } from "crypto";
|
|
40
|
-
import { existsSync as
|
|
41
|
-
import { homedir as
|
|
42
|
-
import { dirname as dirname10, join as
|
|
43
|
+
import { existsSync as existsSync16, readdirSync as readdirSync2, readFileSync as readFileSync13, statSync as statSync3 } from "fs";
|
|
44
|
+
import { homedir as homedir13 } from "os";
|
|
45
|
+
import { dirname as dirname10, join as join16 } from "path";
|
|
43
46
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
44
47
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
45
48
|
import Database13 from "better-sqlite3";
|
|
@@ -47,6 +50,7 @@ import * as sqliteVec4 from "sqlite-vec";
|
|
|
47
50
|
|
|
48
51
|
// src/artifact.ts
|
|
49
52
|
init_esm_shims();
|
|
53
|
+
init_ulid();
|
|
50
54
|
import { createHash } from "crypto";
|
|
51
55
|
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "fs";
|
|
52
56
|
import { dirname, join } from "path";
|
|
@@ -167,8 +171,8 @@ function importArtifact(dbPath, projectRoot) {
|
|
|
167
171
|
const transaction = db.transaction(() => {
|
|
168
172
|
for (const row of rows) {
|
|
169
173
|
const result = insertStmt.run(
|
|
170
|
-
row.id,
|
|
171
|
-
row.session_key,
|
|
174
|
+
row.id ?? generateId(),
|
|
175
|
+
row.session_key ?? "import",
|
|
172
176
|
row.agent_id,
|
|
173
177
|
row.type,
|
|
174
178
|
row.content,
|
|
@@ -190,7 +194,7 @@ function importArtifact(dbPath, projectRoot) {
|
|
|
190
194
|
transaction();
|
|
191
195
|
db.close();
|
|
192
196
|
if (inserted > 0) {
|
|
193
|
-
console.
|
|
197
|
+
console.error(
|
|
194
198
|
`[remem-mcp] Imported ${inserted} captures from team artifact (${skipped} already exist).`
|
|
195
199
|
);
|
|
196
200
|
}
|
|
@@ -718,6 +722,9 @@ async function demoCodegraph() {
|
|
|
718
722
|
const distDir = dirname3(fileURLToPath2(import.meta.url));
|
|
719
723
|
const indexPath = join3(distDir, "index.js");
|
|
720
724
|
const cleanDb = new Database2(dbPath);
|
|
725
|
+
cleanDb.exec(`CREATE TABLE IF NOT EXISTS symbols (id TEXT PRIMARY KEY, repo_path TEXT NOT NULL, file_path TEXT NOT NULL, name TEXT NOT NULL, kind TEXT NOT NULL, line INTEGER, end_line INTEGER, created_at INTEGER NOT NULL)`);
|
|
726
|
+
cleanDb.exec(`CREATE TABLE IF NOT EXISTS calls (id TEXT PRIMARY KEY, repo_path TEXT NOT NULL, caller_id TEXT NOT NULL, callee_name TEXT NOT NULL, callee_id TEXT, call_line INTEGER, created_at INTEGER NOT NULL)`);
|
|
727
|
+
cleanDb.exec(`CREATE TABLE IF NOT EXISTS imports (id TEXT PRIMARY KEY, repo_path TEXT NOT NULL, file_path TEXT NOT NULL, module_path TEXT NOT NULL, imported_names TEXT, created_at INTEGER NOT NULL)`);
|
|
721
728
|
cleanDb.exec("DELETE FROM calls");
|
|
722
729
|
cleanDb.exec("DELETE FROM symbols");
|
|
723
730
|
cleanDb.exec("DELETE FROM imports");
|
|
@@ -1004,7 +1011,7 @@ function extractRuleBasedFacts(content, type2) {
|
|
|
1004
1011
|
if (fact.length > 10) facts.push({ text: fact, confidence: 0.85 });
|
|
1005
1012
|
}
|
|
1006
1013
|
const useMatch = text.match(/use\s+\S+\s+for\s+\S+/i);
|
|
1007
|
-
if (useMatch && facts.length < 3) {
|
|
1014
|
+
if (useMatch && useMatch.index !== void 0 && facts.length < 3) {
|
|
1008
1015
|
const fact = text.slice(useMatch.index, useMatch.index + useMatch[0].length);
|
|
1009
1016
|
if (fact.length > 10) facts.push({ text: fact, confidence: 0.8 });
|
|
1010
1017
|
}
|
|
@@ -1125,7 +1132,9 @@ async function extractCommand(dbPath, flags) {
|
|
|
1125
1132
|
captures = entry ? [entry] : [];
|
|
1126
1133
|
} else {
|
|
1127
1134
|
const allCaptures = await storage.listAll(limit, 0);
|
|
1128
|
-
captures = allCaptures.filter(
|
|
1135
|
+
captures = allCaptures.filter(
|
|
1136
|
+
(e) => ["decision", "learning", "error", "conversation"].includes(e.type)
|
|
1137
|
+
);
|
|
1129
1138
|
}
|
|
1130
1139
|
if (captures.length === 0) {
|
|
1131
1140
|
console.log("No captures to extract atoms from.");
|
|
@@ -1175,8 +1184,10 @@ async function extractCommand(dbPath, flags) {
|
|
|
1175
1184
|
console.error(` ${capture.id}: FAILED \u2014 ${err}`);
|
|
1176
1185
|
}
|
|
1177
1186
|
}
|
|
1178
|
-
console.log(
|
|
1179
|
-
|
|
1187
|
+
console.log(
|
|
1188
|
+
`
|
|
1189
|
+
Done. Extracted ${totalAtoms} atom(s) from ${captures.length - skipped} capture(s).`
|
|
1190
|
+
);
|
|
1180
1191
|
if (skipped > 0) console.log(`${skipped} capture(s) already had atoms (skipped).`);
|
|
1181
1192
|
if (errors2 > 0) console.log(`${errors2} capture(s) failed.`);
|
|
1182
1193
|
} finally {
|
|
@@ -1282,7 +1293,9 @@ async function personaCommand(dbPath, flags) {
|
|
|
1282
1293
|
}
|
|
1283
1294
|
const db = new Database3(dbPath, { readonly: true });
|
|
1284
1295
|
try {
|
|
1285
|
-
const row = db.prepare(
|
|
1296
|
+
const row = db.prepare(
|
|
1297
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? ORDER BY updated_at DESC LIMIT 1"
|
|
1298
|
+
).get(teamId, userId);
|
|
1286
1299
|
if (!row || !row.content) {
|
|
1287
1300
|
console.log('No persona set. Use --set "trait: value" to add one.');
|
|
1288
1301
|
return;
|
|
@@ -1521,7 +1534,7 @@ async function runPipelineWorker(opts, embedder) {
|
|
|
1521
1534
|
type: cap.type,
|
|
1522
1535
|
tags: cap.tags ? JSON.parse(cap.tags) : [],
|
|
1523
1536
|
sessionKey: cap.session_key,
|
|
1524
|
-
teamId: cap.team_id,
|
|
1537
|
+
teamId: cap.team_id ?? void 0,
|
|
1525
1538
|
userId: cap.user_id ?? void 0
|
|
1526
1539
|
};
|
|
1527
1540
|
const output2 = await pipeline.process(input, ctx);
|
|
@@ -1542,9 +1555,7 @@ async function runPipelineWorker(opts, embedder) {
|
|
|
1542
1555
|
groups.get(key).push(a);
|
|
1543
1556
|
}
|
|
1544
1557
|
const existingScenarios = await storage.listScenarios({ limit: 100 });
|
|
1545
|
-
const existingTopics = new Set(
|
|
1546
|
-
existingScenarios.flatMap((s) => s.personaTags ?? [])
|
|
1547
|
-
);
|
|
1558
|
+
const existingTopics = new Set(existingScenarios.flatMap((s) => s.personaTags ?? []));
|
|
1548
1559
|
for (const [topic, groupAtoms] of groups) {
|
|
1549
1560
|
if (groupAtoms.length < consolidateThreshold) continue;
|
|
1550
1561
|
if (existingTopics.has(topic)) continue;
|
|
@@ -1675,7 +1686,7 @@ function loadConfig() {
|
|
|
1675
1686
|
model: model ?? "gpt-4o-mini"
|
|
1676
1687
|
} : void 0;
|
|
1677
1688
|
const fileSecurity = file?.security ?? {};
|
|
1678
|
-
|
|
1689
|
+
const cfg = {
|
|
1679
1690
|
storage: env.REMEM_STORAGE ?? file?.storage ?? "sqlite",
|
|
1680
1691
|
pipeline: env.REMEM_PIPELINE ?? file?.pipeline ?? "noop",
|
|
1681
1692
|
dbPath: env.REMEM_DB_PATH ?? file?.dbPath ?? defaultDbPath4,
|
|
@@ -1699,8 +1710,20 @@ function loadConfig() {
|
|
|
1699
1710
|
fileSecurity.maxContentLength ?? 5e4
|
|
1700
1711
|
),
|
|
1701
1712
|
auditLog: parseBool(env.REMEM_AUDIT_LOG, fileSecurity.auditLog ?? true)
|
|
1702
|
-
}
|
|
1713
|
+
},
|
|
1714
|
+
offloadEnabled: parseBool(env.REMEM_OFFLOAD_ENABLED, false)
|
|
1703
1715
|
};
|
|
1716
|
+
if (env.REMEM_FLOW === "full") {
|
|
1717
|
+
if (!env.REMEM_OFFLOAD_ENABLED) {
|
|
1718
|
+
process.env.REMEM_OFFLOAD_ENABLED = "true";
|
|
1719
|
+
cfg.offloadEnabled = true;
|
|
1720
|
+
}
|
|
1721
|
+
if (!env.REMEM_PIPELINE) {
|
|
1722
|
+
process.env.REMEM_PIPELINE = "skill";
|
|
1723
|
+
cfg.pipeline = "skill";
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return cfg;
|
|
1704
1727
|
}
|
|
1705
1728
|
|
|
1706
1729
|
// src/doctor.ts
|
|
@@ -1861,7 +1884,7 @@ function errors(dbPath = defaultDbPath2()) {
|
|
|
1861
1884
|
try {
|
|
1862
1885
|
db = new Database5(dbPath, { readonly: true });
|
|
1863
1886
|
} catch {
|
|
1864
|
-
console.
|
|
1887
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
1865
1888
|
process.exit(1);
|
|
1866
1889
|
}
|
|
1867
1890
|
console.log("remem-mcp errors \u2014 Error Learning Dashboard\n");
|
|
@@ -1980,7 +2003,7 @@ function errorsRetro(dbPath = defaultDbPath2()) {
|
|
|
1980
2003
|
try {
|
|
1981
2004
|
db = new Database5(dbPath, { readonly: true });
|
|
1982
2005
|
} catch {
|
|
1983
|
-
console.
|
|
2006
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
1984
2007
|
process.exit(1);
|
|
1985
2008
|
}
|
|
1986
2009
|
console.log("remem-mcp errors retro \u2014 Session Retrospective\n");
|
|
@@ -2335,7 +2358,7 @@ function errorsDrift(dbPath = defaultDbPath2()) {
|
|
|
2335
2358
|
try {
|
|
2336
2359
|
db = new Database5(dbPath, { readonly: true });
|
|
2337
2360
|
} catch {
|
|
2338
|
-
console.
|
|
2361
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
2339
2362
|
process.exit(1);
|
|
2340
2363
|
}
|
|
2341
2364
|
console.log("remem-mcp errors drift \u2014 Drift Detection Report\n");
|
|
@@ -2446,7 +2469,7 @@ function errorsLineage(dbPath = defaultDbPath2()) {
|
|
|
2446
2469
|
try {
|
|
2447
2470
|
db = new Database5(dbPath, { readonly: true });
|
|
2448
2471
|
} catch {
|
|
2449
|
-
console.
|
|
2472
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
2450
2473
|
process.exit(1);
|
|
2451
2474
|
}
|
|
2452
2475
|
console.log("remem-mcp errors lineage \u2014 Fix Lineage Chains\n");
|
|
@@ -2615,7 +2638,7 @@ function errorsByGoal(dbPath = defaultDbPath2()) {
|
|
|
2615
2638
|
try {
|
|
2616
2639
|
db = new Database5(dbPath, { readonly: true });
|
|
2617
2640
|
} catch {
|
|
2618
|
-
console.
|
|
2641
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
2619
2642
|
process.exit(1);
|
|
2620
2643
|
}
|
|
2621
2644
|
console.log("remem-mcp errors by-goal \u2014 Goal-Linked Error Report\n");
|
|
@@ -2672,7 +2695,7 @@ function errorsActions(dbPath = defaultDbPath2()) {
|
|
|
2672
2695
|
try {
|
|
2673
2696
|
db = new Database5(dbPath, { readonly: true });
|
|
2674
2697
|
} catch {
|
|
2675
|
-
console.
|
|
2698
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
2676
2699
|
process.exit(1);
|
|
2677
2700
|
}
|
|
2678
2701
|
console.log("remem-mcp errors actions \u2014 Action Item Tracker\n");
|
|
@@ -2793,7 +2816,7 @@ function errorsSeverity(dbPath = defaultDbPath2()) {
|
|
|
2793
2816
|
try {
|
|
2794
2817
|
db = new Database5(dbPath, { readonly: true });
|
|
2795
2818
|
} catch {
|
|
2796
|
-
console.
|
|
2819
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
2797
2820
|
process.exit(1);
|
|
2798
2821
|
}
|
|
2799
2822
|
console.log("remem-mcp errors severity \u2014 Impact Classification\n");
|
|
@@ -2904,7 +2927,7 @@ function errorsTemplates(dbPath = defaultDbPath2()) {
|
|
|
2904
2927
|
try {
|
|
2905
2928
|
db = new Database5(dbPath, { readonly: true });
|
|
2906
2929
|
} catch {
|
|
2907
|
-
console.
|
|
2930
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
2908
2931
|
process.exit(1);
|
|
2909
2932
|
}
|
|
2910
2933
|
console.log("remem-mcp errors templates \u2014 Fix Template Extraction\n");
|
|
@@ -2976,7 +2999,7 @@ function errorsCorrelations(dbPath = defaultDbPath2()) {
|
|
|
2976
2999
|
try {
|
|
2977
3000
|
db = new Database5(dbPath, { readonly: true });
|
|
2978
3001
|
} catch {
|
|
2979
|
-
console.
|
|
3002
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
2980
3003
|
process.exit(1);
|
|
2981
3004
|
}
|
|
2982
3005
|
console.log("remem-mcp errors correlations \u2014 Sequential Error Patterns\n");
|
|
@@ -3057,7 +3080,7 @@ function errorsPlaybooks(dbPath = defaultDbPath2()) {
|
|
|
3057
3080
|
try {
|
|
3058
3081
|
db = new Database5(dbPath, { readonly: true });
|
|
3059
3082
|
} catch {
|
|
3060
|
-
console.
|
|
3083
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3061
3084
|
process.exit(1);
|
|
3062
3085
|
}
|
|
3063
3086
|
console.log("remem-mcp errors playbooks \u2014 Recovery Pattern Library\n");
|
|
@@ -3125,7 +3148,7 @@ function errorsStale(dbPath = defaultDbPath2()) {
|
|
|
3125
3148
|
try {
|
|
3126
3149
|
db = new Database5(dbPath, { readonly: true });
|
|
3127
3150
|
} catch {
|
|
3128
|
-
console.
|
|
3151
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3129
3152
|
process.exit(1);
|
|
3130
3153
|
}
|
|
3131
3154
|
console.log("remem-mcp errors stale \u2014 Fix Staleness Report\n");
|
|
@@ -3203,7 +3226,7 @@ function errorsEscalations(dbPath = defaultDbPath2()) {
|
|
|
3203
3226
|
try {
|
|
3204
3227
|
db = new Database5(dbPath, { readonly: true });
|
|
3205
3228
|
} catch {
|
|
3206
|
-
console.
|
|
3229
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3207
3230
|
process.exit(1);
|
|
3208
3231
|
}
|
|
3209
3232
|
console.log("remem-mcp errors escalations \u2014 Escalation Policy Report\n");
|
|
@@ -3297,7 +3320,7 @@ function errorsContext(dbPath = defaultDbPath2()) {
|
|
|
3297
3320
|
try {
|
|
3298
3321
|
db = new Database5(dbPath, { readonly: true });
|
|
3299
3322
|
} catch {
|
|
3300
|
-
console.
|
|
3323
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3301
3324
|
process.exit(1);
|
|
3302
3325
|
}
|
|
3303
3326
|
console.log("remem-mcp errors context \u2014 Error Context Enrichment\n");
|
|
@@ -3371,7 +3394,7 @@ function errorsInherited(dbPath = defaultDbPath2()) {
|
|
|
3371
3394
|
try {
|
|
3372
3395
|
db = new Database5(dbPath, { readonly: true });
|
|
3373
3396
|
} catch {
|
|
3374
|
-
console.
|
|
3397
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3375
3398
|
process.exit(1);
|
|
3376
3399
|
}
|
|
3377
3400
|
console.log("remem-mcp errors inherited \u2014 Cross-Project Fix Inheritance\n");
|
|
@@ -3444,7 +3467,7 @@ function errorsProvenance(dbPath = defaultDbPath2()) {
|
|
|
3444
3467
|
try {
|
|
3445
3468
|
db = new Database5(dbPath, { readonly: true });
|
|
3446
3469
|
} catch {
|
|
3447
|
-
console.
|
|
3470
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3448
3471
|
process.exit(1);
|
|
3449
3472
|
}
|
|
3450
3473
|
console.log("remem-mcp errors provenance \u2014 Fix Provenance Chain\n");
|
|
@@ -3520,7 +3543,7 @@ function errorsPersona(dbPath = defaultDbPath2()) {
|
|
|
3520
3543
|
try {
|
|
3521
3544
|
db = new Database5(dbPath, { readonly: true });
|
|
3522
3545
|
} catch {
|
|
3523
|
-
console.
|
|
3546
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3524
3547
|
process.exit(1);
|
|
3525
3548
|
}
|
|
3526
3549
|
console.log("remem-mcp errors persona \u2014 Error Profile per Project\n");
|
|
@@ -3640,7 +3663,7 @@ function decisionsDashboard(dbPath = defaultDbPath2()) {
|
|
|
3640
3663
|
try {
|
|
3641
3664
|
db = new Database5(dbPath, { readonly: true });
|
|
3642
3665
|
} catch {
|
|
3643
|
-
console.
|
|
3666
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3644
3667
|
process.exit(1);
|
|
3645
3668
|
}
|
|
3646
3669
|
console.log("remem-mcp decisions \u2014 Decision Learning Dashboard\n");
|
|
@@ -3707,7 +3730,7 @@ function decisionsRetro(dbPath = defaultDbPath2()) {
|
|
|
3707
3730
|
try {
|
|
3708
3731
|
db = new Database5(dbPath, { readonly: true });
|
|
3709
3732
|
} catch {
|
|
3710
|
-
console.
|
|
3733
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3711
3734
|
process.exit(1);
|
|
3712
3735
|
}
|
|
3713
3736
|
console.log("remem-mcp decisions retro \u2014 Decision Retrospective\n");
|
|
@@ -3768,7 +3791,7 @@ function patternsDashboard(dbPath = defaultDbPath2()) {
|
|
|
3768
3791
|
try {
|
|
3769
3792
|
db = new Database5(dbPath, { readonly: true });
|
|
3770
3793
|
} catch {
|
|
3771
|
-
console.
|
|
3794
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3772
3795
|
process.exit(1);
|
|
3773
3796
|
}
|
|
3774
3797
|
console.log("remem-mcp patterns \u2014 Pattern Learning Dashboard\n");
|
|
@@ -3843,7 +3866,7 @@ function patternsRetro(dbPath = defaultDbPath2()) {
|
|
|
3843
3866
|
try {
|
|
3844
3867
|
db = new Database5(dbPath, { readonly: true });
|
|
3845
3868
|
} catch {
|
|
3846
|
-
console.
|
|
3869
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3847
3870
|
process.exit(1);
|
|
3848
3871
|
}
|
|
3849
3872
|
console.log("remem-mcp patterns retro \u2014 Pattern Retrospective\n");
|
|
@@ -3897,7 +3920,7 @@ function decisionsConflicts(dbPath = defaultDbPath2()) {
|
|
|
3897
3920
|
try {
|
|
3898
3921
|
db = new Database5(dbPath, { readonly: true });
|
|
3899
3922
|
} catch {
|
|
3900
|
-
console.
|
|
3923
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3901
3924
|
process.exit(1);
|
|
3902
3925
|
}
|
|
3903
3926
|
console.log("remem-mcp decisions conflicts \u2014 Decision Conflict Report\n");
|
|
@@ -3941,7 +3964,7 @@ function decisionsInherited(dbPath = defaultDbPath2()) {
|
|
|
3941
3964
|
try {
|
|
3942
3965
|
db = new Database5(dbPath, { readonly: true });
|
|
3943
3966
|
} catch {
|
|
3944
|
-
console.
|
|
3967
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3945
3968
|
process.exit(1);
|
|
3946
3969
|
}
|
|
3947
3970
|
console.log("remem-mcp decisions inherited \u2014 Cross-Project Decision Inheritance\n");
|
|
@@ -3984,7 +4007,7 @@ function patternsConflicts(dbPath = defaultDbPath2()) {
|
|
|
3984
4007
|
try {
|
|
3985
4008
|
db = new Database5(dbPath, { readonly: true });
|
|
3986
4009
|
} catch {
|
|
3987
|
-
console.
|
|
4010
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
3988
4011
|
process.exit(1);
|
|
3989
4012
|
}
|
|
3990
4013
|
console.log("remem-mcp patterns conflicts \u2014 Pattern Conflict Report\n");
|
|
@@ -4029,7 +4052,7 @@ function patternsTemplates(dbPath = defaultDbPath2()) {
|
|
|
4029
4052
|
try {
|
|
4030
4053
|
db = new Database5(dbPath, { readonly: true });
|
|
4031
4054
|
} catch {
|
|
4032
|
-
console.
|
|
4055
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
4033
4056
|
process.exit(1);
|
|
4034
4057
|
}
|
|
4035
4058
|
console.log("remem-mcp patterns templates \u2014 Pattern Template Extraction\n");
|
|
@@ -4081,7 +4104,7 @@ function patternsInherited(dbPath = defaultDbPath2()) {
|
|
|
4081
4104
|
try {
|
|
4082
4105
|
db = new Database5(dbPath, { readonly: true });
|
|
4083
4106
|
} catch {
|
|
4084
|
-
console.
|
|
4107
|
+
console.log("No database found. Run `remem-mcp setup` to get started.");
|
|
4085
4108
|
process.exit(1);
|
|
4086
4109
|
}
|
|
4087
4110
|
console.log("remem-mcp patterns inherited \u2014 Cross-Project Pattern Inheritance\n");
|
|
@@ -4273,11 +4296,58 @@ function exportMarkdown(dbPath, outputPath, filters) {
|
|
|
4273
4296
|
init_esm_shims();
|
|
4274
4297
|
import { execSync as execSync2, spawn as spawn2 } from "child_process";
|
|
4275
4298
|
import { createHash as createHash3 } from "crypto";
|
|
4276
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
4277
|
-
import { homedir as
|
|
4278
|
-
import { dirname as dirname4, join as
|
|
4299
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
4300
|
+
import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
|
|
4301
|
+
import { dirname as dirname4, join as join9 } from "path";
|
|
4279
4302
|
import Database8 from "better-sqlite3";
|
|
4303
|
+
|
|
4304
|
+
// src/storage/refs.ts
|
|
4305
|
+
init_esm_shims();
|
|
4306
|
+
import {
|
|
4307
|
+
existsSync as existsSync7,
|
|
4308
|
+
mkdirSync as mkdirSync4,
|
|
4309
|
+
readdirSync,
|
|
4310
|
+
readFileSync as readFileSync5,
|
|
4311
|
+
rmSync as rmSync2,
|
|
4312
|
+
statSync as statSync2,
|
|
4313
|
+
writeFileSync as writeFileSync4
|
|
4314
|
+
} from "fs";
|
|
4315
|
+
import { homedir as homedir6 } from "os";
|
|
4316
|
+
import { join as join8 } from "path";
|
|
4317
|
+
function defaultRefsDir() {
|
|
4318
|
+
const xdgData = process.env.XDG_DATA_HOME;
|
|
4319
|
+
if (xdgData) return join8(xdgData, "remem-mcp", "refs");
|
|
4320
|
+
return join8(homedir6(), ".local", "share", "remem-mcp", "refs");
|
|
4321
|
+
}
|
|
4322
|
+
function getRefsDir() {
|
|
4323
|
+
return process.env.REMEM_REFS_DIR ?? defaultRefsDir();
|
|
4324
|
+
}
|
|
4325
|
+
function getSessionRefsDir(sessionKey) {
|
|
4326
|
+
return join8(getRefsDir(), sessionKey);
|
|
4327
|
+
}
|
|
4328
|
+
function sanitizeSessionKey(sessionKey) {
|
|
4329
|
+
return sessionKey.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4330
|
+
}
|
|
4331
|
+
function sanitizeNodeId(nodeId) {
|
|
4332
|
+
return nodeId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
4333
|
+
}
|
|
4334
|
+
function writeRef(sessionKey, nodeId, content) {
|
|
4335
|
+
const dir = getSessionRefsDir(sanitizeSessionKey(sessionKey));
|
|
4336
|
+
if (!existsSync7(dir)) mkdirSync4(dir, { recursive: true });
|
|
4337
|
+
const filePath = join8(dir, `${sanitizeNodeId(nodeId)}.md`);
|
|
4338
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
4339
|
+
return filePath;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
// src/hook-handlers.ts
|
|
4343
|
+
init_ulid();
|
|
4280
4344
|
var SHOW_FEEDBACK = process.env.REMEM_QUIET !== "1";
|
|
4345
|
+
function isOffloadEnabled() {
|
|
4346
|
+
return process.env.REMEM_OFFLOAD_ENABLED === "true" || process.env.REMEM_OFFLOAD_ENABLED === "1" || process.env.REMEM_FLOW === "full";
|
|
4347
|
+
}
|
|
4348
|
+
function isSkillPipelineEnabled() {
|
|
4349
|
+
return process.env.REMEM_PIPELINE === "skill" || process.env.REMEM_FLOW === "full";
|
|
4350
|
+
}
|
|
4281
4351
|
function feedback(emoji, message) {
|
|
4282
4352
|
if (SHOW_FEEDBACK) {
|
|
4283
4353
|
process.stderr.write(`
|
|
@@ -4287,12 +4357,12 @@ function feedback(emoji, message) {
|
|
|
4287
4357
|
}
|
|
4288
4358
|
}
|
|
4289
4359
|
function defaultLogPath() {
|
|
4290
|
-
return process.env.REMEM_HOOK_LOG_PATH ??
|
|
4360
|
+
return process.env.REMEM_HOOK_LOG_PATH ?? join9(homedir7(), ".local", "share", "remem-mcp", "session.log");
|
|
4291
4361
|
}
|
|
4292
4362
|
function logToFile(text) {
|
|
4293
4363
|
try {
|
|
4294
4364
|
const logPath = defaultLogPath();
|
|
4295
|
-
|
|
4365
|
+
mkdirSync5(dirname4(logPath), { recursive: true });
|
|
4296
4366
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
4297
4367
|
appendFileSync2(logPath, `[${ts}] ${text}
|
|
4298
4368
|
`);
|
|
@@ -4309,11 +4379,11 @@ function safeParseTags(raw) {
|
|
|
4309
4379
|
}
|
|
4310
4380
|
}
|
|
4311
4381
|
function driftFilePath(sessionKey) {
|
|
4312
|
-
return
|
|
4382
|
+
return join9(tmpdir2(), `remem-drift-${sessionKey}.jsonl`);
|
|
4313
4383
|
}
|
|
4314
4384
|
function logInjectionDrift(sessionKey, captureType, captureId, command) {
|
|
4315
4385
|
try {
|
|
4316
|
-
const path =
|
|
4386
|
+
const path = join9(tmpdir2(), `remem-drift-${sessionKey}.jsonl`);
|
|
4317
4387
|
const record = JSON.stringify({
|
|
4318
4388
|
type: captureType,
|
|
4319
4389
|
content_hash: captureId,
|
|
@@ -4472,8 +4542,8 @@ function logDriftInjection(sessionKey, contentHash, errorId, command) {
|
|
|
4472
4542
|
function checkDriftInjection(sessionKey, contentHash) {
|
|
4473
4543
|
try {
|
|
4474
4544
|
const path = driftFilePath(sessionKey);
|
|
4475
|
-
if (!
|
|
4476
|
-
const content =
|
|
4545
|
+
if (!existsSync8(path)) return null;
|
|
4546
|
+
const content = readFileSync6(path, "utf-8");
|
|
4477
4547
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
4478
4548
|
const now = Date.now();
|
|
4479
4549
|
const maxAge = 30 * 60 * 1e3;
|
|
@@ -4492,7 +4562,7 @@ function checkDriftInjection(sessionKey, contentHash) {
|
|
|
4492
4562
|
}
|
|
4493
4563
|
}
|
|
4494
4564
|
if (recent.length !== lines.length) {
|
|
4495
|
-
|
|
4565
|
+
writeFileSync5(path, recent.join("\n") + (recent.length > 0 ? "\n" : ""));
|
|
4496
4566
|
}
|
|
4497
4567
|
return match;
|
|
4498
4568
|
} catch {
|
|
@@ -4502,8 +4572,8 @@ function checkDriftInjection(sessionKey, contentHash) {
|
|
|
4502
4572
|
function checkAllDriftInjections(sessionKey) {
|
|
4503
4573
|
try {
|
|
4504
4574
|
const path = driftFilePath(sessionKey);
|
|
4505
|
-
if (!
|
|
4506
|
-
const content =
|
|
4575
|
+
if (!existsSync8(path)) return [];
|
|
4576
|
+
const content = readFileSync6(path, "utf-8");
|
|
4507
4577
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
4508
4578
|
const now = Date.now();
|
|
4509
4579
|
const maxAge = 30 * 60 * 1e3;
|
|
@@ -4520,7 +4590,7 @@ function checkAllDriftInjections(sessionKey) {
|
|
|
4520
4590
|
}
|
|
4521
4591
|
}
|
|
4522
4592
|
if (recent.length !== lines.length) {
|
|
4523
|
-
|
|
4593
|
+
writeFileSync5(path, recent.join("\n") + (recent.length > 0 ? "\n" : ""));
|
|
4524
4594
|
}
|
|
4525
4595
|
return results;
|
|
4526
4596
|
} catch {
|
|
@@ -4595,7 +4665,9 @@ function hookRecall(dbPath) {
|
|
|
4595
4665
|
lines.push(`- (${row.type}${tagStr}) ${date}: ${content}`);
|
|
4596
4666
|
}
|
|
4597
4667
|
try {
|
|
4598
|
-
const personaRow = db.prepare(
|
|
4668
|
+
const personaRow = db.prepare(
|
|
4669
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? ORDER BY updated_at DESC LIMIT 1"
|
|
4670
|
+
).get("default", "default");
|
|
4599
4671
|
if (personaRow && personaRow.content) {
|
|
4600
4672
|
lines.push("");
|
|
4601
4673
|
lines.push(`## Persona (L3)`);
|
|
@@ -4662,11 +4734,20 @@ function extractUpdateFact(prompt2) {
|
|
|
4662
4734
|
if (p.length < 15) return null;
|
|
4663
4735
|
const updatePatterns = [
|
|
4664
4736
|
// "remember that ..." / "nhớ là ..." / "đừng quên ..."
|
|
4665
|
-
{
|
|
4737
|
+
{
|
|
4738
|
+
re: /^(?:remember that|remember|don't forget|dont forget|note that|for future reference)\s*:?\s*(.+)/i,
|
|
4739
|
+
type: "learning"
|
|
4740
|
+
},
|
|
4666
4741
|
{ re: /^(?:nhớ là|nhớ rằng|đừng quên|ghi nhớ)\s*:?\s*(.+)/i, type: "learning" },
|
|
4667
4742
|
// "we use/chose/decided ..." / "mình dùng/chọn/quyết định ..."
|
|
4668
|
-
{
|
|
4669
|
-
|
|
4743
|
+
{
|
|
4744
|
+
re: /^(?:we use|we chose|we decided|we prefer|we always|our convention|our standard)\s*:?\s*(.+)/i,
|
|
4745
|
+
type: "decision"
|
|
4746
|
+
},
|
|
4747
|
+
{
|
|
4748
|
+
re: /^(?:mình dùng|mình chọn|mình quyết định|chúng ta dùng|chúng ta chọn)\s*:?\s*(.+)/i,
|
|
4749
|
+
type: "decision"
|
|
4750
|
+
},
|
|
4670
4751
|
// "update X to Y" / "change X to Y" / "sửa X thành Y"
|
|
4671
4752
|
{ re: /^(?:update|change|set|configure)\s+.+\s+(?:to|as|=\s*)\s*(.+)/i, type: "decision" },
|
|
4672
4753
|
{ re: /^(?:sửa|đổi|cập nhật)\s+.+\s+(?:thành|sang|=\s*)\s*(.+)/i, type: "decision" },
|
|
@@ -4729,6 +4810,7 @@ function hookUserPromptSubmit(dbPath) {
|
|
|
4729
4810
|
let db;
|
|
4730
4811
|
try {
|
|
4731
4812
|
db = new Database8(dbPath);
|
|
4813
|
+
ensureSchema2(db);
|
|
4732
4814
|
} catch {
|
|
4733
4815
|
try {
|
|
4734
4816
|
db = new Database8(dbPath, { readonly: true });
|
|
@@ -4846,7 +4928,9 @@ ${scenarioLines.join("\n")}`;
|
|
|
4846
4928
|
} catch {
|
|
4847
4929
|
}
|
|
4848
4930
|
try {
|
|
4849
|
-
const personaRow = db.prepare(
|
|
4931
|
+
const personaRow = db.prepare(
|
|
4932
|
+
"SELECT content FROM persona WHERE team_id = ? AND user_id = ? ORDER BY updated_at DESC LIMIT 1"
|
|
4933
|
+
).get("default", "default");
|
|
4850
4934
|
if (personaRow && personaRow.content) {
|
|
4851
4935
|
if (recallContext) {
|
|
4852
4936
|
recallContext += `
|
|
@@ -4863,7 +4947,9 @@ ${personaRow.content}`;
|
|
|
4863
4947
|
try {
|
|
4864
4948
|
const skills = db.prepare("SELECT name, description FROM skills ORDER BY updated_at DESC LIMIT 3").all();
|
|
4865
4949
|
if (skills.length > 0) {
|
|
4866
|
-
const skillLines = skills.map(
|
|
4950
|
+
const skillLines = skills.map(
|
|
4951
|
+
(s) => `- ${s.name}: ${(s.description || "").slice(0, 80)}`
|
|
4952
|
+
);
|
|
4867
4953
|
if (recallContext) {
|
|
4868
4954
|
recallContext += `
|
|
4869
4955
|
|
|
@@ -4941,9 +5027,12 @@ function hookStop(dbPath) {
|
|
|
4941
5027
|
if (dbPath && input.session_id) {
|
|
4942
5028
|
const sid = input.session_id;
|
|
4943
5029
|
const tpath = input.transcript_path ?? null;
|
|
4944
|
-
if (tpath &&
|
|
5030
|
+
if (tpath && existsSync8(tpath)) {
|
|
4945
5031
|
void captureSessionTranscript(dbPath, sid, tpath, input.cwd).then((capId) => {
|
|
4946
5032
|
logToFile(`Stop: direct capture for session ${sid}, id=${capId ?? "skipped"}`);
|
|
5033
|
+
if (capId && isSkillPipelineEnabled()) {
|
|
5034
|
+
void extractSkillFromCapture(dbPath, capId).then(() => logToFile(`Stop: skill extraction done for ${capId}`)).catch((err) => logToFile(`Stop: skill extraction error - ${err}`));
|
|
5035
|
+
}
|
|
4947
5036
|
}).catch((err) => {
|
|
4948
5037
|
logToFile(`Stop: capture error - ${err}`);
|
|
4949
5038
|
});
|
|
@@ -4958,11 +5047,10 @@ function hookStop(dbPath) {
|
|
|
4958
5047
|
logToFile(`Stop: spawned background capture for session ${sid}`);
|
|
4959
5048
|
}
|
|
4960
5049
|
const scriptPath = process.argv[1];
|
|
4961
|
-
const workerChild = spawn2(
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
);
|
|
5050
|
+
const workerChild = spawn2(process.execPath, [scriptPath, "worker-run", dbPath], {
|
|
5051
|
+
detached: true,
|
|
5052
|
+
stdio: "ignore"
|
|
5053
|
+
});
|
|
4966
5054
|
workerChild.unref();
|
|
4967
5055
|
logToFile(`Stop: spawned background pipeline worker for session ${sid}`);
|
|
4968
5056
|
}
|
|
@@ -4972,9 +5060,9 @@ function hookStop(dbPath) {
|
|
|
4972
5060
|
}
|
|
4973
5061
|
const isTrivial = (() => {
|
|
4974
5062
|
const tpath = input.transcript_path;
|
|
4975
|
-
if (!tpath || !
|
|
5063
|
+
if (!tpath || !existsSync8(tpath)) return false;
|
|
4976
5064
|
try {
|
|
4977
|
-
const lines =
|
|
5065
|
+
const lines = readFileSync6(tpath, "utf-8").trim().split("\n");
|
|
4978
5066
|
let userCount = 0;
|
|
4979
5067
|
let hasEdit = false;
|
|
4980
5068
|
for (const line2 of lines) {
|
|
@@ -5031,9 +5119,23 @@ function hookPostToolUse(dbPath) {
|
|
|
5031
5119
|
const isBash = toolName === "Bash" || toolName === "exec";
|
|
5032
5120
|
const isWriteEdit = toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit";
|
|
5033
5121
|
if (!isBash && !isWriteEdit) {
|
|
5122
|
+
if (isOffloadEnabled()) {
|
|
5123
|
+
try {
|
|
5124
|
+
offloadToolOutput(dbPath, toolName, toolInput, toolResponse, input.cwd);
|
|
5125
|
+
} catch (e) {
|
|
5126
|
+
logToFile(`PostToolUse: offload failed: ${e}`);
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5034
5129
|
process.stdout.write(JSON.stringify({}));
|
|
5035
5130
|
return;
|
|
5036
5131
|
}
|
|
5132
|
+
if (isOffloadEnabled()) {
|
|
5133
|
+
try {
|
|
5134
|
+
offloadToolOutput(dbPath, toolName, toolInput, toolResponse, input.cwd);
|
|
5135
|
+
} catch (e) {
|
|
5136
|
+
logToFile(`PostToolUse: offload failed: ${e}`);
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5037
5139
|
if (isWriteEdit) {
|
|
5038
5140
|
const writeEditCwd = input.cwd ?? toolInput.workdir ?? process.cwd();
|
|
5039
5141
|
const ignorePatterns = loadCaptureExclusions(writeEditCwd);
|
|
@@ -5048,6 +5150,7 @@ function hookPostToolUse(dbPath) {
|
|
|
5048
5150
|
}
|
|
5049
5151
|
try {
|
|
5050
5152
|
const db2 = new Database8(dbPath);
|
|
5153
|
+
ensureSchema2(db2);
|
|
5051
5154
|
const sessionKey2 = hashPath(writeEditCwd);
|
|
5052
5155
|
const pattern = detectPattern(toolName, toolInput);
|
|
5053
5156
|
if (pattern) {
|
|
@@ -5149,6 +5252,7 @@ function hookPostToolUse(dbPath) {
|
|
|
5149
5252
|
const driftRecords = checkAllDriftInjections(sessionKey2);
|
|
5150
5253
|
if (driftRecords.length > 0 && pattern) {
|
|
5151
5254
|
const driftDb = new Database8(dbPath);
|
|
5255
|
+
ensureSchema2(driftDb);
|
|
5152
5256
|
for (const dr of driftRecords) {
|
|
5153
5257
|
if (dr.type === "pattern") {
|
|
5154
5258
|
const injectedPat = driftDb.prepare("SELECT id, metadata FROM captures WHERE id = ?").get(dr.content_hash);
|
|
@@ -5206,6 +5310,7 @@ function hookPostToolUse(dbPath) {
|
|
|
5206
5310
|
let db;
|
|
5207
5311
|
try {
|
|
5208
5312
|
db = new Database8(dbPath);
|
|
5313
|
+
ensureSchema2(db);
|
|
5209
5314
|
} catch {
|
|
5210
5315
|
process.stdout.write(JSON.stringify({}));
|
|
5211
5316
|
return;
|
|
@@ -5588,6 +5693,7 @@ Error (${errorType}): ${normalizedError}`;
|
|
|
5588
5693
|
logToFile(`PostToolUse: auto-captured ${errorType} error. id=${id}`);
|
|
5589
5694
|
try {
|
|
5590
5695
|
const corrDb = new Database8(dbPath);
|
|
5696
|
+
ensureSchema2(corrDb);
|
|
5591
5697
|
const recentErrors = corrDb.prepare(
|
|
5592
5698
|
`SELECT id, json_extract(metadata, '$.error_type') as etype, json_extract(metadata, '$.title') as title
|
|
5593
5699
|
FROM captures
|
|
@@ -5661,6 +5767,7 @@ function hookPreToolUse(dbPath) {
|
|
|
5661
5767
|
});
|
|
5662
5768
|
process.stdin.on("end", () => {
|
|
5663
5769
|
clearTimeout(stdinTimeout);
|
|
5770
|
+
const contextBlocks = [];
|
|
5664
5771
|
try {
|
|
5665
5772
|
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
5666
5773
|
if (!raw.trim()) {
|
|
@@ -5671,17 +5778,28 @@ function hookPreToolUse(dbPath) {
|
|
|
5671
5778
|
const toolName = input.tool_name ?? "";
|
|
5672
5779
|
const toolInput = input.tool_input ?? {};
|
|
5673
5780
|
const command = toolInput.command ?? "";
|
|
5781
|
+
if (isOffloadEnabled()) {
|
|
5782
|
+
try {
|
|
5783
|
+
const canvasContext = getCanvasContext(dbPath, input.cwd ?? process.cwd());
|
|
5784
|
+
if (canvasContext) {
|
|
5785
|
+
contextBlocks.push(canvasContext);
|
|
5786
|
+
}
|
|
5787
|
+
} catch (e) {
|
|
5788
|
+
logToFile(`PreToolUse: canvas injection failed: ${e}`);
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
try {
|
|
5792
|
+
const skillContext = getSkillContext(dbPath, command || toolName);
|
|
5793
|
+
if (skillContext) {
|
|
5794
|
+
contextBlocks.push(skillContext);
|
|
5795
|
+
}
|
|
5796
|
+
} catch (e) {
|
|
5797
|
+
logToFile(`PreToolUse: skill injection failed: ${e}`);
|
|
5798
|
+
}
|
|
5674
5799
|
const dangerWarning = checkDangerousCommand(command);
|
|
5675
5800
|
if (dangerWarning) {
|
|
5676
|
-
|
|
5677
|
-
hookSpecificOutput: {
|
|
5678
|
-
hookEventName: "PreToolUse",
|
|
5679
|
-
additionalContext: dangerWarning
|
|
5680
|
-
}
|
|
5681
|
-
};
|
|
5801
|
+
contextBlocks.push(dangerWarning);
|
|
5682
5802
|
logToFile(`PreToolUse: DANGER warning for: ${command.slice(0, 80)}`);
|
|
5683
|
-
process.stdout.write(JSON.stringify(output3));
|
|
5684
|
-
return;
|
|
5685
5803
|
}
|
|
5686
5804
|
if (toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
|
|
5687
5805
|
const filePath = toolInput.file_path ?? toolInput.path ?? "";
|
|
@@ -5824,12 +5942,29 @@ function hookPreToolUse(dbPath) {
|
|
|
5824
5942
|
} catch {
|
|
5825
5943
|
}
|
|
5826
5944
|
}
|
|
5827
|
-
|
|
5945
|
+
if (contextBlocks.length > 0) {
|
|
5946
|
+
process.stdout.write(JSON.stringify({
|
|
5947
|
+
hookSpecificOutput: {
|
|
5948
|
+
hookEventName: "PreToolUse",
|
|
5949
|
+
additionalContext: contextBlocks.join("\n\n")
|
|
5950
|
+
}
|
|
5951
|
+
}));
|
|
5952
|
+
} else {
|
|
5953
|
+
process.stdout.write(JSON.stringify({}));
|
|
5954
|
+
}
|
|
5828
5955
|
return;
|
|
5829
5956
|
}
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5957
|
+
if (!isRelevantBuildCommand(command) && !isDecisionLearningCommand(command)) {
|
|
5958
|
+
if (contextBlocks.length > 0) {
|
|
5959
|
+
process.stdout.write(JSON.stringify({
|
|
5960
|
+
hookSpecificOutput: {
|
|
5961
|
+
hookEventName: "PreToolUse",
|
|
5962
|
+
additionalContext: contextBlocks.join("\n\n")
|
|
5963
|
+
}
|
|
5964
|
+
}));
|
|
5965
|
+
} else {
|
|
5966
|
+
process.stdout.write(JSON.stringify({}));
|
|
5967
|
+
}
|
|
5833
5968
|
return;
|
|
5834
5969
|
}
|
|
5835
5970
|
const cwd = input.cwd ?? toolInput.workdir ?? process.cwd();
|
|
@@ -5838,7 +5973,16 @@ function hookPreToolUse(dbPath) {
|
|
|
5838
5973
|
try {
|
|
5839
5974
|
db = new Database8(dbPath, { readonly: true });
|
|
5840
5975
|
} catch {
|
|
5841
|
-
|
|
5976
|
+
if (contextBlocks.length > 0) {
|
|
5977
|
+
process.stdout.write(JSON.stringify({
|
|
5978
|
+
hookSpecificOutput: {
|
|
5979
|
+
hookEventName: "PreToolUse",
|
|
5980
|
+
additionalContext: contextBlocks.join("\n\n")
|
|
5981
|
+
}
|
|
5982
|
+
}));
|
|
5983
|
+
} else {
|
|
5984
|
+
process.stdout.write(JSON.stringify({}));
|
|
5985
|
+
}
|
|
5842
5986
|
return;
|
|
5843
5987
|
}
|
|
5844
5988
|
const globalErrors = process.env.REMEM_GLOBAL_ERRORS === "1";
|
|
@@ -5887,7 +6031,8 @@ function hookPreToolUse(dbPath) {
|
|
|
5887
6031
|
provenance: m.fix_provenance ?? "auto_captured",
|
|
5888
6032
|
inherited: r.session_key !== sessionKey,
|
|
5889
6033
|
rollbackPlan: m.rollback_plan ?? null,
|
|
5890
|
-
autoNotes: m.auto_notes ?? []
|
|
6034
|
+
autoNotes: m.auto_notes ?? [],
|
|
6035
|
+
failedCommand: m.command ?? ""
|
|
5891
6036
|
};
|
|
5892
6037
|
});
|
|
5893
6038
|
if (resolvedFixes.length < 2) {
|
|
@@ -5913,14 +6058,20 @@ function hookPreToolUse(dbPath) {
|
|
|
5913
6058
|
provenance: "inherited",
|
|
5914
6059
|
inherited: true,
|
|
5915
6060
|
rollbackPlan: m.rollback_plan ?? null,
|
|
5916
|
-
autoNotes: m.auto_notes ?? []
|
|
6061
|
+
autoNotes: m.auto_notes ?? [],
|
|
6062
|
+
failedCommand: m.command ?? ""
|
|
5917
6063
|
});
|
|
5918
6064
|
}
|
|
5919
6065
|
}
|
|
5920
6066
|
rDb.close();
|
|
5921
6067
|
} catch {
|
|
5922
6068
|
}
|
|
5923
|
-
|
|
6069
|
+
const relevantErrors = errors2.filter((err) => {
|
|
6070
|
+
const meta = err.metadata ? JSON.parse(err.metadata) : {};
|
|
6071
|
+
return isErrorRelevantToCommand(command, meta);
|
|
6072
|
+
});
|
|
6073
|
+
const relevantFixes = resolvedFixes;
|
|
6074
|
+
if (relevantErrors.length === 0 && relevantFixes.length === 0) {
|
|
5924
6075
|
try {
|
|
5925
6076
|
const decDb = new Database8(dbPath, { readonly: true });
|
|
5926
6077
|
const lowerCmd = command.toLowerCase();
|
|
@@ -6009,19 +6160,19 @@ function hookPreToolUse(dbPath) {
|
|
|
6009
6160
|
process.stdout.write(JSON.stringify({}));
|
|
6010
6161
|
return;
|
|
6011
6162
|
}
|
|
6012
|
-
const validErrors =
|
|
6013
|
-
const meta = JSON.parse(err.metadata);
|
|
6163
|
+
const validErrors = relevantErrors.filter((err) => {
|
|
6164
|
+
const meta = err.metadata ? JSON.parse(err.metadata) : {};
|
|
6014
6165
|
const filesInError = extractFilePaths(err.content);
|
|
6015
6166
|
if (filesInError.length === 0) return true;
|
|
6016
|
-
return filesInError.some((f) =>
|
|
6167
|
+
return filesInError.some((f) => existsSync8(join9(cwd, f)));
|
|
6017
6168
|
});
|
|
6018
|
-
if (validErrors.length === 0 &&
|
|
6169
|
+
if (validErrors.length === 0 && relevantFixes.length === 0) {
|
|
6019
6170
|
logToFile("PreToolUse: all past errors are stale and no proven fixes");
|
|
6020
6171
|
process.stdout.write(JSON.stringify({}));
|
|
6021
6172
|
return;
|
|
6022
6173
|
}
|
|
6023
6174
|
const decayed = validErrors.map((err) => {
|
|
6024
|
-
const meta = JSON.parse(err.metadata);
|
|
6175
|
+
const meta = err.metadata ? JSON.parse(err.metadata) : {};
|
|
6025
6176
|
const base = meta.confidence ?? 2;
|
|
6026
6177
|
const decayed2 = applyConfidenceDecay(base, err.created_at, meta.last_recurred);
|
|
6027
6178
|
return { err, meta, decayedConfidence: decayed2 };
|
|
@@ -6054,12 +6205,12 @@ function hookPreToolUse(dbPath) {
|
|
|
6054
6205
|
}
|
|
6055
6206
|
}
|
|
6056
6207
|
}
|
|
6057
|
-
if (
|
|
6208
|
+
if (relevantFixes.length > 0) {
|
|
6058
6209
|
lines.push("");
|
|
6059
6210
|
lines.push("Proven fixes from past resolved errors:");
|
|
6060
6211
|
const stalenessDays = Number(process.env.REMEM_FIX_STALENESS_DAYS ?? 180);
|
|
6061
6212
|
const stalenessMs = stalenessDays * 864e5;
|
|
6062
|
-
for (const fix of
|
|
6213
|
+
for (const fix of relevantFixes) {
|
|
6063
6214
|
const fixAgeMs = Date.now() - new Date(fix.resolvedAt).getTime();
|
|
6064
6215
|
const isStale = fixAgeMs > stalenessMs;
|
|
6065
6216
|
const staleTag = isStale ? " [STALE \u2014 verify before applying]" : "";
|
|
@@ -6155,7 +6306,7 @@ function hookPreToolUse(dbPath) {
|
|
|
6155
6306
|
}
|
|
6156
6307
|
} catch {
|
|
6157
6308
|
}
|
|
6158
|
-
|
|
6309
|
+
let context = lines.join("\n");
|
|
6159
6310
|
const k = decayed.length;
|
|
6160
6311
|
logToFile(
|
|
6161
6312
|
`PreToolUse: injected ${k} past error(s) (k=${k}, decayed confidence, ${resolvedFixes.length} fixes) before: ${command.slice(0, 60)}`
|
|
@@ -6170,6 +6321,9 @@ function hookPreToolUse(dbPath) {
|
|
|
6170
6321
|
logDriftInjection(sessionKey, err.content_hash, err.id, command);
|
|
6171
6322
|
}
|
|
6172
6323
|
}
|
|
6324
|
+
if (contextBlocks.length > 0) {
|
|
6325
|
+
context = contextBlocks.join("\n\n") + (context ? "\n\n" + context : "");
|
|
6326
|
+
}
|
|
6173
6327
|
const output2 = {
|
|
6174
6328
|
hookSpecificOutput: {
|
|
6175
6329
|
hookEventName: "PreToolUse",
|
|
@@ -6181,7 +6335,16 @@ function hookPreToolUse(dbPath) {
|
|
|
6181
6335
|
process.stderr.write(`[remem-mcp hook-pre-tool-use] Error: ${err}
|
|
6182
6336
|
`);
|
|
6183
6337
|
logToFile(`PreToolUse: error - ${err}`);
|
|
6184
|
-
|
|
6338
|
+
if (contextBlocks.length > 0) {
|
|
6339
|
+
process.stdout.write(JSON.stringify({
|
|
6340
|
+
hookSpecificOutput: {
|
|
6341
|
+
hookEventName: "PreToolUse",
|
|
6342
|
+
additionalContext: contextBlocks.join("\n\n")
|
|
6343
|
+
}
|
|
6344
|
+
}));
|
|
6345
|
+
} else {
|
|
6346
|
+
process.stdout.write(JSON.stringify({}));
|
|
6347
|
+
}
|
|
6185
6348
|
}
|
|
6186
6349
|
});
|
|
6187
6350
|
}
|
|
@@ -6206,9 +6369,9 @@ function loadCaptureExclusions(cwd) {
|
|
|
6206
6369
|
try {
|
|
6207
6370
|
let dir = cwd;
|
|
6208
6371
|
for (let i = 0; i < MAX_ANCESTORS; i++) {
|
|
6209
|
-
const markerPath =
|
|
6210
|
-
if (
|
|
6211
|
-
const content =
|
|
6372
|
+
const markerPath = join9(dir, ".remem.toml");
|
|
6373
|
+
if (existsSync8(markerPath)) {
|
|
6374
|
+
const content = readFileSync6(markerPath, "utf-8");
|
|
6212
6375
|
patterns = parseCaptureExclusions(content);
|
|
6213
6376
|
break;
|
|
6214
6377
|
}
|
|
@@ -6230,7 +6393,8 @@ function shouldExcludePath(filePath, ignorePatterns) {
|
|
|
6230
6393
|
if (normalized.endsWith(pattern.slice(1))) return true;
|
|
6231
6394
|
continue;
|
|
6232
6395
|
}
|
|
6233
|
-
|
|
6396
|
+
const stripped = pattern.replace(/\/\*.*$/, "").replace(/\*.*$/, "");
|
|
6397
|
+
if (stripped && segments.includes(stripped)) return true;
|
|
6234
6398
|
}
|
|
6235
6399
|
return false;
|
|
6236
6400
|
}
|
|
@@ -6259,6 +6423,98 @@ function isNoiseCommand(command) {
|
|
|
6259
6423
|
if (/git\s+diff\s+.*--exit-code/.test(lower)) return true;
|
|
6260
6424
|
return false;
|
|
6261
6425
|
}
|
|
6426
|
+
var COMMAND_INTENTS = {
|
|
6427
|
+
biome: "check",
|
|
6428
|
+
build: "build",
|
|
6429
|
+
cargo: "build",
|
|
6430
|
+
check: "check",
|
|
6431
|
+
eslint: "lint",
|
|
6432
|
+
format: "format",
|
|
6433
|
+
jest: "test",
|
|
6434
|
+
lint: "lint",
|
|
6435
|
+
make: "build",
|
|
6436
|
+
mvn: "build",
|
|
6437
|
+
mypy: "typecheck",
|
|
6438
|
+
pytest: "test",
|
|
6439
|
+
ruff: "lint",
|
|
6440
|
+
tsc: "typecheck",
|
|
6441
|
+
typecheck: "typecheck",
|
|
6442
|
+
vitest: "test"
|
|
6443
|
+
};
|
|
6444
|
+
function getCommandIntent(command) {
|
|
6445
|
+
for (const segment of command.split(/&&|\|\||[;|]/)) {
|
|
6446
|
+
let part = segment.trim();
|
|
6447
|
+
if (!part) continue;
|
|
6448
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+/.test(part)) {
|
|
6449
|
+
part = part.replace(/^[A-Za-z_][A-Za-z0-9_]*=(?:"[^"]*"|'[^']*'|\S*)\s+/, "");
|
|
6450
|
+
}
|
|
6451
|
+
const tokens = part.split(/\s+/).filter(Boolean);
|
|
6452
|
+
if (tokens.length === 0) continue;
|
|
6453
|
+
while (tokens[0] === "sudo" || tokens[0] === "time") tokens.shift();
|
|
6454
|
+
const executable = (tokens[0] ?? "").split("/").pop() ?? "";
|
|
6455
|
+
if (["cd", "pushd", "popd"].includes(executable)) continue;
|
|
6456
|
+
if (["npm", "pnpm", "yarn"].includes(executable)) {
|
|
6457
|
+
const args = tokens.slice(1);
|
|
6458
|
+
const runAt = args.findIndex((arg) => arg === "run" || arg === "exec");
|
|
6459
|
+
const action = runAt >= 0 ? args[runAt + 1] : args.find((arg) => !arg.startsWith("-"));
|
|
6460
|
+
if (action && COMMAND_INTENTS[action]) return COMMAND_INTENTS[action];
|
|
6461
|
+
continue;
|
|
6462
|
+
}
|
|
6463
|
+
if (executable === "python" && tokens[1] === "-m") {
|
|
6464
|
+
const module = (tokens[2] ?? "").split("/").pop() ?? "";
|
|
6465
|
+
if (COMMAND_INTENTS[module]) return COMMAND_INTENTS[module];
|
|
6466
|
+
continue;
|
|
6467
|
+
}
|
|
6468
|
+
if (COMMAND_INTENTS[executable]) return COMMAND_INTENTS[executable];
|
|
6469
|
+
if (["cargo", "dotnet", "go", "gradle", "mvn"].includes(executable)) {
|
|
6470
|
+
const action = (tokens[1] ?? "").toLowerCase();
|
|
6471
|
+
if (action === "test") return "test";
|
|
6472
|
+
if (action === "fmt") return "format";
|
|
6473
|
+
if (["build", "vet", "clippy"].includes(action)) return "check";
|
|
6474
|
+
}
|
|
6475
|
+
}
|
|
6476
|
+
return null;
|
|
6477
|
+
}
|
|
6478
|
+
function isRelevantBuildCommand(command) {
|
|
6479
|
+
return getCommandIntent(command) !== null;
|
|
6480
|
+
}
|
|
6481
|
+
function isDecisionLearningCommand(command) {
|
|
6482
|
+
return /\b(?:npm|pnpm|yarn)\s+(?:install|i|add)\b/.test(command) || /\b(?:pip|uv)\s+install\b/.test(command) || /\bcargo\s+add\b/.test(command) || /\bgit\s+commit\b/.test(command);
|
|
6483
|
+
}
|
|
6484
|
+
function isErrorRelevantToCommand(command, meta) {
|
|
6485
|
+
const intent = getCommandIntent(command);
|
|
6486
|
+
if (!intent) return false;
|
|
6487
|
+
const failedCommand = typeof meta.command === "string" ? meta.command : "";
|
|
6488
|
+
if (failedCommand) {
|
|
6489
|
+
if (getCommandIntent(failedCommand) === intent) return true;
|
|
6490
|
+
const genericWords = /* @__PURE__ */ new Set([
|
|
6491
|
+
"and",
|
|
6492
|
+
"error",
|
|
6493
|
+
"failed",
|
|
6494
|
+
"false",
|
|
6495
|
+
"npm",
|
|
6496
|
+
"npx",
|
|
6497
|
+
"pnpm",
|
|
6498
|
+
"run",
|
|
6499
|
+
"the",
|
|
6500
|
+
"true",
|
|
6501
|
+
"yarn"
|
|
6502
|
+
]);
|
|
6503
|
+
const words = (text) => new Set(
|
|
6504
|
+
text.toLowerCase().split(/[^a-z0-9@/_-]+/).filter(
|
|
6505
|
+
(word) => word.length >= 3 && !genericWords.has(word)
|
|
6506
|
+
)
|
|
6507
|
+
);
|
|
6508
|
+
const currentWords = words(command);
|
|
6509
|
+
const failedWords = words(failedCommand);
|
|
6510
|
+
for (const word of currentWords) {
|
|
6511
|
+
if (failedWords.has(word)) return true;
|
|
6512
|
+
}
|
|
6513
|
+
return false;
|
|
6514
|
+
}
|
|
6515
|
+
const errorType = typeof meta.error_type === "string" ? meta.error_type.toLowerCase() : "";
|
|
6516
|
+
return !errorType || errorType === intent || intent === "check";
|
|
6517
|
+
}
|
|
6262
6518
|
function checkDangerousCommand(command) {
|
|
6263
6519
|
const lower = command.toLowerCase();
|
|
6264
6520
|
if (/git\s+push\s+(--force|-f)(?!\S)/.test(lower)) {
|
|
@@ -6630,13 +6886,118 @@ function extractFilePaths(content) {
|
|
|
6630
6886
|
function hashPath(path) {
|
|
6631
6887
|
return createHash3("sha256").update(path).digest("hex").slice(0, 16);
|
|
6632
6888
|
}
|
|
6889
|
+
function ensureSchema2(db) {
|
|
6890
|
+
db.exec(`CREATE TABLE IF NOT EXISTS captures (
|
|
6891
|
+
id TEXT PRIMARY KEY,
|
|
6892
|
+
session_key TEXT NOT NULL,
|
|
6893
|
+
team_id TEXT,
|
|
6894
|
+
agent_id TEXT,
|
|
6895
|
+
user_id TEXT,
|
|
6896
|
+
type TEXT NOT NULL,
|
|
6897
|
+
content TEXT NOT NULL,
|
|
6898
|
+
tags TEXT,
|
|
6899
|
+
metadata TEXT,
|
|
6900
|
+
content_hash TEXT,
|
|
6901
|
+
created_at INTEGER NOT NULL,
|
|
6902
|
+
deleted_at INTEGER,
|
|
6903
|
+
trust_state TEXT DEFAULT 'trusted',
|
|
6904
|
+
rejection_reason TEXT,
|
|
6905
|
+
superseded_by TEXT,
|
|
6906
|
+
access_count INTEGER DEFAULT 0,
|
|
6907
|
+
last_accessed_at INTEGER,
|
|
6908
|
+
bayesian_alpha REAL DEFAULT 1.0,
|
|
6909
|
+
bayesian_beta REAL DEFAULT 1.0,
|
|
6910
|
+
fix_validated INTEGER,
|
|
6911
|
+
fix_validated_at INTEGER,
|
|
6912
|
+
escalation_level INTEGER DEFAULT 0
|
|
6913
|
+
)`);
|
|
6914
|
+
db.exec(`CREATE TABLE IF NOT EXISTS error_patterns (
|
|
6915
|
+
id TEXT PRIMARY KEY,
|
|
6916
|
+
session_key TEXT NOT NULL,
|
|
6917
|
+
command TEXT NOT NULL,
|
|
6918
|
+
error_type TEXT,
|
|
6919
|
+
error_message TEXT,
|
|
6920
|
+
content_hash TEXT,
|
|
6921
|
+
confidence REAL DEFAULT 2.0,
|
|
6922
|
+
occurrences INTEGER DEFAULT 1,
|
|
6923
|
+
first_seen INTEGER NOT NULL,
|
|
6924
|
+
last_seen INTEGER NOT NULL,
|
|
6925
|
+
resolved_at INTEGER,
|
|
6926
|
+
fix_command TEXT,
|
|
6927
|
+
fix_approach TEXT,
|
|
6928
|
+
fix_validated INTEGER,
|
|
6929
|
+
fix_recorded_at INTEGER,
|
|
6930
|
+
escalation_level INTEGER DEFAULT 0,
|
|
6931
|
+
root_cause TEXT,
|
|
6932
|
+
auto_notes TEXT,
|
|
6933
|
+
deleted_at INTEGER
|
|
6934
|
+
)`);
|
|
6935
|
+
db.exec(`CREATE TABLE IF NOT EXISTS decisions (
|
|
6936
|
+
id TEXT PRIMARY KEY,
|
|
6937
|
+
session_key TEXT NOT NULL,
|
|
6938
|
+
title TEXT NOT NULL,
|
|
6939
|
+
choice TEXT NOT NULL,
|
|
6940
|
+
rationale TEXT,
|
|
6941
|
+
context TEXT,
|
|
6942
|
+
created_at INTEGER NOT NULL
|
|
6943
|
+
)`);
|
|
6944
|
+
db.exec(`CREATE TABLE IF NOT EXISTS persona (
|
|
6945
|
+
team_id TEXT NOT NULL,
|
|
6946
|
+
user_id TEXT NOT NULL,
|
|
6947
|
+
content TEXT NOT NULL,
|
|
6948
|
+
updated_at INTEGER NOT NULL,
|
|
6949
|
+
PRIMARY KEY (team_id, user_id)
|
|
6950
|
+
)`);
|
|
6951
|
+
db.exec(`CREATE TABLE IF NOT EXISTS skills (
|
|
6952
|
+
id TEXT PRIMARY KEY,
|
|
6953
|
+
team_id TEXT NOT NULL,
|
|
6954
|
+
agent_id TEXT,
|
|
6955
|
+
name TEXT NOT NULL,
|
|
6956
|
+
description TEXT,
|
|
6957
|
+
content TEXT,
|
|
6958
|
+
version INTEGER DEFAULT 1,
|
|
6959
|
+
created_at INTEGER NOT NULL,
|
|
6960
|
+
updated_at INTEGER NOT NULL,
|
|
6961
|
+
trigger_conditions TEXT,
|
|
6962
|
+
steps TEXT,
|
|
6963
|
+
validation_rules TEXT,
|
|
6964
|
+
source_capture_ids TEXT,
|
|
6965
|
+
archived INTEGER DEFAULT 0
|
|
6966
|
+
)`);
|
|
6967
|
+
db.exec(`CREATE TABLE IF NOT EXISTS atoms (
|
|
6968
|
+
id TEXT PRIMARY KEY,
|
|
6969
|
+
capture_id TEXT NOT NULL,
|
|
6970
|
+
fact TEXT NOT NULL,
|
|
6971
|
+
confidence REAL NOT NULL DEFAULT 1.0,
|
|
6972
|
+
created_at INTEGER NOT NULL,
|
|
6973
|
+
team_id TEXT,
|
|
6974
|
+
agent_id TEXT,
|
|
6975
|
+
user_id TEXT
|
|
6976
|
+
)`);
|
|
6977
|
+
db.exec(`CREATE TABLE IF NOT EXISTS scenarios (
|
|
6978
|
+
id TEXT PRIMARY KEY,
|
|
6979
|
+
atom_ids TEXT NOT NULL,
|
|
6980
|
+
summary TEXT,
|
|
6981
|
+
created_at INTEGER NOT NULL,
|
|
6982
|
+
team_id TEXT
|
|
6983
|
+
)`);
|
|
6984
|
+
db.exec(`CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL)`);
|
|
6985
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_captures_session ON captures(session_key)`);
|
|
6986
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_captures_type ON captures(type)`);
|
|
6987
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_errors_session ON error_patterns(session_key)`);
|
|
6988
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_atoms_capture ON atoms(capture_id)`);
|
|
6989
|
+
}
|
|
6633
6990
|
function defaultTranscriptDir() {
|
|
6634
|
-
return process.env.DEVIN_TRANSCRIPTS_DIR ??
|
|
6991
|
+
return process.env.DEVIN_TRANSCRIPTS_DIR ?? join9(homedir7(), ".local", "share", "devin", "cli", "transcripts");
|
|
6635
6992
|
}
|
|
6636
6993
|
function generateId2() {
|
|
6637
|
-
|
|
6638
|
-
|
|
6639
|
-
|
|
6994
|
+
try {
|
|
6995
|
+
return generateId();
|
|
6996
|
+
} catch {
|
|
6997
|
+
const ts = Date.now().toString(36).toUpperCase();
|
|
6998
|
+
const rand = Math.random().toString(36).slice(2, 12).toUpperCase();
|
|
6999
|
+
return `01${ts}${rand}`;
|
|
7000
|
+
}
|
|
6640
7001
|
}
|
|
6641
7002
|
async function captureSessionTranscript(dbPath, sessionId, transcriptPath, cwd) {
|
|
6642
7003
|
const sid = sessionId ?? "unknown";
|
|
@@ -6648,13 +7009,13 @@ async function captureSessionTranscript(dbPath, sessionId, transcriptPath, cwd)
|
|
|
6648
7009
|
if (transcriptPath) {
|
|
6649
7010
|
filePath = transcriptPath;
|
|
6650
7011
|
} else {
|
|
6651
|
-
filePath =
|
|
7012
|
+
filePath = join9(defaultTranscriptDir(), `${sid}.json`);
|
|
6652
7013
|
}
|
|
6653
|
-
if (!
|
|
7014
|
+
if (!existsSync8(filePath)) {
|
|
6654
7015
|
logToFile(`Stop: transcript not found at ${filePath}`);
|
|
6655
7016
|
return null;
|
|
6656
7017
|
}
|
|
6657
|
-
const raw =
|
|
7018
|
+
const raw = readFileSync6(filePath, "utf-8");
|
|
6658
7019
|
const userMessages = [];
|
|
6659
7020
|
const assistantMessages = [];
|
|
6660
7021
|
const trimmed = raw.trim();
|
|
@@ -6721,6 +7082,7 @@ Task: ${taskText}
|
|
|
6721
7082
|
Outcome: ${outcomeText}`;
|
|
6722
7083
|
const contentHash = createHash3("sha256").update(content).digest("hex");
|
|
6723
7084
|
const db = new Database8(dbPath);
|
|
7085
|
+
ensureSchema2(db);
|
|
6724
7086
|
try {
|
|
6725
7087
|
const sqliteVec5 = await import("sqlite-vec");
|
|
6726
7088
|
sqliteVec5.load(db);
|
|
@@ -6828,19 +7190,19 @@ async function waitAndCapture(dbPath, sessionId, transcriptPath) {
|
|
|
6828
7190
|
if (transcriptPath) {
|
|
6829
7191
|
filePath = transcriptPath;
|
|
6830
7192
|
} else {
|
|
6831
|
-
filePath =
|
|
7193
|
+
filePath = join9(defaultTranscriptDir(), `${sessionId}.json`);
|
|
6832
7194
|
}
|
|
6833
7195
|
const maxWait = 1e4;
|
|
6834
7196
|
const interval = 500;
|
|
6835
7197
|
const start = Date.now();
|
|
6836
7198
|
while (Date.now() - start < maxWait) {
|
|
6837
|
-
if (
|
|
7199
|
+
if (existsSync8(filePath)) {
|
|
6838
7200
|
await sleep2(500);
|
|
6839
7201
|
break;
|
|
6840
7202
|
}
|
|
6841
7203
|
await sleep2(interval);
|
|
6842
7204
|
}
|
|
6843
|
-
if (!
|
|
7205
|
+
if (!existsSync8(filePath)) {
|
|
6844
7206
|
logToFile(`Stop: transcript never appeared at ${filePath} after 10s`);
|
|
6845
7207
|
return;
|
|
6846
7208
|
}
|
|
@@ -6858,8 +7220,8 @@ function sleep2(ms) {
|
|
|
6858
7220
|
}
|
|
6859
7221
|
async function hookPostCommit(dbPath) {
|
|
6860
7222
|
try {
|
|
6861
|
-
const { execSync:
|
|
6862
|
-
const output2 =
|
|
7223
|
+
const { execSync: execSync4 } = await import("child_process");
|
|
7224
|
+
const output2 = execSync4("git diff-tree --no-commit-id --name-only -r HEAD", {
|
|
6863
7225
|
encoding: "utf-8",
|
|
6864
7226
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6865
7227
|
});
|
|
@@ -6869,18 +7231,19 @@ async function hookPostCommit(dbPath) {
|
|
|
6869
7231
|
process.stdout.write(JSON.stringify({}));
|
|
6870
7232
|
return;
|
|
6871
7233
|
}
|
|
6872
|
-
const { readFileSync:
|
|
6873
|
-
const { join:
|
|
7234
|
+
const { readFileSync: readFileSync14 } = await import("fs");
|
|
7235
|
+
const { join: join17, dirname: dirname11 } = await import("path");
|
|
6874
7236
|
const { fileURLToPath: fileURLToPath5 } = await import("url");
|
|
6875
7237
|
const sqliteVec5 = await import("sqlite-vec");
|
|
6876
|
-
const { indexFile: indexFile2 } = await import("./engine-
|
|
7238
|
+
const { indexFile: indexFile2 } = await import("./engine-S47HHGAO.js");
|
|
6877
7239
|
const db = new Database8(dbPath);
|
|
7240
|
+
ensureSchema2(db);
|
|
6878
7241
|
try {
|
|
6879
7242
|
db.pragma("journal_mode = WAL");
|
|
6880
7243
|
sqliteVec5.load(db);
|
|
6881
|
-
const schemaPath =
|
|
7244
|
+
const schemaPath = join17(dirname11(fileURLToPath5(import.meta.url)), "storage", "schema.sql");
|
|
6882
7245
|
try {
|
|
6883
|
-
const schema =
|
|
7246
|
+
const schema = readFileSync14(schemaPath, "utf-8");
|
|
6884
7247
|
db.exec(schema);
|
|
6885
7248
|
} catch {
|
|
6886
7249
|
}
|
|
@@ -6904,7 +7267,7 @@ async function hookPostCommit(dbPath) {
|
|
|
6904
7267
|
];
|
|
6905
7268
|
let indexed = 0;
|
|
6906
7269
|
let skipped = 0;
|
|
6907
|
-
const repoRoot =
|
|
7270
|
+
const repoRoot = execSync4("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
6908
7271
|
for (const file of files) {
|
|
6909
7272
|
const ext = file.slice(file.lastIndexOf(".")).toLowerCase();
|
|
6910
7273
|
if (!SUPPORTED_EXT.includes(ext)) {
|
|
@@ -6912,7 +7275,7 @@ async function hookPostCommit(dbPath) {
|
|
|
6912
7275
|
continue;
|
|
6913
7276
|
}
|
|
6914
7277
|
try {
|
|
6915
|
-
const fullPath =
|
|
7278
|
+
const fullPath = join17(repoRoot, file);
|
|
6916
7279
|
await indexFile2(db, fullPath, repoRoot, null);
|
|
6917
7280
|
indexed++;
|
|
6918
7281
|
} catch {
|
|
@@ -6948,6 +7311,7 @@ function hookPreCompact(dbPath) {
|
|
|
6948
7311
|
const trigger = input.trigger ?? "unknown";
|
|
6949
7312
|
const sessionKey = hashPath(input.cwd ?? process.cwd());
|
|
6950
7313
|
const db = new Database8(dbPath);
|
|
7314
|
+
ensureSchema2(db);
|
|
6951
7315
|
let transcriptNote = "";
|
|
6952
7316
|
try {
|
|
6953
7317
|
db.pragma("journal_mode = WAL");
|
|
@@ -6958,9 +7322,9 @@ function hookPreCompact(dbPath) {
|
|
|
6958
7322
|
}
|
|
6959
7323
|
try {
|
|
6960
7324
|
const { fileURLToPath: fileURLToPath5 } = await import("url");
|
|
6961
|
-
const schemaPath =
|
|
6962
|
-
if (
|
|
6963
|
-
db.exec(
|
|
7325
|
+
const schemaPath = join9(dirname4(fileURLToPath5(import.meta.url)), "storage", "schema.sql");
|
|
7326
|
+
if (existsSync8(schemaPath)) {
|
|
7327
|
+
db.exec(readFileSync6(schemaPath, "utf-8"));
|
|
6964
7328
|
}
|
|
6965
7329
|
} catch {
|
|
6966
7330
|
}
|
|
@@ -6983,9 +7347,9 @@ function hookPreCompact(dbPath) {
|
|
|
6983
7347
|
"remem-mcp-hook",
|
|
6984
7348
|
Date.now()
|
|
6985
7349
|
);
|
|
6986
|
-
if (input.transcript_path &&
|
|
7350
|
+
if (input.transcript_path && existsSync8(input.transcript_path)) {
|
|
6987
7351
|
try {
|
|
6988
|
-
const transcript =
|
|
7352
|
+
const transcript = readFileSync6(input.transcript_path, "utf-8");
|
|
6989
7353
|
const recent = transcript.slice(-2e3);
|
|
6990
7354
|
const transcriptId = `txcpt-${createHash3("sha256").update(sessionKey + "transcript" + Date.now()).digest("hex").slice(0, 12)}`;
|
|
6991
7355
|
db.prepare(
|
|
@@ -7056,7 +7420,7 @@ function hookPostCompaction(dbPath) {
|
|
|
7056
7420
|
const sessionKey = hashPath(input.cwd ?? process.cwd());
|
|
7057
7421
|
const globalKey = process.env.REMEM_GLOBAL_SESSION_KEY ?? null;
|
|
7058
7422
|
const { fileURLToPath: fileURLToPath5 } = await import("url");
|
|
7059
|
-
const schemaPath =
|
|
7423
|
+
const schemaPath = join9(dirname4(fileURLToPath5(import.meta.url)), "storage", "schema.sql");
|
|
7060
7424
|
const db = new Database8(dbPath, { readonly: true });
|
|
7061
7425
|
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
7062
7426
|
const sessionKeys = globalKey ? [sessionKey, globalKey] : [sessionKey];
|
|
@@ -7107,23 +7471,365 @@ function hookPostCompaction(dbPath) {
|
|
|
7107
7471
|
}
|
|
7108
7472
|
});
|
|
7109
7473
|
}
|
|
7474
|
+
function offloadToolOutput(dbPath, toolName, toolInput, toolResponse, cwd) {
|
|
7475
|
+
const sessionKey = hashPath(cwd ?? process.cwd());
|
|
7476
|
+
const nodeId = generateId2();
|
|
7477
|
+
const label = buildToolLabel(toolName, toolInput);
|
|
7478
|
+
const rawContent = buildRawRefContent(toolName, toolInput, toolResponse);
|
|
7479
|
+
writeRef(sessionKey, nodeId, rawContent);
|
|
7480
|
+
const db = new Database8(dbPath);
|
|
7481
|
+
ensureSchema2(db);
|
|
7482
|
+
try {
|
|
7483
|
+
db.exec(`CREATE TABLE IF NOT EXISTS canvases (
|
|
7484
|
+
id TEXT PRIMARY KEY,
|
|
7485
|
+
session_key TEXT NOT NULL,
|
|
7486
|
+
mermaid_text TEXT,
|
|
7487
|
+
node_count INTEGER DEFAULT 0,
|
|
7488
|
+
created_at INTEGER NOT NULL,
|
|
7489
|
+
updated_at INTEGER NOT NULL,
|
|
7490
|
+
team_id TEXT
|
|
7491
|
+
)`);
|
|
7492
|
+
db.exec(`CREATE TABLE IF NOT EXISTS canvas_nodes (
|
|
7493
|
+
id TEXT PRIMARY KEY,
|
|
7494
|
+
canvas_id TEXT NOT NULL,
|
|
7495
|
+
node_id TEXT NOT NULL,
|
|
7496
|
+
label TEXT NOT NULL,
|
|
7497
|
+
capture_id TEXT,
|
|
7498
|
+
seq INTEGER NOT NULL,
|
|
7499
|
+
created_at INTEGER NOT NULL,
|
|
7500
|
+
FOREIGN KEY (canvas_id) REFERENCES canvases(id)
|
|
7501
|
+
)`);
|
|
7502
|
+
db.exec(`CREATE TABLE IF NOT EXISTS canvas_edges (
|
|
7503
|
+
id TEXT PRIMARY KEY,
|
|
7504
|
+
canvas_id TEXT NOT NULL,
|
|
7505
|
+
from_node_id TEXT NOT NULL,
|
|
7506
|
+
to_node_id TEXT NOT NULL,
|
|
7507
|
+
label TEXT,
|
|
7508
|
+
created_at INTEGER NOT NULL,
|
|
7509
|
+
FOREIGN KEY (canvas_id) REFERENCES canvases(id)
|
|
7510
|
+
)`);
|
|
7511
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_canvases_session ON canvases(session_key)`);
|
|
7512
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_canvas_nodes_canvas ON canvas_nodes(canvas_id)`);
|
|
7513
|
+
const now = Date.now();
|
|
7514
|
+
let canvasRow = db.prepare("SELECT id FROM canvases WHERE session_key = ? ORDER BY updated_at DESC LIMIT 1").get(sessionKey);
|
|
7515
|
+
if (!canvasRow) {
|
|
7516
|
+
const canvasId = generateId2();
|
|
7517
|
+
db.prepare(
|
|
7518
|
+
"INSERT INTO canvases (id, session_key, mermaid_text, node_count, created_at, updated_at, team_id) VALUES (?, ?, NULL, 0, ?, ?, NULL)"
|
|
7519
|
+
).run(canvasId, sessionKey, now, now);
|
|
7520
|
+
canvasRow = { id: canvasId };
|
|
7521
|
+
}
|
|
7522
|
+
const maxSeq = db.prepare("SELECT MAX(seq) as max_seq FROM canvas_nodes WHERE canvas_id = ?").get(canvasRow.id);
|
|
7523
|
+
const seq = (maxSeq?.max_seq ?? -1) + 1;
|
|
7524
|
+
db.prepare(
|
|
7525
|
+
"INSERT INTO canvas_nodes (id, canvas_id, node_id, label, capture_id, seq, created_at) VALUES (?, ?, ?, ?, NULL, ?, ?)"
|
|
7526
|
+
).run(generateId2(), canvasRow.id, nodeId, label, seq, now);
|
|
7527
|
+
if (seq > 0) {
|
|
7528
|
+
const prevNode = db.prepare("SELECT node_id FROM canvas_nodes WHERE canvas_id = ? ORDER BY seq DESC LIMIT 1 OFFSET 1").get(canvasRow.id);
|
|
7529
|
+
if (prevNode) {
|
|
7530
|
+
db.prepare(
|
|
7531
|
+
"INSERT INTO canvas_edges (id, canvas_id, from_node_id, to_node_id, label, created_at) VALUES (?, ?, ?, ?, ?, ?)"
|
|
7532
|
+
).run(generateId2(), canvasRow.id, prevNode.node_id, nodeId, inferEdgeLabel(toolName), now);
|
|
7533
|
+
}
|
|
7534
|
+
}
|
|
7535
|
+
const nodeCount = db.prepare("SELECT COUNT(*) as count FROM canvas_nodes WHERE canvas_id = ?").get(canvasRow.id);
|
|
7536
|
+
const mermaidText = renderCanvasMermaid(db, canvasRow.id);
|
|
7537
|
+
db.prepare("UPDATE canvases SET node_count = ?, updated_at = ?, mermaid_text = ? WHERE id = ?").run(
|
|
7538
|
+
nodeCount.count,
|
|
7539
|
+
now,
|
|
7540
|
+
mermaidText,
|
|
7541
|
+
canvasRow.id
|
|
7542
|
+
);
|
|
7543
|
+
} finally {
|
|
7544
|
+
db.close();
|
|
7545
|
+
}
|
|
7546
|
+
}
|
|
7547
|
+
function buildToolLabel(toolName, toolInput) {
|
|
7548
|
+
if (toolName === "Bash" || toolName === "exec") {
|
|
7549
|
+
const cmd = toolInput.command ?? "";
|
|
7550
|
+
return truncateLabel(cmd.replace(/\n/g, " ").trim(), 40);
|
|
7551
|
+
}
|
|
7552
|
+
if (toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
|
|
7553
|
+
const fp = toolInput.file_path ?? "";
|
|
7554
|
+
return truncateLabel(fp, 40);
|
|
7555
|
+
}
|
|
7556
|
+
if (toolName === "Read") {
|
|
7557
|
+
const fp = toolInput.file_path ?? "";
|
|
7558
|
+
return truncateLabel(`read ${fp}`, 40);
|
|
7559
|
+
}
|
|
7560
|
+
if (toolName === "grep" || toolName === "search") {
|
|
7561
|
+
const pattern = toolInput.pattern ?? toolInput.query ?? "";
|
|
7562
|
+
return truncateLabel(`search: ${pattern}`, 40);
|
|
7563
|
+
}
|
|
7564
|
+
return truncateLabel(toolName, 40);
|
|
7565
|
+
}
|
|
7566
|
+
function buildRawRefContent(toolName, toolInput, toolResponse) {
|
|
7567
|
+
const lines = [
|
|
7568
|
+
`# Tool: ${toolName}`,
|
|
7569
|
+
`## Input`,
|
|
7570
|
+
"```json",
|
|
7571
|
+
JSON.stringify(toolInput, null, 2),
|
|
7572
|
+
"```",
|
|
7573
|
+
`## Output`
|
|
7574
|
+
];
|
|
7575
|
+
if (typeof toolResponse === "string") {
|
|
7576
|
+
lines.push("```", toolResponse, "```");
|
|
7577
|
+
} else if (toolResponse && typeof toolResponse === "object") {
|
|
7578
|
+
const resp = toolResponse;
|
|
7579
|
+
if (resp.content && Array.isArray(resp.content)) {
|
|
7580
|
+
for (const c of resp.content) {
|
|
7581
|
+
if (c.type === "text" && c.text) {
|
|
7582
|
+
lines.push("```", c.text, "```");
|
|
7583
|
+
}
|
|
7584
|
+
}
|
|
7585
|
+
} else {
|
|
7586
|
+
lines.push("```json", JSON.stringify(toolResponse, null, 2), "```");
|
|
7587
|
+
}
|
|
7588
|
+
} else {
|
|
7589
|
+
lines.push("(no output)");
|
|
7590
|
+
}
|
|
7591
|
+
return lines.join("\n");
|
|
7592
|
+
}
|
|
7593
|
+
function inferEdgeLabel(toolName) {
|
|
7594
|
+
if (toolName === "Bash" || toolName === "exec") return "ran";
|
|
7595
|
+
if (toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") return "edited";
|
|
7596
|
+
if (toolName === "Read") return "read";
|
|
7597
|
+
if (toolName === "grep" || toolName === "search") return "searched";
|
|
7598
|
+
return "next";
|
|
7599
|
+
}
|
|
7600
|
+
function truncateLabel(s, maxLen) {
|
|
7601
|
+
if (s.length <= maxLen) return s;
|
|
7602
|
+
return s.slice(0, maxLen - 3) + "...";
|
|
7603
|
+
}
|
|
7604
|
+
async function extractSkillFromCapture(dbPath, captureId) {
|
|
7605
|
+
const db = new Database8(dbPath, { readonly: true });
|
|
7606
|
+
try {
|
|
7607
|
+
const row = db.prepare("SELECT id, content, type, tags, team_id, agent_id FROM captures WHERE id = ? AND deleted_at IS NULL").get(captureId);
|
|
7608
|
+
if (!row || row.type !== "task") return;
|
|
7609
|
+
const tags = row.tags ? JSON.parse(row.tags) : [];
|
|
7610
|
+
const steps = extractStepsFromContent(row.content);
|
|
7611
|
+
if (steps.length < 2) return;
|
|
7612
|
+
const triggers = extractTriggersFromContent(row.content, tags);
|
|
7613
|
+
const skillName = extractSkillNameFromContent(row.content) ?? `skill-${captureId.slice(-8)}`;
|
|
7614
|
+
const description = row.content.split(/[.!]\s/)[0].slice(0, 120);
|
|
7615
|
+
const existing = db.prepare("SELECT id, version, created_at FROM skills WHERE name = ? ORDER BY version DESC LIMIT 1").get(skillName);
|
|
7616
|
+
const skillId = existing?.id ?? generateId2();
|
|
7617
|
+
const version = (existing?.version ?? 0) + 1;
|
|
7618
|
+
const now = Date.now();
|
|
7619
|
+
db.close();
|
|
7620
|
+
const writeDb = new Database8(dbPath);
|
|
7621
|
+
ensureSchema2(writeDb);
|
|
7622
|
+
try {
|
|
7623
|
+
writeDb.prepare(
|
|
7624
|
+
`INSERT INTO skills (id, team_id, agent_id, name, description, content, version, created_at, updated_at, trigger_conditions, steps, validation_rules, source_capture_ids, archived)
|
|
7625
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)
|
|
7626
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
7627
|
+
version = excluded.version, updated_at = excluded.updated_at,
|
|
7628
|
+
trigger_conditions = excluded.trigger_conditions, steps = excluded.steps,
|
|
7629
|
+
source_capture_ids = excluded.source_capture_ids`
|
|
7630
|
+
).run(
|
|
7631
|
+
skillId,
|
|
7632
|
+
row.team_id ?? "default",
|
|
7633
|
+
row.agent_id ?? "pipeline",
|
|
7634
|
+
skillName,
|
|
7635
|
+
description,
|
|
7636
|
+
row.content,
|
|
7637
|
+
version,
|
|
7638
|
+
existing?.created_at ?? now,
|
|
7639
|
+
now,
|
|
7640
|
+
JSON.stringify(triggers),
|
|
7641
|
+
JSON.stringify(steps),
|
|
7642
|
+
null,
|
|
7643
|
+
JSON.stringify([captureId])
|
|
7644
|
+
);
|
|
7645
|
+
logToFile(`Stop: extracted skill "${skillName}" v${version} from capture ${captureId}`);
|
|
7646
|
+
} finally {
|
|
7647
|
+
writeDb.close();
|
|
7648
|
+
}
|
|
7649
|
+
} finally {
|
|
7650
|
+
if (db.open) db.close();
|
|
7651
|
+
}
|
|
7652
|
+
}
|
|
7653
|
+
function extractStepsFromContent(content) {
|
|
7654
|
+
const steps = [];
|
|
7655
|
+
const numberedMatch = content.match(/(?:^|\n)\s*(\d+)[.)]\s+(.+)/g);
|
|
7656
|
+
if (numberedMatch) {
|
|
7657
|
+
for (const match of numberedMatch) {
|
|
7658
|
+
const step = match.replace(/(?:^|\n)\s*\d+[.)]\s+/, "").trim();
|
|
7659
|
+
if (step.length > 5 && step.length < 200) steps.push(step);
|
|
7660
|
+
}
|
|
7661
|
+
}
|
|
7662
|
+
if (steps.length === 0) {
|
|
7663
|
+
const bulletMatch = content.match(/(?:^|\n)\s*[-*]\s+(.+)/g);
|
|
7664
|
+
if (bulletMatch) {
|
|
7665
|
+
for (const match of bulletMatch) {
|
|
7666
|
+
const step = match.replace(/(?:^|\n)\s*[-*]\s+/, "").trim();
|
|
7667
|
+
if (step.length > 5 && step.length < 200) steps.push(step);
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
}
|
|
7671
|
+
return steps.slice(0, 10);
|
|
7672
|
+
}
|
|
7673
|
+
function extractTriggersFromContent(content, tags) {
|
|
7674
|
+
const triggers = /* @__PURE__ */ new Set();
|
|
7675
|
+
for (const tag of tags) {
|
|
7676
|
+
if (tag.length > 2 && tag !== "task") triggers.add(tag);
|
|
7677
|
+
}
|
|
7678
|
+
const firstWords = content.slice(0, 200).split(/\s+/).filter((w) => w.length > 4);
|
|
7679
|
+
for (const word of firstWords.slice(0, 5)) {
|
|
7680
|
+
triggers.add(word.toLowerCase().replace(/[^a-z0-9]/g, ""));
|
|
7681
|
+
}
|
|
7682
|
+
return Array.from(triggers).slice(0, 8);
|
|
7683
|
+
}
|
|
7684
|
+
function extractSkillNameFromContent(content) {
|
|
7685
|
+
const firstLine = content.split("\n")[0].trim();
|
|
7686
|
+
if (firstLine.length > 5 && firstLine.length < 60) {
|
|
7687
|
+
return firstLine.replace(/[^a-zA-Z0-9\s-]/g, "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
7688
|
+
}
|
|
7689
|
+
return null;
|
|
7690
|
+
}
|
|
7691
|
+
function getCanvasContext(dbPath, cwd) {
|
|
7692
|
+
const sessionKey = hashPath(cwd);
|
|
7693
|
+
const db = new Database8(dbPath, { readonly: true });
|
|
7694
|
+
try {
|
|
7695
|
+
const row = db.prepare("SELECT mermaid_text, node_count FROM canvases WHERE session_key = ? ORDER BY updated_at DESC LIMIT 1").get(sessionKey);
|
|
7696
|
+
if (!row || !row.mermaid_text || row.node_count === 0) return null;
|
|
7697
|
+
return `## Task Canvas (${row.node_count} steps)
|
|
7698
|
+
|
|
7699
|
+
\`\`\`mermaid
|
|
7700
|
+
${row.mermaid_text}
|
|
7701
|
+
\`\`\`
|
|
7702
|
+
|
|
7703
|
+
Drill down: call ref_read(node_id) to get raw output for any node.`;
|
|
7704
|
+
} finally {
|
|
7705
|
+
db.close();
|
|
7706
|
+
}
|
|
7707
|
+
}
|
|
7708
|
+
function getSkillContext(dbPath, query) {
|
|
7709
|
+
const db = new Database8(dbPath, { readonly: true });
|
|
7710
|
+
try {
|
|
7711
|
+
const archived = db.prepare(
|
|
7712
|
+
"SELECT name, description, steps FROM skills WHERE archived = 1 ORDER BY updated_at DESC LIMIT 3"
|
|
7713
|
+
).all();
|
|
7714
|
+
const allSkills = db.prepare(
|
|
7715
|
+
"SELECT name, description, steps, trigger_conditions FROM skills WHERE archived = 0 ORDER BY updated_at DESC LIMIT 20"
|
|
7716
|
+
).all();
|
|
7717
|
+
const matched = [];
|
|
7718
|
+
const queryLower = query.toLowerCase();
|
|
7719
|
+
for (const skill of allSkills) {
|
|
7720
|
+
if (!skill.trigger_conditions) continue;
|
|
7721
|
+
const triggers = JSON.parse(skill.trigger_conditions);
|
|
7722
|
+
if (triggers.some((t) => queryLower.includes(t.toLowerCase()))) {
|
|
7723
|
+
matched.push(skill);
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
if (archived.length === 0 && matched.length === 0) return null;
|
|
7727
|
+
const lines = [];
|
|
7728
|
+
if (archived.length > 0) {
|
|
7729
|
+
lines.push("## Archived Skills (always available)");
|
|
7730
|
+
for (const s of archived) {
|
|
7731
|
+
const steps = s.steps ? JSON.parse(s.steps) : [];
|
|
7732
|
+
const stepStr = steps.length > 0 ? `
|
|
7733
|
+
Steps: ${steps.slice(0, 3).join(" \u2192 ")}` : "";
|
|
7734
|
+
lines.push(`- **${s.name}**: ${s.description ?? ""}${stepStr}`);
|
|
7735
|
+
}
|
|
7736
|
+
}
|
|
7737
|
+
if (matched.length > 0) {
|
|
7738
|
+
lines.push("## Matched Skills");
|
|
7739
|
+
for (const s of matched.slice(0, 2)) {
|
|
7740
|
+
const steps = s.steps ? JSON.parse(s.steps) : [];
|
|
7741
|
+
const stepStr = steps.length > 0 ? `
|
|
7742
|
+
Steps: ${steps.slice(0, 3).join(" \u2192 ")}` : "";
|
|
7743
|
+
lines.push(`- **${s.name}**: ${s.description ?? ""}${stepStr}`);
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
return lines.join("\n");
|
|
7747
|
+
} finally {
|
|
7748
|
+
db.close();
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7751
|
+
function renderCanvasMermaid(db, canvasId) {
|
|
7752
|
+
const nodeRows = db.prepare("SELECT * FROM canvas_nodes WHERE canvas_id = ? ORDER BY seq ASC").all(canvasId);
|
|
7753
|
+
const edgeRows = db.prepare("SELECT * FROM canvas_edges WHERE canvas_id = ? ORDER BY created_at ASC").all(canvasId);
|
|
7754
|
+
const lines = ["graph LR"];
|
|
7755
|
+
for (const node of nodeRows) {
|
|
7756
|
+
const safeLabel = node.label.replace(/"/g, "'");
|
|
7757
|
+
lines.push(` ${node.node_id}["${safeLabel}"]`);
|
|
7758
|
+
}
|
|
7759
|
+
for (const edge of edgeRows) {
|
|
7760
|
+
if (edge.label) {
|
|
7761
|
+
const safeLabel = edge.label.replace(/"/g, "'");
|
|
7762
|
+
lines.push(` ${edge.from_node_id} -->|${safeLabel}| ${edge.to_node_id}`);
|
|
7763
|
+
} else {
|
|
7764
|
+
lines.push(` ${edge.from_node_id} --> ${edge.to_node_id}`);
|
|
7765
|
+
}
|
|
7766
|
+
}
|
|
7767
|
+
return lines.join("\n");
|
|
7768
|
+
}
|
|
7110
7769
|
|
|
7111
7770
|
// src/hooks.ts
|
|
7112
7771
|
init_esm_shims();
|
|
7113
7772
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
7114
|
-
import { existsSync as
|
|
7115
|
-
import { homedir as
|
|
7116
|
-
import { dirname as dirname5, join as
|
|
7773
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
7774
|
+
import { homedir as homedir8 } from "os";
|
|
7775
|
+
import { dirname as dirname5, join as join10 } from "path";
|
|
7117
7776
|
function hookCommand(subcommand) {
|
|
7118
7777
|
try {
|
|
7119
7778
|
const binPath = execFileSync2("which", ["remem-mcp"], { encoding: "utf-8" }).trim();
|
|
7120
|
-
if (binPath &&
|
|
7779
|
+
if (binPath && existsSync9(binPath)) {
|
|
7121
7780
|
return `${binPath} ${subcommand}`;
|
|
7122
7781
|
}
|
|
7123
7782
|
} catch {
|
|
7124
7783
|
}
|
|
7125
7784
|
return `npx --prefer-offline -y remem-mcp ${subcommand}`;
|
|
7126
7785
|
}
|
|
7786
|
+
var CODEX_HOOK_SUBCOMMANDS = [
|
|
7787
|
+
"hook-recall",
|
|
7788
|
+
"hook-pre-tool-use",
|
|
7789
|
+
"hook-post-tool-use",
|
|
7790
|
+
"hook-post-compaction",
|
|
7791
|
+
"hook-user-prompt",
|
|
7792
|
+
"hook-stop",
|
|
7793
|
+
"hook-session-end"
|
|
7794
|
+
];
|
|
7795
|
+
function hasCodexRememHooks(content) {
|
|
7796
|
+
return CODEX_HOOK_SUBCOMMANDS.some((subcommand) => content.includes(` ${subcommand}"`));
|
|
7797
|
+
}
|
|
7798
|
+
function removeCodexHooksFromToml(content) {
|
|
7799
|
+
const lines = content.split(/\r?\n/);
|
|
7800
|
+
const output2 = [];
|
|
7801
|
+
let index = 0;
|
|
7802
|
+
while (index < lines.length) {
|
|
7803
|
+
const match = lines[index].match(/^\[\[hooks\.([A-Za-z]+)\]\]\s*$/);
|
|
7804
|
+
if (!match) {
|
|
7805
|
+
output2.push(lines[index]);
|
|
7806
|
+
index++;
|
|
7807
|
+
continue;
|
|
7808
|
+
}
|
|
7809
|
+
const event = match[1];
|
|
7810
|
+
const nestedPrefix = `[[hooks.${event}.hooks]]`;
|
|
7811
|
+
let end = index + 1;
|
|
7812
|
+
while (end < lines.length) {
|
|
7813
|
+
const line2 = lines[end];
|
|
7814
|
+
if (line2.startsWith(nestedPrefix)) {
|
|
7815
|
+
end++;
|
|
7816
|
+
continue;
|
|
7817
|
+
}
|
|
7818
|
+
if (/^\[./.test(line2)) break;
|
|
7819
|
+
end++;
|
|
7820
|
+
}
|
|
7821
|
+
const block = lines.slice(index, end);
|
|
7822
|
+
if (CODEX_HOOK_SUBCOMMANDS.some((subcommand) => block.includes(` ${subcommand}"`))) {
|
|
7823
|
+
while (output2.length > 0 && (output2[output2.length - 1].trim() === "" || /^\s*#/.test(output2[output2.length - 1]) && output2[output2.length - 1].includes("remem-mcp"))) {
|
|
7824
|
+
output2.pop();
|
|
7825
|
+
}
|
|
7826
|
+
} else {
|
|
7827
|
+
output2.push(...block);
|
|
7828
|
+
}
|
|
7829
|
+
index = end;
|
|
7830
|
+
}
|
|
7831
|
+
return output2.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
7832
|
+
}
|
|
7127
7833
|
var HOOKS_CONFIG = {
|
|
7128
7834
|
SessionStart: [
|
|
7129
7835
|
{
|
|
@@ -7214,17 +7920,17 @@ var HOOKS_CONFIG = {
|
|
|
7214
7920
|
]
|
|
7215
7921
|
};
|
|
7216
7922
|
function readJsonConfig(path) {
|
|
7217
|
-
if (!
|
|
7923
|
+
if (!existsSync9(path)) return {};
|
|
7218
7924
|
try {
|
|
7219
|
-
return JSON.parse(
|
|
7925
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
7220
7926
|
} catch {
|
|
7221
7927
|
return {};
|
|
7222
7928
|
}
|
|
7223
7929
|
}
|
|
7224
7930
|
function writeJsonConfig(path, data) {
|
|
7225
7931
|
const dir = dirname5(path);
|
|
7226
|
-
if (!
|
|
7227
|
-
|
|
7932
|
+
if (!existsSync9(dir)) mkdirSync6(dir, { recursive: true });
|
|
7933
|
+
writeFileSync6(path, `${JSON.stringify(data, null, 2)}
|
|
7228
7934
|
`, "utf-8");
|
|
7229
7935
|
}
|
|
7230
7936
|
function mergeHooks(config, hooks) {
|
|
@@ -7245,8 +7951,8 @@ function mergeHooks(config, hooks) {
|
|
|
7245
7951
|
};
|
|
7246
7952
|
}
|
|
7247
7953
|
function installDevinHooks() {
|
|
7248
|
-
const configPath =
|
|
7249
|
-
if (!
|
|
7954
|
+
const configPath = join10(homedir8(), ".config", "devin", "config.json");
|
|
7955
|
+
if (!existsSync9(dirname5(configPath))) {
|
|
7250
7956
|
return false;
|
|
7251
7957
|
}
|
|
7252
7958
|
const config = readJsonConfig(configPath);
|
|
@@ -7256,9 +7962,9 @@ function installDevinHooks() {
|
|
|
7256
7962
|
return true;
|
|
7257
7963
|
}
|
|
7258
7964
|
function installClaudeCodeHooks() {
|
|
7259
|
-
const settingsPath =
|
|
7260
|
-
const claudeDir =
|
|
7261
|
-
if (!
|
|
7965
|
+
const settingsPath = join10(homedir8(), ".claude", "settings.json");
|
|
7966
|
+
const claudeDir = join10(homedir8(), ".claude");
|
|
7967
|
+
if (!existsSync9(claudeDir)) {
|
|
7262
7968
|
return false;
|
|
7263
7969
|
}
|
|
7264
7970
|
const { PostCompaction: _omit, ...baseWithoutPostCompaction } = HOOKS_CONFIG;
|
|
@@ -7294,12 +8000,12 @@ function installClaudeCodeHooks() {
|
|
|
7294
8000
|
return true;
|
|
7295
8001
|
}
|
|
7296
8002
|
function installCodexHooks() {
|
|
7297
|
-
const configPath =
|
|
7298
|
-
if (!
|
|
8003
|
+
const configPath = join10(homedir8(), ".codex", "config.toml");
|
|
8004
|
+
if (!existsSync9(configPath)) {
|
|
7299
8005
|
return false;
|
|
7300
8006
|
}
|
|
7301
|
-
let content =
|
|
7302
|
-
if (content
|
|
8007
|
+
let content = readFileSync7(configPath, "utf-8");
|
|
8008
|
+
if (hasCodexRememHooks(content)) {
|
|
7303
8009
|
console.log(` Codex CLI: Hooks already installed in ${configPath}`);
|
|
7304
8010
|
return true;
|
|
7305
8011
|
}
|
|
@@ -7371,7 +8077,7 @@ timeout = 10
|
|
|
7371
8077
|
# <<< remem-mcp SessionEnd <<<
|
|
7372
8078
|
`;
|
|
7373
8079
|
content = content.trimEnd() + "\n" + hooksToml;
|
|
7374
|
-
|
|
8080
|
+
writeFileSync6(configPath, content, "utf-8");
|
|
7375
8081
|
console.log(` Codex CLI: Hooks wired into ${configPath}`);
|
|
7376
8082
|
console.log(` Note: Set sandbox_mode = "danger-full-access" for MCP tools to work.`);
|
|
7377
8083
|
return true;
|
|
@@ -7416,8 +8122,8 @@ async function uninstallHooks() {
|
|
|
7416
8122
|
"PostCompaction",
|
|
7417
8123
|
"UserPromptSubmit"
|
|
7418
8124
|
];
|
|
7419
|
-
const devinPath =
|
|
7420
|
-
if (
|
|
8125
|
+
const devinPath = join10(homedir8(), ".config", "devin", "config.json");
|
|
8126
|
+
if (existsSync9(devinPath)) {
|
|
7421
8127
|
const config = readJsonConfig(devinPath);
|
|
7422
8128
|
if (config.hooks) {
|
|
7423
8129
|
const hooks = config.hooks;
|
|
@@ -7441,8 +8147,8 @@ async function uninstallHooks() {
|
|
|
7441
8147
|
removed++;
|
|
7442
8148
|
}
|
|
7443
8149
|
}
|
|
7444
|
-
const claudePath =
|
|
7445
|
-
if (
|
|
8150
|
+
const claudePath = join10(homedir8(), ".claude", "settings.json");
|
|
8151
|
+
if (existsSync9(claudePath)) {
|
|
7446
8152
|
const config = readJsonConfig(claudePath);
|
|
7447
8153
|
if (config.hooks) {
|
|
7448
8154
|
const hooks = config.hooks;
|
|
@@ -7466,12 +8172,13 @@ async function uninstallHooks() {
|
|
|
7466
8172
|
removed++;
|
|
7467
8173
|
}
|
|
7468
8174
|
}
|
|
7469
|
-
const codexPath =
|
|
7470
|
-
if (
|
|
7471
|
-
let content =
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
8175
|
+
const codexPath = join10(homedir8(), ".codex", "config.toml");
|
|
8176
|
+
if (existsSync9(codexPath)) {
|
|
8177
|
+
let content = readFileSync7(codexPath, "utf-8");
|
|
8178
|
+
const cleaned = removeCodexHooksFromToml(content);
|
|
8179
|
+
if (cleaned !== content) {
|
|
8180
|
+
content = cleaned;
|
|
8181
|
+
writeFileSync6(codexPath, content.trimEnd() + "\n", "utf-8");
|
|
7475
8182
|
console.log(` Codex CLI: Hooks removed from ${codexPath}`);
|
|
7476
8183
|
removed++;
|
|
7477
8184
|
}
|
|
@@ -7487,22 +8194,22 @@ Hooks removed from ${removed} agent(s).`);
|
|
|
7487
8194
|
// src/import.ts
|
|
7488
8195
|
init_esm_shims();
|
|
7489
8196
|
init_ulid();
|
|
7490
|
-
import { existsSync as
|
|
7491
|
-
import { dirname as dirname6, join as
|
|
8197
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
8198
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
7492
8199
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7493
8200
|
import Database9 from "better-sqlite3";
|
|
7494
8201
|
import * as sqliteVec3 from "sqlite-vec";
|
|
7495
8202
|
var __dirname3 = dirname6(fileURLToPath3(import.meta.url));
|
|
7496
|
-
function
|
|
8203
|
+
function ensureSchema3(db) {
|
|
7497
8204
|
const candidates = [
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
8205
|
+
join11(__dirname3, "storage", "schema.sql"),
|
|
8206
|
+
join11(__dirname3, "schema.sql"),
|
|
8207
|
+
join11(__dirname3, "..", "storage", "schema.sql")
|
|
7501
8208
|
];
|
|
7502
8209
|
let schema = null;
|
|
7503
8210
|
for (const path of candidates) {
|
|
7504
8211
|
try {
|
|
7505
|
-
schema =
|
|
8212
|
+
schema = readFileSync8(path, "utf-8");
|
|
7506
8213
|
break;
|
|
7507
8214
|
} catch {
|
|
7508
8215
|
}
|
|
@@ -7513,11 +8220,11 @@ function ensureSchema2(db) {
|
|
|
7513
8220
|
db.exec(schema);
|
|
7514
8221
|
}
|
|
7515
8222
|
function importData(dbPath, inputPath) {
|
|
7516
|
-
if (!
|
|
8223
|
+
if (!existsSync10(inputPath)) {
|
|
7517
8224
|
console.error(`Error: File not found: ${inputPath}`);
|
|
7518
8225
|
process.exit(1);
|
|
7519
8226
|
}
|
|
7520
|
-
const raw =
|
|
8227
|
+
const raw = readFileSync8(inputPath, "utf-8");
|
|
7521
8228
|
let data;
|
|
7522
8229
|
try {
|
|
7523
8230
|
data = JSON.parse(raw);
|
|
@@ -7533,7 +8240,7 @@ function importData(dbPath, inputPath) {
|
|
|
7533
8240
|
db.pragma("journal_mode = WAL");
|
|
7534
8241
|
db.pragma("synchronous = NORMAL");
|
|
7535
8242
|
sqliteVec3.load(db);
|
|
7536
|
-
|
|
8243
|
+
ensureSchema3(db);
|
|
7537
8244
|
let inserted = 0;
|
|
7538
8245
|
let skipped = 0;
|
|
7539
8246
|
let messagesInserted = 0;
|
|
@@ -7591,9 +8298,9 @@ function importData(dbPath, inputPath) {
|
|
|
7591
8298
|
|
|
7592
8299
|
// src/install-mcp.ts
|
|
7593
8300
|
init_esm_shims();
|
|
7594
|
-
import { existsSync as
|
|
7595
|
-
import { homedir as
|
|
7596
|
-
import { dirname as dirname7, join as
|
|
8301
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
8302
|
+
import { homedir as homedir9 } from "os";
|
|
8303
|
+
import { dirname as dirname7, join as join12 } from "path";
|
|
7597
8304
|
var MCP_SERVER_ENTRY = {
|
|
7598
8305
|
command: "npx",
|
|
7599
8306
|
args: ["-y", "remem-mcp"]
|
|
@@ -7608,26 +8315,26 @@ var MCP_SERVER_ENTRY_WITH_GLOBAL = {
|
|
|
7608
8315
|
var JSON_TARGETS = [
|
|
7609
8316
|
{
|
|
7610
8317
|
name: "Claude Code",
|
|
7611
|
-
path:
|
|
8318
|
+
path: join12(homedir9(), ".claude.json"),
|
|
7612
8319
|
key: "mcpServers"
|
|
7613
8320
|
},
|
|
7614
8321
|
{
|
|
7615
8322
|
name: "Devin CLI",
|
|
7616
|
-
path:
|
|
8323
|
+
path: join12(homedir9(), ".config", "devin", "mcp_config.json"),
|
|
7617
8324
|
key: "mcpServers",
|
|
7618
8325
|
useGlobal: true
|
|
7619
8326
|
},
|
|
7620
8327
|
{
|
|
7621
8328
|
name: "Cursor",
|
|
7622
|
-
path:
|
|
8329
|
+
path: join12(homedir9(), ".cursor", "mcp.json"),
|
|
7623
8330
|
key: "mcpServers"
|
|
7624
8331
|
}
|
|
7625
8332
|
];
|
|
7626
8333
|
function registerJsonServer(target) {
|
|
7627
8334
|
let config = {};
|
|
7628
|
-
if (
|
|
8335
|
+
if (existsSync11(target.path)) {
|
|
7629
8336
|
try {
|
|
7630
|
-
config = JSON.parse(
|
|
8337
|
+
config = JSON.parse(readFileSync9(target.path, "utf-8"));
|
|
7631
8338
|
} catch {
|
|
7632
8339
|
console.log(` ${target.name}: Config file unreadable, skipping.`);
|
|
7633
8340
|
return false;
|
|
@@ -7642,10 +8349,10 @@ function registerJsonServer(target) {
|
|
|
7642
8349
|
servers["remem-mcp"] = entry;
|
|
7643
8350
|
config[target.key] = servers;
|
|
7644
8351
|
const dir = dirname7(target.path);
|
|
7645
|
-
if (!
|
|
7646
|
-
|
|
8352
|
+
if (!existsSync11(dir)) {
|
|
8353
|
+
mkdirSync7(dir, { recursive: true });
|
|
7647
8354
|
}
|
|
7648
|
-
|
|
8355
|
+
writeFileSync7(target.path, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
7649
8356
|
console.log(` ${target.name}: MCP server registered.`);
|
|
7650
8357
|
return true;
|
|
7651
8358
|
}
|
|
@@ -7654,12 +8361,12 @@ async function installMcpServer() {
|
|
|
7654
8361
|
let count = 0;
|
|
7655
8362
|
for (const target of JSON_TARGETS) {
|
|
7656
8363
|
const agentDir = dirname7(target.path);
|
|
7657
|
-
if (!
|
|
8364
|
+
if (!existsSync11(agentDir)) {
|
|
7658
8365
|
if (target.name === "Cursor") {
|
|
7659
8366
|
const cursorApp = "/Applications/Cursor.app";
|
|
7660
|
-
const cursorAppHome =
|
|
7661
|
-
if (
|
|
7662
|
-
|
|
8367
|
+
const cursorAppHome = join12(homedir9(), "Library", "Application Support", "Cursor");
|
|
8368
|
+
if (existsSync11(cursorApp) || existsSync11(cursorAppHome)) {
|
|
8369
|
+
mkdirSync7(agentDir, { recursive: true });
|
|
7663
8370
|
} else {
|
|
7664
8371
|
continue;
|
|
7665
8372
|
}
|
|
@@ -7671,9 +8378,9 @@ async function installMcpServer() {
|
|
|
7671
8378
|
count++;
|
|
7672
8379
|
}
|
|
7673
8380
|
}
|
|
7674
|
-
const codexConfig =
|
|
7675
|
-
if (
|
|
7676
|
-
const content =
|
|
8381
|
+
const codexConfig = join12(homedir9(), ".codex", "config.toml");
|
|
8382
|
+
if (existsSync11(codexConfig)) {
|
|
8383
|
+
const content = readFileSync9(codexConfig, "utf-8");
|
|
7677
8384
|
if (content.includes("[mcp_servers.remem-mcp]")) {
|
|
7678
8385
|
console.log(" Codex CLI: Already registered.");
|
|
7679
8386
|
count++;
|
|
@@ -7686,7 +8393,7 @@ args = ["-y", "remem-mcp"]
|
|
|
7686
8393
|
[mcp_servers.remem-mcp.env]
|
|
7687
8394
|
REMEM_GLOBAL_SESSION_KEY = "global"
|
|
7688
8395
|
`;
|
|
7689
|
-
|
|
8396
|
+
writeFileSync7(codexConfig, content + tomlEntry, "utf-8");
|
|
7690
8397
|
console.log(" Codex CLI: MCP server registered.");
|
|
7691
8398
|
count++;
|
|
7692
8399
|
}
|
|
@@ -7702,18 +8409,18 @@ MCP server registered in ${count} agent config(s).`);
|
|
|
7702
8409
|
|
|
7703
8410
|
// src/install-skill.ts
|
|
7704
8411
|
init_esm_shims();
|
|
7705
|
-
import { existsSync as
|
|
7706
|
-
import { homedir as
|
|
7707
|
-
import { dirname as dirname8, join as
|
|
8412
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
|
|
8413
|
+
import { homedir as homedir10 } from "os";
|
|
8414
|
+
import { dirname as dirname8, join as join13 } from "path";
|
|
7708
8415
|
function loadSkillContent() {
|
|
7709
8416
|
const candidates = [
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
8417
|
+
join13(process.cwd(), "skills", "remem-mcp", "SKILL.md"),
|
|
8418
|
+
join13(__dirname, "..", "skills", "remem-mcp", "SKILL.md"),
|
|
8419
|
+
join13(__dirname, "skills", "remem-mcp", "SKILL.md")
|
|
7713
8420
|
];
|
|
7714
8421
|
for (const path of candidates) {
|
|
7715
8422
|
try {
|
|
7716
|
-
return
|
|
8423
|
+
return readFileSync10(path, "utf-8");
|
|
7717
8424
|
} catch {
|
|
7718
8425
|
}
|
|
7719
8426
|
}
|
|
@@ -7724,19 +8431,19 @@ function loadSkillContent() {
|
|
|
7724
8431
|
var SKILL_TARGETS = [
|
|
7725
8432
|
{
|
|
7726
8433
|
name: "Devin CLI",
|
|
7727
|
-
path:
|
|
8434
|
+
path: join13(homedir10(), ".config", "devin", "skills", "remem-mcp", "SKILL.md")
|
|
7728
8435
|
},
|
|
7729
8436
|
{
|
|
7730
8437
|
name: "Claude Code",
|
|
7731
|
-
path:
|
|
8438
|
+
path: join13(homedir10(), ".claude", "skills", "remem-mcp", "SKILL.md")
|
|
7732
8439
|
},
|
|
7733
8440
|
{
|
|
7734
8441
|
name: "Codex CLI",
|
|
7735
|
-
path:
|
|
8442
|
+
path: join13(homedir10(), ".codex", "skills", "remem-mcp", "SKILL.md")
|
|
7736
8443
|
},
|
|
7737
8444
|
{
|
|
7738
8445
|
name: "Generic (.agents)",
|
|
7739
|
-
path:
|
|
8446
|
+
path: join13(homedir10(), ".agents", "skills", "remem-mcp", "SKILL.md")
|
|
7740
8447
|
}
|
|
7741
8448
|
];
|
|
7742
8449
|
async function installSkill() {
|
|
@@ -7744,15 +8451,15 @@ async function installSkill() {
|
|
|
7744
8451
|
let installed = 0;
|
|
7745
8452
|
for (const target of SKILL_TARGETS) {
|
|
7746
8453
|
const dir = dirname8(target.path);
|
|
7747
|
-
if (!
|
|
7748
|
-
|
|
8454
|
+
if (!existsSync12(dir)) {
|
|
8455
|
+
mkdirSync8(dir, { recursive: true });
|
|
7749
8456
|
}
|
|
7750
|
-
if (
|
|
8457
|
+
if (existsSync12(target.path)) {
|
|
7751
8458
|
console.log(` ${target.name}: Already installed. Updated.`);
|
|
7752
8459
|
} else {
|
|
7753
8460
|
console.log(` ${target.name}: Installed.`);
|
|
7754
8461
|
}
|
|
7755
|
-
|
|
8462
|
+
writeFileSync8(target.path, skillContent, "utf-8");
|
|
7756
8463
|
installed++;
|
|
7757
8464
|
}
|
|
7758
8465
|
console.log(`
|
|
@@ -7765,47 +8472,268 @@ Skill installed to ${installed} location(s).`);
|
|
|
7765
8472
|
console.log(" - Forget only on explicit user request");
|
|
7766
8473
|
}
|
|
7767
8474
|
|
|
7768
|
-
// src/
|
|
8475
|
+
// src/pipeline/mermaid.ts
|
|
7769
8476
|
init_esm_shims();
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
if (!existsSync12(dir)) mkdirSync8(dir, { recursive: true });
|
|
7782
|
-
}
|
|
7783
|
-
}
|
|
7784
|
-
/** Log a tool call. */
|
|
7785
|
-
log(entry) {
|
|
7786
|
-
if (!this.enabled) return;
|
|
7787
|
-
const fullEntry = {
|
|
7788
|
-
ts: Date.now(),
|
|
7789
|
-
...entry
|
|
8477
|
+
init_ulid();
|
|
8478
|
+
var MermaidPipeline = class {
|
|
8479
|
+
name = "mermaid";
|
|
8480
|
+
requiresLLM = false;
|
|
8481
|
+
async process(input, ctx) {
|
|
8482
|
+
const nodeId = generateId();
|
|
8483
|
+
const label = extractNodeLabel(input);
|
|
8484
|
+
const node = {
|
|
8485
|
+
id: nodeId,
|
|
8486
|
+
label,
|
|
8487
|
+
captureId: input.id
|
|
7790
8488
|
};
|
|
7791
|
-
const
|
|
8489
|
+
const edges = [];
|
|
7792
8490
|
try {
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
7796
|
-
|
|
8491
|
+
const prevNode = await ctx.storage.getLatestCanvasNode(input.sessionKey);
|
|
8492
|
+
if (prevNode) {
|
|
8493
|
+
edges.push({
|
|
8494
|
+
from: prevNode.id,
|
|
8495
|
+
to: nodeId,
|
|
8496
|
+
label: inferEdgeLabel2(prevNode, input)
|
|
8497
|
+
});
|
|
8498
|
+
}
|
|
8499
|
+
} catch {
|
|
7797
8500
|
}
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
7801
|
-
|
|
7802
|
-
|
|
8501
|
+
try {
|
|
8502
|
+
await ctx.storage.appendCanvasNode(input.sessionKey, node, edges);
|
|
8503
|
+
} catch {
|
|
8504
|
+
}
|
|
8505
|
+
try {
|
|
8506
|
+
await ctx.storage.writeRef(input.sessionKey, nodeId, input.content);
|
|
8507
|
+
} catch {
|
|
8508
|
+
}
|
|
8509
|
+
const canvas = { nodes: [node], edges };
|
|
8510
|
+
return { canvas };
|
|
8511
|
+
}
|
|
8512
|
+
};
|
|
8513
|
+
var LLMMermaidPipeline = class {
|
|
8514
|
+
constructor(llm) {
|
|
8515
|
+
this.llm = llm;
|
|
8516
|
+
}
|
|
8517
|
+
llm;
|
|
8518
|
+
name = "mermaid-llm";
|
|
8519
|
+
requiresLLM = true;
|
|
8520
|
+
async process(input, ctx) {
|
|
8521
|
+
const nodeId = generateId();
|
|
8522
|
+
let label = extractNodeLabel(input);
|
|
8523
|
+
try {
|
|
8524
|
+
const prompt2 = `Summarize this ${input.type} in max 40 characters. Output ONLY the summary, no quotes:
|
|
8525
|
+
|
|
8526
|
+
${input.content.slice(0, 500)}`;
|
|
8527
|
+
const response = await this.llm.complete(prompt2);
|
|
8528
|
+
label = response.trim().slice(0, 40);
|
|
8529
|
+
} catch {
|
|
8530
|
+
}
|
|
8531
|
+
const node = { id: nodeId, label, captureId: input.id };
|
|
8532
|
+
const edges = [];
|
|
8533
|
+
try {
|
|
8534
|
+
const prevNode = await ctx.storage.getLatestCanvasNode(input.sessionKey);
|
|
8535
|
+
if (prevNode) {
|
|
8536
|
+
let edgeLabel = inferEdgeLabel2(prevNode, input);
|
|
8537
|
+
try {
|
|
8538
|
+
const edgePrompt = `In one word, describe the transition from "${prevNode.label}" to "${label}". Output ONLY the word:`;
|
|
8539
|
+
const edgeResp = await this.llm.complete(edgePrompt);
|
|
8540
|
+
edgeLabel = edgeResp.trim().slice(0, 20);
|
|
8541
|
+
} catch {
|
|
8542
|
+
}
|
|
8543
|
+
edges.push({ from: prevNode.id, to: nodeId, label: edgeLabel });
|
|
8544
|
+
}
|
|
8545
|
+
} catch {
|
|
8546
|
+
}
|
|
8547
|
+
try {
|
|
8548
|
+
await ctx.storage.appendCanvasNode(input.sessionKey, node, edges);
|
|
8549
|
+
} catch {
|
|
8550
|
+
}
|
|
8551
|
+
try {
|
|
8552
|
+
await ctx.storage.writeRef(input.sessionKey, nodeId, input.content);
|
|
8553
|
+
} catch {
|
|
8554
|
+
}
|
|
8555
|
+
return { canvas: { nodes: [node], edges } };
|
|
8556
|
+
}
|
|
8557
|
+
};
|
|
8558
|
+
function extractNodeLabel(input) {
|
|
8559
|
+
const content = input.content.trim();
|
|
8560
|
+
if (input.type === "error") {
|
|
8561
|
+
const errorMatch = content.match(/Error[:\s]+(.+)/i);
|
|
8562
|
+
if (errorMatch) return truncate(errorMatch[1].trim(), 40);
|
|
8563
|
+
}
|
|
8564
|
+
if (input.type === "decision") {
|
|
8565
|
+
const firstSentence = content.split(/[.!]\s/)[0];
|
|
8566
|
+
return truncate(firstSentence, 40);
|
|
8567
|
+
}
|
|
8568
|
+
if (input.type === "task") {
|
|
8569
|
+
return truncate(content.split("\n")[0], 40);
|
|
8570
|
+
}
|
|
8571
|
+
const firstLine = content.split("\n").find((l) => l.trim().length > 0);
|
|
8572
|
+
return truncate(firstLine ?? "untitled", 40);
|
|
8573
|
+
}
|
|
8574
|
+
function inferEdgeLabel2(prev, current) {
|
|
8575
|
+
if (current.type === "error") return "failed";
|
|
8576
|
+
if (current.type === "decision") return "decided";
|
|
8577
|
+
if (current.type === "task") return "executed";
|
|
8578
|
+
if (current.type === "learning") return "learned";
|
|
8579
|
+
return "next";
|
|
8580
|
+
}
|
|
8581
|
+
function truncate(s, maxLen) {
|
|
8582
|
+
if (s.length <= maxLen) return s;
|
|
8583
|
+
return s.slice(0, maxLen - 3) + "...";
|
|
8584
|
+
}
|
|
8585
|
+
|
|
8586
|
+
// src/pipeline/skill.ts
|
|
8587
|
+
init_esm_shims();
|
|
8588
|
+
init_ulid();
|
|
8589
|
+
var SkillExtractionPipeline = class {
|
|
8590
|
+
name = "skill";
|
|
8591
|
+
requiresLLM = false;
|
|
8592
|
+
async process(input, ctx) {
|
|
8593
|
+
if (input.type !== "task") return {};
|
|
8594
|
+
const steps = extractSteps(input.content);
|
|
8595
|
+
const triggers = extractTriggers(input.content, input.tags);
|
|
8596
|
+
const validation = extractValidationRules(input.content);
|
|
8597
|
+
if (steps.length < 2) return {};
|
|
8598
|
+
const skillName = extractSkillName(input.content) ?? `skill-${input.id.slice(-8)}`;
|
|
8599
|
+
const description = extractDescription(input.content);
|
|
8600
|
+
let skillId = null;
|
|
8601
|
+
try {
|
|
8602
|
+
const now = Date.now();
|
|
8603
|
+
skillId = generateId();
|
|
8604
|
+
await ctx.storage.putSkill({
|
|
8605
|
+
id: skillId,
|
|
8606
|
+
teamId: input.teamId ?? "default",
|
|
8607
|
+
agentId: "pipeline",
|
|
8608
|
+
name: skillName,
|
|
8609
|
+
description,
|
|
8610
|
+
content: input.content,
|
|
8611
|
+
version: 1,
|
|
8612
|
+
createdAt: now,
|
|
8613
|
+
updatedAt: now,
|
|
8614
|
+
triggerConditions: triggers,
|
|
8615
|
+
steps,
|
|
8616
|
+
validationRules: validation,
|
|
8617
|
+
sourceCaptureIds: [input.id],
|
|
8618
|
+
archived: false
|
|
8619
|
+
});
|
|
8620
|
+
} catch (e) {
|
|
8621
|
+
return {};
|
|
8622
|
+
}
|
|
8623
|
+
return { skill: { id: skillId, name: skillName, steps, triggers, validation } };
|
|
8624
|
+
}
|
|
8625
|
+
};
|
|
8626
|
+
function extractSteps(content) {
|
|
8627
|
+
const steps = [];
|
|
8628
|
+
const numberedMatch = content.match(/(?:^|\n)\s*(\d+)[.)]\s+(.+)/g);
|
|
8629
|
+
if (numberedMatch) {
|
|
8630
|
+
for (const match of numberedMatch) {
|
|
8631
|
+
const step = match.replace(/(?:^|\n)\s*\d+[.)]\s+/, "").trim();
|
|
8632
|
+
if (step.length > 5 && step.length < 200) steps.push(step);
|
|
8633
|
+
}
|
|
8634
|
+
}
|
|
8635
|
+
if (steps.length === 0) {
|
|
8636
|
+
const bulletMatch = content.match(/(?:^|\n)\s*[-*]\s+(.+)/g);
|
|
8637
|
+
if (bulletMatch) {
|
|
8638
|
+
for (const match of bulletMatch) {
|
|
8639
|
+
const step = match.replace(/(?:^|\n)\s*[-*]\s+/, "").trim();
|
|
8640
|
+
if (step.length > 5 && step.length < 200) steps.push(step);
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
if (steps.length === 0) {
|
|
8645
|
+
const stepMatch = content.match(/(?:^|\n)\s*Step\s+\d+:\s*(.+)/gi);
|
|
8646
|
+
if (stepMatch) {
|
|
8647
|
+
for (const match of stepMatch) {
|
|
8648
|
+
const step = match.replace(/(?:^|\n)\s*Step\s+\d+:\s*/i, "").trim();
|
|
8649
|
+
if (step.length > 5 && step.length < 200) steps.push(step);
|
|
8650
|
+
}
|
|
8651
|
+
}
|
|
8652
|
+
}
|
|
8653
|
+
return steps.slice(0, 10);
|
|
8654
|
+
}
|
|
8655
|
+
function extractTriggers(content, tags) {
|
|
8656
|
+
const triggers = /* @__PURE__ */ new Set();
|
|
8657
|
+
for (const tag of tags) {
|
|
8658
|
+
if (tag.length > 2 && tag !== "task") triggers.add(tag);
|
|
8659
|
+
}
|
|
8660
|
+
const firstWords = content.slice(0, 200).split(/\s+/).filter((w) => w.length > 4);
|
|
8661
|
+
for (const word of firstWords.slice(0, 5)) {
|
|
8662
|
+
triggers.add(word.toLowerCase().replace(/[^a-z0-9]/g, ""));
|
|
8663
|
+
}
|
|
8664
|
+
return Array.from(triggers).slice(0, 8);
|
|
8665
|
+
}
|
|
8666
|
+
function extractValidationRules(content) {
|
|
8667
|
+
const rules = [];
|
|
8668
|
+
const patterns = [
|
|
8669
|
+
/(?:verify|check|validate|confirm)\s+[^.]+/gi,
|
|
8670
|
+
/(?:success|succeeded|passed)\s+[^.]+/gi,
|
|
8671
|
+
/(?:expected|should)\s+[^.]+/gi
|
|
8672
|
+
];
|
|
8673
|
+
for (const pattern of patterns) {
|
|
8674
|
+
const matches = content.match(pattern);
|
|
8675
|
+
if (matches) {
|
|
8676
|
+
for (const match of matches.slice(0, 3)) {
|
|
8677
|
+
rules.push(match.trim());
|
|
8678
|
+
}
|
|
8679
|
+
}
|
|
8680
|
+
}
|
|
8681
|
+
return rules.slice(0, 5);
|
|
8682
|
+
}
|
|
8683
|
+
function extractSkillName(content) {
|
|
8684
|
+
const firstLine = content.split("\n")[0].trim();
|
|
8685
|
+
if (firstLine.length > 5 && firstLine.length < 60) {
|
|
8686
|
+
return firstLine.replace(/[^a-zA-Z0-9\s-]/g, "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
8687
|
+
}
|
|
8688
|
+
return null;
|
|
8689
|
+
}
|
|
8690
|
+
function extractDescription(content) {
|
|
8691
|
+
const firstSentence = content.split(/[.!]\s/)[0];
|
|
8692
|
+
return firstSentence.slice(0, 120);
|
|
8693
|
+
}
|
|
8694
|
+
|
|
8695
|
+
// src/security/audit.ts
|
|
8696
|
+
init_esm_shims();
|
|
8697
|
+
import { createHash as createHash4 } from "crypto";
|
|
8698
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync13, mkdirSync as mkdirSync9 } from "fs";
|
|
8699
|
+
import { dirname as dirname9 } from "path";
|
|
8700
|
+
var AuditLogger = class {
|
|
8701
|
+
logPath;
|
|
8702
|
+
enabled;
|
|
8703
|
+
constructor(logPath, enabled) {
|
|
8704
|
+
this.logPath = logPath;
|
|
8705
|
+
this.enabled = enabled;
|
|
8706
|
+
if (enabled) {
|
|
8707
|
+
const dir = dirname9(logPath);
|
|
8708
|
+
if (!existsSync13(dir)) mkdirSync9(dir, { recursive: true });
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
/** Log a tool call. */
|
|
8712
|
+
log(entry) {
|
|
8713
|
+
if (!this.enabled) return;
|
|
8714
|
+
const fullEntry = {
|
|
8715
|
+
ts: Date.now(),
|
|
8716
|
+
...entry
|
|
8717
|
+
};
|
|
8718
|
+
const line2 = JSON.stringify(fullEntry);
|
|
8719
|
+
try {
|
|
8720
|
+
appendFileSync3(this.logPath, `${line2}
|
|
8721
|
+
`);
|
|
8722
|
+
} catch (err) {
|
|
8723
|
+
console.error(`[remem-mcp] Audit log write failed: ${err}`);
|
|
8724
|
+
}
|
|
8725
|
+
}
|
|
8726
|
+
/** Hash the arguments for the audit log. The log does not store raw arguments. */
|
|
8727
|
+
static hashArgs(args) {
|
|
8728
|
+
const json = JSON.stringify(args);
|
|
8729
|
+
return createHash4("sha256").update(json).digest("hex");
|
|
7803
8730
|
}
|
|
7804
8731
|
};
|
|
7805
8732
|
|
|
7806
8733
|
// src/server.ts
|
|
7807
8734
|
init_esm_shims();
|
|
7808
8735
|
init_engine();
|
|
8736
|
+
import { execSync as execSync3 } from "child_process";
|
|
7809
8737
|
import { createHash as createHash5 } from "crypto";
|
|
7810
8738
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7811
8739
|
import {
|
|
@@ -7869,6 +8797,7 @@ function formatResults(results) {
|
|
|
7869
8797
|
|
|
7870
8798
|
// src/server.ts
|
|
7871
8799
|
init_ulid();
|
|
8800
|
+
var indexingStatus = /* @__PURE__ */ new Map();
|
|
7872
8801
|
function defaultSessionKey() {
|
|
7873
8802
|
if (process.env.REMEM_SESSION_KEY) return process.env.REMEM_SESSION_KEY;
|
|
7874
8803
|
const cwd = process.cwd();
|
|
@@ -8056,6 +8985,29 @@ var TOOLS = [
|
|
|
8056
8985
|
}
|
|
8057
8986
|
}
|
|
8058
8987
|
},
|
|
8988
|
+
{
|
|
8989
|
+
name: "feedback",
|
|
8990
|
+
description: "Record a quality signal for a recalled memory. Call this AFTER using a recall result: use 'helpful' if the memory answered your question, 'not_helpful' if it was irrelevant, 'stale' if the information is outdated, or 'wrong' if it is incorrect. This creates a feedback flywheel \u2014 useful memories rise, unhelpful ones fade. Do not call this for memories you have not actually used.",
|
|
8991
|
+
inputSchema: {
|
|
8992
|
+
type: "object",
|
|
8993
|
+
properties: {
|
|
8994
|
+
capture_id: {
|
|
8995
|
+
type: "string",
|
|
8996
|
+
description: "The capture ID from a recall/search result."
|
|
8997
|
+
},
|
|
8998
|
+
signal: {
|
|
8999
|
+
type: "string",
|
|
9000
|
+
enum: ["helpful", "not_helpful", "stale", "wrong"],
|
|
9001
|
+
description: "helpful = memory was useful (salience +0.1, max 2.0). not_helpful = memory was irrelevant (salience -0.1, min 0.1). stale = information is outdated (salience floored at 0.3). wrong = information is incorrect (salience floored at 0.1)."
|
|
9002
|
+
},
|
|
9003
|
+
reason: {
|
|
9004
|
+
type: "string",
|
|
9005
|
+
description: "Optional explanation for the feedback signal."
|
|
9006
|
+
}
|
|
9007
|
+
},
|
|
9008
|
+
required: ["capture_id", "signal"]
|
|
9009
|
+
}
|
|
9010
|
+
},
|
|
8059
9011
|
{
|
|
8060
9012
|
name: "search",
|
|
8061
9013
|
description: "Search memory by keyword or by semantic similarity. Use this tool when recall is too broad and you need specific facts. Automatically searches both project and global cross-project memory when REMEM_GLOBAL_SESSION_KEY is configured.",
|
|
@@ -8099,6 +9051,11 @@ var TOOLS = [
|
|
|
8099
9051
|
enum: ["text", "json"],
|
|
8100
9052
|
default: "text",
|
|
8101
9053
|
description: "The response format. Use 'json' for structured data (e.g. benchmarks). Defaults to 'text'."
|
|
9054
|
+
},
|
|
9055
|
+
explain: {
|
|
9056
|
+
type: "boolean",
|
|
9057
|
+
default: false,
|
|
9058
|
+
description: "When true, attach per-hit score_details explaining ranking (BM25 rank, vector score, entity matches, authority multiplier, feedback salience, link provenance). Useful for debugging search quality."
|
|
8102
9059
|
}
|
|
8103
9060
|
},
|
|
8104
9061
|
required: ["query"]
|
|
@@ -8492,7 +9449,7 @@ var TOOLS = [
|
|
|
8492
9449
|
},
|
|
8493
9450
|
{
|
|
8494
9451
|
name: "codegraph_search",
|
|
8495
|
-
description: "Search for code symbols by name. Returns matching functions, classes, methods, etc. with file paths and line numbers. Use this INSTEAD of grep when looking for function/class/method definitions. If the codebase hasn't been indexed yet, this
|
|
9452
|
+
description: "Search for code symbols by name. Returns matching functions, classes, methods, etc. with file paths and line numbers. Use this INSTEAD of grep when looking for function/class/method definitions. If the codebase hasn't been indexed yet, this starts background indexing and asks you to retry in a few seconds \u2014 non-blocking, so you can continue working while indexing runs.",
|
|
8496
9453
|
inputSchema: {
|
|
8497
9454
|
type: "object",
|
|
8498
9455
|
properties: {
|
|
@@ -8597,6 +9554,20 @@ var TOOLS = [
|
|
|
8597
9554
|
required: ["repo_path"]
|
|
8598
9555
|
}
|
|
8599
9556
|
},
|
|
9557
|
+
{
|
|
9558
|
+
name: "codegraph_stats",
|
|
9559
|
+
description: "Return indexed CodeGraph statistics: total symbols, calls, resolved calls, imports, and file counts. Optionally scoped to a repo_path. Use this to check indexing coverage before searching.",
|
|
9560
|
+
inputSchema: {
|
|
9561
|
+
type: "object",
|
|
9562
|
+
properties: {
|
|
9563
|
+
repo_path: {
|
|
9564
|
+
type: "string",
|
|
9565
|
+
description: "Optional: scope stats to this repository root path."
|
|
9566
|
+
},
|
|
9567
|
+
team_id: { type: "string", description: "The team ID for isolation." }
|
|
9568
|
+
}
|
|
9569
|
+
}
|
|
9570
|
+
},
|
|
8600
9571
|
// ─── Wiki tools ───
|
|
8601
9572
|
{
|
|
8602
9573
|
name: "wiki_ingest",
|
|
@@ -8870,6 +9841,96 @@ var TOOLS = [
|
|
|
8870
9841
|
type: "object",
|
|
8871
9842
|
properties: {}
|
|
8872
9843
|
}
|
|
9844
|
+
},
|
|
9845
|
+
{
|
|
9846
|
+
name: "canvas_get",
|
|
9847
|
+
description: "Get the Mermaid task canvas for the current session. The canvas is a symbolic graph of tool calls and state transitions \u2014 it replaces verbose tool logs in your context with a compact Mermaid diagram. Use this to see the task structure without re-reading full tool outputs. Requires REMEM_OFFLOAD_ENABLED=true and pipeline=mermaid.",
|
|
9848
|
+
inputSchema: {
|
|
9849
|
+
type: "object",
|
|
9850
|
+
properties: {
|
|
9851
|
+
session_key: {
|
|
9852
|
+
type: "string",
|
|
9853
|
+
description: "The session key. Defaults to hash(cwd). Use this to get the canvas from a different session."
|
|
9854
|
+
},
|
|
9855
|
+
format: {
|
|
9856
|
+
type: "string",
|
|
9857
|
+
enum: ["mermaid", "json"],
|
|
9858
|
+
default: "mermaid",
|
|
9859
|
+
description: "Output format. 'mermaid' returns the Mermaid graph text. 'json' returns structured nodes/edges."
|
|
9860
|
+
}
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
},
|
|
9864
|
+
{
|
|
9865
|
+
name: "ref_read",
|
|
9866
|
+
description: "Read the raw tool output for a specific canvas node by node_id. Use this to drill down from the Mermaid canvas to the full output when you need details. Requires REMEM_OFFLOAD_ENABLED=true and pipeline=mermaid.",
|
|
9867
|
+
inputSchema: {
|
|
9868
|
+
type: "object",
|
|
9869
|
+
properties: {
|
|
9870
|
+
node_id: {
|
|
9871
|
+
type: "string",
|
|
9872
|
+
description: "The node_id from the Mermaid canvas (e.g., '01M0FFZKQT540SVZPAZ4EZVE4G')."
|
|
9873
|
+
}
|
|
9874
|
+
},
|
|
9875
|
+
required: ["node_id"]
|
|
9876
|
+
}
|
|
9877
|
+
},
|
|
9878
|
+
{
|
|
9879
|
+
name: "skill_create",
|
|
9880
|
+
description: "Create a reusable skill (SOP) from a successful task or conversation. Skills are injected into recall when matching trigger conditions are met. Use this after completing a non-trivial task to capture the workflow for reuse.",
|
|
9881
|
+
inputSchema: {
|
|
9882
|
+
type: "object",
|
|
9883
|
+
properties: {
|
|
9884
|
+
name: {
|
|
9885
|
+
type: "string",
|
|
9886
|
+
description: "Short name for the skill (e.g., 'deploy-to-vercel')."
|
|
9887
|
+
},
|
|
9888
|
+
description: {
|
|
9889
|
+
type: "string",
|
|
9890
|
+
description: "One-line description of what the skill does."
|
|
9891
|
+
},
|
|
9892
|
+
content: {
|
|
9893
|
+
type: "string",
|
|
9894
|
+
description: "Full SOP content in markdown. Include steps, commands, and validation."
|
|
9895
|
+
},
|
|
9896
|
+
trigger_conditions: {
|
|
9897
|
+
type: "array",
|
|
9898
|
+
items: { type: "string" },
|
|
9899
|
+
description: "Keywords or patterns that trigger this skill (e.g., ['deploy', 'vercel', 'production'])."
|
|
9900
|
+
},
|
|
9901
|
+
steps: {
|
|
9902
|
+
type: "array",
|
|
9903
|
+
items: { type: "string" },
|
|
9904
|
+
description: "Ordered execution steps."
|
|
9905
|
+
},
|
|
9906
|
+
validation_rules: {
|
|
9907
|
+
type: "array",
|
|
9908
|
+
items: { type: "string" },
|
|
9909
|
+
description: "How to verify the skill succeeded (e.g., ['curl returns 200', 'no errors in logs'])."
|
|
9910
|
+
},
|
|
9911
|
+
source_capture_ids: {
|
|
9912
|
+
type: "array",
|
|
9913
|
+
items: { type: "string" },
|
|
9914
|
+
description: "Capture IDs that this skill was extracted from (for traceability)."
|
|
9915
|
+
},
|
|
9916
|
+
...TENANT_PARAMS
|
|
9917
|
+
},
|
|
9918
|
+
required: ["name", "content"]
|
|
9919
|
+
}
|
|
9920
|
+
},
|
|
9921
|
+
{
|
|
9922
|
+
name: "skill_archive",
|
|
9923
|
+
description: "Archive a skill \u2014 forces it to always be injected into recall, even when the query doesn't strongly match. Use this for critical SOPs that must always be available.",
|
|
9924
|
+
inputSchema: {
|
|
9925
|
+
type: "object",
|
|
9926
|
+
properties: {
|
|
9927
|
+
id: {
|
|
9928
|
+
type: "string",
|
|
9929
|
+
description: "The skill ID to archive."
|
|
9930
|
+
}
|
|
9931
|
+
},
|
|
9932
|
+
required: ["id"]
|
|
9933
|
+
}
|
|
8873
9934
|
}
|
|
8874
9935
|
];
|
|
8875
9936
|
var CORE_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -9004,6 +10065,8 @@ function createServer(opts) {
|
|
|
9004
10065
|
return handleRecall(args, opts);
|
|
9005
10066
|
case "capture":
|
|
9006
10067
|
return handleCapture(args, opts);
|
|
10068
|
+
case "feedback":
|
|
10069
|
+
return handleFeedback(args, opts);
|
|
9007
10070
|
case "search":
|
|
9008
10071
|
return handleSearch(args, opts);
|
|
9009
10072
|
case "related":
|
|
@@ -9050,6 +10113,8 @@ function createServer(opts) {
|
|
|
9050
10113
|
return handleCodegraphList(args, opts);
|
|
9051
10114
|
case "codegraph_detect_changes":
|
|
9052
10115
|
return handleCodegraphDetectChanges(args, opts);
|
|
10116
|
+
case "codegraph_stats":
|
|
10117
|
+
return handleCodegraphStats(args, opts);
|
|
9053
10118
|
case "wiki_ingest":
|
|
9054
10119
|
return handleWikiIngest(args, opts);
|
|
9055
10120
|
case "wiki_search":
|
|
@@ -9082,6 +10147,14 @@ function createServer(opts) {
|
|
|
9082
10147
|
return handleSessionCheckpoint(args, opts);
|
|
9083
10148
|
case "health":
|
|
9084
10149
|
return handleHealth(args, opts);
|
|
10150
|
+
case "canvas_get":
|
|
10151
|
+
return handleCanvasGet(args, opts);
|
|
10152
|
+
case "ref_read":
|
|
10153
|
+
return handleRefRead(args, opts);
|
|
10154
|
+
case "skill_create":
|
|
10155
|
+
return handleSkillCreate(args, opts);
|
|
10156
|
+
case "skill_archive":
|
|
10157
|
+
return handleSkillArchive(args, opts);
|
|
9085
10158
|
default:
|
|
9086
10159
|
return {
|
|
9087
10160
|
content: [{ type: "text", text: `Error: Unknown tool "${name}".` }],
|
|
@@ -9099,6 +10172,12 @@ function extractTenant(args) {
|
|
|
9099
10172
|
taskId: args.task_id
|
|
9100
10173
|
};
|
|
9101
10174
|
}
|
|
10175
|
+
function textResult(text) {
|
|
10176
|
+
return { content: [{ type: "text", text }] };
|
|
10177
|
+
}
|
|
10178
|
+
function errorResult(text) {
|
|
10179
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
10180
|
+
}
|
|
9102
10181
|
async function handleRecall(args, opts) {
|
|
9103
10182
|
const query = args.query;
|
|
9104
10183
|
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
@@ -9187,7 +10266,10 @@ async function handleRecall(args, opts) {
|
|
|
9187
10266
|
trust_state: r.entry.trustState ?? "candidate"
|
|
9188
10267
|
}));
|
|
9189
10268
|
const jsonText = JSON.stringify(
|
|
9190
|
-
vectorDegraded ? { results: jsonResults, _meta: { vector_degraded: true, channels_run: ["keyword"] } } : {
|
|
10269
|
+
vectorDegraded ? { results: jsonResults, _meta: { vector_degraded: true, channels_run: ["keyword"] } } : {
|
|
10270
|
+
results: jsonResults,
|
|
10271
|
+
_meta: { channels_run: mode === "vector" ? ["vector"] : ["keyword", "vector"] }
|
|
10272
|
+
}
|
|
9191
10273
|
);
|
|
9192
10274
|
opts.audit.log({
|
|
9193
10275
|
tool: "recall",
|
|
@@ -9234,9 +10316,7 @@ ${wikiLines.join("\n")}`;
|
|
|
9234
10316
|
limit: 3
|
|
9235
10317
|
});
|
|
9236
10318
|
if (scenarios.length > 0) {
|
|
9237
|
-
const scenarioLines = scenarios.map(
|
|
9238
|
-
(s) => ` ${s.summary}`
|
|
9239
|
-
);
|
|
10319
|
+
const scenarioLines = scenarios.map((s) => ` ${s.summary}`);
|
|
9240
10320
|
text += `
|
|
9241
10321
|
|
|
9242
10322
|
## Scenarios (L2 summaries)
|
|
@@ -9258,6 +10338,27 @@ ${text}`;
|
|
|
9258
10338
|
});
|
|
9259
10339
|
return { content: [{ type: "text", text: finalText }] };
|
|
9260
10340
|
}
|
|
10341
|
+
async function handleFeedback(args, opts) {
|
|
10342
|
+
const captureId = String(args.capture_id ?? "");
|
|
10343
|
+
const signal = String(args.signal ?? "");
|
|
10344
|
+
const reason = args.reason ? String(args.reason) : void 0;
|
|
10345
|
+
if (!captureId || !signal) {
|
|
10346
|
+
return errorResult("capture_id and signal are required");
|
|
10347
|
+
}
|
|
10348
|
+
const validSignals = ["helpful", "not_helpful", "stale", "wrong"];
|
|
10349
|
+
if (!validSignals.includes(signal)) {
|
|
10350
|
+
return errorResult(`signal must be one of: ${validSignals.join(", ")}`);
|
|
10351
|
+
}
|
|
10352
|
+
const storage = opts.storage;
|
|
10353
|
+
const entry = await storage.get(captureId);
|
|
10354
|
+
if (!entry) {
|
|
10355
|
+
return errorResult(`capture not found: ${captureId}`);
|
|
10356
|
+
}
|
|
10357
|
+
storage.recordFeedback(captureId, signal, reason, opts.agentId ?? detectAgentId());
|
|
10358
|
+
return textResult(
|
|
10359
|
+
`Recorded feedback: ${signal} for capture ${captureId.slice(-8)}. Salience multiplier adjusted. This memory will ${signal === "helpful" ? "rank higher" : signal === "not_helpful" ? "rank lower" : signal === "stale" ? "be deprioritized as outdated" : "be deprioritized as incorrect"} in future searches.`
|
|
10360
|
+
);
|
|
10361
|
+
}
|
|
9261
10362
|
async function handleCapture(args, opts) {
|
|
9262
10363
|
const type2 = args.type ?? "conversation";
|
|
9263
10364
|
const tags = args.tags ?? [];
|
|
@@ -9353,7 +10454,15 @@ async function handleCapture(args, opts) {
|
|
|
9353
10454
|
} else {
|
|
9354
10455
|
opts.audit.log({
|
|
9355
10456
|
tool: "capture",
|
|
9356
|
-
argsHash: AuditLogger.hashArgs({
|
|
10457
|
+
argsHash: AuditLogger.hashArgs({
|
|
10458
|
+
type: type2,
|
|
10459
|
+
tags,
|
|
10460
|
+
sessionKey,
|
|
10461
|
+
overrideReason,
|
|
10462
|
+
teamId,
|
|
10463
|
+
userId,
|
|
10464
|
+
taskId
|
|
10465
|
+
}),
|
|
9357
10466
|
resultLen: 0,
|
|
9358
10467
|
quotaHit: false,
|
|
9359
10468
|
redacted: wasRedacted
|
|
@@ -9417,12 +10526,14 @@ async function handleCapture(args, opts) {
|
|
|
9417
10526
|
};
|
|
9418
10527
|
try {
|
|
9419
10528
|
await opts.storage.put(entry);
|
|
9420
|
-
} catch {
|
|
10529
|
+
} catch (err) {
|
|
10530
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
10531
|
+
const isBusy = /busy|locked|SQLITE_BUSY/i.test(errMsg);
|
|
9421
10532
|
return {
|
|
9422
10533
|
content: [
|
|
9423
10534
|
{
|
|
9424
10535
|
type: "text",
|
|
9425
|
-
text: `Error: Database is read-only (sandbox restriction). Capture failed. Set sandbox_mode to "danger-full-access" or add the DB directory to writable roots.`
|
|
10536
|
+
text: isBusy ? `Error: Database is temporarily locked (concurrent write). Retry the capture.` : `Error: Database is read-only (sandbox restriction). Capture failed. Set sandbox_mode to "danger-full-access" or add the DB directory to writable roots.`
|
|
9426
10537
|
}
|
|
9427
10538
|
],
|
|
9428
10539
|
isError: true
|
|
@@ -9550,6 +10661,7 @@ async function handleSearch(args, opts) {
|
|
|
9550
10661
|
const query = args.query;
|
|
9551
10662
|
const mode = args.mode ?? "hybrid";
|
|
9552
10663
|
const format = args.format ?? "text";
|
|
10664
|
+
const explain = args.explain ?? false;
|
|
9553
10665
|
const filters = args.filters;
|
|
9554
10666
|
const limit = Math.min(args.limit ?? 20, 500);
|
|
9555
10667
|
let queryEmbedding = null;
|
|
@@ -9585,7 +10697,8 @@ async function handleSearch(args, opts) {
|
|
|
9585
10697
|
limit: projectLimit,
|
|
9586
10698
|
offset: 0,
|
|
9587
10699
|
mode,
|
|
9588
|
-
filters: searchFilters
|
|
10700
|
+
filters: searchFilters,
|
|
10701
|
+
explain
|
|
9589
10702
|
});
|
|
9590
10703
|
let globalResults = [];
|
|
9591
10704
|
try {
|
|
@@ -9594,7 +10707,8 @@ async function handleSearch(args, opts) {
|
|
|
9594
10707
|
limit: reservedGlobal,
|
|
9595
10708
|
offset: 0,
|
|
9596
10709
|
mode,
|
|
9597
|
-
filters: searchFilters
|
|
10710
|
+
filters: searchFilters,
|
|
10711
|
+
explain
|
|
9598
10712
|
});
|
|
9599
10713
|
} catch {
|
|
9600
10714
|
}
|
|
@@ -9607,7 +10721,8 @@ async function handleSearch(args, opts) {
|
|
|
9607
10721
|
limit,
|
|
9608
10722
|
offset: 0,
|
|
9609
10723
|
mode,
|
|
9610
|
-
filters: searchFilters
|
|
10724
|
+
filters: searchFilters,
|
|
10725
|
+
explain
|
|
9611
10726
|
});
|
|
9612
10727
|
}
|
|
9613
10728
|
results = dedupByContentPrefix(results, 60);
|
|
@@ -9638,7 +10753,10 @@ async function handleSearch(args, opts) {
|
|
|
9638
10753
|
trust_state: r.entry.trustState ?? "candidate"
|
|
9639
10754
|
}));
|
|
9640
10755
|
const jsonText = JSON.stringify(
|
|
9641
|
-
vectorDegraded ? { results: jsonResults, _meta: { vector_degraded: true, channels_run: ["keyword"] } } : {
|
|
10756
|
+
vectorDegraded ? { results: jsonResults, _meta: { vector_degraded: true, channels_run: ["keyword"] } } : {
|
|
10757
|
+
results: jsonResults,
|
|
10758
|
+
_meta: { channels_run: mode === "vector" ? ["vector"] : ["keyword", "vector"] }
|
|
10759
|
+
}
|
|
9642
10760
|
);
|
|
9643
10761
|
opts.audit.log({
|
|
9644
10762
|
tool: "search",
|
|
@@ -10368,6 +11486,7 @@ async function handleConfirm(args, opts) {
|
|
|
10368
11486
|
}
|
|
10369
11487
|
try {
|
|
10370
11488
|
db.prepare("UPDATE captures SET confirmations = confirmations + 1 WHERE id = ?").run(captureId);
|
|
11489
|
+
opts.storage.recordFeedback(captureId, "helpful", "auto: confirmed by agent", opts.agentId ?? detectAgentId());
|
|
10371
11490
|
} catch (err) {
|
|
10372
11491
|
return { content: [{ type: "text", text: `Error: ${err}` }], isError: true };
|
|
10373
11492
|
}
|
|
@@ -10378,7 +11497,7 @@ async function handleConfirm(args, opts) {
|
|
|
10378
11497
|
content: [
|
|
10379
11498
|
{
|
|
10380
11499
|
type: "text",
|
|
10381
|
-
text: `Confirmed capture ${captureId}. Confidence: ${confidence.toFixed(2)} (${row.confirmations + 1} confirmations, ${row.corrections} corrections).`
|
|
11500
|
+
text: `Confirmed capture ${captureId}. Confidence: ${confidence.toFixed(2)} (${row.confirmations + 1} confirmations, ${row.corrections} corrections). Feedback salience boosted.`
|
|
10382
11501
|
}
|
|
10383
11502
|
]
|
|
10384
11503
|
};
|
|
@@ -10407,6 +11526,7 @@ async function handleCorrect(args, opts) {
|
|
|
10407
11526
|
if (reason) {
|
|
10408
11527
|
db.prepare("UPDATE captures SET rejection_reason = ? WHERE id = ?").run(reason, captureId);
|
|
10409
11528
|
}
|
|
11529
|
+
opts.storage.recordFeedback(captureId, "wrong", reason ? `auto: ${reason}` : "auto: corrected by agent", opts.agentId ?? detectAgentId());
|
|
10410
11530
|
} catch (err) {
|
|
10411
11531
|
return { content: [{ type: "text", text: `Error: ${err}` }], isError: true };
|
|
10412
11532
|
}
|
|
@@ -11071,6 +12191,12 @@ async function handleKnowledgeList(args, opts) {
|
|
|
11071
12191
|
}
|
|
11072
12192
|
async function handleKnowledgeDelete(args, opts) {
|
|
11073
12193
|
const knowledgeIds = args.knowledge_ids;
|
|
12194
|
+
if (!Array.isArray(knowledgeIds) || knowledgeIds.length === 0) {
|
|
12195
|
+
return {
|
|
12196
|
+
content: [{ type: "text", text: "Error: knowledge_ids (non-empty array) is required." }],
|
|
12197
|
+
isError: true
|
|
12198
|
+
};
|
|
12199
|
+
}
|
|
11074
12200
|
let count;
|
|
11075
12201
|
try {
|
|
11076
12202
|
count = await opts.storage.deleteKnowledge(knowledgeIds);
|
|
@@ -11096,10 +12222,16 @@ async function handleScenarioConsolidate(args, opts) {
|
|
|
11096
12222
|
return { content: [{ type: "text", text: "Error: atom_ids is required." }], isError: true };
|
|
11097
12223
|
}
|
|
11098
12224
|
if (!summary || summary.trim().length < 10) {
|
|
11099
|
-
return {
|
|
12225
|
+
return {
|
|
12226
|
+
content: [{ type: "text", text: "Error: summary must be at least 10 characters." }],
|
|
12227
|
+
isError: true
|
|
12228
|
+
};
|
|
11100
12229
|
}
|
|
11101
12230
|
if (atomIds.length > 20) {
|
|
11102
|
-
return {
|
|
12231
|
+
return {
|
|
12232
|
+
content: [{ type: "text", text: "Error: Maximum 20 atom_ids per consolidation." }],
|
|
12233
|
+
isError: true
|
|
12234
|
+
};
|
|
11103
12235
|
}
|
|
11104
12236
|
const id = generateId();
|
|
11105
12237
|
const scenario = {
|
|
@@ -11139,11 +12271,14 @@ async function handlePersonaUpdate(args, opts) {
|
|
|
11139
12271
|
const { teamId, userId } = extractTenant(args);
|
|
11140
12272
|
const agentId = args.agent_id ?? detectAgentId();
|
|
11141
12273
|
if (!trait || !value) {
|
|
11142
|
-
return {
|
|
12274
|
+
return {
|
|
12275
|
+
content: [{ type: "text", text: "Error: trait and value are required." }],
|
|
12276
|
+
isError: true
|
|
12277
|
+
};
|
|
11143
12278
|
}
|
|
11144
12279
|
const tid = teamId ?? "default";
|
|
11145
12280
|
const uid = userId ?? "default";
|
|
11146
|
-
|
|
12281
|
+
const existing = await opts.storage.readPersona(tid, agentId, uid);
|
|
11147
12282
|
let content;
|
|
11148
12283
|
if (existing) {
|
|
11149
12284
|
const lines = existing.content.split("\n").filter((l) => l.trim());
|
|
@@ -11232,10 +12367,10 @@ async function handleCodegraphIndex(args, opts) {
|
|
|
11232
12367
|
return { content: [{ type: "text", text: "Error: path is required." }], isError: true };
|
|
11233
12368
|
}
|
|
11234
12369
|
const db = getDb(opts);
|
|
11235
|
-
const { statSync:
|
|
12370
|
+
const { statSync: statSync4 } = await import("fs");
|
|
11236
12371
|
let stat;
|
|
11237
12372
|
try {
|
|
11238
|
-
stat =
|
|
12373
|
+
stat = statSync4(path);
|
|
11239
12374
|
} catch {
|
|
11240
12375
|
return { content: [{ type: "text", text: `Error: path not found: ${path}` }], isError: true };
|
|
11241
12376
|
}
|
|
@@ -11283,41 +12418,80 @@ async function handleCodegraphSearch(args, opts) {
|
|
|
11283
12418
|
let symbols = searchSymbols(db, query, { teamId, kind, language, limit, repoPath });
|
|
11284
12419
|
if (symbols.length === 0) {
|
|
11285
12420
|
const cwd = process.cwd();
|
|
11286
|
-
const { existsSync:
|
|
12421
|
+
const { existsSync: existsSync17 } = await import("fs");
|
|
11287
12422
|
let indexPath;
|
|
11288
12423
|
let repoRoot;
|
|
11289
12424
|
if (customPath) {
|
|
11290
12425
|
indexPath = customPath;
|
|
11291
12426
|
repoRoot = customPath;
|
|
11292
12427
|
} else {
|
|
11293
|
-
indexPath =
|
|
12428
|
+
indexPath = existsSync17(`${cwd}/src`) ? `${cwd}/src` : cwd;
|
|
11294
12429
|
repoRoot = cwd;
|
|
11295
12430
|
}
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11300
|
-
|
|
11301
|
-
|
|
12431
|
+
const statusKey = `${repoRoot}:${teamId ?? "default"}`;
|
|
12432
|
+
const status2 = indexingStatus.get(statusKey);
|
|
12433
|
+
if (!status2 || status2.state === "error") {
|
|
12434
|
+
indexingStatus.set(statusKey, {
|
|
12435
|
+
state: "indexing",
|
|
12436
|
+
startedAt: Date.now()
|
|
12437
|
+
});
|
|
12438
|
+
indexDirectory(db, indexPath, repoRoot, teamId, 1e4).then((results) => {
|
|
12439
|
+
const indexed = results.filter((r) => !r.skipped);
|
|
12440
|
+
const totalSymbols = indexed.reduce((s, r) => s + r.symbols, 0);
|
|
12441
|
+
indexingStatus.set(statusKey, {
|
|
12442
|
+
state: "done",
|
|
12443
|
+
startedAt: status2?.startedAt ?? Date.now(),
|
|
12444
|
+
finishedAt: Date.now(),
|
|
12445
|
+
fileCount: indexed.length,
|
|
12446
|
+
symbolCount: totalSymbols
|
|
12447
|
+
});
|
|
12448
|
+
console.error(
|
|
12449
|
+
`[remem-mcp] background index complete: ${indexed.length} files, ${totalSymbols} symbols from ${indexPath}`
|
|
12450
|
+
);
|
|
12451
|
+
}).catch((err) => {
|
|
12452
|
+
indexingStatus.set(statusKey, {
|
|
12453
|
+
state: "error",
|
|
12454
|
+
startedAt: status2?.startedAt ?? Date.now(),
|
|
12455
|
+
finishedAt: Date.now(),
|
|
12456
|
+
error: String(err)
|
|
12457
|
+
});
|
|
12458
|
+
console.error(`[remem-mcp] background index failed: ${err}`);
|
|
12459
|
+
});
|
|
12460
|
+
return {
|
|
12461
|
+
content: [
|
|
12462
|
+
{
|
|
12463
|
+
type: "text",
|
|
12464
|
+
text: `No symbols found yet. Background indexing started for ${indexPath}.
|
|
12465
|
+
Retry codegraph_search in ~10-30s (large repos may take longer).
|
|
12466
|
+
You can continue working \u2014 indexing runs in the background.`
|
|
12467
|
+
}
|
|
12468
|
+
]
|
|
12469
|
+
};
|
|
12470
|
+
} else if (status2.state === "indexing") {
|
|
12471
|
+
const elapsed = Math.round((Date.now() - status2.startedAt) / 1e3);
|
|
12472
|
+
return {
|
|
12473
|
+
content: [
|
|
12474
|
+
{
|
|
12475
|
+
type: "text",
|
|
12476
|
+
text: `No symbols found yet. Indexing in progress (${elapsed}s elapsed).
|
|
12477
|
+
Retry codegraph_search in a few seconds.
|
|
12478
|
+
Meanwhile, use grep/glob for immediate code navigation.`
|
|
12479
|
+
}
|
|
12480
|
+
]
|
|
12481
|
+
};
|
|
12482
|
+
} else if (status2.state === "done") {
|
|
12483
|
+
symbols = searchSymbols(db, query, { teamId, kind, language, limit, repoPath: repoRoot });
|
|
11302
12484
|
if (symbols.length === 0) {
|
|
11303
12485
|
return {
|
|
11304
12486
|
content: [
|
|
11305
12487
|
{
|
|
11306
12488
|
type: "text",
|
|
11307
|
-
text: `No symbols found
|
|
12489
|
+
text: `No symbols found for "${query}". Indexing completed: ${status2.fileCount} files, ${status2.symbolCount} symbols.
|
|
12490
|
+
Try a different search term, or call codegraph_index with a specific path.`
|
|
11308
12491
|
}
|
|
11309
12492
|
]
|
|
11310
12493
|
};
|
|
11311
12494
|
}
|
|
11312
|
-
} catch {
|
|
11313
|
-
return {
|
|
11314
|
-
content: [
|
|
11315
|
-
{
|
|
11316
|
-
type: "text",
|
|
11317
|
-
text: "No symbols found. Call codegraph_index with a path to index your codebase first."
|
|
11318
|
-
}
|
|
11319
|
-
]
|
|
11320
|
-
};
|
|
11321
12495
|
}
|
|
11322
12496
|
}
|
|
11323
12497
|
const lines = symbols.map(
|
|
@@ -11425,7 +12599,6 @@ function handleCodegraphDetectChanges(args, opts) {
|
|
|
11425
12599
|
const db = getDb(opts);
|
|
11426
12600
|
let changedFiles;
|
|
11427
12601
|
try {
|
|
11428
|
-
const { execSync: execSync3 } = __require("child_process");
|
|
11429
12602
|
const output2 = execSync3("git diff --name-only HEAD", {
|
|
11430
12603
|
cwd: repoPath,
|
|
11431
12604
|
encoding: "utf-8",
|
|
@@ -11488,9 +12661,15 @@ function handleCodegraphDetectChanges(args, opts) {
|
|
|
11488
12661
|
} catch {
|
|
11489
12662
|
}
|
|
11490
12663
|
}
|
|
11491
|
-
const lines = [
|
|
12664
|
+
const lines = [
|
|
12665
|
+
`Changed files: ${changedFiles.length}`,
|
|
12666
|
+
`Affected symbols: ${changedSymbols.length}`,
|
|
12667
|
+
""
|
|
12668
|
+
];
|
|
11492
12669
|
for (const r of results) {
|
|
11493
|
-
lines.push(
|
|
12670
|
+
lines.push(
|
|
12671
|
+
`[${r.risk.toUpperCase()}] ${r.symbol.kind} ${r.symbol.name} (${r.symbol.file}:${r.symbol.line})`
|
|
12672
|
+
);
|
|
11494
12673
|
if (r.callers.length > 0) {
|
|
11495
12674
|
lines.push(` Callers (${r.callers.length} shown):`);
|
|
11496
12675
|
for (const c of r.callers) {
|
|
@@ -11504,6 +12683,49 @@ function handleCodegraphDetectChanges(args, opts) {
|
|
|
11504
12683
|
content: [{ type: "text", text: lines.join("\n") }]
|
|
11505
12684
|
};
|
|
11506
12685
|
}
|
|
12686
|
+
function handleCodegraphStats(args, opts) {
|
|
12687
|
+
const repoPath = args.repo_path ?? void 0;
|
|
12688
|
+
const teamId = args.team_id ?? void 0;
|
|
12689
|
+
const db = getDb(opts);
|
|
12690
|
+
const repoClause = repoPath ? "AND repo_path IS ?" : "";
|
|
12691
|
+
const repoParams = repoPath ? [repoPath] : [];
|
|
12692
|
+
const teamClause = teamId !== void 0 ? "AND team_id IS ?" : "";
|
|
12693
|
+
const teamParams = teamId !== void 0 ? [teamId] : [];
|
|
12694
|
+
const filter = `${repoClause} ${teamClause}`.trim();
|
|
12695
|
+
const symbolCount = db.prepare(`SELECT COUNT(*) as c FROM symbols WHERE 1=1 ${filter}`).get(...repoParams, ...teamParams);
|
|
12696
|
+
const callCount = db.prepare(
|
|
12697
|
+
`SELECT COUNT(*) as c FROM calls AS c
|
|
12698
|
+
JOIN symbols AS caller ON caller.id = c.caller_id
|
|
12699
|
+
WHERE 1=1 ${filter}`
|
|
12700
|
+
).get(...repoParams, ...teamParams);
|
|
12701
|
+
const resolvedCount = db.prepare(
|
|
12702
|
+
`SELECT COUNT(*) as c FROM calls AS c
|
|
12703
|
+
JOIN symbols AS caller ON caller.id = c.caller_id
|
|
12704
|
+
WHERE c.callee_id IS NOT NULL ${filter}`
|
|
12705
|
+
).get(...repoParams, ...teamParams);
|
|
12706
|
+
const importCount = db.prepare(`SELECT COUNT(*) as c FROM imports WHERE 1=1 ${filter}`).get(...repoParams, ...teamParams);
|
|
12707
|
+
const fileCount = db.prepare(`SELECT COUNT(DISTINCT file_path) as c FROM symbols WHERE 1=1 ${filter}`).get(...repoParams, ...teamParams);
|
|
12708
|
+
const byKind = db.prepare(
|
|
12709
|
+
`SELECT kind, COUNT(*) as c FROM symbols WHERE 1=1 ${filter} GROUP BY kind ORDER BY c DESC`
|
|
12710
|
+
).all(...repoParams, ...teamParams);
|
|
12711
|
+
const lines = [
|
|
12712
|
+
`CodeGraph Statistics${repoPath ? ` (repo: ${repoPath})` : ""}`,
|
|
12713
|
+
` files: ${fileCount.c}`,
|
|
12714
|
+
` symbols: ${symbolCount.c}`,
|
|
12715
|
+
` calls: ${callCount.c} (${resolvedCount.c} resolved, ${callCount.c - resolvedCount.c} unresolved)`,
|
|
12716
|
+
` imports: ${importCount.c}`,
|
|
12717
|
+
""
|
|
12718
|
+
];
|
|
12719
|
+
if (byKind.length > 0) {
|
|
12720
|
+
lines.push("By kind:");
|
|
12721
|
+
for (const k of byKind) {
|
|
12722
|
+
lines.push(` ${k.kind.padEnd(12)} ${k.c}`);
|
|
12723
|
+
}
|
|
12724
|
+
}
|
|
12725
|
+
return {
|
|
12726
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
12727
|
+
};
|
|
12728
|
+
}
|
|
11507
12729
|
async function handleWikiIngest(args, opts) {
|
|
11508
12730
|
const path = args.path;
|
|
11509
12731
|
const repoPath = args.repo_path ?? path;
|
|
@@ -11513,10 +12735,10 @@ async function handleWikiIngest(args, opts) {
|
|
|
11513
12735
|
return { content: [{ type: "text", text: "Error: path is required." }], isError: true };
|
|
11514
12736
|
}
|
|
11515
12737
|
const db = getDb(opts);
|
|
11516
|
-
const { statSync:
|
|
12738
|
+
const { statSync: statSync4 } = await import("fs");
|
|
11517
12739
|
let stat;
|
|
11518
12740
|
try {
|
|
11519
|
-
stat =
|
|
12741
|
+
stat = statSync4(path);
|
|
11520
12742
|
} catch {
|
|
11521
12743
|
return { content: [{ type: "text", text: `Error: path not found: ${path}` }], isError: true };
|
|
11522
12744
|
}
|
|
@@ -11612,6 +12834,158 @@ ${lines.join("\n")}` }
|
|
|
11612
12834
|
]
|
|
11613
12835
|
};
|
|
11614
12836
|
}
|
|
12837
|
+
async function handleCanvasGet(args, opts) {
|
|
12838
|
+
const sessionKey = args.session_key ?? defaultSessionKey();
|
|
12839
|
+
const format = args.format ?? "mermaid";
|
|
12840
|
+
try {
|
|
12841
|
+
const mermaidText = await opts.storage.getCanvasMermaidText(sessionKey);
|
|
12842
|
+
if (!mermaidText) {
|
|
12843
|
+
return {
|
|
12844
|
+
content: [
|
|
12845
|
+
{
|
|
12846
|
+
type: "text",
|
|
12847
|
+
text: "No canvas found for this session. Enable REMEM_OFFLOAD_ENABLED=true and pipeline=mermaid to start building a task canvas."
|
|
12848
|
+
}
|
|
12849
|
+
]
|
|
12850
|
+
};
|
|
12851
|
+
}
|
|
12852
|
+
if (format === "json") {
|
|
12853
|
+
return {
|
|
12854
|
+
content: [
|
|
12855
|
+
{
|
|
12856
|
+
type: "text",
|
|
12857
|
+
text: JSON.stringify(
|
|
12858
|
+
{
|
|
12859
|
+
sessionKey,
|
|
12860
|
+
mermaid: mermaidText,
|
|
12861
|
+
tokenEstimate: Math.ceil(mermaidText.length / 4)
|
|
12862
|
+
},
|
|
12863
|
+
null,
|
|
12864
|
+
2
|
|
12865
|
+
)
|
|
12866
|
+
}
|
|
12867
|
+
]
|
|
12868
|
+
};
|
|
12869
|
+
}
|
|
12870
|
+
return {
|
|
12871
|
+
content: [
|
|
12872
|
+
{
|
|
12873
|
+
type: "text",
|
|
12874
|
+
text: `## Task Canvas (Mermaid)
|
|
12875
|
+
|
|
12876
|
+
\`\`\`mermaid
|
|
12877
|
+
${mermaidText}
|
|
12878
|
+
\`\`\`
|
|
12879
|
+
|
|
12880
|
+
Drill down: call ref_read(node_id) to get the raw output for any node.`
|
|
12881
|
+
}
|
|
12882
|
+
]
|
|
12883
|
+
};
|
|
12884
|
+
} catch (err) {
|
|
12885
|
+
return {
|
|
12886
|
+
content: [{ type: "text", text: `Error reading canvas: ${err}` }]
|
|
12887
|
+
};
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12890
|
+
async function handleRefRead(args, opts) {
|
|
12891
|
+
const nodeId = args.node_id;
|
|
12892
|
+
if (!nodeId) {
|
|
12893
|
+
return {
|
|
12894
|
+
content: [{ type: "text", text: "Error: node_id is required." }]
|
|
12895
|
+
};
|
|
12896
|
+
}
|
|
12897
|
+
try {
|
|
12898
|
+
const content = await opts.storage.readRef(nodeId);
|
|
12899
|
+
if (!content) {
|
|
12900
|
+
return {
|
|
12901
|
+
content: [
|
|
12902
|
+
{
|
|
12903
|
+
type: "text",
|
|
12904
|
+
text: `No ref found for node_id "${nodeId}". The ref file may have been cleaned up (REMEM_REFS_RETENTION_HOURS) or offloading is not enabled.`
|
|
12905
|
+
}
|
|
12906
|
+
]
|
|
12907
|
+
};
|
|
12908
|
+
}
|
|
12909
|
+
return {
|
|
12910
|
+
content: [{ type: "text", text: content }]
|
|
12911
|
+
};
|
|
12912
|
+
} catch (err) {
|
|
12913
|
+
return {
|
|
12914
|
+
content: [{ type: "text", text: `Error reading ref: ${err}` }]
|
|
12915
|
+
};
|
|
12916
|
+
}
|
|
12917
|
+
}
|
|
12918
|
+
async function handleSkillCreate(args, opts) {
|
|
12919
|
+
const name = args.name;
|
|
12920
|
+
const content = args.content;
|
|
12921
|
+
if (!name || !content) {
|
|
12922
|
+
return {
|
|
12923
|
+
content: [{ type: "text", text: "Error: name and content are required." }]
|
|
12924
|
+
};
|
|
12925
|
+
}
|
|
12926
|
+
const tenant = extractTenant(args);
|
|
12927
|
+
const teamId = tenant.teamId ?? "default";
|
|
12928
|
+
const agentId = tenant.agentId ?? "default";
|
|
12929
|
+
const existing = await opts.storage.searchSkills(teamId, agentId, name, 1);
|
|
12930
|
+
let version = 1;
|
|
12931
|
+
let skillId = generateId();
|
|
12932
|
+
if (existing.length > 0 && existing[0].name === name) {
|
|
12933
|
+
version = existing[0].version + 1;
|
|
12934
|
+
skillId = existing[0].id;
|
|
12935
|
+
}
|
|
12936
|
+
const now = Date.now();
|
|
12937
|
+
await opts.storage.putSkill({
|
|
12938
|
+
id: skillId,
|
|
12939
|
+
teamId,
|
|
12940
|
+
agentId,
|
|
12941
|
+
name,
|
|
12942
|
+
description: args.description,
|
|
12943
|
+
content,
|
|
12944
|
+
version,
|
|
12945
|
+
createdAt: existing.length > 0 ? existing[0].createdAt : now,
|
|
12946
|
+
updatedAt: now,
|
|
12947
|
+
triggerConditions: args.trigger_conditions,
|
|
12948
|
+
steps: args.steps,
|
|
12949
|
+
validationRules: args.validation_rules,
|
|
12950
|
+
sourceCaptureIds: args.source_capture_ids,
|
|
12951
|
+
archived: false
|
|
12952
|
+
});
|
|
12953
|
+
return {
|
|
12954
|
+
content: [
|
|
12955
|
+
{
|
|
12956
|
+
type: "text",
|
|
12957
|
+
text: `Skill "${name}" created (v${version}, id: ${skillId}). It will be injected into recall when trigger conditions match.`
|
|
12958
|
+
}
|
|
12959
|
+
]
|
|
12960
|
+
};
|
|
12961
|
+
}
|
|
12962
|
+
async function handleSkillArchive(args, opts) {
|
|
12963
|
+
const id = args.id;
|
|
12964
|
+
if (!id) {
|
|
12965
|
+
return {
|
|
12966
|
+
content: [{ type: "text", text: "Error: id is required." }]
|
|
12967
|
+
};
|
|
12968
|
+
}
|
|
12969
|
+
const skill = await opts.storage.getSkill(id);
|
|
12970
|
+
if (!skill) {
|
|
12971
|
+
return {
|
|
12972
|
+
content: [{ type: "text", text: `Error: Skill "${id}" not found.` }]
|
|
12973
|
+
};
|
|
12974
|
+
}
|
|
12975
|
+
await opts.storage.putSkill({
|
|
12976
|
+
...skill,
|
|
12977
|
+
archived: true,
|
|
12978
|
+
updatedAt: Date.now()
|
|
12979
|
+
});
|
|
12980
|
+
return {
|
|
12981
|
+
content: [
|
|
12982
|
+
{
|
|
12983
|
+
type: "text",
|
|
12984
|
+
text: `Skill "${skill.name}" archived. It will now always be injected into recall.`
|
|
12985
|
+
}
|
|
12986
|
+
]
|
|
12987
|
+
};
|
|
12988
|
+
}
|
|
11615
12989
|
|
|
11616
12990
|
// src/stats.ts
|
|
11617
12991
|
init_esm_shims();
|
|
@@ -11743,9 +13117,9 @@ Knowledge assets: ${knowledgeCount.count}`);
|
|
|
11743
13117
|
|
|
11744
13118
|
// src/token-stats.ts
|
|
11745
13119
|
init_esm_shims();
|
|
11746
|
-
import { existsSync as
|
|
11747
|
-
import { homedir as
|
|
11748
|
-
import { join as
|
|
13120
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
13121
|
+
import { homedir as homedir11 } from "os";
|
|
13122
|
+
import { join as join14 } from "path";
|
|
11749
13123
|
import Database11 from "better-sqlite3";
|
|
11750
13124
|
import { encode } from "gpt-tokenizer";
|
|
11751
13125
|
function countTokens(text) {
|
|
@@ -11753,7 +13127,7 @@ function countTokens(text) {
|
|
|
11753
13127
|
return encode(text).length;
|
|
11754
13128
|
}
|
|
11755
13129
|
function tokenStats(dbPath) {
|
|
11756
|
-
const logPath = process.env.REMEM_HOOK_LOG_PATH ??
|
|
13130
|
+
const logPath = process.env.REMEM_HOOK_LOG_PATH ?? join14(homedir11(), ".local", "share", "remem-mcp", "session.log");
|
|
11757
13131
|
const db = new Database11(dbPath, { readonly: true });
|
|
11758
13132
|
const totalCaptures = db.prepare("SELECT COUNT(*) as count FROM captures").get();
|
|
11759
13133
|
const captures = db.prepare("SELECT id, type, content, tags, created_at FROM captures ORDER BY created_at ASC").all();
|
|
@@ -11768,8 +13142,8 @@ function tokenStats(dbPath) {
|
|
|
11768
13142
|
let _noMemoryCount = 0;
|
|
11769
13143
|
let captureCount = 0;
|
|
11770
13144
|
const recallBlocks = [];
|
|
11771
|
-
if (
|
|
11772
|
-
const log =
|
|
13145
|
+
if (existsSync14(logPath)) {
|
|
13146
|
+
const log = readFileSync11(logPath, "utf-8");
|
|
11773
13147
|
const lines = log.split("\n");
|
|
11774
13148
|
let inBlock = false;
|
|
11775
13149
|
let blockText = [];
|
|
@@ -11905,10 +13279,10 @@ function printReactExample(fmt, _countTok) {
|
|
|
11905
13279
|
// src/viewer.ts
|
|
11906
13280
|
init_esm_shims();
|
|
11907
13281
|
init_engine();
|
|
11908
|
-
import { existsSync as
|
|
13282
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
|
|
11909
13283
|
import { createServer as createHttpServer } from "http";
|
|
11910
|
-
import { homedir as
|
|
11911
|
-
import { join as
|
|
13284
|
+
import { homedir as homedir12 } from "os";
|
|
13285
|
+
import { join as join15 } from "path";
|
|
11912
13286
|
import Database12 from "better-sqlite3";
|
|
11913
13287
|
function startViewer(dbPath, port) {
|
|
11914
13288
|
const db = new Database12(dbPath);
|
|
@@ -12184,7 +13558,7 @@ function startViewer(dbPath, port) {
|
|
|
12184
13558
|
}
|
|
12185
13559
|
if (url.pathname === "/api/token-stats") {
|
|
12186
13560
|
try {
|
|
12187
|
-
const logPath = process.env.REMEM_HOOK_LOG_PATH ??
|
|
13561
|
+
const logPath = process.env.REMEM_HOOK_LOG_PATH ?? join15(homedir12(), ".local", "share", "remem-mcp", "session.log");
|
|
12188
13562
|
const captures = db.prepare("SELECT content FROM captures ORDER BY created_at ASC").all();
|
|
12189
13563
|
const sessions = db.prepare("SELECT COUNT(DISTINCT session_key) as count FROM captures").get();
|
|
12190
13564
|
const totalStored = captures.reduce(
|
|
@@ -12194,8 +13568,8 @@ function startViewer(dbPath, port) {
|
|
|
12194
13568
|
let recallCount = 0;
|
|
12195
13569
|
let totalInjected = 0;
|
|
12196
13570
|
let captureCount = 0;
|
|
12197
|
-
if (
|
|
12198
|
-
const log =
|
|
13571
|
+
if (existsSync15(logPath)) {
|
|
13572
|
+
const log = readFileSync12(logPath, "utf-8");
|
|
12199
13573
|
const lines = log.split("\n");
|
|
12200
13574
|
let inBlock = false;
|
|
12201
13575
|
let blockChars = 0;
|
|
@@ -13629,7 +15003,7 @@ function renderPage() {
|
|
|
13629
15003
|
|
|
13630
15004
|
// src/index.ts
|
|
13631
15005
|
function defaultDbPath3() {
|
|
13632
|
-
return process.env.REMEM_DB_PATH ??
|
|
15006
|
+
return process.env.REMEM_DB_PATH ?? join16(homedir13(), ".local", "share", "remem-mcp", "memory.db");
|
|
13633
15007
|
}
|
|
13634
15008
|
function openDbWithSchema(dbPath) {
|
|
13635
15009
|
const db = new Database13(dbPath);
|
|
@@ -13640,13 +15014,13 @@ function openDbWithSchema(dbPath) {
|
|
|
13640
15014
|
if (!hasSymbols) {
|
|
13641
15015
|
const distDir = dirname10(fileURLToPath4(import.meta.url));
|
|
13642
15016
|
const candidates = [
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
15017
|
+
join16(distDir, "storage", "schema.sql"),
|
|
15018
|
+
join16(distDir, "schema.sql"),
|
|
15019
|
+
join16(process.cwd(), "src", "storage", "schema.sql")
|
|
13646
15020
|
];
|
|
13647
15021
|
for (const p of candidates) {
|
|
13648
15022
|
try {
|
|
13649
|
-
db.exec(
|
|
15023
|
+
db.exec(readFileSync13(p, "utf-8"));
|
|
13650
15024
|
break;
|
|
13651
15025
|
} catch {
|
|
13652
15026
|
}
|
|
@@ -13671,25 +15045,25 @@ function parseFlags(argv) {
|
|
|
13671
15045
|
}
|
|
13672
15046
|
function detectPackageManager() {
|
|
13673
15047
|
const cwd = process.cwd();
|
|
13674
|
-
if (
|
|
13675
|
-
if (
|
|
13676
|
-
if (
|
|
13677
|
-
if (
|
|
13678
|
-
if (
|
|
13679
|
-
if (
|
|
13680
|
-
if (
|
|
13681
|
-
if (
|
|
13682
|
-
if (
|
|
13683
|
-
if (
|
|
15048
|
+
if (existsSync16(join16(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
15049
|
+
if (existsSync16(join16(cwd, "bun.lockb")) || existsSync16(join16(cwd, "bun.lock"))) return "bun";
|
|
15050
|
+
if (existsSync16(join16(cwd, "yarn.lock"))) return "yarn";
|
|
15051
|
+
if (existsSync16(join16(cwd, "package-lock.json"))) return "npm";
|
|
15052
|
+
if (existsSync16(join16(cwd, "Cargo.toml"))) return "cargo";
|
|
15053
|
+
if (existsSync16(join16(cwd, "go.mod"))) return "go";
|
|
15054
|
+
if (existsSync16(join16(cwd, "pom.xml")) || existsSync16(join16(cwd, "build.gradle"))) return "maven";
|
|
15055
|
+
if (existsSync16(join16(cwd, "build.gradle.kts"))) return "gradle";
|
|
15056
|
+
if (existsSync16(join16(cwd, "Gemfile"))) return "bundle";
|
|
15057
|
+
if (existsSync16(join16(cwd, "requirements.txt")) || existsSync16(join16(cwd, "pyproject.toml")))
|
|
13684
15058
|
return "pip";
|
|
13685
15059
|
return null;
|
|
13686
15060
|
}
|
|
13687
15061
|
function detectPackageScripts() {
|
|
13688
15062
|
const cwd = process.cwd();
|
|
13689
|
-
const pkgPath =
|
|
13690
|
-
if (!
|
|
15063
|
+
const pkgPath = join16(cwd, "package.json");
|
|
15064
|
+
if (!existsSync16(pkgPath)) return {};
|
|
13691
15065
|
try {
|
|
13692
|
-
const pkg = JSON.parse(
|
|
15066
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
13693
15067
|
const scripts = pkg.scripts ?? {};
|
|
13694
15068
|
const important = {};
|
|
13695
15069
|
for (const key of ["build", "test", "lint", "format", "dev", "start", "typecheck", "check"]) {
|
|
@@ -13702,14 +15076,14 @@ function detectPackageScripts() {
|
|
|
13702
15076
|
}
|
|
13703
15077
|
function detectFramework() {
|
|
13704
15078
|
const cwd = process.cwd();
|
|
13705
|
-
const pkgPath =
|
|
13706
|
-
if (!
|
|
15079
|
+
const pkgPath = join16(cwd, "package.json");
|
|
15080
|
+
if (!existsSync16(pkgPath)) {
|
|
13707
15081
|
const fw = detectNonJsFramework(cwd);
|
|
13708
15082
|
if (fw) return fw;
|
|
13709
15083
|
return null;
|
|
13710
15084
|
}
|
|
13711
15085
|
try {
|
|
13712
|
-
const pkg = JSON.parse(
|
|
15086
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
13713
15087
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
13714
15088
|
if (deps.next) return "Next.js";
|
|
13715
15089
|
if (deps["react-scripts"]) return "Create React App";
|
|
@@ -13731,10 +15105,10 @@ function detectFramework() {
|
|
|
13731
15105
|
}
|
|
13732
15106
|
}
|
|
13733
15107
|
function detectNonJsFramework(cwd) {
|
|
13734
|
-
const pyPath =
|
|
13735
|
-
if (
|
|
15108
|
+
const pyPath = join16(cwd, "pyproject.toml");
|
|
15109
|
+
if (existsSync16(pyPath)) {
|
|
13736
15110
|
try {
|
|
13737
|
-
const content =
|
|
15111
|
+
const content = readFileSync13(pyPath, "utf-8");
|
|
13738
15112
|
if (/\bdjango\b/i.test(content)) return "Django";
|
|
13739
15113
|
if (/\bfastapi\b/i.test(content)) return "FastAPI";
|
|
13740
15114
|
if (/\bflask\b/i.test(content)) return "Flask";
|
|
@@ -13750,20 +15124,20 @@ function detectNonJsFramework(cwd) {
|
|
|
13750
15124
|
} catch {
|
|
13751
15125
|
}
|
|
13752
15126
|
}
|
|
13753
|
-
if (
|
|
15127
|
+
if (existsSync16(join16(cwd, "requirements.txt"))) {
|
|
13754
15128
|
try {
|
|
13755
|
-
const content =
|
|
15129
|
+
const content = readFileSync13(join16(cwd, "requirements.txt"), "utf-8");
|
|
13756
15130
|
if (/^\s*django\b/im.test(content)) return "Django";
|
|
13757
15131
|
if (/^\s*fastapi\b/im.test(content)) return "FastAPI";
|
|
13758
15132
|
if (/^\s*flask\b/im.test(content)) return "Flask";
|
|
13759
15133
|
} catch {
|
|
13760
15134
|
}
|
|
13761
15135
|
}
|
|
13762
|
-
if (
|
|
13763
|
-
const cargoPath =
|
|
13764
|
-
if (
|
|
15136
|
+
if (existsSync16(join16(cwd, "manage.py")) && existsSync16(join16(cwd, "wsgi.py"))) return "Django";
|
|
15137
|
+
const cargoPath = join16(cwd, "Cargo.toml");
|
|
15138
|
+
if (existsSync16(cargoPath)) {
|
|
13765
15139
|
try {
|
|
13766
|
-
const content =
|
|
15140
|
+
const content = readFileSync13(cargoPath, "utf-8");
|
|
13767
15141
|
if (/\baxum\b/i.test(content)) return "Axum";
|
|
13768
15142
|
if (/\bactix-web\b/i.test(content)) return "Actix Web";
|
|
13769
15143
|
if (/\bwarp\b/i.test(content)) return "Warp";
|
|
@@ -13778,10 +15152,10 @@ function detectNonJsFramework(cwd) {
|
|
|
13778
15152
|
} catch {
|
|
13779
15153
|
}
|
|
13780
15154
|
}
|
|
13781
|
-
const goModPath =
|
|
13782
|
-
if (
|
|
15155
|
+
const goModPath = join16(cwd, "go.mod");
|
|
15156
|
+
if (existsSync16(goModPath)) {
|
|
13783
15157
|
try {
|
|
13784
|
-
const content =
|
|
15158
|
+
const content = readFileSync13(goModPath, "utf-8");
|
|
13785
15159
|
if (/\bgin-gin\b/i.test(content)) return "Gin";
|
|
13786
15160
|
if (/\becho\b/i.test(content)) return "Echo";
|
|
13787
15161
|
if (/\bfiber\b/i.test(content)) return "Fiber";
|
|
@@ -13792,10 +15166,10 @@ function detectNonJsFramework(cwd) {
|
|
|
13792
15166
|
} catch {
|
|
13793
15167
|
}
|
|
13794
15168
|
}
|
|
13795
|
-
const gemfilePath =
|
|
13796
|
-
if (
|
|
15169
|
+
const gemfilePath = join16(cwd, "Gemfile");
|
|
15170
|
+
if (existsSync16(gemfilePath)) {
|
|
13797
15171
|
try {
|
|
13798
|
-
const content =
|
|
15172
|
+
const content = readFileSync13(gemfilePath, "utf-8");
|
|
13799
15173
|
if (/\brails\b/i.test(content)) return "Rails";
|
|
13800
15174
|
if (/\bsinatra\b/i.test(content)) return "Sinatra";
|
|
13801
15175
|
if (/\bhanami\b/i.test(content)) return "Hanami";
|
|
@@ -13805,9 +15179,9 @@ function detectNonJsFramework(cwd) {
|
|
|
13805
15179
|
} catch {
|
|
13806
15180
|
}
|
|
13807
15181
|
}
|
|
13808
|
-
if (
|
|
15182
|
+
if (existsSync16(join16(cwd, "pom.xml"))) {
|
|
13809
15183
|
try {
|
|
13810
|
-
const content =
|
|
15184
|
+
const content = readFileSync13(join16(cwd, "pom.xml"), "utf-8");
|
|
13811
15185
|
if (/\bspring-boot\b/i.test(content)) return "Spring Boot";
|
|
13812
15186
|
if (/\bspring-webflux\b/i.test(content)) return "Spring WebFlux";
|
|
13813
15187
|
if (/\bquarkus\b/i.test(content)) return "Quarkus";
|
|
@@ -13820,10 +15194,10 @@ function detectNonJsFramework(cwd) {
|
|
|
13820
15194
|
} catch {
|
|
13821
15195
|
}
|
|
13822
15196
|
}
|
|
13823
|
-
if (
|
|
15197
|
+
if (existsSync16(join16(cwd, "build.gradle")) || existsSync16(join16(cwd, "build.gradle.kts"))) {
|
|
13824
15198
|
try {
|
|
13825
|
-
const content =
|
|
13826
|
-
|
|
15199
|
+
const content = readFileSync13(
|
|
15200
|
+
existsSync16(join16(cwd, "build.gradle.kts")) ? join16(cwd, "build.gradle.kts") : join16(cwd, "build.gradle"),
|
|
13827
15201
|
"utf-8"
|
|
13828
15202
|
);
|
|
13829
15203
|
if (/\bspring-boot\b/i.test(content)) return "Spring Boot";
|
|
@@ -13834,9 +15208,9 @@ function detectNonJsFramework(cwd) {
|
|
|
13834
15208
|
}
|
|
13835
15209
|
}
|
|
13836
15210
|
try {
|
|
13837
|
-
const dotnetFiles =
|
|
15211
|
+
const dotnetFiles = readdirSync2(cwd).filter((f) => f.endsWith(".csproj") || f.endsWith(".sln"));
|
|
13838
15212
|
if (dotnetFiles.length > 0) {
|
|
13839
|
-
const content =
|
|
15213
|
+
const content = readFileSync13(join16(cwd, dotnetFiles[0]), "utf-8");
|
|
13840
15214
|
if (/\bMicrosoft\.NET\.Sdk\.Web\b/i.test(content)) return "ASP.NET Core";
|
|
13841
15215
|
if (/\bMicrosoft\.NET\.Sdk\b/i.test(content)) return ".NET";
|
|
13842
15216
|
return ".NET";
|
|
@@ -13847,10 +15221,10 @@ function detectNonJsFramework(cwd) {
|
|
|
13847
15221
|
}
|
|
13848
15222
|
function detectLintTool() {
|
|
13849
15223
|
const cwd = process.cwd();
|
|
13850
|
-
const pkgPath =
|
|
13851
|
-
if (
|
|
15224
|
+
const pkgPath = join16(cwd, "package.json");
|
|
15225
|
+
if (existsSync16(pkgPath)) {
|
|
13852
15226
|
try {
|
|
13853
|
-
const pkg = JSON.parse(
|
|
15227
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
13854
15228
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
13855
15229
|
if (deps.biome) return "Biome";
|
|
13856
15230
|
if (deps.eslint) return "ESLint";
|
|
@@ -13860,23 +15234,23 @@ function detectLintTool() {
|
|
|
13860
15234
|
} catch {
|
|
13861
15235
|
}
|
|
13862
15236
|
}
|
|
13863
|
-
if (
|
|
13864
|
-
if (
|
|
15237
|
+
if (existsSync16(join16(cwd, "biome.json")) || existsSync16(join16(cwd, "biome.jsonc"))) return "Biome";
|
|
15238
|
+
if (existsSync16(join16(cwd, ".eslintrc")) || existsSync16(join16(cwd, ".eslintrc.js")) || existsSync16(join16(cwd, ".eslintrc.json")) || existsSync16(join16(cwd, "eslint.config.js")) || existsSync16(join16(cwd, "eslint.config.mjs")))
|
|
13865
15239
|
return "ESLint";
|
|
13866
|
-
if (
|
|
15240
|
+
if (existsSync16(join16(cwd, ".prettierrc")) || existsSync16(join16(cwd, ".prettierrc.json")))
|
|
13867
15241
|
return "Prettier";
|
|
13868
|
-
if (
|
|
13869
|
-
if (
|
|
13870
|
-
if (
|
|
13871
|
-
if (
|
|
15242
|
+
if (existsSync16(join16(cwd, "rustfmt.toml"))) return "rustfmt";
|
|
15243
|
+
if (existsSync16(join16(cwd, ".rustfmt.toml"))) return "rustfmt";
|
|
15244
|
+
if (existsSync16(join16(cwd, ".gofmt"))) return "gofmt";
|
|
15245
|
+
if (existsSync16(join16(cwd, ".golangci.yml")) || existsSync16(join16(cwd, ".golangci.yaml")))
|
|
13872
15246
|
return "golangci-lint";
|
|
13873
|
-
if (
|
|
13874
|
-
if (
|
|
15247
|
+
if (existsSync16(join16(cwd, ".ruff.toml")) || existsSync16(join16(cwd, "ruff.toml"))) return "Ruff";
|
|
15248
|
+
if (existsSync16(join16(cwd, ".flake8")) || existsSync16(join16(cwd, "setup.cfg")) || existsSync16(join16(cwd, "tox.ini")))
|
|
13875
15249
|
return "Flake8";
|
|
13876
|
-
if (
|
|
15250
|
+
if (existsSync16(join16(cwd, ".pylintrc")) || existsSync16(join16(cwd, "pyproject.toml"))) {
|
|
13877
15251
|
try {
|
|
13878
|
-
if (
|
|
13879
|
-
const content =
|
|
15252
|
+
if (existsSync16(join16(cwd, "pyproject.toml"))) {
|
|
15253
|
+
const content = readFileSync13(join16(cwd, "pyproject.toml"), "utf-8");
|
|
13880
15254
|
if (/\bruff\b/i.test(content)) return "Ruff";
|
|
13881
15255
|
if (/\bblack\b/i.test(content)) return "Black";
|
|
13882
15256
|
if (/\bflake8\b/i.test(content)) return "Flake8";
|
|
@@ -13886,15 +15260,15 @@ function detectLintTool() {
|
|
|
13886
15260
|
} catch {
|
|
13887
15261
|
}
|
|
13888
15262
|
}
|
|
13889
|
-
if (
|
|
13890
|
-
if (
|
|
15263
|
+
if (existsSync16(join16(cwd, ".black")) || existsSync16(join16(cwd, "black-config.toml"))) return "Black";
|
|
15264
|
+
if (existsSync16(join16(cwd, ".rubocop.yml")) || existsSync16(join16(cwd, ".rubocop.yaml")))
|
|
13891
15265
|
return "RuboCop";
|
|
13892
|
-
if (
|
|
15266
|
+
if (existsSync16(join16(cwd, ".standard.yml")) || existsSync16(join16(cwd, ".standard.yaml")))
|
|
13893
15267
|
return "Standard";
|
|
13894
|
-
if (
|
|
13895
|
-
if (
|
|
15268
|
+
if (existsSync16(join16(cwd, ".checkstyle"))) return "Checkstyle";
|
|
15269
|
+
if (existsSync16(join16(cwd, "spotbugs-exclude.xml"))) return "SpotBugs";
|
|
13896
15270
|
try {
|
|
13897
|
-
const dotnetFiles =
|
|
15271
|
+
const dotnetFiles = readdirSync2(cwd).filter((f) => f.endsWith(".editorconfig"));
|
|
13898
15272
|
if (dotnetFiles.length > 0) return "EditorConfig";
|
|
13899
15273
|
} catch {
|
|
13900
15274
|
}
|
|
@@ -13903,10 +15277,10 @@ function detectLintTool() {
|
|
|
13903
15277
|
function detectTestCommand(pkgManager) {
|
|
13904
15278
|
const cwd = process.cwd();
|
|
13905
15279
|
const pm = pkgManager ?? "npm";
|
|
13906
|
-
const pkgPath =
|
|
13907
|
-
if (
|
|
15280
|
+
const pkgPath = join16(cwd, "package.json");
|
|
15281
|
+
if (existsSync16(pkgPath)) {
|
|
13908
15282
|
try {
|
|
13909
|
-
const pkg = JSON.parse(
|
|
15283
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
13910
15284
|
const scripts = pkg.scripts ?? {};
|
|
13911
15285
|
if (scripts.test && scripts.test !== 'echo "Error: no test specified" && exit 1') {
|
|
13912
15286
|
return `${pm} run test`;
|
|
@@ -13919,32 +15293,32 @@ function detectTestCommand(pkgManager) {
|
|
|
13919
15293
|
} catch {
|
|
13920
15294
|
}
|
|
13921
15295
|
}
|
|
13922
|
-
if (
|
|
13923
|
-
if (
|
|
13924
|
-
if (
|
|
13925
|
-
if (
|
|
15296
|
+
if (existsSync16(join16(cwd, "Cargo.toml"))) return "cargo test";
|
|
15297
|
+
if (existsSync16(join16(cwd, "go.mod"))) return "go test ./...";
|
|
15298
|
+
if (existsSync16(join16(cwd, "pytest.ini")) || existsSync16(join16(cwd, "conftest.py"))) return "pytest";
|
|
15299
|
+
if (existsSync16(join16(cwd, "pyproject.toml"))) {
|
|
13926
15300
|
try {
|
|
13927
|
-
const content =
|
|
15301
|
+
const content = readFileSync13(join16(cwd, "pyproject.toml"), "utf-8");
|
|
13928
15302
|
if (/\bpytest\b/i.test(content)) return "pytest";
|
|
13929
15303
|
if (/\bunittest\b/i.test(content)) return "python -m unittest";
|
|
13930
15304
|
} catch {
|
|
13931
15305
|
}
|
|
13932
15306
|
}
|
|
13933
|
-
if (
|
|
13934
|
-
if (
|
|
15307
|
+
if (existsSync16(join16(cwd, "tox.ini"))) return "tox";
|
|
15308
|
+
if (existsSync16(join16(cwd, "Gemfile"))) {
|
|
13935
15309
|
try {
|
|
13936
|
-
const content =
|
|
15310
|
+
const content = readFileSync13(join16(cwd, "Gemfile"), "utf-8");
|
|
13937
15311
|
if (/\brspec\b/i.test(content)) return "bundle exec rspec";
|
|
13938
15312
|
if (/\bminitest\b/i.test(content)) return "bundle exec rake test";
|
|
13939
15313
|
} catch {
|
|
13940
15314
|
}
|
|
13941
15315
|
return "bundle exec rspec";
|
|
13942
15316
|
}
|
|
13943
|
-
if (
|
|
13944
|
-
if (
|
|
15317
|
+
if (existsSync16(join16(cwd, "pom.xml"))) return "mvn test";
|
|
15318
|
+
if (existsSync16(join16(cwd, "build.gradle")) || existsSync16(join16(cwd, "build.gradle.kts")))
|
|
13945
15319
|
return "gradle test";
|
|
13946
15320
|
try {
|
|
13947
|
-
const dotnetFiles =
|
|
15321
|
+
const dotnetFiles = readdirSync2(cwd).filter(
|
|
13948
15322
|
(f) => f.endsWith(".csproj") || f.endsWith(".fsproj")
|
|
13949
15323
|
);
|
|
13950
15324
|
if (dotnetFiles.length > 0) return "dotnet test";
|
|
@@ -13959,6 +15333,10 @@ async function main() {
|
|
|
13959
15333
|
return;
|
|
13960
15334
|
}
|
|
13961
15335
|
if (arg === "init") {
|
|
15336
|
+
try {
|
|
15337
|
+
new SQLiteBackend(defaultDbPath3());
|
|
15338
|
+
} catch {
|
|
15339
|
+
}
|
|
13962
15340
|
console.log(`remem-mcp init
|
|
13963
15341
|
|
|
13964
15342
|
This command tells your agent to activate long-term memory for this session.
|
|
@@ -14051,7 +15429,7 @@ After that, the agent follows the rules automatically.`);
|
|
|
14051
15429
|
if (captured > 0) {
|
|
14052
15430
|
console.log(` Captured ${captured} project basics.`);
|
|
14053
15431
|
} else {
|
|
14054
|
-
const hasProjectFiles =
|
|
15432
|
+
const hasProjectFiles = existsSync16(join16(process.cwd(), "package.json")) || existsSync16(join16(process.cwd(), "Cargo.toml")) || existsSync16(join16(process.cwd(), "go.mod")) || existsSync16(join16(process.cwd(), "pyproject.toml"));
|
|
14055
15433
|
if (hasProjectFiles) {
|
|
14056
15434
|
console.log(" Project basics already captured (no changes since last setup).");
|
|
14057
15435
|
} else {
|
|
@@ -14086,7 +15464,13 @@ After that, the agent follows the rules automatically.`);
|
|
|
14086
15464
|
}
|
|
14087
15465
|
if (arg === "recent" || arg === "list") {
|
|
14088
15466
|
const limit = parseInt(process.argv[3] ?? "20", 10);
|
|
14089
|
-
|
|
15467
|
+
let db;
|
|
15468
|
+
try {
|
|
15469
|
+
db = new Database13(defaultDbPath3(), { readonly: true });
|
|
15470
|
+
} catch {
|
|
15471
|
+
console.log("No captures found.");
|
|
15472
|
+
return;
|
|
15473
|
+
}
|
|
14090
15474
|
const rows = db.prepare(
|
|
14091
15475
|
`SELECT id, type, content, tags, created_at FROM captures WHERE deleted_at IS NULL ORDER BY created_at DESC LIMIT ?`
|
|
14092
15476
|
).all(limit);
|
|
@@ -14176,7 +15560,7 @@ ${sorted.length} unique tag(s).`);
|
|
|
14176
15560
|
).get();
|
|
14177
15561
|
const oldest = db.prepare("SELECT MIN(created_at) as ts FROM captures WHERE deleted_at IS NULL").get();
|
|
14178
15562
|
const newest = db.prepare("SELECT MAX(created_at) as ts FROM captures WHERE deleted_at IS NULL").get();
|
|
14179
|
-
const dbSize =
|
|
15563
|
+
const dbSize = statSync3(defaultDbPath3()).size;
|
|
14180
15564
|
console.log("remem-mcp stats\n");
|
|
14181
15565
|
console.log(` Total captures: ${total}`);
|
|
14182
15566
|
console.log(
|
|
@@ -14367,13 +15751,13 @@ ${sorted.length} unique tag(s).`);
|
|
|
14367
15751
|
return;
|
|
14368
15752
|
}
|
|
14369
15753
|
if (arg === "viewer") {
|
|
14370
|
-
const port = Number(process.argv[
|
|
15754
|
+
const port = Number(process.argv[3] ?? process.env.REMEM_VIEWER_PORT ?? 7331);
|
|
14371
15755
|
startViewer(defaultDbPath3(), port);
|
|
14372
15756
|
return;
|
|
14373
15757
|
}
|
|
14374
15758
|
if (arg === "backup") {
|
|
14375
15759
|
const dbPath = defaultDbPath3();
|
|
14376
|
-
const auditPath = process.env.REMEM_AUDIT_LOG_PATH ??
|
|
15760
|
+
const auditPath = process.env.REMEM_AUDIT_LOG_PATH ?? join16(dirname10(dbPath), "audit.jsonl");
|
|
14377
15761
|
const outputDir = process.argv[3] ?? "-";
|
|
14378
15762
|
backup(dbPath, auditPath, outputDir);
|
|
14379
15763
|
return;
|
|
@@ -14394,7 +15778,7 @@ ${sorted.length} unique tag(s).`);
|
|
|
14394
15778
|
}
|
|
14395
15779
|
return;
|
|
14396
15780
|
}
|
|
14397
|
-
if (arg === "hook-recall") {
|
|
15781
|
+
if (arg === "hook-recall" || arg === "hook-session-start") {
|
|
14398
15782
|
hookRecall(defaultDbPath3());
|
|
14399
15783
|
return;
|
|
14400
15784
|
}
|
|
@@ -14437,6 +15821,45 @@ ${sorted.length} unique tag(s).`);
|
|
|
14437
15821
|
hookPostCompaction(defaultDbPath3());
|
|
14438
15822
|
return;
|
|
14439
15823
|
}
|
|
15824
|
+
if (arg === "proxy") {
|
|
15825
|
+
const { loadProxyConfig, startProxyServer } = await import("./server-5PWQTRGZ.js");
|
|
15826
|
+
const proxyConfig = loadProxyConfig();
|
|
15827
|
+
if (!proxyConfig.dbPath) {
|
|
15828
|
+
proxyConfig.dbPath = defaultDbPath3();
|
|
15829
|
+
}
|
|
15830
|
+
await startProxyServer(proxyConfig);
|
|
15831
|
+
return;
|
|
15832
|
+
}
|
|
15833
|
+
if (arg === "skill-extract") {
|
|
15834
|
+
const dbPath = defaultDbPath3();
|
|
15835
|
+
const db = new Database13(dbPath);
|
|
15836
|
+
sqliteVec4.load(db);
|
|
15837
|
+
const storage2 = new SQLiteBackend(dbPath);
|
|
15838
|
+
const pipeline2 = new SkillExtractionPipeline();
|
|
15839
|
+
const taskCaptures = db.prepare(
|
|
15840
|
+
"SELECT id, content, type, tags, team_id, agent_id FROM captures WHERE type = 'task' AND deleted_at IS NULL"
|
|
15841
|
+
).all();
|
|
15842
|
+
console.log(`Found ${taskCaptures.length} task capture(s) to extract skills from.`);
|
|
15843
|
+
let extracted = 0;
|
|
15844
|
+
for (const row of taskCaptures) {
|
|
15845
|
+
const tags = row.tags ? JSON.parse(row.tags) : [];
|
|
15846
|
+
const input = {
|
|
15847
|
+
id: row.id,
|
|
15848
|
+
content: row.content,
|
|
15849
|
+
type: row.type,
|
|
15850
|
+
tags,
|
|
15851
|
+
sessionKey: "default",
|
|
15852
|
+
teamId: row.team_id ?? "default",
|
|
15853
|
+
agentId: row.agent_id ?? "default"
|
|
15854
|
+
};
|
|
15855
|
+
const ctx = { storage: storage2, embedder: new LocalEmbedder(), sessionKey: "default" };
|
|
15856
|
+
const result = await pipeline2.process(input, ctx);
|
|
15857
|
+
if (Object.keys(result).length > 0) extracted++;
|
|
15858
|
+
}
|
|
15859
|
+
console.log(`Extracted ${extracted} skill(s) from ${taskCaptures.length} task capture(s).`);
|
|
15860
|
+
db.close();
|
|
15861
|
+
return;
|
|
15862
|
+
}
|
|
14440
15863
|
if (arg === "index") {
|
|
14441
15864
|
const flags = parseFlags(process.argv.slice(3));
|
|
14442
15865
|
const path = flags.path ?? flags.p ?? process.cwd();
|
|
@@ -14673,8 +16096,8 @@ ${sorted.length} unique tag(s).`);
|
|
|
14673
16096
|
}
|
|
14674
16097
|
if (arg === "version" || arg === "--version" || arg === "-v") {
|
|
14675
16098
|
try {
|
|
14676
|
-
const pkgPath =
|
|
14677
|
-
const pkg = JSON.parse(
|
|
16099
|
+
const pkgPath = join16(dirname10(fileURLToPath4(import.meta.url)), "..", "package.json");
|
|
16100
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
14678
16101
|
console.log(`remem-mcp v${pkg.version}`);
|
|
14679
16102
|
} catch {
|
|
14680
16103
|
console.log("remem-mcp (version unknown)");
|
|
@@ -14845,6 +16268,17 @@ To install the skill (Devin CLI only):
|
|
|
14845
16268
|
});
|
|
14846
16269
|
pipeline = new AtomPipeline();
|
|
14847
16270
|
pipeline._llmClient = llmClient;
|
|
16271
|
+
} else if (config.pipeline === "mermaid" && config.llm) {
|
|
16272
|
+
const llmClient = new OpenAILLMClient({
|
|
16273
|
+
apiKey: config.llm.apiKey,
|
|
16274
|
+
baseUrl: config.llm.baseUrl,
|
|
16275
|
+
model: config.llm.model
|
|
16276
|
+
});
|
|
16277
|
+
pipeline = new LLMMermaidPipeline(llmClient);
|
|
16278
|
+
} else if (config.pipeline === "mermaid") {
|
|
16279
|
+
pipeline = new MermaidPipeline();
|
|
16280
|
+
} else if (config.pipeline === "skill") {
|
|
16281
|
+
pipeline = new SkillExtractionPipeline();
|
|
14848
16282
|
} else {
|
|
14849
16283
|
pipeline = new RuleBasedAtomPipeline();
|
|
14850
16284
|
}
|
|
@@ -14902,8 +16336,8 @@ async function verifyMemory(dbPath, query) {
|
|
|
14902
16336
|
storage.close();
|
|
14903
16337
|
return;
|
|
14904
16338
|
}
|
|
14905
|
-
const { statSync:
|
|
14906
|
-
const { join:
|
|
16339
|
+
const { statSync: statSync4, readdirSync: readdirSync3 } = await import("fs");
|
|
16340
|
+
const { join: join17, extname } = await import("path");
|
|
14907
16341
|
const codeExts = [
|
|
14908
16342
|
".ts",
|
|
14909
16343
|
".tsx",
|
|
@@ -14925,16 +16359,16 @@ async function verifyMemory(dbPath, query) {
|
|
|
14925
16359
|
if (depth > 2 || fileCount > 50) return;
|
|
14926
16360
|
let entries;
|
|
14927
16361
|
try {
|
|
14928
|
-
entries =
|
|
16362
|
+
entries = readdirSync3(dir);
|
|
14929
16363
|
} catch {
|
|
14930
16364
|
return;
|
|
14931
16365
|
}
|
|
14932
16366
|
for (const name of entries) {
|
|
14933
16367
|
if (name.startsWith(".") || name === "node_modules" || name === "dist" || name === "build")
|
|
14934
16368
|
continue;
|
|
14935
|
-
const full =
|
|
16369
|
+
const full = join17(dir, name);
|
|
14936
16370
|
try {
|
|
14937
|
-
const stat =
|
|
16371
|
+
const stat = statSync4(full);
|
|
14938
16372
|
if (stat.isDirectory()) {
|
|
14939
16373
|
scanDir(full, depth + 1);
|
|
14940
16374
|
} else if (codeExts.includes(extname(name))) {
|