@rubytech/create-maxy-code 0.1.132 → 0.1.138

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.
Files changed (128) hide show
  1. package/dist/__tests__/installer-specialist-registration.test.js +5 -40
  2. package/dist/index.js +24 -28
  3. package/dist/specialist-registration.js +0 -15
  4. package/package.json +1 -1
  5. package/payload/platform/lib/graph-search/dist/boosts.d.ts +39 -0
  6. package/payload/platform/lib/graph-search/dist/boosts.d.ts.map +1 -0
  7. package/payload/platform/lib/graph-search/dist/boosts.js +57 -0
  8. package/payload/platform/lib/graph-search/dist/boosts.js.map +1 -0
  9. package/payload/platform/lib/graph-search/dist/dedup.d.ts +29 -0
  10. package/payload/platform/lib/graph-search/dist/dedup.d.ts.map +1 -0
  11. package/payload/platform/lib/graph-search/dist/dedup.js +97 -0
  12. package/payload/platform/lib/graph-search/dist/dedup.js.map +1 -0
  13. package/payload/platform/lib/graph-search/dist/index.d.ts +70 -8
  14. package/payload/platform/lib/graph-search/dist/index.d.ts.map +1 -1
  15. package/payload/platform/lib/graph-search/dist/index.js +323 -62
  16. package/payload/platform/lib/graph-search/dist/index.js.map +1 -1
  17. package/payload/platform/lib/graph-search/dist/query-expansion.d.ts +37 -0
  18. package/payload/platform/lib/graph-search/dist/query-expansion.d.ts.map +1 -0
  19. package/payload/platform/lib/graph-search/dist/query-expansion.js +101 -0
  20. package/payload/platform/lib/graph-search/dist/query-expansion.js.map +1 -0
  21. package/payload/platform/lib/graph-search/dist/route.d.ts +29 -0
  22. package/payload/platform/lib/graph-search/dist/route.d.ts.map +1 -0
  23. package/payload/platform/lib/graph-search/dist/route.js +53 -0
  24. package/payload/platform/lib/graph-search/dist/route.js.map +1 -0
  25. package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts +31 -0
  26. package/payload/platform/lib/graph-search/dist/rrf-fusion.d.ts.map +1 -0
  27. package/payload/platform/lib/graph-search/dist/rrf-fusion.js +57 -0
  28. package/payload/platform/lib/graph-search/dist/rrf-fusion.js.map +1 -0
  29. package/payload/platform/lib/graph-search/src/__tests__/boosts.test.ts +59 -0
  30. package/payload/platform/lib/graph-search/src/__tests__/dedup.test.ts +83 -0
  31. package/payload/platform/lib/graph-search/src/__tests__/query-expansion.test.ts +96 -0
  32. package/payload/platform/lib/graph-search/src/__tests__/route.test.ts +62 -0
  33. package/payload/platform/lib/graph-search/src/__tests__/rrf-fusion.test.ts +39 -0
  34. package/payload/platform/lib/graph-search/src/boosts.ts +61 -0
  35. package/payload/platform/lib/graph-search/src/dedup.ts +108 -0
  36. package/payload/platform/lib/graph-search/src/index.ts +407 -65
  37. package/payload/platform/lib/graph-search/src/query-expansion.ts +129 -0
  38. package/payload/platform/lib/graph-search/src/route.ts +70 -0
  39. package/payload/platform/lib/graph-search/src/rrf-fusion.ts +62 -0
  40. package/payload/platform/neo4j/schema.cypher +80 -1
  41. package/payload/platform/plugins/admin/hooks/session-end-retrospective.sh +4 -2
  42. package/payload/platform/plugins/admin/mcp/dist/index.js +6 -4
  43. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  44. package/payload/platform/plugins/docs/references/internals.md +36 -10
  45. package/payload/platform/plugins/docs/references/neo4j.md +63 -0
  46. package/payload/platform/plugins/docs/references/session-retrospective.md +11 -4
  47. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +2 -2
  48. package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
  49. package/payload/platform/plugins/memory/mcp/dist/index.js +114 -5
  50. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  51. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts +2 -0
  52. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.d.ts.map +1 -0
  53. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js +25 -0
  54. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/graph-write-gate-property.test.js.map +1 -0
  55. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts +2 -0
  56. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.d.ts.map +1 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js +70 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/timeline-extractor.test.js.map +1 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts +2 -0
  60. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.d.ts.map +1 -0
  61. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js +50 -0
  62. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/typed-edge-schema.test.js.map +1 -0
  63. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts +33 -0
  64. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.d.ts.map +1 -0
  65. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js +99 -0
  66. package/payload/platform/plugins/memory/mcp/dist/lib/compiled-truth-rewriter.js.map +1 -0
  67. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts +21 -0
  68. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.d.ts.map +1 -1
  69. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js +24 -0
  70. package/payload/platform/plugins/memory/mcp/dist/lib/graph-write-gate.js.map +1 -1
  71. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts +13 -0
  72. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.d.ts.map +1 -0
  73. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js +151 -0
  74. package/payload/platform/plugins/memory/mcp/dist/lib/timeline-extractor.js.map +1 -0
  75. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts +55 -0
  76. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.d.ts.map +1 -0
  77. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js +290 -0
  78. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-pass.js.map +1 -0
  79. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts +19 -0
  80. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -0
  81. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +102 -0
  82. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -0
  83. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js +18 -1
  84. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-search-fields.test.js.map +1 -1
  85. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js +5 -0
  86. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-emit.test.js.map +1 -1
  87. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js +2 -0
  88. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-write-emit.test.js.map +1 -1
  89. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.d.ts.map +1 -1
  90. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js +14 -0
  91. package/payload/platform/plugins/memory/mcp/dist/tools/memory-search.js.map +1 -1
  92. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts +29 -0
  93. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.d.ts.map +1 -0
  94. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js +22 -0
  95. package/payload/platform/plugins/memory/mcp/dist/tools/memory-typed-edge-pass.js.map +1 -0
  96. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts +31 -1
  97. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  98. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +182 -50
  99. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  100. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  101. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +76 -1
  102. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  103. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts +30 -0
  104. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.d.ts.map +1 -0
  105. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js +129 -0
  106. package/payload/platform/plugins/memory/mcp/dist/tools/session-retrospective-skip-rate.js.map +1 -0
  107. package/payload/platform/plugins/memory/mcp/scripts/backfill-typed-edges.ts +72 -0
  108. package/payload/platform/plugins/memory/mcp/scripts/generate-edge-docs.ts +75 -0
  109. package/payload/platform/plugins/memory/mcp/vitest.config.ts +5 -0
  110. package/payload/platform/plugins/memory/references/schema-base.md +44 -0
  111. package/payload/platform/scripts/seed-neo4j.sh +49 -0
  112. package/payload/platform/templates/agents/admin/IDENTITY.md +4 -3
  113. package/payload/platform/templates/specialists/agents/database-operator.md +9 -1
  114. package/payload/server/{adminuser-self-heal-QAWOZ3JV.js → adminuser-self-heal-YC47O34W.js} +2 -1
  115. package/payload/server/{chunk-JSBRDJBE.js → chunk-HYQNUVGO.js} +9 -1
  116. package/payload/server/{chunk-HCYM5FLU.js → chunk-T3DJD5QR.js} +3 -1
  117. package/payload/server/maxy-edge.js +3 -2
  118. package/payload/server/public/assets/{admin-FcRHAL-3.js → admin-BM9aXUUh.js} +1 -1
  119. package/payload/server/public/assets/{data-Ds37mflX.js → data-Ds2ECtfP.js} +1 -1
  120. package/payload/server/public/assets/{graph-CmWRhaiS.js → graph-BzxObKMM.js} +1 -1
  121. package/payload/server/public/assets/{graph-labels-Ch2r00Gt.js → graph-labels-Dwgep_nr.js} +1 -1
  122. package/payload/server/public/assets/{page-BcHhJXUt.js → page-BlkBsjP2.js} +1 -1
  123. package/payload/server/public/assets/page-h_SA5SVQ.js +51 -0
  124. package/payload/server/public/data.html +3 -3
  125. package/payload/server/public/graph.html +3 -3
  126. package/payload/server/public/index.html +4 -4
  127. package/payload/server/server.js +803 -76
  128. package/payload/server/public/assets/page-BOtNny_4.js +0 -51
@@ -1,3 +1,4 @@
1
+ import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
1
2
  import {
2
3
  ACCOUNTS_DIR,
3
4
  BIN_DIR,
@@ -83,11 +84,12 @@ import {
83
84
  vncLog,
84
85
  walkPremiumBundles,
85
86
  writeAdminUserAndPerson
86
- } from "./chunk-HCYM5FLU.js";
87
+ } from "./chunk-T3DJD5QR.js";
87
88
  import {
88
89
  __commonJS,
90
+ __require,
89
91
  __toESM
90
- } from "./chunk-JSBRDJBE.js";
92
+ } from "./chunk-HYQNUVGO.js";
91
93
 
92
94
  // ../lib/graph-trash/dist/index.js
93
95
  var require_dist = __commonJS({
@@ -365,8 +367,314 @@ var require_dist = __commonJS({
365
367
  }
366
368
  });
367
369
 
368
- // ../lib/graph-style/dist/index.js
370
+ // ../lib/oauth-llm/dist/index.js
369
371
  var require_dist2 = __commonJS({
372
+ "../lib/oauth-llm/dist/index.js"(exports) {
373
+ "use strict";
374
+ Object.defineProperty(exports, "__esModule", { value: true });
375
+ exports.OAUTH_BETA_HEADER = void 0;
376
+ exports.callOauthLlm = callOauthLlm3;
377
+ var node_fs_1 = __require("fs");
378
+ var node_path_1 = __require("path");
379
+ var node_os_1 = __require("os");
380
+ var CREDENTIALS_FILE2 = process.env.CLAUDE_CONFIG_DIR ? (0, node_path_1.resolve)(process.env.CLAUDE_CONFIG_DIR, ".credentials.json") : (0, node_path_1.resolve)((0, node_os_1.homedir)(), ".claude", ".credentials.json");
381
+ var TOKEN_ENDPOINT3 = "https://platform.claude.com/v1/oauth/token";
382
+ var ANTHROPIC_MESSAGES_ENDPOINT2 = "https://api.anthropic.com/v1/messages";
383
+ var ANTHROPIC_VERSION2 = "2023-06-01";
384
+ var CLIENT_ID3 = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
385
+ var OAUTH_BETA_HEADER2 = "oauth-2025-04-20";
386
+ exports.OAUTH_BETA_HEADER = OAUTH_BETA_HEADER2;
387
+ var EXPIRING_THRESHOLD_MS3 = 5 * 60 * 1e3;
388
+ var INPUT_TOO_LARGE_MARKERS2 = [
389
+ "prompt is too long",
390
+ "context_length_exceeded",
391
+ "too many tokens"
392
+ ];
393
+ function isInputTooLargeBody2(body) {
394
+ const lower = body.toLowerCase();
395
+ return INPUT_TOO_LARGE_MARKERS2.some((m) => lower.includes(m));
396
+ }
397
+ function readCredentials3() {
398
+ let raw;
399
+ try {
400
+ raw = (0, node_fs_1.readFileSync)(CREDENTIALS_FILE2, "utf-8");
401
+ } catch {
402
+ return null;
403
+ }
404
+ let data;
405
+ try {
406
+ data = JSON.parse(raw);
407
+ } catch {
408
+ return null;
409
+ }
410
+ const oauth = data.claudeAiOauth;
411
+ if (!oauth || typeof oauth !== "object")
412
+ return null;
413
+ const accessToken = oauth.accessToken;
414
+ const refreshToken = oauth.refreshToken;
415
+ const expiresAt = oauth.expiresAt;
416
+ if (typeof accessToken !== "string" || !accessToken)
417
+ return null;
418
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt) || expiresAt <= 0) {
419
+ return null;
420
+ }
421
+ return {
422
+ accessToken,
423
+ refreshToken: typeof refreshToken === "string" && refreshToken ? refreshToken : null,
424
+ expiresAt
425
+ };
426
+ }
427
+ function writeCredentials2(tokens) {
428
+ const newExpiresAt = typeof tokens.expiresIn === "number" && tokens.expiresIn > 0 ? Date.now() + tokens.expiresIn * 1e3 : Date.now() + 3600 * 1e3;
429
+ let fileData = {};
430
+ try {
431
+ fileData = JSON.parse((0, node_fs_1.readFileSync)(CREDENTIALS_FILE2, "utf-8"));
432
+ } catch {
433
+ }
434
+ const existing = fileData.claudeAiOauth;
435
+ fileData.claudeAiOauth = {
436
+ ...existing,
437
+ accessToken: tokens.accessToken,
438
+ ...tokens.refreshToken != null ? { refreshToken: tokens.refreshToken } : {},
439
+ expiresAt: newExpiresAt
440
+ };
441
+ (0, node_fs_1.writeFileSync)(CREDENTIALS_FILE2, JSON.stringify(fileData, null, 2), "utf-8");
442
+ return newExpiresAt;
443
+ }
444
+ var refreshLock3 = null;
445
+ async function refreshAccessToken3(current) {
446
+ if (refreshLock3)
447
+ return refreshLock3;
448
+ refreshLock3 = (async () => {
449
+ const fresh = readCredentials3();
450
+ if (fresh && fresh.expiresAt - Date.now() > EXPIRING_THRESHOLD_MS3) {
451
+ return fresh;
452
+ }
453
+ if (!current.refreshToken) {
454
+ return null;
455
+ }
456
+ let res;
457
+ try {
458
+ res = await fetch(TOKEN_ENDPOINT3, {
459
+ method: "POST",
460
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
461
+ body: new URLSearchParams({
462
+ grant_type: "refresh_token",
463
+ refresh_token: current.refreshToken,
464
+ client_id: CLIENT_ID3
465
+ })
466
+ });
467
+ } catch (err) {
468
+ const msg = err instanceof Error ? err.message : String(err);
469
+ console.error(`[oauth-llm] refresh network error: ${msg}`);
470
+ return null;
471
+ }
472
+ if (!res.ok) {
473
+ const body = await res.text().catch(() => "");
474
+ console.error(`[oauth-llm] refresh failed status=${res.status} body=${body.slice(0, 200)}`);
475
+ return null;
476
+ }
477
+ let tokenData;
478
+ try {
479
+ tokenData = await res.json();
480
+ } catch {
481
+ console.error("[oauth-llm] refresh returned malformed JSON");
482
+ return null;
483
+ }
484
+ const newAccessToken = tokenData.access_token;
485
+ const newRefreshToken = tokenData.refresh_token;
486
+ const expiresIn = tokenData.expires_in;
487
+ if (typeof newAccessToken !== "string" || !newAccessToken) {
488
+ console.error("[oauth-llm] refresh response missing access_token");
489
+ return null;
490
+ }
491
+ const newExpiresAt = writeCredentials2({
492
+ accessToken: newAccessToken,
493
+ refreshToken: typeof newRefreshToken === "string" && newRefreshToken ? newRefreshToken : current.refreshToken,
494
+ expiresIn: typeof expiresIn === "number" && expiresIn > 0 ? expiresIn : void 0
495
+ });
496
+ console.error(`[oauth-llm] refresh ok expiresAt=${new Date(newExpiresAt).toISOString()}`);
497
+ return {
498
+ accessToken: newAccessToken,
499
+ refreshToken: typeof newRefreshToken === "string" && newRefreshToken ? newRefreshToken : current.refreshToken,
500
+ expiresAt: newExpiresAt
501
+ };
502
+ })();
503
+ try {
504
+ return await refreshLock3;
505
+ } finally {
506
+ refreshLock3 = null;
507
+ }
508
+ }
509
+ async function ensureValidToken2() {
510
+ const creds = readCredentials3();
511
+ if (!creds)
512
+ return null;
513
+ const remaining = creds.expiresAt - Date.now();
514
+ if (remaining > EXPIRING_THRESHOLD_MS3)
515
+ return creds;
516
+ if (remaining > 0 && !creds.refreshToken)
517
+ return creds;
518
+ if (remaining <= 0 && !creds.refreshToken)
519
+ return null;
520
+ return refreshAccessToken3(creds);
521
+ }
522
+ var betaHeaderLogged2 = false;
523
+ function logBetaHeaderOnce2() {
524
+ if (betaHeaderLogged2)
525
+ return;
526
+ betaHeaderLogged2 = true;
527
+ console.error(`[oauth-llm] beta-header=${OAUTH_BETA_HEADER2}`);
528
+ }
529
+ async function callOauthLlm3(params) {
530
+ const { model, system, userMessage, maxTokens = 4096, timeoutMs = 6e4, tools, toolChoiceName } = params;
531
+ if (tools && tools.length > 0 && !toolChoiceName) {
532
+ return {
533
+ kind: "fallback",
534
+ cause: "unexpected",
535
+ reason: "callOauthLlm: `tools` provided without `toolChoiceName`. Forced tool selection is required."
536
+ };
537
+ }
538
+ logBetaHeaderOnce2();
539
+ const creds = await ensureValidToken2();
540
+ if (!creds) {
541
+ return {
542
+ kind: "fallback",
543
+ cause: "missing-creds",
544
+ reason: "Claude Code OAuth credentials missing or expired with no refresh token available \u2014 operator must re-authenticate."
545
+ };
546
+ }
547
+ const controller = new AbortController();
548
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
549
+ let res;
550
+ try {
551
+ res = await fetch(ANTHROPIC_MESSAGES_ENDPOINT2, {
552
+ method: "POST",
553
+ headers: {
554
+ "Content-Type": "application/json",
555
+ "anthropic-version": ANTHROPIC_VERSION2,
556
+ "anthropic-beta": OAUTH_BETA_HEADER2,
557
+ Authorization: `Bearer ${creds.accessToken}`
558
+ },
559
+ body: JSON.stringify({
560
+ model,
561
+ max_tokens: maxTokens,
562
+ system,
563
+ messages: [{ role: "user", content: userMessage }],
564
+ ...tools && tools.length > 0 ? { tools } : {},
565
+ ...toolChoiceName ? { tool_choice: { type: "tool", name: toolChoiceName } } : {}
566
+ }),
567
+ signal: controller.signal
568
+ });
569
+ } catch (err) {
570
+ clearTimeout(timer2);
571
+ const msg = err instanceof Error ? err.message : String(err);
572
+ if (controller.signal.aborted) {
573
+ return {
574
+ kind: "fallback",
575
+ cause: "timeout",
576
+ reason: `Model call timed out after ${timeoutMs}ms.`
577
+ };
578
+ }
579
+ return {
580
+ kind: "fallback",
581
+ cause: "network-error",
582
+ reason: `Network error reaching Anthropic: ${msg}`
583
+ };
584
+ }
585
+ clearTimeout(timer2);
586
+ if (!res.ok) {
587
+ const body = await res.text().catch(() => "");
588
+ if (res.status === 401 || res.status === 403) {
589
+ return {
590
+ kind: "fallback",
591
+ cause: "auth-error",
592
+ reason: `OAuth bearer rejected (status=${res.status}) \u2014 token may be revoked or beta header rejected. body=${body.slice(0, 200)}`
593
+ };
594
+ }
595
+ if (res.status === 429) {
596
+ return {
597
+ kind: "fallback",
598
+ cause: "rate-limit",
599
+ reason: `Anthropic rate limit hit (status=429): ${body.slice(0, 200)}`
600
+ };
601
+ }
602
+ if (res.status >= 500) {
603
+ return {
604
+ kind: "fallback",
605
+ cause: "server-error",
606
+ reason: `Anthropic server error (status=${res.status}): ${body.slice(0, 200)}`
607
+ };
608
+ }
609
+ if (res.status === 400 && isInputTooLargeBody2(body)) {
610
+ return {
611
+ kind: "fallback",
612
+ cause: "input-too-large",
613
+ reason: `Input exceeds Haiku context window (status=400 body=${body.slice(0, 200)}). Split the source into smaller segments.`
614
+ };
615
+ }
616
+ return {
617
+ kind: "fallback",
618
+ cause: "unexpected",
619
+ reason: `Anthropic returned status=${res.status}: ${body.slice(0, 200)}`
620
+ };
621
+ }
622
+ let payload;
623
+ try {
624
+ payload = await res.json();
625
+ } catch {
626
+ return {
627
+ kind: "fallback",
628
+ cause: "malformed-response",
629
+ reason: "Anthropic returned malformed JSON."
630
+ };
631
+ }
632
+ if (payload.error) {
633
+ return {
634
+ kind: "fallback",
635
+ cause: "unexpected",
636
+ reason: `Anthropic API error: ${payload.error.type ?? "unknown"} ${payload.error.message ?? ""}`
637
+ };
638
+ }
639
+ const stopReason = typeof payload.stop_reason === "string" ? payload.stop_reason : void 0;
640
+ const outputTokens = typeof payload.usage?.output_tokens === "number" ? payload.usage.output_tokens : void 0;
641
+ if (toolChoiceName) {
642
+ const toolBlock = payload.content?.find((block) => block.type === "tool_use");
643
+ if (!toolBlock || !toolBlock.input || typeof toolBlock.input !== "object") {
644
+ return {
645
+ kind: "fallback",
646
+ cause: "empty-response",
647
+ reason: "Model returned no tool_use block despite forced tool selection."
648
+ };
649
+ }
650
+ return {
651
+ kind: "ok-tool",
652
+ toolName: toolBlock.name ?? toolChoiceName,
653
+ input: toolBlock.input,
654
+ stopReason,
655
+ outputTokens
656
+ };
657
+ }
658
+ const textBlock = payload.content?.find((block) => {
659
+ if (block.type !== "text")
660
+ return false;
661
+ const candidate = block.text;
662
+ return typeof candidate === "string" && candidate.trim().length > 0;
663
+ });
664
+ if (!textBlock) {
665
+ return {
666
+ kind: "fallback",
667
+ cause: "empty-response",
668
+ reason: "Model returned no text content."
669
+ };
670
+ }
671
+ return { kind: "ok", text: textBlock.text, stopReason, outputTokens };
672
+ }
673
+ }
674
+ });
675
+
676
+ // ../lib/graph-style/dist/index.js
677
+ var require_dist3 = __commonJS({
370
678
  "../lib/graph-style/dist/index.js"(exports) {
371
679
  "use strict";
372
680
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -1874,17 +2182,17 @@ var INBOUND_TTL_MS = 20 * 6e4;
1874
2182
  var INBOUND_MAX = 5e3;
1875
2183
  var agentSentCache = /* @__PURE__ */ new Map();
1876
2184
  var inboundCache = /* @__PURE__ */ new Map();
1877
- function pruneCache(cache2, ttl, max) {
2185
+ function pruneCache(cache3, ttl, max) {
1878
2186
  const now = Date.now();
1879
- for (const [key, entry] of cache2) {
1880
- if (now - entry.ts > ttl) cache2.delete(key);
2187
+ for (const [key, entry] of cache3) {
2188
+ if (now - entry.ts > ttl) cache3.delete(key);
1881
2189
  }
1882
- if (cache2.size > max) {
1883
- const excess = cache2.size - max;
2190
+ if (cache3.size > max) {
2191
+ const excess = cache3.size - max;
1884
2192
  let count = 0;
1885
- for (const key of cache2.keys()) {
2193
+ for (const key of cache3.keys()) {
1886
2194
  if (count >= excess) break;
1887
- cache2.delete(key);
2195
+ cache3.delete(key);
1888
2196
  count++;
1889
2197
  }
1890
2198
  }
@@ -6502,16 +6810,11 @@ function readUsersFile() {
6502
6810
  if (!raw) return [];
6503
6811
  return JSON.parse(raw);
6504
6812
  }
6505
- function claudeSpawnPath() {
6506
- const base = process.env.PATH ?? "/usr/bin:/bin:/usr/sbin:/sbin";
6507
- return `${base}:/opt/homebrew/bin:/usr/local/bin`;
6508
- }
6509
6813
  function checkAuthStatus() {
6510
6814
  try {
6511
6815
  execFileSync2("claude", ["auth", "status"], {
6512
6816
  encoding: "utf-8",
6513
- timeout: 5e3,
6514
- env: { ...process.env, PATH: claudeSpawnPath() }
6817
+ timeout: 5e3
6515
6818
  });
6516
6819
  return true;
6517
6820
  } catch {
@@ -6573,7 +6876,6 @@ app4.post("/claude-auth", async (c) => {
6573
6876
  if (process.platform === "darwin") {
6574
6877
  vncLog("claude-auth", { action: "start", transport: "native", platform: "darwin" });
6575
6878
  const claudeProc2 = spawn("claude", ["auth", "login"], {
6576
- env: { ...process.env, PATH: claudeSpawnPath() },
6577
6879
  stdio: ["ignore", "pipe", "pipe"]
6578
6880
  });
6579
6881
  claudeProc2.unref();
@@ -9716,10 +10018,251 @@ app18.delete("/", requireAdminSession, async (c) => {
9716
10018
  var files_default = app18;
9717
10019
 
9718
10020
  // ../lib/graph-search/src/index.ts
9719
- var import_dist = __toESM(require_dist());
10021
+ var import_dist2 = __toESM(require_dist());
9720
10022
  import { int } from "neo4j-driver";
10023
+
10024
+ // ../lib/graph-search/src/rrf-fusion.ts
10025
+ function fuseRrf(rankedLists, k = 60) {
10026
+ const scores = /* @__PURE__ */ new Map();
10027
+ const representatives = /* @__PURE__ */ new Map();
10028
+ const firstSeen = /* @__PURE__ */ new Map();
10029
+ let order = 0;
10030
+ for (const list of rankedLists) {
10031
+ for (let rank = 0; rank < list.length; rank++) {
10032
+ const candidate = list[rank];
10033
+ const contribution = 1 / (k + rank);
10034
+ const prior = scores.get(candidate.nodeId) ?? 0;
10035
+ scores.set(candidate.nodeId, prior + contribution);
10036
+ if (!representatives.has(candidate.nodeId)) {
10037
+ representatives.set(candidate.nodeId, candidate);
10038
+ firstSeen.set(candidate.nodeId, order++);
10039
+ }
10040
+ }
10041
+ }
10042
+ const out = [];
10043
+ for (const [nodeId, rrfScore] of scores) {
10044
+ const rep = representatives.get(nodeId);
10045
+ out.push({ ...rep, rrfScore });
10046
+ }
10047
+ out.sort((a, b) => {
10048
+ if (b.rrfScore !== a.rrfScore) return b.rrfScore - a.rrfScore;
10049
+ return firstSeen.get(a.nodeId) - firstSeen.get(b.nodeId);
10050
+ });
10051
+ return out;
10052
+ }
10053
+
10054
+ // ../lib/graph-search/src/query-expansion.ts
10055
+ var import_dist = __toESM(require_dist2());
10056
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
10057
+ var HAIKU_MODEL2 = "claude-haiku-4-5";
10058
+ var MAX_PARAPHRASES = 5;
10059
+ var cache2 = /* @__PURE__ */ new Map();
10060
+ async function expandQuery(params) {
10061
+ const { query, retrievalClass = "general", accountId = "", useCache = true } = params;
10062
+ const normalised = query.trim().toLowerCase();
10063
+ const cacheKey = `${accountId}::${retrievalClass}::${normalised}`;
10064
+ if (useCache) {
10065
+ const hit = cache2.get(cacheKey);
10066
+ if (hit && hit.expiresAt > Date.now()) {
10067
+ return [query, ...hit.expansions];
10068
+ }
10069
+ }
10070
+ const systemPrompt = [
10071
+ "You generate paraphrases of a user query for hybrid graph search.",
10072
+ "Return 3-5 alternative phrasings that preserve the user's intent but vary the surface form (synonyms, word order, entity-vs-attribute framing).",
10073
+ "Do not change the entity names. Do not invent constraints the user did not state.",
10074
+ "Output ONLY a JSON object via the provided tool; never free text."
10075
+ ].join("\n");
10076
+ const classHint = retrievalClass === "general" ? "" : `The retrieval class is '${retrievalClass}' \u2014 tailor paraphrases accordingly.`;
10077
+ const userMessage = [
10078
+ `Original query: ${query}`,
10079
+ classHint
10080
+ ].filter((s) => s.length > 0).join("\n");
10081
+ const result = await (0, import_dist.callOauthLlm)({
10082
+ model: HAIKU_MODEL2,
10083
+ system: systemPrompt,
10084
+ userMessage,
10085
+ maxTokens: 512,
10086
+ timeoutMs: 15e3,
10087
+ tools: [
10088
+ {
10089
+ name: "emit_paraphrases",
10090
+ description: "Emit 3-5 paraphrases of the user query.",
10091
+ input_schema: {
10092
+ type: "object",
10093
+ properties: {
10094
+ paraphrases: {
10095
+ type: "array",
10096
+ items: { type: "string" },
10097
+ minItems: 3,
10098
+ maxItems: MAX_PARAPHRASES
10099
+ }
10100
+ },
10101
+ required: ["paraphrases"]
10102
+ }
10103
+ }
10104
+ ],
10105
+ toolChoiceName: "emit_paraphrases"
10106
+ });
10107
+ if (result.kind !== "ok-tool") {
10108
+ process.stderr.write(
10109
+ `[graph-search:expansion-failed] cause=${result.kind === "fallback" ? result.cause : "unexpected"} query=${query.slice(0, 60).replace(/\s+/g, " ")}
10110
+ `
10111
+ );
10112
+ return [query];
10113
+ }
10114
+ const raw = result.input.paraphrases;
10115
+ if (!Array.isArray(raw)) return [query];
10116
+ const expansions = raw.filter((p) => typeof p === "string" && p.trim().length > 0).map((p) => p.trim()).slice(0, MAX_PARAPHRASES);
10117
+ if (useCache) {
10118
+ cache2.set(cacheKey, { expansions, expiresAt: Date.now() + CACHE_TTL_MS });
10119
+ }
10120
+ return [query, ...expansions];
10121
+ }
10122
+
10123
+ // ../lib/graph-search/src/boosts.ts
10124
+ function applyCompiledTruthBoost(hits, weight = 0.15) {
10125
+ if (weight < 0 || weight > 1) {
10126
+ throw new Error(`applyCompiledTruthBoost: weight must be in [0,1], got ${weight}`);
10127
+ }
10128
+ return hits.map((hit) => {
10129
+ const compiled = hit.properties["compiledTruth"];
10130
+ if (compiled === null || compiled === void 0 || compiled === "") return hit;
10131
+ return { ...hit, score: hit.score * (1 + weight) };
10132
+ });
10133
+ }
10134
+ function applyBacklinkBoost(hits) {
10135
+ return hits.map((hit) => {
10136
+ const raw = hit.properties["backlinkCount"];
10137
+ const count = typeof raw === "number" ? raw : Number(raw);
10138
+ if (!Number.isFinite(count) || count <= 0) return hit;
10139
+ const bump = Math.max(0.05, Math.min(0.25, 0.05 + 0.05 * Math.log10(count)));
10140
+ return { ...hit, score: hit.score * (1 + bump) };
10141
+ });
10142
+ }
10143
+
10144
+ // ../lib/graph-search/src/dedup.ts
10145
+ import { createHash as createHash3 } from "crypto";
10146
+ var DEFAULT_LAYERS = [
10147
+ "nodeId",
10148
+ "slug",
10149
+ "canonicalName",
10150
+ "contentHash"
10151
+ ];
10152
+ function readSlug(props) {
10153
+ const slug = props["slug"];
10154
+ return typeof slug === "string" && slug.length > 0 ? slug : null;
10155
+ }
10156
+ function readCanonicalName(props) {
10157
+ const canonical = props["canonicalName"];
10158
+ if (typeof canonical === "string" && canonical.length > 0) {
10159
+ return canonical.toLowerCase();
10160
+ }
10161
+ const name = props["name"];
10162
+ if (typeof name === "string" && name.length > 0) {
10163
+ return name.toLowerCase();
10164
+ }
10165
+ return null;
10166
+ }
10167
+ function readContentHash(props) {
10168
+ const compiled = props["compiledTruth"];
10169
+ const content = props["content"];
10170
+ const source = typeof compiled === "string" && compiled.length > 0 ? compiled : typeof content === "string" && content.length > 0 ? content : null;
10171
+ if (source === null) return null;
10172
+ return createHash3("sha256").update(source).digest("hex").slice(0, 16);
10173
+ }
10174
+ function dedup(hits, layers = DEFAULT_LAYERS) {
10175
+ const sorted = [...hits].sort((a, b) => b.score - a.score);
10176
+ const seen = /* @__PURE__ */ new Map();
10177
+ for (const layer of layers) seen.set(layer, /* @__PURE__ */ new Set());
10178
+ const out = [];
10179
+ for (const hit of sorted) {
10180
+ let collision = false;
10181
+ const keys = [];
10182
+ for (const layer of layers) {
10183
+ let key;
10184
+ switch (layer) {
10185
+ case "nodeId":
10186
+ key = hit.nodeId;
10187
+ break;
10188
+ case "slug":
10189
+ key = readSlug(hit.properties);
10190
+ break;
10191
+ case "canonicalName":
10192
+ key = readCanonicalName(hit.properties);
10193
+ break;
10194
+ case "contentHash":
10195
+ key = readContentHash(hit.properties);
10196
+ break;
10197
+ }
10198
+ if (key === null) continue;
10199
+ if (seen.get(layer).has(key)) {
10200
+ collision = true;
10201
+ break;
10202
+ }
10203
+ keys.push([layer, key]);
10204
+ }
10205
+ if (collision) continue;
10206
+ for (const [layer, key] of keys) seen.get(layer).add(key);
10207
+ out.push(hit);
10208
+ }
10209
+ return out;
10210
+ }
10211
+
10212
+ // ../lib/graph-search/src/route.ts
10213
+ function pickIndexMix(retrievalClass) {
10214
+ switch (retrievalClass) {
10215
+ case "entity":
10216
+ return {
10217
+ skip: false,
10218
+ labelFilter: ["Person", "Company", "Concept"],
10219
+ vectorWeight: 0.85,
10220
+ bm25Weight: 0.15
10221
+ };
10222
+ case "temporal":
10223
+ return {
10224
+ skip: false,
10225
+ labelFilter: ["Event"],
10226
+ vectorWeight: 0.2,
10227
+ bm25Weight: 0.8
10228
+ };
10229
+ case "event":
10230
+ return {
10231
+ skip: false,
10232
+ labelFilter: ["Event"],
10233
+ vectorWeight: 0,
10234
+ bm25Weight: 1
10235
+ };
10236
+ case "none":
10237
+ return { skip: true, vectorWeight: 0, bm25Weight: 0 };
10238
+ case "general":
10239
+ default:
10240
+ if (retrievalClass && retrievalClass !== "general") {
10241
+ process.stderr.write(
10242
+ `[graph-search:unknown-class] rc=${retrievalClass} fallback=general
10243
+ `
10244
+ );
10245
+ }
10246
+ return { skip: false, vectorWeight: 0.7, bm25Weight: 0.3 };
10247
+ }
10248
+ }
10249
+
10250
+ // ../lib/graph-search/src/index.ts
9721
10251
  var VECTOR_WEIGHT = 0.7;
9722
10252
  var BM25_WEIGHT = 0.3;
10253
+ function envFlag(name) {
10254
+ const v = process.env[name];
10255
+ return v === "1" || v === "true" || v === "yes";
10256
+ }
10257
+ function readFlags() {
10258
+ return {
10259
+ rrf: envFlag("MAXY_GS_RRF"),
10260
+ expansion: envFlag("MAXY_GS_EXPANSION"),
10261
+ boosts: envFlag("MAXY_GS_BOOSTS"),
10262
+ dedup: envFlag("MAXY_GS_DEDUP"),
10263
+ route: envFlag("MAXY_GS_ROUTE")
10264
+ };
10265
+ }
9723
10266
  var FULLTEXT_INDEX_NAME = "entity_search";
9724
10267
  function escapeLucene(query) {
9725
10268
  return query.replace(/[+\-&|!(){}[\]^"~*?:\\/]/g, "\\$&");
@@ -9771,7 +10314,7 @@ async function bm25Only(session, params) {
9771
10314
  ${scopeClause}
9772
10315
  ${agentClause}
9773
10316
  ${labelClause}
9774
- AND ${(0, import_dist.notTrashed)("node")}
10317
+ AND ${(0, import_dist2.notTrashed)("node")}
9775
10318
  ${kwClause}
9776
10319
  RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
9777
10320
  ORDER BY score DESC
@@ -9818,17 +10361,49 @@ async function hybrid(session, embed2, params) {
9818
10361
  agentSlug,
9819
10362
  keywordSubscriptions,
9820
10363
  expandHops = 1,
9821
- degradeOnEmbedFailure = false
10364
+ degradeOnEmbedFailure = false,
10365
+ retrievalClass,
10366
+ dedupLayers = DEFAULT_LAYERS,
10367
+ rrfK = 60
9822
10368
  } = params;
10369
+ const envFlags = readFlags();
10370
+ const useRoute = params.enableRoute ?? envFlags.route;
10371
+ const useRrf = params.enableRrf ?? envFlags.rrf;
10372
+ const useExpansion = params.enableExpansion ?? envFlags.expansion;
10373
+ const useBoosts = params.enableBoosts ?? envFlags.boosts;
10374
+ const useDedup = params.enableDedup ?? envFlags.dedup;
10375
+ const plan = useRoute ? pickIndexMix(retrievalClass) : { skip: false, vectorWeight: VECTOR_WEIGHT, bm25Weight: BM25_WEIGHT, labelFilter: void 0 };
10376
+ const emptyStageCounts = {
10377
+ expansions: 1,
10378
+ vector: 0,
10379
+ bm25: 0,
10380
+ fused: 0,
10381
+ boosted: 0,
10382
+ deduped: 0,
10383
+ final: 0
10384
+ };
10385
+ if (plan.skip) {
10386
+ return {
10387
+ mode: useRrf ? "rrf" : "hybrid",
10388
+ results: [],
10389
+ expandMs: 0,
10390
+ rawMerged: 0,
10391
+ suppressed: 0,
10392
+ bm25Bypass: 0,
10393
+ threshold: params.vectorThreshold ?? null,
10394
+ stageCounts: emptyStageCounts
10395
+ };
10396
+ }
10397
+ const effectiveLabels = labels && labels.length > 0 ? labels : plan.labelFilter;
9823
10398
  let queryEmbedding;
9824
10399
  try {
9825
10400
  queryEmbedding = await embed2(query);
9826
10401
  } catch (err) {
9827
10402
  if (!degradeOnEmbedFailure) throw err;
9828
10403
  const msg = err instanceof Error ? err.message : String(err);
9829
- const bm25Hits2 = await bm25Only(session, params);
9830
- const normalised = normaliseBm25Scores(bm25Hits2.map((h) => h.score));
9831
- const results2 = bm25Hits2.map((h, i) => ({
10404
+ const bm25Hits = await bm25Only(session, { ...params, labels: effectiveLabels });
10405
+ const normalised = normaliseBm25Scores(bm25Hits.map((h) => h.score));
10406
+ const results2 = bm25Hits.map((h, i) => ({
9832
10407
  ...h,
9833
10408
  vectorScore: 0,
9834
10409
  bm25Score: normalised[i] ?? 0,
@@ -9842,26 +10417,32 @@ async function hybrid(session, embed2, params) {
9842
10417
  rawMerged: results2.length,
9843
10418
  suppressed: 0,
9844
10419
  bm25Bypass: 0,
9845
- threshold: null
10420
+ threshold: null,
10421
+ stageCounts: {
10422
+ expansions: 1,
10423
+ vector: 0,
10424
+ bm25: results2.length,
10425
+ fused: results2.length,
10426
+ boosted: results2.length,
10427
+ deduped: results2.length,
10428
+ final: results2.length
10429
+ }
9846
10430
  };
9847
10431
  }
9848
10432
  const labelToIndex = await discoverIndexes(session);
9849
- const keywordFilter = buildKeywordFilter(keywords, keywordMatch);
9850
- const keywordClause = keywordFilter?.clause ?? "";
9851
- const keywordParams = keywordFilter?.params ?? {};
9852
- const scoreMap = /* @__PURE__ */ new Map();
9853
10433
  let indexesToQuery;
9854
- if (labels && labels.length > 0) {
9855
- indexesToQuery = labels.map((l) => labelToIndex.get(l)).filter((idx) => idx !== void 0);
10434
+ if (effectiveLabels && effectiveLabels.length > 0) {
10435
+ indexesToQuery = effectiveLabels.map((l) => labelToIndex.get(l)).filter((idx) => idx !== void 0);
9856
10436
  if (indexesToQuery.length === 0) {
9857
10437
  return {
9858
- mode: "hybrid",
10438
+ mode: useRrf ? "rrf" : "hybrid",
9859
10439
  results: [],
9860
10440
  expandMs: 0,
9861
10441
  rawMerged: 0,
9862
10442
  suppressed: 0,
9863
10443
  bm25Bypass: 0,
9864
- threshold: params.vectorThreshold ?? null
10444
+ threshold: params.vectorThreshold ?? null,
10445
+ stageCounts: emptyStageCounts
9865
10446
  };
9866
10447
  }
9867
10448
  } else {
@@ -9871,56 +10452,162 @@ async function hybrid(session, embed2, params) {
9871
10452
  const scopeParams = allowedScopes ? { allowedScopes } : {};
9872
10453
  const agentClause = agentSlug ? "AND node.agents IS NOT NULL AND $agentSlug IN node.agents" : "";
9873
10454
  const agentParams = agentSlug ? { agentSlug } : {};
9874
- for (const indexName of indexesToQuery) {
9875
- const vectorResult = await session.run(
9876
- `CALL db.index.vector.queryNodes($indexName, $limit, $embedding)
9877
- YIELD node, score
10455
+ const keywordFilter = buildKeywordFilter(keywords, keywordMatch);
10456
+ const keywordClause = keywordFilter?.clause ?? "";
10457
+ const keywordParams = keywordFilter?.params ?? {};
10458
+ const expansions = useExpansion ? await expandQuery({ query, retrievalClass, accountId }) : [query];
10459
+ const passes = await Promise.all(
10460
+ expansions.map(async (qText, idx) => {
10461
+ let pEmbed;
10462
+ if (idx === 0) {
10463
+ pEmbed = queryEmbedding;
10464
+ } else {
10465
+ try {
10466
+ pEmbed = await embed2(qText);
10467
+ } catch {
10468
+ pEmbed = null;
10469
+ }
10470
+ }
10471
+ const vectorRanked = [];
10472
+ const vectorSeen = /* @__PURE__ */ new Map();
10473
+ if (pEmbed) {
10474
+ for (const indexName of indexesToQuery) {
10475
+ const vectorResult = await session.run(
10476
+ `CALL db.index.vector.queryNodes($indexName, $limit, $embedding)
10477
+ YIELD node, score
10478
+ WHERE node.accountId = $accountId
10479
+ ${scopeClause}
10480
+ ${agentClause}
10481
+ AND ${(0, import_dist2.notTrashed)("node")}
10482
+ ${keywordClause}
10483
+ RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
10484
+ ORDER BY score DESC
10485
+ LIMIT $limit`,
10486
+ {
10487
+ indexName,
10488
+ embedding: pEmbed,
10489
+ limit: int(limit),
10490
+ accountId,
10491
+ ...scopeParams,
10492
+ ...agentParams,
10493
+ ...keywordParams
10494
+ }
10495
+ );
10496
+ for (const record of vectorResult.records) {
10497
+ const nodeId = record.get("nodeId");
10498
+ const scoreRaw = record.get("score");
10499
+ const score = typeof scoreRaw === "number" ? scoreRaw : Number(scoreRaw);
10500
+ const existingIdx = vectorSeen.get(nodeId);
10501
+ if (existingIdx !== void 0) {
10502
+ const existing = vectorRanked[existingIdx];
10503
+ if (score > existing.vectorScore) existing.vectorScore = score;
10504
+ continue;
10505
+ }
10506
+ const node = record.get("node");
10507
+ vectorSeen.set(nodeId, vectorRanked.length);
10508
+ vectorRanked.push({
10509
+ nodeId,
10510
+ labels: record.get("nodeLabels"),
10511
+ properties: plainProperties(node.properties),
10512
+ vectorScore: score,
10513
+ bm25Score: 0,
10514
+ bm25Hit: false
10515
+ });
10516
+ }
10517
+ }
10518
+ vectorRanked.sort((a, b) => b.vectorScore - a.vectorScore);
10519
+ }
10520
+ const bm25Hits = await bm25Only(session, {
10521
+ ...params,
10522
+ query: qText,
10523
+ labels: effectiveLabels
10524
+ });
10525
+ const bm25Norm = normaliseBm25Scores(bm25Hits.map((h) => h.score));
10526
+ const bm25Ranked = bm25Hits.map((h, i) => ({
10527
+ nodeId: h.nodeId,
10528
+ labels: h.labels,
10529
+ properties: h.properties,
10530
+ vectorScore: 0,
10531
+ bm25Score: bm25Norm[i] ?? 0,
10532
+ bm25Hit: true
10533
+ }));
10534
+ return { queryText: qText, embedding: pEmbed, vectorRanked, bm25Ranked };
10535
+ })
10536
+ );
10537
+ const scoreMap = /* @__PURE__ */ new Map();
10538
+ let totalVector = 0;
10539
+ let totalBm25 = 0;
10540
+ for (const pass of passes) {
10541
+ totalVector += pass.vectorRanked.length;
10542
+ totalBm25 += pass.bm25Ranked.length;
10543
+ for (const v of pass.vectorRanked) {
10544
+ const existing = scoreMap.get(v.nodeId);
10545
+ if (existing) {
10546
+ existing.vectorScore = Math.max(existing.vectorScore, v.vectorScore);
10547
+ } else {
10548
+ scoreMap.set(v.nodeId, { ...v });
10549
+ }
10550
+ }
10551
+ for (const b of pass.bm25Ranked) {
10552
+ mergeBm25Hit(scoreMap, {
10553
+ nodeId: b.nodeId,
10554
+ labels: b.labels,
10555
+ properties: b.properties,
10556
+ score: b.bm25Score
10557
+ }, b.bm25Score);
10558
+ }
10559
+ }
10560
+ if (keywordSubscriptions && keywordSubscriptions.length > 0) {
10561
+ for (const kw of keywordSubscriptions) {
10562
+ const kwHits = await bm25Only(session, {
10563
+ query: kw,
10564
+ accountId,
10565
+ limit,
10566
+ allowedScopes
10567
+ });
10568
+ if (kwHits.length === 0) continue;
10569
+ const rawScores = kwHits.map((h) => h.score);
10570
+ const normalised = normaliseBm25Scores(rawScores);
10571
+ for (let i = 0; i < kwHits.length; i++) {
10572
+ mergeBm25Hit(scoreMap, kwHits[i], normalised[i]);
10573
+ }
10574
+ }
10575
+ const propScopeClause = allowedScopes ? "AND (node.scope IS NULL OR node.scope IN $allowedScopes)" : "";
10576
+ const propResult = await session.run(
10577
+ `MATCH (node)
9878
10578
  WHERE node.accountId = $accountId
9879
- ${scopeClause}
9880
- ${agentClause}
9881
- AND ${(0, import_dist.notTrashed)("node")}
9882
- ${keywordClause}
9883
- RETURN node, score, labels(node) AS nodeLabels, elementId(node) AS nodeId
9884
- ORDER BY score DESC
10579
+ AND ${(0, import_dist2.notTrashed)("node")}
10580
+ AND node.keywords IS NOT NULL
10581
+ AND ANY(kw IN $kwSubs WHERE ANY(nk IN node.keywords WHERE toLower(nk) = kw))
10582
+ ${propScopeClause}
10583
+ RETURN node, labels(node) AS nodeLabels, elementId(node) AS nodeId
9885
10584
  LIMIT $limit`,
9886
10585
  {
9887
- indexName,
9888
- embedding: queryEmbedding,
9889
- limit: int(limit),
9890
10586
  accountId,
9891
- ...scopeParams,
9892
- ...agentParams,
9893
- ...keywordParams
10587
+ kwSubs: keywordSubscriptions,
10588
+ limit: int(limit),
10589
+ ...allowedScopes ? { allowedScopes } : {}
9894
10590
  }
9895
10591
  );
9896
- for (const record of vectorResult.records) {
10592
+ for (const record of propResult.records) {
9897
10593
  const nodeId = record.get("nodeId");
9898
- const scoreRaw = record.get("score");
9899
- const score = typeof scoreRaw === "number" ? scoreRaw : Number(scoreRaw);
9900
10594
  const existing = scoreMap.get(nodeId);
9901
10595
  if (existing) {
9902
- existing.vectorScore = Math.max(existing.vectorScore, score);
10596
+ existing.bm25Score = Math.max(existing.bm25Score, 1);
10597
+ existing.bm25Hit = true;
9903
10598
  } else {
9904
10599
  const node = record.get("node");
9905
10600
  scoreMap.set(nodeId, {
9906
10601
  nodeId,
9907
10602
  labels: record.get("nodeLabels"),
9908
10603
  properties: plainProperties(node.properties),
9909
- vectorScore: score,
9910
- bm25Score: 0,
9911
- bm25Hit: false
10604
+ vectorScore: 0,
10605
+ bm25Score: 1,
10606
+ bm25Hit: true
9912
10607
  });
9913
10608
  }
9914
10609
  }
9915
10610
  }
9916
- const bm25Hits = await bm25Only(session, params);
9917
- if (bm25Hits.length > 0) {
9918
- const rawScores = bm25Hits.map((h) => h.score);
9919
- const normalised = normaliseBm25Scores(rawScores);
9920
- for (let i = 0; i < bm25Hits.length; i++) {
9921
- mergeBm25Hit(scoreMap, bm25Hits[i], normalised[i]);
9922
- }
9923
- }
9924
10611
  if (keywordSubscriptions && keywordSubscriptions.length > 0) {
9925
10612
  for (const kw of keywordSubscriptions) {
9926
10613
  const kwHits = await bm25Only(session, {
@@ -9940,7 +10627,7 @@ async function hybrid(session, embed2, params) {
9940
10627
  const propResult = await session.run(
9941
10628
  `MATCH (node)
9942
10629
  WHERE node.accountId = $accountId
9943
- AND ${(0, import_dist.notTrashed)("node")}
10630
+ AND ${(0, import_dist2.notTrashed)("node")}
9944
10631
  AND node.keywords IS NOT NULL
9945
10632
  AND ANY(kw IN $kwSubs WHERE ANY(nk IN node.keywords WHERE toLower(nk) = kw))
9946
10633
  ${propScopeClause}
@@ -9972,10 +10659,41 @@ async function hybrid(session, embed2, params) {
9972
10659
  }
9973
10660
  }
9974
10661
  }
9975
- const allMerged = [...scoreMap.values()].map((node) => ({
9976
- ...node,
9977
- combinedScore: VECTOR_WEIGHT * node.vectorScore + BM25_WEIGHT * node.bm25Score
9978
- }));
10662
+ let allMerged;
10663
+ if (useRrf) {
10664
+ const rankedLists = [];
10665
+ for (const pass of passes) {
10666
+ if (pass.vectorRanked.length > 0) rankedLists.push(pass.vectorRanked);
10667
+ if (pass.bm25Ranked.length > 0) rankedLists.push(pass.bm25Ranked);
10668
+ }
10669
+ const fused = fuseRrf(rankedLists, rrfK);
10670
+ allMerged = fused.map((f) => {
10671
+ const rep = scoreMap.get(f.nodeId) ?? f;
10672
+ return { ...rep, combinedScore: f.rrfScore };
10673
+ });
10674
+ } else {
10675
+ allMerged = [...scoreMap.values()].map((node) => ({
10676
+ ...node,
10677
+ combinedScore: plan.vectorWeight * node.vectorScore + plan.bm25Weight * node.bm25Score
10678
+ }));
10679
+ }
10680
+ const fusedCount = allMerged.length;
10681
+ if (useBoosts) {
10682
+ allMerged = applyCompiledTruthBoost(
10683
+ applyBacklinkBoost(allMerged.map((m) => ({
10684
+ ...m,
10685
+ score: m.combinedScore
10686
+ })))
10687
+ ).map((m) => ({ ...m, combinedScore: m.score }));
10688
+ }
10689
+ const boostedCount = allMerged.length;
10690
+ if (useDedup) {
10691
+ allMerged = dedup(
10692
+ allMerged.map((m) => ({ ...m, score: m.combinedScore })),
10693
+ dedupLayers
10694
+ ).map((m) => ({ ...m, combinedScore: m.score }));
10695
+ }
10696
+ const dedupedCount = allMerged.length;
9979
10697
  const rawMerged = allMerged.length;
9980
10698
  const threshold = params.vectorThreshold;
9981
10699
  let kept = allMerged;
@@ -10016,7 +10734,7 @@ async function hybrid(session, embed2, params) {
10016
10734
  `UNWIND $nodeIds AS nid
10017
10735
  MATCH (n)-[r]-(related)
10018
10736
  WHERE elementId(n) = nid
10019
- AND ${(0, import_dist.notTrashed)("related")}
10737
+ AND ${(0, import_dist2.notTrashed)("related")}
10020
10738
  ${expandScopeClause}
10021
10739
  ${expandAgentClause}
10022
10740
  WITH nid, n, r, related
@@ -10049,13 +10767,22 @@ async function hybrid(session, embed2, params) {
10049
10767
  }
10050
10768
  }
10051
10769
  return {
10052
- mode: "hybrid",
10770
+ mode: useRrf ? "rrf" : "hybrid",
10053
10771
  results,
10054
10772
  expandMs,
10055
10773
  rawMerged,
10056
10774
  suppressed,
10057
10775
  bm25Bypass,
10058
- threshold: threshold ?? null
10776
+ threshold: threshold ?? null,
10777
+ stageCounts: {
10778
+ expansions: expansions.length,
10779
+ vector: totalVector,
10780
+ bm25: totalBm25,
10781
+ fused: fusedCount,
10782
+ boosted: boostedCount,
10783
+ deduped: dedupedCount,
10784
+ final: results.length
10785
+ }
10059
10786
  };
10060
10787
  }
10061
10788
  function mergeBm25Hit(map, hit, normalisedScore) {
@@ -10231,8 +10958,8 @@ var graph_search_default = app19;
10231
10958
  import neo4j from "neo4j-driver";
10232
10959
 
10233
10960
  // app/lib/graph-labels.ts
10234
- var import_dist2 = __toESM(require_dist2(), 1);
10235
- var import_dist3 = __toESM(require_dist2(), 1);
10961
+ var import_dist3 = __toESM(require_dist3(), 1);
10962
+ var import_dist4 = __toESM(require_dist3(), 1);
10236
10963
  var HIDDEN_BY_DEFAULT_LABELS = Object.freeze(
10237
10964
  /* @__PURE__ */ new Set(["Chunk", "GraphPreference"])
10238
10965
  );
@@ -10288,7 +11015,7 @@ var EXCLUDED_EDGE_TYPES = Object.freeze(
10288
11015
  ])
10289
11016
  );
10290
11017
  function isKnownLabel(label) {
10291
- return Object.prototype.hasOwnProperty.call(import_dist3.GRAPH_LABEL_COLOURS, label);
11018
+ return Object.prototype.hasOwnProperty.call(import_dist4.GRAPH_LABEL_COLOURS, label);
10292
11019
  }
10293
11020
  function isHiddenByDefault(label) {
10294
11021
  return HIDDEN_BY_DEFAULT_LABELS.has(label);
@@ -13096,7 +13823,7 @@ function broadcastAdminShutdown(reason) {
13096
13823
  }
13097
13824
 
13098
13825
  // ../lib/entitlement/src/index.ts
13099
- import { createPublicKey, createHash as createHash3, verify as cryptoVerify } from "crypto";
13826
+ import { createPublicKey, createHash as createHash4, verify as cryptoVerify } from "crypto";
13100
13827
  import { existsSync as existsSync23, readFileSync as readFileSync20, statSync as statSync8 } from "fs";
13101
13828
  import { resolve as resolve20 } from "path";
13102
13829
 
@@ -13167,7 +13894,7 @@ function verifyAndResolve(brand, entitlementPath, account) {
13167
13894
  reason: "pubkey-missing"
13168
13895
  });
13169
13896
  }
13170
- const pubkeyHash = createHash3("sha256").update(pubkeyPem).digest("hex");
13897
+ const pubkeyHash = createHash4("sha256").update(pubkeyPem).digest("hex");
13171
13898
  if (pubkeyHash !== PUBKEY_SHA256) {
13172
13899
  return logResolved(anonymousFallback("pubkey-tamper"), {
13173
13900
  reason: "pubkey-tamper"