@kage-core/kage-graph-mcp 3.1.0 → 3.3.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/dist/check.js +612 -0
- package/dist/cli.js +44 -1
- package/dist/index.js +32 -7
- package/dist/kernel.js +461 -169
- package/dist/metrics-math.js +71 -0
- package/dist/okf.js +6 -3
- package/package.json +2 -2
- package/viewer/console.js +3 -11
- package/viewer/index.html +1 -3
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Pure metric and statistics helpers used by the benchmark/metrics code.
|
|
3
|
+
// This is a dependency-free leaf module: it imports nothing from the kernel,
|
|
4
|
+
// so pulling it out carries no circular-dependency risk. Leaf-first is the
|
|
5
|
+
// safe way to decompose the kernel — extract the bottom of the dependency
|
|
6
|
+
// graph first, where nothing depends back inward.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.codingRecallAt = codingRecallAt;
|
|
9
|
+
exports.codingPrecisionAt = codingPrecisionAt;
|
|
10
|
+
exports.codingNdcgAt = codingNdcgAt;
|
|
11
|
+
exports.codingMrr = codingMrr;
|
|
12
|
+
exports.averageNumber = averageNumber;
|
|
13
|
+
exports.percentileNumber = percentileNumber;
|
|
14
|
+
exports.roundDecimal = roundDecimal;
|
|
15
|
+
exports.countByKey = countByKey;
|
|
16
|
+
exports.titleCase = titleCase;
|
|
17
|
+
/** Recall@k: fraction of the relevant set retrieved within the top k. */
|
|
18
|
+
function codingRecallAt(retrieved, relevant, k) {
|
|
19
|
+
if (!relevant.size)
|
|
20
|
+
return 0;
|
|
21
|
+
return retrieved.slice(0, k).filter((item) => relevant.has(item.packet_id)).length / relevant.size;
|
|
22
|
+
}
|
|
23
|
+
/** Precision@k: fraction of the top k that is relevant. */
|
|
24
|
+
function codingPrecisionAt(retrieved, relevant, k) {
|
|
25
|
+
const rows = retrieved.slice(0, k);
|
|
26
|
+
return rows.length ? rows.filter((item) => relevant.has(item.packet_id)).length / rows.length : 0;
|
|
27
|
+
}
|
|
28
|
+
/** Normalized discounted cumulative gain at k (binary relevance). */
|
|
29
|
+
function codingNdcgAt(retrieved, relevant, k) {
|
|
30
|
+
const dcg = retrieved.slice(0, k).reduce((sum, item, index) => sum + (relevant.has(item.packet_id) ? 1 / Math.log2(index + 2) : 0), 0);
|
|
31
|
+
const idealHits = Math.min(relevant.size, k);
|
|
32
|
+
let ideal = 0;
|
|
33
|
+
for (let index = 0; index < idealHits; index += 1)
|
|
34
|
+
ideal += 1 / Math.log2(index + 2);
|
|
35
|
+
return ideal ? dcg / ideal : 0;
|
|
36
|
+
}
|
|
37
|
+
/** Mean reciprocal rank: 1 / rank of the first relevant hit, else 0. */
|
|
38
|
+
function codingMrr(retrieved, relevant) {
|
|
39
|
+
const index = retrieved.findIndex((item) => relevant.has(item.packet_id));
|
|
40
|
+
return index >= 0 ? 1 / (index + 1) : 0;
|
|
41
|
+
}
|
|
42
|
+
/** Arithmetic mean, 0 for an empty list. */
|
|
43
|
+
function averageNumber(values) {
|
|
44
|
+
return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : 0;
|
|
45
|
+
}
|
|
46
|
+
/** p-th percentile (0..1) via nearest-rank, 0 for an empty list. */
|
|
47
|
+
function percentileNumber(values, p) {
|
|
48
|
+
if (!values.length)
|
|
49
|
+
return 0;
|
|
50
|
+
const sorted = values.slice().sort((a, b) => a - b);
|
|
51
|
+
const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * p) - 1));
|
|
52
|
+
return sorted[index];
|
|
53
|
+
}
|
|
54
|
+
/** Round to a fixed number of decimal places. */
|
|
55
|
+
function roundDecimal(value, digits = 2) {
|
|
56
|
+
const factor = 10 ** digits;
|
|
57
|
+
return Math.round(value * factor) / factor;
|
|
58
|
+
}
|
|
59
|
+
/** Tally rows by a string key. */
|
|
60
|
+
function countByKey(rows, fn) {
|
|
61
|
+
const counts = {};
|
|
62
|
+
for (const row of rows) {
|
|
63
|
+
const key = fn(row);
|
|
64
|
+
counts[key] = (counts[key] ?? 0) + 1;
|
|
65
|
+
}
|
|
66
|
+
return counts;
|
|
67
|
+
}
|
|
68
|
+
/** Upper-case the first letter of each word. */
|
|
69
|
+
function titleCase(value) {
|
|
70
|
+
return value.replace(/\b[a-z]/g, (match) => match.toUpperCase());
|
|
71
|
+
}
|
package/dist/okf.js
CHANGED
|
@@ -132,8 +132,10 @@ function okfVerifiedStatus(packet) {
|
|
|
132
132
|
return "superseded";
|
|
133
133
|
if (packet.status === "deprecated")
|
|
134
134
|
return "deprecated";
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
// "verified" is earned, not born: capture provenance (repo_local_agent_capture)
|
|
136
|
+
// is not a check of the claim. Only an actual recheck (evidence-backed
|
|
137
|
+
// reverification, validation pass) may carry the label.
|
|
138
|
+
return (0, kernel_js_1.packetVerificationLabel)(packet);
|
|
137
139
|
}
|
|
138
140
|
// ---- packet -> OKF concept document ----
|
|
139
141
|
function packetToOkfConcept(packet) {
|
|
@@ -156,7 +158,8 @@ function packetToOkfConcept(packet) {
|
|
|
156
158
|
fm.push(`x-kage-status: ${yamlScalar(packet.status)}`);
|
|
157
159
|
fm.push(`x-kage-scope: ${yamlScalar(packet.scope)}`);
|
|
158
160
|
fm.push(`x-kage-visibility: ${yamlScalar(packet.visibility)}`);
|
|
159
|
-
|
|
161
|
+
// No x-kage-confidence: the field was a hardcoded 0.7 nobody computed or
|
|
162
|
+
// consumed — a number that means nothing must not ship as trust metadata.
|
|
160
163
|
fm.push(`x-kage-verified: ${yamlScalar(okfVerifiedStatus(packet))}`);
|
|
161
164
|
if (packet.paths?.length)
|
|
162
165
|
fm.push(`x-kage-paths: ${yamlList(packet.paths)}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kage-core/kage-graph-mcp",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.3.0",
|
|
4
|
+
"description": "Agent memory on Google's Open Knowledge Format (OKF). Kage maintains your coding agents' OKF memory bundle in git and verifies every concept against your code, deterministically. The verification and freshness layer OKF leaves out. MCP server, no account, no API key.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist/**/*.js",
|
package/viewer/console.js
CHANGED
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
// ---- nav ----
|
|
68
68
|
var META = {
|
|
69
69
|
dashboard: ["kage://dashboard", "Dashboard", "Your team's captured knowledge — decisions, runbooks, and fixes — at a glance."],
|
|
70
|
-
gains: ["kage://gains", "What Kage saved you", "Tokens
|
|
70
|
+
gains: ["kage://gains", "What Kage saved you", "Tokens saved and bad memories caught — receipts, not vibes."],
|
|
71
71
|
overview: ["kage://trust", "Memory trust", "Whether this repo's agent memory can be trusted — at a glance."],
|
|
72
72
|
graph: ["kage://memory-map", "Memory ↔ code map", "Each packet anchored to the files it's grounded in. Hover a node to inspect."],
|
|
73
73
|
memory: ["kage://memory", "Memory", "Every packet Kage has stored, with health and grounding."],
|
|
@@ -137,12 +137,6 @@
|
|
|
137
137
|
// value.json is the raw ledger written by recall: { totals, events[] }. Windows are
|
|
138
138
|
// recomputed here with the same rules as `kage gains` (today = local midnight,
|
|
139
139
|
// 7d = rolling, all-time = totals so trimmed events never lose history).
|
|
140
|
-
var DOLLARS_PER_MILLION_TOKENS = 15;
|
|
141
|
-
function dollars(tokens) { return (tokens / 1e6) * DOLLARS_PER_MILLION_TOKENS; }
|
|
142
|
-
function fmtDollars(tokens) {
|
|
143
|
-
var d = dollars(tokens);
|
|
144
|
-
return "$" + (d >= 100 ? Math.round(d) : d.toFixed(2));
|
|
145
|
-
}
|
|
146
140
|
function summarizeWindow(events, cutoff) {
|
|
147
141
|
var w = { tokens_saved: 0, stale_withheld: 0, recalls: 0, caller_answers: 0 };
|
|
148
142
|
events.forEach(function (e) {
|
|
@@ -175,7 +169,6 @@
|
|
|
175
169
|
var big = el("div", "big");
|
|
176
170
|
big.appendChild(el("b", null, all.tokens_saved ? "0" : "—"));
|
|
177
171
|
big.appendChild(el("span", "unit", "tokens saved, all time"));
|
|
178
|
-
if (all.tokens_saved) big.appendChild(el("span", "dollars", "≈ " + fmtDollars(all.tokens_saved)));
|
|
179
172
|
hh.appendChild(big);
|
|
180
173
|
hh.appendChild(el("p", "sub", all.tokens_saved
|
|
181
174
|
? "Context your agents did not have to re-read from source, because Kage served grounded memory instead."
|
|
@@ -186,7 +179,6 @@
|
|
|
186
179
|
var w = el("div", "r-win");
|
|
187
180
|
w.appendChild(el("div", "k", p[0]));
|
|
188
181
|
w.appendChild(el("div", "v", fmt(p[1].tokens_saved) + " tok"));
|
|
189
|
-
w.appendChild(el("div", "d", "≈ " + fmtDollars(p[1].tokens_saved)));
|
|
190
182
|
wins.appendChild(w);
|
|
191
183
|
});
|
|
192
184
|
hero.appendChild(wins);
|
|
@@ -203,13 +195,13 @@
|
|
|
203
195
|
lines.appendChild(row);
|
|
204
196
|
});
|
|
205
197
|
hero.appendChild(lines);
|
|
206
|
-
hero.appendChild(el("div", "r-foot", "
|
|
198
|
+
hero.appendChild(el("div", "r-foot", "ledger: .agent_memory/reports/value.json · verify: kage gains"));
|
|
207
199
|
if (all.tokens_saved) countUp(big.querySelector("b"), all.tokens_saved, 900);
|
|
208
200
|
|
|
209
201
|
if (tiles) {
|
|
210
202
|
tiles.textContent = "";
|
|
211
203
|
[
|
|
212
|
-
{ k: "Saved (7 days)", v: fmt(week.tokens_saved), s: "
|
|
204
|
+
{ k: "Saved (7 days)", v: fmt(week.tokens_saved), s: "tokens of context not re-read", cls: "green" },
|
|
213
205
|
{ k: "Recalls served (7d)", v: fmt(week.recalls), s: fmt(all.recalls) + " all-time", cls: "green" },
|
|
214
206
|
{ k: "Stale caught", v: fmt(all.stale_withheld), s: all.stale_withheld ? "withheld before they misled an agent" : "nothing withheld yet", cls: all.stale_withheld ? "warn" : "" },
|
|
215
207
|
{ k: "Graph answers", v: fmt(all.caller_answers), s: "caller questions answered from the code graph", cls: "code" },
|
package/viewer/index.html
CHANGED
|
@@ -69,14 +69,12 @@
|
|
|
69
69
|
.receipt .r-hero .big { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
|
|
70
70
|
.receipt .r-hero .big b { font: 600 64px/0.95 var(--serif); letter-spacing: -0.02em; color: var(--gain); font-variant-numeric: tabular-nums; }
|
|
71
71
|
.receipt .r-hero .big .unit { color: var(--muted); font: 500 16px/1 var(--sans); }
|
|
72
|
-
.receipt .r-hero .big .dollars { color: var(--ink); font: 600 22px/1 var(--serif); }
|
|
73
72
|
.receipt .r-hero .sub { margin-top: 10px; color: var(--muted); font: 400 13.5px/1.5 var(--sans); max-width: 560px; }
|
|
74
73
|
.receipt .r-windows { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px dashed var(--line-strong); }
|
|
75
74
|
.receipt .r-win { padding: 16px 24px; border-left: 1px dashed var(--line-strong); }
|
|
76
75
|
.receipt .r-win:first-child { border-left: 0; }
|
|
77
76
|
.receipt .r-win .k { color: var(--faint); font: 600 10px/1 var(--mono); text-transform: uppercase; letter-spacing: 0.1em; }
|
|
78
77
|
.receipt .r-win .v { margin-top: 8px; font: 600 24px/1 var(--serif); color: var(--ink); font-variant-numeric: tabular-nums; }
|
|
79
|
-
.receipt .r-win .d { margin-top: 5px; color: var(--gain); font: 600 12px/1 var(--mono); }
|
|
80
78
|
.receipt .r-lines { border-top: 1px dashed var(--line-strong); padding: 14px 24px 16px; }
|
|
81
79
|
.receipt .r-line { display: flex; align-items: baseline; gap: 10px; padding: 5px 0; font: 400 13.5px/1.5 var(--sans); color: var(--ink); }
|
|
82
80
|
.receipt .r-line .dots { flex: 1; border-bottom: 1px dotted var(--line-strong); transform: translateY(-4px); }
|
|
@@ -290,7 +288,7 @@
|
|
|
290
288
|
<header class="head">
|
|
291
289
|
<span class="eyebrow" id="eyebrow">kage://gains</span>
|
|
292
290
|
<h1 id="title">What Kage saved you</h1>
|
|
293
|
-
<p id="subtitle">Tokens
|
|
291
|
+
<p id="subtitle">Tokens saved and bad memories caught — receipts, not vibes.</p>
|
|
294
292
|
</header>
|
|
295
293
|
<div class="wrap">
|
|
296
294
|
<section class="section active" id="section-dashboard">
|