@theglitchking/semantic-memory 1.6.0 → 1.7.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.
Files changed (149) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +90 -0
  4. package/LICENSES/Apache-2.0.txt +202 -0
  5. package/README.md +49 -29
  6. package/THIRD_PARTY_NOTICES.md +17 -0
  7. package/commands/jev-mode.md +18 -0
  8. package/commands/skill-loop.md +23 -0
  9. package/dist/{chunk-7PNF3AP2.js → chunk-34VN47IW.js} +2 -2
  10. package/dist/chunk-3FPUBAEW.js +113 -0
  11. package/dist/chunk-3FPUBAEW.js.map +1 -0
  12. package/dist/{chunk-WZEKIUYG.js → chunk-6TIKWOY4.js} +10 -8
  13. package/dist/{chunk-WZEKIUYG.js.map → chunk-6TIKWOY4.js.map} +1 -1
  14. package/dist/{chunk-NBY4TRP7.js → chunk-7M34JDTH.js} +3 -3
  15. package/dist/{chunk-FTRXH2WC.js → chunk-7UAVDOI6.js} +15 -12
  16. package/dist/chunk-7UAVDOI6.js.map +1 -0
  17. package/dist/{chunk-BBP3DG2U.js → chunk-AIXQFTSB.js} +25 -26
  18. package/dist/{chunk-BBP3DG2U.js.map → chunk-AIXQFTSB.js.map} +1 -1
  19. package/dist/chunk-BEACL25M.js +224 -0
  20. package/dist/chunk-BEACL25M.js.map +1 -0
  21. package/dist/chunk-CATPGINC.js +186 -0
  22. package/dist/chunk-CATPGINC.js.map +1 -0
  23. package/dist/{chunk-IGHCVAAL.js → chunk-CJUY55GG.js} +6 -3
  24. package/dist/{chunk-IGHCVAAL.js.map → chunk-CJUY55GG.js.map} +1 -1
  25. package/dist/chunk-DBFD3MTT.js +130 -0
  26. package/dist/chunk-DBFD3MTT.js.map +1 -0
  27. package/dist/{chunk-XAXK7NM4.js → chunk-EXAUVIGW.js} +9 -7
  28. package/dist/{chunk-XAXK7NM4.js.map → chunk-EXAUVIGW.js.map} +1 -1
  29. package/dist/{chunk-LGQPP5FQ.js → chunk-EYVFC745.js} +2 -2
  30. package/dist/chunk-GYR7U6KA.js +80 -0
  31. package/dist/chunk-GYR7U6KA.js.map +1 -0
  32. package/dist/{chunk-OEF5U5GV.js → chunk-H2BES6FU.js} +10 -2
  33. package/dist/{chunk-OEF5U5GV.js.map → chunk-H2BES6FU.js.map} +1 -1
  34. package/dist/chunk-IEZVFKUT.js +157 -0
  35. package/dist/chunk-IEZVFKUT.js.map +1 -0
  36. package/dist/chunk-J7LYDRML.js +9 -0
  37. package/dist/chunk-J7LYDRML.js.map +1 -0
  38. package/dist/chunk-MQS7JEBD.js +137 -0
  39. package/dist/chunk-MQS7JEBD.js.map +1 -0
  40. package/dist/chunk-NCGM36NX.js +118 -0
  41. package/dist/chunk-NCGM36NX.js.map +1 -0
  42. package/dist/chunk-NIZLBNH4.js +165 -0
  43. package/dist/chunk-NIZLBNH4.js.map +1 -0
  44. package/dist/chunk-OT3MV7XF.js +123 -0
  45. package/dist/chunk-OT3MV7XF.js.map +1 -0
  46. package/dist/chunk-P3GLY42T.js +291 -0
  47. package/dist/chunk-P3GLY42T.js.map +1 -0
  48. package/dist/chunk-PJXYCCI2.js +177 -0
  49. package/dist/chunk-PJXYCCI2.js.map +1 -0
  50. package/dist/chunk-R6GNP7DS.js +87 -0
  51. package/dist/chunk-R6GNP7DS.js.map +1 -0
  52. package/dist/{chunk-44BQDRXD.js → chunk-SHSOCFJH.js} +4 -1
  53. package/dist/chunk-SHSOCFJH.js.map +1 -0
  54. package/dist/chunk-TKHWVISV.js +71 -0
  55. package/dist/chunk-TKHWVISV.js.map +1 -0
  56. package/dist/{chunk-CAX46XLY.js → chunk-VBLCUWT4.js} +23 -12
  57. package/dist/chunk-VBLCUWT4.js.map +1 -0
  58. package/dist/chunk-VQCNWRTL.js +121 -0
  59. package/dist/chunk-VQCNWRTL.js.map +1 -0
  60. package/dist/{chunk-SX427M2R.js → chunk-XKGK643R.js} +2 -2
  61. package/dist/{chunk-FURCIJBO.js → chunk-Y7H76Q7F.js} +6 -3
  62. package/dist/chunk-Y7H76Q7F.js.map +1 -0
  63. package/dist/chunk-ZSWI3Y4Y.js +232 -0
  64. package/dist/chunk-ZSWI3Y4Y.js.map +1 -0
  65. package/dist/cli/index.js +389 -60
  66. package/dist/cli/index.js.map +1 -1
  67. package/dist/core/features.d.ts +38 -0
  68. package/dist/core/features.js +28 -0
  69. package/dist/core/index.d.ts +285 -2
  70. package/dist/core/index.js +170 -20
  71. package/dist/core/index.js.map +1 -1
  72. package/dist/core/jev.d.ts +35 -0
  73. package/dist/core/jev.js +20 -0
  74. package/dist/core/redact.js +1 -1
  75. package/dist/core/skill-curator.d.ts +1 -0
  76. package/dist/core/skill-curator.js +41 -0
  77. package/dist/core/skill-evidence.d.ts +25 -0
  78. package/dist/core/skill-evidence.js +23 -0
  79. package/dist/core/skill-queue.d.ts +40 -0
  80. package/dist/core/skill-queue.js +38 -0
  81. package/dist/core/skill-review.d.ts +3 -0
  82. package/dist/core/skill-review.js +29 -0
  83. package/dist/core/skill-worker.d.ts +21 -0
  84. package/dist/core/skill-worker.js +70 -0
  85. package/dist/core/skill-worker.js.map +1 -0
  86. package/dist/{dossier-RWTIMGZN.js → dossier-RXBLQOXC.js} +5 -4
  87. package/dist/failure-eval-IUJO47UC.js +18 -0
  88. package/dist/healthcheck-S7D6YNTA.js +21 -0
  89. package/dist/indexer-RZPH2PVL.js +9 -0
  90. package/dist/{lexicon-IVNJTMWE.js → lexicon-SEJSLBHO.js} +6 -5
  91. package/dist/lexicon-SEJSLBHO.js.map +1 -0
  92. package/dist/lint-3JNLRM5U.js +18 -0
  93. package/dist/lint-3JNLRM5U.js.map +1 -0
  94. package/dist/log-RM2NDNPH.js +11 -0
  95. package/dist/log-RM2NDNPH.js.map +1 -0
  96. package/dist/matter-SNJ2WDL6.js +8 -0
  97. package/dist/matter-SNJ2WDL6.js.map +1 -0
  98. package/dist/mcp/server.js +43 -38
  99. package/dist/mcp/server.js.map +1 -1
  100. package/dist/{profile-UE4YVTDC.js → profile-SNULTJ27.js} +6 -5
  101. package/dist/profile-SNULTJ27.js.map +1 -0
  102. package/dist/{schema-LY6533NJ.js → schema-RDYUK6CC.js} +3 -2
  103. package/dist/schema-RDYUK6CC.js.map +1 -0
  104. package/dist/{search-MZC4DC7H.js → search-325ULY57.js} +4 -3
  105. package/dist/{search-MZC4DC7H.js.map → search-325ULY57.js.map} +1 -1
  106. package/dist/skill-curator-D7HHZaUI.d.ts +71 -0
  107. package/dist/skill-land-XU2APPBJ.js +31 -0
  108. package/dist/skill-land-XU2APPBJ.js.map +1 -0
  109. package/dist/skill-ledger-AYXVNFX7.js +18 -0
  110. package/dist/skill-ledger-AYXVNFX7.js.map +1 -0
  111. package/dist/skill-merge-GXBQLBZA.js +24 -0
  112. package/dist/skill-merge-GXBQLBZA.js.map +1 -0
  113. package/dist/skill-review-BaW5n0Zg.d.ts +179 -0
  114. package/dist/skill-schema-RVB3EHEF.js +30 -0
  115. package/dist/skill-schema-RVB3EHEF.js.map +1 -0
  116. package/dist/{telemetry-B4KP3UVE.js → telemetry-YAG3VA4Y.js} +4 -4
  117. package/dist/telemetry-YAG3VA4Y.js.map +1 -0
  118. package/docs/regression-snapshot.md +5 -5
  119. package/hooks/hooks.json +26 -1
  120. package/hooks/jev-bridge.py +109 -0
  121. package/hooks/session-start.js +5 -0
  122. package/hooks/skill-loop.js +65 -0
  123. package/hooks/vault-context.js +104 -6
  124. package/package.json +4 -2
  125. package/dist/chunk-44BQDRXD.js.map +0 -1
  126. package/dist/chunk-5F5ZIAW2.js +0 -79
  127. package/dist/chunk-5F5ZIAW2.js.map +0 -1
  128. package/dist/chunk-CAX46XLY.js.map +0 -1
  129. package/dist/chunk-FTRXH2WC.js.map +0 -1
  130. package/dist/chunk-FURCIJBO.js.map +0 -1
  131. package/dist/failure-eval-37JRNTIW.js +0 -10
  132. package/dist/healthcheck-3FSNL2U7.js +0 -20
  133. package/dist/indexer-RAKJ7JR3.js +0 -8
  134. package/dist/lint-36WTW762.js +0 -17
  135. package/dist/log-YIXF6CYM.js +0 -11
  136. /package/dist/{chunk-7PNF3AP2.js.map → chunk-34VN47IW.js.map} +0 -0
  137. /package/dist/{chunk-NBY4TRP7.js.map → chunk-7M34JDTH.js.map} +0 -0
  138. /package/dist/{chunk-LGQPP5FQ.js.map → chunk-EYVFC745.js.map} +0 -0
  139. /package/dist/{chunk-SX427M2R.js.map → chunk-XKGK643R.js.map} +0 -0
  140. /package/dist/{dossier-RWTIMGZN.js.map → core/features.js.map} +0 -0
  141. /package/dist/{failure-eval-37JRNTIW.js.map → core/jev.js.map} +0 -0
  142. /package/dist/{healthcheck-3FSNL2U7.js.map → core/skill-curator.js.map} +0 -0
  143. /package/dist/{indexer-RAKJ7JR3.js.map → core/skill-evidence.js.map} +0 -0
  144. /package/dist/{lexicon-IVNJTMWE.js.map → core/skill-queue.js.map} +0 -0
  145. /package/dist/{lint-36WTW762.js.map → core/skill-review.js.map} +0 -0
  146. /package/dist/{log-YIXF6CYM.js.map → dossier-RXBLQOXC.js.map} +0 -0
  147. /package/dist/{profile-UE4YVTDC.js.map → failure-eval-IUJO47UC.js.map} +0 -0
  148. /package/dist/{schema-LY6533NJ.js.map → healthcheck-S7D6YNTA.js.map} +0 -0
  149. /package/dist/{telemetry-B4KP3UVE.js.map → indexer-RZPH2PVL.js.map} +0 -0
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ """semantic-memory → Jev, through jev-loop's router (v1.7 jev mode). Stdlib only.
3
+
4
+ echo '{"cwd": "...", "caller": "semantic-memory-judge", "state": "...",
5
+ "questions": [{"key": "a", "instructions": "..."}]}' | python3 jev-bridge.py
6
+
7
+ Prints one JSON line: {"scores": {key: probability}, "tokens": n} or {"scores": {}, "error": "..."}.
8
+ Never raises, never exits non-zero: every failure is an empty answer with a reason.
9
+
10
+ The only way semantic-memory reaches TypeSafe is `router.ask`, which redacts the state and every
11
+ question (redact.py), caches answers, writes the usage ledger under the caller's name and enforces the
12
+ batch budget. This file never builds a request itself and never prints the key.
13
+
14
+ Callers: semantic-memory-hook (the failure hook) and semantic-memory-judge / semantic-memory-skill-loop.
15
+ jev-loop counts all three as batch (capped and cached) until it lists semantic-memory-hook as live. The
16
+ hook caller has its own daily ceiling here, SEMANTIC_MEMORY_JEV_DAILY_TOKENS (default 500000).
17
+ """
18
+ import json
19
+ import os
20
+ import sys
21
+ from datetime import datetime
22
+ from pathlib import Path
23
+
24
+ CALLERS = {"semantic-memory-hook", "semantic-memory-judge", "semantic-memory-skill-loop"}
25
+ LIVE_CALLER = "semantic-memory-hook"
26
+
27
+
28
+ def out(obj):
29
+ sys.stdout.write(json.dumps(obj) + "\n")
30
+ sys.stdout.flush()
31
+
32
+
33
+ def live_tokens_today(logs, caller):
34
+ """Tokens this caller spent today, from jev-loop's usage ledger (numbers only)."""
35
+ day, used = datetime.now().date().isoformat(), 0
36
+ for path in Path(logs).glob("jev-usage-*.jsonl"):
37
+ for line in path.read_text().splitlines():
38
+ try:
39
+ r = json.loads(line)
40
+ except ValueError:
41
+ continue
42
+ if r.get("caller") == caller and str(r.get("time", "")).startswith(day):
43
+ used += r.get("input_tokens", 0) + r.get("output_tokens", 0)
44
+ return used
45
+
46
+
47
+ def main():
48
+ try:
49
+ req = json.loads(sys.stdin.read() or "{}")
50
+ except ValueError as e:
51
+ return out({"scores": {}, "error": f"bad input: {e}"})
52
+ caller = req.get("caller")
53
+ if caller not in CALLERS:
54
+ return out({"scores": {}, "error": f"unknown caller {caller!r}"})
55
+ state, questions = req.get("state"), req.get("questions") or []
56
+ if not isinstance(state, str) or not state or not questions:
57
+ return out({"scores": {}, "error": "state and questions are required"})
58
+ if not all(isinstance(q, dict) and q.get("key") and isinstance(q.get("instructions"), str) for q in questions):
59
+ return out({"scores": {}, "error": "each question needs a key and instructions"})
60
+
61
+ jev_dir = Path(os.environ.get("JEV_LOOP_DIR") or Path.home() / ".claude" / "jev-loop")
62
+ if not (jev_dir / "router.py").exists():
63
+ return out({"scores": {}, "error": f"jev-loop not found at {jev_dir}"})
64
+ if not os.environ.get("TYPESAFE_API_KEY"):
65
+ # cron and plain shells have no Claude settings env: read the key the way jev-loop's daily.sh does
66
+ try:
67
+ settings = Path(os.environ.get("SEMANTIC_MEMORY_CLAUDE_SETTINGS") or Path.home() / ".claude" / "settings.json")
68
+ os.environ["TYPESAFE_API_KEY"] = json.loads(settings.read_text())["env"]["TYPESAFE_API_KEY"]
69
+ except Exception:
70
+ return out({"scores": {}, "error": "TYPESAFE_API_KEY is not set"})
71
+
72
+ # the router names its caller from argv[0] (older jev-loop) or JEV_CALLER: set both before importing it
73
+ os.environ["JEV_CALLER"] = caller
74
+ sys.argv = [caller]
75
+ sys.path.insert(0, str(jev_dir))
76
+ try:
77
+ import router # noqa: E402
78
+ except Exception as e:
79
+ return out({"scores": {}, "error": f"router import failed: {type(e).__name__}: {e}"})
80
+
81
+ cwd = req.get("cwd") or os.getcwd()
82
+ if router.repo_for(cwd, []) == "excluded":
83
+ return out({"scores": {}, "error": "excluded path: nothing sent"})
84
+
85
+ if caller == LIVE_CALLER:
86
+ cap = int(os.environ.get("SEMANTIC_MEMORY_JEV_DAILY_TOKENS") or 500_000)
87
+ used = live_tokens_today(router.LOGS, caller)
88
+ if used >= cap:
89
+ return out({"scores": {}, "error": f"daily ceiling reached: {used} of {cap} tokens (SEMANTIC_MEMORY_JEV_DAILY_TOKENS)", "ceiling": True})
90
+ timeout = req.get("timeout_seconds")
91
+ if isinstance(timeout, (int, float)) and timeout > 0:
92
+ router.R["timeout_seconds"] = timeout
93
+
94
+ state = state[: router.R["max_prompt_chars"]]
95
+ items = [(q["key"], q["instructions"]) for q in questions]
96
+ try:
97
+ scores, tokens = router.ask_all(state, items)
98
+ except Exception as e: # JevBudget, HTTP errors, timeouts: no answer, never a crash
99
+ router.flush_ledger()
100
+ return out({"scores": {}, "error": f"{type(e).__name__}: {e}"[:300]})
101
+ router.flush_ledger()
102
+ out({"scores": {k: v for k, v in scores.items() if isinstance(v, (int, float))}, "tokens": tokens})
103
+
104
+
105
+ if __name__ == "__main__":
106
+ try:
107
+ main()
108
+ except BaseException as e: # noqa: BLE001 — the bridge must never crash its caller
109
+ out({"scores": {}, "error": f"{type(e).__name__}: {e}"[:300]})
@@ -18,6 +18,11 @@ import { reconcile } from "./reconcile.js";
18
18
 
19
19
  async function main() {
20
20
  const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
21
+ // recursion guard: a skill-loop claude run (v1.7) must not reconcile or update anything
22
+ if (process.env.SEMANTIC_MEMORY_REVIEWER === "1") {
23
+ emit();
24
+ return;
25
+ }
21
26
 
22
27
  // 1. Reconcile — no-deps, always runs.
23
28
  try {
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ // skill-loop.js — the skill loop's hooks (v1.7). Off by default: with the skill loop off (or inside a
3
+ // skill-loop claude run) every event leaves at once, writes nothing and spawns nothing.
4
+ //
5
+ // SessionStart → once per session (startup / resume): "N skill drafts waiting"; starts the curator
6
+ // detached when it is due (every 7 days, repo quiet >= 2 h)
7
+ // PostToolUse (matcher Skill) → one usage line in .claude/.semantic-memory/skill-usage.jsonl
8
+ // SessionEnd → a review job when the session has signal (a failure, a correction or many
9
+ // tool calls), and the repo's worker started detached. Must finish well inside
10
+ // SessionEnd's 1.5 s: it only writes the job and spawns.
11
+ //
12
+ // Never blocks the session and never fails it: errors are swallowed (SIDEKICK_DEBUG=1 prints them).
13
+
14
+ import { readFileSync } from "node:fs";
15
+ import { resolve } from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+
18
+ const DEBUG = process.env.SIDEKICK_DEBUG === "1";
19
+ const debug = (msg) => DEBUG && process.stderr.write(`[skill-loop] ${msg}\n`);
20
+ const dist = (m) => new URL(`../dist/core/${m}.js`, import.meta.url).href;
21
+
22
+ function readStdinJson() {
23
+ try {
24
+ const raw = readFileSync(0, "utf8");
25
+ return raw.trim() ? JSON.parse(raw) : {};
26
+ } catch {
27
+ return {};
28
+ }
29
+ }
30
+
31
+ export async function main(input = readStdinJson(), env = process.env) {
32
+ if (env.SEMANTIC_MEMORY_REVIEWER === "1") return;
33
+ const cwd = input.cwd || env.CLAUDE_PROJECT_DIR || process.cwd();
34
+ const { featureOn } = await import(dist("features"));
35
+ if (!featureOn("skill_loop", cwd, env)) return;
36
+ const ev = await import(dist("skill-evidence"));
37
+ const root = ev.repoRoot(cwd);
38
+ const session_id = typeof input.session_id === "string" ? input.session_id : null;
39
+ if (input.hook_event_name === "SessionStart") {
40
+ if (input.source === "clear" || input.source === "compact") return;
41
+ const { listDrafts } = await import(dist("skill-review"));
42
+ const n = listDrafts(root).length;
43
+ const { curatorDue } = await import(dist("skill-curator"));
44
+ if (curatorDue(root)) {
45
+ const { spawn } = await import("node:child_process");
46
+ const cli = new URL("../dist/cli/index.js", import.meta.url).pathname;
47
+ spawn(process.execPath, [cli, "skill-loop", "curate", "--auto"], { cwd: root, env, detached: true, stdio: "ignore" }).unref();
48
+ }
49
+ if (n) process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: `[skill loop] ${n} skill draft${n > 1 ? "s" : ""} waiting for review (\`/skill-loop review\`).` } }) + "\n");
50
+ } else if (input.hook_event_name === "PostToolUse" && input.tool_name === "Skill") {
51
+ const skill = input.tool_input?.skill;
52
+ if (typeof skill === "string" && skill) ev.appendEvidence(root, "usage", { session_id, skill, via: "tool" }, env);
53
+ } else if (input.hook_event_name === "SessionEnd") {
54
+ const q = await import(dist("skill-queue"));
55
+ const job = q.sessionJob(root, input, env);
56
+ if (!job) return;
57
+ q.enqueue(job, env);
58
+ const { spawnWorker } = await import(dist("skill-worker"));
59
+ spawnWorker(root, env);
60
+ }
61
+ }
62
+
63
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
64
+ main().catch((e) => debug(e.message));
65
+ }
@@ -939,7 +939,32 @@ async function handleStop(projectRoot, sessionId) {
939
939
  // --- PostToolUseFailure: look up a knowledge-base fact for a failed tool call (v1.6, opt-in) ---
940
940
  // Measured on 553 real failures (scripts/eval-failures): facts-only at score >= 0.50, about 6 in 10
941
941
  // injections were on target, firing on ~4% of failures. Below the plan's 95% bar, hence off by default.
942
- const FAILURE_HOOK_ON = () => process.env.SEMANTIC_MEMORY_FAILURE_HOOK === "1";
942
+ // v1.7 jev mode: Jev can veto the candidate fact. Measured on 1,171 Claude-labelled failure/doc pairs, Jev's
943
+ // confident no's (<= 0.05) were 727 of 729 right, but it never said yes at >= 0.95 to a relevant pair (max 0.89):
944
+ // a "yes" gate would inject nothing. So a confident no suppresses, anything else keeps the plain hook's choice.
945
+ // Replayed, the veto removed only wrong facts (12 wrong, 0 right at score 0.30) but none at the shipped 0.50, and
946
+ // precision stays far from the 95% bar, so jev mode does not turn the hook on by itself.
947
+ const JEV_GATE = { adopted: false, budgetMs: Number(process.env.SEMANTIC_MEMORY_JEV_HOOK_BUDGET_MS) || 1500 };
948
+ const FAILURE_HOOK_ENV = () => process.env.SEMANTIC_MEMORY_FAILURE_HOOK;
949
+ const FAILURE_HOOK_ON = (projectRoot) =>
950
+ FAILURE_HOOK_ENV() === "1" || (FAILURE_HOOK_ENV() !== "0" && JEV_GATE.adopted && jevOn(projectRoot));
951
+
952
+ // cheap (env + a few stats); the hook-side copy of src/core/features.ts
953
+ let featuresMod;
954
+ async function loadFeatures() {
955
+ if (featuresMod === undefined) {
956
+ try {
957
+ featuresMod = await import(new URL("../dist/core/features.js", import.meta.url).href);
958
+ } catch (e) {
959
+ debug(`features unavailable: ${e.message}`);
960
+ featuresMod = null;
961
+ }
962
+ }
963
+ return featuresMod;
964
+ }
965
+ function jevOn(projectRoot) {
966
+ return Boolean(featuresMod && featuresMod.featureOn("jev", projectRoot));
967
+ }
943
968
  const FAILURE_MIN_SCORE = Number(process.env.SEMANTIC_MEMORY_FAILURE_MIN_SCORE) || 0.5;
944
969
  const USER_DENIED = /^(The user doesn't want to proceed|Permission to use .* has been denied)/;
945
970
  const isFactPath = (p) => /(^|\/)facts\/[^/]+\.md$/.test(String(p));
@@ -964,7 +989,65 @@ async function redactText(text) {
964
989
  }
965
990
  }
966
991
 
967
- async function handleFailure(projectRoot, sources, cliBin, input, sessionId) {
992
+ // One live Jev call for the candidate: a confident no (<= 0.05) vetoes it, a yes or an unsure answer lets it
993
+ // through. An error, a timeout or the daily ceiling injects nothing: in jev mode an unchecked fact is not shown.
994
+ async function jevAllows(projectRoot, command, error, fact, started) {
995
+ try {
996
+ const jev = await import(new URL("../dist/core/jev.js", import.meta.url).href);
997
+ const title = String(fact.title ?? fact.path);
998
+ const excerpt = await redactText(String(fact.snippet ?? ""));
999
+ const left = JEV_GATE.budgetMs - (Date.now() - started);
1000
+ if (left < 300) {
1001
+ debug(`jev gate: ${left} ms left of the hook budget — skipped`);
1002
+ return false;
1003
+ }
1004
+ const a = await jev.askJev(
1005
+ {
1006
+ cwd: projectRoot,
1007
+ caller: "semantic-memory-hook",
1008
+ state: jev.failureState(command, error),
1009
+ questions: [{ key: "fits", instructions: jev.relevanceQuestion(title, excerpt) }],
1010
+ },
1011
+ { timeoutMs: left - 50 },
1012
+ );
1013
+ if (a.ceiling) process.stderr.write(`[semantic-memory] jev mode: ${a.error}; the failure hook injects nothing until tomorrow\n`);
1014
+ else if (a.error) debug(`jev gate: ${a.error}`);
1015
+ if (a.error || typeof a.scores.fits !== "number") return false;
1016
+ return jev.jevVerdict(a.scores.fits) !== false;
1017
+ } catch (e) {
1018
+ debug(`jev gate failed: ${e.message}`);
1019
+ return false;
1020
+ }
1021
+ }
1022
+
1023
+ // v1.7 skill loop: failures, corrections and /skill uses become evidence for the between-session review.
1024
+ // Never blocks or changes this hook's output; any error is swallowed.
1025
+ async function recordSkillEvidence(eventName, projectRoot, input) {
1026
+ try {
1027
+ const ev = await import(new URL("../dist/core/skill-evidence.js", import.meta.url).href);
1028
+ const root = ev.repoRoot(projectRoot);
1029
+ const session_id = typeof input.session_id === "string" ? input.session_id : null;
1030
+ if (eventName === "PostToolUseFailure") {
1031
+ const name = String(input.tool_name || "");
1032
+ const error = String(input.error || "");
1033
+ if ((name === "Bash" || name.startsWith("mcp__")) && !input.is_interrupt && error && !USER_DENIED.test(error.trim())) {
1034
+ ev.appendEvidence(root, "failures", ev.failureRecord(input));
1035
+ }
1036
+ } else if (eventName === "UserPromptSubmit" && typeof input.prompt === "string") {
1037
+ const cue = ev.detectCorrection(input.prompt);
1038
+ if (cue) ev.appendEvidence(root, "corrections", { session_id, cue, excerpt: input.prompt.slice(0, 300) });
1039
+ const slash = ev.slashSkill(input.prompt);
1040
+ if (slash) {
1041
+ const file = join(root, ".claude", "skills", slash, "SKILL.md");
1042
+ if (existsSync(file) && /^skill_loop:/m.test(readFileSync(file, "utf8"))) ev.appendEvidence(root, "usage", { session_id, skill: slash, via: "slash" });
1043
+ }
1044
+ }
1045
+ } catch (e) {
1046
+ debug(`skill evidence failed: ${e.message}`);
1047
+ }
1048
+ }
1049
+
1050
+ async function handleFailure(projectRoot, sources, cliBin, input, sessionId, started = Date.now()) {
968
1051
  const name = String(input.tool_name || "");
969
1052
  const error = String(input.error || "");
970
1053
  if (!(name === "Bash" || name.startsWith("mcp__")) || input.is_interrupt || !error || USER_DENIED.test(error.trim())) {
@@ -979,11 +1062,16 @@ async function handleFailure(projectRoot, sources, cliBin, input, sessionId) {
979
1062
  const command = typeof args.command === "string" ? args.command : `${name} ${JSON.stringify(args)}`.slice(0, 300);
980
1063
  const query = await failureQueryFor(error, command);
981
1064
  const hits = await searchSources(cliBin, sources, 8, query);
1065
+ const gated = jevOn(projectRoot);
982
1066
  const fact = hits.find((h) => isFactPath(h.path) && typeof h.score === "number" && h.score >= FAILURE_MIN_SCORE);
983
1067
  if (!fact) {
984
1068
  emit("PostToolUseFailure", "");
985
1069
  return;
986
1070
  }
1071
+ if (gated && !(await jevAllows(projectRoot, command, error, fact, started))) {
1072
+ emit("PostToolUseFailure", "");
1073
+ return;
1074
+ }
987
1075
 
988
1076
  // the same fact at most once per session: a repeat failure the agent already saw the fact for adds only noise
989
1077
  const { resolvedRead, writePath } = statePath(projectRoot, "failure-shown");
@@ -1006,8 +1094,10 @@ async function handleFailure(projectRoot, sources, cliBin, input, sessionId) {
1006
1094
  }
1007
1095
 
1008
1096
  const lines = [
1009
- `<vault-fact source="tool-failure" score="${fact.score.toFixed(2)}">`,
1010
- `A knowledge-base fact may explain this failure. Check it applies before acting on it (measured: about 6 in 10 of these are on target).`,
1097
+ `<vault-fact source="tool-failure" score="${fact.score.toFixed(2)}"${gated ? ` checked="jev"` : ""}>`,
1098
+ gated
1099
+ ? `A knowledge-base fact may explain this failure (checked: not ruled out). Check it applies before acting on it.`
1100
+ : `A knowledge-base fact may explain this failure. Check it applies before acting on it (measured: about 6 in 10 of these are on target).`,
1011
1101
  `- \`${fact.path}\`${fact.source ? ` (${fact.source})` : ""}`,
1012
1102
  ];
1013
1103
  if (fact.snippet) lines.push(` > ${(await redactText(String(fact.snippet))).replace(/\s+/g, " ").slice(0, 240)}`);
@@ -1066,8 +1156,16 @@ async function main() {
1066
1156
  const projectRoot = input.cwd || process.env.CLAUDE_PROJECT_DIR || process.cwd();
1067
1157
  const sessionId = typeof input.session_id === "string" ? input.session_id : undefined;
1068
1158
  debug(`event=${eventName} projectRoot=${projectRoot}`);
1159
+ const started = Date.now();
1160
+ // recursion guard: inside a skill-loop claude run every semantic-memory hook leaves at once
1161
+ if (process.env.SEMANTIC_MEMORY_REVIEWER === "1") {
1162
+ emitNoop(eventName);
1163
+ return;
1164
+ }
1165
+ await loadFeatures();
1166
+ if (featuresMod?.featureOn("skill_loop", projectRoot)) await recordSkillEvidence(eventName, projectRoot, input);
1069
1167
  // opt-in, and registered for every failed tool call: leave before any lookup when off
1070
- if (eventName === "PostToolUseFailure" && !FAILURE_HOOK_ON()) {
1168
+ if (eventName === "PostToolUseFailure" && !FAILURE_HOOK_ON(projectRoot)) {
1071
1169
  emitNoop(eventName);
1072
1170
  return;
1073
1171
  }
@@ -1091,7 +1189,7 @@ async function main() {
1091
1189
  if (eventName === "UserPromptSubmit") {
1092
1190
  await handlePrompt(projectRoot, sources, cliBin, input);
1093
1191
  } else if (eventName === "PostToolUseFailure") {
1094
- await handleFailure(projectRoot, sources, cliBin, input, sessionId);
1192
+ await handleFailure(projectRoot, sources, cliBin, input, sessionId, started);
1095
1193
  } else if (eventName === "Stop") {
1096
1194
  await handleStop(projectRoot, sessionId);
1097
1195
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theglitchking/semantic-memory",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Unified memory-layer plugin for Claude Code. One MCP server, multiple typed corpora (vault, code, plans, docs, research, project-map), unified cross-corpus knowledge graph, conditional tool registration, in-process drift detection, and human↔code translation. Rebrand of @theglitchking/semantic-sidekick at v1.0 — see CHANGELOG for migration.",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",
@@ -29,7 +29,9 @@
29
29
  "README.md",
30
30
  "CHANGELOG.md",
31
31
  "ROADMAP.md",
32
- "LICENSE"
32
+ "LICENSE",
33
+ "THIRD_PARTY_NOTICES.md",
34
+ "LICENSES"
33
35
  ],
34
36
  "scripts": {
35
37
  "build": "tsup",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/embedder.ts","../src/core/graph.ts","../src/core/vector.ts","../src/core/search-text.ts"],"sourcesContent":["import { AutoTokenizer } from \"@huggingface/transformers\";\nimport { readFile, writeFile, mkdir, rename, unlink } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport { existsSync, createWriteStream } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { Readable } from \"node:stream\";\nimport { pipeline as streamPipeline } from \"node:stream/promises\";\nimport { Worker } from \"node:worker_threads\";\nimport { fileURLToPath } from \"node:url\";\n\n// MiniLM-L6-v2 is the default: ~3 min to index 2,853 chunks on CPU vs ~16 min for nomic.\n// nomic-embed-text-v1.5 gives higher quality embeddings but is much slower on CPU.\nconst DEFAULT_MODEL = \"sentence-transformers/all-MiniLM-L6-v2\";\nconst CACHE_DIR = join(homedir(), \".semantic-sidekick\", \"models\");\n// Default to 1 worker (serial). Worker threads only help on memory-rich machines\n// (each worker loads its own ONNX session). On typical dev machines\n// with <4 GB free RAM, parallel workers cause swap thrashing and are slower.\n// Enable with --workers N when you have sufficient RAM.\nconst DEFAULT_WORKERS = 1;\n// batch=16 is optimal for MiniLM on CPU (short sequences, low padding waste).\n// For larger models (nomic 768d), batch=1 is faster due to padding overhead.\nconst DEFAULT_BATCH_SIZE = 16;\n// Quantized ONNX: faster on CPU but not all models have a quantized variant.\n// MiniLM fp32 is already fast enough; nomic benefits from quantized.\n// Falls back to fp32 automatically if quantized file is not available.\nconst DEFAULT_QUANTIZED = false;\n\n// Full-precision ONNX model subpaths\nconst ONNX_MODEL_PATHS: Record<string, string> = {\n \"nomic-ai/nomic-embed-text-v1.5\": \"onnx/model.onnx\",\n \"sentence-transformers/all-MiniLM-L6-v2\": \"onnx/model.onnx\",\n};\n\n// Quantized (int8) ONNX model subpaths — faster on CPU, ~same quality\nconst ONNX_QUANTIZED_MODEL_PATHS: Record<string, string> = {\n \"nomic-ai/nomic-embed-text-v1.5\": \"onnx/model_quantized.onnx\",\n \"sentence-transformers/all-MiniLM-L6-v2\": \"onnx/model_quantized.onnx\",\n};\n\ninterface OrtSession {\n run(feeds: Record<string, unknown>): Promise<Record<string, { data: Float32Array; dims: number[] }>>;\n inputNames: string[];\n outputNames: string[];\n}\n\ninterface OrtModule {\n InferenceSession: {\n create(path: string, options?: Record<string, unknown>): Promise<OrtSession>;\n };\n Tensor: new (type: string, data: ArrayLike<number | bigint>, dims: number[]) => unknown;\n}\n\ntype RuntimeLabel = \"native\" | \"wasm\";\n\nasync function resolveOnnxRuntime(): Promise<{ ort: OrtModule; label: RuntimeLabel }> {\n try {\n const ort = await import(\"onnxruntime-node\");\n return { ort: ort as unknown as OrtModule, label: \"native\" };\n } catch {\n const ort = await import(\"onnxruntime-web\");\n return { ort: ort as unknown as OrtModule, label: \"wasm\" };\n }\n}\n\n// Download to a process-unique temp file, then atomically rename onto the\n// final path. Two concurrent downloaders won't corrupt each other's partial\n// writes — the worst case is one wasted download (rename is last-writer-wins\n// but each rename installs a complete, valid file). Without this, multiple\n// processes calling Embedder.init() against the same cache (e.g. parallel\n// vitest workers, or multiple semantic-sidekick servers on a fresh install) race\n// on a single shared writeStream and produce a corrupt ONNX file that fails\n// Protobuf parsing on load.\nasync function downloadFile(url: string, destPath: string): Promise<void> {\n const response = await fetch(url);\n if (!response.ok) throw new Error(`Download failed (${response.status}): ${url}`);\n if (!response.body) throw new Error(`No response body: ${url}`);\n const tempPath = `${destPath}.${process.pid}.${Date.now()}.tmp`;\n try {\n const fileStream = createWriteStream(tempPath);\n await streamPipeline(Readable.fromWeb(response.body as never), fileStream);\n await rename(tempPath, destPath);\n } catch (err) {\n try { await unlink(tempPath); } catch { /* temp may not exist */ }\n throw err;\n }\n}\n\nexport class Embedder {\n private model: string;\n private session: OrtSession | null = null;\n private tokenizer: Awaited<ReturnType<typeof AutoTokenizer.from_pretrained>> | null = null;\n private ort: OrtModule | null = null;\n private dimensions = 0;\n private runtimeLabel: RuntimeLabel = \"wasm\";\n private initialized = false;\n private numWorkers: number;\n private batchSize: number;\n private quantized: boolean;\n private modelPath = \"\";\n\n constructor(\n model: string = DEFAULT_MODEL,\n numWorkers: number = DEFAULT_WORKERS,\n batchSize: number = DEFAULT_BATCH_SIZE,\n quantized: boolean = DEFAULT_QUANTIZED\n ) {\n this.model = model;\n this.numWorkers = numWorkers;\n this.batchSize = batchSize;\n this.quantized = quantized;\n }\n\n async init(): Promise<void> {\n if (this.initialized) return;\n\n const modelDir = join(CACHE_DIR, this.model.replace(/\\//g, \"--\"));\n await mkdir(modelDir, { recursive: true });\n\n // Resolve ONNX runtime (native C++ or WASM fallback)\n const { ort, label } = await resolveOnnxRuntime();\n this.ort = ort;\n this.runtimeLabel = label;\n\n // Download ONNX model if not cached. Tries quantized first if requested,\n // falls back to fp32 if the quantized file is not available for this model.\n let useQuantized = this.quantized;\n const modelFileName = useQuantized ? \"model_quantized.onnx\" : \"model.onnx\";\n this.modelPath = join(modelDir, modelFileName);\n const modelPath = this.modelPath;\n if (!existsSync(modelPath)) {\n const pathMap = useQuantized ? ONNX_QUANTIZED_MODEL_PATHS : ONNX_MODEL_PATHS;\n const onnxSubpath = pathMap[this.model] ?? (useQuantized ? \"onnx/model_quantized.onnx\" : \"onnx/model.onnx\");\n const url = `https://huggingface.co/${this.model}/resolve/main/${onnxSubpath}`;\n process.stderr.write(`Downloading ONNX model: ${this.model} (${useQuantized ? \"quantized\" : \"fp32\"})...\\n`);\n try {\n await downloadFile(url, modelPath);\n } catch (err: any) {\n if (useQuantized && err?.message?.includes(\"404\")) {\n // Quantized not available for this model — fall back to fp32\n process.stderr.write(`Quantized model not available, falling back to fp32\\n`);\n useQuantized = false;\n this.modelPath = join(modelDir, \"model.onnx\");\n const fp32Subpath = ONNX_MODEL_PATHS[this.model] ?? \"onnx/model.onnx\";\n const fp32Url = `https://huggingface.co/${this.model}/resolve/main/${fp32Subpath}`;\n process.stderr.write(`Downloading ONNX model: ${this.model} (fp32)...\\n`);\n await downloadFile(fp32Url, this.modelPath);\n } else {\n throw err;\n }\n }\n process.stderr.write(`Model downloaded to ${modelDir}\\n`);\n }\n // Load tokenizer (uses HF transformers tokenizer infrastructure)\n this.tokenizer = await AutoTokenizer.from_pretrained(this.model, {\n cache_dir: CACHE_DIR,\n });\n\n // Create ONNX inference session (use this.modelPath — may have been updated by fallback)\n this.session = await ort.InferenceSession.create(this.modelPath, {\n executionProviders: [label === \"native\" ? \"cpu\" : \"wasm\"],\n });\n\n // Determine dimensions from a test embedding\n const test = await this.embed(\"test\");\n this.dimensions = test.length;\n this.initialized = true;\n\n const modelShort = this.model.split(\"/\").pop() ?? this.model;\n process.stderr.write(`Embedder ready (${modelShort}, ${label}, ${this.dimensions}d, batch=${this.batchSize})\\n`);\n }\n\n async embed(text: string): Promise<Float32Array> {\n if (!this.session || !this.tokenizer || !this.ort)\n throw new Error(\"Embedder not initialized. Call init() first.\");\n\n // Tokenize\n const encoded = await this.tokenizer(text, {\n padding: true,\n truncation: true,\n max_length: 512,\n return_tensor: false,\n });\n\n const inputIdsRaw: number[] = Array.from(encoded.input_ids);\n const attentionMaskRaw: number[] = Array.from(encoded.attention_mask);\n const seqLen = inputIdsRaw.length;\n\n // Build ONNX input tensors (most models expect int64)\n const inputIds = new this.ort.Tensor(\n \"int64\",\n BigInt64Array.from(inputIdsRaw.map(BigInt)),\n [1, seqLen]\n );\n const attentionMask = new this.ort.Tensor(\n \"int64\",\n BigInt64Array.from(attentionMaskRaw.map(BigInt)),\n [1, seqLen]\n );\n\n const feeds: Record<string, unknown> = { input_ids: inputIds, attention_mask: attentionMask };\n\n // Some models need token_type_ids\n if (this.session.inputNames.includes(\"token_type_ids\")) {\n feeds.token_type_ids = new this.ort.Tensor(\n \"int64\",\n new BigInt64Array(seqLen),\n [1, seqLen]\n );\n }\n\n // Run inference\n const output = await this.session.run(feeds);\n const outputTensor = output[this.session.outputNames[0]];\n const hiddenSize = outputTensor.dims[outputTensor.dims.length - 1];\n\n // Mean pooling with attention mask + L2 normalization\n return this.meanPoolAndNormalize(outputTensor.data, attentionMaskRaw, seqLen, hiddenSize);\n }\n\n private meanPoolAndNormalize(\n embeddings: Float32Array,\n attentionMask: number[],\n seqLen: number,\n hiddenSize: number\n ): Float32Array {\n const result = new Float32Array(hiddenSize);\n let maskSum = 0;\n\n for (let t = 0; t < seqLen; t++) {\n const mask = attentionMask[t];\n maskSum += mask;\n const offset = t * hiddenSize;\n for (let d = 0; d < hiddenSize; d++) {\n result[d] += embeddings[offset + d] * mask;\n }\n }\n\n if (maskSum > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= maskSum;\n }\n\n // L2 normalize\n let norm = 0;\n for (let d = 0; d < hiddenSize; d++) norm += result[d] * result[d];\n norm = Math.sqrt(norm);\n if (norm > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= norm;\n }\n\n return result;\n }\n\n // Mean pool + L2 normalize the output of a batched ONNX forward pass.\n // outputData: flat Float32Array of shape [batchSize, seqLen, hiddenSize]\n // maskData: flat number[] of shape [batchSize, seqLen]\n private meanPoolAndNormalizeMany(\n outputData: Float32Array,\n maskData: number[],\n batchSize: number,\n seqLen: number,\n hiddenSize: number\n ): Float32Array[] {\n const results: Float32Array[] = [];\n for (let b = 0; b < batchSize; b++) {\n const result = new Float32Array(hiddenSize);\n let maskSum = 0;\n for (let t = 0; t < seqLen; t++) {\n const mask = maskData[b * seqLen + t];\n maskSum += mask;\n const offset = (b * seqLen + t) * hiddenSize;\n for (let d = 0; d < hiddenSize; d++) {\n result[d] += outputData[offset + d] * mask;\n }\n }\n if (maskSum > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= maskSum;\n }\n // L2 normalize\n let norm = 0;\n for (let d = 0; d < hiddenSize; d++) norm += result[d] * result[d];\n norm = Math.sqrt(norm);\n if (norm > 0) {\n for (let d = 0; d < hiddenSize; d++) result[d] /= norm;\n }\n results.push(result);\n }\n return results;\n }\n\n // Embed a sub-batch of texts (length <= batchSize) in a single ONNX forward pass.\n //\n // Tokenizes each text individually (well-defined output format for all HF versions),\n // then manually pads to the longest sequence in the batch and builds a [n, seqLen]\n // tensor for one ONNX call. The speedup is from batching the ONNX inference —\n // individual tokenization is negligible (<1ms each).\n private async embedSubBatch(texts: string[]): Promise<Float32Array[]> {\n if (!this.session || !this.tokenizer || !this.ort)\n throw new Error(\"Embedder not initialized. Call init() first.\");\n\n const n = texts.length;\n\n // Tokenize each text individually — avoids ambiguous batch tokenizer output format\n const encodings = await Promise.all(\n texts.map((text) =>\n this.tokenizer!(text, {\n padding: false,\n truncation: true,\n max_length: 512,\n return_tensor: false,\n })\n )\n );\n\n // Extract flat token arrays and find max sequence length for this batch\n const tokenized = encodings.map((enc) => ({\n ids: Array.from(enc.input_ids) as number[],\n mask: Array.from(enc.attention_mask) as number[],\n }));\n const seqLen = Math.max(...tokenized.map((t) => t.ids.length));\n\n // Build flat padded tensors [n * seqLen] — pad with 0 (PAD token, zero attention)\n const flatIds = new BigInt64Array(n * seqLen);\n const flatMask = new BigInt64Array(n * seqLen);\n const flatMaskNums = new Array<number>(n * seqLen).fill(0);\n\n for (let i = 0; i < n; i++) {\n const { ids, mask } = tokenized[i];\n for (let j = 0; j < ids.length; j++) {\n flatIds[i * seqLen + j] = BigInt(ids[j]);\n flatMask[i * seqLen + j] = BigInt(mask[j]);\n flatMaskNums[i * seqLen + j] = mask[j];\n }\n // Positions beyond ids.length remain 0 (padding)\n }\n\n // Build batched ONNX tensors [n, seqLen]\n const inputIds = new this.ort.Tensor(\"int64\", flatIds, [n, seqLen]);\n const attentionMask = new this.ort.Tensor(\"int64\", flatMask, [n, seqLen]);\n\n const feeds: Record<string, unknown> = { input_ids: inputIds, attention_mask: attentionMask };\n\n if (this.session.inputNames.includes(\"token_type_ids\")) {\n feeds.token_type_ids = new this.ort.Tensor(\n \"int64\",\n new BigInt64Array(n * seqLen),\n [n, seqLen]\n );\n }\n\n // Single forward pass → output shape [n, seqLen, hiddenSize]\n const output = await this.session.run(feeds);\n const outputTensor = output[this.session.outputNames[0]];\n const hiddenSize = outputTensor.dims[outputTensor.dims.length - 1];\n\n return this.meanPoolAndNormalizeMany(\n outputTensor.data,\n flatMaskNums,\n n,\n seqLen,\n hiddenSize\n );\n }\n\n async embedBatch(\n texts: string[],\n onProgress?: (embedded: number, total: number, subBatch?: Float32Array[]) => Promise<void> | void\n ): Promise<Float32Array[]> {\n // Workers path (disabled by default; kept for --workers N users)\n if (this.numWorkers > 1 && texts.length >= this.numWorkers * 2) {\n return this.embedBatchParallel(texts, onProgress);\n }\n\n // True batched inference: slice into sub-batches and run one ONNX call each\n const results: Float32Array[] = [];\n for (let i = 0; i < texts.length; i += this.batchSize) {\n const subBatch = texts.slice(i, i + this.batchSize);\n const embeddings = await this.embedSubBatch(subBatch);\n results.push(...embeddings);\n await onProgress?.(Math.min(i + subBatch.length, texts.length), texts.length, embeddings);\n }\n return results;\n }\n\n private async embedBatchParallel(\n texts: string[],\n onProgress?: (embedded: number, total: number, subBatch?: Float32Array[]) => Promise<void> | void\n ): Promise<Float32Array[]> {\n // Resolve worker script path. With tsup splitting, embedder code may land\n // in a top-level chunk (dist/chunk-*.js) rather than dist/core/index.js,\n // so we try two locations: adjacent to this chunk, and dist/core/ relative\n // to the package root (two levels up from a dist/ chunk file).\n const thisDir = dirname(fileURLToPath(import.meta.url));\n let workerPath = join(thisDir, \"embed-worker.js\");\n if (!existsSync(workerPath)) {\n // Chunk file at dist/ level — worker is in dist/core/\n workerPath = join(thisDir, \"core\", \"embed-worker.js\");\n }\n\n if (!existsSync(workerPath)) {\n // Fallback to serial batched if worker script not found\n process.stderr.write(\"Worker script not found, falling back to batched embedding\\n\");\n return this.embedBatch(texts, onProgress);\n }\n\n // Split texts into chunks for each worker\n const chunkSize = Math.ceil(texts.length / this.numWorkers);\n const chunks: { texts: string[]; startIndex: number }[] = [];\n for (let i = 0; i < texts.length; i += chunkSize) {\n chunks.push({ texts: texts.slice(i, i + chunkSize), startIndex: i });\n }\n\n const allResults = new Array<Float32Array>(texts.length);\n let totalDone = 0;\n\n const workerPromises = chunks.map((chunk) => {\n return new Promise<void>((resolve, reject) => {\n const worker = new Worker(workerPath, {\n workerData: {\n modelPath: this.modelPath,\n modelName: this.model,\n cacheDir: CACHE_DIR,\n runtimeLabel: this.runtimeLabel,\n batchSize: this.batchSize,\n },\n });\n\n worker.on(\"message\", (msg: any) => {\n if (msg.type === \"ready\") {\n worker.postMessage({ type: \"embed\", texts: chunk.texts, startIndex: chunk.startIndex });\n } else if (msg.type === \"progress\") {\n totalDone++;\n onProgress?.(totalDone, texts.length);\n } else if (msg.type === \"result\") {\n for (let i = 0; i < msg.embeddings.length; i++) {\n allResults[chunk.startIndex + i] = new Float32Array(msg.embeddings[i]);\n }\n worker.terminate();\n resolve();\n } else if (msg.type === \"error\") {\n worker.terminate();\n reject(new Error(msg.error));\n }\n });\n\n worker.on(\"error\", (err) => {\n worker.terminate();\n reject(err);\n });\n });\n });\n\n await Promise.all(workerPromises);\n return allResults;\n }\n\n getDimensions(): number {\n return this.dimensions;\n }\n\n getModel(): string {\n return this.model;\n }\n\n getRuntime(): RuntimeLabel {\n return this.runtimeLabel;\n }\n\n async saveEmbeddings(\n embeddings: Map<string, Float32Array>,\n indexPath: string\n ): Promise<void> {\n const entries: Array<{ key: string; data: number[] }> = [];\n for (const [key, vec] of embeddings) {\n entries.push({ key, data: Array.from(vec) });\n }\n await writeFile(join(indexPath, \"embeddings.json\"), JSON.stringify(entries));\n }\n\n async loadEmbeddings(\n indexPath: string\n ): Promise<Map<string, Float32Array>> {\n const filePath = join(indexPath, \"embeddings.json\");\n if (!existsSync(filePath)) return new Map();\n\n const raw = await readFile(filePath, \"utf-8\");\n const entries: Array<{ key: string; data: number[] }> = JSON.parse(raw);\n const map = new Map<string, Float32Array>();\n for (const entry of entries) {\n map.set(entry.key, new Float32Array(entry.data));\n }\n return map;\n }\n}\n","import Graph from \"graphology\";\nimport { bfsFromNode } from \"graphology-traversal\";\nimport { bidirectional } from \"graphology-shortest-path\";\nimport type { IndexedDocument, GraphNode, GraphEdge, GraphStats } from \"./types.js\";\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\n\n/**\n * Tags shared by more than this many docs are too generic to be meaningful\n * graph edges (e.g. \"api\", \"testing\"). Only specific/rare tags create edges.\n * Docs without frontmatter are unaffected — their inline #tags still participate\n * but only if those tags are specific enough to pass this threshold.\n */\nconst TAG_EDGE_MAX_DOCS = 15;\n\nexport class GraphBuilder {\n private graph: Graph;\n\n constructor() {\n this.graph = new Graph({ type: \"directed\", multi: false });\n }\n\n buildFromDocuments(documents: IndexedDocument[]): void {\n this.graph.clear();\n\n // Add nodes — store optional metadata fields when present\n for (const doc of documents) {\n this.graph.addNode(doc.path, {\n title: doc.title,\n tags: doc.tags,\n ...(doc.status !== undefined && { status: doc.status }),\n ...(doc.loadPriority !== undefined && { loadPriority: doc.loadPriority }),\n });\n }\n\n const pathLookup = new Map<string, string>();\n for (const doc of documents) {\n const nameNoExt = doc.path.replace(/\\.md$/, \"\");\n const basename = nameNoExt.split(\"/\").pop()!;\n pathLookup.set(basename.toLowerCase(), doc.path);\n pathLookup.set(nameNoExt.toLowerCase(), doc.path);\n }\n\n // Add wikilink edges (weight 1.0)\n for (const doc of documents) {\n for (const link of doc.wikilinks) {\n const target = pathLookup.get(link.toLowerCase());\n if (target && target !== doc.path && !this.graph.hasEdge(doc.path, target)) {\n this.graph.addEdge(doc.path, target, { type: \"wikilink\", weight: 1.0 });\n }\n }\n }\n\n // Add related_docs edges (weight 1.0) — only present when frontmatter has related_docs\n // Falls back gracefully: docs without this field are simply skipped\n for (const doc of documents) {\n if (!doc.relatedDocs?.length) continue;\n for (const ref of doc.relatedDocs) {\n // Try full normalized path first, then basename — same strategy as wikilinks\n const normalized = ref.replace(/\\.md$/, \"\").toLowerCase();\n const base = normalized.split(\"/\").pop()!;\n const target = pathLookup.get(normalized) ?? pathLookup.get(base);\n if (target && target !== doc.path && !this.graph.hasEdge(doc.path, target)) {\n this.graph.addEdge(doc.path, target, { type: \"related\", weight: 1.0 });\n }\n }\n }\n\n // Add tag-based edges (weight 0.5) — capped at TAG_EDGE_MAX_DOCS per tag.\n // Generic tags shared by many docs (e.g. \"api\", \"testing\") create noise;\n // only specific tags (e.g. \"qdrant\", \"celery\") create meaningful connections.\n const tagToNotes = new Map<string, string[]>();\n for (const doc of documents) {\n for (const tag of doc.tags) {\n const notes = tagToNotes.get(tag) ?? [];\n notes.push(doc.path);\n tagToNotes.set(tag, notes);\n }\n }\n\n for (const [, notes] of tagToNotes) {\n if (notes.length >= TAG_EDGE_MAX_DOCS) continue; // too generic — skip\n for (let i = 0; i < notes.length; i++) {\n for (let j = i + 1; j < notes.length; j++) {\n if (!this.graph.hasEdge(notes[i], notes[j])) {\n this.graph.addEdge(notes[i], notes[j], { type: \"tag\", weight: 0.5 });\n }\n if (!this.graph.hasEdge(notes[j], notes[i])) {\n this.graph.addEdge(notes[j], notes[i], { type: \"tag\", weight: 0.5 });\n }\n }\n }\n }\n }\n\n backlinks(notePath: string): GraphNode[] {\n if (!this.graph.hasNode(notePath)) return [];\n return this.graph.inNeighbors(notePath).map((n) => this.nodeToGraphNode(n));\n }\n\n forwardlinks(notePath: string): GraphNode[] {\n if (!this.graph.hasNode(notePath)) return [];\n return this.graph.outNeighbors(notePath).map((n) => this.nodeToGraphNode(n));\n }\n\n findPath(from: string, to: string): string[] | null {\n if (!this.graph.hasNode(from) || !this.graph.hasNode(to)) return null;\n const path = bidirectional(this.graph, from, to);\n return path;\n }\n\n searchGraph(concept: string, maxDepth = 2, limit = 20): GraphNode[] {\n const lc = concept.toLowerCase();\n\n const directMatches = new Set<string>(\n this.graph.nodes().filter((n) => {\n const attrs = this.graph.getNodeAttributes(n);\n const title = typeof attrs.title === \"string\" ? attrs.title : String(attrs.title ?? \"\");\n return (\n n.toLowerCase().includes(lc) ||\n title.toLowerCase().includes(lc) ||\n (attrs.tags as string[] | undefined)?.some((t) => t.toLowerCase().includes(lc))\n );\n })\n );\n\n const visited = new Set<string>();\n for (const start of directMatches) {\n bfsFromNode(this.graph, start, (_node, _attr, depth) => {\n if (visited.size >= limit * 3) return true; // gather 3x limit before ranking\n visited.add(_node);\n return depth >= maxDepth;\n });\n }\n\n // Sort: direct matches first, then by loadPriority desc, then by degree desc\n // deprecated/archived nodes sort to the end\n const ranked = [...visited].sort((a, b) => {\n const aAttrs = this.graph.getNodeAttributes(a);\n const bAttrs = this.graph.getNodeAttributes(b);\n const aDeprecated = aAttrs.status === \"deprecated\" || aAttrs.status === \"archived\" ? 1 : 0;\n const bDeprecated = bAttrs.status === \"deprecated\" || bAttrs.status === \"archived\" ? 1 : 0;\n if (aDeprecated !== bDeprecated) return aDeprecated - bDeprecated;\n const aDirect = directMatches.has(a) ? 0 : 1;\n const bDirect = directMatches.has(b) ? 0 : 1;\n if (aDirect !== bDirect) return aDirect - bDirect;\n const aPriority = (aAttrs.loadPriority as number | undefined) ?? 5;\n const bPriority = (bAttrs.loadPriority as number | undefined) ?? 5;\n if (aPriority !== bPriority) return bPriority - aPriority;\n return this.graph.degree(b) - this.graph.degree(a);\n });\n\n return ranked.slice(0, limit).map((n) => this.nodeToGraphNode(n));\n }\n\n statistics(): GraphStats {\n const nodes = this.graph.order;\n const edges = this.graph.size;\n const orphans = this.graph.nodes().filter(\n (n) => this.graph.degree(n) === 0\n );\n\n const connections = this.graph.nodes().map((n) => ({\n path: n,\n connections: this.graph.degree(n),\n }));\n connections.sort((a, b) => b.connections - a.connections);\n\n const maxPossibleEdges = nodes * (nodes - 1);\n const density = maxPossibleEdges > 0 ? edges / maxPossibleEdges : 0;\n\n return {\n totalNodes: nodes,\n totalEdges: edges,\n orphanCount: orphans.length,\n mostConnected: connections.slice(0, 10),\n density,\n };\n }\n\n private nodeToGraphNode(nodePath: string): GraphNode {\n const attrs = this.graph.getNodeAttributes(nodePath);\n return {\n path: nodePath,\n title: typeof attrs.title === \"string\" ? attrs.title : nodePath,\n tags: (attrs.tags as string[]) || [],\n linkCount: this.graph.outDegree(nodePath),\n backlinkCount: this.graph.inDegree(nodePath),\n ...(attrs.loadPriority !== undefined && { loadPriority: attrs.loadPriority as number }),\n ...(attrs.status !== undefined && { status: attrs.status as string }),\n };\n }\n\n async save(indexPath: string): Promise<void> {\n const data = this.graph.export();\n await writeFile(join(indexPath, \"graph.json\"), JSON.stringify(data));\n }\n\n async load(indexPath: string): Promise<boolean> {\n const filePath = join(indexPath, \"graph.json\");\n if (!existsSync(filePath)) return false;\n\n const raw = await readFile(filePath, \"utf-8\");\n const data = JSON.parse(raw);\n this.graph.import(data);\n return true;\n }\n\n getGraph(): Graph {\n return this.graph;\n }\n}\n","import hnswlib from \"hnswlib-node\";\nconst { HierarchicalNSW } = hnswlib;\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport type { SearchResult } from \"./types.js\";\n\ninterface ChunkMeta {\n docPath: string;\n chunkIndex: number;\n text: string;\n}\n\nexport class VectorIndex {\n private index: InstanceType<typeof HierarchicalNSW> | null = null;\n private dimensions: number;\n private chunkMeta: ChunkMeta[] = [];\n\n constructor(dimensions: number) {\n this.dimensions = dimensions;\n }\n\n build(\n embeddings: Float32Array[],\n meta: ChunkMeta[]\n ): void {\n if (embeddings.length === 0) {\n this.index = null;\n this.chunkMeta = [];\n return;\n }\n\n this.index = new HierarchicalNSW(\"cosine\", this.dimensions);\n this.index.initIndex(embeddings.length);\n\n for (let i = 0; i < embeddings.length; i++) {\n this.index.addPoint(Array.from(embeddings[i]), i);\n }\n\n this.chunkMeta = meta;\n }\n\n search(queryEmbedding: Float32Array, k: number = 10): SearchResult[] {\n if (!this.index || this.chunkMeta.length === 0) return [];\n\n const numResults = Math.min(k, this.chunkMeta.length);\n const result = this.index.searchKnn(Array.from(queryEmbedding), numResults);\n\n const seen = new Set<string>();\n const results: SearchResult[] = [];\n\n for (let i = 0; i < result.neighbors.length; i++) {\n const idx = result.neighbors[i];\n const meta = this.chunkMeta[idx];\n if (!meta || seen.has(meta.docPath)) continue;\n seen.add(meta.docPath);\n\n results.push({\n path: meta.docPath,\n title: meta.docPath,\n score: 1 - result.distances[i],\n snippet: meta.text.slice(0, 200),\n matchedChunk: meta.text,\n });\n }\n\n return results;\n }\n\n async save(indexPath: string): Promise<void> {\n if (!this.index) return;\n\n this.index.writeIndexSync(join(indexPath, \"hnsw.bin\"));\n await writeFile(\n join(indexPath, \"hnsw-meta.json\"),\n JSON.stringify(this.chunkMeta)\n );\n }\n\n async load(indexPath: string): Promise<boolean> {\n const hnswPath = join(indexPath, \"hnsw.bin\");\n const metaPath = join(indexPath, \"hnsw-meta.json\");\n\n if (!existsSync(hnswPath) || !existsSync(metaPath)) return false;\n\n const raw = await readFile(metaPath, \"utf-8\");\n this.chunkMeta = JSON.parse(raw);\n\n this.index = new HierarchicalNSW(\"cosine\", this.dimensions);\n this.index.initIndex(this.chunkMeta.length);\n this.index.readIndexSync(hnswPath);\n\n return true;\n }\n\n getChunkMeta(): ChunkMeta[] {\n return this.chunkMeta;\n }\n}\n","import type { IndexedDocument, SearchResult, SearchTextOptions } from \"./types.js\";\nimport { minimatch } from \"minimatch\";\n\nexport class TextSearch {\n private documents: IndexedDocument[] = [];\n\n setDocuments(documents: IndexedDocument[]): void {\n this.documents = documents;\n }\n\n search(options: SearchTextOptions): SearchResult[] {\n const { pattern, regex, caseSensitive, pathGlob, tagFilter, limit = 20 } = options;\n\n let matcher: (text: string) => { matched: boolean; index: number };\n\n if (regex) {\n const flags = caseSensitive ? \"g\" : \"gi\";\n const re = new RegExp(pattern, flags);\n matcher = (text) => {\n re.lastIndex = 0;\n const m = re.exec(text);\n return { matched: !!m, index: m?.index ?? -1 };\n };\n } else {\n const needle = caseSensitive ? pattern : pattern.toLowerCase();\n matcher = (text) => {\n const haystack = caseSensitive ? text : text.toLowerCase();\n const idx = haystack.indexOf(needle);\n return { matched: idx >= 0, index: idx };\n };\n }\n\n const results: SearchResult[] = [];\n\n for (const doc of this.documents) {\n // Path filter\n if (pathGlob && !minimatch(doc.path, pathGlob)) continue;\n\n // Tag filter\n if (tagFilter?.length) {\n const hasTag = tagFilter.some((t) => doc.tags.includes(t));\n if (!hasTag) continue;\n }\n\n const { matched, index } = matcher(doc.content);\n if (!matched) continue;\n\n const snippetStart = Math.max(0, index - 80);\n const snippetEnd = Math.min(doc.content.length, index + 120);\n const snippet = doc.content.slice(snippetStart, snippetEnd).trim();\n\n results.push({\n path: doc.path,\n title: doc.title,\n score: 1.0,\n snippet: (snippetStart > 0 ? \"...\" : \"\") + snippet + (snippetEnd < doc.content.length ? \"...\" : \"\"),\n });\n\n if (results.length >= limit) break;\n }\n\n return results;\n }\n}\n"],"mappings":"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","names":["readFile","writeFile","join","existsSync","readFile","writeFile","join","existsSync"]}
@@ -1,79 +0,0 @@
1
- // src/core/failure-eval.ts
2
- var key = (c, p) => `${c}\0${p}`;
3
- function evaluateFailures(results, judgments, opts = {}) {
4
- const target = opts.targetPrecision ?? 0.95;
5
- const minJudged = opts.minJudged ?? 20;
6
- const thresholds = opts.thresholds ?? Array.from({ length: 17 }, (_, i) => Math.round((0.2 + i * 0.05) * 100) / 100);
7
- const label = /* @__PURE__ */ new Map();
8
- const relevantFor = /* @__PURE__ */ new Map();
9
- for (const j of judgments) {
10
- label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);
11
- if (j.relevant) {
12
- if (!relevantFor.has(j.case)) relevantFor.set(j.case, /* @__PURE__ */ new Set());
13
- relevantFor.get(j.case).add(j.path);
14
- }
15
- }
16
- let answerable = 0, rank1 = 0, recallAtK = 0;
17
- const unjudged = [];
18
- for (const r of results) {
19
- const relevant = relevantFor.get(r.case);
20
- if (relevant && relevant.size > 0) {
21
- answerable++;
22
- if (r.hits[0] && relevant.has(r.hits[0].path)) rank1++;
23
- if (r.hits.some((h) => relevant.has(h.path))) recallAtK++;
24
- }
25
- const top = r.hits[0];
26
- if (top && !label.has(key(r.case, top.path))) unjudged.push({ case: r.case, path: top.path, score: top.score });
27
- }
28
- const rows = thresholds.map((threshold) => {
29
- let injected = 0, judged = 0, correct = 0;
30
- for (const r of results) {
31
- const top = r.hits[0];
32
- if (!top || top.score < threshold) continue;
33
- injected++;
34
- const l = label.get(key(r.case, top.path));
35
- if (l === void 0) continue;
36
- judged++;
37
- if (l) correct++;
38
- }
39
- return {
40
- threshold,
41
- injected,
42
- judged,
43
- correct,
44
- precision: judged > 0 ? correct / judged : null,
45
- coverage: results.length > 0 ? injected / results.length : 0
46
- };
47
- });
48
- const meets = (row) => row.precision !== null && row.precision >= target;
49
- const recommended = rows.find(
50
- (row, i) => row.judged >= minJudged && meets(row) && rows.slice(i + 1).every((r) => r.judged < minJudged || meets(r))
51
- )?.threshold ?? null;
52
- return { cases: results.length, answerable, rank1, recallAtK, rows, recommended, unjudged };
53
- }
54
- function formatFailureEval(report, label = "") {
55
- const pct = (n) => n === null ? " - " : `${(n * 100).toFixed(1)}%`.padStart(6);
56
- const lines = [
57
- `Failure-lookup eval${label ? ` (${label})` : ""} \u2014 ${report.cases} cases, ${report.answerable} with a known answer`,
58
- ` answer ranked first: ${report.rank1}/${report.answerable} answer anywhere in hits: ${report.recallAtK}/${report.answerable}`,
59
- "",
60
- " threshold injected judged correct precision coverage"
61
- ];
62
- for (const r of report.rows) {
63
- lines.push(
64
- ` ${r.threshold.toFixed(2).padStart(9)} ${String(r.injected).padStart(8)} ${String(r.judged).padStart(6)} ${String(r.correct).padStart(7)} ${pct(r.precision).padStart(9)} ${pct(r.coverage).padStart(8)}`
65
- );
66
- }
67
- lines.push("");
68
- lines.push(
69
- report.recommended === null ? " recommended threshold: none reaches the target precision with enough judged injections" : ` recommended threshold: ${report.recommended.toFixed(2)}`
70
- );
71
- lines.push(` unjudged top hits: ${report.unjudged.length}`);
72
- return lines.join("\n");
73
- }
74
-
75
- export {
76
- evaluateFailures,
77
- formatFailureEval
78
- };
79
- //# sourceMappingURL=chunk-5F5ZIAW2.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/core/failure-eval.ts"],"sourcesContent":["/**\n * Failure-lookup eval (v1.6): given failed tool calls, the hits a search returned for each, and human\n * (or judge) relevance labels, measure how well the top hit answers the failure and pick the score\n * threshold above which injecting it is right often enough. Pure — no I/O, no search.\n */\n\nexport interface FailureCase {\n id: string;\n /** notes folder this failure's repo searches (its docs) */\n notes: string;\n error: string;\n command: string;\n}\n\nexport interface Judgment {\n case: string;\n path: string;\n relevant: boolean;\n}\n\nexport interface CaseResult {\n case: string;\n hits: { path: string; score: number }[];\n}\n\nexport interface ThresholdRow {\n threshold: number;\n /** cases whose top hit scored >= threshold (would be injected) */\n injected: number;\n /** injected cases whose top hit has a label */\n judged: number;\n correct: number;\n /** correct / judged; null when nothing judged */\n precision: number | null;\n /** injected / all cases */\n coverage: number;\n}\n\nexport interface FailureEvalReport {\n cases: number;\n /** cases with at least one relevant path among their labels */\n answerable: number;\n /** answerable cases whose top hit is relevant */\n rank1: number;\n /** answerable cases with a relevant path anywhere in the hits */\n recallAtK: number;\n rows: ThresholdRow[];\n /** lowest threshold reaching the target precision on >= minJudged judged injections; null if none */\n recommended: number | null;\n /** top hits nobody has labelled yet: label these, then re-run */\n unjudged: { case: string; path: string; score: number }[];\n}\n\nconst key = (c: string, p: string) => `${c}\\u0000${p}`;\n\nexport function evaluateFailures(\n results: CaseResult[],\n judgments: Judgment[],\n opts: { targetPrecision?: number; minJudged?: number; thresholds?: number[] } = {}\n): FailureEvalReport {\n const target = opts.targetPrecision ?? 0.95;\n const minJudged = opts.minJudged ?? 20;\n const thresholds = opts.thresholds ?? Array.from({ length: 17 }, (_, i) => Math.round((0.2 + i * 0.05) * 100) / 100);\n\n const label = new Map<string, boolean>();\n const relevantFor = new Map<string, Set<string>>();\n for (const j of judgments) {\n // a positive label wins over a negative one for the same pair\n label.set(key(j.case, j.path), (label.get(key(j.case, j.path)) ?? false) || j.relevant);\n if (j.relevant) {\n if (!relevantFor.has(j.case)) relevantFor.set(j.case, new Set());\n relevantFor.get(j.case)!.add(j.path);\n }\n }\n\n let answerable = 0, rank1 = 0, recallAtK = 0;\n const unjudged: FailureEvalReport[\"unjudged\"] = [];\n for (const r of results) {\n const relevant = relevantFor.get(r.case);\n if (relevant && relevant.size > 0) {\n answerable++;\n if (r.hits[0] && relevant.has(r.hits[0].path)) rank1++;\n if (r.hits.some((h) => relevant.has(h.path))) recallAtK++;\n }\n const top = r.hits[0];\n if (top && !label.has(key(r.case, top.path))) unjudged.push({ case: r.case, path: top.path, score: top.score });\n }\n\n const rows: ThresholdRow[] = thresholds.map((threshold) => {\n let injected = 0, judged = 0, correct = 0;\n for (const r of results) {\n const top = r.hits[0];\n if (!top || top.score < threshold) continue;\n injected++;\n const l = label.get(key(r.case, top.path));\n if (l === undefined) continue;\n judged++;\n if (l) correct++;\n }\n return {\n threshold,\n injected,\n judged,\n correct,\n precision: judged > 0 ? correct / judged : null,\n coverage: results.length > 0 ? injected / results.length : 0,\n };\n });\n\n // lowest threshold where this row and every higher row with enough judged injections reach the target\n const meets = (row: ThresholdRow) => row.precision !== null && row.precision >= target;\n const recommended =\n rows.find(\n (row, i) => row.judged >= minJudged && meets(row) && rows.slice(i + 1).every((r) => r.judged < minJudged || meets(r))\n )?.threshold ?? null;\n\n return { cases: results.length, answerable, rank1, recallAtK, rows, recommended, unjudged };\n}\n\nexport function formatFailureEval(report: FailureEvalReport, label = \"\"): string {\n const pct = (n: number | null) => (n === null ? \" - \" : `${(n * 100).toFixed(1)}%`.padStart(6));\n const lines = [\n `Failure-lookup eval${label ? ` (${label})` : \"\"} — ${report.cases} cases, ${report.answerable} with a known answer`,\n ` answer ranked first: ${report.rank1}/${report.answerable} answer anywhere in hits: ${report.recallAtK}/${report.answerable}`,\n \"\",\n \" threshold injected judged correct precision coverage\",\n ];\n for (const r of report.rows) {\n lines.push(\n ` ${r.threshold.toFixed(2).padStart(9)} ${String(r.injected).padStart(8)} ${String(r.judged).padStart(6)} ${String(r.correct).padStart(7)} ${pct(r.precision).padStart(9)} ${pct(r.coverage).padStart(8)}`\n );\n }\n lines.push(\"\");\n lines.push(\n report.recommended === null\n ? \" recommended threshold: none reaches the target precision with enough judged injections\"\n : ` recommended threshold: ${report.recommended.toFixed(2)}`\n );\n lines.push(` unjudged top hits: ${report.unjudged.length}`);\n return lines.join(\"\\n\");\n}\n"],"mappings":";AAqDA,IAAM,MAAM,CAAC,GAAW,MAAc,GAAG,CAAC,KAAS,CAAC;AAE7C,SAAS,iBACd,SACA,WACA,OAAgF,CAAC,GAC9D;AACnB,QAAM,SAAS,KAAK,mBAAmB;AACvC,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,aAAa,KAAK,cAAc,MAAM,KAAK,EAAE,QAAQ,GAAG,GAAG,CAAC,GAAG,MAAM,KAAK,OAAO,MAAM,IAAI,QAAQ,GAAG,IAAI,GAAG;AAEnH,QAAM,QAAQ,oBAAI,IAAqB;AACvC,QAAM,cAAc,oBAAI,IAAyB;AACjD,aAAW,KAAK,WAAW;AAEzB,UAAM,IAAI,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,MAAM,IAAI,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,UAAU,EAAE,QAAQ;AACtF,QAAI,EAAE,UAAU;AACd,UAAI,CAAC,YAAY,IAAI,EAAE,IAAI,EAAG,aAAY,IAAI,EAAE,MAAM,oBAAI,IAAI,CAAC;AAC/D,kBAAY,IAAI,EAAE,IAAI,EAAG,IAAI,EAAE,IAAI;AAAA,IACrC;AAAA,EACF;AAEA,MAAI,aAAa,GAAG,QAAQ,GAAG,YAAY;AAC3C,QAAM,WAA0C,CAAC;AACjD,aAAW,KAAK,SAAS;AACvB,UAAM,WAAW,YAAY,IAAI,EAAE,IAAI;AACvC,QAAI,YAAY,SAAS,OAAO,GAAG;AACjC;AACA,UAAI,EAAE,KAAK,CAAC,KAAK,SAAS,IAAI,EAAE,KAAK,CAAC,EAAE,IAAI,EAAG;AAC/C,UAAI,EAAE,KAAK,KAAK,CAAC,MAAM,SAAS,IAAI,EAAE,IAAI,CAAC,EAAG;AAAA,IAChD;AACA,UAAM,MAAM,EAAE,KAAK,CAAC;AACpB,QAAI,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC,EAAG,UAAS,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,IAAI,MAAM,CAAC;AAAA,EAChH;AAEA,QAAM,OAAuB,WAAW,IAAI,CAAC,cAAc;AACzD,QAAI,WAAW,GAAG,SAAS,GAAG,UAAU;AACxC,eAAW,KAAK,SAAS;AACvB,YAAM,MAAM,EAAE,KAAK,CAAC;AACpB,UAAI,CAAC,OAAO,IAAI,QAAQ,UAAW;AACnC;AACA,YAAM,IAAI,MAAM,IAAI,IAAI,EAAE,MAAM,IAAI,IAAI,CAAC;AACzC,UAAI,MAAM,OAAW;AACrB;AACA,UAAI,EAAG;AAAA,IACT;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,WAAW,SAAS,IAAI,UAAU,SAAS;AAAA,MAC3C,UAAU,QAAQ,SAAS,IAAI,WAAW,QAAQ,SAAS;AAAA,IAC7D;AAAA,EACF,CAAC;AAGD,QAAM,QAAQ,CAAC,QAAsB,IAAI,cAAc,QAAQ,IAAI,aAAa;AAChF,QAAM,cACJ,KAAK;AAAA,IACH,CAAC,KAAK,MAAM,IAAI,UAAU,aAAa,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,aAAa,MAAM,CAAC,CAAC;AAAA,EACtH,GAAG,aAAa;AAElB,SAAO,EAAE,OAAO,QAAQ,QAAQ,YAAY,OAAO,WAAW,MAAM,aAAa,SAAS;AAC5F;AAEO,SAAS,kBAAkB,QAA2B,QAAQ,IAAY;AAC/E,QAAM,MAAM,CAAC,MAAsB,MAAM,OAAO,UAAU,IAAI,IAAI,KAAK,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC;AAC/F,QAAM,QAAQ;AAAA,IACZ,sBAAsB,QAAQ,KAAK,KAAK,MAAM,EAAE,WAAM,OAAO,KAAK,WAAW,OAAO,UAAU;AAAA,IAC9F,0BAA0B,OAAO,KAAK,IAAI,OAAO,UAAU,+BAA+B,OAAO,SAAS,IAAI,OAAO,UAAU;AAAA,IAC/H;AAAA,IACA;AAAA,EACF;AACA,aAAW,KAAK,OAAO,MAAM;AAC3B,UAAM;AAAA,MACJ,KAAK,EAAE,UAAU,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC,KAAK,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,KAAK,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,KAAK,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC,KAAK,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAAA,IAChN;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AACb,QAAM;AAAA,IACJ,OAAO,gBAAgB,OACnB,6FACA,4BAA4B,OAAO,YAAY,QAAQ,CAAC,CAAC;AAAA,EAC/D;AACA,QAAM,KAAK,wBAAwB,OAAO,SAAS,MAAM,EAAE;AAC3D,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}