@untangle-code/search 0.0.0-stage → 0.1.1

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 (140) hide show
  1. package/LICENSE +661 -0
  2. package/NOTICE +90 -0
  3. package/README.md +433 -2
  4. package/THIRD_PARTY_LICENSES.md +29 -0
  5. package/dist/benchmark-integrity.d.ts +12 -0
  6. package/dist/benchmark-integrity.d.ts.map +1 -0
  7. package/dist/benchmark-integrity.js +10 -0
  8. package/dist/benchmark-integrity.js.map +1 -0
  9. package/dist/bm25.d.ts +46 -0
  10. package/dist/bm25.d.ts.map +1 -0
  11. package/dist/bm25.js +105 -0
  12. package/dist/bm25.js.map +1 -0
  13. package/dist/deadline.d.ts +4 -0
  14. package/dist/deadline.d.ts.map +1 -0
  15. package/dist/deadline.js +54 -0
  16. package/dist/deadline.js.map +1 -0
  17. package/dist/dedupe.d.ts +48 -0
  18. package/dist/dedupe.d.ts.map +1 -0
  19. package/dist/dedupe.js +58 -0
  20. package/dist/dedupe.js.map +1 -0
  21. package/dist/diversify.d.ts +45 -0
  22. package/dist/diversify.d.ts.map +1 -0
  23. package/dist/diversify.js +30 -0
  24. package/dist/diversify.js.map +1 -0
  25. package/dist/engine.d.ts +157 -0
  26. package/dist/engine.d.ts.map +1 -0
  27. package/dist/engine.js +1498 -0
  28. package/dist/engine.js.map +1 -0
  29. package/dist/enrichment.d.ts +16 -0
  30. package/dist/enrichment.d.ts.map +1 -0
  31. package/dist/enrichment.js +92 -0
  32. package/dist/enrichment.js.map +1 -0
  33. package/dist/filter.d.ts +86 -0
  34. package/dist/filter.d.ts.map +1 -0
  35. package/dist/filter.js +166 -0
  36. package/dist/filter.js.map +1 -0
  37. package/dist/findability.d.ts +144 -0
  38. package/dist/findability.d.ts.map +1 -0
  39. package/dist/findability.js +393 -0
  40. package/dist/findability.js.map +1 -0
  41. package/dist/format.d.ts +28 -0
  42. package/dist/format.d.ts.map +1 -0
  43. package/dist/format.js +172 -0
  44. package/dist/format.js.map +1 -0
  45. package/dist/fusion.d.ts +47 -0
  46. package/dist/fusion.d.ts.map +1 -0
  47. package/dist/fusion.js +84 -0
  48. package/dist/fusion.js.map +1 -0
  49. package/dist/glob.d.ts +50 -0
  50. package/dist/glob.d.ts.map +1 -0
  51. package/dist/glob.js +144 -0
  52. package/dist/glob.js.map +1 -0
  53. package/dist/grep.d.ts +76 -0
  54. package/dist/grep.d.ts.map +1 -0
  55. package/dist/grep.js +475 -0
  56. package/dist/grep.js.map +1 -0
  57. package/dist/index.d.ts +42 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +63 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/ledger.d.ts +45 -0
  62. package/dist/ledger.d.ts.map +1 -0
  63. package/dist/ledger.js +39 -0
  64. package/dist/ledger.js.map +1 -0
  65. package/dist/payload.d.ts +108 -0
  66. package/dist/payload.d.ts.map +1 -0
  67. package/dist/payload.js +263 -0
  68. package/dist/payload.js.map +1 -0
  69. package/dist/port.d.ts +112 -0
  70. package/dist/port.d.ts.map +1 -0
  71. package/dist/port.js +2 -0
  72. package/dist/port.js.map +1 -0
  73. package/dist/product-complexity.d.ts +89 -0
  74. package/dist/product-complexity.d.ts.map +1 -0
  75. package/dist/product-complexity.js +1367 -0
  76. package/dist/product-complexity.js.map +1 -0
  77. package/dist/regex-safety.d.ts +75 -0
  78. package/dist/regex-safety.d.ts.map +1 -0
  79. package/dist/regex-safety.js +902 -0
  80. package/dist/regex-safety.js.map +1 -0
  81. package/dist/relevance-prerequisites.d.ts +21 -0
  82. package/dist/relevance-prerequisites.d.ts.map +1 -0
  83. package/dist/relevance-prerequisites.js +47 -0
  84. package/dist/relevance-prerequisites.js.map +1 -0
  85. package/dist/relevance.d.ts +107 -0
  86. package/dist/relevance.d.ts.map +1 -0
  87. package/dist/relevance.js +199 -0
  88. package/dist/relevance.js.map +1 -0
  89. package/dist/retrieval.d.ts +46 -0
  90. package/dist/retrieval.d.ts.map +1 -0
  91. package/dist/retrieval.js +182 -0
  92. package/dist/retrieval.js.map +1 -0
  93. package/dist/scoring.d.ts +102 -0
  94. package/dist/scoring.d.ts.map +1 -0
  95. package/dist/scoring.js +175 -0
  96. package/dist/scoring.js.map +1 -0
  97. package/dist/sort.d.ts +21 -0
  98. package/dist/sort.d.ts.map +1 -0
  99. package/dist/sort.js +53 -0
  100. package/dist/sort.js.map +1 -0
  101. package/dist/symbol-match.d.ts +21 -0
  102. package/dist/symbol-match.d.ts.map +1 -0
  103. package/dist/symbol-match.js +72 -0
  104. package/dist/symbol-match.js.map +1 -0
  105. package/dist/tokenize.d.ts +41 -0
  106. package/dist/tokenize.d.ts.map +1 -0
  107. package/dist/tokenize.js +202 -0
  108. package/dist/tokenize.js.map +1 -0
  109. package/dist/types.d.ts +173 -0
  110. package/dist/types.d.ts.map +1 -0
  111. package/dist/types.js +42 -0
  112. package/dist/types.js.map +1 -0
  113. package/package.json +61 -3
  114. package/src/benchmark-integrity.ts +25 -0
  115. package/src/bm25.ts +135 -0
  116. package/src/deadline.ts +59 -0
  117. package/src/dedupe.ts +99 -0
  118. package/src/diversify.ts +71 -0
  119. package/src/engine.ts +1957 -0
  120. package/src/enrichment.ts +115 -0
  121. package/src/filter.ts +267 -0
  122. package/src/findability.ts +580 -0
  123. package/src/format.ts +228 -0
  124. package/src/fusion.ts +121 -0
  125. package/src/glob.ts +151 -0
  126. package/src/grep.ts +618 -0
  127. package/src/index.ts +83 -0
  128. package/src/ledger.ts +72 -0
  129. package/src/payload.ts +382 -0
  130. package/src/port.ts +133 -0
  131. package/src/product-complexity.ts +2168 -0
  132. package/src/regex-safety.ts +1002 -0
  133. package/src/relevance-prerequisites.ts +83 -0
  134. package/src/relevance.ts +328 -0
  135. package/src/retrieval.ts +299 -0
  136. package/src/scoring.ts +321 -0
  137. package/src/sort.ts +86 -0
  138. package/src/symbol-match.ts +87 -0
  139. package/src/tokenize.ts +213 -0
  140. package/src/types.ts +206 -0
package/NOTICE ADDED
@@ -0,0 +1,90 @@
1
+ # NOTICE
2
+
3
+ Copyright (C) 2026 Luke Bölling and contributors.
4
+
5
+ This attribution covers original Untangle work. Contributors retain copyright in
6
+ their contributions. Third-party packages, grammars and other materials retain
7
+ their own copyright holders, licenses and notices; this line does not replace them.
8
+
9
+ ## License — binding decision
10
+
11
+ Untangle is licensed under the **GNU Affero General Public License, Version 3
12
+ (AGPL-3.0-only)**. See [`LICENSE`](./LICENSE).
13
+
14
+ This decision is **final, not provisional**, and was explicitly instructed by
15
+ the project owner. No contributor, agent or tool may change it without a new
16
+ explicit instruction.
17
+
18
+ The repository-wide license covers Untangle's first-party source code, scripts,
19
+ tests, fixtures, configuration, documentation, website and brand artwork, including
20
+ the logo and 3D yarn illustrations. Every workspace `package.json` declares
21
+ `"license": "AGPL-3.0-only"`. There is no separately licensed enterprise component
22
+ or separate permissive license for documentation or brand assets.
23
+
24
+ Third-party code, embedded grammars, dependency assets and their notices retain
25
+ their original licenses. Their inclusion does not relicense those works. The
26
+ repository license does not grant trademark rights or change the license of a
27
+ repository analyzed with Untangle. See the repository's `docs/LICENSING.md` for
28
+ scope and verification details.
29
+
30
+ This resolves the license question left open in `vision.md` §14 and completes
31
+ `product.md` §16, item 2.
32
+
33
+ ## Rationale
34
+
35
+ - **Network copyleft fits the intended product model.** AGPL §13 sets source
36
+ availability requirements for modified versions used through a computer network.
37
+ The current local CLI/stdio-MCP entry and future shared-server modes share the
38
+ same license.
39
+ - **Local-first analysis has a clear boundary.** Running Untangle against a codebase
40
+ does not replace that codebase's own license with Untangle's license.
41
+ - **First-party components share one license.** Implemented components and future
42
+ official plugins use this repository's licensing policy. Planned integrations do
43
+ not establish implemented or published products.
44
+
45
+ ## Deliberately accepted consequence
46
+
47
+ The project deliberately chooses AGPL rather than a permissive embedding license.
48
+ Redistribution, modifications and combined works must be assessed against the
49
+ actual terms in `LICENSE`, including applicable Corresponding Source requirements.
50
+ No permissive exception, commercial dual license or special contract-only package
51
+ license is granted or promised by this notice.
52
+
53
+ ## Dependencies
54
+
55
+ Third-party runtime dependencies are checked against the allowlist in
56
+ `scripts/check-licenses.mjs`. Their licenses and copyright notices remain unchanged
57
+ and traceable through package `THIRD_PARTY_LICENSES.md` files, grammar provenance,
58
+ the lockfile and release SBOM. The allowlist includes MIT / Apache-2.0 / BSD / ISC
59
+ and other explicitly listed licenses; it is not a license grant for dependencies.
60
+
61
+ The allowlist in `scripts/check-licenses.mjs` applies to **dependencies**, not
62
+ workspace packages. Untangle's own packages carry AGPL-3.0-only and are excluded
63
+ from that check.
64
+
65
+ ## Clean room
66
+
67
+ Untangle is an independent implementation with its own data models, APIs, tool
68
+ names, tests and sources. Feature ideas may be derived from publicly observable
69
+ behavior and generally known techniques. Third-party source code is not copied,
70
+ including from AGPL projects even where the license would permit it.
71
+
72
+ ## Contribution terms
73
+
74
+ Pull requests are welcome under the repository's existing AGPL-3.0-only license.
75
+ No separate CLA, copyright assignment or DCO sign-off is required. Contributors
76
+ must have the right to submit their work under those terms. See CONTRIBUTING.md.
77
+
78
+ ## Release decisions
79
+
80
+ - [x] Copyright attribution: Luke Bölling, with contributors retaining their rights.
81
+ - [x] Contributions use AGPL-3.0-only without an additional CLA or DCO gate.
82
+
83
+ ## Open legal items before public beta
84
+
85
+ - [ ] Trademark search for the product name `Untangle`.
86
+ - [ ] Registration and legal review of the npm scope `@untangle-code`.
87
+ - [ ] Verify source availability and notices for each released network daemon/server
88
+ mode (WP-59). The current stdio MCP `untangle_about` already reports the license,
89
+ source repository and build commit when supplied; metadata alone does not prove
90
+ that a concrete deployed version's source is available.
package/README.md CHANGED
@@ -1,3 +1,434 @@
1
- # Temporary Holding Version
1
+ # @untangle-code/search
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Untangle's **retrieval core** (spec.md §13, architecture.md §18, vision.md §1b/§1d/§1e): hybrid
4
+ search, explainable score breakdowns, retrieval funnels, Search Journeys, complexity and
5
+ Findability filters, grep mode, and deterministic output formats. I/O goes exclusively through the
6
+ domain ports (`VectorStore`, `EmbeddingProvider`, `SearchJourneyStorePort`, `Clock`, `Random`,
7
+ `LoggerPort`); no Qdrant details, `Date.now()`, `Math.random()`, or `localeCompare`.
8
+
9
+ Normative sources: [`spec.md`](../../spec.md) §13 (takes precedence in a conflict),
10
+ [`architecture.md`](../../architecture.md) §18, [`vision.md`](../../vision.md) §1b/§1d/§1e.
11
+
12
+ <a id="wofür-das-paket-da-ist"></a>
13
+
14
+ ## Purpose
15
+
16
+ > **Product complexity is the distance between intent and implementation.** Easy to find is
17
+ > evidence of a product that is well-defined, clear, and easy to understand. It is evidence, not
18
+ > proof: search effort is a sensor whose meaning depends on the retrieval context and an explicit
19
+ > target.
20
+
21
+ The user described the original goal as follows (translated):
22
+
23
+ > “When I search for ‘function X’, I should quickly reach the right place. If I do not, the code
24
+ > may be too complex or insufficiently documented.”
25
+
26
+ This currently produces three separate but connected capabilities:
27
+
28
+ - **Search**: dense + lexical + exact symbol name, fused with RRF.
29
+ - **Findability**: the same score breakdown used as a synthetic self-probe of a known symbol
30
+ (`findability.ts`: a symbol searches for itself; if it cannot find itself, the cause is named).
31
+ - **Dynamic Search Complexity**: several real search attempts within a task, up to an explicitly
32
+ confirmed target (`product-complexity.ts`), including retrieval funnels, reformulations,
33
+ candidate dispersion, and explicitly reported navigation. **Dynamic Product Complexity** emerges
34
+ only across several completed, comparable Journeys.
35
+ - **Feature Probes**: optionally tagged Journeys connecting a pseudonymized product capability to
36
+ confirmed code targets and reporting Search Complexity and Target-Code Complexity as two
37
+ separate evidence axes.
38
+
39
+ Static Code Complexity remains separate. It can be a filter (`--cognitive-min`), a visible score
40
+ component (`complexity_modifier`), or a query sort key. Dynamic Search Complexity instead assesses
41
+ the observed route from product intent to implementation location rather than a file's control
42
+ flow. Product Complexity aggregates these task-local vectors only within compatible cohorts.
43
+
44
+ <a id="modulübersicht"></a>
45
+
46
+ ## Module overview
47
+
48
+ | Module | Contents | Normative source |
49
+ |---|---|---|
50
+ | `types.ts` | `UntangleSearchRequestV1` (= `SearchRequestV1` + mode/grep/format/bias), `GrepRequestV1`, `GrepResultSetV1` | spec §13.1; request A4 |
51
+ | `port.ts` | `SearchPort` (`search`, `grep`), `ProfileTargetV1`, `SparseVectorizer`, `FindabilityLookup`, `GraphProximityLookup` | arch §18.1 |
52
+ | `engine.ts` | `HybridSearchEngine`: reference port implementation; returns retrieval diagnostics, an effective ranking-context fingerprint, and a profile-bound corpus revision when index context is complete | spec §13.2, §13.7 |
53
+ | `tokenize.ts` | identifier-aware tokenization (`getUserById` → `getuserbyid`, `get`, `user`, `by`, `id`) | spec §13.2 step 3 |
54
+ | `bm25.ts` | `Bm25Ranker`: a deterministic local Okapi BM25 over the candidate pool | spec §10.2, §13.2 |
55
+ | `symbol-match.ts` | four-tier name comparison and storage filter candidates | vision §1d |
56
+ | `fusion.ts` | weighted Reciprocal Rank Fusion, `k = 60`, normalized against the theoretical maximum | spec §13.4 |
57
+ | `scoring.ts` | `ScoreBreakdownV1`, including boost cap (`MAX_BOOST_SHARE`) | spec §13.3 |
58
+ | `relevance.ts` | profile-calibrated absolute/relative relevance decisions and absence statements | vision §1d; WP-231 |
59
+ | `filter.ts` | `SearchFiltersV1` → `PayloadFilter` **plus** a list of filters that cannot be pushed down | spec §13.2 step 7, §10.4 |
60
+ | `glob.ts` | deterministic glob matching for path inclusion/exclusion | request A4 |
61
+ | `dedupe.ts` | dedupe key `workspace + repo + ref + metrics_scope_id`, Related Fragments | arch §18.4; spec §13.5 |
62
+ | `diversify.ts` | saturating duplicate penalty per file/repository | arch §18.5 |
63
+ | `sort.ts` | sorting with code-point tie-breaking, rank assignment | request A4 |
64
+ | `grep.ts` | regex/literal search over indexed content, with context lines and budgets | request A4 |
65
+ | `format.ts` | Text / JSON / JSONL | request A4 |
66
+ | `findability.ts` | self-probes, dispersion, hops, named causes | vision §1d |
67
+ | `product-complexity.ts` | `SearchJourneyService`, task-based assessments, compatible workspace cohorts, and Feature→Code mappings separated by feature fingerprint and measurement context; incomplete evidence remains visible but never sufficient | spec §13.7; vision §1e; WP-248/WP-249/WP-253 |
68
+ | `payload.ts` | stored payload → candidate, with a named reason when unusable | spec §10.3 |
69
+ | `ledger.ts` | Skip Ledger collector | BUILD_PLAN E4 |
70
+
71
+ All public symbols are re-exported exclusively through `src/index.ts`.
72
+
73
+ ## Dynamic Product Complexity
74
+
75
+
76
+ A Search Journey is an opt-in observation of real work, not a synthetic benchmark. It starts with a
77
+ task goal, records successful and failed searches, accepts explicitly reported file/symbol/graph
78
+ navigation, and can end as `completed` or `abandoned`. A completed journey requires at least one
79
+ search attempt and an explicit file, chunk, or symbol target. Until then, Untangle does not infer
80
+ that the agent found the right place.
81
+
82
+ An optional stable `featureId` turns that task into a repeatable Feature Probe. The service stores
83
+ it as a separate workspace-local HMAC fingerprint unless text retention is enabled. New search
84
+ observations preserve the hit's versioned `ComplexitySummaryV1` and payload-completeness evidence;
85
+ completion exposes this as observed target-code evidence only when the accepted candidate is
86
+ unambiguous. Missing, broad, or legacy evidence stays explicitly unavailable.
87
+
88
+ The assessment exposes components rather than one opaque number: searches and reformulations to
89
+ target, accepted rank and reciprocal rank when the target was returned, retrieval health, eligible
90
+ and exposed candidates, file dispersion, candidate churn, limit pressure, and observed navigation
91
+ hops. An externally found or profile-ambiguous target remains valid evidence but receives no
92
+ invented rank.
93
+
94
+ Reports keep search effort and target-code complexity side by side. Embeddings are retrieval
95
+ representations, not complexity metrics, and no combined score or causal relationship is emitted.
96
+ `featureMappings` include only explicitly tagged journeys and group by both feature fingerprint and
97
+ measurement context. Target metric variants keep their exact scope, metrics version, and index
98
+ generation instead of being averaged across incompatible evidence. The report-level
99
+ `sessionCounts.featureTaggedCompleted` counts exactly the completed, explicitly tagged journeys;
100
+ untagged completed journeys can contribute to workspace cohorts but not to feature mappings.
101
+
102
+ One completed journey has a caller-closed discovery record for that task and target. The wider
103
+ question — whether the product is valuable or the target is the right product decision — remains
104
+ feedback-open. Reports
105
+ therefore aggregate only journeys with the same effective ranker, ranking-context fingerprint,
106
+ result limit and candidate limit, with the embedding profile, generation set, and immutable corpus
107
+ revision bound together separately for every searched profile. At least three completed journeys
108
+ with a complete context are required before `sufficientEvidence` becomes true. Even then, the
109
+ cohort is a heuristic, timeless learning artifact, not a precision claim, temporal trend view, or
110
+ universal product score.
111
+
112
+ Text retention is deliberately narrow. By default, task and query text become workspace-local,
113
+ domain-separated HMAC-SHA-256 fingerprints; successful candidates retain locators,
114
+ profile/generation, rank, and score, never chunk or source content. `retainText: true` additionally
115
+ retains goal/query text, failed-search reasons, and free abandonment details. The HMAC key is
116
+ supplied by the storage adapter and kept outside the session records. This is pseudonymization, not
117
+ anonymity or encryption: an operator with the full local state and key can test guesses. Explicit
118
+ tool metadata and a controlled abandonment `reasonCode` may remain without `retainText` and should
119
+ not contain secrets.
120
+
121
+ Every retained goal/query, free reason, tool name, and individual locator component is capped at
122
+ 4,096 UTF-16 code units; `featureId` remains capped at 256. These limits apply in the service and
123
+ persisted-domain validator as well as MCP, so a direct adapter call cannot bypass them. Successful
124
+ results are first reduced to the exact bounded profile/candidate projection that is both
125
+ fingerprinted and retained; every service-created Journey state is then domain-validated before
126
+ any `SearchJourneyStorePort.put`, including with a permissive alternate adapter. Confirmed and
127
+ navigated targets are normalized into a new bounded locator object before persistence.
128
+
129
+ Without an explicit `acceptedAttemptId`, every search through `complete` remains part of the effort
130
+ vector; rank and score margin are attributed to the last matching attempt with a visible caveat.
131
+ Supplying an earlier accepted attempt deliberately closes the search portion at that attempt. New
132
+ explicit completions retain that ID in `outcome.acceptedAttemptId`, so persisted and MCP-returned
133
+ assessments can be checked against the exact attempt even when a target repeats or numbered results
134
+ arrive out of order. Legacy outcomes without the additive field retain last-match reconstruction.
135
+ Partially unhealthy retrieval never qualifies as a complete measurement context and therefore
136
+ cannot make a cohort `sufficientEvidence: true`.
137
+ Callers can provide the locally known generation, corpus revision, and run outcome for each
138
+ repository/ref. Only a `completed` index context with its `corpusRevisionFingerprint` is
139
+ measurement-healthy. The immutable revision is derived from the complete scanned
140
+ File-ID/content-hash manifest after every completed index, including a zero-change incremental run.
141
+ Hits from a running, cancelled, failed, legacy, or otherwise unverifiable context remain usable,
142
+ while the result warning `INDEX_CONTEXT_INCOMPLETE` makes a linked Search Journey's retrieval
143
+ unhealthy and its measurement context incomplete.
144
+ A rejected candidate with reason `missing_index_entry` or the result warning `INCOMPLETE_PAYLOAD`
145
+ has the same effect: useful partial results remain available, but incomplete index evidence never
146
+ counts as healthy retrieval.
147
+ Completeness also requires retrieval diagnostics and one identical full attempt tuple across every
148
+ counted successful search: ranker, ranking-context fingerprint, effective result/candidate limits,
149
+ and each profile's bound embedding-profile, index-generation set, and corpus revision. New
150
+ observations mark this as measurement context v2. Legacy sessions remain readable and visible in
151
+ reports, but the report deliberately downgrades them to incomplete so they can never make a cohort
152
+ sufficient. Missing diagnostics or any tuple change has the same result.
153
+
154
+ A journey accepts at most 128 search attempts and 512 navigation events. Optional client steps are
155
+ 1-based and make retries idempotent: the same step with the same observation is reused, while the
156
+ same step with different evidence conflicts. Numbered and unnumbered attempts cannot be mixed, and
157
+ a numbered journey cannot complete with gaps in its step sequence. Active and abandoned journeys
158
+ remain visible in report counts but never enter cohort metrics.
159
+
160
+ <a id="die-zentrale-bauentscheidung-abruf--rangfolge"></a>
161
+
162
+ ## The central design decision: retrieval ≠ ranking
163
+
164
+ ```text
165
+ RETRIEVAL (from storage) RANKING (in this process)
166
+ ──────────────────────── ──────────────────────────────
167
+ hybridQuery(dense) ─┐ dense rank (storage cosine)
168
+ hybridQuery(sparse) ─┼─► Pool ─► lexical rank (local BM25) ─► RRF ─► base_hybrid
169
+ scroll(symbol_name in …)─┘ symbol rank (name comparison)
170
+ ```
171
+
172
+ The **search mode** selects retrieval and ranking: `semantic` queries dense only, `lexical` sparse
173
+ only, and `symbol` the exact-name filter only. Lexical and symbol search do not create a query
174
+ embedding. `hybrid` starts all three branches independently and fuses the branches retrieved
175
+ successfully. A branch failure leaves the other responses usable and is reported through
176
+ `SEARCH_BRANCH_UNAVAILABLE` (branch and original error code), a caveat, and the Skip Ledger. If every
177
+ requested branch of every profile fails, search throws a provider error; zero hits from a successful
178
+ search remain distinguishable. Cancellation signals are forwarded.
179
+ Hybrid branches have their own deadline, 10 seconds by default: a hanging branch is cancelled and
180
+ also reported through `SEARCH_BRANCH_TIMEOUT`, while successful branches respond. The deadline is
181
+ configurable through `branchTimeoutMs` in the engine port and belongs to the ranking context. Pure
182
+ semantic/lexical/symbol search uses the outer call deadline so a semantic model cold start gets its
183
+ regular time allowance.
184
+
185
+ A dense/sparse absence policy is explicitly not evaluated when calibrated branch evidence is
186
+ missing; an observed exact symbol name remains direct evidence. Partially failed hybrid retrieval
187
+ does not supply a complete Journey measurement context. The new retrieval strategy is versioned in
188
+ the ranking context, keeping older Journeys with the previous mode behavior separate.
189
+
190
+ <a id="die-score-zerlegung-ist-öffentliche-api"></a>
191
+
192
+ ## The score breakdown is public API
193
+
194
+ ```text
195
+ final = base_hybrid
196
+ + exact_symbol_boost + exact_path_boost + repo_priority_boost
197
+ + graph_proximity_boost + recency_boost
198
+ + complexity_modifier
199
+ - duplicate_penalty
200
+ ```
201
+
202
+ Each component carries `rawValue`, `weight`, `contribution = rawValue * weight`, and evidence
203
+ (`evidence`). The domain's `validateScoreBreakdown()` checks the identity and sum; this package's
204
+ tests call it on **every** generated hit.
205
+
206
+ Text output for a hit:
207
+
208
+ ```text
209
+ 1. 1.0743 billing@main src/payment/payment-service.ts:1-3 method charge
210
+ complexity: cognitive 22
211
+ why:
212
+ + base_hybrid = 1.0000 (raw 1.0000 x weight 1.0000) [RRF over dense#1, lexical#1, symbol#1]
213
+ + exact_symbol_boost = 0.1500 (raw 1.0000 x weight 0.1500) [symbol_name == "charge"]
214
+ ```
215
+
216
+ <a id="entscheidungen"></a>
217
+
218
+ ## Decisions
219
+
220
+ Where spec.md/architecture.md leave questions open, the following reasoned decisions apply.
221
+ Downstream packages that need something else should extend **additively**.
222
+
223
+ 1. **Lexical ranking happens here, not in storage.** The `VectorStore` sparse branch supplies
224
+ candidates (recall); `Bm25Ranker` computes their rank in this process. The score breakdown must be
225
+ reproducible, and Qdrant's IDF statistics are not reproducible outside Qdrant. The trade-off: IDF
226
+ applies to the candidate pool rather than the entire corpus (see “Known limits”).
227
+
228
+ 2. **Sparse vectorization is injected (`SparseVectorizer`), not implemented here.** It must match
229
+ index-run tokenization bit for bit, so it belongs to the storage adapter. Without it, hybrid
230
+ loses sparse retrieval and reports `SPARSE_RECALL_UNAVAILABLE` and the failed branch evidence.
231
+ Pure lexical search then cannot execute any branch and reports an error. Semantic and symbol
232
+ search do not need a sparse vectorizer.
233
+
234
+ 3. **`k = 60` in the RRF formula** comes from the original paper (Cormack/Clarke/Buettcher 2009) and
235
+ is the default in Qdrant's own RRF implementation. Branch weights are dense 1.0, lexical 1.0,
236
+ **symbol name 0.5**: half weight because an exact name match also receives `exact_symbol_boost`
237
+ and would otherwise count twice.
238
+
239
+ 4. **Normalization uses the theoretical maximum, not the best hit in the list.** Otherwise the
240
+ best hit would always score 1.0, even when poor; Findability diagnosis needs that distinction.
241
+
242
+ 5. **Boosts are capped at `MAX_BOOST_SHARE = 0.5` of the base amount** (spec §13.3: they “must not
243
+ completely override Dense/BM25”). The cap applies to the **weight**, not the contribution, keeping
244
+ `contribution = rawValue * weight` true and the raw signal visible. The cap is disclosed through
245
+ `clamped`/`clampReason`.
246
+
247
+ 6. **The complexity modifier is OFF by default.** Complexity must not silently shift ranking.
248
+ Opting in (`complexityBias: "prefer-simple"`) adds a visible `complexity_modifier` to the
249
+ breakdown. For this, `SCORE_COMPONENT_KINDS` in `packages/domain` was extended **additively** with
250
+ `"complexity_modifier"`, as was `ScoreBreakdownV1.inputs` with optional fields `denseRank`,
251
+ `lexicalScore`, `lexicalRank`, `symbolRank`: without them, three-branch fusion could not be
252
+ reproduced. No existing signature changed.
253
+
254
+ 7. **A component with raw value 0 is omitted rather than emitted as zero.** A breakdown should
255
+ explain what happened, not everything that could have happened, keeping output economical in
256
+ tokens (vision §1a, “For Agents”).
257
+
258
+ 8. **`changedSince` is pushed into storage as a range condition on `changed_at_epoch_ms`.** Points
259
+ without this field fail the condition and are excluded. “Changed since X” cannot be answered
260
+ without a change date, and silently accepting such points would give a false answer.
261
+
262
+ 9. **Scope is a REQUIRED PARAMETER, not a filter.** `buildPayloadFilter` requires a `SearchScopeV1`
263
+ (workspace, target repositories) and ALWAYS pushes `workspace_id` into the storage filter; the
264
+ target repositories are intersected with the request filter. If the request is entirely outside
265
+ the target, storage is not queried: an empty `in` list would not be a filter at all. The engine
266
+ also checks scope again on each hit. A store that ignores the filter then creates ledger entries
267
+ rather than out-of-scope hits. The same rule applies to grep mode.
268
+
269
+ 10. **Path globs and Findability are post-filters.** The payload filter supports only exact values
270
+ and ranges. Any post-filtering is disclosed as a `caveat` on the result group; every rejected
271
+ candidate appears in the Skip Ledger. The same holds for grep mode, where rejections beyond
272
+ `GREP_GLOB_LEDGER_DETAIL_LIMIT` become an aggregate entry with the complete count.
273
+
274
+ 11. **The dedupe key includes the ref.** The same code in two branches or worktrees represents two
275
+ answers (vision §1a). A request returning hits from several refs of one repository additionally
276
+ receives `MULTIPLE_REFS`.
277
+
278
+ 12. **Diversification without content vectors.** `hybridQuery` returns payloads, not vectors;
279
+ content MMR would require a second storage round trip. Instead, a saturating penalty
280
+ `1 - 1/(1+n)` applies across file and repository, the dimension named first in arch §18.5.
281
+
282
+ 13. **grep searches the index, not the disk.** Accordingly, `GrepResultSetV1` carries `scannedChunks`
283
+ and the Skip Ledger: “no hits” and “nothing searched” must not look alike.
284
+
285
+ 14. **The port lives in this package, not `domain`.** `SearchPort` composes two existing domain
286
+ ports and the grep/format extensions; the domain does not need it yet. Its contract suite
287
+ therefore lives here under `src/testing/` and is not included in the build.
288
+
289
+ 15. **A pattern without `/` is also checked against the basename**, so `*.ts` behaves as expected
290
+ without requiring `**/*.ts`.
291
+
292
+ 16. **Absence is a profile- and corpus-bound conjunction, not a universal cosine value.** A policy
293
+ is found by `embeddingProfileId` and applies only when its `workspaceId` and physical
294
+ `collection` also match the search target. An empty policy map preserves the previous unfiltered
295
+ behavior without an additional warning. If a nonempty map lacks the profile key of an active
296
+ target, its hits stay unchanged, but `RELEVANCE_POLICY_NOT_EVALUATED` names
297
+ `policy_profile_mismatch`, the actual profile, and all configured keys. The supplied
298
+ `QWEN3_EMBEDDING_06B_RELEVANCE_POLICY` is calibrated exclusively against the existing Untangle
299
+ self-index. It accepts an exact symbol name directly; otherwise **all three** signals must
300
+ support the match: dense top value ≥ 0.45, gap between rank 1 and the actually observed rank 50
301
+ ≥ 0.061, and IDF-weighted coverage of all normalized query terms within a single candidate
302
+ ≥ 0.291. The calibrated evidence pool is the fixed union of the first 50 raw hits from each of
303
+ the dense and sparse retrieval branches. Residually invalid candidates are removed but never
304
+ replaced by deeper ranks. Symbol scrolling does not change IDF, and a higher `candidateLimit`
305
+ only enlarges the ranking pool. Thus the absence decision stays identical at 50, 80, 100, 150,
306
+ and 200 candidates. Both newly calibrated thresholds lie between measured counterexamples:
307
+ the gap between the reconciliation truth (0.06686) and clearly absent blockchain configuration
308
+ (0.05556); coverage between the configuration-array truth (0.29827) and clearly absent blockchain
309
+ transaction question (0.28482). The conjunction matters: the latter negative question itself
310
+ has a 0.06908 gap but fails coverage. In the measurement, this rule retained 22/22 main truths
311
+ plus seven separate German-prose truths and rejected 11/14 adversarial negative questions.
312
+ `NO_SUFFICIENT_MATCH` contains every measurement and threshold; each rejected hit appears in
313
+ the Skip Ledger. If fewer than 50 dense ranks were observed, the sparse retrieval branch is
314
+ missing, or the corpus binding mismatches, **no** absence decision is made:
315
+ `RELEVANCE_POLICY_NOT_EVALUATED` visibly names actual and expected values and leaves hits
316
+ unchanged. A search target whose declared `embeddingProfileId` differs from its embedder's
317
+ actual profile ID is rejected during wiring. Significant revisions of the same corpus require
318
+ new measurement and calibration.
319
+
320
+ 17. **German natural-language questions retain only a densely corroborated local BM25 rank.**
321
+ Diagnostics from `benchmark/diagnose.mjs` show the measured conflict: for d3/d4/d6 the gold hit
322
+ ranks 3/10/3 in dense results, but is unranked or ranks 71/70 in local BM25. In hybrid mode, the
323
+ lexical branch therefore contributes exactly when its top candidate also appears in the dense
324
+ top 10. This retains the useful BM25 signal for d1 and the counterprobe asking where the
325
+ measured baseline is documented, while excluding the conflicting d3/d4/d6 branches. Sparse
326
+ **retrieval** always stays active; only an unconfirmed rank is excluded from fusion. An explicit
327
+ `mode: "lexical"` is unchanged. Narrow question detection also covers German phrasings meaning
328
+ “In which file”, “What is/happens”, “When”, “Who”, and “What for”; BM25 removes a small documented
329
+ set of German and English question function words.
330
+
331
+ 18. **An exact-character symbol query expresses precision intent within the RRF base.** In this
332
+ case, the symbol branch contains only exact-character matches and receives enough computed
333
+ weight to stay ahead of non-exact candidates even between the two permitted boost-clamp
334
+ extremes. `exact_symbol_boost` and `MAX_BOOST_SHARE` are neither bypassed nor increased. This
335
+ intent applies in `hybrid` and `symbol`. Residual filters run first, so an excluded exact
336
+ candidate affects neither intent, weight, nor pool IDF, and each exclusion receives exactly one
337
+ ledger entry. Equivalent definitions are ordered by visible provenance (repository, ref, path,
338
+ line), and only finally by opaque point ID. Effective branch weights appear in score evidence.
339
+
340
+ <a id="testbarkeit-ohne-qdrant"></a>
341
+
342
+ ## Testability without Qdrant
343
+
344
+ `src/testing/in-memory-index.ts` builds a complete `InMemoryVectorStore` (testkit) from simple fixture
345
+ rows, embeds it with the deterministic embedder, and returns a fully wired `HybridSearchEngine`.
346
+ `src/testing/search-port.contract.ts` contains the port contract suite (`runSearchPortContract`);
347
+ `search-port.contract.self-test.test.ts` runs it against the in-memory reference.
348
+
349
+ **No test in this package touches 127.0.0.1:6333.** An unrelated Qdrant instance runs there and must
350
+ remain untouched under spec.md §3.2.
351
+
352
+ <a id="bekannte-grenzen"></a>
353
+
354
+ ## Known limits
355
+
356
+ Named rather than hidden, following the same rule as the Skip Ledger:
357
+
358
+ - **BM25 IDF applies to the candidate pool**, not the entire corpus. That suffices for ordering
359
+ *within* the pool, and fusion uses only ordering; absolute `lexicalScore` values are not comparable
360
+ between queries with different pools.
361
+ - **An absence statement requires a calibrated profile and corpus policy.** Without a matching
362
+ `relevancePolicies` entry, hits are deliberately not suppressed automatically. This prevents raw
363
+ values specific to Qwen and Untangle from being applied to another embedder or project. The three
364
+ compositional negative diagnoses still accepted are also named: vector search demonstrates
365
+ thematic similarity but does not reliably establish the absence of an asserted relationship
366
+ between two genuinely present sub-concepts.
367
+ - **Without `SparseVectorizer`, sparse retrieval is missing.** A hit that only BM25 would have found
368
+ never enters the hybrid pool. Warning: `SPARSE_RECALL_UNAVAILABLE`; pure lexical search reports an
369
+ error. In this state, a configured absence policy explicitly is **not** evaluated for non-symbol
370
+ questions (`RELEVANCE_POLICY_NOT_EVALUATED`), because its calibrated evidence pool is incomplete.
371
+ - **Context lines in grep mode stop at chunk boundaries.** Neighboring lines reside in another
372
+ point and would have to be loaded separately.
373
+ - **No regex timeout; patterns are checked in advance instead.** A time limit would not help:
374
+ evaluating a regex is ONE synchronous call; while it runs, no timer fires and no `AbortSignal` is
375
+ checked. Measurement: a chunk containing a 31-character line and pattern `(a+)+$` blocked `grep()`
376
+ for more than 88 seconds despite `maxScannedChunks` being 1 and cancellation after 200 ms.
377
+ `assertSafeGrepPattern` (in `regex-safety.ts`) therefore rejects dangerous patterns BEFORE the
378
+ first storage access, deterministically, without a clock or thread. It checks **ambiguity under
379
+ an unbounded repetition**, no longer star height: `R+` is dangerous if `R` can match empty
380
+ (`(a*)*`), two alternatives within `R` can match the same word (`(a|a)+`, `(\s|\t)+`), or `R` can
381
+ consume unbounded characters at its boundary that could also begin or end `R` (`(a+)+`,
382
+ `(\w+\s?)+`, `(\w+a)+`). The criterion is more precise in both directions than the old star-height
383
+ check: `(\s|\t)+$`, a search for trailing whitespace, previously ran indefinitely and is now
384
+ rejected, while `(a+b)+` and `(\w+\.)+` were previously rejected and now run (the final separator
385
+ makes decomposition unambiguous).
386
+ **Limit:** this remains a decidable approximation of an undecidable problem, deliberately biased
387
+ toward rejection. Backreferences under repetition, `\p{…}` treated as “any character”, and
388
+ alternatives of different fixed lengths sharing a prefix (`(a|ab)+`) are rejected even when
389
+ harmless in a particular case. Only the EXPONENTIAL family is covered; an accepted pattern may
390
+ still be polynomially expensive (`\w+\d+`). Full protection would need a dedicated matcher
391
+ without backtracking or evaluation in a killable process. Neither is built, deliberately.
392
+ Cancellation is now also checked per chunk (previously only per scroll page).
393
+ - **`includeContext` and `includeLinked` are accepted but NOT executed** (step 11 of the spec §13.2
394
+ pipeline is missing). Results now disclose this through `GRAPH_CONTEXT_UNAVAILABLE` and
395
+ `LINKED_ARTIFACTS_UNAVAILABLE`. `timings` remains undefined: measuring each phase would sacrifice
396
+ byte-for-byte result repeatability, which takes priority here.
397
+ - **Missing payload fields are substituted and NAMED.** `readCodeChunkPayload` returns
398
+ `substitutedFields`, hits carry them as `incompletePayloadFields`, and results warn once with
399
+ `INCOMPLETE_PAYLOAD`. A stored range ending before its start is rejected rather than adjusted.
400
+ - **`candidateLimit` is capped at `searchConfig.maxCandidateLimit`** (warning
401
+ `CANDIDATE_LIMIT_CLAMPED`); the returned `query` shows the value actually used.
402
+ - **No MMR based on content similarity** (see decision 12).
403
+ - **`graph_proximity_boost` and the Findability filter need injected lookups.** Without them, the
404
+ component is omitted or the filter unapplied. The latter produces
405
+ `FINDABILITY_FILTER_UNAVAILABLE`, never silent omission.
406
+ - **`measureFindability` cannot determine hops without a symbol graph.** In that case, `hops.hops`
407
+ is `null` **with** `hopsMissingReason`: the missing value is visible, not omitted.
408
+ - **`measureFindability` returns TWO possible outcomes** (`FindabilityOutcomeV1`, vision §1d): a
409
+ `FindabilityV1` measurement if search ran; otherwise a `FindabilityUnavailableV1` status record.
410
+ The record carries neither score nor diagnosis: a failed search is a finding about us, not the
411
+ code. Failure is also detected when the engine exposes it only as a warning rather than throwing,
412
+ per spec §25.2 (`detectRetrievalFailure`); `findabilitySkipEntry` puts the record in the Skip Ledger
413
+ (E4).
414
+ - **`diagnose` no longer accepts loose probes**, only a `FindabilityDiagnosisBasisV1` from
415
+ `openFindabilityDiagnosisBasis`, which does not exist for a failed probe.
416
+ - **Rank normalization across profile groups is not implemented** (arch §18.3 describes it as
417
+ opt-in). `rankNormalized` is always `false`; groups remain separate.
418
+
419
+ <a id="lizenz"></a>
420
+
421
+ ## License
422
+
423
+
424
+ **AGPL-3.0-only.** This package includes the complete license in [`LICENSE`](./LICENSE), the
425
+ license-choice rationale in [`NOTICE`](./NOTICE), and licenses for all third-party production
426
+ dependencies in [`THIRD_PARTY_LICENSES.md`](./THIRD_PARTY_LICENSES.md). All three ship in the npm
427
+ tarball: AGPL-3.0-only §4 and §5 require recipients to receive the license; a README alone is not
428
+ a copy of the license.
429
+
430
+ Local use alone does not trigger an obligation. Copyleft obligations apply to redistribution and
431
+ providing a network service (AGPL §13).
432
+
433
+ Corresponding TypeScript source ships under `src/` in the same tarball; the complete repository
434
+ is available at <https://github.com/lboel/untangle>.
@@ -0,0 +1,29 @@
1
+ # Third-party licenses — @untangle-code/search
2
+
3
+ Generated by `pnpm third-party-licenses` from this package's installed
4
+ production dependency tree: transitive `dependencies`, across
5
+ workspace edges, with no `devDependencies` included. The generator reads
6
+ each resolved package's `package.json` in `node_modules`; nothing is maintained by hand
7
+ or estimated.
8
+
9
+ Do not edit manually: `pnpm third-party-licenses --check` fails on drift,
10
+ and `pnpm check-publishable` rejects packages without a current file.
11
+
12
+ This file exists because `docs/BUILD_PLAN.md`, risk R6, makes
13
+ per-package `THIRD_PARTY_LICENSES.md` a release gate for npm publication.
14
+
15
+ Untangle itself is **AGPL-3.0-only** (see `LICENSE` and `NOTICE` in
16
+ this package). The packages below are third-party works; their own
17
+ licenses and copyright notices continue to apply unchanged. Workspace
18
+ packages (`@untangle-code/*`) are not listed here; they belong to the same
19
+ work and use the same AGPL-3.0-only license.
20
+
21
+ ## Production dependencies (transitive, 1)
22
+
23
+ | Package | Version | License | Project |
24
+ | --- | --- | --- | --- |
25
+ | `smol-toml` | 1.8.0 | BSD-3-Clause | `github:squirrelchat/smol-toml` |
26
+
27
+ The allowlist (MIT / Apache-2.0 / BSD / ISC / …) is checked by
28
+ `scripts/check-licenses.mjs` against this exact tree on every run;
29
+ the GPL family is excluded (architecture.md §2.4, BUILD_PLAN R6).
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Harte Ehrlichkeitsgrenze fuer den Live-Benchmark.
3
+ *
4
+ * Der Search-Port degradiert ausgefallene Ziele absichtlich zu Warnungen,
5
+ * damit eine interaktive Suche andere Profile weiter anzeigen kann. Ein
6
+ * Benchmark darf diese Semantik nicht als Messwert missverstehen: ohne
7
+ * ausgefuehrten Abruf sind null Treffer weder Akzeptanz noch Abstention.
8
+ */
9
+ import type { SearchResultSetV1 } from "@untangle-code/domain";
10
+ /** Wirft, bevor ein Benchmark eine nicht ausgefuehrte Suche serialisieren kann. */
11
+ export declare function assertBenchmarkRetrievalCompleted(questionId: string, result: SearchResultSetV1): void;
12
+ //# sourceMappingURL=benchmark-integrity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmark-integrity.d.ts","sourceRoot":"","sources":["../src/benchmark-integrity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAI/D,mFAAmF;AACnF,wBAAgB,iCAAiC,CAC/C,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,iBAAiB,GACxB,IAAI,CAQN"}
@@ -0,0 +1,10 @@
1
+ import { detectRetrievalFailure } from "./findability.js";
2
+ /** Wirft, bevor ein Benchmark eine nicht ausgefuehrte Suche serialisieren kann. */
3
+ export function assertBenchmarkRetrievalCompleted(questionId, result) {
4
+ const failure = detectRetrievalFailure(result);
5
+ if (failure === undefined) {
6
+ return;
7
+ }
8
+ throw new Error(`benchmark row "${questionId}" is invalid because retrieval did not complete (${failure.reason}): ${failure.detail}`);
9
+ }
10
+ //# sourceMappingURL=benchmark-integrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"benchmark-integrity.js","sourceRoot":"","sources":["../src/benchmark-integrity.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,mFAAmF;AACnF,MAAM,UAAU,iCAAiC,CAC/C,UAAkB,EAClB,MAAyB;IAEzB,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO;IACT,CAAC;IACD,MAAM,IAAI,KAAK,CACb,kBAAkB,UAAU,oDAAoD,OAAO,CAAC,MAAM,MAAM,OAAO,CAAC,MAAM,EAAE,CACrH,CAAC;AACJ,CAAC"}
package/dist/bm25.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ /** Saettigungskonstante der Termhaeufigkeit (Standardwert). */
2
+ export declare const BM25_K1 = 1.2;
3
+ /** Laengennormalisierung (Standardwert). */
4
+ export declare const BM25_B = 0.75;
5
+ /** Ein Dokument des lexikalischen Index. */
6
+ export interface LexicalDocument {
7
+ id: string;
8
+ text: string;
9
+ }
10
+ /** Ein Rangeintrag des lexikalischen Zweigs. */
11
+ export interface LexicalRanking {
12
+ id: string;
13
+ score: number;
14
+ /** 1-basierter Rang. */
15
+ rank: number;
16
+ }
17
+ /**
18
+ * BM25 ueber eine feste Dokumentmenge. Die Statistiken werden EINMAL im
19
+ * Konstruktor gebildet; danach ist `score()` eine reine Funktion.
20
+ */
21
+ export declare class Bm25Ranker {
22
+ private readonly k1;
23
+ private readonly b;
24
+ private readonly documents;
25
+ private readonly documentFrequency;
26
+ private readonly averageLength;
27
+ constructor(documents: readonly LexicalDocument[], k1?: number, b?: number);
28
+ get documentCount(): number;
29
+ /**
30
+ * Inverse Dokumenthaeufigkeit in der geglaetteten Form
31
+ * `ln(1 + (N - df + 0.5) / (df + 0.5))` — immer positiv, damit ein in jedem
32
+ * Dokument vorkommender Term den Score nicht negativ macht.
33
+ */
34
+ private inverseDocumentFrequency;
35
+ private scoreDocument;
36
+ /**
37
+ * Rangliste zu einem Suchtext. Dokumente mit Score 0 (kein Term getroffen)
38
+ * fallen heraus — sie haben lexikalisch nichts beigetragen und duerfen die
39
+ * Fusion nicht mit einem Scheinrang beeinflussen.
40
+ *
41
+ * Gleichstand wird deterministisch nach Codepoints der ID aufgeloest, nie
42
+ * nach Einfuegereihenfolge und nie ueber `localeCompare`.
43
+ */
44
+ rank(queryText: string): LexicalRanking[];
45
+ }
46
+ //# sourceMappingURL=bm25.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bm25.d.ts","sourceRoot":"","sources":["../src/bm25.ts"],"names":[],"mappings":"AA8BA,+DAA+D;AAC/D,eAAO,MAAM,OAAO,MAAM,CAAC;AAC3B,4CAA4C;AAC5C,eAAO,MAAM,MAAM,OAAO,CAAC;AAE3B,4CAA4C;AAC5C,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAQD;;;GAGG;AACH,qBAAa,UAAU;IAOnB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,CAAC;IAPpB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAGrC,SAAS,EAAE,SAAS,eAAe,EAAE,EACpB,EAAE,GAAE,MAAgB,EACpB,CAAC,GAAE,MAAe;IAerC,IAAW,aAAa,IAAI,MAAM,CAEjC;IAED;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAMhC,OAAO,CAAC,aAAa;IAerB;;;;;;;OAOG;IACI,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,EAAE;CAcjD"}