klypix-mcp 1.95.0 → 1.96.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.
- package/README.md +9 -0
- package/bin/klypix-worker.mjs +1 -1
- package/package.json +2 -2
- package/src/klypix-core.mjs +8 -5
- package/src/klypix-format.mjs +43 -2
package/README.md
CHANGED
|
@@ -1143,6 +1143,15 @@ independently validated.
|
|
|
1143
1143
|
0.43, top-1 33%. The stratum that matters most is the honest one: **paraphrase questions that share
|
|
1144
1144
|
no words with their card reach recall@5 44%**; status, temporal and multi-hop questions sit at
|
|
1145
1145
|
81–93% because they still share vocabulary. Lexical-only scores 0% on the same set.
|
|
1146
|
+
- **The caller's words (`brain_ask`, 1.96).** A question can end with one more line,
|
|
1147
|
+
`Also: <words a card answering it would likely use>`; the calling agent is itself a model, so it
|
|
1148
|
+
can guess the card's wording at no extra cost. The words join the meaning query only. Measured
|
|
1149
|
+
2026-10-11 on the same brain grown to 3,829 cards (102 of the frozen questions still answerable),
|
|
1150
|
+
with words written by Gemini 2.5 Flash as a stand-in for the calling agent: recall@5 54% → 59%
|
|
1151
|
+
(8 found, 3 lost), recall@8 61% → 68%, recall@20 68% → 76%, MRR 0.375 → 0.424. With no local
|
|
1152
|
+
model (a fresh install, the Claude plugin) the words count as extra keywords at half weight:
|
|
1153
|
+
recall@5 19% → 24% (5 found, none lost), recall@10 25% → 31%. Without the line, ranking is
|
|
1154
|
+
unchanged byte for byte.
|
|
1146
1155
|
- **Second brain.** The same ranker on a different project's brain (1,601 cards, 19 hand-written
|
|
1147
1156
|
paraphrases, first cross-brain run): recall@5 **42%** (CI 23–64), top-1 11%, MRR 0.25. Paraphrase
|
|
1148
1157
|
recall transfers between brains; the ranker is not tuned to the brain it was built on. It is
|
package/bin/klypix-worker.mjs
CHANGED
|
@@ -508,7 +508,7 @@ server.registerTool('search_all_brains', {
|
|
|
508
508
|
|
|
509
509
|
server.registerTool('brain_ask', {
|
|
510
510
|
title: 'Ask the project brain a question (whole-brain, correction-aware answer)',
|
|
511
|
-
description: 'Answer a natural-language question from the WHOLE project brain — "what did we decide about X?", "where did the auth work land?", "why did we drop Y?". Ranks every card lexically, blended with on-device semantic similarity ONLY when the optional local model is installed (a fresh install is lexical; it degrades cleanly). INCLUDES superseded/archived history (flagged, so you can see how a decision changed), and attaches each stale card\'s live CORRECTION so the answer reflects the current truth, not an outdated card. Returns a synthesis-ready context (full cards + provenance + lifecycle) for you to turn into a direct, cited answer — it does not itself write prose. Prefer this over search_canvases when the user asks a QUESTION (not a keyword lookup). Optional as_of (YYYY-MM-DD) answers "what was true then". Defaults to the project brain ("brain").',
|
|
511
|
+
description: 'Answer a natural-language question from the WHOLE project brain — "what did we decide about X?", "where did the auth work land?", "why did we drop Y?". Ranks every card lexically, blended with on-device semantic similarity ONLY when the optional local model is installed (a fresh install is lexical; it degrades cleanly). INCLUDES superseded/archived history (flagged, so you can see how a decision changed), and attaches each stale card\'s live CORRECTION so the answer reflects the current truth, not an outdated card. Returns a synthesis-ready context (full cards + provenance + lifecycle) for you to turn into a direct, cited answer — it does not itself write prose. Prefer this over search_canvases when the user asks a QUESTION (not a keyword lookup). Optional as_of (YYYY-MM-DD) answers "what was true then". Cards are often worded differently from the question, so end the question with one more line, `Also: <8–14 words a card answering it would likely use: synonyms, feature, file or tool names>`; they widen the search while the question still leads (measured: right card in the top 5 for 60 of 102 test questions instead of 55; 24 instead of 19 when no local model is installed). Defaults to the project brain ("brain").',
|
|
512
512
|
inputSchema: {
|
|
513
513
|
question: z.string().describe('The natural-language question to answer from the brain.'),
|
|
514
514
|
canvas: z.string().optional().describe('Brain canvas filename/path. Defaults to the project brain ("brain").'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "klypix-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.96.0",
|
|
4
4
|
"mcpName": "io.github.dahshanlabs/klypix-mcp",
|
|
5
5
|
"description": "Active state management for multi-agent coding: a shared, versioned project brain over MCP.",
|
|
6
6
|
"type": "module",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"bench": "node bin/klypix-mcp.mjs bench",
|
|
87
87
|
"test:bench": "node test/bench.mjs",
|
|
88
88
|
"pretest": "node test/publish-workflow.mjs",
|
|
89
|
-
"test": "node test/publish-verdict.mjs && node test/npx-owned-names.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/session-mailbox.mjs && node test/session-reopen.mjs && node test/lane-write-retry.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/install-rename-backoff.mjs && node test/project-binding-rebind.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/capture-gap.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/orphan-gardener.mjs && node test/brief-and-recall.mjs && node test/guard-cards.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/retrieval-fusion.mjs && node test/eval-retrieval.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/partial-notes.mjs && node test/lifecycle-prefix.mjs && node test/close-link-safety.mjs && node test/resolve-ledger.mjs && node test/plan-fulfillment.mjs && node test/arrange-receipts.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/semantic-hash-parity.mjs && node test/enrichment.mjs && node test/provenance.mjs && node test/confirm-trail.mjs && node test/hook-fallback.mjs && node test/eval-hook-lane.mjs && node test/hook-unified-lane.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/marker-suffix-grammar.mjs && node test/evidence-anchors.mjs && node test/brain-evidence.mjs && node test/presence-visibility.mjs && node test/undeclared-active.mjs && node test/presence-liveness.mjs && node test/observed-scope.mjs && node test/release-lease.mjs && node test/release-reconcile.mjs && node test/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.mjs && node test/completion-guard.mjs && node test/merge-brains.mjs && node test/merge-readings.mjs && node test/revival-map.mjs && node test/merge-scale.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/one-command-setup.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/canvas-groups.mjs && node test/git-tools.mjs && node test/link-compat.mjs && node test/uninstall.mjs && node test/current-guidance.mjs && node test/status-shape.mjs && node test/status-hook.mjs && node test/read-canvas-saved-readings.mjs && node test/document-readings.mjs && node test/open-lease.mjs && node test/agent-card-style.mjs && node test/tool-schema-freeze.mjs && node test/app-lease-vectors.mjs && node test/app-tools-stdio.mjs && node test/card-contents-files.mjs && node test/codex-app-table.mjs && node test/plugin-mode.mjs && node test/app-bridge-vectors.mjs && node test/app-bridge-client.mjs && node test/app-tools-static.mjs && node test/app-tools-stdio-live.mjs",
|
|
89
|
+
"test": "node test/publish-verdict.mjs && node test/npx-owned-names.mjs && node test/project-graph.mjs && node test/project-map-cli.mjs && node test/mcp-auto-update.mjs && node test/mcp-supervisor.mjs && node test/runtime-inspector.mjs && node test/codex-hooks.mjs && node test/request-identity.mjs && node test/session-identity-core.mjs && node test/agent-presence.mjs && node test/message-delivery-v3.mjs && node test/claude-message-delivery-v3.mjs && node test/session-mailbox.mjs && node test/session-reopen.mjs && node test/lane-write-retry.mjs && node test/result-reconcile.mjs && node test/evidence-publication-gate.mjs && node test/release-evidence-cli.mjs && node test/intent-guard.mjs && node test/git-capture-install.mjs && node test/brain-history.mjs && node test/brain-graveyard.mjs && node test/archived-visibility.mjs && node test/finding-routing.mjs && node test/finding-routing-hook.mjs && node test/presence-relay.mjs && node test/install-version.mjs && node test/install-rename-backoff.mjs && node test/project-binding-rebind.mjs && node test/context-gateway.mjs && node test/repo-state.mjs && node test/released-tag-guard.mjs && node test/conformance.mjs && node test/brain-doctor.mjs && node test/version-currency.mjs && node test/ship-capture.mjs && node test/capture-gap.mjs && node test/lane-message.mjs && node test/brain-quality.mjs && node test/brain-connect-orphans.mjs && node test/orphan-gardener.mjs && node test/brief-and-recall.mjs && node test/guard-cards.mjs && node test/layout-cluster.mjs && node test/brain-ask.mjs && node test/brain-ask-words.mjs && node test/retrieval-fusion.mjs && node test/eval-retrieval.mjs && node test/field-report-2026-07-04.mjs && node test/autoprop.mjs && node test/overlay-recency-2026-07-12.mjs && node test/brain-challenge.mjs && node test/brain-lens.mjs && node test/brain-kind.mjs && node test/rule-drafts.mjs && node test/claim-engine.mjs && node test/partial-notes.mjs && node test/lifecycle-prefix.mjs && node test/close-link-safety.mjs && node test/resolve-ledger.mjs && node test/plan-fulfillment.mjs && node test/arrange-receipts.mjs && node test/skill-staleness.mjs && node test/canvas-view.mjs && node test/status-completeness.mjs && node test/semantic-security.mjs && node test/semantic-gate.mjs && node test/memory-runtime.mjs && node test/semantic-cache.mjs && node test/semantic-hash-parity.mjs && node test/enrichment.mjs && node test/provenance.mjs && node test/confirm-trail.mjs && node test/hook-fallback.mjs && node test/eval-hook-lane.mjs && node test/hook-unified-lane.mjs && node test/decay-status.mjs && node test/decay-hook.mjs && node test/marker-suffix-grammar.mjs && node test/evidence-anchors.mjs && node test/brain-evidence.mjs && node test/presence-visibility.mjs && node test/undeclared-active.mjs && node test/presence-liveness.mjs && node test/observed-scope.mjs && node test/release-lease.mjs && node test/release-reconcile.mjs && node test/release-ancestry.mjs && node test/release-claim-join.mjs && node test/release-claims.mjs && node test/release-handshake.mjs && node test/completion-guard.mjs && node test/merge-brains.mjs && node test/merge-readings.mjs && node test/revival-map.mjs && node test/merge-scale.mjs && node test/concurrent-writes.mjs && node test/lock-interop.mjs && node test/capture-write-failure.mjs && node test/a2a-smoke.mjs && node test/one-command-setup.mjs && node test/cli-args.mjs && node test/format-guard.mjs && node test/canvas-groups.mjs && node test/git-tools.mjs && node test/link-compat.mjs && node test/uninstall.mjs && node test/current-guidance.mjs && node test/status-shape.mjs && node test/status-hook.mjs && node test/read-canvas-saved-readings.mjs && node test/document-readings.mjs && node test/open-lease.mjs && node test/agent-card-style.mjs && node test/tool-schema-freeze.mjs && node test/app-lease-vectors.mjs && node test/app-tools-stdio.mjs && node test/card-contents-files.mjs && node test/codex-app-table.mjs && node test/plugin-mode.mjs && node test/app-bridge-vectors.mjs && node test/app-bridge-client.mjs && node test/app-tools-static.mjs && node test/app-tools-stdio-live.mjs",
|
|
90
90
|
"test:memory": "node test/memory-runtime.mjs",
|
|
91
91
|
"test:memory:soak": "node --expose-gc test/memory-soak.mjs",
|
|
92
92
|
"runtime": "node bin/klypix-runtime.mjs",
|
package/src/klypix-core.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
findUnrecordedMigrations, captureIntoBrain, tidyBrain, noteToCaptureInput,
|
|
31
31
|
selectGardenCandidates, applyGarden, detectContradictions,
|
|
32
32
|
findDuplicatePartialNotes, collapseDuplicatePartialNotes,
|
|
33
|
-
rankForQuestion, questionContextToMarkdown, findLegacyShipCards,
|
|
33
|
+
rankForQuestion, splitAskWords, questionContextToMarkdown, findLegacyShipCards,
|
|
34
34
|
challengeBrain, challengeContextToMarkdown, buildRenderSpec, structToBrief,
|
|
35
35
|
brainLensData, lensToMarkdown, deathDateOfCard,
|
|
36
36
|
statusContextToMarkdown, findFulfillmentCandidates, findStaleOpenCards, releaseFulfilledOpens, openStatusSummary, perAreaTableToMarkdown,
|
|
@@ -766,7 +766,9 @@ export async function opBrainTaskContext({
|
|
|
766
766
|
}
|
|
767
767
|
|
|
768
768
|
export async function opBrainAsk({ vault, canvas, question, as_of, k = 10, log = () => {} }) {
|
|
769
|
-
|
|
769
|
+
// A last line `Also: <words>` is the caller's guess at the card's wording; it
|
|
770
|
+
// joins the meaning query only (splitAskWords in klypix-format.mjs).
|
|
771
|
+
const { question: q, words } = splitAskWords(question);
|
|
770
772
|
if (!q) return err('brain_ask needs a question.');
|
|
771
773
|
const t = brainTarget(vault, canvas);
|
|
772
774
|
if (t.ambiguous) return ambiguousBrainErr(t.ambiguous);
|
|
@@ -783,9 +785,9 @@ export async function opBrainAsk({ vault, canvas, question, as_of, k = 10, log =
|
|
|
783
785
|
try {
|
|
784
786
|
const pipe = await getEmbedderForUse(log, 20_000);
|
|
785
787
|
if (pipe) {
|
|
786
|
-
const [qv] = await embedTexts(pipe, [q], { kind: 'query' });
|
|
788
|
+
const [qv] = await embedTexts(pipe, [words ? `${q}\n${words}` : q], { kind: 'query' });
|
|
787
789
|
const vecs = await vectorsForBrain(pipe, t.file, struct.cards);
|
|
788
|
-
if (qv && vecs && vecs.size) { semantic = new Map(); for (const [id, v] of vecs) semantic.set(id, dot(qv, v)); cardVecs = vecs; mode =
|
|
790
|
+
if (qv && vecs && vecs.size) { semantic = new Map(); for (const [id, v] of vecs) semantic.set(id, dot(qv, v)); cardVecs = vecs; mode = `semantic+lexical (on-device)${words ? ' + your Also words' : ''}`; }
|
|
789
791
|
}
|
|
790
792
|
} catch { semantic = null; cardVecs = null; }
|
|
791
793
|
// Parity with search_all_brains (8445e9c): never say "warming — retry" to a
|
|
@@ -799,6 +801,7 @@ export async function opBrainAsk({ vault, canvas, question, as_of, k = 10, log =
|
|
|
799
801
|
if (!semantic) {
|
|
800
802
|
fallbackNotice = semanticFallbackNotice(false);
|
|
801
803
|
if (fallbackNotice) mode = fallbackNotice.startsWith('LEXICAL ONLY') ? 'lexical-only (no semantic runtime)' : 'lexical (semantic warming)';
|
|
804
|
+
if (words) mode += ' + your Also words';
|
|
802
805
|
}
|
|
803
806
|
const kk = Math.max(1, Math.min(20, k || 10));
|
|
804
807
|
const timeTravel = asOfTs != null;
|
|
@@ -811,7 +814,7 @@ export async function opBrainAsk({ vault, canvas, question, as_of, k = 10, log =
|
|
|
811
814
|
// vectors that rank the question, reused; null degrades pairing to its
|
|
812
815
|
// lexical anchor/coverage fallback (works on every host, embeddings or not).
|
|
813
816
|
const pairSim = cardVecs ? (a, b) => { const va = cardVecs.get(a), vb = cardVecs.get(b); return va && vb ? dot(va, vb) : null; } : null;
|
|
814
|
-
const result = rankForQuestion(struct, q, { semantic, k: wantRerank ? Math.max(kk, 50) : kk, as_of: timeTravel ? as_of : null, pairSim });
|
|
817
|
+
const result = rankForQuestion(struct, q, { semantic, k: wantRerank ? Math.max(kk, 50) : kk, as_of: timeTravel ? as_of : null, pairSim, words });
|
|
815
818
|
if (wantRerank && result.hits.length > 1) {
|
|
816
819
|
try {
|
|
817
820
|
const reranked = await withRerankerForUse(log, 8_000, rr => rerankHits(rr, q, result.hits));
|
package/src/klypix-format.mjs
CHANGED
|
@@ -4042,14 +4042,51 @@ export const deathDateOfCard = (card) => {
|
|
|
4042
4042
|
const m = /(?:↩︎ superseded|↩ superseded|✅|⤵ consolidated) (\d{4}-\d{2}-\d{2})/.exec(String(c.text || ''));
|
|
4043
4043
|
return m ? Date.parse(m[1]) : null;
|
|
4044
4044
|
};
|
|
4045
|
-
|
|
4045
|
+
// THE CALLER'S WORDS (1.96.0). A question is worded the asker's way; the card
|
|
4046
|
+
// that answers it was worded the day it was written ("magnification controls"
|
|
4047
|
+
// vs a card that says "zoom"). The calling agent is itself a model, so brain_ask
|
|
4048
|
+
// invites it to add one last line `Also: <words a card answering this would
|
|
4049
|
+
// likely use>`. Those words join the MEANING query only (embedded together with
|
|
4050
|
+
// the question, one vector); the keyword anchor test, status shape and the
|
|
4051
|
+
// rendered question all stay the question's own. Measured 2026-10-11 on the
|
|
4052
|
+
// real 3,829-card brain, frozen v2 set (102 answerable), production embedder +
|
|
4053
|
+
// warm enrichment-aware vectors, model-written words: recall@5 55 → 60
|
|
4054
|
+
// (+8 −3), @8 62 → 69, @20 69 → 78, MRR .375 → .424. Fusing a separate words
|
|
4055
|
+
// vector by rank instead did less (+5 −2 at equal weight). Carried inside
|
|
4056
|
+
// `question` because no existing tool's input schema may change (see
|
|
4057
|
+
// test/tool-schema-freeze.mjs) — a new property would force every host to
|
|
4058
|
+
// reconnect; a line in the text works the moment the worker hot-swaps.
|
|
4059
|
+
export const ASK_WORDS_MAX = 240;
|
|
4060
|
+
// Keyword-only mode (no meaning model), same brain and set, words as extra
|
|
4061
|
+
// tokens, through splitAskWords: recall@5 19 → 24 of 102 at half weight with
|
|
4062
|
+
// none lost (@10 26 → 32); full weight lost a question the question alone
|
|
4063
|
+
// found. Measured 2026-10-11.
|
|
4064
|
+
export const ASK_WORDS_LEXICAL_WEIGHT = 0.5;
|
|
4065
|
+
export function splitAskWords(question) {
|
|
4066
|
+
const whole = String(question || '').trim();
|
|
4067
|
+
const lines = whole.split(/\r?\n/);
|
|
4068
|
+
const last = lines.length > 1 ? /^\s*also\s*:\s*(.*)$/i.exec(lines[lines.length - 1]) : null;
|
|
4069
|
+
const rest = last ? lines.slice(0, -1).join('\n').trim() : '';
|
|
4070
|
+
// No words line, or nothing above it: the text is the question, untouched.
|
|
4071
|
+
if (!last || !rest) return { question: whole, words: '' };
|
|
4072
|
+
const words = last[1].replace(/\s+/g, ' ').trim().slice(0, ASK_WORDS_MAX).trim();
|
|
4073
|
+
// An empty `Also:` line is dropped; words equal to the question add nothing.
|
|
4074
|
+
return { question: rest, words: words.toLowerCase() === rest.toLowerCase() ? '' : words };
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
export function rankForQuestion(struct, question, { semantic = null, k = 10, as_of = null, now = Date.now(), recentDays = 30, pairSim = null, words = '' } = {}) {
|
|
4046
4078
|
// Status-shaped questions score by their CONTENT tokens only — "remaining"
|
|
4047
4079
|
// must never lexically select the stale cards that say "remaining:".
|
|
4048
4080
|
const { content: tokens, statusShaped, strong: statusStrong, areaFamilies = [] } = splitQueryTokens(question);
|
|
4081
|
+
// The caller's Also words (splitAskWords). With the meaning model they ride
|
|
4082
|
+
// the query vector (opBrainAsk); WITHOUT it — a fresh install, the Claude
|
|
4083
|
+
// plugin — they are extra keyword tokens at ASK_WORDS_LEXICAL_WEIGHT, after
|
|
4084
|
+
// the question's own, so the question still leads.
|
|
4085
|
+
const extra = words && !semantic ? splitQueryTokens(words).content.filter(t => !tokens.includes(t)) : [];
|
|
4049
4086
|
// Area scope (1.85.0): this function HAS the struct, so it resolves the
|
|
4050
4087
|
// prompt's static families to exact area titles for the status renderer.
|
|
4051
4088
|
const areas = statusStrong ? areaHintsFromPrompt(struct, question) : null;
|
|
4052
|
-
if (!struct || !Array.isArray(struct.cards) || (!tokens.length && !semantic)) return { hits: [], total: 0, tokens, statusShaped, statusStrong, areaFamilies, areas };
|
|
4089
|
+
if (!struct || !Array.isArray(struct.cards) || (!tokens.length && !extra.length && !semantic)) return { hits: [], total: 0, tokens, statusShaped, statusStrong, areaFamilies, areas };
|
|
4053
4090
|
const isArchived = (c) => /^archive$/i.test(c.area || '');
|
|
4054
4091
|
const asOfTs = as_of ? Date.parse(as_of) : null;
|
|
4055
4092
|
const timeTravel = asOfTs != null && Number.isFinite(asOfTs);
|
|
@@ -4087,6 +4124,10 @@ export function rankForQuestion(struct, question, { semantic = null, k = 10, as_
|
|
|
4087
4124
|
else if (wordMatches(tagWords, tagStems, tok)) lex += 3;
|
|
4088
4125
|
else if (wordMatches(bodyW, bodyStems, tok)) lex += lenNorm;
|
|
4089
4126
|
}
|
|
4127
|
+
for (const tok of extra) {
|
|
4128
|
+
if (wordMatches(titleW, titleStems, tok) || wordMatches(tagWords, tagStems, tok)) lex += 3 * ASK_WORDS_LEXICAL_WEIGHT;
|
|
4129
|
+
else if (wordMatches(bodyW, bodyStems, tok)) lex += lenNorm * ASK_WORDS_LEXICAL_WEIGHT;
|
|
4130
|
+
}
|
|
4090
4131
|
const sem = semantic ? (semantic.get(c.id) ?? null) : null;
|
|
4091
4132
|
if (!semantic && lex <= 0) continue;
|
|
4092
4133
|
if (semantic && lex <= 0 && sem == null) continue;
|