@kage-core/kage-graph-mcp 1.1.35 → 1.1.37

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/daemon.js CHANGED
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.viewerStaticHeaders = viewerStaticHeaders;
4
+ exports.viewerRedirectLocation = viewerRedirectLocation;
5
+ exports.viewerBenchmarkReport = viewerBenchmarkReport;
6
+ exports.daemonContextReport = daemonContextReport;
3
7
  exports.readDaemonStatus = readDaemonStatus;
4
8
  exports.daemonDoctor = daemonDoctor;
5
9
  exports.stopDaemon = stopDaemon;
@@ -34,6 +38,107 @@ function contentType(filePath) {
34
38
  return "text/markdown; charset=utf-8";
35
39
  return "application/octet-stream";
36
40
  }
41
+ function viewerStaticHeaders(filePath) {
42
+ return {
43
+ "content-type": contentType(filePath),
44
+ "x-content-type-options": "nosniff",
45
+ "referrer-policy": "no-referrer",
46
+ "cross-origin-opener-policy": "same-origin",
47
+ "content-security-policy": [
48
+ "default-src 'self'",
49
+ "script-src 'self'",
50
+ "script-src-attr 'none'",
51
+ "style-src 'self' 'unsafe-inline'",
52
+ "img-src 'self' data:",
53
+ "connect-src 'self'",
54
+ "font-src 'none'",
55
+ "object-src 'none'",
56
+ "base-uri 'none'",
57
+ "frame-ancestors 'none'",
58
+ ].join("; "),
59
+ };
60
+ }
61
+ function viewerRedirectLocation(pathname, search, fallbackSearch) {
62
+ if (pathname !== "/" && pathname !== "/viewer" && pathname !== "/viewer/")
63
+ return null;
64
+ return `/viewer/index.html${search || fallbackSearch}`;
65
+ }
66
+ function viewerBenchmarkReport(projectDir) {
67
+ const gates = (0, kernel_js_1.benchmarkProject)(projectDir);
68
+ const memoryQuality = (0, kernel_js_1.benchmarkCodingMemoryQuality)();
69
+ const memoryScale = (0, kernel_js_1.benchmarkMemoryScale)({ sizes: [240], topK: 10 });
70
+ const requiredGates = gates.gates.filter((gate) => gate.required);
71
+ const requiredGatePasses = requiredGates.filter((gate) => gate.pass).length;
72
+ const requiredGatePercent = requiredGates.length ? Math.round((requiredGatePasses / requiredGates.length) * 100) : (gates.ok ? 100 : 0);
73
+ const recallAt10 = Number(memoryQuality.summary.recall_at_10_percent ?? memoryQuality.summary.recall_at_k_percent ?? 0);
74
+ const sourceDiversity = memoryQuality.source_diversity;
75
+ const scaleHitRate = Number(memoryScale.summary.largest_hit_rate_percent ?? 0);
76
+ const scaleContextCut = Number(memoryScale.summary.largest_context_reduction_percent ?? 0);
77
+ return {
78
+ ...gates,
79
+ summary: memoryQuality.summary,
80
+ memory_quality: {
81
+ dataset: memoryQuality.dataset,
82
+ summary: memoryQuality.summary,
83
+ source_diversity: memoryQuality.source_diversity,
84
+ by_category: memoryQuality.by_category,
85
+ baselines: memoryQuality.baselines,
86
+ caveats: memoryQuality.caveats,
87
+ },
88
+ memory_scale: {
89
+ sizes: memoryScale.sizes,
90
+ summary: memoryScale.summary,
91
+ results: memoryScale.results,
92
+ caveats: memoryScale.caveats,
93
+ },
94
+ proof_ledger: [
95
+ {
96
+ id: "memory-quality",
97
+ label: "Coding-memory retrieval",
98
+ metric: `${recallAt10.toFixed(2)}% R@10, ${memoryQuality.summary.ndcg_at_10.toFixed(4)} NDCG@10`,
99
+ target: ">=95% R@10 and >=0.85 NDCG@10",
100
+ actual: recallAt10,
101
+ unit: "percent",
102
+ pass: recallAt10 >= 95 && memoryQuality.summary.ndcg_at_10 >= 0.85,
103
+ command: "kage benchmark --memory-quality --json",
104
+ next_action: "Use this as the fast coding-memory retrieval proof before publishing benchmark claims.",
105
+ },
106
+ {
107
+ id: "source-diversity",
108
+ label: "Source diversity",
109
+ metric: `${sourceDiversity.unique_sources} sources in top ${sourceDiversity.top_k}; max ${sourceDiversity.max_results_from_one_source} from one source`,
110
+ target: ">=2 sources and <=3 results from one observed session",
111
+ actual: sourceDiversity.unique_sources,
112
+ unit: "score",
113
+ pass: sourceDiversity.pass,
114
+ command: "kage benchmark --memory-quality --json",
115
+ next_action: sourceDiversity.pass ? "Use this to prove noisy sessions cannot crowd out independent repo memory." : "Inspect recall source diversity before publishing memory-quality claims.",
116
+ },
117
+ {
118
+ id: "memory-scale",
119
+ label: "Memory scale sanity",
120
+ metric: `${memoryScale.summary.largest_packets} packets, ${scaleHitRate.toFixed(2)}% hit rate, ${scaleContextCut.toFixed(2)}% context cut`,
121
+ target: ">=95% hit rate and >=80% context reduction",
122
+ actual: scaleHitRate,
123
+ unit: "percent",
124
+ pass: scaleHitRate >= 95 && scaleContextCut >= 80,
125
+ command: "kage benchmark --scale --sizes 240 --json",
126
+ next_action: "Run kage benchmark --scale --sizes 240,1000,5000 --json for a larger release proof.",
127
+ },
128
+ {
129
+ id: "local-gates",
130
+ label: "Repo trust gates",
131
+ metric: `${requiredGatePasses}/${requiredGates.length || gates.gates.length} required gates passing`,
132
+ target: "100% required gates passing",
133
+ actual: requiredGatePercent,
134
+ unit: "percent",
135
+ pass: gates.ok,
136
+ command: "kage benchmark --project . --json",
137
+ next_action: gates.ok ? "Keep this green before handoff, README updates, or release." : "Fix failing repo benchmark gates before sharing this memory state.",
138
+ },
139
+ ],
140
+ };
141
+ }
37
142
  function statusPath(projectDir) {
38
143
  return (0, node_path_1.join)(daemonDir(projectDir), "status.json");
39
144
  }
@@ -64,6 +169,68 @@ function readBody(req) {
64
169
  req.on("error", reject);
65
170
  });
66
171
  }
172
+ function stringArray(value) {
173
+ if (Array.isArray(value))
174
+ return value.map(String).filter(Boolean);
175
+ if (typeof value === "string")
176
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
177
+ return [];
178
+ }
179
+ function filePathHints(query) {
180
+ const matches = query.match(/[A-Za-z0-9_./@-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|go|rs|java|kt|kts|rb|php|cs|c|h|cc|cpp|hpp|swift|json|md)\b/g) ?? [];
181
+ return [...new Set(matches.map((match) => match.replace(/^\.\//, "")).filter((match) => !/^https?:\/\//.test(match)))];
182
+ }
183
+ function wantsDependencyPath(query) {
184
+ return /\b(connect|connected|dependency|depend|depends|path|impact|flow|trace)\b/i.test(query);
185
+ }
186
+ function riskContextBlock(result) {
187
+ const targets = Object.values(result.targets);
188
+ if (!targets.length)
189
+ return "";
190
+ const lines = targets.slice(0, 5).map((item) => {
191
+ const coChange = item.git.co_change_partners.length
192
+ ? ` Co-change: ${item.git.co_change_partners.slice(0, 3).map((partner) => `${partner.file_path} (${partner.count})`).join(", ")}.`
193
+ : "";
194
+ return `- ${item.risk_summary}${coChange}`;
195
+ });
196
+ return `\n## Risk Signals\n${lines.join("\n")}`;
197
+ }
198
+ function daemonContextReport(projectDir, body) {
199
+ const query = String(body.query ?? "");
200
+ const limit = Number(body.limit ?? 5);
201
+ const validation = (0, kernel_js_1.validateProject)(projectDir);
202
+ const recallResult = (0, kernel_js_1.recall)(projectDir, query, limit, Boolean(body.explain));
203
+ const graphResult = (0, kernel_js_1.queryGraph)(projectDir, query, 5);
204
+ const explicitTargets = [...stringArray(body.targets), ...filePathHints(query)];
205
+ const changedFiles = stringArray(body.changed_files);
206
+ const riskResult = explicitTargets.length || changedFiles.length ? (0, kernel_js_1.kageRisk)(projectDir, explicitTargets, changedFiles) : null;
207
+ const pathHints = filePathHints(query);
208
+ const dependencyResult = wantsDependencyPath(query) && pathHints.length >= 2
209
+ ? (0, kernel_js_1.kageDependencyPath)(projectDir, pathHints[0], pathHints[1])
210
+ : null;
211
+ const reconciliation = (0, kernel_js_1.kageMemoryReconciliation)(projectDir, {
212
+ sessionId: typeof body.session_id === "string" ? body.session_id : undefined,
213
+ limit: 5,
214
+ });
215
+ const validationText = validation.ok ? "Memory healthy." : `Warnings: ${validation.warnings.join("; ")}`;
216
+ const contextBlock = [
217
+ recallResult.context_block,
218
+ graphResult.context_block ? `\n## Graph Facts\n${graphResult.context_block}` : "",
219
+ riskResult ? riskContextBlock(riskResult) : "",
220
+ dependencyResult ? `\n## Dependency Path\n${dependencyResult.summary}${dependencyResult.path.length ? `\nPath: ${dependencyResult.path.join(" -> ")}` : ""}` : "",
221
+ reconciliation.unresolved_count ? `\n## Memory Reconciliation\n${reconciliation.agent_instruction}` : "",
222
+ `\n_${validationText}_`,
223
+ ].filter(Boolean).join("");
224
+ return {
225
+ context_block: contextBlock,
226
+ recall: recallResult,
227
+ graph: graphResult,
228
+ risk: riskResult,
229
+ dependency_path: dependencyResult,
230
+ validation,
231
+ memory_reconciliation: reconciliation,
232
+ };
233
+ }
67
234
  function readDaemonStatus(projectDir) {
68
235
  const path = statusPath(projectDir);
69
236
  if (!(0, node_fs_1.existsSync)(path))
@@ -93,9 +260,20 @@ function daemonDoctor(projectDir) {
93
260
  status,
94
261
  endpoints: [
95
262
  `GET http://${DEFAULT_HOST}:${restPort}/health`,
263
+ `POST http://${DEFAULT_HOST}:${restPort}/kage/context`,
96
264
  `POST http://${DEFAULT_HOST}:${restPort}/kage/recall`,
265
+ `POST http://${DEFAULT_HOST}:${restPort}/kage/capture`,
266
+ `POST http://${DEFAULT_HOST}:${restPort}/kage/learn`,
267
+ `POST http://${DEFAULT_HOST}:${restPort}/kage/feedback`,
97
268
  `POST http://${DEFAULT_HOST}:${restPort}/kage/observe`,
98
269
  `POST http://${DEFAULT_HOST}:${restPort}/kage/distill`,
270
+ `GET http://${DEFAULT_HOST}:${restPort}/kage/replay`,
271
+ `GET http://${DEFAULT_HOST}:${restPort}/kage/setup-doctor`,
272
+ `GET http://${DEFAULT_HOST}:${restPort}/kage/profile`,
273
+ `GET http://${DEFAULT_HOST}:${restPort}/kage/capabilities`,
274
+ `GET http://${DEFAULT_HOST}:${restPort}/kage/context-slots`,
275
+ `POST http://${DEFAULT_HOST}:${restPort}/kage/context-slots`,
276
+ `DELETE http://${DEFAULT_HOST}:${restPort}/kage/context-slots/:label`,
99
277
  `GET http://${DEFAULT_HOST}:${restPort}/kage/metrics`,
100
278
  `GET http://${DEFAULT_HOST}:${restPort}/kage/quality`,
101
279
  `GET http://${DEFAULT_HOST}:${restPort}/kage/inbox`,
@@ -187,12 +365,60 @@ async function startDaemon(projectDir, options = {}) {
187
365
  json(res, 200, (0, kernel_js_1.qualityReport)(projectDir));
188
366
  return;
189
367
  }
368
+ if (req.method === "GET" && url.pathname === "/kage/profile") {
369
+ json(res, 200, (0, kernel_js_1.kageProjectProfile)(projectDir));
370
+ return;
371
+ }
372
+ if (req.method === "GET" && url.pathname === "/kage/capabilities") {
373
+ json(res, 200, (0, kernel_js_1.kageCapabilityAudit)(projectDir));
374
+ return;
375
+ }
376
+ if (req.method === "GET" && url.pathname === "/kage/replay") {
377
+ json(res, 200, (0, kernel_js_1.kageSessionReplay)(projectDir, {
378
+ sessionId: url.searchParams.get("session") ?? undefined,
379
+ limit: Number(url.searchParams.get("limit") ?? 200),
380
+ }));
381
+ return;
382
+ }
383
+ if (req.method === "GET" && url.pathname === "/kage/context-slots") {
384
+ json(res, 200, (0, kernel_js_1.kageContextSlots)(projectDir));
385
+ return;
386
+ }
387
+ if (req.method === "GET" && url.pathname === "/kage/lifecycle") {
388
+ json(res, 200, (0, kernel_js_1.kageMemoryLifecycle)(projectDir));
389
+ return;
390
+ }
391
+ if (req.method === "GET" && url.pathname === "/kage/memory-audit") {
392
+ json(res, 200, (0, kernel_js_1.kageMemoryAudit)(projectDir, Number(url.searchParams.get("limit") ?? 100)));
393
+ return;
394
+ }
395
+ if (req.method === "GET" && url.pathname === "/kage/handoff") {
396
+ json(res, 200, (0, kernel_js_1.kageMemoryHandoff)(projectDir));
397
+ return;
398
+ }
399
+ if (req.method === "GET" && url.pathname === "/kage/timeline") {
400
+ json(res, 200, (0, kernel_js_1.kageMemoryTimeline)(projectDir, Number(url.searchParams.get("days") ?? 14)));
401
+ return;
402
+ }
403
+ if (req.method === "GET" && url.pathname === "/kage/lineage") {
404
+ json(res, 200, (0, kernel_js_1.kageMemoryLineage)(projectDir));
405
+ return;
406
+ }
190
407
  if (req.method === "GET" && url.pathname === "/kage/inbox") {
191
408
  json(res, 200, (0, kernel_js_1.memoryInbox)(projectDir));
192
409
  return;
193
410
  }
194
411
  if (req.method === "GET" && url.pathname === "/kage/benchmark") {
195
- json(res, 200, (0, kernel_js_1.benchmarkProject)(projectDir));
412
+ json(res, 200, url.searchParams.get("mode") === "memory_quality" ? (0, kernel_js_1.benchmarkCodingMemoryQuality)() : (0, kernel_js_1.benchmarkProject)(projectDir));
413
+ return;
414
+ }
415
+ if (req.method === "GET" && url.pathname === "/kage/setup-doctor") {
416
+ json(res, 200, (0, kernel_js_1.setupDoctor)(projectDir));
417
+ return;
418
+ }
419
+ if (req.method === "POST" && url.pathname === "/kage/context") {
420
+ const body = await readBody(req);
421
+ json(res, 200, daemonContextReport(projectDir, body));
196
422
  return;
197
423
  }
198
424
  if (req.method === "POST" && url.pathname === "/kage/recall") {
@@ -200,6 +426,63 @@ async function startDaemon(projectDir, options = {}) {
200
426
  json(res, 200, (0, kernel_js_1.recall)(projectDir, String(body.query ?? ""), Number(body.limit ?? 5), Boolean(body.explain)));
201
427
  return;
202
428
  }
429
+ if (req.method === "POST" && url.pathname === "/kage/capture") {
430
+ const body = await readBody(req);
431
+ const result = (0, kernel_js_1.capture)({
432
+ projectDir,
433
+ title: String(body.title ?? ""),
434
+ summary: body.summary == null ? undefined : String(body.summary),
435
+ body: String(body.body ?? body.learning ?? ""),
436
+ type: body.type == null ? undefined : String(body.type),
437
+ tags: stringArray(body.tags),
438
+ paths: stringArray(body.paths),
439
+ stack: stringArray(body.stack),
440
+ });
441
+ json(res, result.ok ? 200 : 400, result);
442
+ return;
443
+ }
444
+ if (req.method === "POST" && url.pathname === "/kage/learn") {
445
+ const body = await readBody(req);
446
+ const result = (0, kernel_js_1.learn)({
447
+ projectDir,
448
+ learning: String(body.learning ?? body.body ?? ""),
449
+ title: body.title == null ? undefined : String(body.title),
450
+ type: body.type == null ? undefined : String(body.type),
451
+ evidence: body.evidence == null ? undefined : String(body.evidence),
452
+ verifiedBy: body.verified_by == null ? body.verifiedBy == null ? undefined : String(body.verifiedBy) : String(body.verified_by),
453
+ tags: stringArray(body.tags),
454
+ paths: stringArray(body.paths),
455
+ stack: stringArray(body.stack),
456
+ });
457
+ json(res, result.ok ? 200 : 400, result);
458
+ return;
459
+ }
460
+ if (req.method === "POST" && url.pathname === "/kage/feedback") {
461
+ const body = await readBody(req);
462
+ const result = (0, kernel_js_1.recordFeedback)(projectDir, String(body.packet_id ?? body.packet ?? ""), String(body.kind ?? ""));
463
+ json(res, result.ok ? 200 : 400, result);
464
+ return;
465
+ }
466
+ if (req.method === "POST" && url.pathname === "/kage/context-slots") {
467
+ const body = await readBody(req);
468
+ const result = (0, kernel_js_1.setContextSlot)(projectDir, {
469
+ label: String(body.label ?? ""),
470
+ content: String(body.content ?? ""),
471
+ description: body.description == null ? undefined : String(body.description),
472
+ pinned: body.pinned == null ? undefined : Boolean(body.pinned),
473
+ size_limit: body.size_limit == null ? undefined : Number(body.size_limit),
474
+ paths: stringArray(body.paths),
475
+ tags: stringArray(body.tags),
476
+ });
477
+ json(res, result.ok ? 200 : 400, result);
478
+ return;
479
+ }
480
+ if (req.method === "DELETE" && url.pathname.startsWith("/kage/context-slots/")) {
481
+ const label = decodeURIComponent(url.pathname.slice("/kage/context-slots/".length));
482
+ const result = (0, kernel_js_1.deleteContextSlot)(projectDir, label);
483
+ json(res, result.ok ? 200 : 404, result);
484
+ return;
485
+ }
203
486
  if (req.method === "POST" && url.pathname === "/kage/observe") {
204
487
  const body = await readBody(req);
205
488
  json(res, 200, (0, kernel_js_1.observe)(projectDir, body));
@@ -244,11 +527,23 @@ async function startViewer(projectDir, options = {}) {
244
527
  const qualityPath = (0, node_path_1.join)(reportsDir, "quality.json");
245
528
  const benchmarkPath = (0, node_path_1.join)(reportsDir, "benchmark.json");
246
529
  const contributorsPath = (0, node_path_1.join)(reportsDir, "contributors.json");
530
+ const profilePath = (0, node_path_1.join)(reportsDir, "profile.json");
531
+ const capabilitiesPath = (0, node_path_1.join)(reportsDir, "capabilities.json");
532
+ const slotsPath = (0, node_path_1.join)(reportsDir, "context-slots.json");
247
533
  const decisionsPath = (0, node_path_1.join)(reportsDir, "decisions.json");
248
534
  const riskPath = (0, node_path_1.join)(reportsDir, "risk.json");
249
535
  const moduleHealthPath = (0, node_path_1.join)(reportsDir, "module-health.json");
250
536
  const graphInsightsPath = (0, node_path_1.join)(reportsDir, "graph-insights.json");
251
537
  const workspacePath = (0, node_path_1.join)(reportsDir, "workspace.json");
538
+ const sessionsPath = (0, node_path_1.join)(reportsDir, "sessions.json");
539
+ const replayPath = (0, node_path_1.join)(reportsDir, "replay.json");
540
+ const memoryAccessPath = (0, node_path_1.join)(reportsDir, "memory-access.json");
541
+ const memoryAuditPath = (0, node_path_1.join)(reportsDir, "memory-audit.json");
542
+ const handoffPath = (0, node_path_1.join)(reportsDir, "handoff.json");
543
+ const lifecyclePath = (0, node_path_1.join)(reportsDir, "lifecycle.json");
544
+ const timelinePath = (0, node_path_1.join)(reportsDir, "timeline.json");
545
+ const lineagePath = (0, node_path_1.join)(reportsDir, "lineage.json");
546
+ const setupPath = (0, node_path_1.join)(reportsDir, "setup.json");
252
547
  // Pre-generate lightweight JSON reports so the viewer can load them directly.
253
548
  try {
254
549
  (0, node_fs_1.mkdirSync)(reportsDir, { recursive: true });
@@ -257,24 +552,36 @@ async function startViewer(projectDir, options = {}) {
257
552
  const inbox = (0, kernel_js_1.memoryInbox)(projectDir);
258
553
  (0, node_fs_1.writeFileSync)(inboxPath, JSON.stringify(inbox, null, 2));
259
554
  (0, node_fs_1.writeFileSync)(qualityPath, JSON.stringify((0, kernel_js_1.qualityReport)(projectDir), null, 2));
260
- (0, node_fs_1.writeFileSync)(benchmarkPath, JSON.stringify((0, kernel_js_1.benchmarkProject)(projectDir), null, 2));
555
+ (0, node_fs_1.writeFileSync)(benchmarkPath, JSON.stringify(viewerBenchmarkReport(projectDir), null, 2));
261
556
  (0, node_fs_1.writeFileSync)(contributorsPath, JSON.stringify((0, kernel_js_1.kageContributors)(projectDir), null, 2));
557
+ (0, node_fs_1.writeFileSync)(profilePath, JSON.stringify((0, kernel_js_1.kageProjectProfile)(projectDir), null, 2));
558
+ (0, node_fs_1.writeFileSync)(capabilitiesPath, JSON.stringify((0, kernel_js_1.kageCapabilityAudit)(projectDir), null, 2));
559
+ (0, node_fs_1.writeFileSync)(slotsPath, JSON.stringify((0, kernel_js_1.kageContextSlots)(projectDir), null, 2));
262
560
  (0, node_fs_1.writeFileSync)(decisionsPath, JSON.stringify((0, kernel_js_1.kageDecisionIntelligence)(projectDir), null, 2));
263
561
  (0, node_fs_1.writeFileSync)(riskPath, JSON.stringify((0, kernel_js_1.kageRisk)(projectDir), null, 2));
264
562
  (0, node_fs_1.writeFileSync)(moduleHealthPath, JSON.stringify((0, kernel_js_1.kageModuleHealth)(projectDir), null, 2));
265
563
  (0, node_fs_1.writeFileSync)(graphInsightsPath, JSON.stringify((0, kernel_js_1.kageGraphInsights)(projectDir), null, 2));
266
564
  (0, node_fs_1.writeFileSync)(workspacePath, JSON.stringify((0, kernel_js_1.kageWorkspace)(projectDir), null, 2));
565
+ (0, node_fs_1.writeFileSync)(sessionsPath, JSON.stringify((0, kernel_js_1.kageSessionCaptureReport)(projectDir), null, 2));
566
+ (0, node_fs_1.writeFileSync)(replayPath, JSON.stringify((0, kernel_js_1.kageSessionReplay)(projectDir), null, 2));
567
+ (0, node_fs_1.writeFileSync)(memoryAccessPath, JSON.stringify((0, kernel_js_1.kageMemoryAccess)(projectDir), null, 2));
568
+ (0, node_fs_1.writeFileSync)(memoryAuditPath, JSON.stringify((0, kernel_js_1.kageMemoryAudit)(projectDir), null, 2));
569
+ (0, node_fs_1.writeFileSync)(handoffPath, JSON.stringify((0, kernel_js_1.kageMemoryHandoff)(projectDir), null, 2));
570
+ (0, node_fs_1.writeFileSync)(lifecyclePath, JSON.stringify((0, kernel_js_1.kageMemoryLifecycle)(projectDir), null, 2));
571
+ (0, node_fs_1.writeFileSync)(timelinePath, JSON.stringify((0, kernel_js_1.kageMemoryTimeline)(projectDir), null, 2));
572
+ (0, node_fs_1.writeFileSync)(lineagePath, JSON.stringify((0, kernel_js_1.kageMemoryLineage)(projectDir), null, 2));
573
+ (0, node_fs_1.writeFileSync)(setupPath, JSON.stringify((0, kernel_js_1.setupDoctor)(projectDir), null, 2));
267
574
  }
268
575
  catch {
269
576
  // non-fatal: viewer will show 404 for reports if generation fails
270
577
  }
271
- const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&inbox=${encodeURIComponent(inboxPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}&quality=${encodeURIComponent(qualityPath)}&benchmark=${encodeURIComponent(benchmarkPath)}&contributors=${encodeURIComponent(contributorsPath)}&decisions=${encodeURIComponent(decisionsPath)}&risk=${encodeURIComponent(riskPath)}&moduleHealth=${encodeURIComponent(moduleHealthPath)}&graphInsights=${encodeURIComponent(graphInsightsPath)}&workspace=${encodeURIComponent(workspacePath)}&view=code`;
578
+ const url = `http://${host}:${port}/viewer/index.html?graph=${encodeURIComponent(graphPath)}&code=${encodeURIComponent(codePath)}&metrics=${encodeURIComponent(metricsPath)}&inbox=${encodeURIComponent(inboxPath)}&review=${encodeURIComponent(reviewPath)}&pending=${encodeURIComponent(pendingDir)}&quality=${encodeURIComponent(qualityPath)}&benchmark=${encodeURIComponent(benchmarkPath)}&contributors=${encodeURIComponent(contributorsPath)}&profile=${encodeURIComponent(profilePath)}&capabilities=${encodeURIComponent(capabilitiesPath)}&slots=${encodeURIComponent(slotsPath)}&decisions=${encodeURIComponent(decisionsPath)}&risk=${encodeURIComponent(riskPath)}&moduleHealth=${encodeURIComponent(moduleHealthPath)}&graphInsights=${encodeURIComponent(graphInsightsPath)}&workspace=${encodeURIComponent(workspacePath)}&sessions=${encodeURIComponent(sessionsPath)}&replay=${encodeURIComponent(replayPath)}&memoryAccess=${encodeURIComponent(memoryAccessPath)}&memoryAudit=${encodeURIComponent(memoryAuditPath)}&handoff=${encodeURIComponent(handoffPath)}&lifecycle=${encodeURIComponent(lifecyclePath)}&timeline=${encodeURIComponent(timelinePath)}&lineage=${encodeURIComponent(lineagePath)}&setup=${encodeURIComponent(setupPath)}&view=code`;
272
579
  const server = (0, node_http_1.createServer)((req, res) => {
273
580
  const requestUrl = new URL(req.url ?? "/", `http://${host}:${port}`);
274
581
  let filePath = null;
275
- if (requestUrl.pathname === "/" || requestUrl.pathname === "/viewer") {
276
- const viewerSearch = requestUrl.search || new URL(url).search;
277
- res.writeHead(302, { location: `/viewer/index.html${viewerSearch}` });
582
+ const redirectLocation = viewerRedirectLocation(requestUrl.pathname, requestUrl.search, new URL(url).search);
583
+ if (redirectLocation) {
584
+ res.writeHead(302, { location: redirectLocation });
278
585
  res.end();
279
586
  return;
280
587
  }
@@ -303,7 +610,7 @@ async function startViewer(projectDir, options = {}) {
303
610
  json(res, 200, { ok: true, files });
304
611
  return;
305
612
  }
306
- res.writeHead(200, { "content-type": contentType(filePath) });
613
+ res.writeHead(200, viewerStaticHeaders(filePath));
307
614
  res.end((0, node_fs_1.readFileSync)(filePath));
308
615
  });
309
616
  await new Promise((resolveListen) => server.listen(port, host, resolveListen));