oscar64-mcp-docs 1.0.7 → 1.0.8

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 (2) hide show
  1. package/dist/stdio.js +148 -302
  2. package/package.json +1 -1
package/dist/stdio.js CHANGED
@@ -394,67 +394,30 @@ var toolErrorSchema = z.object({
394
394
  })
395
395
  ).optional().describe("Optional follow-up tool calls that can recover the workflow.")
396
396
  });
397
- var classificationEvidenceSchema = z.object({
398
- label: z.string().describe("Label supported by this evidence item."),
399
- facet: z.enum(["primary_track", "domain", "hardware", "technique", "abstraction", "artifact"]).describe("Facet channel that this evidence contributes to."),
400
- reason: z.string().describe("Human-readable explanation for why this rule matched."),
401
- weight: z.number().describe("Weighted contribution from this evidence match."),
402
- matched_on: z.string().describe("Stable rule identifier that produced the evidence."),
403
- source_field: z.enum(["title", "rel_path", "text", "files", "asset_refs", "derived"]).describe("Artifact field where the match was detected."),
404
- matched_text: z.string().describe("Concrete snippet/text token that matched the rule."),
405
- direct: z.boolean().describe("True when this is a direct artifact signal (API include/symbol) instead of a weak heuristic mention.")
406
- });
407
- var classificationDetailsSchema = z.object({
408
- primary_track: z.string().describe("Selected primary track after evidence gating and fallback handling."),
409
- primary_track_status: z.enum(["asserted", "neutral_fallback"]).describe("Whether the primary track is directly asserted or forced to a neutral fallback."),
410
- facets: z.object({
411
- domain: z.array(z.string()).describe("Functional domains covered by this result."),
412
- hardware: z.array(z.string()).describe("Hardware domains referenced by this result."),
413
- technique: z.array(z.string()).describe("Implementation techniques shown in this result."),
414
- abstraction: z.array(z.string()).describe("Abstraction styles represented in this result."),
415
- artifact: z.array(z.string()).describe("Artifact kinds used or produced in this result."),
416
- systems: z.array(systemFamilySchema).describe("Detected target systems for this result; empty means shared/common."),
417
- scope: z.enum(["tutorial", "sample", "manual"]).describe("Content scope of this result.")
418
- }),
419
- confidence: z.number().describe("Calibrated numeric confidence for the primary track decision."),
420
- confidence_bucket: z.enum(["low", "medium", "high"]).describe("Confidence band intended for policy decisions and quality monitoring."),
421
- evidence: z.array(classificationEvidenceSchema).describe("Inspectable evidence records used to derive primary track and facets.")
422
- });
423
397
  var classificationSummarySchema = z.object({
424
398
  track: z.string().describe("Primary track summary value for routing and filtering."),
425
399
  track_status: z.enum(["asserted", "neutral_fallback"]).describe("Whether the summary track was asserted or neutralized by fallback policy."),
426
- confidence_bucket: z.enum(["low", "medium", "high"]).describe("Coarse confidence bucket for this classification summary."),
427
- confidence: z.number().describe("Calibrated numeric confidence for summary consumers."),
428
- domains: z.array(z.string()).describe("Top-level functional domains admitted after evidence gating."),
429
- hardware: z.array(z.string()).describe("Relevant hardware domains admitted by evidence."),
430
- techniques: z.array(z.string()).describe("Top techniques surfaced for this result."),
431
- systems: z.array(systemFamilySchema).describe("Detected target systems for this result; empty means shared/common."),
432
- scope: z.enum(["tutorial", "sample", "manual"]).describe("Content scope of this result.")
400
+ confidence_bucket: z.enum(["low", "medium", "high"]).describe("Coarse confidence bucket for this classification summary.")
433
401
  });
434
402
  var searchPreviewSchema = z.object({
435
403
  summary: z.string().describe("Compact preview summary for quick relevance checks."),
436
404
  signature: z.string().optional().describe("Declaration-like line extracted from content when available."),
437
- include_path: z.string().optional().describe("Header include path context when relevant."),
438
- declaration_context: z.string().optional().describe("Nearby declaration context that helps disambiguate similar APIs.")
405
+ include_path: z.string().optional().describe("Header include path context when relevant.")
439
406
  });
440
407
  var searchHitSchema = z.object({
441
- source: z.enum(["manual", "code"]).describe("Where the result came from."),
442
408
  result_type: z.enum(["topics", "tutorials", "samples", "headers"]).describe("Artifact type for this result."),
443
409
  uri: z.string().describe("URI to pass into `read_uri` for full content."),
444
410
  title: z.string().describe("Short title for the matched result."),
445
411
  preview: searchPreviewSchema.describe("Structured preview fields for relevance evaluation."),
446
- score: z.number().describe("Relative relevance score; higher means better match."),
447
412
  referenced_files: z.array(z.string()).optional().describe("Referenced `code://...` URIs that can be read with `read_uri` (for example from #embed or #include)."),
448
- classification_summary: classificationSummarySchema.describe("Compact classification metadata always returned."),
449
- classification_details: classificationDetailsSchema.optional().describe("Detailed classification, included when `include_details=true`.")
413
+ classification_summary: classificationSummarySchema.describe("Compact classification metadata always returned.")
450
414
  });
451
415
  var searchInputSchema = z.object({
452
416
  query: z.string().min(1).describe("Query text, symbol, API name, or error phrase to search for."),
453
417
  limit: z.number().int().min(1).max(80).default(20).describe("Maximum number of results to return."),
454
418
  type: searchTypeSchema.default("all").describe("Filter results by artifact type. Defaults to `all`."),
455
- system: systemFilterSchema.default("c64").describe("Target system filter. Defaults to `c64`; use `all` for cross-system search."),
456
- include_details: z.boolean().default(false).describe("Set to true to include full classification details and evidence per hit.")
457
- });
419
+ system: systemFilterSchema.default("c64").describe("Target system filter. Defaults to `c64`; use `all` for cross-system search.")
420
+ }).strict();
458
421
  var searchDataSchema = z.object({
459
422
  results: z.array(searchHitSchema).describe("Ranked search hits.")
460
423
  });
@@ -1820,30 +1783,10 @@ var readUriTool = createTool2({
1820
1783
  // src/mcp/tools/search.tool.ts
1821
1784
  import { createTool as createTool3 } from "@mastra/core/tools";
1822
1785
  import path9 from "path";
1823
- var INTENT_SYNONYMS = {
1824
- screenmem: ["screen", "memory", "screen memory"],
1825
- charset: ["character set", "font", "d018"],
1826
- d018: ["charset", "screen memory", "vic"],
1827
- bank: ["banking", "vic_setbank", "memmap", "bank switch"],
1828
- banking: ["bank", "vic_setbank", "memmap"],
1829
- rasterirq: ["raster irq", "rirq", "interrupt"],
1830
- sprite: ["sprites", "spr_", "vic"],
1831
- sprites: ["sprite", "spr_", "vic"],
1832
- charwin: ["cwin", "window", "text window"],
1833
- joystick: ["joy", "input", "cia"],
1834
- memmap: ["memory map", "banking", "mmap_"],
1835
- vic: ["vic_ii", "d018", "screen memory"]
1836
- };
1837
1786
  function parseCodeUri(uri) {
1838
- if (uri.startsWith("code://oscar/")) {
1839
- return { scope: "oscar", relPath: uri.replace("code://oscar/", "") };
1840
- }
1841
- if (uri.startsWith("code://sample/")) {
1842
- return { scope: "sample", relPath: uri.replace("code://sample/", "") };
1843
- }
1844
- if (uri.startsWith("code://tutorial/")) {
1845
- return { scope: "tutorial", relPath: uri.replace("code://tutorial/", "") };
1846
- }
1787
+ if (uri.startsWith("code://oscar/")) return { scope: "oscar", relPath: uri.replace("code://oscar/", "") };
1788
+ if (uri.startsWith("code://sample/")) return { scope: "sample", relPath: uri.replace("code://sample/", "") };
1789
+ if (uri.startsWith("code://tutorial/")) return { scope: "tutorial", relPath: uri.replace("code://tutorial/", "") };
1847
1790
  return null;
1848
1791
  }
1849
1792
  async function resolveReferencedUris(state, sourceUri, refs) {
@@ -1894,6 +1837,12 @@ async function resolveReferencedUris(state, sourceUri, refs) {
1894
1837
  function escapeRegExp(value) {
1895
1838
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1896
1839
  }
1840
+ function tokenizeQuery(query) {
1841
+ return query.toLowerCase().split(/[^a-z0-9_]+/).map((token) => token.trim()).filter((token) => token.length >= 3);
1842
+ }
1843
+ function isExactSymbolQuery(query) {
1844
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(query.trim());
1845
+ }
1897
1846
  function looksLikeDeclarationLine(line) {
1898
1847
  const trimmed = line.trim();
1899
1848
  if (!trimmed) return false;
@@ -1913,286 +1862,171 @@ function inferResultType(result) {
1913
1862
  if (uri.startsWith("code://oscar/include/") && uri.toLowerCase().endsWith(".h")) return "headers";
1914
1863
  return "samples";
1915
1864
  }
1916
- function buildPreview(result, query) {
1865
+ function extractSignatureFromBody(body, query) {
1866
+ if (!body || !query.trim()) return void 0;
1867
+ const symbol = query.trim().toLowerCase();
1868
+ const exactSymbol = isExactSymbolQuery(query) ? new RegExp(`\\b${escapeRegExp(symbol)}\\b`) : null;
1869
+ const queryTokens = tokenizeQuery(query);
1870
+ const lines = body.split(/\r?\n/);
1871
+ for (const line of lines) {
1872
+ const trimmed = line.trim();
1873
+ if (!trimmed || !looksLikeDeclarationLine(trimmed)) continue;
1874
+ const lower = trimmed.toLowerCase();
1875
+ if (exactSymbol && exactSymbol.test(lower)) return trimmed;
1876
+ if (queryTokens.some((token) => lower.includes(token))) return trimmed;
1877
+ }
1878
+ return void 0;
1879
+ }
1880
+ function buildPreview(result, query, resultType) {
1917
1881
  const uri = String(result?.uri ?? "");
1918
1882
  const body = String(result?.body ?? "");
1919
1883
  const fallbackSummary = makeExcerpt(String(result?.snippet ?? ""), 700);
1920
1884
  const basePreview = typeof result?.preview === "object" && result.preview ? result.preview : { summary: fallbackSummary };
1921
- let signature = typeof basePreview.signature === "string" && basePreview.signature.trim().length > 0 ? basePreview.signature.trim() : void 0;
1922
- let declarationContext = typeof basePreview.declarationContext === "string" && basePreview.declarationContext.trim().length > 0 ? basePreview.declarationContext.trim() : void 0;
1923
- if (body && query.trim()) {
1924
- const queryWord = query.trim().toLowerCase();
1925
- const matcher = new RegExp(`\\b${escapeRegExp(queryWord)}\\b`);
1926
- const lines = body.split(/\r?\n/);
1927
- for (let i = 0; i < lines.length; i += 1) {
1928
- const line = String(lines[i] ?? "");
1929
- const lower = line.toLowerCase();
1930
- if (!lower.includes(queryWord)) continue;
1931
- if (!matcher.test(lower) || !looksLikeDeclarationLine(line)) continue;
1932
- signature = signature ?? line.trim();
1933
- const prev = lines.slice(Math.max(0, i - 1), i).find((v) => String(v).trim().length > 0)?.trim();
1934
- const next = lines.slice(i + 1, i + 3).find((v) => String(v).trim().length > 0)?.trim();
1935
- declarationContext = declarationContext ?? [prev, line.trim(), next].filter(Boolean).join(" | ");
1936
- break;
1937
- }
1938
- }
1885
+ const signature = (typeof basePreview.signature === "string" && basePreview.signature.trim().length > 0 ? basePreview.signature.trim() : void 0) ?? extractSignatureFromBody(body, query);
1939
1886
  const includePathFromUri = uri.startsWith("code://oscar/include/") ? uri.replace("code://oscar/include/", "") : void 0;
1940
- const includePath = typeof basePreview.includePath === "string" && basePreview.includePath.trim().length > 0 ? basePreview.includePath.trim() : includePathFromUri;
1887
+ const includePath = resultType === "headers" ? typeof basePreview.includePath === "string" && basePreview.includePath.trim().length > 0 ? basePreview.includePath.trim() : includePathFromUri : void 0;
1941
1888
  const summaryCandidate = typeof basePreview.summary === "string" && basePreview.summary.trim().length > 0 ? basePreview.summary : fallbackSummary;
1942
1889
  return {
1943
1890
  summary: summaryCandidate || makeExcerpt(body || String(result?.title ?? ""), 700),
1944
1891
  ...signature ? { signature } : {},
1945
- ...includePath ? { include_path: includePath } : {},
1946
- ...declarationContext ? { declaration_context: declarationContext } : {}
1892
+ ...includePath ? { include_path: includePath } : {}
1947
1893
  };
1948
1894
  }
1949
- function computeSymbolBoost(result, query) {
1950
- const symbol = query.trim();
1951
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(symbol)) return 0;
1952
- const symbolLower = symbol.toLowerCase();
1953
- let boost = 0;
1954
- const title = String(result?.title ?? "").toLowerCase();
1955
- const titleBase = title.replace(/\.[a-z0-9]+$/i, "");
1956
- if (title === symbolLower || titleBase === symbolLower) boost += 90;
1957
- const body = String(result?.body ?? "");
1958
- if (body) {
1959
- const exactWord = new RegExp(`\\b${escapeRegExp(symbolLower)}\\b`);
1960
- const lines = body.split(/\r?\n/);
1961
- for (const line of lines) {
1962
- const lower = line.toLowerCase();
1963
- if (!lower.includes(symbolLower)) continue;
1964
- if (exactWord.test(lower)) {
1965
- boost = Math.max(boost, looksLikeDeclarationLine(line) ? 70 : 28);
1966
- break;
1967
- }
1968
- boost = Math.max(boost, 10);
1895
+ function normalizeSearchResults(raw) {
1896
+ const seen = /* @__PURE__ */ new Map();
1897
+ for (const result of raw) {
1898
+ const uri = String(result?.uri ?? "");
1899
+ if (!uri) continue;
1900
+ const prev = seen.get(uri);
1901
+ if (!prev || Number(result?.score ?? 0) > Number(prev?.score ?? 0)) {
1902
+ seen.set(uri, result);
1969
1903
  }
1970
1904
  }
1971
- const uri = String(result?.uri ?? "");
1972
- if (boost > 0 && uri.startsWith("code://oscar/include/")) boost += 12;
1973
- return boost;
1974
- }
1975
- function isExactSymbolQuery(query) {
1976
- return /^[A-Za-z_][A-Za-z0-9_]*$/.test(query.trim());
1905
+ return [...seen.values()];
1977
1906
  }
1978
- function tokenizeQuery(query) {
1979
- return query.toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean);
1907
+ function toClassificationSummary(classification) {
1908
+ const confidence = Number(classification?.confidence ?? 0.25);
1909
+ const confidenceBucket = classification?.confidenceBucket === "high" || classification?.confidenceBucket === "medium" || classification?.confidenceBucket === "low" ? classification.confidenceBucket : confidence >= 0.8 ? "high" : confidence >= 0.55 ? "medium" : "low";
1910
+ const scope = classification?.facets?.scope === "manual" ? "manual" : "code";
1911
+ const neutralTrack = scope === "manual" ? "compiler_language" : "fundamentals";
1912
+ const track = String(classification?.primaryTrack ?? neutralTrack);
1913
+ const status = classification?.primaryTrackStatus === "asserted" || classification?.primaryTrackStatus === "neutral_fallback" ? classification.primaryTrackStatus : confidence >= 0.62 ? "asserted" : "neutral_fallback";
1914
+ return {
1915
+ track,
1916
+ track_status: status,
1917
+ confidence_bucket: confidenceBucket
1918
+ };
1980
1919
  }
1981
- function expandQueryTerms(query) {
1982
- const tokens = tokenizeQuery(query);
1920
+ function extractSystems(classification) {
1921
+ if (!Array.isArray(classification?.facets?.systems)) return [];
1983
1922
  const out = /* @__PURE__ */ new Set();
1984
- for (const token of tokens) {
1985
- out.add(token);
1986
- for (const synonym of INTENT_SYNONYMS[token] ?? []) out.add(synonym.toLowerCase());
1923
+ for (const item of classification.facets.systems) {
1924
+ const normalized = normalizeSystemToken(String(item ?? ""));
1925
+ if (normalized) out.add(normalized);
1987
1926
  }
1988
- if (tokens.includes("vic") && tokens.includes("bank")) out.add("vic_setbank");
1989
- if (tokens.includes("screenmem")) out.add("d018");
1990
1927
  return [...out];
1991
1928
  }
1992
- function buildExpandedQuery(query) {
1993
- const terms = expandQueryTerms(query);
1994
- if (terms.length === 0) return query;
1995
- return terms.join(" ");
1996
- }
1997
- function hasApiIntent(query) {
1998
- const tokens = tokenizeQuery(query);
1999
- return tokens.some(
2000
- (token) => ["vic", "d018", "bank", "banking", "charset", "screenmem", "memmap", "charwin", "rasterirq", "sprite"].includes(
2001
- token
2002
- )
2003
- );
2004
- }
2005
- function hasImplementationIntent(query) {
2006
- const tokens = tokenizeQuery(query);
2007
- return tokens.some((token) => ["setup", "init", "move", "print", "poll", "wait", "split"].includes(token));
2008
- }
2009
- function isActionableResult(result) {
2010
- const uri = String(result?.uri ?? "");
2011
- if (uri.startsWith("code://oscar/include/")) return uri.toLowerCase().endsWith(".h");
2012
- const signature = String(result?.preview?.signature ?? "");
2013
- if (signature.trim().length > 0) return true;
2014
- return uri.startsWith("code://tutorial/") || uri.startsWith("code://sample/");
2015
- }
2016
- function isLowConfidenceResultSet(rawResults, query) {
2017
- if (rawResults.length === 0) return true;
2018
- const top = Number(rawResults[0]?.score ?? 0);
2019
- const second = Number(rawResults[1]?.score ?? 0);
2020
- if (top < 0.6) return true;
2021
- if (second > 0 && top / second < 1.12) return true;
2022
- if (hasApiIntent(query) && !rawResults.slice(0, 3).some((entry) => isActionableResult(entry))) return true;
2023
- return false;
2024
- }
2025
- function computeIntentBoost(result, query) {
2026
- const uri = String(result?.uri ?? "");
1929
+ function hasQuerySignal(result, query) {
1930
+ const queryTokens = tokenizeQuery(query);
1931
+ if (queryTokens.length === 0) return false;
2027
1932
  const title = String(result?.title ?? "").toLowerCase();
1933
+ const snippet = String(result?.snippet ?? "").toLowerCase();
2028
1934
  const body = String(result?.body ?? "").toLowerCase();
2029
- let boost = 0;
2030
- if (hasApiIntent(query) && uri.startsWith("code://oscar/include/")) boost += 14;
2031
- if (hasImplementationIntent(query) && (uri.startsWith("code://tutorial/") || uri.startsWith("code://sample/"))) {
2032
- boost += 8;
2033
- }
2034
- const expandedTerms = expandQueryTerms(query);
2035
- for (const term of expandedTerms) {
2036
- if (term.length < 3) continue;
2037
- if (title.includes(term)) boost += 1.2;
2038
- else if (body.includes(term)) boost += 0.5;
2039
- }
2040
- if (uri.includes("vic.h") && /\b(vic|d018|screenmem|charset|bank)\b/i.test(query)) boost += 10;
2041
- if (uri.includes("memmap.h") && /\b(memmap|bank|banking|rom|ram)\b/i.test(query)) boost += 9;
2042
- if (uri.toLowerCase().includes("screenmem") && /\b(screenmem|screen memory|charset|d018)\b/i.test(query)) boost += 11;
2043
- return boost;
1935
+ return queryTokens.some((token) => title.includes(token) || snippet.includes(token) || body.includes(token));
2044
1936
  }
2045
- function normalizePrimaryTrackForConfidence(details) {
2046
- const neutralTrack = details.facets.scope === "manual" ? "compiler_language" : "fundamentals";
2047
- if (details.confidence >= 0.55) return details;
2048
- if (details.primary_track === neutralTrack) return details;
2049
- return {
2050
- ...details,
2051
- primary_track: neutralTrack,
2052
- primary_track_status: "neutral_fallback",
2053
- confidence_bucket: "low"
2054
- };
1937
+ function hasClearCodingValue(hit) {
1938
+ if (typeof hit.preview.signature === "string" && hit.preview.signature.trim().length > 0) return true;
1939
+ if (hit.resultType === "headers" && typeof hit.preview.include_path === "string" && hit.preview.include_path.length > 0) return true;
1940
+ if ((hit.referencedUris?.length ?? 0) > 0) return true;
1941
+ if ((hit.resultType === "tutorials" || hit.resultType === "samples" || hit.resultType === "topics") && String(hit.preview.summary ?? "").trim().length > 0) {
1942
+ return true;
1943
+ }
1944
+ if (hit.hasQuerySignal && (hit.resultType === "tutorials" || hit.resultType === "samples" || hit.resultType === "topics")) {
1945
+ return true;
1946
+ }
1947
+ return false;
2055
1948
  }
2056
- function normalizeSearchResults(raw) {
2057
- const seen = /* @__PURE__ */ new Map();
2058
- for (const result of raw) {
2059
- const uri = String(result?.uri ?? "");
2060
- if (!uri) continue;
2061
- const prev = seen.get(uri);
2062
- if (!prev || Number(result?.score ?? 0) > Number(prev?.score ?? 0)) {
2063
- seen.set(uri, result);
2064
- }
1949
+ function computeRankingBoost(hit) {
1950
+ let boost = 0;
1951
+ if (hit.resultType === "headers") boost += 20;
1952
+ if (hit.resultType === "tutorials" || hit.resultType === "samples") boost += 6;
1953
+ if (hit.preview.signature) boost += 16;
1954
+ if ((hit.referencedUris?.length ?? 0) > 0) boost += 8;
1955
+ if (hit.hasQuerySignal) boost += 5;
1956
+ const symbol = hit.query.trim().toLowerCase();
1957
+ if (isExactSymbolQuery(hit.query)) {
1958
+ const signature = String(hit.preview.signature ?? "").toLowerCase();
1959
+ if (signature.includes(symbol)) boost += 42;
1960
+ if (hit.resultType === "headers" && hit.uri.toLowerCase().includes(`${symbol}.h`)) boost += 18;
1961
+ }
1962
+ if (hit.classificationSummary.track === "fundamentals" && hit.classificationSummary.confidence_bucket === "low") {
1963
+ boost -= 8;
2065
1964
  }
2066
- return [...seen.values()];
1965
+ return boost;
2067
1966
  }
2068
1967
  async function executeSearch(context) {
2069
- const { query, limit, include_details } = context;
1968
+ const { query, limit } = context;
2070
1969
  const requestedType = context.type ?? "all";
2071
1970
  const system = context.system ?? "c64";
2072
1971
  const state = await getStateSnapshot();
2073
- const toFallbackClassification = () => ({
2074
- primary_track: "fundamentals",
2075
- primary_track_status: "neutral_fallback",
2076
- facets: {
2077
- domain: [],
2078
- hardware: [],
2079
- technique: [],
2080
- abstraction: [],
2081
- artifact: [],
2082
- systems: [],
2083
- scope: "sample"
2084
- },
2085
- confidence: 0.25,
2086
- confidence_bucket: "low",
2087
- evidence: [
2088
- {
2089
- label: "fundamentals",
2090
- facet: "primary_track",
2091
- reason: "Fallback classification due to missing source metadata",
2092
- weight: 3,
2093
- matched_on: "fallback",
2094
- source_field: "derived",
2095
- matched_text: "fallback",
2096
- direct: true
2097
- }
2098
- ]
2099
- });
2100
- const toDetails = (classification) => {
2101
- const fallback = toFallbackClassification();
2102
- const normalizeSystems = (systems) => {
2103
- if (!Array.isArray(systems)) return [];
2104
- const out = /* @__PURE__ */ new Set();
2105
- for (const entry of systems) {
2106
- const normalized = normalizeSystemToken(String(entry ?? ""));
2107
- if (normalized) out.add(normalized);
2108
- }
2109
- return [...out];
2110
- };
2111
- if (!classification || typeof classification !== "object") return fallback;
2112
- return normalizePrimaryTrackForConfidence({
2113
- primary_track: String(classification.primaryTrack ?? fallback.primary_track),
2114
- primary_track_status: classification.primaryTrackStatus === "asserted" || classification.primaryTrackStatus === "neutral_fallback" ? classification.primaryTrackStatus : fallback.primary_track_status,
2115
- facets: {
2116
- domain: Array.isArray(classification.facets?.domain) ? classification.facets.domain.map(String) : [],
2117
- hardware: Array.isArray(classification.facets?.hardware) ? classification.facets.hardware.map(String) : [],
2118
- technique: Array.isArray(classification.facets?.technique) ? classification.facets.technique.map(String) : [],
2119
- abstraction: Array.isArray(classification.facets?.abstraction) ? classification.facets.abstraction.map(String) : [],
2120
- artifact: Array.isArray(classification.facets?.artifact) ? classification.facets.artifact.map(String) : [],
2121
- systems: normalizeSystems(classification.facets?.systems),
2122
- scope: classification.facets?.scope === "tutorial" || classification.facets?.scope === "sample" || classification.facets?.scope === "manual" ? classification.facets.scope : fallback.facets.scope
2123
- },
2124
- confidence: Number(classification.confidence ?? fallback.confidence),
2125
- confidence_bucket: classification.confidenceBucket === "low" || classification.confidenceBucket === "medium" || classification.confidenceBucket === "high" ? classification.confidenceBucket : fallback.confidence_bucket,
2126
- evidence: Array.isArray(classification.evidence) ? classification.evidence.map((item) => ({
2127
- label: String(item?.label ?? ""),
2128
- facet: item?.facet === "primary_track" || item?.facet === "domain" || item?.facet === "hardware" || item?.facet === "technique" || item?.facet === "abstraction" || item?.facet === "artifact" ? item.facet : "primary_track",
2129
- reason: String(item?.reason ?? ""),
2130
- weight: Number(item?.weight ?? 0),
2131
- matched_on: String(item?.matchedOn ?? item?.matched_on ?? ""),
2132
- source_field: item?.sourceField === "title" || item?.sourceField === "rel_path" || item?.sourceField === "text" || item?.sourceField === "files" || item?.sourceField === "asset_refs" || item?.sourceField === "derived" ? item.sourceField : item?.source_field === "title" || item?.source_field === "rel_path" || item?.source_field === "text" || item?.source_field === "files" || item?.source_field === "asset_refs" || item?.source_field === "derived" ? item.source_field : "derived",
2133
- matched_text: String(item?.matchedText ?? item?.matched_text ?? ""),
2134
- direct: Boolean(item?.direct)
2135
- })) : []
2136
- });
2137
- };
2138
- const toSummary = (details) => ({
2139
- track: details.primary_track,
2140
- track_status: details.primary_track_status,
2141
- confidence_bucket: details.confidence_bucket,
2142
- confidence: details.confidence,
2143
- domains: details.facets.domain,
2144
- hardware: details.facets.hardware,
2145
- techniques: details.facets.technique,
2146
- systems: details.facets.systems,
2147
- scope: details.facets.scope
2148
- });
2149
- const primaryResults = state.searchIndex.search(query, {
2150
- combineWith: inferCombineMode(query),
2151
- prefix: true,
2152
- fuzzy: 0.12
2153
- });
2154
- const symbolQuery = isExactSymbolQuery(query);
2155
- const expandedQuery = buildExpandedQuery(query);
2156
- const useFallback = !symbolQuery && (isLowConfidenceResultSet(primaryResults, query) || expandedQuery !== query);
2157
- const expandedResults = useFallback ? state.searchIndex.search(expandedQuery, {
2158
- combineWith: "OR",
2159
- prefix: true,
2160
- fuzzy: 0.16
2161
- }) : [];
2162
- const rawResults = normalizeSearchResults([
2163
- ...primaryResults,
2164
- ...expandedResults.map((item) => ({
2165
- ...item,
2166
- score: Number(item?.score ?? 0) * 0.82
2167
- }))
2168
- ]);
1972
+ const rawResults = normalizeSearchResults(
1973
+ state.searchIndex.search(query, {
1974
+ combineWith: inferCombineMode(query),
1975
+ prefix: true,
1976
+ fuzzy: 0.12
1977
+ })
1978
+ );
1979
+ const fallbackResults = rawResults.length === 0 ? normalizeSearchResults(
1980
+ state.searchIndex.search(query, {
1981
+ combineWith: "OR",
1982
+ prefix: true,
1983
+ fuzzy: 0.16
1984
+ })
1985
+ ) : [];
1986
+ const mergedResults = normalizeSearchResults([...rawResults, ...fallbackResults]);
2169
1987
  const mapped = await Promise.all(
2170
- rawResults.map(async (result) => {
2171
- const details = toDetails(result.classification);
2172
- const referencedUris = await resolveReferencedUris(state, String(result.uri ?? ""), result.referencedFiles);
1988
+ mergedResults.map(async (result) => {
2173
1989
  const resultType = inferResultType(result);
2174
- const rerankBoost = computeSymbolBoost(result, query) + computeIntentBoost(result, query);
1990
+ const preview = buildPreview(result, query, resultType);
1991
+ const referencedUris = await resolveReferencedUris(state, String(result.uri ?? ""), result.referencedFiles);
1992
+ const classificationSummary = toClassificationSummary(result.classification);
1993
+ const systems = extractSystems(result.classification);
1994
+ const hasSignal = hasQuerySignal(result, query);
1995
+ const baseScore = Number(result?.score ?? 0);
1996
+ const rankScore = baseScore + computeRankingBoost({
1997
+ uri: String(result.uri ?? ""),
1998
+ resultType,
1999
+ preview,
2000
+ query,
2001
+ referencedUris,
2002
+ classificationSummary,
2003
+ hasQuerySignal: hasSignal
2004
+ });
2175
2005
  return {
2176
- score: (result.score ?? 0) + rerankBoost,
2006
+ rankScore,
2007
+ systems,
2008
+ keep: hasClearCodingValue({
2009
+ resultType,
2010
+ preview,
2011
+ referencedUris,
2012
+ hasQuerySignal: hasSignal
2013
+ }),
2177
2014
  hit: {
2178
- source: result.source === "manual" ? "manual" : "code",
2179
2015
  result_type: resultType,
2180
2016
  uri: String(result.uri ?? ""),
2181
2017
  title: String(result.title ?? ""),
2182
- preview: buildPreview(result, query),
2183
- score: (result.score ?? 0) + rerankBoost,
2018
+ preview,
2184
2019
  ...referencedUris ? { referenced_files: referencedUris } : {},
2185
- classification_summary: toSummary(details),
2186
- ...include_details ? { classification_details: details } : {}
2020
+ classification_summary: classificationSummary
2187
2021
  }
2188
2022
  };
2189
2023
  })
2190
2024
  );
2191
- const hits = mapped.filter((entry) => {
2025
+ const hits = mapped.filter((entry) => entry.keep).filter((entry) => {
2192
2026
  const uri = String(entry.hit.uri ?? "");
2193
2027
  if (uri.startsWith("code://oscar/include/") && !uri.toLowerCase().endsWith(".h")) return false;
2194
2028
  return true;
2195
- }).filter((entry) => requestedType === "all" || entry.hit.result_type === requestedType).filter((entry) => matchesSystemFilter(entry.hit.classification_summary.systems, system)).sort((a, b) => b.score - a.score || a.hit.uri.localeCompare(b.hit.uri)).slice(0, limit);
2029
+ }).filter((entry) => requestedType === "all" || entry.hit.result_type === requestedType).filter((entry) => matchesSystemFilter(entry.systems, system)).sort((a, b) => b.rankScore - a.rankScore || a.hit.uri.localeCompare(b.hit.uri)).slice(0, limit);
2196
2030
  return {
2197
2031
  ok: true,
2198
2032
  data: {
@@ -2206,7 +2040,19 @@ var searchTool = createTool3({
2206
2040
  inputSchema: searchInputSchema,
2207
2041
  outputSchema: searchOutputSchema,
2208
2042
  execute: async (input) => {
2209
- const context = input?.context ?? input;
2043
+ const rawInput = input?.context ?? input;
2044
+ if (Object.prototype.hasOwnProperty.call(rawInput, "include_details")) {
2045
+ return {
2046
+ ok: false,
2047
+ error: {
2048
+ code: "INVALID_INPUT",
2049
+ message: "include_details is no longer supported.",
2050
+ hint: "Remove include_details and use classification_summary only.",
2051
+ recoverable: true
2052
+ }
2053
+ };
2054
+ }
2055
+ const context = rawInput;
2210
2056
  try {
2211
2057
  return await executeSearch(context);
2212
2058
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oscar64-mcp-docs",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {