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 +24 -0
- package/README.md +3 -3
- package/bin/ui.mjs +5 -0
- package/context-pack/server.js +244 -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/docs/superpowers/specs/2026-07-22-v3-0-0-god-tier.md +32 -0
- package/lib/ui-server.js +151 -0
- package/package.json +3 -2
- package/project-memory/auto-indexer.js +44 -0
- package/project-memory/server.js +80 -0
- package/project-memory/swarm.js +56 -0
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,
|
|
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`), 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`** | **
|
|
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
package/context-pack/server.js
CHANGED
|
@@ -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
|
+
}
|
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,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.
|
package/lib/ui-server.js
ADDED
|
@@ -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": "
|
|
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
|
+
}
|
package/project-memory/server.js
CHANGED
|
@@ -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
|
+
}
|