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