codex-dev-mcp-suite 3.1.1 → 3.2.0
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/CHANGELOG.md +40 -31
- package/package.json +1 -1
- package/project-memory/auto-indexer.js +114 -6
- package/project-memory/server.js +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,37 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
### Added
|
|
4
|
-
- **Zero-Dependency Local Offline Vector Embedding Engine**:
|
|
5
|
-
- Pure JS 384-dimensional term-frequency hashing vector engine with sublinear $1 + \log(tf)$ scaling and L2 normalization (`project-memory/local-embed.js`).
|
|
6
|
-
- Activated via `MCP_LOCAL_EMBED=true` / `LOCAL_EMBED=true` env flag when no remote embedding API key is available.
|
|
7
|
-
- Sub-millisecond execution, zero network calls, zero external npm dependencies.
|
|
8
|
-
- Full compatibility with existing `cosine(a, b)` and graph ranking in `project-memory`.
|
|
9
|
-
- **Universal Multi-Client Guides**:
|
|
10
|
-
- Hermes Agent setup guide (`docs/clients/hermes.md`).
|
|
11
|
-
- Google Antigravity CLI (AGY CLI) setup guide (`docs/clients/antigravity.md`).
|
|
12
|
-
- Claude Code & Generic MCP setup guides (`docs/clients/claude-code.md`, `docs/clients/generic-mcp.md`).
|
|
13
|
-
- **Test Suite**: 100/100 unit tests passing (100% green across 8 test suites).
|
|
1
|
+
# Changelog
|
|
14
2
|
|
|
15
|
-
##
|
|
3
|
+
## 3.2.0 - 2026-07-27 — Session Conversation Watchdog & Transcript Digesting 🧠
|
|
16
4
|
|
|
17
5
|
### Added
|
|
18
|
-
- **
|
|
19
|
-
- `
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
## 1.5.0
|
|
25
|
-
|
|
26
|
-
- add `memory_link` with wiki-link resolution for `[[id]]`, `[[title]]`, `[[project:id]]`, and `[[project:title]]`, plus backlink inspection
|
|
27
|
-
- add `memory_global_recall` with same-project bias and graceful keyword/semantic fallback across project vaults
|
|
28
|
-
- add `memory_dedup` with non-destructive duplicate suggestions for project or global scope
|
|
29
|
-
- add hybrid lazy graph backfill so link metadata is derived on first graph-aware use and remains rebuildable from canonical notes
|
|
30
|
-
- add graph-aware soft boost in recall paths without making embeddings mandatory
|
|
31
|
-
- 36 project-memory tests pass after graph/global/dedup coverage expansion
|
|
32
|
-
|
|
33
|
-
# Changelog
|
|
34
|
-
|
|
6
|
+
- **Session Conversation Watchdog & Transcript Digesting (`project-memory`)**:
|
|
7
|
+
- Added `scanSessionLogs()` helper in `auto-indexer.js` to autonomously scan recent `.jsonl`, `.json`, or `.log` session transcript files across `.codex/`, `.gemini/`, `.agents/logs/`, and custom session directories.
|
|
8
|
+
- Heuristic keyword analysis automatically detects architectural decisions, migration plans, and strategy discussions (e.g., V1 vs V2 trading bots).
|
|
9
|
+
- Upgraded `memory_auto_index` tool schema (`server.js`) with `scanSessions` and `sessionDir` parameters.
|
|
10
|
+
- Auto-derived decisions are now persisted directly into the Obsidian knowledge vault as individual notes (`notesCreated`), ensuring key chat discussions are permanently indexed and searchable via `memory_recall`.
|
|
35
11
|
## 3.1.1 - 2026-07-22 — CI/CD Hardening & Complete Wizard Tracking 🛡️
|
|
36
12
|
|
|
37
13
|
### Fixed
|
|
@@ -80,6 +56,39 @@
|
|
|
80
56
|
- Notifies users via `stderr` when a new version of `codex-dev-mcp-suite` is published to upstream npm.
|
|
81
57
|
- Safe for stdio MCP JSON-RPC protocol transport; sub-millisecond execution, zero startup latency.
|
|
82
58
|
|
|
59
|
+
## 1.8.0 - 2026-07-22
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
- **Zero-Dependency Local Offline Vector Embedding Engine**:
|
|
63
|
+
- Pure JS 384-dimensional term-frequency hashing vector engine with sublinear $1 + \log(tf)$ scaling and L2 normalization (`project-memory/local-embed.js`).
|
|
64
|
+
- Activated via `MCP_LOCAL_EMBED=true` / `LOCAL_EMBED=true` env flag when no remote embedding API key is available.
|
|
65
|
+
- Sub-millisecond execution, zero network calls, zero external npm dependencies.
|
|
66
|
+
- Full compatibility with existing `cosine(a, b)` and graph ranking in `project-memory`.
|
|
67
|
+
- **Universal Multi-Client Guides**:
|
|
68
|
+
- Hermes Agent setup guide (`docs/clients/hermes.md`).
|
|
69
|
+
- Google Antigravity CLI (AGY CLI) setup guide (`docs/clients/antigravity.md`).
|
|
70
|
+
- Claude Code & Generic MCP setup guides (`docs/clients/claude-code.md`, `docs/clients/generic-mcp.md`).
|
|
71
|
+
- **Test Suite**: 100/100 unit tests passing (100% green across 8 test suites).
|
|
72
|
+
|
|
73
|
+
## 1.7.0 - 2026-07-22
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
- **Obsidian Vault Parity & MOC Tools**:
|
|
77
|
+
- `memory_moc` Map of Content generator & `.obsidian` vault structure integration.
|
|
78
|
+
- `memory_graph` knowledge graph visualization & backlinks.
|
|
79
|
+
- **Codebase Security Audit Tool (`pack_audit`)**:
|
|
80
|
+
- Detect missing `.gitignore`, exposed secrets/keys (`.env`, `.pem`, `id_rsa`), and overly large files.
|
|
81
|
+
|
|
82
|
+
## 1.5.0
|
|
83
|
+
|
|
84
|
+
- add `memory_link` with wiki-link resolution for `[[id]]`, `[[title]]`, `[[project:id]]`, and `[[project:title]]`, plus backlink inspection
|
|
85
|
+
- add `memory_global_recall` with same-project bias and graceful keyword/semantic fallback across project vaults
|
|
86
|
+
- add `memory_dedup` with non-destructive duplicate suggestions for project or global scope
|
|
87
|
+
- add hybrid lazy graph backfill so link metadata is derived on first graph-aware use and remains rebuildable from canonical notes
|
|
88
|
+
- add graph-aware soft boost in recall paths without making embeddings mandatory
|
|
89
|
+
- 36 project-memory tests pass after graph/global/dedup coverage expansion
|
|
90
|
+
|
|
91
|
+
|
|
83
92
|
## 1.4.0 - 2026-06-17
|
|
84
93
|
|
|
85
94
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-dev-mcp-suite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Four local, file-based MCP servers for solo devs/vibecoders: persistent project memory, session handoff/resume, git-independent file checkpoints, and token-efficient project briefings. Works with any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,14 +1,97 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Autonomous Background Git &
|
|
3
|
-
* Inspects recent git commits
|
|
4
|
-
* project notes and update the
|
|
2
|
+
* Autonomous Background Git, Mtime & Session Watchdog Observer for project-memory.
|
|
3
|
+
* Inspects recent git commits, modified file mtimes, and chat session transcripts
|
|
4
|
+
* to auto-derive project notes, extract architectural decisions, and update the
|
|
5
|
+
* Obsidian knowledge vault without manual prompts.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import fs from "fs/promises";
|
|
8
9
|
import path from "path";
|
|
9
10
|
import { execSync } from "child_process";
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
async function scanSessionLogs(root, customSessionDir) {
|
|
13
|
+
const decisions = [];
|
|
14
|
+
const candidateDirs = customSessionDir
|
|
15
|
+
? [path.resolve(root, customSessionDir)]
|
|
16
|
+
: [
|
|
17
|
+
path.join(root, ".codex", "sessions"),
|
|
18
|
+
path.join(root, ".codex"),
|
|
19
|
+
path.join(root, ".gemini", "logs"),
|
|
20
|
+
path.join(root, ".gemini"),
|
|
21
|
+
path.join(root, ".agents", "logs"),
|
|
22
|
+
path.join(root, "logs"),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const filesToScan = [];
|
|
26
|
+
for (const d of candidateDirs) {
|
|
27
|
+
try {
|
|
28
|
+
const stat = await fs.stat(d);
|
|
29
|
+
if (stat.isFile() && (d.endsWith(".jsonl") || d.endsWith(".log") || d.endsWith(".json"))) {
|
|
30
|
+
filesToScan.push(d);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (stat.isDirectory()) {
|
|
34
|
+
const entries = await fs.readdir(d, { withFileTypes: true });
|
|
35
|
+
for (const e of entries) {
|
|
36
|
+
if (e.isFile() && (e.name.endsWith(".jsonl") || e.name.endsWith(".log") || e.name.endsWith(".json") || e.name.includes("history"))) {
|
|
37
|
+
filesToScan.push(path.join(d, e.name));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const fileStats = [];
|
|
45
|
+
for (const f of filesToScan) {
|
|
46
|
+
try {
|
|
47
|
+
const s = await fs.stat(f);
|
|
48
|
+
fileStats.push({ file: f, mtime: s.mtimeMs });
|
|
49
|
+
} catch {}
|
|
50
|
+
}
|
|
51
|
+
fileStats.sort((a, b) => b.mtime - a.mtime);
|
|
52
|
+
const topFiles = fileStats.slice(0, 5).map((x) => x.file);
|
|
53
|
+
|
|
54
|
+
const keywordRegex = /\b(v1|v2|v3|vs|versus|architecture|arsitektur|migration|migrasi|decision|keputusan|strategy|strategi|refactor|database|schema|tradeoff|perbedaan|kesimpulan|monolith|microservice)\b/i;
|
|
55
|
+
|
|
56
|
+
for (const file of topFiles) {
|
|
57
|
+
try {
|
|
58
|
+
const raw = await fs.readFile(file, "utf8");
|
|
59
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
let text = "";
|
|
62
|
+
let role = "unknown";
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(line);
|
|
65
|
+
text = typeof parsed === "string" ? parsed : parsed.content || parsed.text || parsed.message || JSON.stringify(parsed);
|
|
66
|
+
role = parsed.type || parsed.role || parsed.source || "log";
|
|
67
|
+
} catch {
|
|
68
|
+
text = line;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof text !== "string" || text.length < 20) continue;
|
|
72
|
+
|
|
73
|
+
const match = text.match(keywordRegex);
|
|
74
|
+
if (match) {
|
|
75
|
+
const keyword = match[0].toLowerCase();
|
|
76
|
+
const cleanText = text.replace(/\s+/g, " ").trim();
|
|
77
|
+
const snippet = cleanText.length > 300 ? cleanText.substring(0, 300) + "..." : cleanText;
|
|
78
|
+
decisions.push({
|
|
79
|
+
keyword,
|
|
80
|
+
role: String(role),
|
|
81
|
+
snippet,
|
|
82
|
+
file: path.relative(root, file) || path.basename(file),
|
|
83
|
+
});
|
|
84
|
+
if (decisions.length >= 10) break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch {}
|
|
88
|
+
if (decisions.length >= 10) break;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return decisions;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function runAutoIndexer(projectDir, { dryRun = false, scanSessions = true, sessionDir = null } = {}) {
|
|
12
95
|
const root = path.resolve(projectDir || process.cwd());
|
|
13
96
|
const notesCreated = [];
|
|
14
97
|
|
|
@@ -25,19 +108,44 @@ export async function runAutoIndexer(projectDir, { dryRun = false } = {}) {
|
|
|
25
108
|
branch = execSync("git branch --show-current", { cwd: root, encoding: "utf8" }).trim();
|
|
26
109
|
} catch {}
|
|
27
110
|
|
|
28
|
-
|
|
111
|
+
// 3. Inspect recent session transcripts / chat logs if enabled
|
|
112
|
+
let sessionDecisions = [];
|
|
113
|
+
if (scanSessions) {
|
|
114
|
+
sessionDecisions = await scanSessionLogs(root, sessionDir);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const summaryLines = [
|
|
29
118
|
`# Auto-Derived Knowledge Snapshot: ${path.basename(root)}`,
|
|
30
119
|
`Active Branch: \`${branch}\``,
|
|
31
120
|
`Inspected At: ${new Date().toISOString()}`,
|
|
32
121
|
``,
|
|
33
122
|
`## 📜 Recent Git Activity`,
|
|
34
123
|
...(gitLogs.length ? gitLogs.map((l) => `- \`${l}\``) : ["- No git history found"]),
|
|
35
|
-
]
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
if (sessionDecisions.length > 0) {
|
|
127
|
+
summaryLines.push(``, `## 💬 Session Conversation Digest (Watchdog)`);
|
|
128
|
+
const seenKeywords = new Set();
|
|
129
|
+
for (const d of sessionDecisions) {
|
|
130
|
+
summaryLines.push(`- **[${d.keyword.toUpperCase()}]** (${d.file}): ${d.snippet}`);
|
|
131
|
+
if (!seenKeywords.has(d.keyword)) {
|
|
132
|
+
seenKeywords.add(d.keyword);
|
|
133
|
+
notesCreated.push({
|
|
134
|
+
title: `Session Digest: ${d.keyword.toUpperCase()} Discussion (${new Date().toISOString().substring(0, 10)})`,
|
|
135
|
+
content: `# Session Digest: ${d.keyword.toUpperCase()} Discussion\n\nExtracted from conversation watchdog on branch \`${branch}\` (Source: \`${d.file}\`).\n\n## Discussion Context\n\n- **Role (${d.role})**: ${d.snippet}\n`,
|
|
136
|
+
tags: ["session-digest", "watchdog", d.keyword.toLowerCase()],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const summary = summaryLines.join("\n");
|
|
36
143
|
|
|
37
144
|
return {
|
|
38
145
|
projectDir: root,
|
|
39
146
|
branch,
|
|
40
147
|
recentCommits: gitLogs,
|
|
148
|
+
sessionDecisions,
|
|
41
149
|
summaryText: summary,
|
|
42
150
|
notesCreated,
|
|
43
151
|
};
|
package/project-memory/server.js
CHANGED
|
@@ -437,12 +437,14 @@ class ProjectMemoryServer {
|
|
|
437
437
|
},
|
|
438
438
|
{
|
|
439
439
|
name: "memory_auto_index",
|
|
440
|
-
description: "Run autonomous background observer to inspect git commits, branch switches,
|
|
440
|
+
description: "Run autonomous background observer to inspect git commits, branch switches, file modifications, and chat session transcripts to auto-derive project notes and architectural decisions.",
|
|
441
441
|
inputSchema: {
|
|
442
442
|
type: "object",
|
|
443
443
|
properties: {
|
|
444
444
|
dir: { type: "string", description: "Project directory (defaults to CWD)" },
|
|
445
445
|
dryRun: { type: "boolean", description: "Preview derived knowledge without saving", default: false },
|
|
446
|
+
scanSessions: { type: "boolean", description: "Scan recent chat session logs/transcripts to auto-extract architectural decisions and key discussions", default: true },
|
|
447
|
+
sessionDir: { type: "string", description: "Optional custom directory containing session JSONL logs" },
|
|
446
448
|
},
|
|
447
449
|
},
|
|
448
450
|
},
|
|
@@ -876,17 +878,26 @@ class ProjectMemoryServer {
|
|
|
876
878
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
877
879
|
}
|
|
878
880
|
|
|
879
|
-
async autoIndex({ dir, dryRun = false } = {}) {
|
|
881
|
+
async autoIndex({ dir, dryRun = false, scanSessions = true, sessionDir = null } = {}) {
|
|
880
882
|
const p = this.paths(dir);
|
|
881
|
-
const res = await runAutoIndexer(dir, { dryRun });
|
|
883
|
+
const res = await runAutoIndexer(dir, { dryRun, scanSessions, sessionDir });
|
|
882
884
|
if (!dryRun && res.summaryText) {
|
|
883
885
|
await this.save({
|
|
884
886
|
title: `Auto-Index ${new Date().toISOString().substring(0, 10)} (${res.branch})`,
|
|
885
887
|
content: res.summaryText,
|
|
886
888
|
dir,
|
|
887
|
-
tags: ["auto-index", "git"],
|
|
889
|
+
tags: ["auto-index", "git", "session-digest"],
|
|
888
890
|
kind: "auto-derived"
|
|
889
891
|
});
|
|
892
|
+
for (const note of (res.notesCreated || [])) {
|
|
893
|
+
await this.save({
|
|
894
|
+
title: note.title,
|
|
895
|
+
content: note.content,
|
|
896
|
+
dir,
|
|
897
|
+
tags: note.tags || ["session-digest", "auto-derived"],
|
|
898
|
+
kind: "session-digest"
|
|
899
|
+
});
|
|
900
|
+
}
|
|
890
901
|
}
|
|
891
902
|
return { content: [{ type: "text", text: res.summaryText + (dryRun ? "\n\n(Dry Run — note not saved)" : "\n\n(Saved to project memory vault)") }] };
|
|
892
903
|
}
|