codex-dev-mcp-suite 1.0.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/.env.example ADDED
@@ -0,0 +1,26 @@
1
+ # All network features are OPTIONAL. Without them, recall falls back to keyword
2
+ # search (fully offline). Any OpenAI-compatible endpoint works: OpenAI, Ollama,
3
+ # LM Studio, OpenRouter, vLLM, LiteLLM, 9router, etc.
4
+
5
+ # Base URL of an OpenAI-compatible API (must expose /v1/...)
6
+ LLM_BASE_URL=http://localhost:11434/v1
7
+ # API key for that endpoint (leave blank for local servers that don't need one)
8
+ LLM_API_KEY=
9
+
10
+ # Optional: semantic embedding recall (needs /v1/embeddings)
11
+ EMBED_MODEL=nomic-embed-text
12
+ EMBED_TIMEOUT_MS=12000
13
+
14
+ # Optional: LLM rerank recall (needs /v1/chat/completions)
15
+ # Used automatically when embeddings are unavailable.
16
+ RERANK_MODEL=llama3.1:8b
17
+ RERANK_TIMEOUT_MS=30000
18
+ RERANK_ENABLED=1
19
+
20
+ # Storage locations (defaults shown; override if you want)
21
+ # MEMORY_VAULT_DIR=~/.codex/memories/vault
22
+ # JOURNAL_DIR=~/.codex/memories/journal
23
+ # CHECKPOINT_DIR=~/.codex/memories/checkpoints
24
+
25
+ # Backward-compatible aliases (still honored): NINEROUTER_URL, NINEROUTER_KEY,
26
+ # EMBED_URL, EMBED_KEY, RERANK_URL, RERANK_KEY
@@ -0,0 +1,297 @@
1
+ # Codex Dev MCP Suite — Solo Dev / Vibecoder Toolkit
2
+
3
+ Built for sessions that hit "input too long" / get compacted, so you can stop
4
+ copy-pasting context across sessions. All servers are local, file-based, and
5
+ split storage per-project (by directory path hash), so projects never mix.
6
+
7
+ Servers live in `~/.codex/mcp-servers/<name>/` and are registered in
8
+ `~/.codex/config.toml`. Restart/reload Codex to load them.
9
+
10
+ Shared storage root: `~/.codex/memories/`
11
+ - `memories/vault/` → project-memory notes
12
+ - `memories/checkpoints/` → checkpoint snapshots
13
+ - `memories/journal/` → devjournal logs + handoffs
14
+
15
+ `dir` argument defaults to the current working directory on every tool, so in
16
+ normal use you don't pass it — context auto-separates per project folder.
17
+
18
+ --------------------------------------------------------------------------------
19
+ ## 1) project-memory — searchable Markdown knowledge vault
20
+ Obsidian-style `.md` notes + Context7-style on-demand recall (keyword index).
21
+
22
+ Tools:
23
+ - memory_save {title, content, tags?, kind?, dir?} → save + auto-index a note
24
+ - memory_recall {query, limit?, full?, dir?} → pull most relevant notes
25
+ - memory_list {limit?, dir?} → list notes (newest first)
26
+ - memory_get {id, dir?} → full Markdown of one note
27
+ - memory_delete {id, dir?} → remove a note
28
+
29
+ Use it for: durable knowledge — decisions, gotchas, API quirks, snippets.
30
+ Recall is keyword-based (fast, local, no embeddings).
31
+
32
+ Example flow:
33
+ 1. After solving something: memory_save title="Fix CORS on /api" content="..." tags=["cors","api"]
34
+ 2. Next session: memory_recall query="how did we fix cors" → returns that note.
35
+
36
+ --------------------------------------------------------------------------------
37
+ ## 2) devjournal — session timeline + handoff/resume (anti-compaction)
38
+ The key tool for your "session penuh / limit" problem.
39
+
40
+ Tools:
41
+ - journal_log {title, body?, type?, dir?} type: note|decision|blocker|done|idea
42
+ - journal_handoff {summary, next_steps?, open_questions?, active_files?, dir?}
43
+ - journal_resume {recent?, dir?} → latest handoff + recent entries
44
+ - journal_timeline {limit?, type?, dir?}
45
+ - journal_search {query, limit?, dir?} → relevance search (rerank/keyword)
46
+ - journal_clear_handoff {dir?}
47
+
48
+ Use it for: never losing your place.
49
+ - BEFORE a session ends / before context compacts: journal_handoff with what you
50
+ did + next steps + active files.
51
+ - START of a new session: journal_resume → instantly rebuild context, no paste.
52
+
53
+ Storage is append-only `journal.jsonl` + human-readable `journal.md` mirror +
54
+ canonical `handoff.json`.
55
+
56
+ --------------------------------------------------------------------------------
57
+ ## 3) checkpoint — git-independent file snapshots (safe vibecoding)
58
+ Snapshot/restore project text files so you can experiment freely and roll back.
59
+
60
+ Tools:
61
+ - checkpoint_create {label?, dir?} → snapshot all text files now
62
+ - checkpoint_list {dir?}
63
+ - checkpoint_diff {id, dir?} → added / modified / deleted vs now
64
+ - checkpoint_restore {id, clean?, dir?} → revert files (clean=true also removes new files)
65
+ - checkpoint_delete {id, dir?}
66
+
67
+ Use it for: "let me try a wild refactor" without committing to git.
68
+ - checkpoint_create label="before experiment"
69
+ - ...make changes...
70
+ - checkpoint_diff <id> to see what moved, checkpoint_restore <id> to undo.
71
+
72
+ Notes/limits: skips binary files and files >2MB; ignores node_modules/.git/
73
+ build dirs; caps at 4000 files. Content-addressed storage dedupes unchanged files.
74
+
75
+ --------------------------------------------------------------------------------
76
+ ## 4) context-pack — token-efficient project briefing
77
+ Get oriented in a project without dumping files into context.
78
+
79
+ Tools:
80
+ - pack_overview {include_readme?, dir?} → stack, scripts, key files, top dirs, README excerpt
81
+ - pack_tree {max_depth?, max_entries?, dir?}
82
+ - pack_outline {file, dir?} → top-level functions/classes/exports only
83
+ - pack_search {query, limit?, dir?} → fast path/filename match
84
+
85
+ Use it for: session start in an unfamiliar/large repo. pack_overview first,
86
+ then pack_outline on the few files that matter — far cheaper than reading whole files.
87
+ Detects: Node/JS (+ Next/React/Vue/Svelte/Express/TS/Tailwind/Prisma/Vite),
88
+ Python, Rust, Go, Java/JVM, PHP, Ruby, Docker.
89
+
90
+ --------------------------------------------------------------------------------
91
+ ## Recommended daily loop
92
+ 1. Session start: context-pack.pack_overview + devjournal.journal_resume + memory_recall (topic)
93
+ 2. Before risky change: checkpoint.checkpoint_create
94
+ 3. While working: memory_save (durable facts), journal_log (events/blockers)
95
+ 4. Session end / context about to compact: journal_handoff
96
+
97
+ ## Maintenance
98
+ - All data is plain files under ~/.codex/memories/ — back up or inspect freely.
99
+ - To wipe one project's data, delete its slug folder under each store.
100
+ - Env overrides: MEMORY_VAULT_DIR, CHECKPOINT_DIR, JOURNAL_DIR.
101
+
102
+ ================================================================================
103
+ ## UPGRADE 1 — Semantic (embedding) recall in project-memory
104
+ project-memory now stores a semantic embedding per note and uses it for recall
105
+ when available. Hybrid scoring: 0.8 * cosine(semantic) + 0.2 * keyword.
106
+
107
+ - Embeddings via 9router (`/v1/embeddings`), model `bm/baai/bge-m3`.
108
+ - Configured in config.toml under [mcp_servers.project-memory.env]:
109
+ NINEROUTER_URL, NINEROUTER_KEY, EMBED_MODEL, EMBED_TIMEOUT_MS.
110
+ - GRACEFUL FALLBACK: if the embedding endpoint is unavailable (e.g. 503 "no
111
+ channel"), recall automatically falls back to keyword search. Nothing breaks.
112
+ - memory_recall output is tagged with the mode used: [semantic] or [keyword],
113
+ and per-note shows sim:0.xxx (semantic) or score:N (keyword).
114
+
115
+ New tool:
116
+ - memory_reindex {force?, dir?} → backfill embeddings for notes that don't have
117
+ one yet. Run it once the bge-m3 channel is live on your 9router so old notes
118
+ become semantically searchable. Safe to run repeatedly; force=true re-embeds all.
119
+
120
+ Status note: at build time the bge-m3 channel returned 503, so notes are saved
121
+ keyword-only and recall runs in [keyword] mode. The moment the channel is up,
122
+ new saves embed automatically and `memory_reindex` upgrades old ones — no code
123
+ changes needed.
124
+
125
+ To override the embedding model/endpoint: set EMBED_MODEL / NINEROUTER_URL /
126
+ NINEROUTER_KEY (or EMBED_URL / EMBED_KEY) in the env block.
127
+
128
+ ================================================================================
129
+ ## UPGRADE 2 — auto-capture plugin (hooks)
130
+ A Codex plugin that removes the manual step of pasting context across sessions.
131
+
132
+ Marketplace: `codex-dev-suite` (local) at
133
+ ~/.codex/marketplaces/codex-dev-suite
134
+ Plugin: `auto-capture` (installed + enabled).
135
+
136
+ What it does:
137
+ - SessionStart (matcher: startup|clear|compact|resume): runs
138
+ scripts/session-start.sh, which finds the devjournal handoff + recent entries
139
+ for the CURRENT project (same slug algorithm as the MCP servers) and prints
140
+ them to stdout so Codex injects them as context. This is the anti-compaction
141
+ piece: after a compact/clear/resume, your handoff reappears automatically.
142
+ - PostToolUse (async, throttled): runs scripts/post-tool.sh, which counts tool
143
+ calls per project and, after >=25 calls and at most once per 30 min, emits a
144
+ short reminder to call journal_handoff / memory_save so work survives compaction.
145
+
146
+ Trust: Codex requires you to TRUST the hook the first time. On your next session
147
+ start in a trusted project, Codex will prompt to trust the auto-capture hook —
148
+ approve it once. (Advanced/automation only: `--dangerously-bypass-hook-trust`.)
149
+
150
+ Tuning (env, optional):
151
+ - AUTOCAP_THROTTLE seconds between nudges (default 1800)
152
+ - AUTOCAP_STATE_DIR where call counters live (default ~/.codex/memories/.autocap)
153
+
154
+ Manage:
155
+ - codex plugin list | grep auto-capture
156
+ - codex plugin remove auto-capture@codex-dev-suite
157
+ - Edit scripts under ~/.codex/marketplaces/codex-dev-suite/plugins/auto-capture/
158
+ then re-run `codex plugin marketplace add ~/.codex/marketplaces/codex-dev-suite`
159
+ and `codex plugin add auto-capture@codex-dev-suite` to refresh the cached copy.
160
+
161
+ How the pieces fit:
162
+ auto-capture (SessionStart) --reads--> devjournal handoff --so--> you resume instantly
163
+ auto-capture (PostToolUse) --reminds--> you to journal_handoff / memory_save
164
+ project-memory.memory_recall --semantic/keyword--> durable facts on demand
165
+
166
+ ================================================================================
167
+ ## Testing
168
+ A dependency-free test suite covers all four servers (26 tests).
169
+
170
+ Run everything:
171
+ cd ~/.codex/mcp-servers && node run-tests.mjs
172
+
173
+ Run one server:
174
+ cd ~/.codex/mcp-servers/<server> && npm test
175
+
176
+ Layout:
177
+ - _testkit/harness.mjs shared MCP stdio client + tiny assert/test runner
178
+ - <server>/test.mjs per-server tests (spawn server, call tools, assert)
179
+ - run-tests.mjs runs all suites, prints combined summary
180
+
181
+ Tests run fully offline (project-memory forces keyword mode by passing an empty
182
+ embedding key), use temp dirs, and clean up after themselves — safe to run anytime.
183
+
184
+ ================================================================================
185
+ ## Backfill — import past Codex sessions
186
+ Imports your existing Codex session history (~/.codex/sessions/*.jsonl) into
187
+ project-memory + devjournal, grouped by each session's cwd (project).
188
+
189
+ USE backfill-sessions-v2.mjs (see "Backfill v2" below). The original v1 script
190
+ and its state file (.backfill-state.json) have been removed in favor of v2,
191
+ which extracts deeper content (commands, plan, files, tool counts) and backdates
192
+ entries to the original session time.
193
+
194
+ Embeddings: backfill runs with embeddings OFF (keyword-only) for speed. Once the
195
+ bge-m3 channel on 9router is live, run memory_reindex per project to upgrade old
196
+ notes to semantic search. (LLM rerank still works in the meantime — see UPGRADE 3/4.)
197
+
198
+ ================================================================================
199
+ ## UPGRADE 3 — LLM rerank (Kiro) when embeddings are unavailable
200
+ Since the bge-m3 embedding channel is currently 503, project-memory now has a
201
+ middle tier between semantic and keyword: an LLM reranker using a 9router chat
202
+ model (default Kiro `kr/claude-haiku-4.5`).
203
+
204
+ Recall mode precedence (auto-selected, shown in output as [mode]):
205
+ 1. [semantic] — if note embeddings + query embedding are available (bge-m3)
206
+ 2. [rerank] — keyword prefilter (top ~20; falls back to most-recent if no
207
+ keyword hit) → chat model picks/orders the most relevant → ids
208
+ 3. [keyword] — pure keyword scoring (always-available fallback)
209
+
210
+ Why this matters: rerank handles semantic queries with zero keyword overlap.
211
+ Example: query "how do users sign in securely with tokens" correctly returns a
212
+ note titled "JWT auth flow" — no shared words.
213
+
214
+ Config (config.toml, [mcp_servers.project-memory.env]):
215
+ RERANK_MODEL = "kr/claude-haiku-4.5" # any 9router chat model id
216
+ RERANK_TIMEOUT_MS = "30000"
217
+ RERANK_ENABLED = "1" # set "0" to force keyword-only
218
+
219
+ Files: rerank.js (chat client + parser), wired into server.js recall().
220
+ Graceful: any failure/timeout/non-200 falls back to keyword. Never throws.
221
+
222
+ Cost note: rerank makes ONE small chat call per recall (haiku, temp 0,
223
+ ~200 max tokens). Cheap, but it does use your 9router quota. Disable with
224
+ RERANK_ENABLED=0 if you want zero-cost keyword-only recall.
225
+
226
+ Tests: project-memory/test.rerank.mjs (online; auto-skips if 9router is down).
227
+
228
+ ================================================================================
229
+ ## UPGRADE 4 — devjournal journal_search (rerank)
230
+ devjournal now has a relevance search tool, mirroring project-memory's rerank:
231
+
232
+ - journal_search {query, limit?, dir?}
233
+ Keyword prefilter over journal entries, then LLM rerank (9router Kiro
234
+ kr/claude-haiku-4.5) when available; else keyword scoring. Output tagged
235
+ [rerank] or [keyword]. Falls back to recent entries for semantic-style
236
+ queries with no keyword overlap.
237
+
238
+ journal_resume is intentionally unchanged — it returns the canonical latest
239
+ handoff + recent entries (no query). Use journal_search when you want to find
240
+ entries about a specific topic across past sessions.
241
+
242
+ Files: devjournal/rerank.js (same module as project-memory), wired into
243
+ devjournal/server.js. Same env knobs apply (RERANK_MODEL / RERANK_ENABLED /
244
+ RERANK_TIMEOUT_MS); they're already set in config.toml for project-memory, and
245
+ devjournal reads NINEROUTER_URL/KEY from its own env block — note: enable rerank
246
+ for devjournal by adding the RERANK_* + NINEROUTER_* vars to
247
+ [mcp_servers.devjournal.env] too (see below).
248
+
249
+ ================================================================================
250
+ ## Backfill v2 — deep extraction + original timestamps
251
+ backfill-sessions-v2.mjs supersedes v1. Per session it now extracts:
252
+ - all user prompts, the plan steps (update_plan), shell commands run, files
253
+ touched (apply_patch paths + cat/rm/cp/redirect heuristics), tool usage
254
+ counts, turn/command/reasoning counts, and the final assistant note.
255
+ - The note is BACKDATED to the session's original start time.
256
+
257
+ New tool args enabling this:
258
+ - memory_save ... created: "<ISO>" # backdate the note
259
+ - journal_log ... ts: "<ISO>" # backdate the entry
260
+
261
+ Usage:
262
+ cd ~/.codex/mcp-servers
263
+ node backfill-sessions-v2.mjs --dry --min-prompts 2 # preview deep extract
264
+ node backfill-sessions-v2.mjs --min-prompts 2 --wipe # replace old session
265
+ # entries, re-import deep
266
+ node backfill-sessions-v2.mjs --min-prompts 2 # incremental (new only)
267
+
268
+ --wipe removes existing kind/type=="session" entries (from any prior backfill)
269
+ before re-importing, so you don't get duplicates. Source of truth is always
270
+ ~/.codex/sessions, so this is reversible.
271
+
272
+ State: ~/.codex/memories/.backfill-v2-state.json (separate from v1).
273
+ Result: 87 sessions re-imported with original timestamps (2026-03-16 .. 2026-06-13),
274
+ 0 errors; timestamps verified 87/87 matching original session dates.
275
+
276
+ ================================================================================
277
+ ## UPGRADE 5 — project-memory resources (browsable notes)
278
+ project-memory now also exposes every saved note as a read-only MCP resource,
279
+ so notes can be browsed/opened without calling a tool.
280
+
281
+ - Capability: declares `resources` alongside `tools`.
282
+ - resources/list → one entry per note across ALL projects in the vault, with
283
+ name (title), description ([kind] tags — created), mimeType text/markdown.
284
+ - resources/read → full Markdown of a note.
285
+ - URI scheme: memory://<project-slug>/<noteId>
286
+
287
+ Note on naming: the MCP server id is `project-memory` (hyphen), as written in
288
+ config.toml. Some UIs show a tool namespace like `project_memory` (underscore) —
289
+ that label is NOT the server id. Resource/admin calls must target the real id
290
+ `project-memory`.
291
+
292
+ checkpoint / context-pack / devjournal remain tools-only by design (they're
293
+ actions, not browsable data). devjournal entries are best queried via
294
+ journal_search / journal_timeline.
295
+
296
+ Verified: resources/list returned all current notes; resources/read returned a
297
+ note's Markdown. Full test suite still 27/27.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 maximalabs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # Codex Dev MCP Suite
2
+
3
+ [![CI](https://github.com/verrysimatupang99/codex-dev-mcp-suite/actions/workflows/ci.yml/badge.svg)](https://github.com/verrysimatupang99/codex-dev-mcp-suite/actions/workflows/ci.yml)
4
+
5
+ Four local, file-based MCP servers for solo developers and vibecoders who keep
6
+ losing context when sessions hit "input too long" / get compacted / restart.
7
+ Stop re-pasting context across sessions.
8
+
9
+ All servers are **local, dependency-light (only the MCP SDK), and split storage
10
+ per-project** by the working directory. Works with any MCP-capable client
11
+ (Codex CLI, Claude Code, Cursor, Cline, etc.).
12
+
13
+ ## The four servers
14
+
15
+ | Server | What it does | Key tools |
16
+ |---|---|---|
17
+ | **project-memory** | Searchable Markdown knowledge vault (Obsidian-style notes + on-demand recall). Notes are also exposed as MCP resources. | `memory_save`, `memory_recall`, `memory_list`, `memory_get`, `memory_delete`, `memory_reindex` |
18
+ | **devjournal** | Per-project session timeline + handoff/resume (anti-compaction). | `journal_log`, `journal_handoff`, `journal_resume`, `journal_timeline`, `journal_search`, `journal_clear_handoff` |
19
+ | **checkpoint** | Git-independent file snapshots for safe experimentation. | `checkpoint_create`, `checkpoint_list`, `checkpoint_diff`, `checkpoint_restore`, `checkpoint_delete` |
20
+ | **context-pack** | Token-efficient project briefing (stack, tree, symbols, search). | `pack_overview`, `pack_tree`, `pack_outline`, `pack_search` |
21
+
22
+ ## Recall quality (project-memory & devjournal)
23
+
24
+ Recall auto-selects the best available mode:
25
+
26
+ 1. **semantic** — if an embeddings endpoint is configured (any OpenAI-compatible `/v1/embeddings`: OpenAI, Ollama, LM Studio, OpenRouter, vLLM, LiteLLM, 9router, ...)
27
+ 2. **rerank** — keyword prefilter then an LLM reranker (any OpenAI-compatible chat model)
28
+ 3. **keyword** — always-available offline fallback
29
+
30
+ All network features degrade gracefully: no endpoint = keyword mode, never an error.
31
+
32
+ ## Install
33
+
34
+ Requires Node.js >= 18.
35
+
36
+ ```bash
37
+ git clone https://github.com/<you>/codex-dev-mcp-suite.git
38
+ cd codex-dev-mcp-suite
39
+ # install the MCP SDK in each server
40
+ for s in project-memory checkpoint context-pack devjournal; do (cd "$s" && npm install); done
41
+ ```
42
+
43
+ ### Register with your MCP client
44
+
45
+ **Codex CLI** (`~/.codex/config.toml`):
46
+
47
+ ```toml
48
+ [mcp_servers.project-memory]
49
+ command = "node"
50
+ args = ["/abs/path/codex-dev-mcp-suite/project-memory/server.js"]
51
+ [mcp_servers.project-memory.env]
52
+ MEMORY_VAULT_DIR = "~/.codex/memories/vault"
53
+ # optional recall upgrades (see .env.example)
54
+ # LLM_BASE_URL = "http://localhost:11434/v1" # any OpenAI-compatible endpoint
55
+ # LLM_API_KEY = "..."
56
+ # RERANK_MODEL = "llama3.1:8b"
57
+
58
+ [mcp_servers.checkpoint]
59
+ command = "node"
60
+ args = ["/abs/path/codex-dev-mcp-suite/checkpoint/server.js"]
61
+
62
+ [mcp_servers.context-pack]
63
+ command = "node"
64
+ args = ["/abs/path/codex-dev-mcp-suite/context-pack/server.js"]
65
+
66
+ [mcp_servers.devjournal]
67
+ command = "node"
68
+ args = ["/abs/path/codex-dev-mcp-suite/devjournal/server.js"]
69
+ [mcp_servers.devjournal.env]
70
+ JOURNAL_DIR = "~/.codex/memories/journal"
71
+ ```
72
+
73
+ **Claude Code / Cursor / Cline** (`mcpServers` JSON): same idea — `command: "node"`,
74
+ `args: ["/abs/path/<server>/server.js"]`, optional `env`.
75
+
76
+ ## Daily workflow
77
+
78
+ - Session start: `pack_overview` + `journal_resume` + `memory_recall "<topic>"`
79
+ - Before a risky change: `checkpoint_create`
80
+ - While working: `memory_save` (durable facts), `journal_log` (events/blockers)
81
+ - Session end / before compaction: `journal_handoff`
82
+
83
+ ## Optional: import your existing Codex history
84
+
85
+ `backfill-sessions-v2.mjs` reads `~/.codex/sessions/*.jsonl` and imports each
86
+ session (prompts, plan, commands, files touched) into project-memory + devjournal,
87
+ grouped by project, backdated to the original session time.
88
+
89
+ ```bash
90
+ node backfill-sessions-v2.mjs --dry --min-prompts 2 # preview
91
+ node backfill-sessions-v2.mjs --min-prompts 2 # import
92
+ ```
93
+
94
+ ## Tests
95
+
96
+ ```bash
97
+ node run-tests.mjs # all servers (offline, ~27 tests)
98
+ cd project-memory && npm test
99
+ ```
100
+
101
+ ## Privacy
102
+
103
+ Everything is stored as plain files on your machine. No data leaves your
104
+ computer unless you explicitly configure an embeddings/rerank endpoint. Your
105
+ personal vault/journal/checkpoints are gitignored.
106
+
107
+ ## License
108
+
109
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Minimal MCP stdio test harness — zero external deps.
3
+ * Provides: McpClient (spawn server, initialize, callTool), and a tiny
4
+ * assert/test runner (describe/it/run) with colored-ish plain output.
5
+ */
6
+ import { spawn } from "child_process";
7
+
8
+ export class McpClient {
9
+ constructor(serverPath, env = {}) {
10
+ this.serverPath = serverPath;
11
+ this.env = { ...process.env, ...env };
12
+ this.proc = null;
13
+ this.buf = "";
14
+ this.pending = new Map();
15
+ this.nextId = 1;
16
+ }
17
+
18
+ async start() {
19
+ this.proc = spawn("node", [this.serverPath], { env: this.env, stdio: ["pipe", "pipe", "ignore"] });
20
+ this.proc.stdout.on("data", (d) => this._onData(d.toString()));
21
+ await this._rpc("initialize", {
22
+ protocolVersion: "2024-11-05",
23
+ capabilities: {},
24
+ clientInfo: { name: "testkit", version: "1.0.0" },
25
+ });
26
+ this._notify("notifications/initialized");
27
+ await new Promise((r) => setTimeout(r, 50));
28
+ }
29
+
30
+ _onData(chunk) {
31
+ this.buf += chunk;
32
+ let idx;
33
+ while ((idx = this.buf.indexOf("\n")) !== -1) {
34
+ const line = this.buf.slice(0, idx).trim();
35
+ this.buf = this.buf.slice(idx + 1);
36
+ if (!line) continue;
37
+ let msg;
38
+ try { msg = JSON.parse(line); } catch { continue; }
39
+ if (msg.id != null && this.pending.has(msg.id)) {
40
+ const { resolve } = this.pending.get(msg.id);
41
+ this.pending.delete(msg.id);
42
+ resolve(msg);
43
+ }
44
+ }
45
+ }
46
+
47
+ _send(obj) { this.proc.stdin.write(JSON.stringify(obj) + "\n"); }
48
+ _notify(method, params) { this._send({ jsonrpc: "2.0", method, params }); }
49
+
50
+ _rpc(method, params, timeoutMs = 30000) {
51
+ const id = this.nextId++;
52
+ return new Promise((resolve, reject) => {
53
+ const timer = setTimeout(() => {
54
+ this.pending.delete(id);
55
+ reject(new Error(`RPC timeout: ${method}`));
56
+ }, timeoutMs);
57
+ this.pending.set(id, { resolve: (m) => { clearTimeout(timer); resolve(m); } });
58
+ this._send({ jsonrpc: "2.0", id, method, params });
59
+ });
60
+ }
61
+
62
+ async listTools() {
63
+ const res = await this._rpc("tools/list", {});
64
+ return (res.result?.tools || []).map((t) => t.name);
65
+ }
66
+
67
+ async callTool(name, args = {}, timeoutMs = 30000) {
68
+ const res = await this._rpc("tools/call", { name, arguments: args }, timeoutMs);
69
+ if (res.error) throw new Error(`RPC error: ${res.error.message}`);
70
+ const text = res.result?.content?.[0]?.text ?? "";
71
+ return { text, isError: Boolean(res.result?.isError), raw: res.result };
72
+ }
73
+
74
+ async stop() {
75
+ try { this.proc?.kill(); } catch { /* ignore */ }
76
+ }
77
+ }
78
+
79
+ // ---- tiny test runner ----
80
+ const tests = [];
81
+ let currentSuite = "";
82
+ export function describe(name, fn) { currentSuite = name; fn(); currentSuite = ""; }
83
+ export function it(name, fn) { tests.push({ suite: currentSuite, name, fn }); }
84
+
85
+ export function assert(cond, msg) {
86
+ if (!cond) throw new Error("assertion failed: " + (msg || ""));
87
+ }
88
+ export function assertIncludes(haystack, needle, msg) {
89
+ if (!String(haystack).includes(needle)) {
90
+ throw new Error(`expected to include "${needle}"${msg ? " — " + msg : ""}\n got: ${String(haystack).slice(0, 300)}`);
91
+ }
92
+ }
93
+ export function assertEqual(a, b, msg) {
94
+ if (a !== b) throw new Error(`expected ${JSON.stringify(b)}, got ${JSON.stringify(a)}${msg ? " — " + msg : ""}`);
95
+ }
96
+
97
+ export async function run() {
98
+ let pass = 0, fail = 0;
99
+ const failures = [];
100
+ for (const t of tests) {
101
+ const label = t.suite ? `${t.suite} › ${t.name}` : t.name;
102
+ try {
103
+ await t.fn();
104
+ pass++;
105
+ console.log(` ✓ ${label}`);
106
+ } catch (e) {
107
+ fail++;
108
+ failures.push({ label, err: e });
109
+ console.log(` ✗ ${label}`);
110
+ console.log(` ${e.message.split("\n").join("\n ")}`);
111
+ }
112
+ }
113
+ console.log(`\n${pass} passed, ${fail} failed (${tests.length} total)`);
114
+ if (fail > 0) process.exitCode = 1;
115
+ return { pass, fail, failures };
116
+ }
117
+
118
+ // unique temp dir helper
119
+ import os from "os";
120
+ import path from "path";
121
+ import fs from "fs";
122
+ export function tmpDir(prefix = "mcp-test-") {
123
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
124
+ return d;
125
+ }
126
+ export function rmrf(p) { try { fs.rmSync(p, { recursive: true, force: true }); } catch { /* ignore */ } }