klypix-mcp 1.53.0 → 1.55.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 +36 -1
- package/bin/klypix-project-map.mjs +67 -0
- package/bin/klypix-worker.mjs +20 -5
- package/examples/github/klypix-project-map.yml +45 -0
- package/package.json +4 -3
- package/src/project-graph.mjs +82 -6
package/README.md
CHANGED
|
@@ -193,6 +193,41 @@ 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. When a returned brain card names an exact mapped source path, the structured response
|
|
212
|
+
also includes a review-only evidence-link proposal. It never promotes similarity into truth and
|
|
213
|
+
never writes graph facts or links into `brain.klypix`.
|
|
214
|
+
|
|
215
|
+
Graphify is the first compatible producer. KLYPIX reads artifacts that users generate separately;
|
|
216
|
+
it does not bundle, install, or run Graphify and does not imply a partnership. A compatible generic
|
|
217
|
+
`graph.json` works through the same provider-neutral boundary.
|
|
218
|
+
|
|
219
|
+
For a reproducible map artifact on every pull request and main-branch push, install the shipped
|
|
220
|
+
read-only workflow into a Git checkout:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npx klypix-project-map setup-github /path/to/project
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The command refuses to overwrite an existing workflow unless `--force` is explicit. The installed
|
|
227
|
+
workflow has `contents: read`, pins every action by commit SHA, pins `graphifyy==0.9.33`, validates
|
|
228
|
+
the graph contract, and uploads `graphify-out/` as a 14-day build artifact. This is opt-in CI code:
|
|
229
|
+
the local MCP tool still never installs or launches Graphify.
|
|
230
|
+
|
|
196
231
|
---
|
|
197
232
|
|
|
198
233
|
## Supported hosts and their integration level
|
|
@@ -398,7 +433,7 @@ The MCP verbs below are what agents call. These are what **you** call:
|
|
|
398
433
|
| `brain_message` | Session-to-session coordination notes (24h TTL, never written into the brain) |
|
|
399
434
|
| `brain_sync` | Context Gateway: task capsule, active-task peers, exact-file overlap, one-time alerts, timing |
|
|
400
435
|
| `brain_connect` | Find and draw related-but-unlinked cards |
|
|
401
|
-
| `project_map_context` | Read-only, bounded code-graph evidence beside correction-aware brain context; Graphify artifacts are supported but never installed or run |
|
|
436
|
+
| `project_map_context` | Read-only, bounded code-graph evidence beside correction-aware brain context, with exact-path review proposals; Graphify artifacts are supported but never installed or run locally |
|
|
402
437
|
| `canvas_view` | Returns the board as a structured render spec plus a text summary, and declares an MCP Apps (SEP-1865) UI resource |
|
|
403
438
|
| `read_canvas` | A canvas as markdown (cards, connection graph, `[[links]]`, `#tags`) |
|
|
404
439
|
| `search_canvases` | Search across canvases by name and content |
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { discoverProjectGraph } from '../src/project-graph.mjs';
|
|
6
|
+
|
|
7
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const template = path.resolve(here, '..', 'examples', 'github', 'klypix-project-map.yml');
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0] || 'status';
|
|
11
|
+
const force = args.includes('--force');
|
|
12
|
+
const rootArg = args.find((value, index) => index > 0 && value !== '--force') || process.cwd();
|
|
13
|
+
|
|
14
|
+
function projectRoot(value) {
|
|
15
|
+
const root = fs.realpathSync(path.resolve(value));
|
|
16
|
+
if (!fs.statSync(root).isDirectory()) throw new Error('Project root is not a directory.');
|
|
17
|
+
return root;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function usage() {
|
|
21
|
+
console.log([
|
|
22
|
+
'KLYPIX Project Map',
|
|
23
|
+
'',
|
|
24
|
+
' klypix-project-map status [project]',
|
|
25
|
+
' klypix-project-map setup-github [project] [--force]',
|
|
26
|
+
'',
|
|
27
|
+
'setup-github installs a read-only, pinned GitHub Actions workflow. It never installs or runs a provider on your computer.',
|
|
28
|
+
].join('\n'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (command === '--help' || command === '-h' || command === 'help') {
|
|
33
|
+
usage();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
const root = projectRoot(rootArg);
|
|
37
|
+
if (command === 'status') {
|
|
38
|
+
const graph = discoverProjectGraph({ project: root });
|
|
39
|
+
const workflow = path.join(root, '.github', 'workflows', 'klypix-project-map.yml');
|
|
40
|
+
console.log(`Project: ${root}`);
|
|
41
|
+
console.log(`Graph artifact: ${graph.status === 'ready' ? `ready (${graph.artifact.graphJson})` : `missing (${graph.artifact.graphJson})`}`);
|
|
42
|
+
console.log(`GitHub workflow: ${fs.existsSync(workflow) ? 'installed' : 'not installed'}`);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
if (command !== 'setup-github') {
|
|
46
|
+
usage();
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
if (!fs.existsSync(path.join(root, '.git'))) throw new Error('Run setup-github inside a Git checkout.');
|
|
50
|
+
const destination = path.join(root, '.github', 'workflows', 'klypix-project-map.yml');
|
|
51
|
+
const source = fs.readFileSync(template, 'utf8');
|
|
52
|
+
if (fs.existsSync(destination)) {
|
|
53
|
+
const current = fs.readFileSync(destination, 'utf8');
|
|
54
|
+
if (current === source) {
|
|
55
|
+
console.log(`Project Map workflow is already current: ${destination}`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
if (!force) throw new Error(`Workflow already exists with different contents: ${destination}\nReview it, then rerun with --force if replacement is intended.`);
|
|
59
|
+
}
|
|
60
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
61
|
+
fs.writeFileSync(destination, source, { encoding: 'utf8', flag: 'w' });
|
|
62
|
+
console.log(`Installed Project Map workflow: ${destination}`);
|
|
63
|
+
console.log('Commit the workflow when you are ready. Pull requests and main-branch pushes will produce a read-only Project Map artifact.');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(`Project Map: ${error?.message || error}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
package/bin/klypix-worker.mjs
CHANGED
|
@@ -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, suggestProjectGraphBrainLinks } 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:
|
|
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)
|
|
@@ -298,10 +309,14 @@ server.registerTool('project_map_context', {
|
|
|
298
309
|
.filter(block => block.kind === 'text')
|
|
299
310
|
.map(block => block.text)
|
|
300
311
|
.join('\n\n');
|
|
312
|
+
const evidenceLinkProposals = suggestProjectGraphBrainLinks(graphResult, brainResult.context);
|
|
313
|
+
const proposalMarkdown = evidenceLinkProposals.length
|
|
314
|
+
? `\n\n## Exact-path evidence link proposals\n\n${evidenceLinkProposals.slice(0, 12).map(proposal => `- Review brain card \`${proposal.brainCardId}\` beside **${proposal.nodeLabel}** in \`${proposal.sourceFile}\`.`).join('\n')}\n\n_These are review proposals based only on an exact source-path mention. Nothing was written to the brain._`
|
|
315
|
+
: '';
|
|
301
316
|
return toContent({
|
|
302
|
-
blocks: [{ kind: 'text', text: `${graphMarkdown}\n\n---\n\n# Project Brain - ${deep_history ? 'decisions and history' : 'current decisions and corrections'}\n\n${brainMarkdown}` }],
|
|
317
|
+
blocks: [{ kind: 'text', text: `${graphMarkdown}\n\n---\n\n# Project Brain - ${deep_history ? 'decisions and history' : 'current decisions and corrections'}\n\n${brainMarkdown}${proposalMarkdown}` }],
|
|
303
318
|
isError: brainResult.isError,
|
|
304
|
-
structured: { projectGraph: graphResult, brainContext: brainResult.context || null, deepHistory: deep_history === true },
|
|
319
|
+
structured: { projectGraph: graphResult, brainContext: brainResult.context || null, evidenceLinkProposals, deepHistory: deep_history === true },
|
|
305
320
|
});
|
|
306
321
|
});
|
|
307
322
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: KLYPIX Project Map
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
pull_request:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: klypix-project-map-${{ github.workflow }}-${{ github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
env:
|
|
17
|
+
GRAPHIFY_VERSION: "0.9.33"
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
project-map:
|
|
21
|
+
name: Generate bounded map artifact
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
timeout-minutes: 20
|
|
24
|
+
steps:
|
|
25
|
+
- name: Check out repository
|
|
26
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
27
|
+
|
|
28
|
+
- name: Set up uv
|
|
29
|
+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
|
|
33
|
+
- name: Generate code-only graph
|
|
34
|
+
run: uvx --from "graphifyy==${GRAPHIFY_VERSION}" graphify extract . --code-only
|
|
35
|
+
|
|
36
|
+
- name: Validate the generated contract
|
|
37
|
+
run: node -e "const fs=require('fs'); const p='graphify-out/graph.json'; const g=JSON.parse(fs.readFileSync(p,'utf8')); if(!Array.isArray(g.nodes)||!(Array.isArray(g.edges)||Array.isArray(g.links))) throw Error('unsupported graph contract'); console.log('Project Map:',g.nodes.length,'nodes');"
|
|
38
|
+
|
|
39
|
+
- name: Upload Project Map artifact
|
|
40
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
41
|
+
with:
|
|
42
|
+
name: klypix-project-map-${{ github.sha }}
|
|
43
|
+
path: graphify-out/
|
|
44
|
+
if-no-files-found: error
|
|
45
|
+
retention-days: 14
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klypix-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.55.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",
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"klypix-write": "bin/klypix-write.mjs",
|
|
44
44
|
"klypix-append": "bin/klypix-append.mjs",
|
|
45
45
|
"klypix-install": "bin/klypix-install.mjs",
|
|
46
|
-
"klypix-uninstall": "bin/klypix-uninstall.mjs"
|
|
46
|
+
"klypix-uninstall": "bin/klypix-uninstall.mjs",
|
|
47
|
+
"klypix-project-map": "bin/klypix-project-map.mjs"
|
|
47
48
|
},
|
|
48
49
|
"main": "index.mjs",
|
|
49
50
|
"exports": {
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
},
|
|
76
77
|
"scripts": {
|
|
77
78
|
"test:project-graph": "node test/project-graph.mjs",
|
|
78
|
-
"test": "node test/project-graph.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/agent-presence.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/context-gateway.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/evidence-anchors.mjs && node test/presence-visibility.mjs && node test/merge-brains.mjs && node test/concurrent-writes.mjs && node test/a2a-smoke.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/git-tools.mjs && node test/uninstall.mjs",
|
|
79
|
+
"test": "node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/agent-presence.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/context-gateway.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/brief-and-recall.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/evidence-anchors.mjs && node test/presence-visibility.mjs && node test/merge-brains.mjs && node test/concurrent-writes.mjs && node test/a2a-smoke.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/git-tools.mjs && node test/uninstall.mjs",
|
|
79
80
|
"test:memory": "node test/memory-runtime.mjs",
|
|
80
81
|
"test:memory:soak": "node --expose-gc test/memory-soak.mjs",
|
|
81
82
|
"runtime": "node bin/klypix-runtime.mjs"
|
package/src/project-graph.mjs
CHANGED
|
@@ -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 =
|
|
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,93 @@ 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
|
+
|
|
346
|
+
function textContainsExactSourcePath(text, sourceFile) {
|
|
347
|
+
const haystack = String(text || '').replace(/\\/g, '/').toLocaleLowerCase('en-US');
|
|
348
|
+
const needle = String(sourceFile || '').replace(/\\/g, '/').toLocaleLowerCase('en-US');
|
|
349
|
+
if (!needle || !haystack.includes(needle)) return false;
|
|
350
|
+
const escaped = needle.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
351
|
+
return new RegExp(`(^|[\\s\`'"([{<])${escaped}($|[\\s\`'"\\])}>.,;:#])`, 'i').test(haystack);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Deterministic proposals only. These are never persisted and never imply
|
|
356
|
+
* causality: an exact project-relative source path must appear in the brain
|
|
357
|
+
* card text before KLYPIX will suggest reviewing the pair.
|
|
358
|
+
*/
|
|
359
|
+
export function suggestProjectGraphBrainLinks(graphResult, brainContext, limit = 40) {
|
|
360
|
+
if (graphResult?.status !== 'ready' || !Array.isArray(graphResult.nodes)) return [];
|
|
361
|
+
const hits = Array.isArray(brainContext?.hits) ? brainContext.hits : [];
|
|
362
|
+
const proposals = [];
|
|
363
|
+
const seen = new Set();
|
|
364
|
+
for (const node of graphResult.nodes) {
|
|
365
|
+
if (!node?.sourceFile) continue;
|
|
366
|
+
for (const hit of hits) {
|
|
367
|
+
const texts = [hit?.text, hit?.correctedBy].filter(Boolean);
|
|
368
|
+
if (!texts.some(text => textContainsExactSourcePath(text, node.sourceFile))) continue;
|
|
369
|
+
const key = `${hit.id}\u0000${node.id}\u0000${node.sourceFile}`;
|
|
370
|
+
if (seen.has(key)) continue;
|
|
371
|
+
seen.add(key);
|
|
372
|
+
proposals.push({
|
|
373
|
+
brainCardId: hit.id,
|
|
374
|
+
brainArea: hit.area || 'Notes',
|
|
375
|
+
nodeId: node.id,
|
|
376
|
+
nodeLabel: node.label || node.id,
|
|
377
|
+
sourceFile: node.sourceFile,
|
|
378
|
+
basis: 'exact-source-path',
|
|
379
|
+
status: 'review-proposal',
|
|
380
|
+
});
|
|
381
|
+
if (proposals.length >= Math.max(1, Math.min(100, Number(limit) || 40))) return proposals;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return proposals;
|
|
385
|
+
}
|
|
386
|
+
|
|
314
387
|
export function projectGraphContextMarkdown(result) {
|
|
315
388
|
if (result.status === 'missing') {
|
|
316
389
|
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
390
|
}
|
|
318
391
|
const lines = result.nodes.map(node => {
|
|
319
|
-
const where = node.sourceFile ? `
|
|
320
|
-
return `- **${node.label || node.id}** (${node.kind || 'symbol'})${where}
|
|
392
|
+
const where = node.sourceFile ? ` in \`${node.sourceFile}${node.sourceLocation ? `:${node.sourceLocation}` : ''}\`` : '';
|
|
393
|
+
return `- **${node.label || node.id}** (${node.kind || 'symbol'})${where}; id \`${node.id}\``;
|
|
321
394
|
});
|
|
322
395
|
const edgeLines = result.edges.slice(0, 80).map(edge =>
|
|
323
|
-
`- \`${edge.source}\`
|
|
396
|
+
`- \`${edge.source}\` --${edge.relation}--> \`${edge.target}\` [${edge.confidence}]`);
|
|
324
397
|
const warnings = [];
|
|
325
398
|
if (result.diagnostics?.unsafeSourcePaths) warnings.push(`${result.diagnostics.unsafeSourcePaths} unsafe/out-of-root source path(s) were withheld`);
|
|
326
399
|
if (result.diagnostics?.danglingEdges) warnings.push(`${result.diagnostics.danglingEdges} dangling edge(s) were ignored`);
|
|
327
400
|
return [
|
|
328
|
-
'# Project Map
|
|
329
|
-
`Provider artifact: \`${result.artifact.graphJson}
|
|
401
|
+
'# Project Map: code evidence',
|
|
402
|
+
`Provider artifact: \`${result.artifact.graphJson}\`; ${result.counts.graphNodes.toLocaleString()} nodes; ${result.counts.graphEdges.toLocaleString()} edges; query \`${result.query || '(overview)'}\`.`,
|
|
330
403
|
warnings.length ? `Safety note: ${warnings.join('; ')}.` : '',
|
|
404
|
+
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.` : '',
|
|
405
|
+
result.change?.addedNodes?.length ? `Added in bounded result: ${result.change.addedNodes.slice(0, 20).map(node => `\`${node.label || node.id}\``).join(', ')}.` : '',
|
|
406
|
+
result.change?.removedNodes?.length ? `Removed from bounded result: ${result.change.removedNodes.slice(0, 20).map(node => `\`${node.label || node.id}\``).join(', ')}.` : '',
|
|
331
407
|
'## Relevant code nodes',
|
|
332
408
|
lines.join('\n') || '_No matching code nodes._',
|
|
333
409
|
edgeLines.length ? '## Relationships\n' + edgeLines.join('\n') : '',
|