@kage-core/kage-graph-mcp 2.5.6 → 3.0.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/cli.js CHANGED
@@ -10,12 +10,12 @@ const node_process_1 = require("node:process");
10
10
  const daemon_js_1 = require("./daemon.js");
11
11
  const kernel_js_1 = require("./kernel.js");
12
12
  const graph_registry_js_1 = require("./graph-registry.js");
13
+ const okf_js_1 = require("./okf.js");
13
14
  const CORE_USAGE = `Kage — code-grounded memory for coding agents
14
15
 
15
16
  Core commands:
16
17
  kage install [--project <dir>] one-shot: init + index + auto-wire detected agents
17
18
  kage scan --project <dir> 60-second truth report on any repo (zero setup)
18
- kage demo watch the reject/withhold loop run in a sandbox
19
19
  kage init --project <dir> create repo memory (.agent_memory only)
20
20
  kage index --project <dir> [--full] build/refresh code graph + indexes
21
21
  kage recall "<query>" --project <dir> grounded recall from repo memory
@@ -52,7 +52,7 @@ Usage:
52
52
  kage hook status --project <dir> [--json]
53
53
  kage hook uninstall --project <dir> [--json]
54
54
  kage refresh --project <dir> [--full] [--force] [--json]
55
- kage merge-packet <ours> <base> <theirs> git merge driver for .agent_memory/packets/*.json
55
+ kage merge-packet <ours> <base> <theirs> git merge driver for .agent_memory/packets/*.md
56
56
  kage gc --project <dir> [--dry-run] [--force] [--json]
57
57
  kage compact --project <dir> [--dry-run] [--json]
58
58
  kage verify --project <dir> [--id <packet-id>] [--json]
@@ -449,6 +449,52 @@ async function main() {
449
449
  console.log(" npx -y @kage-core/kage-graph-mcp install");
450
450
  return;
451
451
  }
452
+ if (command === "okf") {
453
+ // OKF (Open Knowledge Format) is Kage's standard on-disk memory format.
454
+ const sub = args[1] && !args[1].startsWith("--") ? args[1] : "";
455
+ const project = (0, node_path_1.resolve)(projectArg(args));
456
+ if (sub === "migrate" || sub === "export") {
457
+ const result = (0, okf_js_1.migratePacketsToOkf)(project, { includePending: args.includes("--pending") });
458
+ console.log(`Kage memory → OKF: wrote ${result.written} concept(s) to ${result.root}`);
459
+ for (const [type, count] of Object.entries(result.byType).sort((a, b) => b[1] - a[1])) {
460
+ console.log(` ${type}: ${count}`);
461
+ }
462
+ console.log("\nConformant OKF bundle: index.md (progressive disclosure), log.md (history), concept docs.");
463
+ console.log("Trust metadata rides in x-kage-* frontmatter, so any OKF consumer (incl. Google's visualizer) reads it unchanged.");
464
+ return;
465
+ }
466
+ if (sub === "lint") {
467
+ const target = args[2] && !args[2].startsWith("--") ? (0, node_path_1.resolve)(args[2]) : (0, okf_js_1.okfBundleDir)(project);
468
+ const { files, failures } = (0, okf_js_1.lintOkfBundle)(target);
469
+ if (failures.length === 0) {
470
+ console.log(`OKF lint: ${files} concept(s) in ${target} — all conformant.`);
471
+ return;
472
+ }
473
+ console.log(`OKF lint: ${failures.length}/${files} concept(s) non-conformant in ${target}:`);
474
+ for (const failure of failures)
475
+ console.log(` ${failure.path}: ${failure.errors.join("; ")}`);
476
+ process.exit(1);
477
+ }
478
+ if (sub === "import") {
479
+ const dir = args[2] && !args[2].startsWith("--") ? (0, node_path_1.resolve)(args[2]) : (0, okf_js_1.okfBundleDir)(project);
480
+ const packets = (0, okf_js_1.loadOkfConcepts)(dir, { projectDir: project });
481
+ if (args.includes("--json")) {
482
+ console.log(JSON.stringify(packets, null, 2));
483
+ return;
484
+ }
485
+ console.log(`Read ${packets.length} concept(s) from OKF bundle ${dir}`);
486
+ for (const packet of packets.slice(0, 20))
487
+ console.log(` [${packet.type}] ${packet.title}`);
488
+ if (packets.length > 20)
489
+ console.log(` … and ${packets.length - 20} more`);
490
+ return;
491
+ }
492
+ console.log("kage okf — Open Knowledge Format is Kage's standard memory format.");
493
+ console.log(" kage okf migrate [--project <dir>] [--pending] packets → OKF bundle (.agent_memory/okf)");
494
+ console.log(" kage okf lint [<dir|file>] [--project <dir>] check OKF conformance");
495
+ console.log(" kage okf import [<dir>] [--project <dir>] [--json] read an OKF bundle back into packets");
496
+ return;
497
+ }
452
498
  if (command === "init") {
453
499
  const withPolicy = args.includes("--with-policy");
454
500
  const result = (0, kernel_js_1.initProject)(projectArg(args), { policy: withPolicy });
@@ -1406,6 +1452,10 @@ async function main() {
1406
1452
  `~${(0, kernel_js_1.formatTokenCount)(summary.all_time.replay_tokens)} all time ` +
1407
1453
  `(discovery cost of served memories vs their compressed read cost)`);
1408
1454
  }
1455
+ const usdOverridden = Number.isFinite(Number(process.env.KAGE_USD_PER_MTOK)) && Number(process.env.KAGE_USD_PER_MTOK) > 0;
1456
+ console.log(`\nDollars estimated at $${kernel_js_1.VALUE_DOLLARS_PER_MILLION_TOKENS}/1M input tokens ` +
1457
+ `(${usdOverridden ? "via KAGE_USD_PER_MTOK" : "Sonnet-class default — set KAGE_USD_PER_MTOK for your model"}). ` +
1458
+ `Ledger: .agent_memory/reports/value.json`);
1409
1459
  return;
1410
1460
  }
1411
1461
  if (command === "file-context") {
package/dist/daemon.js CHANGED
@@ -202,7 +202,7 @@ function startLiveFeed(projectRoot, options = {}) {
202
202
  try {
203
203
  watchers.push((0, node_fs_1.watch)(packetsDir, (_event, filename) => {
204
204
  const name = String(filename ?? "");
205
- if (!name.endsWith(".json"))
205
+ if (!name.endsWith(".json") && !name.endsWith(".md"))
206
206
  return;
207
207
  debounced(`packet:${name}`, () => onPacketChange(name));
208
208
  }));
package/dist/kernel.js CHANGED
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.PACKET_MERGE_DRIVER_CONFIG = exports.PACKET_MERGE_ATTRIBUTE_LINE = exports.AUTO_DISTILL_SIGNAL_THRESHOLD = exports.AUTO_DISTILL_TAG = exports.SETUP_AGENTS = exports.MEMORY_TYPES = exports.PACKET_SCHEMA_VERSION = void 0;
36
+ exports.PACKET_MERGE_DRIVER_CONFIG = exports.PACKET_MERGE_ATTRIBUTE_LINE = exports.AUTO_DISTILL_SIGNAL_THRESHOLD = exports.AUTO_DISTILL_TAG = exports.VALUE_DOLLARS_PER_MILLION_TOKENS = exports.SETUP_AGENTS = exports.MEMORY_TYPES = exports.PACKET_SCHEMA_VERSION = void 0;
37
37
  exports.memoryRoot = memoryRoot;
38
38
  exports.packetsDir = packetsDir;
39
39
  exports.pendingDir = pendingDir;
@@ -208,6 +208,7 @@ const node_path_1 = require("node:path");
208
208
  const node_worker_threads_1 = require("node:worker_threads");
209
209
  const ts = __importStar(require("typescript"));
210
210
  const index_js_1 = require("./registry/index.js");
211
+ const okf_js_1 = require("./okf.js");
211
212
  exports.PACKET_SCHEMA_VERSION = 2;
212
213
  exports.MEMORY_TYPES = [
213
214
  "repo_map",
@@ -271,7 +272,11 @@ const AGENTS_POLICY_END = "<!-- END_KAGE_MEMORY_POLICY_V1 -->";
271
272
  const AGENTS_POLICY = `${AGENTS_POLICY_MARKER}
272
273
  # Kage Memory Harness
273
274
 
274
- This repo uses Kage as an automatic memory harness for coding agents.
275
+ This repo uses Kage as an automatic memory harness for coding agents. Memory is
276
+ stored and exchanged in Open Knowledge Format (OKF) — markdown concept files under
277
+ \`.agent_memory/packets/\`, with Kage's verification metadata in OKF-legal \`x-kage-*\`
278
+ frontmatter, readable by any OKF consumer. Use \`kage okf migrate|lint|import\` to
279
+ work with OKF bundles.
275
280
 
276
281
  ## Automatic Recall
277
282
 
@@ -466,10 +471,23 @@ function ensureDir(path) {
466
471
  (0, node_fs_1.mkdirSync)(path, { recursive: true });
467
472
  }
468
473
  function readJson(path) {
474
+ // Packet files are OKF concept docs (.md); every other store artifact is JSON.
475
+ // Routing the dispatch here makes all packet readers (supersede, stale, repair,
476
+ // sync, …) format-aware without touching each call site.
477
+ if (path.endsWith(".md")) {
478
+ const packet = (0, okf_js_1.okfConceptToPacket)((0, node_fs_1.readFileSync)(path, "utf8"));
479
+ if (!packet)
480
+ throw new Error(`not a parseable OKF concept: ${path}`);
481
+ return packet;
482
+ }
469
483
  return JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
470
484
  }
471
485
  function writeJson(path, value) {
472
486
  ensureDir((0, node_path_1.dirname)(path));
487
+ if (path.endsWith(".md")) {
488
+ (0, node_fs_1.writeFileSync)(path, (0, okf_js_1.packetToOkfConcept)(value), "utf8");
489
+ return;
490
+ }
473
491
  (0, node_fs_1.writeFileSync)(path, `${JSON.stringify(value, null, 2)}\n`, "utf8");
474
492
  }
475
493
  function slugify(input) {
@@ -483,7 +501,7 @@ function slugify(input) {
483
501
  }
484
502
  function packetFileName(packet) {
485
503
  const idHash = (0, node_crypto_1.createHash)("sha256").update(packet.id).digest("hex").slice(0, 8);
486
- return `${packet.type}-${slugify(packet.title)}-${idHash}.json`;
504
+ return `${packet.type}-${slugify(packet.title)}-${idHash}.md`;
487
505
  }
488
506
  function repoKey(projectDir) {
489
507
  const configPath = (0, node_path_1.join)(projectDir, ".git", "config");
@@ -1017,8 +1035,14 @@ function recordRecallAccess(projectDir, results) {
1017
1035
  // ---------------------------------------------------------------------------
1018
1036
  const VALUE_LEDGER_SCHEMA_VERSION = 1;
1019
1037
  const VALUE_LEDGER_EVENT_CAP = 5000;
1020
- // Rough Sonnet-class input price used for the dollar estimate: $15 per 1M tokens.
1021
- const VALUE_DOLLARS_PER_MILLION_TOKENS = 15;
1038
+ // Input price used for the dollar estimate. Default: Sonnet-class ~$3 per 1M input tokens (the
1039
+ // typical coding-agent tier) — deliberately conservative so the savings figure never overstates.
1040
+ // Override with KAGE_USD_PER_MTOK to match your model (e.g. 15 for Opus, 2.5 for GPT-4o, 0.8 for
1041
+ // Haiku). The old default ($15, Opus pricing) overstated savings ~5x for most users.
1042
+ exports.VALUE_DOLLARS_PER_MILLION_TOKENS = (() => {
1043
+ const raw = Number(process.env.KAGE_USD_PER_MTOK);
1044
+ return Number.isFinite(raw) && raw > 0 ? raw : 3;
1045
+ })();
1022
1046
  function valueLedgerPath(projectDir) {
1023
1047
  return (0, node_path_1.join)(reportsDir(projectDir), "value.json");
1024
1048
  }
@@ -1112,7 +1136,7 @@ function recordValueEvent(projectDir, event) {
1112
1136
  recordValueEvents(projectDir, [event]);
1113
1137
  }
1114
1138
  function estimatedTokenDollars(tokensSaved) {
1115
- return Number(((tokensSaved / 1_000_000) * VALUE_DOLLARS_PER_MILLION_TOKENS).toFixed(2));
1139
+ return Number(((tokensSaved / 1_000_000) * exports.VALUE_DOLLARS_PER_MILLION_TOKENS).toFixed(2));
1116
1140
  }
1117
1141
  function summarizeValueWindow(events, cutoff) {
1118
1142
  const window = { tokens_saved: 0, replay_tokens: 0, stale_withheld: 0, stale_caught: 0, recalls: 0, caller_answers: 0 };
@@ -2369,9 +2393,33 @@ function walkFiles(root, predicate) {
2369
2393
  // Tolerant packet read: a single corrupt or merge-conflicted packet (e.g. an
2370
2394
  // unresolved `<<<<<<<` from a teammate's git merge) must not take down all of
2371
2395
  // recall/verify/compact. Skip the bad file with a warning and keep going.
2396
+ // Packets are stored as OKF concept documents (.md). Legacy .json packets stay
2397
+ // readable so existing stores and test fixtures keep loading during/after the swap.
2398
+ function isPacketFile(name) {
2399
+ return name.endsWith(".md") || name.endsWith(".json");
2400
+ }
2401
+ // Read a packet file from disk, dispatching on format. Throws on an unparseable
2402
+ // file (same contract as the JSON reader it replaces); callers that tolerate bad
2403
+ // files go through tryReadPacket.
2404
+ function readPacketFromDisk(path) {
2405
+ if (path.endsWith(".md")) {
2406
+ const packet = (0, okf_js_1.okfConceptToPacket)((0, node_fs_1.readFileSync)(path, "utf8"));
2407
+ if (!packet)
2408
+ throw new Error(`not a parseable OKF concept: ${path}`);
2409
+ return packet;
2410
+ }
2411
+ return readJson(path);
2412
+ }
2413
+ // Write a packet file, preserving its on-disk format (.md = OKF, legacy .json).
2414
+ function writePacketToDisk(path, packet) {
2415
+ if (path.endsWith(".md"))
2416
+ (0, node_fs_1.writeFileSync)(path, (0, okf_js_1.packetToOkfConcept)(packet), "utf8");
2417
+ else
2418
+ writeJson(path, packet);
2419
+ }
2372
2420
  function tryReadPacket(path) {
2373
2421
  try {
2374
- return readJson(path);
2422
+ return readPacketFromDisk(path);
2375
2423
  }
2376
2424
  catch (error) {
2377
2425
  process.stderr.write(`kage: skipping unreadable memory packet ${path}: ${error.message}\n`);
@@ -2382,7 +2430,7 @@ function loadPacketsFromDir(dir) {
2382
2430
  if (!(0, node_fs_1.existsSync)(dir))
2383
2431
  return [];
2384
2432
  return (0, node_fs_1.readdirSync)(dir)
2385
- .filter((name) => name.endsWith(".json"))
2433
+ .filter(isPacketFile)
2386
2434
  .sort()
2387
2435
  .flatMap((name) => {
2388
2436
  const packet = tryReadPacket((0, node_path_1.join)(dir, name));
@@ -2393,7 +2441,7 @@ function loadPacketEntriesFromDir(dir) {
2393
2441
  if (!(0, node_fs_1.existsSync)(dir))
2394
2442
  return [];
2395
2443
  return (0, node_fs_1.readdirSync)(dir)
2396
- .filter((name) => name.endsWith(".json"))
2444
+ .filter(isPacketFile)
2397
2445
  .sort()
2398
2446
  .flatMap((name) => {
2399
2447
  const path = (0, node_path_1.join)(dir, name);
@@ -2416,7 +2464,7 @@ function recallablePendingPackets(projectDir) {
2416
2464
  function writePacket(projectDir, packet, statusDir) {
2417
2465
  const dir = statusDir === "packets" ? packetsDir(projectDir) : pendingDir(projectDir);
2418
2466
  const path = (0, node_path_1.join)(dir, packetFileName(packet));
2419
- writeJson(path, packet);
2467
+ writePacketToDisk(path, packet);
2420
2468
  return path;
2421
2469
  }
2422
2470
  function memoryAuditPath(projectDir) {
@@ -2815,7 +2863,7 @@ const NOISE_PATH_PREFIXES = [
2815
2863
  "elm-stuff/",
2816
2864
  ];
2817
2865
  function isReviewableMemoryPath(filePath) {
2818
- return /^\.agent_memory\/(?:packets|pending)\/[^/]+\.json$/.test(filePath);
2866
+ return /^\.agent_memory\/(?:packets|pending)\/[^/]+\.(?:json|md)$/.test(filePath);
2819
2867
  }
2820
2868
  function isNoisePath(filePath) {
2821
2869
  if (isReviewableMemoryPath(filePath))
@@ -6731,7 +6779,7 @@ function readCurrentKnowledgeGraph(projectDir, codeGraph, expectedInputHash) {
6731
6779
  function graphFastFingerprint(projectDir) {
6732
6780
  const packetPaths = (0, node_fs_1.existsSync)(packetsDir(projectDir))
6733
6781
  ? (0, node_fs_1.readdirSync)(packetsDir(projectDir))
6734
- .filter((name) => name.endsWith(".json"))
6782
+ .filter(isPacketFile)
6735
6783
  .map((name) => (0, node_path_1.join)(packetsDir(projectDir), name))
6736
6784
  : [];
6737
6785
  const paths = [
@@ -7147,7 +7195,7 @@ function compactProject(projectDir, options = {}) {
7147
7195
  if (hardReason) {
7148
7196
  deprecated.push({ id: packet.id, title: packet.title, reason: hardReason });
7149
7197
  if (!dryRun)
7150
- writeJson(path, { ...packet, status: "deprecated", updated_at: nowIso() });
7198
+ writePacketToDisk(path, { ...packet, status: "deprecated", updated_at: nowIso() });
7151
7199
  continue;
7152
7200
  }
7153
7201
  const meaningful = packet.paths.filter((p) => meaningfulMemoryPath(p) && !shouldSkipRepoMemoryPath(p));
@@ -13939,7 +13987,7 @@ function createPublicCandidate(projectDir, id) {
13939
13987
  if (!validation.ok)
13940
13988
  return { ok: false, errors: validation.errors };
13941
13989
  const path = (0, node_path_1.join)(publicCandidatesDir(projectDir), packetFileName(packet));
13942
- writeJson(path, packet);
13990
+ writePacketToDisk(path, packet);
13943
13991
  return { ok: true, packet, path, errors: [] };
13944
13992
  }
13945
13993
  function registryRecommendations(projectDir) {
@@ -15474,7 +15522,7 @@ function distillSession(projectDir, sessionId, options = {}) {
15474
15522
  result.path = promotedPath;
15475
15523
  }
15476
15524
  else {
15477
- writeJson(result.path, result.packet);
15525
+ writePacketToDisk(result.path, result.packet);
15478
15526
  }
15479
15527
  return result;
15480
15528
  };
@@ -15686,7 +15734,7 @@ function createDiffChangeMemory(projectDir, summary) {
15686
15734
  const existing = (0, node_fs_1.readdirSync)(packetsDir(projectDir)).filter((name) => name.startsWith(stalePrefix) && name !== stableFileName);
15687
15735
  for (const name of existing) {
15688
15736
  const stale = (0, node_path_1.join)(packetsDir(projectDir), name);
15689
- const stalePacket = readJson(stale);
15737
+ const stalePacket = readPacketFromDisk(stale);
15690
15738
  if (stalePacket?.type === "workflow" && stalePacket?.title === title) {
15691
15739
  (0, node_fs_1.unlinkSync)(stale);
15692
15740
  }
@@ -16044,7 +16092,7 @@ function prCheck(projectDir) {
16044
16092
  .split(/\r?\n/)
16045
16093
  .map(parsePorcelainPath)
16046
16094
  .map((path) => path.replace(/^.* -> /, ""))
16047
- .filter((path) => path.startsWith(".agent_memory/packets/") && path.endsWith(".json"))).sort();
16095
+ .filter((path) => path.startsWith(".agent_memory/packets/") && isPacketFile(path))).sort();
16048
16096
  const codeGraphCurrent = graphIsCurrent(projectDir, ".agent_memory/code_graph/graph.json", { head: overlay.head, tree, inputHash: codeInputHash });
16049
16097
  const memoryGraphCurrent = graphIsCurrent(projectDir, ".agent_memory/graph/graph.json", { head: overlay.head, tree, inputHash: memoryInputHash });
16050
16098
  const sessions = kageSessionCaptureReport(projectDir);
@@ -16412,8 +16460,8 @@ function recordFeedback(projectDir, id, feedback) {
16412
16460
  if (!["helpful", "wrong", "stale"].includes(feedback)) {
16413
16461
  return { ok: false, errors: [`Invalid feedback: ${feedback}`] };
16414
16462
  }
16415
- for (const path of walkFiles(packetsDir(projectDir), (candidate) => candidate.endsWith(".json"))) {
16416
- const packet = readJson(path);
16463
+ for (const path of walkFiles(packetsDir(projectDir), isPacketFile)) {
16464
+ const packet = readPacketFromDisk(path);
16417
16465
  if (packet.id !== id)
16418
16466
  continue;
16419
16467
  const quality = (packet.quality ?? {});
@@ -16434,7 +16482,7 @@ function recordFeedback(projectDir, id, feedback) {
16434
16482
  stale_reported_at: packet.updated_at,
16435
16483
  };
16436
16484
  }
16437
- writeJson(path, packet);
16485
+ writePacketToDisk(path, packet);
16438
16486
  recordMemoryAudit(projectDir, "feedback", [packet], {
16439
16487
  feedback,
16440
16488
  path: (0, node_path_1.relative)(projectDir, path),
@@ -16454,9 +16502,9 @@ function validateProject(projectDir) {
16454
16502
  [pendingDir(projectDir), "pending"],
16455
16503
  [publicCandidatesDir(projectDir), "public candidate"],
16456
16504
  ]) {
16457
- for (const packetPath of walkFiles(dir, (path) => path.endsWith(".json"))) {
16505
+ for (const packetPath of walkFiles(dir, isPacketFile)) {
16458
16506
  try {
16459
- const packet = readJson(packetPath);
16507
+ const packet = readPacketFromDisk(packetPath);
16460
16508
  const validation = validatePacket(packet, (0, node_path_1.relative)(projectDir, packetPath));
16461
16509
  errors.push(...validation.errors);
16462
16510
  warnings.push(...validation.warnings);
@@ -16654,12 +16702,17 @@ function resolveConflictedPacket(content) {
16654
16702
  for (const side of [sides.ours, sides.theirs]) {
16655
16703
  try {
16656
16704
  const parsed = JSON.parse(side);
16657
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
16705
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
16658
16706
  candidates.push(parsed);
16707
+ continue;
16708
+ }
16659
16709
  }
16660
16710
  catch {
16661
- // This side does not parse; the other side may still win.
16711
+ // Not JSON fall through and try the OKF concept format below.
16662
16712
  }
16713
+ const okf = (0, okf_js_1.okfConceptToPacket)(side);
16714
+ if (okf)
16715
+ candidates.push(okf);
16663
16716
  }
16664
16717
  if (!candidates.length)
16665
16718
  return null;
@@ -16672,7 +16725,7 @@ function resolveConflictedPacket(content) {
16672
16725
  // exit 0 on success, non-zero to leave the conflict). v1 policy is whole-file
16673
16726
  // newest-wins by updated_at — packets are single facts, so field-level merges
16674
16727
  // buy little over taking the most recently verified side.
16675
- exports.PACKET_MERGE_ATTRIBUTE_LINE = ".agent_memory/packets/*.json merge=kage-packet";
16728
+ exports.PACKET_MERGE_ATTRIBUTE_LINE = ".agent_memory/packets/*.md merge=kage-packet";
16676
16729
  exports.PACKET_MERGE_DRIVER_CONFIG = 'git config merge.kage-packet.driver "npx -y @kage-core/kage-graph-mcp merge-packet %A %O %B"';
16677
16730
  function mergePacketFiles(oursPath, basePath, theirsPath) {
16678
16731
  void basePath; // Reserved for a future field-level three-way merge.
@@ -16680,6 +16733,10 @@ function mergePacketFiles(oursPath, basePath, theirsPath) {
16680
16733
  const raw = safeReadText(path);
16681
16734
  if (raw === null)
16682
16735
  return null;
16736
+ if (path.endsWith(".md")) {
16737
+ const packet = (0, okf_js_1.okfConceptToPacket)(raw);
16738
+ return packet ? { raw, packet } : null;
16739
+ }
16683
16740
  try {
16684
16741
  const parsed = JSON.parse(raw);
16685
16742
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
@@ -16729,7 +16786,7 @@ function ensurePacketMergeAttributes(projectDir) {
16729
16786
  const path = (0, node_path_1.join)(projectDir, ".gitattributes");
16730
16787
  const existing = safeReadText(path) ?? "";
16731
16788
  const lines = existing.split(/\r?\n/);
16732
- const pattern = /^\.agent_memory\/packets\/\*\.json\s+merge=/;
16789
+ const pattern = /^\.agent_memory\/packets\/\*\.(?:json|md)\s+merge=/;
16733
16790
  const index = lines.findIndex((line) => pattern.test(line.trim()));
16734
16791
  if (index !== -1) {
16735
16792
  if (lines[index].trim() === exports.PACKET_MERGE_ATTRIBUTE_LINE)
@@ -16756,7 +16813,7 @@ function repairProject(projectDir, options = {}) {
16756
16813
  [publicCandidatesDir(projectDir), "public-candidates"],
16757
16814
  ];
16758
16815
  for (const [dir, label] of packetDirs) {
16759
- for (const path of walkFiles(dir, (candidate) => candidate.endsWith(".json"))) {
16816
+ for (const path of walkFiles(dir, isPacketFile)) {
16760
16817
  const target = `${label}/${(0, node_path_1.basename)(path)}`;
16761
16818
  let raw;
16762
16819
  try {
@@ -16953,14 +17010,14 @@ function remediationFor(error) {
16953
17010
  return "npx -y kage-graph-mcp doctor --project .";
16954
17011
  }
16955
17012
  function approvePending(projectDir, id) {
16956
- const pendingFiles = walkFiles(pendingDir(projectDir), (path) => path.endsWith(".json"));
17013
+ const pendingFiles = walkFiles(pendingDir(projectDir), isPacketFile);
16957
17014
  for (const path of pendingFiles) {
16958
- const packet = readJson(path);
17015
+ const packet = readPacketFromDisk(path);
16959
17016
  if (packet.id === id) {
16960
17017
  packet.status = "approved";
16961
17018
  packet.updated_at = nowIso();
16962
17019
  const target = (0, node_path_1.join)(packetsDir(projectDir), packetFileName(packet));
16963
- writeJson(target, packet);
17020
+ writePacketToDisk(target, packet);
16964
17021
  (0, node_fs_1.renameSync)(path, `${path}.approved`);
16965
17022
  recordMemoryAudit(projectDir, "approve", [packet], {
16966
17023
  from: (0, node_path_1.relative)(projectDir, path),
@@ -16973,9 +17030,9 @@ function approvePending(projectDir, id) {
16973
17030
  throw new Error(`Pending packet not found: ${id}`);
16974
17031
  }
16975
17032
  function rejectPending(projectDir, id) {
16976
- const pendingFiles = walkFiles(pendingDir(projectDir), (path) => path.endsWith(".json"));
17033
+ const pendingFiles = walkFiles(pendingDir(projectDir), isPacketFile);
16977
17034
  for (const path of pendingFiles) {
16978
- const packet = readJson(path);
17035
+ const packet = readPacketFromDisk(path);
16979
17036
  if (packet.id === id) {
16980
17037
  const target = `${path}.rejected`;
16981
17038
  (0, node_fs_1.renameSync)(path, target);
@@ -17733,7 +17790,7 @@ function syncIdentityArgs(memoryDir) {
17733
17790
  }
17734
17791
  const SYNC_SETUP_HINT = "Run: kage sync setup --remote <git-url>";
17735
17792
  function syncPacketFile(file) {
17736
- return file.startsWith("packets/") && file.endsWith(".json");
17793
+ return file.startsWith("packets/") && isPacketFile(file);
17737
17794
  }
17738
17795
  function countSyncPacketFiles(namesOutput) {
17739
17796
  return namesOutput
@@ -17816,7 +17873,7 @@ function rebaseOntoUpstream(memoryDir, upstream) {
17816
17873
  for (const file of conflicted) {
17817
17874
  if (!syncPacketFile(file)) {
17818
17875
  runSyncGit(memoryDir, ["rebase", "--abort"]);
17819
- return { ok: false, resolved, conflictBackups, error: `kage sync only auto-resolves packets/*.json conflicts; ${file} needs manual resolution in ${memoryDir}.` };
17876
+ return { ok: false, resolved, conflictBackups, error: `kage sync only auto-resolves packets/*.md conflicts; ${file} needs manual resolution in ${memoryDir}.` };
17820
17877
  }
17821
17878
  const resolution = resolvePacketSyncConflict(memoryDir, file);
17822
17879
  if (!resolution.ok) {
package/dist/okf.js ADDED
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ // Kage <-> Google Open Knowledge Format (OKF) adapter.
3
+ //
4
+ // OKF (github.com/GoogleCloudPlatform/knowledge-catalog/okf) is Kage's standard
5
+ // on-disk interchange format for memory. A Kage memory packet is rendered as an
6
+ // OKF concept document: a Markdown file with YAML frontmatter whose only required
7
+ // field is `type`, plus the recommended `title` / `description` / `resource` /
8
+ // `tags` / `timestamp`. Kage's trust metadata (status, scope, confidence, the
9
+ // content-hash anchors, the lineage) rides along as OKF-legal custom `x-kage-*`
10
+ // frontmatter keys — OKF mandates that consumers preserve unknown producer fields,
11
+ // so a Kage bundle stays fully OKF-conformant and renders in any OKF consumer.
12
+ //
13
+ // Round-trip is lossless: every Kage-authored concept embeds the exact packet as a
14
+ // fenced ```json kage-state block in the body, so packet -> concept -> packet is an
15
+ // identity. Concepts WITHOUT that block (hand-authored or produced by another tool)
16
+ // are imported best-effort from their frontmatter + body, which is what lets Kage
17
+ // consume any third-party OKF bundle.
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.okfType = okfType;
20
+ exports.kageType = kageType;
21
+ exports.packetToOkfConcept = packetToOkfConcept;
22
+ exports.okfConceptToPacket = okfConceptToPacket;
23
+ exports.lintOkfConcept = lintOkfConcept;
24
+ exports.okfConceptFileName = okfConceptFileName;
25
+ exports.okfBundleDir = okfBundleDir;
26
+ exports.migratePacketsToOkf = migratePacketsToOkf;
27
+ exports.loadOkfConcepts = loadOkfConcepts;
28
+ exports.lintOkfBundle = lintOkfBundle;
29
+ const node_crypto_1 = require("node:crypto");
30
+ const node_fs_1 = require("node:fs");
31
+ const node_path_1 = require("node:path");
32
+ const kernel_js_1 = require("./kernel.js");
33
+ // ---- type vocabulary mapping (Kage snake_case <-> OKF display string) ----
34
+ const TYPE_DISPLAY = {
35
+ repo_map: "Repo Map",
36
+ runbook: "Runbook",
37
+ bug_fix: "Bug Fix",
38
+ decision: "Decision",
39
+ rationale: "Rationale",
40
+ convention: "Convention",
41
+ workflow: "Workflow",
42
+ gotcha: "Gotcha",
43
+ reference: "Reference",
44
+ policy: "Policy",
45
+ issue_context: "Issue Context",
46
+ code_explanation: "Code Explanation",
47
+ negative_result: "Negative Result",
48
+ constraint: "Constraint",
49
+ };
50
+ const DISPLAY_TO_TYPE = Object.fromEntries(Object.entries(TYPE_DISPLAY).map(([k, v]) => [v.toLowerCase(), k]));
51
+ function okfType(type) {
52
+ return TYPE_DISPLAY[type] ?? "Reference";
53
+ }
54
+ function kageType(display) {
55
+ const key = String(display ?? "").trim().toLowerCase();
56
+ if (DISPLAY_TO_TYPE[key])
57
+ return DISPLAY_TO_TYPE[key];
58
+ if (kernel_js_1.MEMORY_TYPES.includes(key))
59
+ return key;
60
+ return "reference";
61
+ }
62
+ // ---- the lossless machine-state block ----
63
+ const STATE_FENCE_OPEN = "```json kage-state";
64
+ const STATE_FENCE_CLOSE = "```";
65
+ function extractKageState(content) {
66
+ const open = content.indexOf(STATE_FENCE_OPEN);
67
+ if (open === -1)
68
+ return null;
69
+ const start = open + STATE_FENCE_OPEN.length;
70
+ const close = content.indexOf("\n```", start);
71
+ if (close === -1)
72
+ return null;
73
+ try {
74
+ const obj = JSON.parse(content.slice(start, close).trim());
75
+ if (obj && obj.schema_version === kernel_js_1.PACKET_SCHEMA_VERSION && obj.id && obj.title)
76
+ return obj;
77
+ }
78
+ catch {
79
+ // fall through to best-effort import
80
+ }
81
+ return null;
82
+ }
83
+ // ---- small local helpers (kept self-contained; no reliance on kernel internals) ----
84
+ function yamlScalar(value) {
85
+ // JSON-encoded strings are valid YAML flow scalars and dodge every quoting/colon
86
+ // edge case, so we never hand-roll YAML escaping.
87
+ return JSON.stringify(value ?? "");
88
+ }
89
+ function yamlList(items) {
90
+ return `[${items.map((item) => JSON.stringify(item)).join(", ")}]`;
91
+ }
92
+ function firstHeading(body) {
93
+ const match = body.match(/^#\s+(.+)$/m);
94
+ return match ? match[1].trim() : null;
95
+ }
96
+ function stripFirstHeading(body) {
97
+ return body.replace(/^#\s+.+\n?/, "").trim();
98
+ }
99
+ function summarize(body) {
100
+ const text = body.replace(/\s+/g, " ").trim();
101
+ return text.length > 280 ? `${text.slice(0, 277).trimEnd()}…` : text;
102
+ }
103
+ function oneLine(value) {
104
+ const text = (value ?? "").replace(/\s+/g, " ").trim();
105
+ return text.length > 120 ? `${text.slice(0, 117)}…` : text;
106
+ }
107
+ function asStringArray(value) {
108
+ if (Array.isArray(value))
109
+ return value.map(String);
110
+ if (typeof value === "string" && value.trim()) {
111
+ return value.replace(/^\[|\]$/g, "").split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
112
+ }
113
+ return [];
114
+ }
115
+ function asNumber(value, fallback) {
116
+ const n = typeof value === "number" ? value : Number.parseFloat(String(value));
117
+ return Number.isFinite(n) ? n : fallback;
118
+ }
119
+ function ensureDir(path) {
120
+ if (!(0, node_fs_1.existsSync)(path))
121
+ (0, node_fs_1.mkdirSync)(path, { recursive: true });
122
+ }
123
+ function citationText(ref) {
124
+ const kind = ref.kind ? String(ref.kind) : "source";
125
+ const where = ref.path ? ` ${String(ref.path)}` : ref.resource ? ` ${String(ref.resource)}` : "";
126
+ const at = ref.captured_at ? ` (${String(ref.captured_at)})` : "";
127
+ return `${kind}${where}${at}`.trim();
128
+ }
129
+ function okfVerifiedStatus(packet) {
130
+ if (packet.status === "superseded")
131
+ return "superseded";
132
+ if (packet.status === "deprecated")
133
+ return "deprecated";
134
+ const freshness = packet.freshness;
135
+ return freshness && freshness.last_verified_at ? "verified" : "unverified";
136
+ }
137
+ // ---- packet -> OKF concept document ----
138
+ function packetToOkfConcept(packet) {
139
+ const fm = [];
140
+ fm.push(`type: ${yamlScalar(okfType(packet.type))}`);
141
+ fm.push(`title: ${yamlScalar(packet.title)}`);
142
+ fm.push(`description: ${yamlScalar(packet.summary ?? "")}`);
143
+ const resource = packet.paths?.[0];
144
+ if (resource)
145
+ fm.push(`resource: ${yamlScalar(resource)}`);
146
+ if (packet.tags?.length)
147
+ fm.push(`tags: ${yamlList(packet.tags)}`);
148
+ const freshness = packet.freshness;
149
+ const timestamp = (freshness && freshness.last_verified_at) || packet.updated_at;
150
+ if (timestamp)
151
+ fm.push(`timestamp: ${yamlScalar(String(timestamp))}`);
152
+ // Kage trust extension (OKF-legal arbitrary producer fields).
153
+ fm.push(`x-kage-id: ${yamlScalar(packet.id)}`);
154
+ fm.push(`x-kage-type: ${yamlScalar(packet.type)}`);
155
+ fm.push(`x-kage-status: ${yamlScalar(packet.status)}`);
156
+ fm.push(`x-kage-scope: ${yamlScalar(packet.scope)}`);
157
+ fm.push(`x-kage-visibility: ${yamlScalar(packet.visibility)}`);
158
+ fm.push(`x-kage-confidence: ${packet.confidence}`);
159
+ fm.push(`x-kage-verified: ${yamlScalar(okfVerifiedStatus(packet))}`);
160
+ if (packet.paths?.length)
161
+ fm.push(`x-kage-paths: ${yamlList(packet.paths)}`);
162
+ if (packet.stack?.length)
163
+ fm.push(`x-kage-stack: ${yamlList(packet.stack)}`);
164
+ const body = [];
165
+ body.push(`# ${packet.title}`, "");
166
+ if (packet.summary?.trim() && packet.summary.trim() !== packet.body?.trim())
167
+ body.push(`> ${oneLine(packet.summary)}`, "");
168
+ if (packet.body?.trim())
169
+ body.push(packet.body.trim(), "");
170
+ const ctx = packet.context;
171
+ if (ctx) {
172
+ if (ctx.why?.trim())
173
+ body.push("## Why", "", ctx.why.trim(), "");
174
+ if (ctx.trigger?.trim())
175
+ body.push("## Trigger", "", ctx.trigger.trim(), "");
176
+ if (ctx.action?.trim())
177
+ body.push("## Action", "", ctx.action.trim(), "");
178
+ if (ctx.verification?.trim())
179
+ body.push("## Verification", "", ctx.verification.trim(), "");
180
+ if (ctx.risk_if_forgotten?.trim())
181
+ body.push("## Risk if forgotten", "", ctx.risk_if_forgotten.trim(), "");
182
+ if (ctx.stale_when?.trim())
183
+ body.push("## Stale when", "", ctx.stale_when.trim(), "");
184
+ if (ctx.rejected_alternatives?.length) {
185
+ body.push("## Rejected alternatives", "", ...ctx.rejected_alternatives.map((alt) => `- ${alt}`), "");
186
+ }
187
+ }
188
+ if (packet.source_refs?.length) {
189
+ body.push("# Citations", "");
190
+ packet.source_refs.forEach((ref, i) => body.push(`[${i + 1}] ${citationText(ref)}`));
191
+ body.push("");
192
+ }
193
+ body.push("## Kage state", "", "Machine state for lossless round-trip; OKF consumers can ignore it.", "", STATE_FENCE_OPEN, JSON.stringify(packet), STATE_FENCE_CLOSE, "");
194
+ return `---\n${fm.join("\n")}\n---\n\n${body.join("\n")}\n`;
195
+ }
196
+ // ---- OKF concept document -> packet ----
197
+ function okfConceptToPacket(content, opts = {}) {
198
+ // A file carrying git conflict markers is not a valid concept — surface it as
199
+ // unparseable so validation flags it and the repair / merge-driver path (which
200
+ // parses each side separately) resolves it, rather than silently picking a side.
201
+ if (/^(?:<{7} |={7}\s*$|>{7} )/m.test(content))
202
+ return null;
203
+ // Lossless path: a Kage-authored concept carries its exact packet.
204
+ const state = extractKageState(content);
205
+ if (state)
206
+ return state;
207
+ // Best-effort import of a foreign / hand-authored OKF concept.
208
+ const { frontmatter, body } = (0, kernel_js_1.parseFrontmatter)(content);
209
+ const fm = frontmatter;
210
+ if (!String(fm.type ?? "").trim() && !firstHeading(body))
211
+ return null;
212
+ const projectDir = opts.projectDir ?? ".";
213
+ const base = opts.sourcePath ? (0, node_path_1.basename)(opts.sourcePath, ".md") : "concept";
214
+ const type = kageType(fm.type);
215
+ const title = String(fm.title ?? firstHeading(body) ?? base);
216
+ const cleanBody = stripFirstHeading(body) || body.trim();
217
+ const paths = fm["x-kage-paths"]
218
+ ? asStringArray(fm["x-kage-paths"])
219
+ : fm.resource
220
+ ? [String(fm.resource)]
221
+ : [];
222
+ const timestamp = fm.timestamp ? String(fm.timestamp) : new Date().toISOString();
223
+ return {
224
+ schema_version: kernel_js_1.PACKET_SCHEMA_VERSION,
225
+ id: fm["x-kage-id"] ? String(fm["x-kage-id"]) : (0, kernel_js_1.makePacketId)(projectDir, type, title, base),
226
+ title,
227
+ summary: String(fm.description ?? summarize(cleanBody)),
228
+ body: cleanBody,
229
+ type,
230
+ scope: (String(fm["x-kage-scope"] ?? "repo")),
231
+ visibility: (String(fm["x-kage-visibility"] ?? "team")),
232
+ sensitivity: "internal",
233
+ status: (String(fm["x-kage-status"] ?? "approved")),
234
+ confidence: asNumber(fm["x-kage-confidence"], 0.7),
235
+ tags: asStringArray(fm.tags),
236
+ paths,
237
+ stack: asStringArray(fm["x-kage-stack"]),
238
+ source_refs: [
239
+ { kind: "okf_import", path: opts.sourcePath ? (0, node_path_1.relative)(projectDir, opts.sourcePath) : undefined, resource: fm.resource },
240
+ ],
241
+ freshness: { ttl_days: 365, last_verified_at: fm.timestamp ?? null, verification: "okf_import" },
242
+ edges: [],
243
+ quality: { reviewer: null, votes_up: 0, votes_down: 0, uses_30d: 0, reports_stale: 0 },
244
+ created_at: timestamp,
245
+ updated_at: timestamp,
246
+ };
247
+ }
248
+ // ---- conformance lint ----
249
+ function lintOkfConcept(content) {
250
+ const errors = [];
251
+ if (!content.startsWith("---"))
252
+ errors.push("missing YAML frontmatter");
253
+ const { frontmatter } = (0, kernel_js_1.parseFrontmatter)(content);
254
+ if (!String(frontmatter.type ?? "").trim()) {
255
+ errors.push("frontmatter missing required non-empty `type`");
256
+ }
257
+ return { ok: errors.length === 0, errors };
258
+ }
259
+ // ---- filenames / bundle layout ----
260
+ function okfConceptFileName(packet) {
261
+ const idHash = (0, node_crypto_1.createHash)("sha256").update(packet.id).digest("hex").slice(0, 8);
262
+ return `${(0, kernel_js_1.slugify)(packet.title) || "concept"}-${idHash}.md`;
263
+ }
264
+ function okfBundleDir(projectDir) {
265
+ return (0, node_path_1.join)((0, kernel_js_1.memoryRoot)(projectDir), "okf");
266
+ }
267
+ function titleCaseDir(dir) {
268
+ return dir.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
269
+ }
270
+ function buildLog(entries) {
271
+ const byDate = new Map();
272
+ for (const entry of entries) {
273
+ const date = entry.date || "unknown";
274
+ if (!byDate.has(date))
275
+ byDate.set(date, []);
276
+ byDate.get(date).push(entry.line);
277
+ }
278
+ const out = ["# Log", ""];
279
+ for (const date of [...byDate.keys()].sort().reverse()) {
280
+ out.push(`## ${date}`, "", ...byDate.get(date).map((line) => `- ${line}`), "");
281
+ }
282
+ return `${out.join("\n")}\n`;
283
+ }
284
+ function migratePacketsToOkf(projectDir, opts = {}) {
285
+ const packets = [
286
+ ...(0, kernel_js_1.loadApprovedPackets)(projectDir),
287
+ ...(opts.includePending ? (0, kernel_js_1.loadPendingPackets)(projectDir) : []),
288
+ ];
289
+ const root = okfBundleDir(projectDir);
290
+ ensureDir(root);
291
+ const byType = {};
292
+ const indexByType = {};
293
+ const logEntries = [];
294
+ for (const packet of packets) {
295
+ const typeDir = (0, kernel_js_1.slugify)(okfType(packet.type));
296
+ const dir = (0, node_path_1.join)(root, typeDir);
297
+ ensureDir(dir);
298
+ const file = okfConceptFileName(packet);
299
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(dir, file), packetToOkfConcept(packet), "utf8");
300
+ byType[packet.type] = (byType[packet.type] ?? 0) + 1;
301
+ (indexByType[typeDir] ??= []).push(`- [${packet.title}](/${typeDir}/${file}) — ${oneLine(packet.summary)}`);
302
+ logEntries.push({
303
+ date: (packet.updated_at || packet.created_at || "").slice(0, 10),
304
+ line: `**Update** ${packet.title} (${okfType(packet.type)})`,
305
+ });
306
+ }
307
+ for (const [typeDir, lines] of Object.entries(indexByType)) {
308
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, typeDir, "index.md"), `# ${titleCaseDir(typeDir)}\n\n${lines.sort().join("\n")}\n`, "utf8");
309
+ }
310
+ const rootIndex = [
311
+ "# Kage knowledge bundle",
312
+ "",
313
+ `${packets.length} concepts in Open Knowledge Format (OKF) v0.1, with the Kage trust extension (\`x-kage-*\`).`,
314
+ "",
315
+ ];
316
+ for (const typeDir of Object.keys(indexByType).sort()) {
317
+ rootIndex.push(`- [${titleCaseDir(typeDir)}](/${typeDir}/index.md) (${indexByType[typeDir].length})`);
318
+ }
319
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, "index.md"), `${rootIndex.join("\n")}\n`, "utf8");
320
+ (0, node_fs_1.writeFileSync)((0, node_path_1.join)(root, "log.md"), buildLog(logEntries), "utf8");
321
+ return { root, written: packets.length, byType };
322
+ }
323
+ // ---- bundle reader (consume any OKF bundle, ours or a third party's) ----
324
+ function walkMarkdown(root) {
325
+ const out = [];
326
+ let entries = [];
327
+ try {
328
+ entries = (0, node_fs_1.readdirSync)(root);
329
+ }
330
+ catch {
331
+ return out;
332
+ }
333
+ for (const name of entries) {
334
+ const path = (0, node_path_1.join)(root, name);
335
+ let stats;
336
+ try {
337
+ stats = (0, node_fs_1.statSync)(path);
338
+ }
339
+ catch {
340
+ continue;
341
+ }
342
+ if (stats.isDirectory())
343
+ out.push(...walkMarkdown(path));
344
+ else if (name.endsWith(".md"))
345
+ out.push(path);
346
+ }
347
+ return out.sort();
348
+ }
349
+ function loadOkfConcepts(dir, opts = {}) {
350
+ if (!(0, node_fs_1.existsSync)(dir))
351
+ return [];
352
+ const out = [];
353
+ for (const path of walkMarkdown(dir)) {
354
+ const name = (0, node_path_1.basename)(path);
355
+ if (name === "index.md" || name === "log.md")
356
+ continue;
357
+ try {
358
+ const packet = okfConceptToPacket((0, node_fs_1.readFileSync)(path, "utf8"), {
359
+ projectDir: opts.projectDir ?? dir,
360
+ sourcePath: path,
361
+ });
362
+ if (packet)
363
+ out.push(packet);
364
+ }
365
+ catch {
366
+ // a single malformed concept must not break the whole bundle read
367
+ }
368
+ }
369
+ return out;
370
+ }
371
+ function lintOkfBundle(dir) {
372
+ const failures = [];
373
+ let files = 0;
374
+ for (const path of walkMarkdown(dir)) {
375
+ const name = (0, node_path_1.basename)(path);
376
+ if (name === "index.md" || name === "log.md")
377
+ continue;
378
+ files += 1;
379
+ const result = lintOkfConcept((0, node_fs_1.readFileSync)(path, "utf8"));
380
+ if (!result.ok)
381
+ failures.push({ path, errors: result.errors });
382
+ }
383
+ return { files, failures };
384
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kage-core/kage-graph-mcp",
3
- "version": "2.5.6",
4
- "description": "Team memory for coding agents: captures the decisions, runbooks, and bug fixes that get lost, verified against your code and shared via git. MCP server, zero deps, no account.",
3
+ "version": "3.0.0",
4
+ "description": "Verified memory for coding agents, stored as a Google Open Knowledge Format (OKF) bundle and checked against your code. The verification layer OKF leaves out. MCP server, no account, no API key.",
5
5
  "main": "dist/index.js",
6
6
  "files": [
7
7
  "dist/**/*.js",