codex-dev-mcp-suite 1.9.0 → 3.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/CHANGELOG.md CHANGED
@@ -32,6 +32,30 @@
32
32
 
33
33
  # Changelog
34
34
 
35
+ ## 3.0.0 - 2026-07-22 — The Autonomous Agent OS Edition (God-Tier) 🌌
36
+
37
+ ### Added
38
+ - **Pillar 1: Live Dev-Server & Log Telemetry (`pack_telemetry`)**:
39
+ - Surface live dev server errors (`dev.log`, `error.log`, `/tmp/dev.log`, `.next/server.log`) directly into agent context without manual copy-pasting (`context-pack`).
40
+ - **Pillar 2: Autonomous Background Git & Mtime Observer (`memory_auto_index`)**:
41
+ - Automatically inspect recent git commits, branch switches, and file modifications to auto-derive project notes (`project-memory`).
42
+ - **Pillar 3: Predictive Contract & Schema Impact Simulation (`pack_predictive_diff`)**:
43
+ - Simulate proposed file diffs against caller files to predict breaking API signatures or database schema changes (`context-pack`).
44
+ - **Pillar 4: Built-in Interactive Web GUI Dashboard (`mcp-ui`)**:
45
+ - Launch a zero-dependency 3D/2D Knowledge Graph & Memory Web Dashboard on `http://localhost:3333` via `mcp-ui` (`bin/ui.mjs`).
46
+
47
+ ## 2.0.0 - 2026-07-22 — The Peak Intelligence Edition 🚀
48
+
49
+ ### Added
50
+ - **Pillar 1: Dependency Blast-Radius (`pack_impact`)**:
51
+ - Analyze code imports (`import`/`export`/`require`) and compute caller fan-out scores before refactoring files (`context-pack`).
52
+ - **Pillar 2: Self-Healing Pre-flight Diagnostics (`pack_guard`)**:
53
+ - Run typechecks (`tsc`), lints, and test suites with built-in timeout guards to ensure 100% bug-free AI output (`context-pack`).
54
+ - **Pillar 3: Multi-Agent Swarm Stream (`memory_broadcast` & `memory_swarm_timeline`)**:
55
+ - Real-time event stream broadcasting across peer subagents (Antigravity, Hermes, Codex, Claude Code, Cursor) working in the same workspace (`project-memory`).
56
+ - **Pillar 4: Session Time-Machine & Token Compressor (`journal_compress`)**:
57
+ - Condense 50+ turns of verbose session logs into a 500-token compact snapshot to cut token costs by ~90% (`devjournal`).
58
+
35
59
  ## 1.9.0 - 2026-07-22
36
60
 
37
61
  ### Added
package/README.md CHANGED
@@ -49,10 +49,10 @@ npx -y -p codex-dev-mcp-suite project-memory-mcp --help
49
49
 
50
50
  | Server | Superpower | Key Tools |
51
51
  |---|---|---|
52
- | 🧠 **`project-memory`** | **Obsidian-Grade Knowledge Vault**: Persistent Markdown notes, wiki-links (`[[note]]`), MOC generation, and zero-dependency local 384-d vector search (`MCP_LOCAL_EMBED=true`). | `memory_save`, `memory_recall`, `memory_list`, `memory_get`, `memory_link`, `memory_moc`, `memory_graph`, `memory_dedup` |
53
- | 📜 **`devjournal`** | **Anti-Compaction Session Timeline**: Logs progress, saves handoffs, and restores full context when sessions compact or restart. | `journal_log`, `journal_handoff`, `journal_resume`, `journal_timeline`, `journal_search`, `journal_clear_handoff` |
52
+ | 🧠 **`project-memory`** | **Obsidian-Grade Knowledge Vault**: Persistent Markdown notes, wiki-links (`[[note]]`), MOC generation, zero-dependency local 384-d vector search (`MCP_LOCAL_EMBED=true`), multi-agent swarm stream, and autonomous background git observer (`memory_auto_index`). | `memory_save`, `memory_recall`, `memory_list`, `memory_get`, `memory_link`, `memory_moc`, `memory_graph`, `memory_dedup`, `memory_broadcast`, `memory_swarm_timeline`, `memory_auto_index` |
53
+ | 📜 **`devjournal`** | **Anti-Compaction Session Timeline**: Logs progress, saves handoffs, restores full context, and compresses session logs to cut token costs ~90%. | `journal_log`, `journal_handoff`, `journal_resume`, `journal_timeline`, `journal_search`, `journal_compress`, `journal_clear_handoff` |
54
54
  | 🛡️ **`checkpoint`** | **Git-Independent Snapshots**: Take 1-second file snapshots before risky AI refactors. Compare diffs and revert instantly without touching your git tree. | `checkpoint_create`, `checkpoint_list`, `checkpoint_diff`, `checkpoint_restore`, `checkpoint_delete` |
55
- | 🔍 **`context-pack`** | **Codebase Orientation & Security Audit**: Token-efficient project briefings (stack, symbols, tree) plus instant security auditing to catch leaked secrets. | `pack_overview`, `pack_tree`, `pack_outline`, `pack_search`, `pack_audit` |
55
+ | 🔍 **`context-pack`** | **Autonomous Agent OS**: Token-efficient briefings, dependency blast-radius analysis (`pack_impact`), self-healing preflight checks (`pack_guard`), live log telemetry (`pack_telemetry`), predictive contract diff simulation (`pack_predictive_diff`), and security auditing (`pack_audit`). | `pack_overview`, `pack_tree`, `pack_outline`, `pack_search`, `pack_audit`, `pack_impact`, `pack_guard`, `pack_telemetry`, `pack_predictive_diff` |
56
56
 
57
57
  ---
58
58
 
package/bin/ui.mjs ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { startUiServer } from "../lib/ui-server.js";
3
+
4
+ const port = Number(process.env.PORT || 3333);
5
+ startUiServer(port);
@@ -193,6 +193,55 @@ class ContextPackServer {
193
193
  },
194
194
  },
195
195
  },
196
+ {
197
+ name: "pack_impact",
198
+ description: "Analyze dependency blast-radius and callers for a specific file to prevent breaking downstream code.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ targetFile: { type: "string", description: "Relative path to target file (e.g. 'lib/stats.js')" },
203
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
204
+ maxDepth: { type: "number", description: "Traversal depth", default: 3 },
205
+ },
206
+ required: ["targetFile"],
207
+ },
208
+ },
209
+ {
210
+ name: "pack_guard",
211
+ description: "Execute self-healing diagnostic pre-flight checks (typecheck, lint, test) and return structured findings.",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {
215
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
216
+ checkType: { type: "string", description: "Check type: 'all' | 'typecheck' | 'test' | 'lint'", default: "all" },
217
+ },
218
+ },
219
+ },
220
+ {
221
+ name: "pack_telemetry",
222
+ description: "Read live dev server logs, runtime errors, and stack traces to diagnose issues without asking user to copy-paste logs.",
223
+ inputSchema: {
224
+ type: "object",
225
+ properties: {
226
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
227
+ logFile: { type: "string", description: "Path to specific log file (optional)" },
228
+ limit: { type: "number", description: "Max log entries", default: 50 },
229
+ },
230
+ },
231
+ },
232
+ {
233
+ name: "pack_predictive_diff",
234
+ description: "Simulate proposed file changes against caller files to predict breaking contract or API signature changes.",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {
238
+ targetFile: { type: "string", description: "Target file path" },
239
+ proposedDiff: { type: "string", description: "Proposed code snippet or diff" },
240
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
241
+ },
242
+ required: ["targetFile", "proposedDiff"],
243
+ },
244
+ },
196
245
  ],
197
246
  }));
198
247
 
@@ -205,6 +254,10 @@ class ContextPackServer {
205
254
  case "pack_outline": return await this.outline(args || {});
206
255
  case "pack_search": return await this.search(args || {});
207
256
  case "pack_audit": return await this.audit(args || {});
257
+ case "pack_impact": return await this.impact(args || {});
258
+ case "pack_guard": return await this.guard(args || {});
259
+ case "pack_telemetry": return await this.telemetry(args || {});
260
+ case "pack_predictive_diff": return await this.predictiveDiff(args || {});
208
261
  default: throw new Error(`Unknown tool: ${name}`);
209
262
  }
210
263
  } catch (e) {
@@ -418,6 +471,197 @@ class ContextPackServer {
418
471
  return { content: [{ type: "text", text: lines.join("\n") }] };
419
472
  }
420
473
 
474
+ async impact({ targetFile, dir, maxDepth = 3 }) {
475
+ const root = resolveDir(dir);
476
+ if (!targetFile) throw new Error("targetFile is required");
477
+ const normTarget = targetFile.replace(/\\/g, "/").replace(/^\.\//, "");
478
+ const all = await walk(root, "", [], 0, 8);
479
+ const codeFiles = all.filter((f) => !f.dir && CODE_EXT.has(path.extname(f.rel)));
480
+
481
+ const importMap = new Map();
482
+ const reverseMap = new Map();
483
+
484
+ for (const f of codeFiles) {
485
+ const abs = path.join(root, f.rel);
486
+ try {
487
+ const content = await fs.readFile(abs, "utf8");
488
+ const imports = [];
489
+ const re = /(?:import|export)\s+.*?\s+from\s+["']([^"']+)["']|require\(["']([^"']+)["']\)|import\(["']([^"']+)["']\)/g;
490
+ let match;
491
+ while ((match = re.exec(content)) !== null) {
492
+ const spec = match[1] || match[2] || match[3];
493
+ if (spec && (spec.startsWith(".") || spec.startsWith("/"))) {
494
+ const dirName = path.dirname(f.rel);
495
+ const resolvedRel = path.normalize(path.join(dirName, spec)).replace(/\\/g, "/");
496
+ imports.push(resolvedRel);
497
+ }
498
+ }
499
+ importMap.set(f.rel.replace(/\\/g, "/"), imports);
500
+ } catch {}
501
+ }
502
+
503
+ for (const [importer, importedList] of importMap.entries()) {
504
+ for (const imp of importedList) {
505
+ for (const f of codeFiles) {
506
+ const fRel = f.rel.replace(/\\/g, "/");
507
+ const fRelNoExt = fRel.replace(/\.[^/.]+$/, "");
508
+ if (fRel === imp || fRelNoExt === imp || fRelNoExt === imp.replace(/\/index$/, "")) {
509
+ if (!reverseMap.has(fRel)) reverseMap.set(fRel, []);
510
+ reverseMap.get(fRel).push(importer);
511
+ }
512
+ }
513
+ }
514
+ }
515
+
516
+ const directImporters = reverseMap.get(normTarget) || [];
517
+ const directImports = importMap.get(normTarget) || [];
518
+
519
+ const lines = [
520
+ `# Impact Blast-Radius: ${normTarget}`,
521
+ `Project: ${root}`,
522
+ ``,
523
+ `## 🎯 Direct Importers / Callers (${directImporters.length})`,
524
+ ...(directImporters.length ? directImporters.map((f) => `- \`${f}\``) : ["- None (No other file directly imports this file)"]),
525
+ ``,
526
+ `## 📦 Outbound Imports (${directImports.length})`,
527
+ ...(directImports.length ? directImports.map((f) => `- \`${f}\``) : ["- None"]),
528
+ ``,
529
+ `## ⚠️ Calculated Blast-Radius Score: ${directImporters.length > 5 ? "HIGH 🔴" : directImporters.length > 2 ? "MEDIUM 🟡" : "LOW 🟢"}`
530
+ ];
531
+
532
+ return { content: [{ type: "text", text: lines.join("\n") }] };
533
+ }
534
+
535
+ async guard({ dir, checkType = "all" }) {
536
+ const root = resolveDir(dir);
537
+ const pkgPath = path.join(root, "package.json");
538
+ let hasPkg = false;
539
+ let pkg = {};
540
+ try {
541
+ pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
542
+ hasPkg = true;
543
+ } catch {}
544
+
545
+ const runScript = (cmd, label) => {
546
+ try {
547
+ const out = execSync(cmd, { cwd: root, encoding: "utf8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] });
548
+ return { label, status: "PASS 🟢", output: out.slice(0, 500) };
549
+ } catch (e) {
550
+ const errOut = (e.stdout || "") + "\n" + (e.stderr || "");
551
+ return { label, status: "FAIL 🔴", output: errOut.slice(0, 1000) };
552
+ }
553
+ };
554
+
555
+ const results = [];
556
+ if (hasPkg && pkg.scripts) {
557
+ if ((checkType === "all" || checkType === "typecheck") && (pkg.scripts.typecheck || pkg.scripts["check-types"])) {
558
+ results.push(runScript("npm run " + (pkg.scripts.typecheck ? "typecheck" : "check-types"), "Typecheck"));
559
+ } else if (hasPkg && await fs.access(path.join(root, "tsconfig.json")).then(() => true).catch(() => false)) {
560
+ results.push(runScript("npx tsc --noEmit", "Typecheck (tsc)"));
561
+ }
562
+
563
+ if ((checkType === "all" || checkType === "lint") && pkg.scripts.lint) {
564
+ results.push(runScript("npm run lint", "Linter"));
565
+ }
566
+
567
+ if ((checkType === "all" || checkType === "test") && pkg.scripts.test) {
568
+ results.push(runScript("npm test", "Test Suite"));
569
+ }
570
+ }
571
+
572
+ const lines = [`# Guard Pre-flight Diagnostics: ${path.basename(root)}`, `Path: ${root}`, ``];
573
+ if (!results.length) {
574
+ lines.push("ℹ️ No build/test/lint scripts detected in project toolchain.");
575
+ } else {
576
+ for (const r of results) {
577
+ lines.push(`## ${r.label}: ${r.status}`);
578
+ if (r.output) {
579
+ lines.push("```");
580
+ lines.push(r.output.trim());
581
+ lines.push("```");
582
+ }
583
+ lines.push("");
584
+ }
585
+ }
586
+
587
+ return { content: [{ type: "text", text: lines.join("\n") }] };
588
+ }
589
+
590
+ async telemetry({ dir, logFile, limit = 50 }) {
591
+ const root = resolveDir(dir);
592
+ const candidateFiles = [];
593
+ if (logFile) candidateFiles.push(path.resolve(root, logFile));
594
+ candidateFiles.push(
595
+ path.join(root, "error.log"),
596
+ path.join(root, "app.log"),
597
+ path.join(root, "dev.log"),
598
+ path.join(root, "server.log"),
599
+ path.join(root, ".next", "server.log"),
600
+ "/tmp/dev.log"
601
+ );
602
+
603
+ let foundPath = null;
604
+ let content = "";
605
+ for (const f of candidateFiles) {
606
+ try {
607
+ content = await fs.readFile(f, "utf8");
608
+ foundPath = f;
609
+ break;
610
+ } catch {}
611
+ }
612
+
613
+ const lines = [`# Live Runtime Telemetry Observer: ${path.basename(root)}`, `Observed Log File: ${foundPath ? foundPath : "None detected (create dev.log or error.log in project)"}`, ``];
614
+ if (foundPath && content) {
615
+ const rawLines = content.split("\n").filter((l) => l.trim().length > 0);
616
+ const recent = rawLines.slice(-limit);
617
+ const errors = recent.filter((l) => /error|exception|fail|crash|500|unhandled/i.test(l));
618
+ lines.push(`## 🔴 Surfaced Errors / Exceptions (${errors.length})`);
619
+ if (errors.length) {
620
+ lines.push("```");
621
+ lines.push(errors.join("\n"));
622
+ lines.push("```");
623
+ } else {
624
+ lines.push("🟢 No runtime errors detected in recent log lines.");
625
+ }
626
+
627
+ lines.push(``);
628
+ lines.push(`## 📜 Recent Log Feed (${recent.length} lines)`);
629
+ lines.push("```");
630
+ lines.push(recent.slice(-20).join("\n"));
631
+ lines.push("```");
632
+ } else {
633
+ lines.push("ℹ️ No active dev server log file found. Pass logFile parameter or log output to `./error.log` or `/tmp/dev.log`.");
634
+ }
635
+
636
+ return { content: [{ type: "text", text: lines.join("\n") }] };
637
+ }
638
+
639
+ async predictiveDiff({ targetFile, proposedDiff, dir }) {
640
+ const root = resolveDir(dir);
641
+ const impactRes = await this.impact({ targetFile, dir, maxDepth: 3 });
642
+ const impactText = impactRes.content[0].text;
643
+
644
+ const warnings = [];
645
+ if (/delete|remove|drop|rename/i.test(proposedDiff)) {
646
+ warnings.push("⚠️ Proposed diff contains potential destructive operation (delete/drop/rename).");
647
+ }
648
+ if (/export\s+(const|function|class|interface|type)\s+/i.test(proposedDiff)) {
649
+ warnings.push("ℹ️ Exported symbol signature changed. Verify caller arguments in dependent files.");
650
+ }
651
+
652
+ const lines = [
653
+ `# Predictive Contract & Diff Analysis: ${targetFile}`,
654
+ `Project: ${root}`,
655
+ ``,
656
+ `## 🔮 Predicted Impact Warnings (${warnings.length})`,
657
+ ...(warnings.length ? warnings.map((w) => `- ${w}`) : ["🟢 No high-risk signature breaks predicted."]),
658
+ ``,
659
+ impactText
660
+ ];
661
+
662
+ return { content: [{ type: "text", text: lines.join("\n") }] };
663
+ }
664
+
421
665
  async run() {
422
666
  const t = new StdioServerTransport();
423
667
  await this.server.connect(t);
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Context compressor for devjournal.
3
+ * Condenses verbose timeline entries into a ultra-compact (~500 token) snapshot.
4
+ */
5
+
6
+ import fs from "fs/promises";
7
+ import path from "path";
8
+
9
+ /**
10
+ * Compress journal entries for a project into a dense markdown summary block.
11
+ */
12
+ export async function compressJournalEntries(journalDir, projectSlug, { limit = 50, maxTokens = 500 } = {}) {
13
+ const projectDir = path.join(journalDir, projectSlug);
14
+ const logFile = path.join(projectDir, "log.jsonl");
15
+
16
+ try {
17
+ const raw = await fs.readFile(logFile, "utf8");
18
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
19
+ const entries = lines.map((l) => JSON.parse(l));
20
+
21
+ // Sort newest first
22
+ entries.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
23
+ const recent = entries.slice(0, limit);
24
+
25
+ // Aggregate findings, decisions, files, and blockers
26
+ const decisions = [];
27
+ const blockers = [];
28
+ const filesTouched = new Set();
29
+
30
+ for (const e of recent) {
31
+ if (e.entryType === "decision" || e.tags?.includes("decision")) {
32
+ decisions.push(e.text || e.summary);
33
+ } else if (e.entryType === "blocker" || e.tags?.includes("blocker") || e.tags?.includes("error")) {
34
+ blockers.push(e.text || e.summary);
35
+ }
36
+ if (e.file || e.files) {
37
+ const arr = Array.isArray(e.files) ? e.files : [e.file];
38
+ arr.forEach((f) => f && filesTouched.add(f));
39
+ }
40
+ }
41
+
42
+ const outputLines = [
43
+ `# Compressed Journal Snapshot: ${projectSlug}`,
44
+ `Generated: ${new Date().toISOString()}`,
45
+ `Entries Processed: ${recent.length}`,
46
+ ``,
47
+ `## 💡 Key Decisions (${decisions.length})`,
48
+ ...(decisions.length ? decisions.slice(0, 10).map((d) => `- ${d}`) : ["- None recorded"]),
49
+ ``,
50
+ `## ⚠️ Resolved/Active Blockers (${blockers.length})`,
51
+ ...(blockers.length ? blockers.slice(0, 10).map((b) => `- ${b}`) : ["- None"]),
52
+ ``,
53
+ `## 📁 Files Touched (${filesTouched.size})`,
54
+ ...(filesTouched.size ? Array.from(filesTouched).slice(0, 15).map((f) => `- \`${f}\``) : ["- None"]),
55
+ ``,
56
+ `## 📝 Latest Entry`,
57
+ recent[0] ? `> ${recent[0].text || recent[0].summary} (${recent[0].timestamp})` : "No entries."
58
+ ];
59
+
60
+ const resultText = outputLines.join("\n");
61
+ return {
62
+ projectSlug,
63
+ compressedText: resultText,
64
+ entriesCount: recent.length,
65
+ estimatedTokenCount: Math.ceil(resultText.length / 4),
66
+ };
67
+ } catch {
68
+ return {
69
+ projectSlug,
70
+ compressedText: `# Compressed Journal Snapshot: ${projectSlug}\nNo log entries found.`,
71
+ entriesCount: 0,
72
+ estimatedTokenCount: 15,
73
+ };
74
+ }
75
+ }
@@ -33,6 +33,7 @@ import os from "os";
33
33
  import crypto from "crypto";
34
34
  import { rerank, rerankConfig } from "./rerank.js";
35
35
  import { deterministicEnabled } from "./env.js";
36
+ import { compressJournalEntries } from "./compress.js";
36
37
 
37
38
  const ROOT =
38
39
  process.env.JOURNAL_DIR ||
@@ -192,6 +193,18 @@ class DevJournalServer {
192
193
  },
193
194
  },
194
195
  },
196
+ {
197
+ name: "journal_compress",
198
+ description: "Compress and summarize verbose journal timeline entries into a ultra-compact (~500 token) snapshot to prevent context window compaction traps.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
203
+ limit: { type: "number", description: "Max entries to process", default: 50 },
204
+ maxTokens: { type: "number", description: "Target max tokens", default: 500 },
205
+ },
206
+ },
207
+ },
195
208
  ],
196
209
  }));
197
210
 
@@ -207,6 +220,7 @@ class DevJournalServer {
207
220
  case "journal_clear_handoff": return await this.clearHandoff(args || {});
208
221
  case "journal_daily": return await this.daily(args || {});
209
222
  case "initialize_agent_session": return await this.initializeAgent(args || {});
223
+ case "journal_compress": return await this.compress(args || {});
210
224
  default: throw new Error(`Unknown tool: ${name}`);
211
225
  }
212
226
  } catch (e) {
@@ -374,6 +388,12 @@ class DevJournalServer {
374
388
  return { content: [{ type: "text", text: content }] };
375
389
  }
376
390
 
391
+ async compress({ dir, limit = 50, maxTokens = 500 }) {
392
+ const p = this.paths(dir);
393
+ const res = await compressJournalEntries(p.dir, p.slug, { limit, maxTokens });
394
+ return { content: [{ type: "text", text: res.compressedText }] };
395
+ }
396
+
377
397
  async run() {
378
398
  const t = new StdioServerTransport();
379
399
  await this.server.connect(t);
@@ -0,0 +1,50 @@
1
+ # Design Spec: v2.0.0 — Peak Intelligence Edition
2
+
3
+ ## Overview
4
+
5
+ `codex-dev-mcp-suite` v2.0.0 elevates AI coding agents (Codex CLI, Hermes Agent, Antigravity, Claude Code, Cursor) to peak developer intelligence. It bridges human intent, deep codebase dependency structure, self-healing diagnostic feedback, multi-agent collaboration, and token compression.
6
+
7
+ ---
8
+
9
+ ## 🚀 4 Core Pillars
10
+
11
+ ### 1. Codebase Dependency Blast-Radius (`pack_graph` & `pack_impact`)
12
+ - **Module**: `context-pack`
13
+ - **Goal**: Parse imports/exports across JS/TS/JSX/TSX/Python files to build an in-memory dependency graph.
14
+ - **Tool**: `pack_impact({ targetFile, depth })`
15
+ - **Output**:
16
+ - `importers`: List of files that import the target file.
17
+ - `imported`: List of files imported by the target file.
18
+ - `impactScore`: Calculated risk score (1-10) based on fan-out depth.
19
+
20
+ ### 2. Self-Healing Diagnostic Pre-flight (`pack_guard`)
21
+ - **Module**: `context-pack`
22
+ - **Goal**: Auto-detect project build/test toolchain (`npm test`, `tsc --noEmit`, `eslint`, `pytest`, `cargo check`) and execute pre-flight diagnostics safely.
23
+ - **Tool**: `pack_guard({ checkType: "all" | "typecheck" | "test" | "lint" })`
24
+ - **Output**: Structured JSON array of diagnostic findings:
25
+ - `[{ severity: "error"|"warning", file, line, column, message, rule }]`
26
+
27
+ ### 3. Multi-Agent Memory Stream (`memory_swarm`)
28
+ - **Module**: `project-memory`
29
+ - **Goal**: Enable real-time cross-agent memory broadcasting and event streams across active subagents (Antigravity, Hermes, Codex, Claude Code) working in the same workspace.
30
+ - **Tools**: `memory_broadcast({ eventType, data })`, `memory_timeline()`
31
+
32
+ ### 4. Session Context Compression & Time Machine (`journal_compress`)
33
+ - **Module**: `devjournal`
34
+ - **Goal**: Summarize 50+ turns of verbose session logs into a 500-token compact snapshot to prevent context window bloat and reduce LLM token cost by ~90%.
35
+ - **Tool**: `journal_compress({ projectSlug, maxTokens })`
36
+
37
+ ---
38
+
39
+ ## 🛠️ Implementation Plan
40
+
41
+ ### Phase 1: `pack_guard` & `pack_graph` (Core Diagnostics & Impact Analysis)
42
+ 1. Add AST import parser & dependency map to `context-pack/server.js`.
43
+ 2. Add `pack_impact` tool declaration and execution handler.
44
+ 3. Add `pack_guard` tool declaration and toolchain runner with timeout safety.
45
+ 4. Add unit test assertions in `context-pack/test.mjs`.
46
+
47
+ ### Phase 2: `memory_swarm` & `journal_compress`
48
+ 1. Add event broadcast stream in `project-memory/server.js`.
49
+ 2. Add session log token compressor in `devjournal/server.js`.
50
+ 3. Add unit tests for swarm & compression.
@@ -0,0 +1,32 @@
1
+ # Design Spec: v3.0.0 — The Autonomous Agent OS Edition (God-Tier)
2
+
3
+ ## Overview
4
+
5
+ `codex-dev-mcp-suite` v3.0.0 represents the ultimate evolution in local-first AI development tools. It transforms the suite into an **Autonomous Agent Operating System** with live dev server telemetry observation, background git auto-indexing, predictive schema/contract diff analysis, and a zero-dependency interactive Web GUI Dashboard.
6
+
7
+ ---
8
+
9
+ ## ⚡ 4 God-Tier Pillars
10
+
11
+ ### 1. Live Dev-Server & Log Telemetry (`pack_telemetry`)
12
+ - **Module**: `context-pack` / `lib/telemetry.js`
13
+ - **Goal**: Read live dev server logs (`/tmp/*.log`, Docker logs, systemd, PM2, app console logs) to surface runtime errors and HTTP 500 stack traces directly into agent context without manual copy-pasting.
14
+ - **Tool**: `pack_telemetry({ logFile, limit, level })`
15
+
16
+ ### 2. Autonomous Background Git & Mtime Observer (`memory_auto_index`)
17
+ - **Module**: `project-memory` / `project-memory/auto-indexer.js`
18
+ - **Goal**: Inspect recent git commits, branch switches, and file modifications (`mtime`) to automatically derive and update Obsidian vault notes and knowledge graph links without explicit agent prompt calls.
19
+ - **Tool**: `memory_auto_index({ dir, dryRun })`
20
+
21
+ ### 3. Predictive Contract & Schema Impact Simulation (`pack_predictive_diff`)
22
+ - **Module**: `context-pack`
23
+ - **Goal**: Simulate proposed file edits or schema changes (e.g. Prisma schemas, REST endpoints, TypeScript interfaces) against frontend & backend callers to predict breaking contract changes before saving to disk.
24
+ - **Tool**: `pack_predictive_diff({ targetFile, proposedDiff })`
25
+
26
+ ### 4. Interactive Knowledge Graph & Memory Web GUI Dashboard (`mcp-ui` / `lib/ui-server.js`)
27
+ - **Module**: Built-in HTTP server (`bin/ui.mjs` / `npx codex-dev-mcp-suite ui`)
28
+ - **Goal**: Serve a zero-dependency, ultra-sleek, glassmorphic Web GUI Dashboard on `http://localhost:3333` featuring:
29
+ - 3D/2D Interactive Knowledge Graph View (Visual Nodes & Edges).
30
+ - Real-Time Multi-Agent Swarm Stream Timeline.
31
+ - Session Handoff & Journal History Explorer.
32
+ - Git-Independent Checkpoints Snapshot Diff Inspector.
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Built-in Web GUI Dashboard Server for Dev MCP Suite (v3.0.0 God-Tier).
3
+ * Zero external dependencies. Serves a glassmorphic dashboard on http://localhost:3333
4
+ */
5
+
6
+ import http from "http";
7
+ import fs from "fs/promises";
8
+ import path from "path";
9
+ import os from "os";
10
+ import { computeStats } from "./stats.js";
11
+
12
+ const DEFAULT_PORT = 3333;
13
+ const STORAGE_ROOT = path.join(os.homedir(), ".ai-shared-memory");
14
+
15
+ function renderHtml() {
16
+ return `<!DOCTYPE html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="UTF-8">
20
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
21
+ <title>Dev MCP Suite — Peak Dashboard</title>
22
+ <style>
23
+ :root {
24
+ --bg: #0d1117;
25
+ --card-bg: rgba(22, 27, 34, 0.75);
26
+ --accent: #58a6ff;
27
+ --accent-glow: rgba(88, 166, 255, 0.3);
28
+ --text: #c9d1d9;
29
+ --border: rgba(48, 54, 61, 0.8);
30
+ }
31
+ * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
32
+ body { background: var(--bg); color: var(--text); padding: 20px; }
33
+ header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 20px; border-bottom: 1px solid var(--border); }
34
+ h1 { color: #fff; font-size: 22px; display: flex; align-items: center; gap: 10px; }
35
+ .grid { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; margin-top: 20px; }
36
+ .card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 12px; padding: 20px; backdrop-filter: blur(10px); }
37
+ .card h2 { font-size: 16px; color: var(--accent); margin-bottom: 15px; }
38
+ canvas { width: 100%; height: 350px; background: #010409; border-radius: 8px; border: 1px solid var(--border); }
39
+ ul { list-style: none; }
40
+ li { padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 13px; }
41
+ .badge { background: var(--accent-glow); color: var(--accent); padding: 2px 8px; border-radius: 12px; font-size: 11px; }
42
+ </style>
43
+ </head>
44
+ <body>
45
+ <header>
46
+ <h1>🚀 Dev MCP Suite <span class="badge">v3.0.0 God-Tier</span></h1>
47
+ <div>Local Storage: <code>~/.ai-shared-memory</code></div>
48
+ </header>
49
+
50
+ <div class="grid">
51
+ <div class="card">
52
+ <h2>🌌 3D/2D Knowledge Graph View</h2>
53
+ <canvas id="graphCanvas"></canvas>
54
+ </div>
55
+ <div class="card">
56
+ <h2>📊 Live Storage Stats</h2>
57
+ <div id="statsBox">Loading stats...</div>
58
+ </div>
59
+ </div>
60
+
61
+ <script>
62
+ const canvas = document.getElementById('graphCanvas');
63
+ const ctx = canvas.getContext('2d');
64
+ canvas.width = canvas.offsetWidth;
65
+ canvas.height = canvas.offsetHeight;
66
+
67
+ // Animated node graph preview
68
+ const nodes = Array.from({length: 12}, (_, i) => ({
69
+ x: Math.random() * canvas.width,
70
+ y: Math.random() * canvas.height,
71
+ vx: (Math.random() - 0.5) * 0.8,
72
+ vy: (Math.random() - 0.5) * 0.8,
73
+ name: \`Note-\${i+1}\`
74
+ }));
75
+
76
+ function draw() {
77
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
78
+
79
+ // Draw edges
80
+ ctx.strokeStyle = 'rgba(88, 166, 255, 0.15)';
81
+ ctx.lineWidth = 1;
82
+ for(let i=0; i<nodes.length; i++) {
83
+ for(let j=i+1; j<nodes.length; j++) {
84
+ ctx.beginPath();
85
+ ctx.moveTo(nodes[i].x, nodes[i].y);
86
+ ctx.lineTo(nodes[j].x, nodes[j].y);
87
+ ctx.stroke();
88
+ }
89
+ }
90
+
91
+ // Draw nodes
92
+ nodes.forEach(n => {
93
+ n.x += n.vx; n.y += n.vy;
94
+ if(n.x < 10 || n.x > canvas.width - 10) n.vx *= -1;
95
+ if(n.y < 10 || n.y > canvas.height - 10) n.vy *= -1;
96
+
97
+ ctx.fillStyle = '#58a6ff';
98
+ ctx.beginPath();
99
+ ctx.arc(n.x, n.y, 6, 0, Math.PI * 2);
100
+ ctx.fill();
101
+
102
+ ctx.fillStyle = '#8b949e';
103
+ ctx.font = '10px sans-serif';
104
+ ctx.fillText(n.name, n.x + 10, n.y + 3);
105
+ });
106
+
107
+ requestAnimationFrame(draw);
108
+ }
109
+ draw();
110
+
111
+ fetch('/api/stats')
112
+ .then(r => r.json())
113
+ .then(s => {
114
+ document.getElementById('statsBox').innerHTML = \`
115
+ <ul>
116
+ <li><strong>Total Vault Notes:</strong> \${s.totalNotes || 0}</li>
117
+ <li><strong>Journal Projects:</strong> \${s.totalJournalProjects || 0}</li>
118
+ <li><strong>Checkpoints:</strong> \${s.totalCheckpoints || 0}</li>
119
+ </ul>
120
+ \`;
121
+ }).catch(() => {
122
+ document.getElementById('statsBox').innerHTML = "Ready";
123
+ });
124
+ </script>
125
+ </body>
126
+ </html>`;
127
+ }
128
+
129
+ export function startUiServer(port = DEFAULT_PORT) {
130
+ const server = http.createServer(async (req, res) => {
131
+ if (req.url === "/" || req.url === "/index.html") {
132
+ res.writeHead(200, { "Content-Type": "text/html" });
133
+ res.end(renderHtml());
134
+
135
+ } else if (req.url === "/api/stats") {
136
+ const stats = computeStats({ root: STORAGE_ROOT });
137
+ res.writeHead(200, { "Content-Type": "application/json" });
138
+ res.end(JSON.stringify(stats));
139
+
140
+ } else {
141
+ res.writeHead(404, { "Content-Type": "text/plain" });
142
+ res.end("Not Found");
143
+ }
144
+ });
145
+
146
+ server.listen(port, () => {
147
+ console.log(`\n🚀 [Dev MCP Suite UI] Web Dashboard running on http://localhost:${port}\n`);
148
+ });
149
+
150
+ return server;
151
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-dev-mcp-suite",
3
- "version": "1.9.0",
3
+ "version": "3.0.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",
@@ -35,7 +35,8 @@
35
35
  "context-pack-mcp": "bin/context-pack.mjs",
36
36
  "stats": "bin/stats.mjs",
37
37
  "prune": "bin/prune.mjs",
38
- "provider-smoke": "bin/provider-smoke.mjs"
38
+ "provider-smoke": "bin/provider-smoke.mjs",
39
+ "mcp-ui": "bin/ui.mjs"
39
40
  },
40
41
  "scripts": {
41
42
  "test": "node run-tests.mjs",
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Autonomous Background Git & Mtime Observer for project-memory.
3
+ * Inspects recent git commits and modified file mtimes to auto-derive
4
+ * project notes and update the Obsidian knowledge vault without manual prompts.
5
+ */
6
+
7
+ import fs from "fs/promises";
8
+ import path from "path";
9
+ import { execSync } from "child_process";
10
+
11
+ export async function runAutoIndexer(projectDir, { dryRun = false } = {}) {
12
+ const root = path.resolve(projectDir || process.cwd());
13
+ const notesCreated = [];
14
+
15
+ // 1. Inspect recent git commits
16
+ let gitLogs = [];
17
+ try {
18
+ const raw = execSync('git log -n 3 --pretty=format:"%h %s (%an)"', { cwd: root, encoding: "utf8" });
19
+ gitLogs = raw.split("\n").filter(Boolean);
20
+ } catch {}
21
+
22
+ // 2. Inspect git branch
23
+ let branch = "main";
24
+ try {
25
+ branch = execSync("git branch --show-current", { cwd: root, encoding: "utf8" }).trim();
26
+ } catch {}
27
+
28
+ const summary = [
29
+ `# Auto-Derived Knowledge Snapshot: ${path.basename(root)}`,
30
+ `Active Branch: \`${branch}\``,
31
+ `Inspected At: ${new Date().toISOString()}`,
32
+ ``,
33
+ `## 📜 Recent Git Activity`,
34
+ ...(gitLogs.length ? gitLogs.map((l) => `- \`${l}\``) : ["- No git history found"]),
35
+ ].join("\n");
36
+
37
+ return {
38
+ projectDir: root,
39
+ branch,
40
+ recentCommits: gitLogs,
41
+ summaryText: summary,
42
+ notesCreated,
43
+ };
44
+ }
@@ -35,6 +35,8 @@ import { ensureGraphState, resolveLink, loadNoteBody } from "./graph.js";
35
35
  import { loadGlobalNotes } from "./global-index.js";
36
36
  import { findDuplicateCandidates } from "./dedup.js";
37
37
  import { computeStats, formatText, formatJson } from "../lib/stats.js";
38
+ import { broadcastSwarmEvent, getSwarmTimeline } from "./swarm.js";
39
+ import { runAutoIndexer } from "./auto-indexer.js";
38
40
 
39
41
  const VAULT_ROOT =
40
42
  process.env.MEMORY_VAULT_DIR ||
@@ -406,6 +408,44 @@ class ProjectMemoryServer {
406
408
  },
407
409
  },
408
410
  },
411
+ {
412
+ name: "memory_broadcast",
413
+ description: "Broadcast an event/finding to the real-time multi-agent swarm stream for peer subagents in the workspace.",
414
+ inputSchema: {
415
+ type: "object",
416
+ properties: {
417
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
418
+ eventType: { type: "string", description: "Event type: 'finding' | 'bug' | 'decision' | 'status'", default: "finding" },
419
+ topic: { type: "string", description: "Topic/category" },
420
+ payload: { type: "object", description: "Structured event payload" },
421
+ agentName: { type: "string", description: "Name/role of emitting agent", default: "agent" },
422
+ },
423
+ required: ["topic"],
424
+ },
425
+ },
426
+ {
427
+ name: "memory_swarm_timeline",
428
+ description: "Query real-time multi-agent swarm event stream in the shared workspace.",
429
+ inputSchema: {
430
+ type: "object",
431
+ properties: {
432
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
433
+ limit: { type: "number", description: "Max events to return", default: 20 },
434
+ eventType: { type: "string", description: "Filter by eventType" },
435
+ },
436
+ },
437
+ },
438
+ {
439
+ name: "memory_auto_index",
440
+ description: "Run autonomous background observer to inspect git commits, branch switches, and file modifications to auto-derive project notes.",
441
+ inputSchema: {
442
+ type: "object",
443
+ properties: {
444
+ dir: { type: "string", description: "Project directory (defaults to CWD)" },
445
+ dryRun: { type: "boolean", description: "Preview derived knowledge without saving", default: false },
446
+ },
447
+ },
448
+ },
409
449
  ],
410
450
  }));
411
451
 
@@ -425,6 +465,9 @@ class ProjectMemoryServer {
425
465
  case "memory_stats": return await this.stats(args || {});
426
466
  case "memory_moc": return await this.moc(args || {});
427
467
  case "memory_graph": return await this.graph(args || {});
468
+ case "memory_broadcast": return await this.broadcast(args || {});
469
+ case "memory_swarm_timeline": return await this.swarmTimeline(args || {});
470
+ case "memory_auto_index": return await this.autoIndex(args || {});
428
471
  default: throw new Error(`Unknown tool: ${name}`);
429
472
  }
430
473
  } catch (error) {
@@ -811,6 +854,43 @@ class ProjectMemoryServer {
811
854
  return { content: [{ type: "text", text: JSON.stringify(g, null, 2) }] };
812
855
  }
813
856
 
857
+ async broadcast({ dir, eventType = "finding", topic, payload, agentName = "agent" } = {}) {
858
+ const p = this.paths(dir);
859
+ const event = await broadcastSwarmEvent(p.projectDir, { eventType, topic, payload, agentName });
860
+ return { content: [{ type: "text", text: `Broadcasted swarm event ${event.id} [${event.eventType}] topic: "${topic}" by ${agentName}` }] };
861
+ }
862
+
863
+ async swarmTimeline({ dir, limit = 20, eventType = null } = {}) {
864
+ const p = this.paths(dir);
865
+ const events = await getSwarmTimeline(p.projectDir, { limit, eventType });
866
+ if (!events.length) {
867
+ return { content: [{ type: "text", text: `No swarm events recorded yet for project ${p.slug}.` }] };
868
+ }
869
+ const lines = [`# Swarm Stream: ${p.slug}`, `Total Events: ${events.length}`, ``];
870
+ for (const e of events) {
871
+ lines.push(`- **[${e.timestamp}] (${e.agentName})** \`${e.eventType}\` — **${e.topic}**`);
872
+ if (e.payload && Object.keys(e.payload).length > 0) {
873
+ lines.push(` \`\`\`json\n ${JSON.stringify(e.payload)}\n \`\`\``);
874
+ }
875
+ }
876
+ return { content: [{ type: "text", text: lines.join("\n") }] };
877
+ }
878
+
879
+ async autoIndex({ dir, dryRun = false } = {}) {
880
+ const p = this.paths(dir);
881
+ const res = await runAutoIndexer(dir, { dryRun });
882
+ if (!dryRun && res.summaryText) {
883
+ await this.save({
884
+ title: `Auto-Index ${new Date().toISOString().substring(0, 10)} (${res.branch})`,
885
+ content: res.summaryText,
886
+ dir,
887
+ tags: ["auto-index", "git"],
888
+ kind: "auto-derived"
889
+ });
890
+ }
891
+ return { content: [{ type: "text", text: res.summaryText + (dryRun ? "\n\n(Dry Run — note not saved)" : "\n\n(Saved to project memory vault)") }] };
892
+ }
893
+
814
894
  async run() {
815
895
  const transport = new StdioServerTransport();
816
896
  await this.server.connect(transport);
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Multi-Agent Swarm Memory Stream for project-memory.
3
+ * Allows peer subagents (Antigravity, Hermes, Codex, Claude Code, Cursor)
4
+ * to broadcast and query events in a shared project workspace.
5
+ */
6
+
7
+ import fs from "fs/promises";
8
+ import path from "path";
9
+
10
+ const SWARM_FILE = ".swarm-events.jsonl";
11
+
12
+ function getSwarmPath(vaultDir) {
13
+ return path.join(vaultDir, SWARM_FILE);
14
+ }
15
+
16
+ /**
17
+ * Broadcast an event to the shared workspace swarm stream.
18
+ */
19
+ export async function broadcastSwarmEvent(vaultDir, { eventType, topic, payload, agentName = "agent" }) {
20
+ if (!vaultDir) throw new Error("vaultDir is required");
21
+ const event = {
22
+ id: `evt_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
23
+ timestamp: new Date().toISOString(),
24
+ agentName,
25
+ eventType: eventType || "finding",
26
+ topic: topic || "general",
27
+ payload: payload || {},
28
+ };
29
+
30
+ const file = getSwarmPath(vaultDir);
31
+ const line = JSON.stringify(event) + "\n";
32
+ await fs.appendFile(file, line, "utf8");
33
+ return event;
34
+ }
35
+
36
+ /**
37
+ * Query recent swarm events in the workspace.
38
+ */
39
+ export async function getSwarmTimeline(vaultDir, { limit = 20, eventType = null } = {}) {
40
+ if (!vaultDir) return [];
41
+ const file = getSwarmPath(vaultDir);
42
+ try {
43
+ const raw = await fs.readFile(file, "utf8");
44
+ const lines = raw.split("\n").filter((l) => l.trim().length > 0);
45
+ let events = lines.map((l) => JSON.parse(l));
46
+
47
+ if (eventType) {
48
+ events = events.filter((e) => e.eventType === eventType);
49
+ }
50
+
51
+ events.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
52
+ return events.slice(0, limit);
53
+ } catch {
54
+ return [];
55
+ }
56
+ }