@kage-core/kage-graph-mcp 1.1.1 → 1.1.3
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 +21 -2
- package/dist/cli.js +96 -0
- package/dist/index.js +54 -0
- package/dist/kernel.js +240 -7
- package/package.json +1 -1
- package/viewer/app.js +168 -37
- package/viewer/styles.css +7 -7
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ kage init --project /path/to/repo
|
|
|
27
27
|
kage policy --project /path/to/repo
|
|
28
28
|
kage doctor --project /path/to/repo
|
|
29
29
|
kage index --project /path/to/repo
|
|
30
|
+
kage refresh --project /path/to/repo
|
|
30
31
|
kage branch --project /path/to/repo
|
|
31
32
|
kage code-graph --project /path/to/repo
|
|
32
33
|
kage code-graph "createApp routes tests" --project /path/to/repo
|
|
@@ -45,6 +46,8 @@ kage learn --project /path/to/repo --learning "Decision: use kage_learn for actu
|
|
|
45
46
|
kage feedback --project /path/to/repo --packet <approved-packet-id> --kind stale
|
|
46
47
|
kage capture --project /path/to/repo --type runbook --title "Webhook tests" --body "Run pnpm test:api -- webhooks."
|
|
47
48
|
kage propose --project /path/to/repo --from-diff
|
|
49
|
+
kage pr summarize --project /path/to/repo
|
|
50
|
+
kage pr check --project /path/to/repo
|
|
48
51
|
kage review-artifact --project /path/to/repo
|
|
49
52
|
kage registry --project /path/to/repo
|
|
50
53
|
kage marketplace --project /path/to/repo
|
|
@@ -57,6 +60,7 @@ kage layered-recall "how do I run tests" --project /path/to/repo --org acme --gl
|
|
|
57
60
|
kage global build --project /path/to/repo --org acme
|
|
58
61
|
kage review --project /path/to/repo
|
|
59
62
|
kage validate --project /path/to/repo
|
|
63
|
+
kage upgrade --dry-run
|
|
60
64
|
```
|
|
61
65
|
|
|
62
66
|
`kage init` is the first-run command. It creates `.agent_memory/`, migrates
|
|
@@ -121,6 +125,16 @@ and parser coverage, code graph counts, evidence coverage, approved vs pending
|
|
|
121
125
|
memory, validation status, estimated tokens saved per recall, duplicate
|
|
122
126
|
candidates, average memory quality, and a readiness score.
|
|
123
127
|
|
|
128
|
+
Use `kage refresh --project <repo>` or the `kage_refresh` MCP tool after
|
|
129
|
+
meaningful file changes. Refresh rebuilds indexes, code graph, memory graph,
|
|
130
|
+
metrics, and stale-memory metadata. Memory is marked stale when status or
|
|
131
|
+
feedback says it is stale, its TTL expires, or grounded paths disappear.
|
|
132
|
+
|
|
133
|
+
Use `kage pr summarize --project <repo>` / `kage_pr_summarize` before handoff to
|
|
134
|
+
write branch review metadata and repo-local change memory from the git diff.
|
|
135
|
+
Use `kage pr check --project <repo>` / `kage_pr_check` before merge to verify
|
|
136
|
+
validation, graph freshness, stale packets, and memory packet changes.
|
|
137
|
+
|
|
124
138
|
Review artifacts include memory quality reasons, risks, duplicate candidates,
|
|
125
139
|
and estimated token savings for legacy pending/quarantine packets and promotion
|
|
126
140
|
review.
|
|
@@ -185,6 +199,9 @@ Local repo tools:
|
|
|
185
199
|
- `kage_recall`
|
|
186
200
|
- `kage_code_graph`
|
|
187
201
|
- `kage_metrics`
|
|
202
|
+
- `kage_refresh`
|
|
203
|
+
- `kage_pr_summarize`
|
|
204
|
+
- `kage_pr_check`
|
|
188
205
|
- `kage_quality`
|
|
189
206
|
- `kage_benchmark`
|
|
190
207
|
- `kage_setup_agent`
|
|
@@ -267,8 +284,10 @@ Before code changes or repo-specific answers:
|
|
|
267
284
|
2. Call `kage_recall` with the user task as the query.
|
|
268
285
|
3. Call `kage_graph` with the user task as the query.
|
|
269
286
|
4. Capture reusable learnings with `kage_learn` or `kage_capture`.
|
|
270
|
-
5.
|
|
271
|
-
6.
|
|
287
|
+
5. After meaningful file changes, call `kage_refresh`.
|
|
288
|
+
6. Before finishing changed-file tasks, call `kage_propose_from_diff` or `kage_pr_summarize`.
|
|
289
|
+
7. Before merge, call `kage_pr_check`.
|
|
290
|
+
8. Never publish or promote org/global memory automatically.
|
|
272
291
|
```
|
|
273
292
|
|
|
274
293
|
Run `kage setup verify-agent --agent codex --project <repo>` after setup. The
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
4
5
|
const promises_1 = require("node:readline/promises");
|
|
5
6
|
const node_process_1 = require("node:process");
|
|
6
7
|
const daemon_js_1 = require("./daemon.js");
|
|
@@ -22,6 +23,10 @@ Usage:
|
|
|
22
23
|
kage daemon status --project <dir> [--json]
|
|
23
24
|
kage daemon doctor --project <dir> [--json]
|
|
24
25
|
kage viewer --project <dir> [--port 3113]
|
|
26
|
+
kage refresh --project <dir> [--json]
|
|
27
|
+
kage pr summarize --project <dir> [--json]
|
|
28
|
+
kage pr check --project <dir> [--json]
|
|
29
|
+
kage upgrade [--dry-run]
|
|
25
30
|
kage branch --project <dir> [--json]
|
|
26
31
|
kage metrics --project <dir> [--json]
|
|
27
32
|
kage quality --project <dir> [--json]
|
|
@@ -296,6 +301,97 @@ async function main() {
|
|
|
296
301
|
await (0, daemon_js_1.startViewer)(projectArg(args), { port: numberArg(args, "--port", 3113) });
|
|
297
302
|
return;
|
|
298
303
|
}
|
|
304
|
+
if (command === "refresh") {
|
|
305
|
+
const result = (0, kernel_js_1.refreshProject)(projectArg(args));
|
|
306
|
+
if (args.includes("--json")) {
|
|
307
|
+
console.log(JSON.stringify(result, null, 2));
|
|
308
|
+
if (!result.ok)
|
|
309
|
+
process.exit(2);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
console.log(`Refreshed ${result.project_dir}`);
|
|
313
|
+
console.log(`Packets indexed: ${result.index.packets}`);
|
|
314
|
+
console.log(`Packet metadata updated: ${result.updated_packets}`);
|
|
315
|
+
console.log(`Code graph: ${result.code_graph.files} files, ${result.code_graph.symbols} symbols, ${result.code_graph.imports} imports, ${result.code_graph.calls} calls`);
|
|
316
|
+
console.log(`Memory graph: ${result.memory_graph.entities} entities, ${result.memory_graph.edges} edges, ${result.memory_graph.episodes} episodes`);
|
|
317
|
+
console.log(`Stale packets: ${result.stale_packets.length}`);
|
|
318
|
+
for (const packet of result.stale_packets.slice(0, 8)) {
|
|
319
|
+
console.log(` - ${packet.title} (${packet.id}): ${packet.reasons.join("; ")}`);
|
|
320
|
+
}
|
|
321
|
+
console.log(result.validation.ok ? "Validation: passed" : "Validation: failed");
|
|
322
|
+
if (result.validation.errors.length)
|
|
323
|
+
console.log(`Errors:\n${result.validation.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
324
|
+
if (result.validation.warnings.length)
|
|
325
|
+
console.log(`Warnings:\n${result.validation.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
326
|
+
console.log(`Next actions:\n${result.next_actions.map((action) => ` - ${action}`).join("\n")}`);
|
|
327
|
+
if (!result.ok)
|
|
328
|
+
process.exit(2);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
if (command === "pr") {
|
|
332
|
+
const action = args[1];
|
|
333
|
+
if (action === "summarize") {
|
|
334
|
+
const result = (0, kernel_js_1.prSummarize)(projectArg(args));
|
|
335
|
+
if (args.includes("--json")) {
|
|
336
|
+
console.log(JSON.stringify(result, null, 2));
|
|
337
|
+
if (!result.ok)
|
|
338
|
+
process.exit(2);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
console.log(`PR summary for ${result.project_dir}`);
|
|
342
|
+
console.log(`Branch: ${result.branch ?? "(detached)"}`);
|
|
343
|
+
console.log(`Changed files: ${result.changed_files.join(", ") || "(none)"}`);
|
|
344
|
+
if (result.diff_memory_packet_id)
|
|
345
|
+
console.log(`Repo memory: ${result.diff_memory_packet_id}`);
|
|
346
|
+
if (result.branch_summary_path)
|
|
347
|
+
console.log(`Branch summary: ${result.branch_summary_path}`);
|
|
348
|
+
if (result.review_artifact_path)
|
|
349
|
+
console.log(`Review artifact: ${result.review_artifact_path}`);
|
|
350
|
+
if (result.warnings.length)
|
|
351
|
+
console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
352
|
+
if (result.errors.length)
|
|
353
|
+
console.log(`Errors:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
354
|
+
if (!result.ok)
|
|
355
|
+
process.exit(2);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (action === "check") {
|
|
359
|
+
const result = (0, kernel_js_1.prCheck)(projectArg(args));
|
|
360
|
+
if (args.includes("--json")) {
|
|
361
|
+
console.log(JSON.stringify(result, null, 2));
|
|
362
|
+
if (!result.ok)
|
|
363
|
+
process.exit(2);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
console.log(`PR memory check for ${result.project_dir}`);
|
|
367
|
+
console.log(`Branch: ${result.branch ?? "(detached)"}`);
|
|
368
|
+
console.log(`Changed files: ${result.changed_files.length}`);
|
|
369
|
+
console.log(`Memory packet changes: ${result.memory_packet_changes.length}`);
|
|
370
|
+
console.log(`Code graph current: ${result.code_graph_current ? "yes" : "no"}`);
|
|
371
|
+
console.log(`Memory graph current: ${result.memory_graph_current ? "yes" : "no"}`);
|
|
372
|
+
console.log(`Stale packets: ${result.stale_packets.length}`);
|
|
373
|
+
if (result.warnings.length)
|
|
374
|
+
console.log(`Warnings:\n${result.warnings.map((warning) => ` - ${warning}`).join("\n")}`);
|
|
375
|
+
if (result.errors.length)
|
|
376
|
+
console.log(`Errors:\n${result.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
377
|
+
console.log(`Required actions:\n${result.required_actions.map((action) => ` - ${action}`).join("\n")}`);
|
|
378
|
+
if (!result.ok)
|
|
379
|
+
process.exit(2);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
usage();
|
|
383
|
+
}
|
|
384
|
+
if (command === "upgrade") {
|
|
385
|
+
const commandLine = "npm install -g @kage-core/kage-graph-mcp@latest";
|
|
386
|
+
if (args.includes("--dry-run")) {
|
|
387
|
+
console.log(commandLine);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
console.log(`Running: ${commandLine}`);
|
|
391
|
+
(0, node_child_process_1.execFileSync)("npm", ["install", "-g", "@kage-core/kage-graph-mcp@latest"], { stdio: "inherit" });
|
|
392
|
+
console.log("Kage upgraded. Restart Codex or Claude Code so the MCP process reloads the new package.");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
299
395
|
if (command === "graph") {
|
|
300
396
|
const query = firstPositional(args);
|
|
301
397
|
if (query) {
|
package/dist/index.js
CHANGED
|
@@ -154,6 +154,39 @@ function listTools() {
|
|
|
154
154
|
required: ["project_dir"],
|
|
155
155
|
},
|
|
156
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: "kage_refresh",
|
|
159
|
+
description: "Rebuild repo indexes, code graph, memory graph, metrics, and stale-memory metadata. Agents should run this after meaningful file changes and before PR checks.",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
type: "object",
|
|
162
|
+
properties: {
|
|
163
|
+
project_dir: { type: "string" },
|
|
164
|
+
},
|
|
165
|
+
required: ["project_dir"],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "kage_pr_summarize",
|
|
170
|
+
description: "Create a PR/branch memory summary from local git diff metadata and write repo-local change memory. Use when a branch is ready to hand off.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
project_dir: { type: "string" },
|
|
175
|
+
},
|
|
176
|
+
required: ["project_dir"],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "kage_pr_check",
|
|
181
|
+
description: "Check whether repo memory, code graph, memory graph, and stale-memory state are ready for merge.",
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
project_dir: { type: "string" },
|
|
186
|
+
},
|
|
187
|
+
required: ["project_dir"],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
157
190
|
{
|
|
158
191
|
name: "kage_quality",
|
|
159
192
|
description: "Return memory quality metrics: useful memory ratio, duplicate burden, stale/wrong feedback, evidence coverage, path grounding, and review queue size.",
|
|
@@ -595,6 +628,27 @@ async function callTool(name, args) {
|
|
|
595
628
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
596
629
|
};
|
|
597
630
|
}
|
|
631
|
+
if (name === "kage_refresh") {
|
|
632
|
+
const result = (0, kernel_js_1.refreshProject)(String(args?.project_dir ?? ""));
|
|
633
|
+
return {
|
|
634
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
635
|
+
isError: !result.ok,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (name === "kage_pr_summarize") {
|
|
639
|
+
const result = (0, kernel_js_1.prSummarize)(String(args?.project_dir ?? ""));
|
|
640
|
+
return {
|
|
641
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
642
|
+
isError: !result.ok,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (name === "kage_pr_check") {
|
|
646
|
+
const result = (0, kernel_js_1.prCheck)(String(args?.project_dir ?? ""));
|
|
647
|
+
return {
|
|
648
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
649
|
+
isError: !result.ok,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
598
652
|
if (name === "kage_quality") {
|
|
599
653
|
const result = (0, kernel_js_1.qualityReport)(String(args?.project_dir ?? ""));
|
|
600
654
|
return {
|
package/dist/kernel.js
CHANGED
|
@@ -66,6 +66,7 @@ exports.buildCodeGraph = buildCodeGraph;
|
|
|
66
66
|
exports.buildKnowledgeGraph = buildKnowledgeGraph;
|
|
67
67
|
exports.buildIndexes = buildIndexes;
|
|
68
68
|
exports.indexProject = indexProject;
|
|
69
|
+
exports.refreshProject = refreshProject;
|
|
69
70
|
exports.installAgentPolicy = installAgentPolicy;
|
|
70
71
|
exports.recall = recall;
|
|
71
72
|
exports.queryCodeGraph = queryCodeGraph;
|
|
@@ -86,6 +87,8 @@ exports.distillSession = distillSession;
|
|
|
86
87
|
exports.proposeFromDiff = proposeFromDiff;
|
|
87
88
|
exports.buildBranchOverlay = buildBranchOverlay;
|
|
88
89
|
exports.createReviewArtifact = createReviewArtifact;
|
|
90
|
+
exports.prSummarize = prSummarize;
|
|
91
|
+
exports.prCheck = prCheck;
|
|
89
92
|
exports.exportPublicBundle = exportPublicBundle;
|
|
90
93
|
exports.orgStatus = orgStatus;
|
|
91
94
|
exports.orgUploadPacket = orgUploadPacket;
|
|
@@ -447,12 +450,41 @@ function packetFeedbackScore(packet) {
|
|
|
447
450
|
const quality = packet.quality;
|
|
448
451
|
return Number(quality.votes_up ?? 0) * 2 - Number(quality.votes_down ?? 0) * 3 - Number(quality.reports_stale ?? 0) * 4;
|
|
449
452
|
}
|
|
453
|
+
function meaningfulMemoryPath(path) {
|
|
454
|
+
return path !== "root" && path !== "." && !isNoisePath(path);
|
|
455
|
+
}
|
|
456
|
+
function staleMemoryReasons(projectDir, packet) {
|
|
457
|
+
const reasons = [];
|
|
458
|
+
const quality = packet.quality;
|
|
459
|
+
const freshness = packet.freshness;
|
|
460
|
+
if (packet.status === "deprecated" || packet.status === "superseded") {
|
|
461
|
+
reasons.push(`packet status is ${packet.status}`);
|
|
462
|
+
}
|
|
463
|
+
if (Number(quality.reports_stale ?? 0) > 0) {
|
|
464
|
+
reasons.push("user or agent reported this memory stale");
|
|
465
|
+
}
|
|
466
|
+
const ttlDays = Number(freshness.ttl_days ?? freshness.ttlDays ?? 0);
|
|
467
|
+
const verifiedAt = Date.parse(String(freshness.last_verified_at ?? packet.updated_at ?? packet.created_at));
|
|
468
|
+
if (Number.isFinite(ttlDays) && ttlDays > 0 && Number.isFinite(verifiedAt)) {
|
|
469
|
+
const ageDays = (Date.now() - verifiedAt) / (1000 * 60 * 60 * 24);
|
|
470
|
+
if (ageDays > ttlDays)
|
|
471
|
+
reasons.push(`freshness ttl expired (${Math.floor(ageDays)}d old, ttl ${ttlDays}d)`);
|
|
472
|
+
}
|
|
473
|
+
const paths = packet.paths.filter(meaningfulMemoryPath);
|
|
474
|
+
const missingPaths = paths.filter((path) => !(0, node_fs_1.existsSync)((0, node_path_1.join)(projectDir, path)));
|
|
475
|
+
if (paths.length > 0 && missingPaths.length === paths.length) {
|
|
476
|
+
reasons.push(`all referenced paths are missing: ${missingPaths.slice(0, 4).join(", ")}`);
|
|
477
|
+
}
|
|
478
|
+
else if (missingPaths.length > 0) {
|
|
479
|
+
reasons.push(`some referenced paths are missing: ${missingPaths.slice(0, 4).join(", ")}`);
|
|
480
|
+
}
|
|
481
|
+
return unique(reasons);
|
|
482
|
+
}
|
|
450
483
|
function classifyPacket(projectDir, packet) {
|
|
451
484
|
const quality = evaluateMemoryQuality(projectDir, packet);
|
|
452
485
|
const score = Number(quality.score);
|
|
453
486
|
const duplicates = quality.duplicate_candidates;
|
|
454
|
-
|
|
455
|
-
if (Number(q.reports_stale ?? 0) > 0 || packet.status === "deprecated" || packet.status === "superseded")
|
|
487
|
+
if (staleMemoryReasons(projectDir, packet).length)
|
|
456
488
|
return "stale";
|
|
457
489
|
if (duplicates.length)
|
|
458
490
|
return "duplicate";
|
|
@@ -522,11 +554,17 @@ function evaluateMemoryQuality(projectDir, packet) {
|
|
|
522
554
|
score -= 18;
|
|
523
555
|
risks.push("possible duplicate memory");
|
|
524
556
|
}
|
|
557
|
+
const staleReasons = staleMemoryReasons(projectDir, packet);
|
|
558
|
+
if (staleReasons.length) {
|
|
559
|
+
score -= 22;
|
|
560
|
+
risks.push(...staleReasons);
|
|
561
|
+
}
|
|
525
562
|
return {
|
|
526
563
|
score: Math.max(0, Math.min(100, score)),
|
|
527
564
|
reasons,
|
|
528
565
|
risks,
|
|
529
566
|
duplicate_candidates: duplicates,
|
|
567
|
+
stale_reasons: staleReasons,
|
|
530
568
|
estimated_tokens_saved: Math.max(40, estimateTokens(packet.body) * 2),
|
|
531
569
|
};
|
|
532
570
|
}
|
|
@@ -769,6 +807,17 @@ function loadPacketsFromDir(dir) {
|
|
|
769
807
|
.sort()
|
|
770
808
|
.map((name) => readJson((0, node_path_1.join)(dir, name)));
|
|
771
809
|
}
|
|
810
|
+
function loadPacketEntriesFromDir(dir) {
|
|
811
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
812
|
+
return [];
|
|
813
|
+
return (0, node_fs_1.readdirSync)(dir)
|
|
814
|
+
.filter((name) => name.endsWith(".json"))
|
|
815
|
+
.sort()
|
|
816
|
+
.map((name) => {
|
|
817
|
+
const path = (0, node_path_1.join)(dir, name);
|
|
818
|
+
return { path, packet: readJson(path) };
|
|
819
|
+
});
|
|
820
|
+
}
|
|
772
821
|
function loadApprovedPackets(projectDir) {
|
|
773
822
|
return loadPacketsFromDir(packetsDir(projectDir)).filter((packet) => packet.status === "approved");
|
|
774
823
|
}
|
|
@@ -837,14 +886,15 @@ function isNoisePath(filePath) {
|
|
|
837
886
|
function parsePorcelainStatus(status) {
|
|
838
887
|
return unique(status
|
|
839
888
|
.split(/\r?\n/)
|
|
840
|
-
.map(
|
|
841
|
-
const raw = line.length > 2 && line[2] === " " ? line.slice(3) : line.slice(2);
|
|
842
|
-
return raw.trim();
|
|
843
|
-
})
|
|
889
|
+
.map(parsePorcelainPath)
|
|
844
890
|
.map((path) => path.replace(/^.* -> /, ""))
|
|
845
891
|
.filter(Boolean)
|
|
846
892
|
.filter((path) => !shouldSkipRepoMemoryPath(path))).sort();
|
|
847
893
|
}
|
|
894
|
+
function parsePorcelainPath(line) {
|
|
895
|
+
const raw = line.length > 2 && line[2] === " " ? line.slice(3) : line.slice(2);
|
|
896
|
+
return raw.trim();
|
|
897
|
+
}
|
|
848
898
|
function shouldSkipRepoMemoryPath(relativePath) {
|
|
849
899
|
return isNoisePath(relativePath) || shouldSkipCodePath(relativePath);
|
|
850
900
|
}
|
|
@@ -2342,6 +2392,104 @@ function indexProject(projectDir) {
|
|
|
2342
2392
|
policyPath: (0, node_path_1.relative)(projectDir, policy.path),
|
|
2343
2393
|
};
|
|
2344
2394
|
}
|
|
2395
|
+
function staleSuggestedAction(reasons) {
|
|
2396
|
+
if (reasons.some((reason) => reason.includes("status is")))
|
|
2397
|
+
return "mark_stale";
|
|
2398
|
+
if (reasons.some((reason) => reason.includes("missing")))
|
|
2399
|
+
return "update";
|
|
2400
|
+
if (reasons.some((reason) => reason.includes("reported")))
|
|
2401
|
+
return "supersede";
|
|
2402
|
+
return "verify";
|
|
2403
|
+
}
|
|
2404
|
+
function staleFinding(packet, reasons) {
|
|
2405
|
+
return {
|
|
2406
|
+
id: packet.id,
|
|
2407
|
+
title: packet.title,
|
|
2408
|
+
type: packet.type,
|
|
2409
|
+
status: packet.status,
|
|
2410
|
+
paths: packet.paths,
|
|
2411
|
+
reasons,
|
|
2412
|
+
suggested_action: staleSuggestedAction(reasons),
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
function refreshPacketStaleness(projectDir) {
|
|
2416
|
+
const findings = [];
|
|
2417
|
+
let updated = 0;
|
|
2418
|
+
for (const entry of loadPacketEntriesFromDir(packetsDir(projectDir))) {
|
|
2419
|
+
const reasons = staleMemoryReasons(projectDir, entry.packet);
|
|
2420
|
+
const oldQuality = entry.packet.quality;
|
|
2421
|
+
const oldFreshness = entry.packet.freshness;
|
|
2422
|
+
let nextQuality;
|
|
2423
|
+
if (reasons.length) {
|
|
2424
|
+
const finding = staleFinding(entry.packet, reasons);
|
|
2425
|
+
findings.push(finding);
|
|
2426
|
+
nextQuality = {
|
|
2427
|
+
...oldQuality,
|
|
2428
|
+
stale: true,
|
|
2429
|
+
stale_reasons: reasons,
|
|
2430
|
+
suggested_action: finding.suggested_action,
|
|
2431
|
+
};
|
|
2432
|
+
}
|
|
2433
|
+
else {
|
|
2434
|
+
const { stale: _stale, stale_reasons: _staleReasons, suggested_action: _suggestedAction, ...rest } = oldQuality;
|
|
2435
|
+
nextQuality = rest;
|
|
2436
|
+
}
|
|
2437
|
+
const nextFreshness = oldFreshness;
|
|
2438
|
+
const changed = JSON.stringify(oldQuality) !== JSON.stringify(nextQuality)
|
|
2439
|
+
|| JSON.stringify(oldFreshness) !== JSON.stringify(nextFreshness);
|
|
2440
|
+
if (changed) {
|
|
2441
|
+
writeJson(entry.path, {
|
|
2442
|
+
...entry.packet,
|
|
2443
|
+
freshness: nextFreshness,
|
|
2444
|
+
quality: nextQuality,
|
|
2445
|
+
updated_at: nowIso(),
|
|
2446
|
+
});
|
|
2447
|
+
updated += 1;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return { findings, updated };
|
|
2451
|
+
}
|
|
2452
|
+
function refreshProject(projectDir) {
|
|
2453
|
+
const index = indexProject(projectDir);
|
|
2454
|
+
const stale = refreshPacketStaleness(projectDir);
|
|
2455
|
+
const indexes = stale.updated > 0 ? buildIndexes(projectDir).map((path) => (0, node_path_1.relative)(projectDir, path)) : index.indexes;
|
|
2456
|
+
const validation = validateProject(projectDir);
|
|
2457
|
+
const metrics = kageMetrics(projectDir);
|
|
2458
|
+
const nextActions = [];
|
|
2459
|
+
if (stale.findings.length)
|
|
2460
|
+
nextActions.push("Update, verify, or supersede stale repo memories before relying on them.");
|
|
2461
|
+
if (!validation.ok)
|
|
2462
|
+
nextActions.push("Fix validation errors before merging or sharing memory.");
|
|
2463
|
+
if (validation.warnings.length)
|
|
2464
|
+
nextActions.push("Review validation warnings for grounding, indexes, or generated artifacts.");
|
|
2465
|
+
if (!nextActions.length)
|
|
2466
|
+
nextActions.push("Repo memory, code graph, and indexes are current.");
|
|
2467
|
+
return {
|
|
2468
|
+
ok: validation.ok,
|
|
2469
|
+
project_dir: projectDir,
|
|
2470
|
+
generated_at: nowIso(),
|
|
2471
|
+
index,
|
|
2472
|
+
validation,
|
|
2473
|
+
metrics,
|
|
2474
|
+
stale_packets: stale.findings,
|
|
2475
|
+
updated_packets: stale.updated,
|
|
2476
|
+
indexes,
|
|
2477
|
+
code_graph: {
|
|
2478
|
+
files: metrics.code_graph.files,
|
|
2479
|
+
symbols: metrics.code_graph.symbols,
|
|
2480
|
+
imports: metrics.code_graph.imports,
|
|
2481
|
+
calls: metrics.code_graph.calls,
|
|
2482
|
+
routes: metrics.code_graph.routes,
|
|
2483
|
+
tests: metrics.code_graph.tests,
|
|
2484
|
+
},
|
|
2485
|
+
memory_graph: {
|
|
2486
|
+
entities: metrics.memory_graph.entities,
|
|
2487
|
+
edges: metrics.memory_graph.edges,
|
|
2488
|
+
episodes: metrics.memory_graph.episodes,
|
|
2489
|
+
},
|
|
2490
|
+
next_actions: nextActions,
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2345
2493
|
function installAgentPolicy(projectDir) {
|
|
2346
2494
|
const agentsPath = (0, node_path_1.join)(projectDir, "AGENTS.md");
|
|
2347
2495
|
const claudePath = (0, node_path_1.join)(projectDir, "CLAUDE.md");
|
|
@@ -3847,7 +3995,7 @@ function proposeFromDiff(projectDir) {
|
|
|
3847
3995
|
}
|
|
3848
3996
|
function buildBranchOverlay(projectDir) {
|
|
3849
3997
|
ensureMemoryDirs(projectDir);
|
|
3850
|
-
const status = readGit(projectDir, ["status", "--porcelain"]) ?? "";
|
|
3998
|
+
const status = readGit(projectDir, ["status", "--porcelain", "-uall"]) ?? "";
|
|
3851
3999
|
const overlay = {
|
|
3852
4000
|
schema_version: 1,
|
|
3853
4001
|
project_dir: projectDir,
|
|
@@ -3925,6 +4073,91 @@ function createReviewArtifact(projectDir) {
|
|
|
3925
4073
|
(0, node_fs_1.writeFileSync)(path, `${lines.join("\n").trim()}\n`, "utf8");
|
|
3926
4074
|
return { path, pending: pending.length };
|
|
3927
4075
|
}
|
|
4076
|
+
function graphIsCurrent(projectDir, relativePath, head) {
|
|
4077
|
+
const path = (0, node_path_1.join)(projectDir, relativePath);
|
|
4078
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
4079
|
+
return false;
|
|
4080
|
+
if (!head)
|
|
4081
|
+
return true;
|
|
4082
|
+
try {
|
|
4083
|
+
const graph = readJson(path);
|
|
4084
|
+
return graph.repo_state?.head === head;
|
|
4085
|
+
}
|
|
4086
|
+
catch {
|
|
4087
|
+
return false;
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
function prSummarize(projectDir) {
|
|
4091
|
+
ensureMemoryDirs(projectDir);
|
|
4092
|
+
const proposal = proposeFromDiff(projectDir);
|
|
4093
|
+
const artifact = createReviewArtifact(projectDir);
|
|
4094
|
+
const validation = validateProject(projectDir);
|
|
4095
|
+
const warnings = [...validation.warnings];
|
|
4096
|
+
if (!proposal.ok)
|
|
4097
|
+
warnings.push(...proposal.errors);
|
|
4098
|
+
return {
|
|
4099
|
+
ok: proposal.ok && validation.ok,
|
|
4100
|
+
project_dir: projectDir,
|
|
4101
|
+
branch: gitBranch(projectDir),
|
|
4102
|
+
head: gitHead(projectDir),
|
|
4103
|
+
changed_files: proposal.changedFiles,
|
|
4104
|
+
diff_memory_packet_id: proposal.packet?.id,
|
|
4105
|
+
diff_memory_packet_path: proposal.packetPath,
|
|
4106
|
+
branch_summary_path: proposal.path,
|
|
4107
|
+
review_artifact_path: artifact.path,
|
|
4108
|
+
validation,
|
|
4109
|
+
errors: validation.errors,
|
|
4110
|
+
warnings,
|
|
4111
|
+
};
|
|
4112
|
+
}
|
|
4113
|
+
function prCheck(projectDir) {
|
|
4114
|
+
ensureMemoryDirs(projectDir);
|
|
4115
|
+
const overlay = buildBranchOverlay(projectDir);
|
|
4116
|
+
const rawStatus = readGit(projectDir, ["status", "--porcelain", "-uall"]) ?? "";
|
|
4117
|
+
const validation = validateProject(projectDir);
|
|
4118
|
+
const stalePackets = loadPacketsFromDir(packetsDir(projectDir))
|
|
4119
|
+
.map((packet) => ({ packet, reasons: staleMemoryReasons(projectDir, packet) }))
|
|
4120
|
+
.filter((entry) => entry.reasons.length)
|
|
4121
|
+
.map((entry) => staleFinding(entry.packet, entry.reasons));
|
|
4122
|
+
const memoryPacketChanges = unique(rawStatus
|
|
4123
|
+
.split(/\r?\n/)
|
|
4124
|
+
.map(parsePorcelainPath)
|
|
4125
|
+
.map((path) => path.replace(/^.* -> /, ""))
|
|
4126
|
+
.filter((path) => path.startsWith(".agent_memory/packets/") && path.endsWith(".json"))).sort();
|
|
4127
|
+
const codeGraphCurrent = graphIsCurrent(projectDir, ".agent_memory/code_graph/graph.json", overlay.head);
|
|
4128
|
+
const memoryGraphCurrent = graphIsCurrent(projectDir, ".agent_memory/graph/graph.json", overlay.head);
|
|
4129
|
+
const errors = [...validation.errors];
|
|
4130
|
+
const warnings = [...validation.warnings];
|
|
4131
|
+
const requiredActions = [];
|
|
4132
|
+
if (stalePackets.length) {
|
|
4133
|
+
errors.push(`${stalePackets.length} stale memory packet(s) require update, verification, or supersession.`);
|
|
4134
|
+
requiredActions.push("Run kage refresh, then update or supersede stale packets.");
|
|
4135
|
+
}
|
|
4136
|
+
if (!codeGraphCurrent || !memoryGraphCurrent) {
|
|
4137
|
+
errors.push("Generated graph artifacts are missing or not current for this branch head.");
|
|
4138
|
+
requiredActions.push("Run kage refresh --project <dir> before merge.");
|
|
4139
|
+
}
|
|
4140
|
+
if (!memoryPacketChanges.length && overlay.changed_files.some((path) => !path.startsWith(".agent_memory/"))) {
|
|
4141
|
+
warnings.push("No repo memory packet changed for this branch. If durable knowledge was learned, run kage propose --from-diff or kage learn.");
|
|
4142
|
+
}
|
|
4143
|
+
if (!requiredActions.length)
|
|
4144
|
+
requiredActions.push("PR memory and graph checks passed.");
|
|
4145
|
+
return {
|
|
4146
|
+
ok: errors.length === 0,
|
|
4147
|
+
project_dir: projectDir,
|
|
4148
|
+
branch: overlay.branch,
|
|
4149
|
+
head: overlay.head,
|
|
4150
|
+
changed_files: overlay.changed_files,
|
|
4151
|
+
validation,
|
|
4152
|
+
stale_packets: stalePackets,
|
|
4153
|
+
memory_packet_changes: memoryPacketChanges,
|
|
4154
|
+
code_graph_current: codeGraphCurrent,
|
|
4155
|
+
memory_graph_current: memoryGraphCurrent,
|
|
4156
|
+
errors,
|
|
4157
|
+
warnings,
|
|
4158
|
+
required_actions: requiredActions,
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
3928
4161
|
function exportPublicBundle(projectDir) {
|
|
3929
4162
|
ensureMemoryDirs(projectDir);
|
|
3930
4163
|
const candidates = loadPacketsFromDir(publicCandidatesDir(projectDir));
|
package/package.json
CHANGED
package/viewer/app.js
CHANGED
|
@@ -35,21 +35,37 @@
|
|
|
35
35
|
|
|
36
36
|
var palette = {
|
|
37
37
|
repo: "#41ff8f",
|
|
38
|
-
memory: "#
|
|
38
|
+
memory: "#41ff8f",
|
|
39
39
|
path: "#ff6b6b",
|
|
40
40
|
tag: "#ffd166",
|
|
41
41
|
package: "#6ad7ff",
|
|
42
42
|
command: "#9be7c0",
|
|
43
43
|
memory_type: "#41ff8f",
|
|
44
44
|
file: "#6ad7ff",
|
|
45
|
-
symbol: "#
|
|
46
|
-
route: "#
|
|
45
|
+
symbol: "#9be7c0",
|
|
46
|
+
route: "#6ad7ff",
|
|
47
47
|
test: "#ffd166",
|
|
48
|
-
external: "#
|
|
48
|
+
external: "#62776b",
|
|
49
49
|
script: "#6ad7ff",
|
|
50
50
|
default: "#9be7c0"
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
+
var graphPalette = {
|
|
54
|
+
background: "#020503",
|
|
55
|
+
grid: "rgba(65,255,143,0.040)",
|
|
56
|
+
gridStrong: "rgba(65,255,143,0.070)",
|
|
57
|
+
text: "#d7f9df",
|
|
58
|
+
muted: "#6ea77d",
|
|
59
|
+
memory: "#41ff8f",
|
|
60
|
+
code: "#6ad7ff",
|
|
61
|
+
amber: "#ffd166",
|
|
62
|
+
danger: "#ff6b6b",
|
|
63
|
+
dependency: "#62776b",
|
|
64
|
+
body: "rgba(4,12,8,0.88)",
|
|
65
|
+
bodyCode: "rgba(5,16,18,0.88)",
|
|
66
|
+
bodyMemory: "rgba(5,18,10,0.90)"
|
|
67
|
+
};
|
|
68
|
+
|
|
53
69
|
var els = {
|
|
54
70
|
graphFile: document.getElementById("graphFile"),
|
|
55
71
|
graphSummary: document.getElementById("graphSummary"),
|
|
@@ -454,7 +470,7 @@
|
|
|
454
470
|
function render() {
|
|
455
471
|
if (!state.graph) return;
|
|
456
472
|
|
|
457
|
-
var query =
|
|
473
|
+
var query = parseSearchQuery(els.searchInput.value);
|
|
458
474
|
var mode = els.viewMode.value;
|
|
459
475
|
var type = els.typeFilter.value;
|
|
460
476
|
var relation = els.relationFilter.value;
|
|
@@ -464,7 +480,7 @@
|
|
|
464
480
|
state.entities.forEach(function (entity) {
|
|
465
481
|
if (mode !== "combined" && entity.graph_kind !== mode) return;
|
|
466
482
|
var passesType = !type || entity.type === type;
|
|
467
|
-
var passesSearch =
|
|
483
|
+
var passesSearch = matchesSearchQuery(entity, query);
|
|
468
484
|
if (passesType && passesSearch) matchedEntityIds.add(entity.id);
|
|
469
485
|
});
|
|
470
486
|
|
|
@@ -472,7 +488,7 @@
|
|
|
472
488
|
if (mode !== "combined" && edge.graph_kind !== mode) return;
|
|
473
489
|
var fromMatched = matchedEntityIds.has(edge.from);
|
|
474
490
|
var toMatched = matchedEntityIds.has(edge.to);
|
|
475
|
-
var edgeMatchesSearch =
|
|
491
|
+
var edgeMatchesSearch = matchesSearchQuery(edge, query);
|
|
476
492
|
var passesRelation = !relation || edge.relation === relation;
|
|
477
493
|
if (passesRelation && (edgeMatchesSearch || fromMatched || toMatched)) {
|
|
478
494
|
matchedEdgeIds.add(edge.id);
|
|
@@ -483,7 +499,7 @@
|
|
|
483
499
|
}
|
|
484
500
|
});
|
|
485
501
|
|
|
486
|
-
if (!query && !type && !relation) {
|
|
502
|
+
if (!query.active && !type && !relation) {
|
|
487
503
|
matchedEntityIds = new Set(state.entities.filter(function (entity) { return mode === "combined" || entity.graph_kind === mode; }).map(function (entity) { return entity.id; }));
|
|
488
504
|
matchedEdgeIds = new Set(state.edges.filter(function (edge) { return mode === "combined" || edge.graph_kind === mode; }).map(function (edge) { return edge.id; }));
|
|
489
505
|
}
|
|
@@ -520,7 +536,7 @@
|
|
|
520
536
|
Array.from(entities).forEach(function (id) {
|
|
521
537
|
var entity = state.entityById.get(id);
|
|
522
538
|
if (!entity) return;
|
|
523
|
-
if (isDependencyEntity(entity) && !(
|
|
539
|
+
if (isDependencyEntity(entity) && !matchesSearchQuery(entity, options.query)) {
|
|
524
540
|
entities.delete(id);
|
|
525
541
|
}
|
|
526
542
|
});
|
|
@@ -751,9 +767,15 @@
|
|
|
751
767
|
|
|
752
768
|
function drawCanvasGrid(ctx, width, height) {
|
|
753
769
|
ctx.save();
|
|
754
|
-
ctx.fillStyle =
|
|
770
|
+
ctx.fillStyle = graphPalette.background;
|
|
755
771
|
ctx.fillRect(0, 0, width, height);
|
|
756
|
-
ctx.
|
|
772
|
+
var gradient = ctx.createRadialGradient(width * 0.52, height * 0.44, 40, width * 0.52, height * 0.44, Math.max(width, height) * 0.72);
|
|
773
|
+
gradient.addColorStop(0, "rgba(65,255,143,0.080)");
|
|
774
|
+
gradient.addColorStop(0.48, "rgba(65,255,143,0.018)");
|
|
775
|
+
gradient.addColorStop(1, "rgba(2,5,3,0)");
|
|
776
|
+
ctx.fillStyle = gradient;
|
|
777
|
+
ctx.fillRect(0, 0, width, height);
|
|
778
|
+
ctx.strokeStyle = graphPalette.grid;
|
|
757
779
|
ctx.lineWidth = 1;
|
|
758
780
|
var grid = 28;
|
|
759
781
|
for (var x = 0; x < width; x += grid) {
|
|
@@ -768,22 +790,29 @@
|
|
|
768
790
|
ctx.lineTo(width, y);
|
|
769
791
|
ctx.stroke();
|
|
770
792
|
}
|
|
793
|
+
ctx.strokeStyle = graphPalette.gridStrong;
|
|
794
|
+
ctx.beginPath();
|
|
795
|
+
ctx.moveTo(0, height / 2);
|
|
796
|
+
ctx.lineTo(width, height / 2);
|
|
797
|
+
ctx.moveTo(width / 2, 0);
|
|
798
|
+
ctx.lineTo(width / 2, height);
|
|
799
|
+
ctx.stroke();
|
|
771
800
|
ctx.restore();
|
|
772
801
|
}
|
|
773
802
|
|
|
774
803
|
function drawCanvasEdges(ctx) {
|
|
775
804
|
var nodeMap = state.sim.nodeById;
|
|
776
805
|
var focusId = focusedCanvasNodeId();
|
|
777
|
-
var query =
|
|
806
|
+
var query = parseSearchQuery(els.searchInput.value);
|
|
778
807
|
var dense = state.sim.nodes.length > 55;
|
|
779
808
|
state.sim.edges.forEach(function (edge) {
|
|
780
809
|
var from = nodeMap.get(edge.from);
|
|
781
810
|
var to = nodeMap.get(edge.to);
|
|
782
811
|
if (!from || !to) return;
|
|
783
812
|
var connected = focusId && (edge.from === focusId || edge.to === focusId);
|
|
784
|
-
var matches =
|
|
785
|
-
var alpha = !matches ? 0.
|
|
786
|
-
var color = hexToRgb(
|
|
813
|
+
var matches = matchesSearchQuery(edge, query) || matchesSearchQuery(from.entity, query) || matchesSearchQuery(to.entity, query);
|
|
814
|
+
var alpha = !matches ? 0.035 : focusId ? (connected ? 0.62 : 0.055) : (dense ? 0.13 : 0.22);
|
|
815
|
+
var color = hexToRgb(edgeThemeColor(edge, from.entity, to.entity));
|
|
787
816
|
var dx = to.x - from.x;
|
|
788
817
|
var dy = to.y - from.y;
|
|
789
818
|
var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
|
|
@@ -794,7 +823,7 @@
|
|
|
794
823
|
ctx.moveTo(from.x, from.y);
|
|
795
824
|
ctx.quadraticCurveTo(cx, cy, to.x, to.y);
|
|
796
825
|
ctx.strokeStyle = "rgba(" + color.r + "," + color.g + "," + color.b + "," + alpha + ")";
|
|
797
|
-
ctx.lineWidth = connected ? 2.
|
|
826
|
+
ctx.lineWidth = connected ? 2.2 : 1;
|
|
798
827
|
ctx.stroke();
|
|
799
828
|
if (connected || (!dense && state.sim.zoom > 1.25)) drawArrow(ctx, from, to, cx, cy, color, alpha);
|
|
800
829
|
if (connected && state.sim.zoom > 0.62) drawEdgeLabel(ctx, edge, cx, cy);
|
|
@@ -803,7 +832,7 @@
|
|
|
803
832
|
|
|
804
833
|
function drawCanvasNodes(ctx) {
|
|
805
834
|
var focusId = focusedCanvasNodeId();
|
|
806
|
-
var query =
|
|
835
|
+
var query = parseSearchQuery(els.searchInput.value);
|
|
807
836
|
var dense = state.sim.nodes.length > 55;
|
|
808
837
|
state.sim.nodes.forEach(function (node) {
|
|
809
838
|
var entity = node.entity;
|
|
@@ -812,35 +841,42 @@
|
|
|
812
841
|
var connected = focusId && (node.id === focusId || state.sim.edges.some(function (edge) {
|
|
813
842
|
return (edge.from === focusId && edge.to === node.id) || (edge.to === focusId && edge.from === node.id);
|
|
814
843
|
}));
|
|
815
|
-
var matches =
|
|
844
|
+
var matches = matchesSearchQuery(entity, query);
|
|
816
845
|
var alpha = !matches ? 0.12 : focusId && !connected ? 0.20 : 1;
|
|
817
|
-
var color =
|
|
846
|
+
var color = nodeThemeColor(entity);
|
|
818
847
|
ctx.save();
|
|
819
848
|
ctx.globalAlpha = alpha;
|
|
820
|
-
if (selected || hovered
|
|
849
|
+
if (selected || hovered) {
|
|
821
850
|
ctx.shadowColor = color;
|
|
822
|
-
ctx.shadowBlur = selected ?
|
|
851
|
+
ctx.shadowBlur = selected ? 14 : 10;
|
|
823
852
|
}
|
|
824
853
|
drawNodeShape(ctx, node.x, node.y, node.r, entity);
|
|
825
|
-
|
|
826
|
-
grad.addColorStop(0, brighten(color, 54));
|
|
827
|
-
grad.addColorStop(1, color);
|
|
828
|
-
ctx.fillStyle = grad;
|
|
854
|
+
ctx.fillStyle = nodeFillColor(entity);
|
|
829
855
|
ctx.fill();
|
|
856
|
+
ctx.strokeStyle = color;
|
|
857
|
+
ctx.lineWidth = selected || hovered ? 2.2 : 1.2;
|
|
858
|
+
ctx.stroke();
|
|
859
|
+
if (entity.graph_kind === "memory") {
|
|
860
|
+
ctx.fillStyle = color;
|
|
861
|
+
ctx.globalAlpha = alpha * 0.85;
|
|
862
|
+
ctx.beginPath();
|
|
863
|
+
ctx.arc(node.x, node.y, Math.max(2.4, node.r * 0.18), 0, Math.PI * 2);
|
|
864
|
+
ctx.fill();
|
|
865
|
+
}
|
|
830
866
|
ctx.restore();
|
|
831
867
|
|
|
832
868
|
if (selected || hovered) {
|
|
833
869
|
ctx.save();
|
|
834
870
|
drawNodeShape(ctx, node.x, node.y, node.r + 4, entity);
|
|
835
871
|
ctx.strokeStyle = color;
|
|
836
|
-
ctx.lineWidth = selected ?
|
|
872
|
+
ctx.lineWidth = selected ? 2.6 : 1.8;
|
|
837
873
|
ctx.shadowColor = color;
|
|
838
|
-
ctx.shadowBlur =
|
|
874
|
+
ctx.shadowBlur = 8;
|
|
839
875
|
ctx.stroke();
|
|
840
876
|
ctx.restore();
|
|
841
877
|
}
|
|
842
878
|
|
|
843
|
-
var shouldLabel = matches && (selected || hovered || (query && matches) || (!dense && state.sim.zoom > 0.75) || (dense && state.sim.zoom > 1.55 && node.r > 13));
|
|
879
|
+
var shouldLabel = matches && (selected || hovered || (query.active && matches) || (!dense && state.sim.zoom > 0.75) || (dense && state.sim.zoom > 1.55 && node.r > 13));
|
|
844
880
|
if (shouldLabel) drawNodeLabel(ctx, node, selected || hovered);
|
|
845
881
|
});
|
|
846
882
|
}
|
|
@@ -851,8 +887,8 @@
|
|
|
851
887
|
var tipY = to.y - to.r * Math.sin(angle);
|
|
852
888
|
ctx.beginPath();
|
|
853
889
|
ctx.moveTo(tipX, tipY);
|
|
854
|
-
ctx.lineTo(tipX -
|
|
855
|
-
ctx.lineTo(tipX -
|
|
890
|
+
ctx.lineTo(tipX - 6 * Math.cos(angle - 0.32), tipY - 6 * Math.sin(angle - 0.32));
|
|
891
|
+
ctx.lineTo(tipX - 6 * Math.cos(angle + 0.32), tipY - 6 * Math.sin(angle + 0.32));
|
|
856
892
|
ctx.closePath();
|
|
857
893
|
ctx.fillStyle = "rgba(" + color.r + "," + color.g + "," + color.b + "," + Math.min(0.85, alpha + 0.10) + ")";
|
|
858
894
|
ctx.fill();
|
|
@@ -861,9 +897,9 @@
|
|
|
861
897
|
function drawEdgeLabel(ctx, edge, x, y) {
|
|
862
898
|
var inv = 1 / state.sim.zoom;
|
|
863
899
|
ctx.save();
|
|
864
|
-
ctx.font = "700 " + (
|
|
900
|
+
ctx.font = "700 " + (9 * inv).toFixed(1) + "px ui-monospace, Menlo, monospace";
|
|
865
901
|
ctx.textAlign = "center";
|
|
866
|
-
ctx.fillStyle = "rgba(
|
|
902
|
+
ctx.fillStyle = "rgba(155,231,192,0.72)";
|
|
867
903
|
ctx.fillText(shortName(edge.relation || "related", 22), x, y - 5 * inv);
|
|
868
904
|
ctx.restore();
|
|
869
905
|
}
|
|
@@ -872,17 +908,17 @@
|
|
|
872
908
|
var inv = 1 / state.sim.zoom;
|
|
873
909
|
var label = shortName(displayName(node.entity), strong ? 30 : 20);
|
|
874
910
|
ctx.save();
|
|
875
|
-
ctx.font = (strong ? "800 " : "700 ") + (
|
|
911
|
+
ctx.font = (strong ? "800 " : "700 ") + (11 * inv).toFixed(1) + "px ui-monospace, Menlo, monospace";
|
|
876
912
|
var width = ctx.measureText(label).width + 16 * inv;
|
|
877
913
|
var height = 20 * inv;
|
|
878
914
|
var x = node.x - width / 2;
|
|
879
915
|
var y = node.y + node.r + 8 * inv;
|
|
880
|
-
ctx.fillStyle = "rgba(
|
|
916
|
+
ctx.fillStyle = "rgba(2,5,3,0.92)";
|
|
881
917
|
roundedRect(ctx, x, y, width, height, 4 * inv);
|
|
882
918
|
ctx.fill();
|
|
883
|
-
ctx.strokeStyle = strong ? "rgba(65,255,143,0.
|
|
919
|
+
ctx.strokeStyle = strong ? "rgba(65,255,143,0.45)" : "rgba(65,255,143,0.14)";
|
|
884
920
|
ctx.stroke();
|
|
885
|
-
ctx.fillStyle = strong ?
|
|
921
|
+
ctx.fillStyle = strong ? graphPalette.text : graphPalette.muted;
|
|
886
922
|
ctx.textAlign = "center";
|
|
887
923
|
ctx.fillText(label, node.x, y + 14 * inv);
|
|
888
924
|
ctx.restore();
|
|
@@ -919,6 +955,30 @@
|
|
|
919
955
|
ctx.arc(x, y, r, 0, Math.PI * 2);
|
|
920
956
|
}
|
|
921
957
|
|
|
958
|
+
function nodeThemeColor(entity) {
|
|
959
|
+
if (isDependencyEntity(entity) || entity.type === "external") return graphPalette.dependency;
|
|
960
|
+
if (entity.type === "test" || entity.type === "tag") return graphPalette.amber;
|
|
961
|
+
if (entity.type === "bug_fix" || entity.type === "path") return graphPalette.danger;
|
|
962
|
+
if (entity.graph_kind === "memory" || ["memory", "repo", "memory_type", "decision", "runbook", "workflow", "convention", "gotcha", "reference", "policy"].indexOf(entity.type) !== -1) return graphPalette.memory;
|
|
963
|
+
if (entity.graph_kind === "code" || ["file", "symbol", "route", "script", "command", "package"].indexOf(entity.type) !== -1) return graphPalette.code;
|
|
964
|
+
return graphPalette.muted;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function nodeFillColor(entity) {
|
|
968
|
+
if (isDependencyEntity(entity) || entity.type === "external") return "rgba(7,13,10,0.88)";
|
|
969
|
+
if (entity.graph_kind === "memory" || entity.type === "memory") return graphPalette.bodyMemory;
|
|
970
|
+
if (entity.graph_kind === "code") return graphPalette.bodyCode;
|
|
971
|
+
return graphPalette.body;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function edgeThemeColor(edge, fromEntity, toEntity) {
|
|
975
|
+
if (edge.relation && /test|covers/i.test(edge.relation)) return graphPalette.amber;
|
|
976
|
+
if (edge.relation && /invalid|risk|missing|bug/i.test(edge.relation)) return graphPalette.danger;
|
|
977
|
+
if (isDependencyEntity(fromEntity) || isDependencyEntity(toEntity)) return graphPalette.dependency;
|
|
978
|
+
if (fromEntity.graph_kind === "memory" || toEntity.graph_kind === "memory") return graphPalette.memory;
|
|
979
|
+
return graphPalette.code;
|
|
980
|
+
}
|
|
981
|
+
|
|
922
982
|
function renderSvg() {
|
|
923
983
|
els.edgeLayer.textContent = "";
|
|
924
984
|
els.nodeLayer.textContent = "";
|
|
@@ -1461,7 +1521,7 @@
|
|
|
1461
1521
|
}
|
|
1462
1522
|
var entity = node.entity;
|
|
1463
1523
|
var relationCount = state.sim.edges.filter(function (edge) { return edge.from === node.id || edge.to === node.id; }).length;
|
|
1464
|
-
var color =
|
|
1524
|
+
var color = nodeThemeColor(entity);
|
|
1465
1525
|
els.tooltip.innerHTML = [
|
|
1466
1526
|
"<div class=\"tt-name\"></div>",
|
|
1467
1527
|
"<div class=\"tt-type\"></div>",
|
|
@@ -1653,6 +1713,77 @@
|
|
|
1653
1713
|
return normalize(JSON.stringify(value || {}));
|
|
1654
1714
|
}
|
|
1655
1715
|
|
|
1716
|
+
function parseSearchQuery(value) {
|
|
1717
|
+
var raw = normalize(value);
|
|
1718
|
+
var tokens = raw
|
|
1719
|
+
.replace(/[^a-z0-9_./:-]+/g, " ")
|
|
1720
|
+
.split(/\s+/)
|
|
1721
|
+
.map(searchStem)
|
|
1722
|
+
.filter(Boolean)
|
|
1723
|
+
.filter(function (token) { return !SEARCH_STOP_WORDS.has(token); });
|
|
1724
|
+
if ((tokens.indexOf("run") !== -1 || tokens.indexOf("runn") !== -1) && tokens.indexOf("test") !== -1) tokens.push("runtest");
|
|
1725
|
+
var groups = tokens.map(function (token) {
|
|
1726
|
+
return unique([token].concat(SEARCH_SYNONYMS[token] || []).map(searchStem).filter(Boolean));
|
|
1727
|
+
});
|
|
1728
|
+
return {
|
|
1729
|
+
active: raw.trim().length > 0,
|
|
1730
|
+
raw: raw,
|
|
1731
|
+
tokens: unique(groups.reduce(function (all, group) { return all.concat(group); }, [])),
|
|
1732
|
+
groups: groups
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
var SEARCH_STOP_WORDS = new Set([
|
|
1737
|
+
"a", "an", "and", "are", "about", "can", "do", "does", "for", "from", "how", "i", "in", "is", "it", "me", "of", "on", "or", "please", "show", "that", "the", "there", "this", "to", "what", "when", "where", "which", "who", "why", "with"
|
|
1738
|
+
]);
|
|
1739
|
+
|
|
1740
|
+
var SEARCH_SYNONYMS = {
|
|
1741
|
+
memory: ["packet", "runbook", "decision", "workflow", "gotcha", "reference"],
|
|
1742
|
+
test: ["tests", "testing", "vitest", "jest", "pytest", "spec"],
|
|
1743
|
+
run: ["running", "command", "script", "npm", "pnpm", "yarn"],
|
|
1744
|
+
runn: ["run", "running", "command", "script", "npm", "pnpm", "yarn"],
|
|
1745
|
+
runtest: ["run", "test", "runbook", "command"],
|
|
1746
|
+
start: ["serve", "dev", "launch"],
|
|
1747
|
+
build: ["compile", "tsc"],
|
|
1748
|
+
bug: ["fix", "gotcha", "error"],
|
|
1749
|
+
route: ["endpoint", "api"],
|
|
1750
|
+
file: ["path"],
|
|
1751
|
+
dependency: ["package", "external", "deps"]
|
|
1752
|
+
};
|
|
1753
|
+
|
|
1754
|
+
function matchesSearchQuery(value, query) {
|
|
1755
|
+
if (!query || !query.active) return true;
|
|
1756
|
+
var text = searchableText(value);
|
|
1757
|
+
if (query.raw && text.indexOf(query.raw) !== -1) return true;
|
|
1758
|
+
if (!query.tokens.length) return true;
|
|
1759
|
+
var textTokens = new Set(text
|
|
1760
|
+
.replace(/[^a-z0-9_./:-]+/g, " ")
|
|
1761
|
+
.split(/\s+/)
|
|
1762
|
+
.map(searchStem)
|
|
1763
|
+
.filter(Boolean));
|
|
1764
|
+
var requiredGroups = (query.groups || []).filter(function (group) {
|
|
1765
|
+
return group.some(function (token) { return SEARCH_SOFT_TOKENS.has(token) ? text.indexOf(token) !== -1 || textTokens.has(token) : true; });
|
|
1766
|
+
});
|
|
1767
|
+
if (!requiredGroups.length) requiredGroups = query.groups || [query.tokens];
|
|
1768
|
+
return requiredGroups.every(function (group) {
|
|
1769
|
+
return group.some(function (token) {
|
|
1770
|
+
if (text.indexOf(token) !== -1) return true;
|
|
1771
|
+
return textTokens.has(token);
|
|
1772
|
+
});
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
var SEARCH_SOFT_TOKENS = new Set(["memory", "packet", "about"]);
|
|
1777
|
+
|
|
1778
|
+
function searchStem(value) {
|
|
1779
|
+
var token = String(value || "").toLowerCase();
|
|
1780
|
+
if (token.length > 5 && token.endsWith("ing")) token = token.slice(0, -3);
|
|
1781
|
+
if (token.length > 4 && token.endsWith("ies")) token = token.slice(0, -3) + "y";
|
|
1782
|
+
if (token.length > 4 && token.endsWith("es")) token = token.slice(0, -2);
|
|
1783
|
+
if (token.length > 3 && token.endsWith("s")) token = token.slice(0, -1);
|
|
1784
|
+
return token;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1656
1787
|
function shortName(value, max) {
|
|
1657
1788
|
var text = String(value || "");
|
|
1658
1789
|
return text.length > max ? text.slice(0, Math.max(1, max - 1)) + "..." : text;
|
package/viewer/styles.css
CHANGED
|
@@ -331,7 +331,7 @@ input:focus, select:focus, button:focus, .file-picker:focus-within {
|
|
|
331
331
|
height: 100%;
|
|
332
332
|
overflow: hidden;
|
|
333
333
|
background:
|
|
334
|
-
radial-gradient(circle at 52% 46%, rgba(65, 255, 143, 0.
|
|
334
|
+
radial-gradient(circle at 52% 46%, rgba(65, 255, 143, 0.055), transparent 44%),
|
|
335
335
|
#020503;
|
|
336
336
|
}
|
|
337
337
|
|
|
@@ -367,10 +367,10 @@ input:focus, select:focus, button:focus, .file-picker:focus-within {
|
|
|
367
367
|
display: none;
|
|
368
368
|
max-width: min(320px, calc(100% - 28px));
|
|
369
369
|
padding: 10px 12px;
|
|
370
|
-
border: 1px solid rgba(65, 255, 143, 0.
|
|
371
|
-
border-radius:
|
|
372
|
-
background: rgba(
|
|
373
|
-
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.
|
|
370
|
+
border: 1px solid rgba(65, 255, 143, 0.28);
|
|
371
|
+
border-radius: 4px;
|
|
372
|
+
background: rgba(2, 5, 3, 0.94);
|
|
373
|
+
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.42), inset 0 0 0 1px rgba(65, 255, 143, 0.06);
|
|
374
374
|
backdrop-filter: blur(12px);
|
|
375
375
|
color: var(--text);
|
|
376
376
|
pointer-events: none;
|
|
@@ -378,14 +378,14 @@ input:focus, select:focus, button:focus, .file-picker:focus-within {
|
|
|
378
378
|
.graph-tooltip.visible { display: block; }
|
|
379
379
|
.tt-name {
|
|
380
380
|
color: var(--terminal);
|
|
381
|
-
font-weight:
|
|
381
|
+
font-weight: 820;
|
|
382
382
|
overflow-wrap: anywhere;
|
|
383
383
|
}
|
|
384
384
|
.tt-type {
|
|
385
385
|
margin-top: 4px;
|
|
386
386
|
font-size: 10px;
|
|
387
387
|
font-weight: 800;
|
|
388
|
-
letter-spacing: 0.
|
|
388
|
+
letter-spacing: 0.10em;
|
|
389
389
|
text-transform: uppercase;
|
|
390
390
|
}
|
|
391
391
|
.tt-summary {
|