cbmem.ts generated by install.ps1 was not working in my Pi agent.
this my modification to make it compatible with Pi (0.85.1). so far work for me.
// codebase-memory-mcp:start
// Generated by codebase-memory-mcp for pi.
import { spawn } from 'node:child_process';
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
const BIN = '~.local/bin/codebase-memory-mcp.exe'; //change it with the right path
async function call(tool: string, args: any, signal?: AbortSignal): Promise<string> {
return new Promise((resolve) => {
const child = spawn(BIN, ['cli', tool], {
stdio: ['pipe', 'pipe', 'pipe'],
env: { ...process.env, CBM_LOG_LEVEL: 'error' },
cwd: process.cwd(),
windowsHide: true,
});
if (args && typeof args === 'object' && Object.keys(args).length > 0) {
child.stdin.write(JSON.stringify(args));
}
child.stdin.end();
let out = '';
let err = '';
let settled = false;
const finish = (result: string) => {
if (!settled) {
settled = true;
child.removeAllListeners();
child.stdout?.removeAllListeners();
child.stderr?.removeAllListeners();
child.stdin?.removeAllListeners();
resolve(result);
}
};
const timeout = setTimeout(() => {
child.kill('SIGKILL');
finish('Error: Proses codebase-memory-mcp macet atau timeout (60 detik).');
}, 60000);
const onAbort = () => {
clearTimeout(timeout);
if (!child.killed) child.kill();
};
signal?.addEventListener('abort', onAbort, { once: true });
child.stdout.on('data', (d) => (out += d.toString()));
child.stderr.on('data', (d) => (err += d.toString()));
child.stdin.on('error', () => {});
child.on('error', (e) => {
clearTimeout(timeout);
finish(`Error: Gagal menjalankan CLI (${e.message}).`);
});
child.on('close', (code) => {
clearTimeout(timeout);
const lines = out.split('\n').map((l) => l.trim()).filter(Boolean);
for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i];
try {
JSON.parse(line);
// Kembalikan hasil MURNI dari CLI (tanpa debug header)
return finish(line);
} catch {
/* Abaikan baris yang bukan JSON */
}
}
finish(out || err || `CLI keluar dengan kode ${code}`);
});
});
}
export default function (pi: ExtensionAPI) {
const register = (name: string, description: string, parameters: any) => {
pi.registerTool({
name,
label: name.replace(/_/g, ' ').toUpperCase(),
description,
parameters,
// Signature Pi Agent: (toolCallId, params, signal, onUpdate, ctx)
execute: async (toolCallId: string, params: any, signal?: AbortSignal, onUpdate?: (update: any) => void) => {
const safeParams = params || {};
// PERBAIKAN UTAMA: onUpdate WAJIB dibungkus dalam properti `content`
if (onUpdate) {
onUpdate({
content: [{
type: 'text',
text: `${JSON.stringify(safeParams, null, 2)}\n\n`
}]
});
}
const resultText = await call(name, safeParams, signal);
// Hasil akhir juga wajib menggunakan format `content` array
return {
content: [{ type: "text", text: resultText }]
};
}
});
};
// 1. index_repository
register('index_repository',
'Index a codebase repository into the knowledge graph. REQUIRED: repo_path.',
{
type: 'object',
properties: {
'repo_path': { type: 'string', description: 'Path to the repository (e.g., "." or "D:/project/INEM-0.1").' },
'mode': { type: 'string', description: 'All modes run type-aware LSP call/usage resolution. full: all files + similarity/semantic edges. moderate: filtered files + similarity/semantic. fast: filtered files, no similarity/semantic. cross-repo-intelligence: match Routes/Channels across projects.' },
'target_projects': { type: 'array', items: { type: 'string' }, description: 'Projects to search for cross-repo links (cross-repo-intelligence mode). Use ["*"] for all indexed projects. Run list_projects to see available projects.' },
'name': { type: 'string', description: 'Override the derived project name. Non-ASCII bytes are encoded and unsafe path characters are normalized.' },
'persistence': { type: 'boolean', description: 'Write compressed artifact to .codebase-memory/graph.db.zst for team sharing. Teammates can bootstrap from the artifact instead of full re-indexing.' }
},
required: ['repo_path']
});
// 2. search_graph
register('search_graph',
'Search the knowledge graph using BM25, patterns, or semantic search. REQUIRED: project.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the indexed project.' },
'query': { type: 'string', description: 'Natural-language or keyword full-text search using BM25 ranking. Tokens are split on whitespace; camelCase identifiers are indexed as individual words. Results are ranked with structural boosting. When provided, name_pattern is ignored.' },
'label': { type: 'string', description: 'Filter by node label (e.g., Function, Class, Route).' },
'name_pattern': { type: 'string', description: 'Regex or glob pattern for node names.' },
'qn_pattern': { type: 'string', description: 'Regex pattern for qualified names.' },
'file_pattern': { type: 'string', description: 'Glob pattern to filter by file path.' },
'relationship': { type: 'string', description: 'Filter by relationship type (e.g., CALLS, IMPORTS).' },
'min_degree': { type: 'integer', description: 'Minimum node degree (connections).' },
'max_degree': { type: 'integer', description: 'Maximum node degree.' },
'exclude_entry_points': { type: 'boolean', description: 'Exclude entry points like HTTP routes or CLI commands.' },
'include_connected': { type: 'boolean', description: 'Include connected nodes even if they don\'t match the primary filter.' },
'semantic_query': { type: 'array', items: { type: 'string' }, description: 'MUST be an ARRAY of keyword strings (e.g. ["send","pubsub","publish"]) — NOT a single string. Each keyword is scored independently via per-keyword min-cosine. Requires moderate/full index mode. Results appear in the semantic_results field.' },
'limit': { type: 'integer', description: 'Max results per call. Default 50. Response carries total and has_more so callers can detect the limit and paginate.' },
'offset': { type: 'integer', description: 'Skip the first N matching nodes. Combine with limit to page: increment offset by limit and re-call while has_more is true.' },
'format': { type: 'string', description: 'Response encoding. tree (default): prefix-grouped text rows. json: structured JSON.' },
'fields': { type: 'array', items: { type: 'string' }, description: 'Extra per-node property columns, e.g., complexity, cognitive, signature, docstring, return_type, is_test, lines(int). Core row columns are always present — do not request them here.' },
'detail': { type: 'string', description: 'ids: bare qualified-name enumeration (cheapest). default: full rows.' }
},
required: ['project']
});
// 3. query_graph
register('query_graph',
'Execute a Cypher query against the knowledge graph. REQUIRED: project, query.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the indexed project.' },
'query': { type: 'string', description: 'Cypher query string.' },
'graph': { type: 'string', description: 'Which graph to query: code (default) or missed (only files not fully indexed).' },
'max_rows': { type: 'integer', description: 'Optional row limit. Default: unlimited up to a 100k row ceiling. No offset support — use search_graph for paginated browsing.' }
},
required: ['project', 'query']
});
// 4. trace_path
register('trace_path',
'Trace execution or dependency paths. REQUIRED: project, function_name.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the indexed project.' },
'function_name': { type: 'string', description: 'The function name or qualified name to start tracing from.' },
'direction': { type: 'string', description: 'Direction of the trace (e.g., callers or callees).' },
'depth': { type: 'integer', description: 'Maximum traversal depth.' },
'limit': { type: 'integer', description: 'Rows per page. When truncated, the response carries a cursor for the next page.' },
'cursor': { type: 'string', description: 'Resume token from a previous response\'s next field. Pass it back with ALL other arguments identical to get the following page.' },
'mode': { type: 'string', description: 'calls: follow CALLS edges. data_flow: follow CALLS+DATA_FLOWS with arg expressions. cross_service: follow HTTP/ASYNC/CROSS edges to hop into other services.' },
'parameter_name': { type: 'string', description: 'For data_flow mode: scope trace to a specific parameter name.' },
'edge_types': { type: 'array', items: { type: 'string' }, description: 'Filter by specific edge types.' },
'risk_labels': { type: 'boolean', description: 'Add risk classification (CRITICAL/HIGH/MEDIUM/LOW) based on hop distance.' },
'include_tests': { type: 'boolean', description: 'Include test files in results. When false (default), test nodes are filtered out.' },
'format': { type: 'string', description: 'Response encoding: tree (default) or json.' },
'include_evidence': { type: 'boolean', description: 'Add how each hop was resolved (lsp | language_rule | heuristic | unresolved) and the resolver\'s confidence.' }
},
required: ['project', 'function_name']
});
// 5. get_code_snippet
register('get_code_snippet',
'Retrieve a specific code snippet. REQUIRED: project, qualified_name.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the indexed project.' },
'qualified_name': { type: 'string', description: 'Full qualified_name from search_graph, or short function name.' },
'include_neighbors': { type: 'boolean', description: 'Include surrounding context/neighbors in the snippet.' }
},
required: ['project', 'qualified_name']
});
// 6. get_graph_schema
register('get_graph_schema',
'Get the schema and node types available in the knowledge graph. REQUIRED: project.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the indexed project.' }
},
required: ['project']
});
// 7. get_architecture
register('get_architecture',
'Analyze high-level architecture. REQUIRED: project.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the indexed project.' },
'path': { type: 'string', description: 'Optional directory prefix to scope architecture (e.g. apps/hoa).' },
'aspects': { type: 'array', items: { type: 'string' }, description: "Aspects to include. 'all' = everything; 'overview' = compact summary. 'cycles' is opt-in ONLY (scans for circular CALLS dependencies)." }
},
required: ['project']
});
// 8. search_code
register('search_code',
'Search for code patterns using grep. REQUIRED: project, pattern.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the indexed project.' },
'pattern': { type: 'string', description: 'The code pattern or text to search for.' },
'file_pattern': { type: 'string', description: 'Glob for grep --include (e.g. *.go).' },
'path_filter': { type: 'string', description: 'Regex filter on result file paths (e.g. ^src/ or \\.(go|ts)$).' },
'mode': { type: 'string', description: 'compact: signatures+metadata (default). full: with source. files: just file list.' },
'context': { type: 'integer', description: 'Lines of context around each match (like grep -C). Only used in compact mode.' },
'regex': { type: 'boolean', description: 'Treat pattern as a regular expression.' },
'debug': { type: 'boolean', description: 'Include scope_ms, scan_ms, and enrich_ms phase timing diagnostics.' },
'limit': { type: 'integer', description: 'Max enriched results per call. Default 10. No offset parameter — raise limit or narrow with file_pattern/path_filter.' }
},
required: ['project', 'pattern']
});
// 9. list_projects
register('list_projects',
'List all projects currently indexed in the codebase memory. No arguments required.',
{ type: 'object', properties: {} });
// 10. delete_project
register('delete_project',
'Delete a project and its associated data from the index. REQUIRED: project.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the project to delete.' }
},
required: ['project']
});
// 11. index_status
register('index_status',
'Check the current indexing status, progress, and health. REQUIRED: project.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the project to check.' },
'verbose': { type: 'boolean', description: 'Include the git context block (worktree/shadow path variants). Only needed when debugging where an index lives.' }
},
required: ['project']
});
// 12. check_index_coverage
register('check_index_coverage',
'Analyze how much of the codebase is indexed and identify gaps. REQUIRED: project.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the project to check.' },
'paths': { type: 'array', items: { type: 'string' }, description: 'Repository-relative files to check exactly. Required if scopes is omitted.' },
'scopes': { type: 'array', items: { type: 'string' }, description: 'Repository-relative path prefixes; use "." for the project root. Required if paths is omitted.' },
'scope_limit': { type: 'integer', description: 'Pagination limit for scope results.' },
'scope_offset': { type: 'integer', description: 'Pagination offset for scope results.' }
},
required: ['project']
});
// 13. detect_changes
register('detect_changes',
'Detect file changes and diffs since the last indexing run. REQUIRED: project.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the project.' },
'scope': { type: 'string', description: 'files: changed files only. impact (default): files + the transitive impact set.' },
'direction': { type: 'string', description: 'inbound (default) = blast radius (callers). outbound = dependencies. both = union.' },
'depth': { type: 'integer', description: 'Max traversal hops from the changed symbols.' },
'limit': { type: 'integer', description: 'Per-symbol impacted rows shown (nearest hops first).' },
'base_branch': { type: 'string', description: 'Base branch to compare against.' },
'since': { type: 'string', description: 'Git ref or tag to compare from (e.g. HEAD~5, v0.5.0). Diffs <ref>...HEAD.' },
'format': { type: 'string', description: 'Response encoding.' }
},
required: ['project']
});
// 14. manage_adr
register('manage_adr',
'Manage Architecture Decision Records (ADRs). REQUIRED: project, mode.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the project.' },
'mode': { type: 'string', description: 'update replaces the entire ADR document; sections only lists existing headings.' },
'content': { type: 'string', description: 'Complete replacement document required by update mode.' }
},
required: ['project', 'mode']
});
// 15. ingest_traces
register('ingest_traces',
'Ingest runtime execution traces into the knowledge graph. REQUIRED: project, traces.',
{
type: 'object',
properties: {
'project': { type: 'string', description: 'The name of the project.' },
'traces': { type: 'array', items: { type: 'string' }, description: 'Array of trace file paths or trace data to ingest.' }
},
required: ['project', 'traces']
});
}
// codebase-memory-mcp:end
Version
codebase-memory-mcp 0.10.6
Platform
Windows (x64)
Install channel
GitHub release archive / install.sh / install.ps1
Binary variant
standard
What happened, and what did you expect?
cbmem.ts generated by install.ps1 was not working in my Pi agent.
this my modification to make it compatible with Pi (0.85.1). so far work for me.
Reproduction
Logs
Diagnostics trajectory (memory / performance / leak issues)
Project scale (if relevant)
No response
Confirmations