codex-dev-mcp-suite 1.8.2 → 2.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 +20 -0
- package/README.md +3 -3
- package/bin/_meta.mjs +4 -0
- package/context-pack/server.js +142 -0
- package/devjournal/compress.js +75 -0
- package/devjournal/server.js +20 -0
- package/docs/superpowers/specs/2026-07-22-v2-0-0-peak-intelligence.md +50 -0
- package/lib/update-check.js +122 -0
- package/package.json +1 -1
- package/project-memory/server.js +52 -0
- package/project-memory/swarm.js +56 -0
package/CHANGELOG.md
CHANGED
|
@@ -32,6 +32,26 @@
|
|
|
32
32
|
|
|
33
33
|
# Changelog
|
|
34
34
|
|
|
35
|
+
## 2.0.0 - 2026-07-22 — The Peak Intelligence Edition 🚀
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- **Pillar 1: Dependency Blast-Radius (`pack_impact`)**:
|
|
39
|
+
- Analyze code imports (`import`/`export`/`require`) and compute caller fan-out scores before refactoring files (`context-pack`).
|
|
40
|
+
- **Pillar 2: Self-Healing Pre-flight Diagnostics (`pack_guard`)**:
|
|
41
|
+
- Run typechecks (`tsc`), lints, and test suites with built-in timeout guards to ensure 100% bug-free AI output (`context-pack`).
|
|
42
|
+
- **Pillar 3: Multi-Agent Swarm Stream (`memory_broadcast` & `memory_swarm_timeline`)**:
|
|
43
|
+
- Real-time event stream broadcasting across peer subagents (Antigravity, Hermes, Codex, Claude Code, Cursor) working in the same workspace (`project-memory`).
|
|
44
|
+
- **Pillar 4: Session Time-Machine & Token Compressor (`journal_compress`)**:
|
|
45
|
+
- Condense 50+ turns of verbose session logs into a 500-token compact snapshot to cut token costs by ~90% (`devjournal`).
|
|
46
|
+
|
|
47
|
+
## 1.9.0 - 2026-07-22
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
- **Non-Blocking Upstream Update Checker (`lib/update-check.js`)**:
|
|
51
|
+
- Automatically queries npm registry in the background once every 24 hours (cached locally).
|
|
52
|
+
- Notifies users via `stderr` when a new version of `codex-dev-mcp-suite` is published to upstream npm.
|
|
53
|
+
- Safe for stdio MCP JSON-RPC protocol transport; sub-millisecond execution, zero startup latency.
|
|
54
|
+
|
|
35
55
|
## 1.4.0 - 2026-06-17
|
|
36
56
|
|
|
37
57
|
### 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,
|
|
53
|
-
| 📜 **`devjournal`** | **Anti-Compaction Session Timeline**: Logs progress, saves handoffs,
|
|
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`), and multi-agent swarm stream. | `memory_save`, `memory_recall`, `memory_list`, `memory_get`, `memory_link`, `memory_moc`, `memory_graph`, `memory_dedup`, `memory_broadcast`, `memory_swarm_timeline` |
|
|
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 &
|
|
55
|
+
| 🔍 **`context-pack`** | **Codebase Orientation & Peak Intelligence**: Token-efficient briefings, dependency blast-radius analysis (`pack_impact`), self-healing preflight checks (`pack_guard`), and security auditing (`pack_audit`). | `pack_overview`, `pack_tree`, `pack_outline`, `pack_search`, `pack_audit`, `pack_impact`, `pack_guard` |
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
package/bin/_meta.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import { checkForUpdates } from "../lib/update-check.js";
|
|
4
5
|
|
|
5
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
7
|
|
|
@@ -41,6 +42,9 @@ function doctorLines(meta) {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export function handleCliMeta(meta) {
|
|
45
|
+
// Non-blocking update check on startup (cached 24h, logged to stderr)
|
|
46
|
+
checkForUpdates().catch(() => {});
|
|
47
|
+
|
|
44
48
|
const argv = process.argv.slice(2);
|
|
45
49
|
if (argv.includes("-v") || argv.includes("--version")) {
|
|
46
50
|
process.stdout.write(`${pkgVersion()}\n`);
|
package/context-pack/server.js
CHANGED
|
@@ -193,6 +193,30 @@ 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
|
+
},
|
|
196
220
|
],
|
|
197
221
|
}));
|
|
198
222
|
|
|
@@ -205,6 +229,8 @@ class ContextPackServer {
|
|
|
205
229
|
case "pack_outline": return await this.outline(args || {});
|
|
206
230
|
case "pack_search": return await this.search(args || {});
|
|
207
231
|
case "pack_audit": return await this.audit(args || {});
|
|
232
|
+
case "pack_impact": return await this.impact(args || {});
|
|
233
|
+
case "pack_guard": return await this.guard(args || {});
|
|
208
234
|
default: throw new Error(`Unknown tool: ${name}`);
|
|
209
235
|
}
|
|
210
236
|
} catch (e) {
|
|
@@ -418,6 +444,122 @@ class ContextPackServer {
|
|
|
418
444
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
419
445
|
}
|
|
420
446
|
|
|
447
|
+
async impact({ targetFile, dir, maxDepth = 3 }) {
|
|
448
|
+
const root = resolveDir(dir);
|
|
449
|
+
if (!targetFile) throw new Error("targetFile is required");
|
|
450
|
+
const normTarget = targetFile.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
451
|
+
const all = await walk(root, "", [], 0, 8);
|
|
452
|
+
const codeFiles = all.filter((f) => !f.dir && CODE_EXT.has(path.extname(f.rel)));
|
|
453
|
+
|
|
454
|
+
const importMap = new Map();
|
|
455
|
+
const reverseMap = new Map();
|
|
456
|
+
|
|
457
|
+
for (const f of codeFiles) {
|
|
458
|
+
const abs = path.join(root, f.rel);
|
|
459
|
+
try {
|
|
460
|
+
const content = await fs.readFile(abs, "utf8");
|
|
461
|
+
const imports = [];
|
|
462
|
+
const re = /(?:import|export)\s+.*?\s+from\s+["']([^"']+)["']|require\(["']([^"']+)["']\)|import\(["']([^"']+)["']\)/g;
|
|
463
|
+
let match;
|
|
464
|
+
while ((match = re.exec(content)) !== null) {
|
|
465
|
+
const spec = match[1] || match[2] || match[3];
|
|
466
|
+
if (spec && (spec.startsWith(".") || spec.startsWith("/"))) {
|
|
467
|
+
const dirName = path.dirname(f.rel);
|
|
468
|
+
const resolvedRel = path.normalize(path.join(dirName, spec)).replace(/\\/g, "/");
|
|
469
|
+
imports.push(resolvedRel);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
importMap.set(f.rel.replace(/\\/g, "/"), imports);
|
|
473
|
+
} catch {}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const [importer, importedList] of importMap.entries()) {
|
|
477
|
+
for (const imp of importedList) {
|
|
478
|
+
for (const f of codeFiles) {
|
|
479
|
+
const fRel = f.rel.replace(/\\/g, "/");
|
|
480
|
+
const fRelNoExt = fRel.replace(/\.[^/.]+$/, "");
|
|
481
|
+
if (fRel === imp || fRelNoExt === imp || fRelNoExt === imp.replace(/\/index$/, "")) {
|
|
482
|
+
if (!reverseMap.has(fRel)) reverseMap.set(fRel, []);
|
|
483
|
+
reverseMap.get(fRel).push(importer);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const directImporters = reverseMap.get(normTarget) || [];
|
|
490
|
+
const directImports = importMap.get(normTarget) || [];
|
|
491
|
+
|
|
492
|
+
const lines = [
|
|
493
|
+
`# Impact Blast-Radius: ${normTarget}`,
|
|
494
|
+
`Project: ${root}`,
|
|
495
|
+
``,
|
|
496
|
+
`## 🎯 Direct Importers / Callers (${directImporters.length})`,
|
|
497
|
+
...(directImporters.length ? directImporters.map((f) => `- \`${f}\``) : ["- None (No other file directly imports this file)"]),
|
|
498
|
+
``,
|
|
499
|
+
`## 📦 Outbound Imports (${directImports.length})`,
|
|
500
|
+
...(directImports.length ? directImports.map((f) => `- \`${f}\``) : ["- None"]),
|
|
501
|
+
``,
|
|
502
|
+
`## ⚠️ Calculated Blast-Radius Score: ${directImporters.length > 5 ? "HIGH 🔴" : directImporters.length > 2 ? "MEDIUM 🟡" : "LOW 🟢"}`
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
async guard({ dir, checkType = "all" }) {
|
|
509
|
+
const root = resolveDir(dir);
|
|
510
|
+
const pkgPath = path.join(root, "package.json");
|
|
511
|
+
let hasPkg = false;
|
|
512
|
+
let pkg = {};
|
|
513
|
+
try {
|
|
514
|
+
pkg = JSON.parse(await fs.readFile(pkgPath, "utf8"));
|
|
515
|
+
hasPkg = true;
|
|
516
|
+
} catch {}
|
|
517
|
+
|
|
518
|
+
const runScript = (cmd, label) => {
|
|
519
|
+
try {
|
|
520
|
+
const out = execSync(cmd, { cwd: root, encoding: "utf8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"] });
|
|
521
|
+
return { label, status: "PASS 🟢", output: out.slice(0, 500) };
|
|
522
|
+
} catch (e) {
|
|
523
|
+
const errOut = (e.stdout || "") + "\n" + (e.stderr || "");
|
|
524
|
+
return { label, status: "FAIL 🔴", output: errOut.slice(0, 1000) };
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const results = [];
|
|
529
|
+
if (hasPkg && pkg.scripts) {
|
|
530
|
+
if ((checkType === "all" || checkType === "typecheck") && (pkg.scripts.typecheck || pkg.scripts["check-types"])) {
|
|
531
|
+
results.push(runScript("npm run " + (pkg.scripts.typecheck ? "typecheck" : "check-types"), "Typecheck"));
|
|
532
|
+
} else if (hasPkg && await fs.access(path.join(root, "tsconfig.json")).then(() => true).catch(() => false)) {
|
|
533
|
+
results.push(runScript("npx tsc --noEmit", "Typecheck (tsc)"));
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if ((checkType === "all" || checkType === "lint") && pkg.scripts.lint) {
|
|
537
|
+
results.push(runScript("npm run lint", "Linter"));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if ((checkType === "all" || checkType === "test") && pkg.scripts.test) {
|
|
541
|
+
results.push(runScript("npm test", "Test Suite"));
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const lines = [`# Guard Pre-flight Diagnostics: ${path.basename(root)}`, `Path: ${root}`, ``];
|
|
546
|
+
if (!results.length) {
|
|
547
|
+
lines.push("ℹ️ No build/test/lint scripts detected in project toolchain.");
|
|
548
|
+
} else {
|
|
549
|
+
for (const r of results) {
|
|
550
|
+
lines.push(`## ${r.label}: ${r.status}`);
|
|
551
|
+
if (r.output) {
|
|
552
|
+
lines.push("```");
|
|
553
|
+
lines.push(r.output.trim());
|
|
554
|
+
lines.push("```");
|
|
555
|
+
}
|
|
556
|
+
lines.push("");
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
561
|
+
}
|
|
562
|
+
|
|
421
563
|
async run() {
|
|
422
564
|
const t = new StdioServerTransport();
|
|
423
565
|
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
|
+
}
|
package/devjournal/server.js
CHANGED
|
@@ -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,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-blocking, cached upstream update checker for Dev MCP Suite.
|
|
3
|
+
* Checks npm registry (https://registry.npmjs.org/codex-dev-mcp-suite/latest)
|
|
4
|
+
* once every 24 hours. Safe for stdio MCP transport (logs only to stderr).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import http from "http";
|
|
8
|
+
import https from "https";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import os from "os";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
|
|
14
|
+
const CACHE_FILE = path.join(os.homedir(), ".ai-shared-memory", ".update-check-cache.json");
|
|
15
|
+
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
16
|
+
const PKG_NAME = "codex-dev-mcp-suite";
|
|
17
|
+
|
|
18
|
+
/** Reads current package version from package.json */
|
|
19
|
+
function getCurrentVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const dir = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const pkgPath = path.join(dir, "..", "package.json");
|
|
23
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
24
|
+
return pkg.version || "1.8.2";
|
|
25
|
+
} catch {
|
|
26
|
+
return "1.8.2";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Simple version comparator (e.g. "1.9.0" > "1.8.2") */
|
|
31
|
+
export function isNewerVersion(current, latest) {
|
|
32
|
+
if (!current || !latest) return false;
|
|
33
|
+
const c = current.split(".").map(Number);
|
|
34
|
+
const l = latest.split(".").map(Number);
|
|
35
|
+
for (let i = 0; i < 3; i++) {
|
|
36
|
+
if ((l[i] || 0) > (c[i] || 0)) return true;
|
|
37
|
+
if ((l[i] || 0) < (c[i] || 0)) return false;
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Check npm registry for latest version asynchronously with timeout */
|
|
43
|
+
function fetchLatestNpmVersion() {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const req = https.get(
|
|
46
|
+
`https://registry.npmjs.org/${PKG_NAME}/latest`,
|
|
47
|
+
{ headers: { "User-Agent": `${PKG_NAME}-update-check` }, timeout: 1500 },
|
|
48
|
+
(res) => {
|
|
49
|
+
if (res.statusCode !== 200) return resolve(null);
|
|
50
|
+
let body = "";
|
|
51
|
+
res.on("data", (chunk) => (body += chunk));
|
|
52
|
+
res.on("end", () => {
|
|
53
|
+
try {
|
|
54
|
+
const data = JSON.parse(body);
|
|
55
|
+
resolve(data.version || null);
|
|
56
|
+
} catch {
|
|
57
|
+
resolve(null);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
req.on("error", () => resolve(null));
|
|
63
|
+
req.on("timeout", () => {
|
|
64
|
+
req.destroy();
|
|
65
|
+
resolve(null);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Triggers a background check for updates.
|
|
72
|
+
* Never blocks process execution or breaks stdio protocol.
|
|
73
|
+
*/
|
|
74
|
+
export async function checkForUpdates() {
|
|
75
|
+
try {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
let cache = { lastCheck: 0, latestVersion: null };
|
|
78
|
+
|
|
79
|
+
// Read existing cache if available
|
|
80
|
+
try {
|
|
81
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
82
|
+
cache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf8"));
|
|
83
|
+
}
|
|
84
|
+
} catch { /* ignore cache read error */ }
|
|
85
|
+
|
|
86
|
+
const currentVersion = getCurrentVersion();
|
|
87
|
+
|
|
88
|
+
// If cache is fresh, check cached latest version
|
|
89
|
+
if (now - (cache.lastCheck || 0) < CHECK_INTERVAL_MS && cache.latestVersion) {
|
|
90
|
+
if (isNewerVersion(currentVersion, cache.latestVersion)) {
|
|
91
|
+
console.error(
|
|
92
|
+
`\n💡 [Dev MCP Suite] Update available: ${currentVersion} → ${cache.latestVersion}\n` +
|
|
93
|
+
` Run "npm i -g ${PKG_NAME}@latest" or use "npx -y -p ${PKG_NAME}@latest"\n`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Perform background check
|
|
100
|
+
fetchLatestNpmVersion().then((latest) => {
|
|
101
|
+
if (!latest) return;
|
|
102
|
+
|
|
103
|
+
// Update cache
|
|
104
|
+
try {
|
|
105
|
+
const cacheDir = path.dirname(CACHE_FILE);
|
|
106
|
+
if (!fs.existsSync(cacheDir)) {
|
|
107
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: now, latestVersion: latest }));
|
|
110
|
+
} catch { /* ignore cache write error */ }
|
|
111
|
+
|
|
112
|
+
if (isNewerVersion(currentVersion, latest)) {
|
|
113
|
+
console.error(
|
|
114
|
+
`\n💡 [Dev MCP Suite] Update available: ${currentVersion} → ${latest}\n` +
|
|
115
|
+
` Run "npm i -g ${PKG_NAME}@latest" or use "npx -y -p ${PKG_NAME}@latest"\n`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}).catch(() => {});
|
|
119
|
+
} catch {
|
|
120
|
+
/* Silent catch: update checks must never throw or disrupt the application */
|
|
121
|
+
}
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codex-dev-mcp-suite",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.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",
|
package/project-memory/server.js
CHANGED
|
@@ -35,6 +35,7 @@ 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";
|
|
38
39
|
|
|
39
40
|
const VAULT_ROOT =
|
|
40
41
|
process.env.MEMORY_VAULT_DIR ||
|
|
@@ -406,6 +407,33 @@ class ProjectMemoryServer {
|
|
|
406
407
|
},
|
|
407
408
|
},
|
|
408
409
|
},
|
|
410
|
+
{
|
|
411
|
+
name: "memory_broadcast",
|
|
412
|
+
description: "Broadcast an event/finding to the real-time multi-agent swarm stream for peer subagents in the workspace.",
|
|
413
|
+
inputSchema: {
|
|
414
|
+
type: "object",
|
|
415
|
+
properties: {
|
|
416
|
+
dir: { type: "string", description: "Project directory (defaults to CWD)" },
|
|
417
|
+
eventType: { type: "string", description: "Event type: 'finding' | 'bug' | 'decision' | 'status'", default: "finding" },
|
|
418
|
+
topic: { type: "string", description: "Topic/category" },
|
|
419
|
+
payload: { type: "object", description: "Structured event payload" },
|
|
420
|
+
agentName: { type: "string", description: "Name/role of emitting agent", default: "agent" },
|
|
421
|
+
},
|
|
422
|
+
required: ["topic"],
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: "memory_swarm_timeline",
|
|
427
|
+
description: "Query real-time multi-agent swarm event stream in the shared workspace.",
|
|
428
|
+
inputSchema: {
|
|
429
|
+
type: "object",
|
|
430
|
+
properties: {
|
|
431
|
+
dir: { type: "string", description: "Project directory (defaults to CWD)" },
|
|
432
|
+
limit: { type: "number", description: "Max events to return", default: 20 },
|
|
433
|
+
eventType: { type: "string", description: "Filter by eventType" },
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
409
437
|
],
|
|
410
438
|
}));
|
|
411
439
|
|
|
@@ -425,6 +453,8 @@ class ProjectMemoryServer {
|
|
|
425
453
|
case "memory_stats": return await this.stats(args || {});
|
|
426
454
|
case "memory_moc": return await this.moc(args || {});
|
|
427
455
|
case "memory_graph": return await this.graph(args || {});
|
|
456
|
+
case "memory_broadcast": return await this.broadcast(args || {});
|
|
457
|
+
case "memory_swarm_timeline": return await this.swarmTimeline(args || {});
|
|
428
458
|
default: throw new Error(`Unknown tool: ${name}`);
|
|
429
459
|
}
|
|
430
460
|
} catch (error) {
|
|
@@ -811,6 +841,28 @@ class ProjectMemoryServer {
|
|
|
811
841
|
return { content: [{ type: "text", text: JSON.stringify(g, null, 2) }] };
|
|
812
842
|
}
|
|
813
843
|
|
|
844
|
+
async broadcast({ dir, eventType = "finding", topic, payload, agentName = "agent" } = {}) {
|
|
845
|
+
const p = this.paths(dir);
|
|
846
|
+
const event = await broadcastSwarmEvent(p.projectDir, { eventType, topic, payload, agentName });
|
|
847
|
+
return { content: [{ type: "text", text: `Broadcasted swarm event ${event.id} [${event.eventType}] topic: "${topic}" by ${agentName}` }] };
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
async swarmTimeline({ dir, limit = 20, eventType = null } = {}) {
|
|
851
|
+
const p = this.paths(dir);
|
|
852
|
+
const events = await getSwarmTimeline(p.projectDir, { limit, eventType });
|
|
853
|
+
if (!events.length) {
|
|
854
|
+
return { content: [{ type: "text", text: `No swarm events recorded yet for project ${p.slug}.` }] };
|
|
855
|
+
}
|
|
856
|
+
const lines = [`# Swarm Stream: ${p.slug}`, `Total Events: ${events.length}`, ``];
|
|
857
|
+
for (const e of events) {
|
|
858
|
+
lines.push(`- **[${e.timestamp}] (${e.agentName})** \`${e.eventType}\` — **${e.topic}**`);
|
|
859
|
+
if (e.payload && Object.keys(e.payload).length > 0) {
|
|
860
|
+
lines.push(` \`\`\`json\n ${JSON.stringify(e.payload)}\n \`\`\``);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
864
|
+
}
|
|
865
|
+
|
|
814
866
|
async run() {
|
|
815
867
|
const transport = new StdioServerTransport();
|
|
816
868
|
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
|
+
}
|