mixdog 0.9.151 → 0.9.152

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 (175) hide show
  1. package/README.md +110 -68
  2. package/package.json +3 -2
  3. package/scripts/tool-stress.mjs +0 -2
  4. package/src/defaults/skills/setup/SKILL.md +18 -27
  5. package/src/headless-exec.mjs +3 -10
  6. package/src/headless-exec.test.mjs +2 -7
  7. package/src/runtime/agent/orchestrator/config.mjs +6 -66
  8. package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
  9. package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
  10. package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
  11. package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
  12. package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
  13. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
  14. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
  15. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  16. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
  17. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
  18. package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
  19. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +10 -2
  20. package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
  21. package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
  22. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
  23. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
  24. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
  25. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
  26. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
  27. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -0
  28. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -2
  29. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
  30. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +4 -7
  31. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -0
  32. package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +235 -0
  33. package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
  34. package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
  35. package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
  36. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +14 -4
  37. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
  38. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +24 -1
  39. package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +161 -0
  40. package/src/runtime/agent/orchestrator/session/context-utils.mjs +14 -4
  41. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +12 -1
  43. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +31 -18
  44. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -0
  45. package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
  46. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +6 -1
  47. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +1 -3
  48. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +556 -0
  49. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +457 -0
  50. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +105 -186
  51. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
  52. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +2 -0
  53. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -0
  54. package/src/runtime/agent/orchestrator/session/store.mjs +23 -1
  55. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
  56. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
  57. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
  58. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
  59. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
  60. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
  61. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
  62. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
  63. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
  64. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
  65. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
  66. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
  67. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
  69. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
  70. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
  71. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
  72. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
  73. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
  74. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  75. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  76. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +8 -0
  77. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  78. package/src/runtime/browser-bridge/client.mjs +103 -0
  79. package/src/runtime/browser-bridge/tool-defs.mjs +42 -0
  80. package/src/runtime/computer-bridge/client.mjs +101 -0
  81. package/src/runtime/computer-bridge/tool-defs.mjs +53 -0
  82. package/src/runtime/media/store.mjs +18 -8
  83. package/src/runtime/media/store.test.mjs +14 -0
  84. package/src/runtime/memory/index.mjs +1 -1
  85. package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
  86. package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
  87. package/src/runtime/shared/child-spawn-gate.mjs +17 -0
  88. package/src/runtime/shared/child-spawn-remote.mjs +69 -7
  89. package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
  90. package/src/runtime/shared/pristine-execution-contract.json +2 -1
  91. package/src/runtime/shared/provider-api-key.mjs +1 -0
  92. package/src/runtime/shared/session-runtime-health.mjs +91 -0
  93. package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
  94. package/src/runtime/shared/skill-document.mjs +86 -0
  95. package/src/runtime/shared/skill-document.test.mjs +81 -0
  96. package/src/runtime/shared/tool-surface.mjs +18 -0
  97. package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
  98. package/src/runtime/shared/turn-snapshot.mjs +32 -9
  99. package/src/runtime/shared/user-cwd.mjs +33 -14
  100. package/src/runtime/shared/user-cwd.test.mjs +43 -0
  101. package/src/session-runtime/cwd-plugins.mjs +26 -26
  102. package/src/session-runtime/cwd-plugins.test.mjs +75 -0
  103. package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
  104. package/src/session-runtime/global-extensions.mjs +26 -0
  105. package/src/session-runtime/global-extensions.test.mjs +28 -0
  106. package/src/session-runtime/goal-runtime.mjs +721 -0
  107. package/src/session-runtime/goal-runtime.test.mjs +108 -0
  108. package/src/session-runtime/lifecycle-api.mjs +18 -4
  109. package/src/session-runtime/lifecycle-api.test.mjs +21 -0
  110. package/src/session-runtime/mcp-glue.mjs +49 -47
  111. package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
  112. package/src/session-runtime/plugin-mcp.mjs +96 -22
  113. package/src/session-runtime/prewarm.mjs +1 -10
  114. package/src/session-runtime/resource-api-global.test.mjs +69 -0
  115. package/src/session-runtime/resource-api.mjs +173 -58
  116. package/src/session-runtime/runtime-core.mjs +148 -20
  117. package/src/session-runtime/runtime-tunables.mjs +6 -8
  118. package/src/session-runtime/session-turn-api.mjs +22 -11
  119. package/src/session-runtime/skills-api.mjs +79 -25
  120. package/src/session-runtime/skills-api.test.mjs +99 -0
  121. package/src/session-runtime/tool-catalog-schema.mjs +0 -5
  122. package/src/session-runtime/tool-catalog.mjs +4 -13
  123. package/src/session-runtime/tool-policy-surface.test.mjs +33 -11
  124. package/src/session-runtime/tool-surface.mjs +12 -0
  125. package/src/standalone/agent-dispatch-broker.mjs +5 -9
  126. package/src/standalone/agent-tool/notify.mjs +6 -8
  127. package/src/standalone/agent-tool/notify.test.mjs +37 -0
  128. package/src/standalone/daemon.mjs +31 -15
  129. package/src/standalone/hook-bus/config.mjs +1 -0
  130. package/src/standalone/plugin-admin.mjs +17 -0
  131. package/src/standalone/plugin-admin.test.mjs +7 -0
  132. package/src/standalone/provider-admin.mjs +1 -0
  133. package/src/standalone/session-protocol.mjs +5 -0
  134. package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
  135. package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
  136. package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
  137. package/src/standalone/session-runtime-host-factory.mjs +17 -0
  138. package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
  139. package/src/standalone/session-runtime-host-health.test.mjs +5 -0
  140. package/src/standalone/session-runtime-host.mjs +696 -58
  141. package/src/standalone/session-runtime-inline-host.mjs +351 -0
  142. package/src/standalone/session-runtime-inline-host.test.mjs +83 -0
  143. package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
  144. package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
  145. package/src/standalone/session-runtime-shard-host.test.mjs +569 -0
  146. package/src/standalone/session-runtime-shard-router.mjs +149 -0
  147. package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
  148. package/src/standalone/session-runtime-worker.mjs +476 -18
  149. package/src/standalone/session-service.mjs +81 -6
  150. package/src/tui/app/app-view.jsx +1 -0
  151. package/src/tui/app/core-memory-picker.mjs +2 -0
  152. package/src/tui/app/extension-pickers.mjs +1 -1
  153. package/src/tui/app/maintenance-pickers.mjs +3 -3
  154. package/src/tui/app/model-options.mjs +2 -0
  155. package/src/tui/app/model-picker.mjs +14 -4
  156. package/src/tui/app/panel-handoff.test.mjs +311 -0
  157. package/src/tui/app/project-picker.mjs +1 -0
  158. package/src/tui/app/route-pickers.mjs +21 -6
  159. package/src/tui/app/settings-picker.mjs +26 -3
  160. package/src/tui/app/slash-commands.mjs +1 -0
  161. package/src/tui/app/slash-dispatch.mjs +73 -22
  162. package/src/tui/app/theme-effort-pickers.mjs +7 -2
  163. package/src/tui/components/Picker.jsx +10 -1
  164. package/src/tui/dist/index.mjs +163 -44
  165. package/src/tui/session/context-state.mjs +1 -0
  166. package/src/tui/session/goal-continuation.mjs +110 -0
  167. package/src/tui/session/goal-continuation.test.mjs +84 -0
  168. package/src/tui/session/queue-helpers.mjs +5 -2
  169. package/src/tui/session/session-action-surface.test.mjs +108 -0
  170. package/src/tui/session/session-api-ext.mjs +39 -8
  171. package/src/tui/session/session-api.mjs +58 -0
  172. package/src/tui/session/session-flow.mjs +6 -0
  173. package/src/tui/session/turn.mjs +1 -0
  174. package/src/tui/session-local.mjs +15 -1
  175. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
@@ -1,301 +0,0 @@
1
- // fuzzy-match.mjs — lightweight subsequence fuzzy scorer for filename / path
2
- // search. Returns a score where a
3
- // higher value is a better match, or null when the query is not a subsequence
4
- // of the candidate.
5
- //
6
- // Scoring favors, in rough order of weight:
7
- // - matches at the START of the basename (last path segment)
8
- // - matches at a word boundary (/, \, _, -, ., space, or camelCase hump)
9
- // - contiguous runs of matched characters
10
- // - exact-case hits (small tie-break)
11
- // - shorter / earlier candidates (mild brevity + earliness pull)
12
- //
13
- // Matching is case-insensitive. The query is matched as an ordered subsequence
14
- // (the chars must appear in order but need not be contiguous), which is what
15
- // lets partial names surface matching files.
16
-
17
- function isBoundaryChar(ch) {
18
- return ch === '/' || ch === '\\' || ch === '_' || ch === '-' || ch === '.' || ch === ' ';
19
- }
20
-
21
- // A camelCase hump (lower/digit followed by upper) is also a word boundary.
22
- function isHump(prevCh, ch) {
23
- return prevCh !== undefined
24
- && /[a-z0-9]/.test(prevCh)
25
- && /[A-Z]/.test(ch);
26
- }
27
-
28
- function prepareFuzzyScore(query) {
29
- if (!query) return 0;
30
- const normalizedQuery = String(query).replace(/[\/\\_.\-\s]+/g, '');
31
- if (!normalizedQuery) return 0;
32
- const q = normalizedQuery.toLowerCase();
33
- const qlen = q.length;
34
- return (str) => {
35
- const s = str.toLowerCase();
36
- const slen = s.length;
37
- if (qlen === 0) return 0;
38
- if (qlen > slen) return null;
39
-
40
- const lastSep = Math.max(str.lastIndexOf('/'), str.lastIndexOf('\\'));
41
-
42
- let score = 0;
43
- let si = 0;
44
- let prevMatch = -2;
45
- let firstMatchIdx = -1;
46
-
47
- for (let qi = 0; qi < qlen; qi++) {
48
- const qc = q[qi];
49
- let found = -1;
50
- for (let k = si; k < slen; k++) {
51
- if (s[k] === qc) { found = k; break; }
52
- }
53
- if (found === -1) return null;
54
- if (firstMatchIdx === -1) firstMatchIdx = found;
55
-
56
- score += 1; // base point per matched char
57
-
58
- if (found === prevMatch + 1) score += 5; // contiguous run
59
-
60
- const prevCh = found > 0 ? str[found - 1] : undefined;
61
- if (prevCh === undefined || isBoundaryChar(prevCh) || isHump(prevCh, str[found])) {
62
- score += 8; // word-boundary start
63
- }
64
-
65
- if (str[found] === normalizedQuery[qi]) score += 1; // exact-case tie-break
66
-
67
- prevMatch = found;
68
- si = found + 1;
69
- }
70
-
71
- // Matches that begin inside the basename (after the last separator) are far
72
- // more relevant than ones buried in directory components.
73
- if (firstMatchIdx > lastSep) score += 10;
74
-
75
- // Mild pulls: shorter candidates and earlier first matches rank higher.
76
- score -= Math.floor(slen / 16);
77
- score -= Math.floor(firstMatchIdx / 8);
78
-
79
- return score;
80
- };
81
- }
82
-
83
- /**
84
- * @param {string} query user query (partial name)
85
- * @param {string} str candidate path (relative)
86
- * @returns {number|null} score, or null if `query` is not a subsequence
87
- */
88
- function fuzzyScore(query, str) {
89
- const scorer = prepareFuzzyScore(query);
90
- return typeof scorer === 'function' ? scorer(str) : scorer;
91
- }
92
-
93
- // Below this per-query-char score, a subsequence-only match (query chars in
94
- // order but scattered mid-word, no contiguity/boundary structure) is treated
95
- // as noise rather than a real hit. Contiguous substring / basename matches
96
- // bypass the floor entirely — an exact hit must never be filtered.
97
- // Measured separation for scattered junk vs real hits ("readme" probe):
98
- // genuine matches score ≥8/char (boundary + contiguity structure), while
99
- // incidental subsequences on unrelated paths land at 5–6/char — 6.5 splits
100
- // them. The floor is tested against the RAW subsequence score (see
101
- // fuzzyRank), never the +40 basename rank bonus, which otherwise vaults
102
- // weak basename subsequences straight past any floor.
103
- const SUBSEQUENCE_MIN_PER_CHAR = 6.5;
104
-
105
- // Separator/case-insensitive normalization used for the "contiguous substring"
106
- // strong-match test. Mirrors the query normalization in fuzzyScore so
107
- // "tool-events.log" matches a "tool-events.log" basename or a ".../tool-events.log"
108
- // path regardless of separators.
109
- function normalizeForContains(s) {
110
- return String(s || '').toLowerCase().replace(/[\/\\_.\-\s]+/g, '');
111
- }
112
-
113
- function fuzzyAsciiMask(value) {
114
- let lo = 0;
115
- let hi = 0;
116
- const text = String(value || '');
117
- for (let index = 0; index < text.length; index++) {
118
- let code = text.charCodeAt(index);
119
- if (code >= 65 && code <= 90) code += 32;
120
- let bit = -1;
121
- if (code >= 48 && code <= 57) bit = code - 48;
122
- else if (code >= 97 && code <= 122) bit = code - 97 + 10;
123
- if (bit < 0) continue;
124
- if (bit < 32) lo |= (1 << bit);
125
- else hi |= (1 << (bit - 32));
126
- }
127
- return { lo: lo >>> 0, hi: hi >>> 0 };
128
- }
129
-
130
- export function prepareFuzzyItems(paths) {
131
- return (paths || []).map((path) => {
132
- const value = String(path || '');
133
- const mask = fuzzyAsciiMask(value);
134
- return {
135
- path: value,
136
- _fuzzyMaskLo: mask.lo,
137
- _fuzzyMaskHi: mask.hi,
138
- _fuzzyLastSep: Math.max(value.lastIndexOf('/'), value.lastIndexOf('\\')),
139
- };
140
- });
141
- }
142
-
143
- function compareRanked(a, b) {
144
- return (b.score - a.score)
145
- || (a.item.path < b.item.path ? -1 : a.item.path > b.item.path ? 1 : 0);
146
- }
147
-
148
- function compareRankedNodes(a, b) {
149
- return compareRanked(a.entry, b.entry) || (a.ordinal - b.ordinal);
150
- }
151
-
152
- function siftDownRankedHeap(heap, index) {
153
- for (;;) {
154
- const left = index * 2 + 1;
155
- const right = left + 1;
156
- let worst = index;
157
- if (left < heap.length && compareRankedNodes(heap[left], heap[worst]) > 0) worst = left;
158
- if (right < heap.length && compareRankedNodes(heap[right], heap[worst]) > 0) worst = right;
159
- if (worst === index) return;
160
- const node = heap[index];
161
- heap[index] = heap[worst];
162
- heap[worst] = node;
163
- index = worst;
164
- }
165
- }
166
-
167
- // Keep the worst retained candidate at index zero. The ordinal completes the
168
- // public comparator only for heap bookkeeping, preserving stable-sort behavior
169
- // for duplicate score/path entries.
170
- function retainTopRanked(heap, entry, ordinal, limit) {
171
- const node = { entry, ordinal };
172
- if (heap.length < limit) {
173
- heap.push(node);
174
- for (let index = heap.length - 1; index > 0;) {
175
- const parent = Math.floor((index - 1) / 2);
176
- if (compareRankedNodes(heap[index], heap[parent]) <= 0) break;
177
- const parentNode = heap[parent];
178
- heap[parent] = heap[index];
179
- heap[index] = parentNode;
180
- index = parent;
181
- }
182
- return;
183
- }
184
- if (compareRankedNodes(node, heap[0]) < 0) {
185
- heap[0] = node;
186
- siftDownRankedHeap(heap, 0);
187
- }
188
- }
189
-
190
- /**
191
- * Rank candidates by fuzzy score against `query`, dropping non-matches.
192
- * @param {string} query
193
- * @param {Array<{path:string}>} items each must expose a `path` string
194
- * @param {number} [limit]
195
- * @returns {Array<{item:object, score:number}>} sorted desc, then path asc
196
- */
197
- export function fuzzyRank(query, items, limit = 0) {
198
- const state = makeTokenScorer(query);
199
- const scored = [];
200
- // Preserve slice's legacy coercion behavior for non-integer public callers;
201
- // normal tool limits are positive integers and take the bounded fast path.
202
- const bounded = Number.isInteger(limit) && limit > 0;
203
- for (let ordinal = 0; ordinal < items.length; ordinal++) {
204
- const sc = scoreItemAgainstToken(state, items[ordinal]);
205
- if (sc === null) continue;
206
- const entry = { item: items[ordinal], score: sc };
207
- if (bounded) retainTopRanked(scored, entry, ordinal, limit);
208
- else scored.push(entry);
209
- }
210
- if (!bounded) {
211
- scored.sort(compareRanked);
212
- return limit > 0 ? scored.slice(0, limit) : scored;
213
- }
214
- return scored
215
- .sort(compareRankedNodes)
216
- .map(({ entry }) => entry);
217
- }
218
-
219
- // Prepared per-token scoring state shared by fuzzyRank / fuzzyRankMulti.
220
- function makeTokenScorer(token) {
221
- const normQuery = normalizeForContains(token);
222
- return {
223
- normQuery,
224
- queryMask: fuzzyAsciiMask(normQuery),
225
- score: prepareFuzzyScore(token),
226
- // Floor scales with query length: a scattered subsequence earns ~1
227
- // point per char, while any contiguous run (+5/char) or word-boundary
228
- // hit (+8) pushes a genuine match well past 6.5/char.
229
- floor: normQuery.length * SUBSEQUENCE_MIN_PER_CHAR,
230
- };
231
- }
232
-
233
- // Gate + score ONE candidate against ONE prepared token. Returns the rank
234
- // score (basename bonus included) or null when the candidate fails the ASCII
235
- // mask prefilter or the subsequence quality floor.
236
- function scoreItemAgainstToken(state, item) {
237
- const p = String(item.path || '');
238
- if (Number.isInteger(item._fuzzyMaskLo)
239
- && (((item._fuzzyMaskLo >>> 0) & state.queryMask.lo) >>> 0) !== state.queryMask.lo) return null;
240
- if (Number.isInteger(item._fuzzyMaskHi)
241
- && (((item._fuzzyMaskHi >>> 0) & state.queryMask.hi) >>> 0) !== state.queryMask.hi) return null;
242
- const lastSep = Number.isInteger(item._fuzzyLastSep)
243
- ? item._fuzzyLastSep
244
- : Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
245
- const base = p.slice(lastSep + 1);
246
- const pathScore = typeof state.score === 'function' ? state.score(p) : state.score;
247
- const baseScore = typeof state.score === 'function' ? state.score(base) : state.score;
248
- const sc = Math.max(pathScore ?? -Infinity, baseScore === null ? -Infinity : baseScore + 40);
249
- if (!Number.isFinite(sc)) return null;
250
- // Strong match: the query (separators stripped) is a contiguous
251
- // substring of the basename or the full path. These ALWAYS pass so an
252
- // exact substring/basename hit can never be starved out as noise.
253
- const strong = state.normQuery.length > 0 && normalizeForContains(p).includes(state.normQuery);
254
- // Otherwise it is subsequence-only: keep it only if it clears the
255
- // per-char floor. Weak scattered matches (the pgAdmin-style junk that
256
- // merely contains the query chars in order) fall below it and drop out.
257
- // Compare the RAW subsequence score — the +40 basename rank bonus is
258
- // for ordering only and must not lift junk over the quality floor.
259
- const rawSc = Math.max(pathScore ?? -Infinity, baseScore ?? -Infinity);
260
- if (!strong && rawSc < state.floor) return null;
261
- return sc;
262
- }
263
-
264
- /** Split a find query into whitespace-separated fragments. */
265
- export function splitFuzzyQueryTokens(query) {
266
- return String(query || '').trim().split(/\s+/).filter(Boolean);
267
- }
268
-
269
- /**
270
- * Multi-fragment AND ranking: every whitespace-separated fragment of `query`
271
- * must independently match a candidate (same gate as fuzzyRank); the rank
272
- * score is the fragment sum. Single-fragment queries delegate to fuzzyRank
273
- * unchanged.
274
- */
275
- export function fuzzyRankMulti(query, items, limit = 0) {
276
- const tokens = splitFuzzyQueryTokens(query);
277
- if (tokens.length <= 1) return fuzzyRank(tokens[0] ?? String(query || ''), items, limit);
278
- const states = tokens.map(makeTokenScorer);
279
- const scored = [];
280
- const bounded = Number.isInteger(limit) && limit > 0;
281
- for (let ordinal = 0; ordinal < items.length; ordinal++) {
282
- const item = items[ordinal];
283
- let total = 0;
284
- for (const state of states) {
285
- const sc = scoreItemAgainstToken(state, item);
286
- if (sc === null) { total = null; break; }
287
- total += sc;
288
- }
289
- if (total === null) continue;
290
- const entry = { item, score: total };
291
- if (bounded) retainTopRanked(scored, entry, ordinal, limit);
292
- else scored.push(entry);
293
- }
294
- if (!bounded) {
295
- scored.sort(compareRanked);
296
- return limit > 0 ? scored.slice(0, limit) : scored;
297
- }
298
- return scored
299
- .sort(compareRankedNodes)
300
- .map(({ entry }) => entry);
301
- }