codex-dev-mcp-suite 1.6.0 → 1.8.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 CHANGED
@@ -1,3 +1,26 @@
1
+ ## 1.8.0 - 2026-07-22
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).
14
+
15
+ ## 1.7.0 - 2026-07-22
16
+
17
+ ### Added
18
+ - **Obsidian Vault Parity & MOC Tools**:
19
+ - `memory_moc` Map of Content generator & `.obsidian` vault structure integration.
20
+ - `memory_graph` knowledge graph visualization & backlinks.
21
+ - **Codebase Security Audit Tool (`pack_audit`)**:
22
+ - Detect missing `.gitignore`, exposed secrets/keys (`.env`, `.pem`, `id_rsa`), and overly large files.
23
+
1
24
  ## 1.5.0
2
25
 
3
26
  - add `memory_link` with wiki-link resolution for `[[id]]`, `[[title]]`, `[[project:id]]`, and `[[project:title]]`, plus backlink inspection
@@ -117,3 +140,19 @@
117
140
  ### Compatibility
118
141
  - Legacy env names remain supported: `LLM_*`, `NINEROUTER_*`, `EMBED_*`, and `RERANK_*`.
119
142
  - Offline keyword search remains the default if no model endpoint is configured.
143
+
144
+ ## [1.7.0] - 2026-07-14 — Obsidian-grade vault (feat/obsidian-grade-vault)
145
+ ### Added (project-memory)
146
+ - `memory_moc` — generate/refresh Obsidian-style Map of Content (`MOC.md`): all notes as wikilinks, `#tag` index, and backlink graph. Auto-refreshed on every save/delete.
147
+ - `memory_graph` — export note graph (nodes + directed edges from `[[wikilinks]]`) as JSON for external graph views.
148
+ - Auto-bootstrap `.obsidian/` workspace (`app.json` + `workspace.json`) on first save, so the vault opens directly in the Obsidian app with graph view + tag pages.
149
+ - MOC regenerates automatically after `memory_save` and `memory_delete`.
150
+
151
+ ## [1.8.0] - 2026-07-14 — Vibecoder ergonomics (P1/P2, feat/obsidian-grade-vault)
152
+ ### Added (project-memory)
153
+ - Auto-resolve `[[wikilinks]]` + backlinks immediately on `memory_save` (graph fresh without waiting for recall).
154
+ - Per-tag index pages: `tags/<tag>.md` emitted alongside MOC, pruned when tag empties.
155
+ - `aliases` field on notes (Obsidian-style); `[[alias]]` now resolves via `resolveLink`.
156
+ ### Added (devjournal)
157
+ - `journal_daily` — Obsidian-style daily note (`daily/YYYY-MM-DD.md`); read or append mode.
158
+ - `journal_log` now auto-appends a line to today's daily note.
@@ -8,7 +8,7 @@
8
8
  * window. Detects stack, shows a pruned tree, surfaces key files, and
9
9
  * extracts top-level symbols (functions/classes/exports) heuristically.
10
10
  *
11
- * Tools: pack_overview, pack_tree, pack_outline, pack_search
11
+ * Tools: pack_overview, pack_tree, pack_outline, pack_search, pack_audit
12
12
  */
13
13
 
14
14
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -19,6 +19,7 @@ import {
19
19
  } from "@modelcontextprotocol/sdk/types.js";
20
20
  import fs from "fs/promises";
21
21
  import path from "path";
22
+ import { execSync } from "child_process";
22
23
 
23
24
  const IGNORE = new Set([
24
25
  "node_modules", ".git", ".next", "dist", "build", "target", ".venv",
@@ -34,6 +35,32 @@ const KEY_FILES = [
34
35
  ];
35
36
  const CODE_EXT = new Set([".js", ".mjs", ".ts", ".tsx", ".jsx", ".py", ".go", ".rs", ".java", ".rb", ".php", ".c", ".cpp", ".h", ".sh"]);
36
37
  const MAX_FILE_BYTES = 1_500_000;
38
+ const AUDIT_SENSITIVE_PATTERNS = [
39
+ /^\.env/i, /\.pem$/i, /\.key$/i, /\.p12$/i, /\.pfx$/i,
40
+ /id_rsa/i, /id_ed25519/i, /id_ecdsa/i, /id_dsa/i,
41
+ /known_hosts/i, /authorized_keys/i,
42
+ /credentials/i, /password/i, /secret/i, /token/i,
43
+ /\.htpasswd/i, /\.netrc/i, /npmrc$/i,
44
+ /service[_-]?account.*\.json$/i,
45
+ ];
46
+ const AUDIT_SECRET_CONTENT_RE = [
47
+ /sk-[a-zA-Z0-9]{20,}/i,
48
+ /ghp_[a-zA-Z0-9]{36}/i,
49
+ /gho_[a-zA-Z0-9]{36}/i,
50
+ /glpat-[a-zA-Z0-9\-_]{20}/i,
51
+ /AKIA[0-9A-Z]{16}/i,
52
+ /AIza[0-9A-Za-z_-]{35}/i,
53
+ /bearer\s+[a-zA-Z0-9_\-\.]{20,}/i,
54
+ /api[_-]?key\s*[:=]\s*["']?[a-zA-Z0-9_\-]{16,}/i,
55
+ /password\s*[:=]\s*["']?[^\s"']{6,}/i,
56
+ ];
57
+ const AUDIT_LARGE_WARN = 100_000;
58
+ const AUDIT_LARGE_CRIT = 1_000_000;
59
+ const AUDIT_MAX_SCAN = 16_384;
60
+
61
+ function isSensitiveName(filename) {
62
+ return AUDIT_SENSITIVE_PATTERNS.some((pattern) => pattern.test(filename));
63
+ }
37
64
 
38
65
  function resolveDir(dir) { return path.resolve(dir || process.cwd()); }
39
66
 
@@ -155,6 +182,17 @@ class ContextPackServer {
155
182
  required: ["query"],
156
183
  },
157
184
  },
185
+ {
186
+ name: "pack_audit",
187
+ description: "Audit project for security risks, leaked secrets/credentials, missing .gitignore, uncommitted sensitive files, and overly large files.",
188
+ inputSchema: {
189
+ type: "object",
190
+ properties: {
191
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
192
+ strict: { type: "boolean", description: "Flag missing .gitignore as critical instead of warn", default: false },
193
+ },
194
+ },
195
+ },
158
196
  ],
159
197
  }));
160
198
 
@@ -166,6 +204,7 @@ class ContextPackServer {
166
204
  case "pack_tree": return await this.tree(args || {});
167
205
  case "pack_outline": return await this.outline(args || {});
168
206
  case "pack_search": return await this.search(args || {});
207
+ case "pack_audit": return await this.audit(args || {});
169
208
  default: throw new Error(`Unknown tool: ${name}`);
170
209
  }
171
210
  } catch (e) {
@@ -246,6 +285,139 @@ class ContextPackServer {
246
285
  return { content: [{ type: "text", text: hits.length ? `Matches for "${query}" (${hits.length}):\n` + hits.map((h) => ` ${h}`).join("\n") : `No path matches for "${query}" in ${root}` }] };
247
286
  }
248
287
 
288
+
289
+
290
+ async audit({ dir, strict = false }) {
291
+ const root = resolveDir(dir);
292
+ const findings = [];
293
+
294
+ function add(sev, msg, fix) {
295
+ findings.push({ severity: sev, message: msg, fix });
296
+ }
297
+
298
+ // walk
299
+ let all = [];
300
+ try { all = await walk(root, "", [], 0, 6); } catch { /* ignore */ }
301
+ const files = all.filter((f) => !f.dir);
302
+
303
+ // git presence and status
304
+ let gitRoot = null;
305
+ try {
306
+ const g = execSync("git rev-parse --show-toplevel", { cwd: root, encoding: "utf8", timeout: 3000 }).trim();
307
+ gitRoot = g;
308
+ } catch { /* not a git repo */ }
309
+
310
+ let gitStatus = [];
311
+ if (gitRoot) {
312
+ try {
313
+ const out = execSync("git status --porcelain", { cwd: root, encoding: "utf8", timeout: 5000 });
314
+ gitStatus = out.trim().split("\n").filter(Boolean).map((line) => {
315
+ const code = line.slice(0, 2);
316
+ const file = line.slice(3);
317
+ return { code, file, untracked: code === "??" };
318
+ });
319
+ } catch { /* ignore */ }
320
+ }
321
+
322
+ const statusMap = new Map(gitStatus.map((s) => [s.file, s]));
323
+
324
+ // Helpers
325
+ const isGitTracked = (rel) => {
326
+ const s = statusMap.get(rel);
327
+ if (!s) return true; // assume tracked if no status (could be clean tracked)
328
+ return !s.untracked;
329
+ };
330
+ const isIgnored = (rel) => {
331
+ try {
332
+ execSync(`git check-ignore -q -- "${rel}"`, { cwd: root, timeout: 2000 });
333
+ return true;
334
+ } catch { return false; }
335
+ };
336
+
337
+ // --- Missing critical files ---
338
+ const hasGitignore = files.some((f) => f.rel === ".gitignore" || path.basename(f.rel) === ".gitignore");
339
+ const hasEnvExample = files.some((f) => f.rel === ".env.example" || f.rel === ".env.sample" || f.rel === ".env.template");
340
+ if (gitRoot && !hasGitignore) add(strict ? "critical" : "warn", "No .gitignore found in a git repository.", "Add a .gitignore to prevent accidental commits of build artifacts, secrets, and OS files.");
341
+ if (!hasEnvExample) add("info", "No .env.example / .env.sample found.", "Add .env.example with dummy values so contributors know which env vars are required.");
342
+
343
+ // --- File-by-file scan ---
344
+ for (const f of files) {
345
+ const basename = path.basename(f.rel);
346
+ const abs = path.join(root, f.rel);
347
+ let stat;
348
+ try { stat = await fs.stat(abs); } catch { continue; }
349
+ const size = stat.size;
350
+ const sensitiveName = isSensitiveName(basename);
351
+
352
+ // large files
353
+ if (size > AUDIT_LARGE_CRIT) {
354
+ add("critical", `Large file: ${f.rel} (${(size/1e6).toFixed(1)} MB)`, "Consider adding to .gitignore, using Git LFS, or splitting.");
355
+ } else if (size > AUDIT_LARGE_WARN) {
356
+ add("warn", `Large file: ${f.rel} (${(size/1e3).toFixed(0)} KB)`, "Consider adding to .gitignore, using Git LFS, or splitting.");
357
+ }
358
+
359
+ // exposed sensitive files
360
+ if (sensitiveName) {
361
+ if (gitRoot) {
362
+ const tracked = isGitTracked(f.rel);
363
+ const ignored = isIgnored(f.rel);
364
+ if (tracked && !ignored) {
365
+ add("critical", `Sensitive file committed to git: ${f.rel}`, "Remove from git history (e.g. git-filter-repo), add to .gitignore, rotate any exposed secrets.");
366
+ } else if (!tracked) {
367
+ add("warn", `Sensitive file untracked: ${f.rel}`, "Ensure it is listed in .gitignore and never committed.");
368
+ } else if (ignored) {
369
+ add("info", `Sensitive file present but gitignored: ${f.rel}`, "OK — ensure secrets are rotated if previously committed.");
370
+ }
371
+ } else {
372
+ add("warn", `Sensitive file found (not a git repo): ${f.rel}`, "Review permissions and consider encrypting or moving to a secret manager.");
373
+ }
374
+ }
375
+
376
+ // secret content scan (small text-ish files only)
377
+ if (!f.dir && (CODE_EXT.has(path.extname(basename)) || [".json", ".yaml", ".yml", ".toml", ".ini", ".cfg", ".env", ".sh"].includes(path.extname(basename).toLowerCase()) || basename.startsWith(".env"))) {
378
+ if (size > 0 && size < AUDIT_MAX_SCAN * 4) {
379
+ let content = "";
380
+ try {
381
+ const fd = await fs.open(abs, "r");
382
+ const buf = Buffer.alloc(Math.min(size, AUDIT_MAX_SCAN));
383
+ await fd.read(buf, 0, buf.length, 0);
384
+ await fd.close();
385
+ content = buf.toString("utf8");
386
+ } catch { continue; }
387
+ for (const re of AUDIT_SECRET_CONTENT_RE) {
388
+ if (re.test(content)) {
389
+ add("critical", `Possible secret/hardcoded credential in ${f.rel}`, "Move to environment vars, secret manager, or encrypted vault. Rotate leaked value immediately.");
390
+ break;
391
+ }
392
+ }
393
+ }
394
+ }
395
+ }
396
+
397
+ // --- duplicate / inconsistent configs ---
398
+ const envFiles = files.filter((f) => path.basename(f.rel).startsWith(".env"));
399
+ if (envFiles.length > 2) {
400
+ add("info", `Multiple env files found (${envFiles.map((f) => f.rel).join(", ")})`, "Consolidate variants into .env, .env.local, .env.production. Ensure .gitignore covers all .env* except .env.example.");
401
+ }
402
+
403
+ // --- render ---
404
+ const icon = { critical: "🔴", warn: "🟡", info: "🟢" };
405
+ const groups = { critical: [], warn: [], info: [] };
406
+ for (const f of findings) groups[f.severity].push(f);
407
+ const lines = [`# Audit: ${path.basename(root)}`, `Path: ${root}`, `Git repo: ${gitRoot ? gitRoot : "no"}`, ``];
408
+ for (const sev of ["critical", "warn", "info"]) {
409
+ if (!groups[sev].length) continue;
410
+ lines.push(`## ${icon[sev]} ${sev.toUpperCase()} (${groups[sev].length})`);
411
+ for (const f of groups[sev]) {
412
+ lines.push(`- ${f.message}`);
413
+ if (f.fix) lines.push(` → Fix: ${f.fix}`);
414
+ }
415
+ lines.push("");
416
+ }
417
+ if (!findings.length) lines.push("🟢 No issues detected.");
418
+ return { content: [{ type: "text", text: lines.join("\n") }] };
419
+ }
420
+
249
421
  async run() {
250
422
  const t = new StdioServerTransport();
251
423
  await this.server.connect(t);
@@ -170,6 +170,18 @@ class DevJournalServer {
170
170
  properties: { dir: { type: "string", description: "Project directory (defaults to CWD)" } },
171
171
  },
172
172
  },
173
+ {
174
+ name: "journal_daily",
175
+ description: "Read or create today's daily note for the project (Obsidian-style YYYY-MM-DD.md under daily/). Append mode adds a line; read mode returns the note. Great for a vibecoder's daily log that survives compaction.",
176
+ inputSchema: {
177
+ type: "object",
178
+ properties: {
179
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
180
+ date: { type: "string", description: "Optional YYYY-MM-DD (defaults to today)" },
181
+ append: { type: "string", description: "If provided, append this line to the daily note" },
182
+ },
183
+ },
184
+ },
173
185
  {
174
186
  name: "initialize_agent_session",
175
187
  description: "Initialize the current agent session. Run this ONCE when the agent starts in a new project to perform a handshake and get contextual handoff.",
@@ -193,6 +205,7 @@ class DevJournalServer {
193
205
  case "journal_timeline": return await this.timeline(args || {});
194
206
  case "journal_search": return await this.search(args || {});
195
207
  case "journal_clear_handoff": return await this.clearHandoff(args || {});
208
+ case "journal_daily": return await this.daily(args || {});
196
209
  case "initialize_agent_session": return await this.initializeAgent(args || {});
197
210
  default: throw new Error(`Unknown tool: ${name}`);
198
211
  }
@@ -209,9 +222,34 @@ class DevJournalServer {
209
222
  const ts = (tsArg && /^\d{4}-\d{2}-\d{2}/.test(String(tsArg))) ? String(tsArg) : nowIso();
210
223
  const entry = { ts, type, title, body };
211
224
  await this.append(p, entry);
225
+ await this.appendDaily(p, ts.slice(0, 10), `- [${type}] ${title}${body ? ` — ${body.split("\n")[0].slice(0, 160)}` : ""}`);
212
226
  return { content: [{ type: "text", text: `Logged [${type}] "${title}" to ${p.slug}` }] };
213
227
  }
214
228
 
229
+ // Obsidian-style daily note: one file per date under daily/. Read or append.
230
+ async appendDaily(p, date, line) {
231
+ const dir = path.join(p.dir, "daily");
232
+ await fs.mkdir(dir, { recursive: true });
233
+ const file = path.join(dir, `${date}.md`);
234
+ const stamp = new Date().toISOString().slice(11, 19);
235
+ await fs.appendFile(file, `- ${stamp} ${line}\n`);
236
+ }
237
+
238
+ async daily({ dir, date, append } = {}) {
239
+ const p = this.paths(dir);
240
+ const day = (date && /^\d{4}-\d{2}-\d{2}$/.test(String(date))) ? String(date) : nowIso().slice(0, 10);
241
+ const dirPath = path.join(p.dir, "daily");
242
+ const file = path.join(dirPath, `${day}.md`);
243
+ if (append && String(append).trim()) {
244
+ await this.appendDaily(p, day, String(append).trim());
245
+ return { content: [{ type: "text", text: `Appended to daily note ${day}.md for ${p.slug}` }] };
246
+ }
247
+ let text;
248
+ try { text = await fs.readFile(file, "utf8"); }
249
+ catch { return { content: [{ type: "text", text: `No daily note for ${day} in ${p.slug} yet.` }] }; }
250
+ return { content: [{ type: "text", text: `# Daily — ${day}\n\n${text}` }] };
251
+ }
252
+
215
253
  async doHandoff({ summary, next_steps, open_questions, active_files, dir }) {
216
254
  summary = clip(summary, "summary", MAX_TEXT);
217
255
  const p = this.paths(dir);
@@ -0,0 +1,74 @@
1
+ # 🚀 Rencana Kerja & Roadmap Masa Depan — codex-dev-mcp-suite
2
+
3
+ > **Repository:** [verrysimatupang99/codex-dev-mcp-suite](https://github.com/verrysimatupang99/codex-dev-mcp-suite)
4
+ > **Versi Terbaru:** `v1.7.0` (Obsidian Vault Grade & Codebase Security Audit)
5
+ > **Lisensi:** MIT
6
+
7
+ ---
8
+
9
+ ## 📌 Status Terkini (v1.7.0)
10
+
11
+ Rilis `v1.7.0` baru saja digabung (*merged*) ke `master` & dipublikasikan dengan pencapaian:
12
+ - ✅ **Obsidian Vault Parity**: Dukungan `.obsidian` setup otomatis, `memory_moc` (Map of Content), `memory_graph`, dan format Markdown standar Obsidian (`[[WikiLinks]]`).
13
+ - ✅ **Codebase Security Audit (`pack_audit`)**: Tool otomatis untuk deteksi kebocoran file sensitif (`.env`, `.pem`, `id_rsa`), deteksi kunci API ter-hardcode, file besar, dan kelengkapan `.gitignore`.
14
+ - ✅ **Stabilitas & Test**: **97 / 97 unit test (100% PASS)** di seluruh 4 server MCP + 3 utility CLI.
15
+
16
+ ---
17
+
18
+ ## 🎯 Roadmap Pengembangan Kedepan
19
+
20
+ ```mermaid
21
+ graph TD
22
+ v17["v1.7.0 (Current)
23
+ Obsidian Vault & Security Audit"] --> v18["v1.8.0 (Q3 2026)
24
+ Local Offline Embeddings"]
25
+ v18 --> v19["v1.9.0 (Q4 2026)
26
+ Multi-Agent & Cross-Project"]
27
+ v19 --> v20["v2.0.0 (Q1 2027)
28
+ Obsidian Live Sync & Web Dashboard"]
29
+ ```
30
+
31
+ ---
32
+
33
+ ### 1. 🟢 v1.8.0 — Local Offline Semantic Search & ONNX Embedding
34
+ > **Fokus:** Menjadikan `project-memory` 100% mandiri tanpa butuh API key eksternal untuk pencarian semantik.
35
+
36
+ - [ ] **Engine Embedding Lokal (WASM/ONNX)**:
37
+ - Integrasi `@xenova/transformers` / ONNX Runtime Web dengan model ringan (`all-MiniLM-L6-v2` atau `bge-small-en-v1.5`).
38
+ - Pencarian semantik otomatis berfungsi saat laptop *offline* / tanpa jaringan internet.
39
+ - [ ] **Fast Keyword-Semantic Hybrid Search**:
40
+ - Penggabungan BM25 / TF-IDF keyword search dengan cosine similarity vektor lokal.
41
+ - [ ] **Smart Cache Vector Storage**:
42
+ - Menyimpan cache vektor di `.project-memory/vectors.json` tanpa membuat ketergantungan database eksternal.
43
+
44
+ ---
45
+
46
+ ### 2. 🔵 v1.9.0 — Multi-Agent Sync & Cross-Project Memory
47
+ > **Fokus:** Koordinasi memori antar sub-agent AI dan pengelolaan memori lintas proyek.
48
+
49
+ - [ ] **Multi-Agent Timeline Stream (`devjournal_stream`)**:
50
+ - Live event stream untuk melihat log & status pengerjaan subagent secara real-time.
51
+ - [ ] **Cross-Project Memory Link (`memory_crosslink`)**:
52
+ - Menghubungkan dokumentasi/solusi bug dari proyek A ke proyek B (misal: solusi konfigurasi server/VPS bisa dipakai ulang di proyek aplikasi).
53
+ - [ ] **Subagent Handoff Lock (`handoff_claim`)**:
54
+ - Mencegah dua subagent menimpa *handoff log* yang sama secara bersamaan.
55
+
56
+ ---
57
+
58
+ ### 3. 🟣 v2.0.0 — Native IDE GUI & Obsidian Live 2-Way Sync
59
+ > **Fokus:** Pengalaman pengguna (*Developer Ergonomics*) tingkat lanjut & visualisasi grafik.
60
+
61
+ - [ ] **Obsidian 2-Way Live Sync**:
62
+ - Sinkronisasi otomatis secara dua arah: editan di Obsidian Desktop langsung ter-update di `project-memory` AI, dan sebaliknya.
63
+ - [ ] **Web Dashboard & GUI (`npx codex-dev-mcp-suite ui`)**:
64
+ - Dashboard web lokal berbasis Svelte/React ringan untuk melihat grafik proyek, statistik memori, checkpoint diff, dan timeline devjournal.
65
+ - [ ] **Git Pre-commit Hook Integration**:
66
+ - Integrasi `pack_audit` langsung ke `.git/hooks/pre-commit` untuk mencegah developer sengaja/tidak sengaja memuat `.env` atau *secret key* ke repository.
67
+
68
+ ---
69
+
70
+ ### 🟡 Peningkatan Berkelanjutan (Ongoing Maintenance)
71
+ - [ ] **Templates Kredensial & Preset MCP**:
72
+ - Konfigurasi siap pakai untuk Cursor, Claude Code, Windsurf, VS Code, dan Antigravity.
73
+ - [ ] **CI/CD Auto-Publish**:
74
+ - GitHub Actions pipeline untuk auto-build & test saat ada `tag` baru.
@@ -0,0 +1,52 @@
1
+ # Google Antigravity CLI (AGY CLI) Setup
2
+
3
+ `codex-dev-mcp-suite` connects seamlessly to **Google Antigravity CLI (AGY CLI)** and the AGY SDK agent framework.
4
+
5
+ ## Quick Installation
6
+
7
+ ```bash
8
+ npm install -g codex-dev-mcp-suite
9
+ ```
10
+
11
+ ## AGY Configuration (`mcp.json` / `settings.json`)
12
+
13
+ Add the suite to your AGY CLI settings or project `mcp.json`:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "project-memory": {
19
+ "command": "project-memory-mcp",
20
+ "args": [],
21
+ "env": {
22
+ "MEMORY_VAULT_DIR": "~/.local/share/dev-mcp-suite/memories/vault",
23
+ "MCP_LOCAL_EMBED": "true"
24
+ }
25
+ },
26
+ "devjournal": {
27
+ "command": "devjournal-mcp",
28
+ "args": [],
29
+ "env": {
30
+ "JOURNAL_DIR": "~/.local/share/dev-mcp-suite/memories/journal"
31
+ }
32
+ },
33
+ "checkpoint": {
34
+ "command": "checkpoint-mcp",
35
+ "args": [],
36
+ "env": {
37
+ "CHECKPOINT_DIR": "~/.local/share/dev-mcp-suite/memories/checkpoints"
38
+ }
39
+ },
40
+ "context-pack": {
41
+ "command": "context-pack-mcp",
42
+ "args": []
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ ## Client Compatibility Highlights
49
+
50
+ - **Stdio Isolation**: Zero stdout noise — all logs and diagnostic messages are routed exclusively to `stderr` or MCP log frames.
51
+ - **Offline Vector Engine**: Automatically uses the built-in 384-d term-frequency hashing vector when no remote embedding API key is set (`MCP_LOCAL_EMBED=true`).
52
+ - **Obsidian Graph Compatibility**: Compatible with `.obsidian` vault structure and `[[WikiLinks]]`.
@@ -0,0 +1,48 @@
1
+ # Hermes Agent Setup
2
+
3
+ `codex-dev-mcp-suite` is fully compatible with **Hermes Agent** via the standard Model Context Protocol (MCP) stdio interface.
4
+
5
+ ## Quick Installation
6
+
7
+ Ensure the package is installed globally or accessible via `npx`:
8
+
9
+ ```bash
10
+ npm install -g codex-dev-mcp-suite
11
+ ```
12
+
13
+ ## Hermes Configuration (`~/.hermes/config.yaml`)
14
+
15
+ Add the suite servers to your Hermes `mcp_servers` configuration block in `~/.hermes/config.yaml`:
16
+
17
+ ```yaml
18
+ mcp_servers:
19
+ project-memory:
20
+ command: project-memory-mcp
21
+ args: []
22
+ env:
23
+ MEMORY_VAULT_DIR: "~/.local/share/dev-mcp-suite/memories/vault"
24
+ MCP_LOCAL_EMBED: "true" # Enables zero-dependency local offline vector search
25
+
26
+ devjournal:
27
+ command: devjournal-mcp
28
+ args: []
29
+ env:
30
+ JOURNAL_DIR: "~/.local/share/dev-mcp-suite/memories/journal"
31
+
32
+ checkpoint:
33
+ command: checkpoint-mcp
34
+ args: []
35
+ env:
36
+ CHECKPOINT_DIR: "~/.local/share/dev-mcp-suite/memories/checkpoints"
37
+
38
+ context-pack:
39
+ command: context-pack-mcp
40
+ args: []
41
+ ```
42
+
43
+ ## Features Available to Hermes
44
+
45
+ - **`memory_save` / `memory_recall`**: Persistent cross-session memory with optional local offline 384-d term-frequency hashing vector search (`MCP_LOCAL_EMBED=true`).
46
+ - **`journal_log` / `journal_resume` / `journal_handoff`**: Session log & handoff recovery.
47
+ - **`checkpoint_create` / `checkpoint_diff`**: File checkpoints independent of git.
48
+ - **`pack_overview` / `pack_tree` / `pack_audit`**: Codebase orientation & security risk auditing.
@@ -0,0 +1,13 @@
1
+ # Provider smoke matrix
2
+
3
+ _Generated 2026-06-17T01:58:41.412Z_
4
+
5
+ | Provider | Kind | Status | Latency | HTTP | Sample / dim | Error |
6
+ |---|---|---|---|---|---|---|
7
+ | groq | chat | ✓ | 430ms | 200 | `OK` | |
8
+ | cerebras | chat | ✓ | 554ms | 200 | — | |
9
+ | openai | chat | ✗ | 158ms | 401 | — | {"error":{"message":"unauthorized client detected, contact support for assistance at https://discord.com/invite/V6kaP6Rg44"},"message":"UNAUTHENTICATED","success":false,"type":"unauthorized_client_err |
10
+ | openai | embed | ✗ | 147ms | 401 | — | {"error":{"message":"unauthorized client detected, contact support for assistance at https://discord.com/invite/V6kaP6Rg44"},"message":"UNAUTHENTICATED","success":false,"type":"unauthorized_client_err |
11
+ | anthropic | chat | ✓ | 5.83s | 200 | — | |
12
+ | cloudflare | chat | ✗ | 907ms | 400 | — | {"errors":[{"message":"AiError: Bad input: Error: oneOf at '/' not met, 0 matches: required properties at '/' are 'text', required properties at '/' are 'requests' (f3f58c5f-7207-461a-96e1-d809e15ede3 |
13
+ | cloudflare | embed | ✓ | 194ms | 200 | dim=768 | |
@@ -0,0 +1,60 @@
1
+ # Hardening v1.2 Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Add CLI UX (help/version/doctor), secret redaction, provider validation, per-provider timeout + 429/5xx cooldown, and a packaged-tarball CI smoke test, without breaking existing behavior.
6
+
7
+ **Architecture:** Add a shared bin meta-handler that intercepts `--help/--version/--doctor` before starting the stdio server. Add redaction + provider diagnostics + cooldown helpers in the provider layer. Wire rerank chat calls through cooldown-aware provider iteration. Extend CI with a tarball install smoke job.
8
+
9
+ **Tech Stack:** Node.js ESM, existing MCP SDK servers, built-in `http`/`https`, offline test harness, GitHub Actions.
10
+
11
+ ---
12
+
13
+ ### Task 1: Bin CLI Meta (help/version/doctor)
14
+
15
+ **Files:**
16
+ - Create: `bin/_meta.mjs`
17
+ - Modify: `bin/project-memory.mjs`, `bin/devjournal.mjs`, `bin/checkpoint.mjs`, `bin/context-pack.mjs`
18
+
19
+ - [ ] Add `handleCliMeta({ bin, title, serverDir })` that handles `-h/--help`, `-v/--version`, `--doctor` then exits; returns false otherwise.
20
+ - [ ] Read version from root `package.json`.
21
+ - [ ] `--doctor` prints storage + model/provider config with API keys redacted (never raw values).
22
+ - [ ] Each bin calls meta first, then imports its server only when not handled.
23
+
24
+ ### Task 2: Redaction + Provider Diagnostics + Cooldown
25
+
26
+ **Files:**
27
+ - Modify: `project-memory/provider-chain.js`, `devjournal/provider-chain.js`
28
+
29
+ - [ ] Add `redactKey(value)` returning `set (<n> chars)` / `not set`, never the value.
30
+ - [ ] Add `providerChainDiagnostics()` listing active providers (redacted) and incomplete-slot issues.
31
+ - [ ] Add pure cooldown helpers `recordOutcome(key, ok, now)` and `isCoolingDown(key, now)` using `MCP_PROVIDER_COOLDOWN_MS` (default 60000).
32
+
33
+ ### Task 3: Cooldown-Aware Rerank
34
+
35
+ **Files:**
36
+ - Modify: `project-memory/rerank.js`, `devjournal/rerank.js`
37
+
38
+ - [ ] Make provider chat return status so 429/5xx/network failures are retryable.
39
+ - [ ] Skip providers currently cooling down; record outcomes.
40
+ - [ ] Stop after first success; fall back to keyword if all fail. Never log keys/bodies.
41
+
42
+ ### Task 4: Tests
43
+
44
+ **Files:**
45
+ - Modify: `project-memory/test.mjs`, `devjournal/test.mjs`
46
+
47
+ - [ ] Test `providerChainDiagnostics()` flags an incomplete slot and redacts keys.
48
+ - [ ] Test cooldown helpers: failure starts cooldown, expiry clears it, success clears it.
49
+ - [ ] Test bin `--version` prints package version and `--doctor` output excludes raw key value.
50
+
51
+ ### Task 5: CI Tarball Smoke + Verify
52
+
53
+ **Files:**
54
+ - Modify: `.github/workflows/ci.yml`
55
+ - Modify: `CHANGELOG.md`, `package.json`, `docs/configuration.md`, `.env.example`
56
+
57
+ - [ ] Add CI job: `npm pack`, install tarball in temp dir, run each bin `--version`.
58
+ - [ ] Document `--doctor` and `MCP_PROVIDER_COOLDOWN_MS`.
59
+ - [ ] Bump version to 1.2.0 and add changelog entry.
60
+ - [ ] Run `npm test` and `npm pack --dry-run`; expect all pass.
@@ -0,0 +1,65 @@
1
+ # Provider Chain v1.1 Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Add optional numbered OpenAI-compatible provider fallback for rerank/chat calls while preserving deterministic no-network mode and legacy env compatibility.
6
+
7
+ **Architecture:** Add focused provider-chain helpers to `project-memory` and `devjournal`, then route existing rerank chat calls through those helpers. Provider slots are built from `MCP_PROVIDER_PRIMARY_*`, `MCP_PROVIDER_CHAIN2_*`, `MCP_PROVIDER_CHAIN3_*`, etc.; if absent, existing `MCP_RERANK_*`/`MCP_LLM_*`/legacy config is used.
8
+
9
+ **Tech Stack:** Node.js ESM, existing MCP SDK servers, built-in `http`/`https`, existing offline test harness.
10
+
11
+ ---
12
+
13
+ ### Task 1: Provider Chain Helpers
14
+
15
+ **Files:**
16
+ - Create: `project-memory/provider-chain.js`
17
+ - Create: `devjournal/provider-chain.js`
18
+ - Test: `project-memory/test.mjs`
19
+ - Test: `devjournal/test.mjs`
20
+
21
+ - [ ] Create a `providerEnv(slotPrefix)` helper that reads `NAME`, `BASE_URL`, `API_KEY`, and `MODEL` from a prefix such as `MCP_PROVIDER_PRIMARY`.
22
+ - [ ] Create `providerChainConfig()` returning `{ providers, enabled, deterministic }`.
23
+ - [ ] Include `MCP_PROVIDER_PRIMARY` first when complete.
24
+ - [ ] Include every complete numbered slot matching `MCP_PROVIDER_CHAIN<N>` in numeric order for `N >= 2`.
25
+ - [ ] Fall back to one legacy provider from existing rerank/LLM envs when no numbered slots exist.
26
+ - [ ] Return disabled config if deterministic mode is on.
27
+ - [ ] Add tests for primary-only, primary+chain2+chain3 ordering, arbitrary chain4, incomplete slot skip, deterministic disable, and legacy fallback.
28
+
29
+ ### Task 2: Rerank Uses Provider Chain
30
+
31
+ **Files:**
32
+ - Modify: `project-memory/rerank.js`
33
+ - Modify: `devjournal/rerank.js`
34
+ - Test: existing `npm test`
35
+
36
+ - [ ] Replace single `BASE`/`KEY`/`MODEL` request selection with `providerChainConfig()`.
37
+ - [ ] Try providers in order for each chat request.
38
+ - [ ] Return first valid OpenAI-compatible response.
39
+ - [ ] On non-200, timeout, network error, invalid JSON, or SSE parse failure, try the next provider.
40
+ - [ ] Keep returning `null` if every provider fails so keyword fallback still works.
41
+ - [ ] Never log API keys or response bodies.
42
+
43
+ ### Task 3: Docs and Examples
44
+
45
+ **Files:**
46
+ - Modify: `docs/configuration.md`
47
+ - Modify: `docs/privacy.md`
48
+ - Modify: `.env.example`
49
+ - Modify: `CHANGELOG.md`
50
+ - Modify: `README.md` if needed
51
+
52
+ - [ ] Document numbered provider slots.
53
+ - [ ] Document Groq/Cerebras/OpenRouter as recommended examples, not required providers.
54
+ - [ ] Clarify users can use any OpenAI-compatible base URL, token, and model ID.
55
+ - [ ] Clarify deterministic mode overrides provider chain.
56
+ - [ ] Add changelog entry for v1.1 unreleased.
57
+
58
+ ### Task 4: Verification
59
+
60
+ **Files:**
61
+ - No source changes.
62
+
63
+ - [ ] Run `npm test` and expect 33+ tests all pass.
64
+ - [ ] Run `npm pack --dry-run` and confirm runtime provider-chain helpers are included.
65
+ - [ ] Run secret scan on changed/staged files and confirm no real secrets.