@kage-core/kage-graph-mcp 1.1.24 → 1.1.25
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/dist/kernel.js +83 -13
- package/package.json +1 -1
- package/viewer/index.html +8 -2
- package/viewer/styles.css +45 -3
package/dist/kernel.js
CHANGED
|
@@ -460,6 +460,12 @@ function estimateTokens(text) {
|
|
|
460
460
|
function packetText(packet) {
|
|
461
461
|
return `${packet.title}\n${packet.summary}\n${packet.body}\n${packet.type}\n${packet.tags.join(" ")}\n${packet.paths.join(" ")}`;
|
|
462
462
|
}
|
|
463
|
+
function isGeneratedChangeMemory(packet) {
|
|
464
|
+
return packet.type === "workflow"
|
|
465
|
+
&& packet.tags.includes("change-memory")
|
|
466
|
+
&& packet.tags.includes("diff-proposal")
|
|
467
|
+
&& packet.source_refs.some((ref) => ref.kind === "git_diff");
|
|
468
|
+
}
|
|
463
469
|
function tokenSet(text) {
|
|
464
470
|
return new Set(tokenize(text).filter((term) => term.length > 2));
|
|
465
471
|
}
|
|
@@ -476,6 +482,7 @@ function duplicateCandidates(projectDir, packet, threshold = 0.58) {
|
|
|
476
482
|
const current = tokenSet(packetText(packet));
|
|
477
483
|
return [...loadApprovedPackets(projectDir), ...loadPendingPackets(projectDir)]
|
|
478
484
|
.filter((candidate) => candidate.id !== packet.id)
|
|
485
|
+
.filter((candidate) => !(isGeneratedChangeMemory(packet) && isGeneratedChangeMemory(candidate)))
|
|
479
486
|
.map((candidate) => ({ packet: candidate, score: jaccard(current, tokenSet(packetText(candidate))) }))
|
|
480
487
|
.filter((entry) => entry.score >= threshold)
|
|
481
488
|
.sort((a, b) => b.score - a.score || a.packet.title.localeCompare(b.packet.title))
|
|
@@ -888,6 +895,30 @@ function readGit(projectDir, args) {
|
|
|
888
895
|
return null;
|
|
889
896
|
}
|
|
890
897
|
}
|
|
898
|
+
function safeStat(path) {
|
|
899
|
+
try {
|
|
900
|
+
return (0, node_fs_1.statSync)(path);
|
|
901
|
+
}
|
|
902
|
+
catch {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function safeLstat(path) {
|
|
907
|
+
try {
|
|
908
|
+
return (0, node_fs_1.lstatSync)(path);
|
|
909
|
+
}
|
|
910
|
+
catch {
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function safeReadText(path) {
|
|
915
|
+
try {
|
|
916
|
+
return (0, node_fs_1.readFileSync)(path, "utf8");
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
891
922
|
function gitBranch(projectDir) {
|
|
892
923
|
return readGit(projectDir, ["branch", "--show-current"]) || readGit(projectDir, ["rev-parse", "--short", "HEAD"]);
|
|
893
924
|
}
|
|
@@ -901,6 +932,12 @@ function gitMergeBase(projectDir) {
|
|
|
901
932
|
return readGit(projectDir, ["merge-base", "HEAD", "origin/main"])
|
|
902
933
|
|| readGit(projectDir, ["merge-base", "HEAD", "origin/master"]);
|
|
903
934
|
}
|
|
935
|
+
function gitProjectPrefix(projectDir) {
|
|
936
|
+
const prefix = readGit(projectDir, ["rev-parse", "--show-prefix"]);
|
|
937
|
+
if (prefix === null)
|
|
938
|
+
return null;
|
|
939
|
+
return prefix.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
940
|
+
}
|
|
904
941
|
// Directories that are never meaningful in change-memory packets.
|
|
905
942
|
// These are typically generated, vendored, or ephemeral — any project can
|
|
906
943
|
// accumulate thousands of files here that bury real signal.
|
|
@@ -934,12 +971,26 @@ function isNoisePath(filePath) {
|
|
|
934
971
|
return false;
|
|
935
972
|
return NOISE_PATH_PREFIXES.some((prefix) => filePath.startsWith(prefix));
|
|
936
973
|
}
|
|
937
|
-
function
|
|
974
|
+
function gitPathToProjectRelative(projectDir, path) {
|
|
975
|
+
const normalized = path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
976
|
+
const projectPrefix = gitProjectPrefix(projectDir);
|
|
977
|
+
if (projectPrefix === null || projectPrefix === "")
|
|
978
|
+
return normalized;
|
|
979
|
+
if (normalized === projectPrefix)
|
|
980
|
+
return "";
|
|
981
|
+
const prefix = `${projectPrefix}/`;
|
|
982
|
+
if (!normalized.startsWith(prefix)) {
|
|
983
|
+
return (0, node_fs_1.existsSync)((0, node_path_1.join)(projectDir, normalized)) ? normalized : null;
|
|
984
|
+
}
|
|
985
|
+
return normalized.slice(prefix.length);
|
|
986
|
+
}
|
|
987
|
+
function parsePorcelainStatus(projectDir, status) {
|
|
938
988
|
return unique(status
|
|
939
989
|
.split(/\r?\n/)
|
|
940
990
|
.map(parsePorcelainPath)
|
|
941
991
|
.map((path) => path.replace(/^.* -> /, ""))
|
|
942
|
-
.
|
|
992
|
+
.map((path) => gitPathToProjectRelative(projectDir, path))
|
|
993
|
+
.filter((path) => Boolean(path))
|
|
943
994
|
.filter((path) => !shouldSkipRepoMemoryPath(path))).sort();
|
|
944
995
|
}
|
|
945
996
|
function parsePorcelainPath(line) {
|
|
@@ -948,13 +999,14 @@ function parsePorcelainPath(line) {
|
|
|
948
999
|
}
|
|
949
1000
|
function branchDiffStat(projectDir, changedFiles) {
|
|
950
1001
|
const diffStats = [
|
|
951
|
-
readGit(projectDir, ["diff", "--stat"]),
|
|
952
|
-
readGit(projectDir, ["diff", "--cached", "--stat"]),
|
|
1002
|
+
readGit(projectDir, ["diff", "--stat", "--relative"]),
|
|
1003
|
+
readGit(projectDir, ["diff", "--cached", "--stat", "--relative"]),
|
|
953
1004
|
].filter(Boolean).join("\n").trim();
|
|
954
1005
|
const untracked = new Set((readGit(projectDir, ["ls-files", "--others", "--exclude-standard"]) ?? "")
|
|
955
1006
|
.split(/\r?\n/)
|
|
956
1007
|
.map((path) => path.trim())
|
|
957
|
-
.
|
|
1008
|
+
.map((path) => gitPathToProjectRelative(projectDir, path))
|
|
1009
|
+
.filter((path) => Boolean(path))
|
|
958
1010
|
.filter((path) => changedFiles.includes(path)));
|
|
959
1011
|
const untrackedStats = [...untracked]
|
|
960
1012
|
.filter((file) => !diffStats.includes(file))
|
|
@@ -1016,8 +1068,9 @@ function createRepoOverviewPacket(projectDir) {
|
|
|
1016
1068
|
if (deps.stripe)
|
|
1017
1069
|
tags.push("stripe");
|
|
1018
1070
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1071
|
+
const readmeText = (0, node_fs_1.existsSync)(readmePath) ? safeReadText(readmePath) : null;
|
|
1072
|
+
if (readmeText) {
|
|
1073
|
+
const readme = readmeText.slice(0, 1000);
|
|
1021
1074
|
bodyParts.push(`README excerpt:\n${readme}`);
|
|
1022
1075
|
}
|
|
1023
1076
|
const createdAt = nowIso();
|
|
@@ -1038,7 +1091,7 @@ function createRepoOverviewPacket(projectDir) {
|
|
|
1038
1091
|
stack,
|
|
1039
1092
|
source_refs: [
|
|
1040
1093
|
...((0, node_fs_1.existsSync)(packagePath) ? [{ kind: "file", path: "package.json" }] : []),
|
|
1041
|
-
...(
|
|
1094
|
+
...(readmeText ? [{ kind: "file", path: "README.md" }] : []),
|
|
1042
1095
|
],
|
|
1043
1096
|
context: {
|
|
1044
1097
|
fact: "Generated repo overview summarizes package metadata and the README as a navigation aid for agent startup.",
|
|
@@ -1748,7 +1801,20 @@ function scanStructuralFiles(projectDir) {
|
|
|
1748
1801
|
ignore("kageignore");
|
|
1749
1802
|
continue;
|
|
1750
1803
|
}
|
|
1751
|
-
const
|
|
1804
|
+
const linkStats = safeLstat(absolutePath);
|
|
1805
|
+
if (!linkStats) {
|
|
1806
|
+
ignore("unreadable_path");
|
|
1807
|
+
continue;
|
|
1808
|
+
}
|
|
1809
|
+
if (linkStats.isSymbolicLink()) {
|
|
1810
|
+
ignore("symlink");
|
|
1811
|
+
continue;
|
|
1812
|
+
}
|
|
1813
|
+
const stats = safeStat(absolutePath);
|
|
1814
|
+
if (!stats) {
|
|
1815
|
+
ignore("unreadable_path");
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1752
1818
|
if (stats.isDirectory()) {
|
|
1753
1819
|
visit(absolutePath);
|
|
1754
1820
|
continue;
|
|
@@ -5282,10 +5348,14 @@ function baselineDiscoveryFiles(projectDir, task) {
|
|
|
5282
5348
|
const absolute = (0, node_path_1.join)(projectDir, path);
|
|
5283
5349
|
if (!(0, node_fs_1.existsSync)(absolute))
|
|
5284
5350
|
return null;
|
|
5285
|
-
const stats = (
|
|
5351
|
+
const stats = safeStat(absolute);
|
|
5352
|
+
if (!stats)
|
|
5353
|
+
return null;
|
|
5286
5354
|
if (!stats.isFile() || stats.size > 240_000)
|
|
5287
5355
|
return null;
|
|
5288
|
-
const text = (
|
|
5356
|
+
const text = safeReadText(absolute);
|
|
5357
|
+
if (text === null)
|
|
5358
|
+
return null;
|
|
5289
5359
|
const score = scoreText(terms, `${path}\n${text.slice(0, 8000)}`, [path]);
|
|
5290
5360
|
const alwaysUseful = ["README.md", "AGENTS.md", "CLAUDE.md", "package.json"].includes(path);
|
|
5291
5361
|
if (score <= 0 && !alwaysUseful)
|
|
@@ -6421,7 +6491,7 @@ function proposeFromDiff(projectDir) {
|
|
|
6421
6491
|
const status = readGit(projectDir, ["status", "--porcelain", "-uall"]);
|
|
6422
6492
|
if (status === null)
|
|
6423
6493
|
return { ok: false, changedFiles: [], errors: ["Not a git repository or git is unavailable."] };
|
|
6424
|
-
const changedFiles = parsePorcelainStatus(status);
|
|
6494
|
+
const changedFiles = parsePorcelainStatus(projectDir, status);
|
|
6425
6495
|
if (changedFiles.length === 0)
|
|
6426
6496
|
return { ok: false, changedFiles: [], errors: ["No changed files found."] };
|
|
6427
6497
|
const stat = branchDiffStat(projectDir, changedFiles);
|
|
@@ -6470,7 +6540,7 @@ function buildBranchOverlay(projectDir) {
|
|
|
6470
6540
|
branch: gitBranch(projectDir),
|
|
6471
6541
|
head: gitHead(projectDir),
|
|
6472
6542
|
merge_base: gitMergeBase(projectDir),
|
|
6473
|
-
changed_files: parsePorcelainStatus(status),
|
|
6543
|
+
changed_files: parsePorcelainStatus(projectDir, status),
|
|
6474
6544
|
pending_packet_ids: loadPendingPackets(projectDir).map((packet) => packet.id).sort(),
|
|
6475
6545
|
generated_at: nowIso(),
|
|
6476
6546
|
};
|
package/package.json
CHANGED
package/viewer/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>Kage Memory Terminal</title>
|
|
7
|
-
<link rel="stylesheet" href="./styles.css?v=
|
|
7
|
+
<link rel="stylesheet" href="./styles.css?v=19">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<header class="app-header">
|
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
<h1>Memory Terminal</h1>
|
|
14
14
|
<p id="graphSummary">Load memory, code, and metrics graphs to inspect what agents know and why.</p>
|
|
15
15
|
</div>
|
|
16
|
+
<nav class="site-links" aria-label="Kage site links">
|
|
17
|
+
<a href="../">Home</a>
|
|
18
|
+
<a href="../guide.html">Docs</a>
|
|
19
|
+
<a href="../releases.html">Releases</a>
|
|
20
|
+
<a href="https://github.com/kage-core/Kage">GitHub</a>
|
|
21
|
+
</nav>
|
|
16
22
|
<div id="statusStrip" class="status-strip" aria-label="Graph health"></div>
|
|
17
23
|
<div id="autoLoadStatus" class="autoload-status">auto-load: waiting</div>
|
|
18
24
|
<label class="file-picker">
|
|
@@ -157,6 +163,6 @@
|
|
|
157
163
|
</section>
|
|
158
164
|
</main>
|
|
159
165
|
|
|
160
|
-
<script src="./app.js?v=
|
|
166
|
+
<script src="./app.js?v=19"></script>
|
|
161
167
|
</body>
|
|
162
168
|
</html>
|
package/viewer/styles.css
CHANGED
|
@@ -40,7 +40,10 @@ h2 { color: var(--terminal); font-size: 13px; letter-spacing: 0.04em; text-trans
|
|
|
40
40
|
|
|
41
41
|
.app-header {
|
|
42
42
|
display: grid;
|
|
43
|
-
grid-template-columns: minmax(
|
|
43
|
+
grid-template-columns: minmax(360px, 1fr) auto auto;
|
|
44
|
+
grid-template-areas:
|
|
45
|
+
"brand links picker"
|
|
46
|
+
"status status autoload";
|
|
44
47
|
gap: 16px;
|
|
45
48
|
align-items: center;
|
|
46
49
|
padding: 14px 18px;
|
|
@@ -52,7 +55,11 @@ h2 { color: var(--terminal); font-size: 13px; letter-spacing: 0.04em; text-trans
|
|
|
52
55
|
z-index: 10;
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
.brand-block {
|
|
58
|
+
.brand-block {
|
|
59
|
+
grid-area: brand;
|
|
60
|
+
min-width: 280px;
|
|
61
|
+
max-width: 620px;
|
|
62
|
+
}
|
|
56
63
|
.eyebrow {
|
|
57
64
|
display: inline-flex;
|
|
58
65
|
margin-bottom: 5px;
|
|
@@ -72,14 +79,42 @@ h2 { color: var(--terminal); font-size: 13px; letter-spacing: 0.04em; text-trans
|
|
|
72
79
|
overflow-wrap: anywhere;
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
.site-links {
|
|
83
|
+
grid-area: links;
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-wrap: wrap;
|
|
86
|
+
gap: 10px;
|
|
87
|
+
justify-content: flex-end;
|
|
88
|
+
}
|
|
89
|
+
.site-links a {
|
|
90
|
+
display: inline-flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
min-height: 28px;
|
|
93
|
+
padding: 4px 9px;
|
|
94
|
+
border: 1px solid var(--line);
|
|
95
|
+
border-radius: 4px;
|
|
96
|
+
background: rgba(13, 25, 19, 0.86);
|
|
97
|
+
color: var(--terminal-dim);
|
|
98
|
+
font-size: 11px;
|
|
99
|
+
font-weight: 780;
|
|
100
|
+
text-decoration: none;
|
|
101
|
+
white-space: nowrap;
|
|
102
|
+
}
|
|
103
|
+
.site-links a:hover {
|
|
104
|
+
border-color: var(--line-strong);
|
|
105
|
+
color: var(--terminal-strong);
|
|
106
|
+
}
|
|
107
|
+
|
|
75
108
|
.status-strip {
|
|
109
|
+
grid-area: status;
|
|
76
110
|
display: flex;
|
|
77
111
|
flex-wrap: wrap;
|
|
78
112
|
gap: 8px;
|
|
79
|
-
justify-content: flex-
|
|
113
|
+
justify-content: flex-start;
|
|
80
114
|
}
|
|
81
115
|
|
|
82
116
|
.autoload-status {
|
|
117
|
+
grid-area: autoload;
|
|
83
118
|
display: inline-flex;
|
|
84
119
|
align-items: center;
|
|
85
120
|
min-height: 28px;
|
|
@@ -131,6 +166,7 @@ h2 { color: var(--terminal); font-size: 13px; letter-spacing: 0.04em; text-trans
|
|
|
131
166
|
font-weight: 780;
|
|
132
167
|
box-shadow: inset 0 0 0 1px rgba(65, 255, 143, 0.10);
|
|
133
168
|
}
|
|
169
|
+
.file-picker { grid-area: picker; }
|
|
134
170
|
.file-picker:hover, button:hover { background: #0d1f15; }
|
|
135
171
|
.file-picker input {
|
|
136
172
|
position: absolute;
|
|
@@ -702,6 +738,12 @@ input:focus, select:focus, button:focus, .file-picker:focus-within {
|
|
|
702
738
|
@media (max-width: 1120px) {
|
|
703
739
|
.app-header {
|
|
704
740
|
grid-template-columns: 1fr;
|
|
741
|
+
grid-template-areas:
|
|
742
|
+
"brand"
|
|
743
|
+
"links"
|
|
744
|
+
"status"
|
|
745
|
+
"autoload"
|
|
746
|
+
"picker";
|
|
705
747
|
align-items: stretch;
|
|
706
748
|
padding: 12px 14px;
|
|
707
749
|
}
|