klypix-mcp 1.53.0 → 1.54.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/README.md CHANGED
@@ -193,6 +193,27 @@ excludes the owning IDE/chat application's RAM, redacts command-line secrets, an
193
193
  brain or terminates a process. Multiple processes under one host are reported as parallel sessions,
194
194
  not called duplicates without an authoritative logical-session receipt.
195
195
 
196
+ ### Project Map: current structure beside project understanding
197
+
198
+ If the project contains a compatible NetworkX node-link `graph.json`, agents can ask for bounded
199
+ code-structure evidence and current brain context in one read-only call:
200
+
201
+ ```jsonc
202
+ project_map_context {
203
+ "question": "what owns refresh-token rotation?",
204
+ "graph_path": "graphify-out/graph.json"
205
+ }
206
+ ```
207
+
208
+ Use `compare_to` with another project-relative graph artifact to add exact total node/edge deltas
209
+ and additions/removals from the two bounded query neighborhoods. Both paths are confined to the
210
+ declared project root; unsafe source paths are withheld; large or unsupported artifacts are
211
+ rejected. The tool never writes graph facts into `brain.klypix`.
212
+
213
+ Graphify is the first compatible producer. KLYPIX reads artifacts that users generate separately;
214
+ it does not bundle, install, or run Graphify and does not imply a partnership. A compatible generic
215
+ `graph.json` works through the same provider-neutral boundary.
216
+
196
217
  ---
197
218
 
198
219
  ## Supported hosts and their integration level
@@ -30,7 +30,7 @@ import {
30
30
  opBrainInsights, opBrainConnect, opBrainReconcile, opBrainGarden, opCreateCanvas, opAddToCanvas, opBrainNote, opBrainMessage, opBrainAsk, opBrainChallenge, opCanvasView, opBrainLens,
31
31
  opBrainTaskContext,
32
32
  } from '../src/klypix-core.mjs';
33
- import { projectGraphContextMarkdown, queryProjectGraph } from '../src/project-graph.mjs';
33
+ import { compareProjectGraphResults, projectGraphContextMarkdown, queryProjectGraph } from '../src/project-graph.mjs';
34
34
  import { auditProject, compactAgentsBrief, linkProject, mcpServerEntry } from '../src/agent-rules.mjs';
35
35
  import { createMcpPresence, KLYPIX_MCP_INSTRUCTIONS } from '../src/mcp-presence.mjs';
36
36
  import {
@@ -256,12 +256,13 @@ server.registerTool('project_map_context', {
256
256
  question: z.string().min(1).describe('Question or code concept to ground in both current structure and project memory.'),
257
257
  project: z.string().optional().describe('Absolute project root. Defaults to this MCP connection\'s configured project/vault.'),
258
258
  graph_path: z.string().optional().describe('Optional project-relative graph JSON path. Defaults to graphify-out/graph.json and may not escape the project root.'),
259
+ compare_to: z.string().optional().describe('Optional project-relative prior graph JSON path. Adds exact total deltas plus bounded query-neighborhood changes; it may not escape the project root.'),
259
260
  depth: z.number().optional().describe('Relationship hops around the best code matches (0-3, default 1).'),
260
261
  max_nodes: z.number().optional().describe('Maximum code nodes returned (default 60, capped 200).'),
261
262
  k: z.number().optional().describe('Maximum brain cards returned (default 8; fast mode caps 8, deep history caps 20).'),
262
263
  deep_history: z.boolean().optional().describe('false (default) uses the sub-second lexical-fast correction-aware path; true opts into whole-brain semantic/history retrieval, which may cold-load the local model.'),
263
264
  },
264
- }, async ({ question, project, graph_path, depth, max_nodes, k, deep_history }) => {
265
+ }, async ({ question, project, graph_path, compare_to, depth, max_nodes, k, deep_history }) => {
265
266
  let graphResult;
266
267
  let graphMarkdown;
267
268
  try {
@@ -272,9 +273,19 @@ server.registerTool('project_map_context', {
272
273
  depth,
273
274
  maxNodes: max_nodes,
274
275
  });
276
+ if (compare_to) {
277
+ const previousGraphResult = queryProjectGraph({
278
+ project: project || mcpPresence.vault,
279
+ graphPath: compare_to,
280
+ query: question,
281
+ depth,
282
+ maxNodes: max_nodes,
283
+ });
284
+ graphResult.change = compareProjectGraphResults(graphResult, previousGraphResult);
285
+ }
275
286
  graphMarkdown = projectGraphContextMarkdown(graphResult);
276
287
  } catch (error) {
277
- graphResult = { schemaVersion: 1, status: 'invalid', error: error?.message || String(error) };
288
+ graphResult = { schemaVersion: 2, status: 'invalid', error: error?.message || String(error) };
278
289
  graphMarkdown = `# Project Map\n\nThe generated graph could not be read safely: ${graphResult.error}`;
279
290
  }
280
291
  const graphFiles = Array.isArray(graphResult?.nodes)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "klypix-mcp",
3
- "version": "1.53.0",
3
+ "version": "1.54.0",
4
4
  "description": "Shared project brain and MCP coordination server for multi-agent coding.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -7,7 +7,7 @@
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
 
10
- export const PROJECT_GRAPH_SCHEMA_VERSION = 1;
10
+ export const PROJECT_GRAPH_SCHEMA_VERSION = 2;
11
11
  export const DEFAULT_PROJECT_GRAPH = 'graphify-out/graph.json';
12
12
  export const DEFAULT_PROJECT_GRAPH_HTML = 'graphify-out/graph.html';
13
13
  export const DEFAULT_PROJECT_GRAPH_REPORT = 'graphify-out/GRAPH_REPORT.md';
@@ -148,6 +148,11 @@ function normalizeGraph(raw, projectRoot, artifact) {
148
148
  return {
149
149
  schemaVersion: PROJECT_GRAPH_SCHEMA_VERSION,
150
150
  provider: 'graphify',
151
+ format: {
152
+ family: 'networkx-node-link',
153
+ sourceSchemaVersion: shortString(raw.schema_version ?? raw.schemaVersion, 80) || null,
154
+ providerVersion: shortString(raw?.metadata?.version ?? raw?.meta?.version ?? raw.generator_version, 80) || null,
155
+ },
151
156
  artifact,
152
157
  nodes,
153
158
  edges,
@@ -294,6 +299,7 @@ export function queryProjectGraph({ project, graphPath, query = '', depth = 1, m
294
299
  return {
295
300
  schemaVersion: PROJECT_GRAPH_SCHEMA_VERSION,
296
301
  provider: graph.provider,
302
+ format: graph.format,
297
303
  status: 'ready',
298
304
  query: String(query || ''),
299
305
  depth: hopLimit,
@@ -311,23 +317,52 @@ export function queryProjectGraph({ project, graphPath, query = '', depth = 1, m
311
317
  };
312
318
  }
313
319
 
320
+ function edgeIdentity(edge) {
321
+ return `${edge.source}\u0000${edge.relation}\u0000${edge.target}\u0000${edge.confidence}`;
322
+ }
323
+
324
+ /**
325
+ * Compare two already-bounded query results. Artifact totals are exact; named
326
+ * node/edge changes describe only the returned neighborhoods and say so.
327
+ */
328
+ export function compareProjectGraphResults(current, previous) {
329
+ if (!current || !previous || current.status !== 'ready' || previous.status !== 'ready') return null;
330
+ const oldNodes = new Map(previous.nodes.map(node => [node.id, node]));
331
+ const newNodes = new Map(current.nodes.map(node => [node.id, node]));
332
+ const oldEdges = new Map(previous.edges.map(edge => [edgeIdentity(edge), edge]));
333
+ const newEdges = new Map(current.edges.map(edge => [edgeIdentity(edge), edge]));
334
+ return {
335
+ graphNodesDelta: current.counts.graphNodes - previous.counts.graphNodes,
336
+ graphEdgesDelta: current.counts.graphEdges - previous.counts.graphEdges,
337
+ addedNodes: current.nodes.filter(node => !oldNodes.has(node.id)).slice(0, 80),
338
+ removedNodes: previous.nodes.filter(node => !newNodes.has(node.id)).slice(0, 80),
339
+ addedEdges: current.edges.filter(edge => !oldEdges.has(edgeIdentity(edge))).slice(0, 160),
340
+ removedEdges: previous.edges.filter(edge => !newEdges.has(edgeIdentity(edge))).slice(0, 160),
341
+ coverage: 'bounded-query-neighborhoods',
342
+ comparedArtifact: previous.artifact?.graphJson || null,
343
+ };
344
+ }
345
+
314
346
  export function projectGraphContextMarkdown(result) {
315
347
  if (result.status === 'missing') {
316
348
  return `# Project Map\n\nNo supported project graph was found at \`${result.artifact.graphJson}\`. KLYPIX did not install or run a provider. Generate the artifact with Graphify, then retry.`;
317
349
  }
318
350
  const lines = result.nodes.map(node => {
319
- const where = node.sourceFile ? ` — \`${node.sourceFile}${node.sourceLocation ? `:${node.sourceLocation}` : ''}\`` : '';
320
- return `- **${node.label || node.id}** (${node.kind || 'symbol'})${where} · id \`${node.id}\``;
351
+ const where = node.sourceFile ? ` in \`${node.sourceFile}${node.sourceLocation ? `:${node.sourceLocation}` : ''}\`` : '';
352
+ return `- **${node.label || node.id}** (${node.kind || 'symbol'})${where}; id \`${node.id}\``;
321
353
  });
322
354
  const edgeLines = result.edges.slice(0, 80).map(edge =>
323
- `- \`${edge.source}\` —${edge.relation}→ \`${edge.target}\` [${edge.confidence}]`);
355
+ `- \`${edge.source}\` --${edge.relation}--> \`${edge.target}\` [${edge.confidence}]`);
324
356
  const warnings = [];
325
357
  if (result.diagnostics?.unsafeSourcePaths) warnings.push(`${result.diagnostics.unsafeSourcePaths} unsafe/out-of-root source path(s) were withheld`);
326
358
  if (result.diagnostics?.danglingEdges) warnings.push(`${result.diagnostics.danglingEdges} dangling edge(s) were ignored`);
327
359
  return [
328
- '# Project Map — code evidence',
329
- `Provider artifact: \`${result.artifact.graphJson}\` · ${result.counts.graphNodes.toLocaleString()} nodes · ${result.counts.graphEdges.toLocaleString()} edges · query \`${result.query || '(overview)'}\`.`,
360
+ '# Project Map: code evidence',
361
+ `Provider artifact: \`${result.artifact.graphJson}\`; ${result.counts.graphNodes.toLocaleString()} nodes; ${result.counts.graphEdges.toLocaleString()} edges; query \`${result.query || '(overview)'}\`.`,
330
362
  warnings.length ? `Safety note: ${warnings.join('; ')}.` : '',
363
+ result.change ? `## Change from \`${result.change.comparedArtifact || 'comparison artifact'}\`\nExact total deltas: ${result.change.graphNodesDelta >= 0 ? '+' : ''}${result.change.graphNodesDelta} nodes; ${result.change.graphEdgesDelta >= 0 ? '+' : ''}${result.change.graphEdgesDelta} edges. Named additions and removals are limited to the two bounded query neighborhoods.` : '',
364
+ result.change?.addedNodes?.length ? `Added in bounded result: ${result.change.addedNodes.slice(0, 20).map(node => `\`${node.label || node.id}\``).join(', ')}.` : '',
365
+ result.change?.removedNodes?.length ? `Removed from bounded result: ${result.change.removedNodes.slice(0, 20).map(node => `\`${node.label || node.id}\``).join(', ')}.` : '',
331
366
  '## Relevant code nodes',
332
367
  lines.join('\n') || '_No matching code nodes._',
333
368
  edgeLines.length ? '## Relationships\n' + edgeLines.join('\n') : '',