oscar64-mcp-docs 1.0.7 → 1.0.9

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 +207 -303
  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
  });
@@ -580,11 +543,68 @@ var TUTORIAL_BAND_RULES = [
580
543
  { min: 5e3, max: 5099, primaryTrack: "bitmap_graphics", domain: "graphics" }
581
544
  ];
582
545
  var CLASSIFICATION_RULES = [
546
+ {
547
+ id: "video_memory_api",
548
+ appliesTo: ["tutorial", "sample", "manual"],
549
+ tests: [
550
+ {
551
+ pattern: "#include\\s*<(?:c64|c128|vic20|plus4|x16|pet|atari|mega65|nes)/(?:vic|vdc|ted)\\.h>",
552
+ flags: "i",
553
+ reason: "includes display memory/control API header",
554
+ weight: 3,
555
+ direct: true
556
+ },
557
+ {
558
+ pattern: "\\b(vic_setbank|vic_waitframe|vic_waitline|vdc_[a-z0-9_]+|ted_[a-z0-9_]+|screenmem|d018)\\b",
559
+ flags: "i",
560
+ reason: "uses display memory/control API symbols",
561
+ weight: 2.5,
562
+ direct: true
563
+ }
564
+ ],
565
+ emits: [
566
+ { kind: "primary_track", label: "memory_mapping" },
567
+ { kind: "domain", label: "memory" },
568
+ { kind: "hardware", label: "vic_ii" },
569
+ { kind: "technique", label: "bank_switching" }
570
+ ]
571
+ },
572
+ {
573
+ id: "charwin_api",
574
+ appliesTo: ["tutorial", "sample", "manual"],
575
+ tests: [
576
+ {
577
+ pattern: "#include\\s*<(?:c64|c128|vic20|plus4|x16|pet|atari|mega65|nes)/charwin\\.h>",
578
+ flags: "i",
579
+ reason: "includes character window API header",
580
+ weight: 3,
581
+ direct: true
582
+ },
583
+ {
584
+ pattern: "\\bcwin_[a-z0-9_]+\\b",
585
+ flags: "i",
586
+ reason: "uses char window API symbols",
587
+ weight: 2.5,
588
+ direct: true
589
+ }
590
+ ],
591
+ emits: [
592
+ { kind: "primary_track", label: "fundamentals" },
593
+ { kind: "domain", label: "display_textmode" },
594
+ { kind: "artifact", label: "text_ui" }
595
+ ]
596
+ },
583
597
  {
584
598
  id: "rasterirq_api",
585
599
  appliesTo: ["tutorial", "sample", "manual"],
586
600
  tests: [
587
- { pattern: "#include\\s*<c64/rasterirq\\.h>", flags: "i", reason: "includes raster IRQ API", weight: 3, direct: true },
601
+ {
602
+ pattern: "#include\\s*<(?:c64|c128|vic20|plus4|x16|pet|atari|mega65|nes)/rasterirq\\.h>",
603
+ flags: "i",
604
+ reason: "includes raster IRQ API",
605
+ weight: 3,
606
+ direct: true
607
+ },
588
608
  { pattern: "\\brirq_[a-z0-9_]+\\b", flags: "i", reason: "uses rirq_* routines", weight: 2, direct: true },
589
609
  { pattern: "\\braster\\s*irq\\b|\\birq\\b", flags: "i", reason: "mentions IRQ timing", weight: 1 }
590
610
  ],
@@ -1820,30 +1840,10 @@ var readUriTool = createTool2({
1820
1840
  // src/mcp/tools/search.tool.ts
1821
1841
  import { createTool as createTool3 } from "@mastra/core/tools";
1822
1842
  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
1843
  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
- }
1844
+ if (uri.startsWith("code://oscar/")) return { scope: "oscar", relPath: uri.replace("code://oscar/", "") };
1845
+ if (uri.startsWith("code://sample/")) return { scope: "sample", relPath: uri.replace("code://sample/", "") };
1846
+ if (uri.startsWith("code://tutorial/")) return { scope: "tutorial", relPath: uri.replace("code://tutorial/", "") };
1847
1847
  return null;
1848
1848
  }
1849
1849
  async function resolveReferencedUris(state, sourceUri, refs) {
@@ -1894,6 +1894,12 @@ async function resolveReferencedUris(state, sourceUri, refs) {
1894
1894
  function escapeRegExp(value) {
1895
1895
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1896
1896
  }
1897
+ function tokenizeQuery(query) {
1898
+ return query.toLowerCase().split(/[^a-z0-9_]+/).map((token) => token.trim()).filter((token) => token.length >= 3);
1899
+ }
1900
+ function isExactSymbolQuery(query) {
1901
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(query.trim());
1902
+ }
1897
1903
  function looksLikeDeclarationLine(line) {
1898
1904
  const trimmed = line.trim();
1899
1905
  if (!trimmed) return false;
@@ -1913,286 +1919,172 @@ function inferResultType(result) {
1913
1919
  if (uri.startsWith("code://oscar/include/") && uri.toLowerCase().endsWith(".h")) return "headers";
1914
1920
  return "samples";
1915
1921
  }
1916
- function buildPreview(result, query) {
1922
+ function extractSignatureFromBody(body, query) {
1923
+ if (!body || !query.trim()) return void 0;
1924
+ const symbol = query.trim().toLowerCase();
1925
+ const exactSymbol = isExactSymbolQuery(query) ? new RegExp(`\\b${escapeRegExp(symbol)}\\b`) : null;
1926
+ const queryTokens = tokenizeQuery(query);
1927
+ const lines = body.split(/\r?\n/);
1928
+ for (const line of lines) {
1929
+ const trimmed = line.trim();
1930
+ if (!trimmed || !looksLikeDeclarationLine(trimmed)) continue;
1931
+ const lower = trimmed.toLowerCase();
1932
+ if (exactSymbol && exactSymbol.test(lower)) return trimmed;
1933
+ if (queryTokens.some((token) => lower.includes(token))) return trimmed;
1934
+ }
1935
+ return void 0;
1936
+ }
1937
+ function buildPreview(result, query, resultType) {
1917
1938
  const uri = String(result?.uri ?? "");
1918
1939
  const body = String(result?.body ?? "");
1919
1940
  const fallbackSummary = makeExcerpt(String(result?.snippet ?? ""), 700);
1920
1941
  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
- }
1942
+ const signature = (typeof basePreview.signature === "string" && basePreview.signature.trim().length > 0 ? basePreview.signature.trim() : void 0) ?? extractSignatureFromBody(body, query);
1939
1943
  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;
1944
+ const includePath = resultType === "headers" ? typeof basePreview.includePath === "string" && basePreview.includePath.trim().length > 0 ? basePreview.includePath.trim() : includePathFromUri : void 0;
1941
1945
  const summaryCandidate = typeof basePreview.summary === "string" && basePreview.summary.trim().length > 0 ? basePreview.summary : fallbackSummary;
1942
1946
  return {
1943
1947
  summary: summaryCandidate || makeExcerpt(body || String(result?.title ?? ""), 700),
1944
1948
  ...signature ? { signature } : {},
1945
- ...includePath ? { include_path: includePath } : {},
1946
- ...declarationContext ? { declaration_context: declarationContext } : {}
1949
+ ...includePath ? { include_path: includePath } : {}
1947
1950
  };
1948
1951
  }
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);
1952
+ function normalizeSearchResults(raw) {
1953
+ const seen = /* @__PURE__ */ new Map();
1954
+ for (const result of raw) {
1955
+ const uri = String(result?.uri ?? "");
1956
+ if (!uri) continue;
1957
+ const prev = seen.get(uri);
1958
+ if (!prev || Number(result?.score ?? 0) > Number(prev?.score ?? 0)) {
1959
+ seen.set(uri, result);
1969
1960
  }
1970
1961
  }
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());
1962
+ return [...seen.values()];
1977
1963
  }
1978
- function tokenizeQuery(query) {
1979
- return query.toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean);
1964
+ function toClassificationSummary(classification) {
1965
+ const confidence = Number(classification?.confidence ?? 0.25);
1966
+ const confidenceBucket = classification?.confidenceBucket === "high" || classification?.confidenceBucket === "medium" || classification?.confidenceBucket === "low" ? classification.confidenceBucket : confidence >= 0.8 ? "high" : confidence >= 0.55 ? "medium" : "low";
1967
+ const scope = classification?.facets?.scope === "manual" ? "manual" : "code";
1968
+ const neutralTrack = scope === "manual" ? "compiler_language" : "fundamentals";
1969
+ const track = String(classification?.primaryTrack ?? neutralTrack);
1970
+ const status = classification?.primaryTrackStatus === "asserted" || classification?.primaryTrackStatus === "neutral_fallback" ? classification.primaryTrackStatus : confidence >= 0.62 ? "asserted" : "neutral_fallback";
1971
+ return {
1972
+ track,
1973
+ track_status: status,
1974
+ confidence_bucket: confidenceBucket
1975
+ };
1980
1976
  }
1981
- function expandQueryTerms(query) {
1982
- const tokens = tokenizeQuery(query);
1977
+ function extractSystems(classification) {
1978
+ if (!Array.isArray(classification?.facets?.systems)) return [];
1983
1979
  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());
1980
+ for (const item of classification.facets.systems) {
1981
+ const normalized = normalizeSystemToken(String(item ?? ""));
1982
+ if (normalized) out.add(normalized);
1987
1983
  }
1988
- if (tokens.includes("vic") && tokens.includes("bank")) out.add("vic_setbank");
1989
- if (tokens.includes("screenmem")) out.add("d018");
1990
1984
  return [...out];
1991
1985
  }
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 ?? "");
1986
+ function hasQuerySignal(result, query) {
1987
+ const queryTokens = tokenizeQuery(query);
1988
+ if (queryTokens.length === 0) return false;
2027
1989
  const title = String(result?.title ?? "").toLowerCase();
1990
+ const snippet = String(result?.snippet ?? "").toLowerCase();
2028
1991
  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;
1992
+ return queryTokens.some((token) => title.includes(token) || snippet.includes(token) || body.includes(token));
2044
1993
  }
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
- };
1994
+ function hasClearCodingValue(hit) {
1995
+ if (typeof hit.preview.signature === "string" && hit.preview.signature.trim().length > 0) return true;
1996
+ if (hit.resultType === "headers" && typeof hit.preview.include_path === "string" && hit.preview.include_path.length > 0) return true;
1997
+ if ((hit.referencedUris?.length ?? 0) > 0) return true;
1998
+ if ((hit.resultType === "tutorials" || hit.resultType === "samples" || hit.resultType === "topics") && String(hit.preview.summary ?? "").trim().length > 0) {
1999
+ return true;
2000
+ }
2001
+ if (hit.hasQuerySignal && (hit.resultType === "tutorials" || hit.resultType === "samples" || hit.resultType === "topics")) {
2002
+ return true;
2003
+ }
2004
+ return false;
2055
2005
  }
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
- }
2006
+ function computeRankingBoost(hit) {
2007
+ let boost = 0;
2008
+ if (hit.resultType === "headers") boost += 20;
2009
+ if (hit.resultType === "headers" && hit.classificationSummary.track_status === "asserted") boost += 12;
2010
+ if (hit.resultType === "tutorials" || hit.resultType === "samples") boost += 6;
2011
+ if (hit.preview.signature) boost += 16;
2012
+ if ((hit.referencedUris?.length ?? 0) > 0) boost += 8;
2013
+ if (hit.hasQuerySignal) boost += 5;
2014
+ const symbol = hit.query.trim().toLowerCase();
2015
+ if (isExactSymbolQuery(hit.query)) {
2016
+ const signature = String(hit.preview.signature ?? "").toLowerCase();
2017
+ if (signature.includes(symbol)) boost += 42;
2018
+ if (hit.resultType === "headers" && hit.uri.toLowerCase().includes(`${symbol}.h`)) boost += 18;
2019
+ }
2020
+ if (hit.classificationSummary.track === "fundamentals" && hit.classificationSummary.confidence_bucket === "low") {
2021
+ boost -= 8;
2065
2022
  }
2066
- return [...seen.values()];
2023
+ return boost;
2067
2024
  }
2068
2025
  async function executeSearch(context) {
2069
- const { query, limit, include_details } = context;
2026
+ const { query, limit } = context;
2070
2027
  const requestedType = context.type ?? "all";
2071
2028
  const system = context.system ?? "c64";
2072
2029
  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
- ]);
2030
+ const rawResults = normalizeSearchResults(
2031
+ state.searchIndex.search(query, {
2032
+ combineWith: inferCombineMode(query),
2033
+ prefix: true,
2034
+ fuzzy: 0.12
2035
+ })
2036
+ );
2037
+ const fallbackResults = rawResults.length === 0 ? normalizeSearchResults(
2038
+ state.searchIndex.search(query, {
2039
+ combineWith: "OR",
2040
+ prefix: true,
2041
+ fuzzy: 0.16
2042
+ })
2043
+ ) : [];
2044
+ const mergedResults = normalizeSearchResults([...rawResults, ...fallbackResults]);
2169
2045
  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);
2046
+ mergedResults.map(async (result) => {
2173
2047
  const resultType = inferResultType(result);
2174
- const rerankBoost = computeSymbolBoost(result, query) + computeIntentBoost(result, query);
2048
+ const preview = buildPreview(result, query, resultType);
2049
+ const referencedUris = await resolveReferencedUris(state, String(result.uri ?? ""), result.referencedFiles);
2050
+ const classificationSummary = toClassificationSummary(result.classification);
2051
+ const systems = extractSystems(result.classification);
2052
+ const hasSignal = hasQuerySignal(result, query);
2053
+ const baseScore = Number(result?.score ?? 0);
2054
+ const rankScore = baseScore + computeRankingBoost({
2055
+ uri: String(result.uri ?? ""),
2056
+ resultType,
2057
+ preview,
2058
+ query,
2059
+ referencedUris,
2060
+ classificationSummary,
2061
+ hasQuerySignal: hasSignal
2062
+ });
2175
2063
  return {
2176
- score: (result.score ?? 0) + rerankBoost,
2064
+ rankScore,
2065
+ systems,
2066
+ keep: hasClearCodingValue({
2067
+ resultType,
2068
+ preview,
2069
+ referencedUris,
2070
+ hasQuerySignal: hasSignal
2071
+ }),
2177
2072
  hit: {
2178
- source: result.source === "manual" ? "manual" : "code",
2179
2073
  result_type: resultType,
2180
2074
  uri: String(result.uri ?? ""),
2181
2075
  title: String(result.title ?? ""),
2182
- preview: buildPreview(result, query),
2183
- score: (result.score ?? 0) + rerankBoost,
2076
+ preview,
2184
2077
  ...referencedUris ? { referenced_files: referencedUris } : {},
2185
- classification_summary: toSummary(details),
2186
- ...include_details ? { classification_details: details } : {}
2078
+ classification_summary: classificationSummary
2187
2079
  }
2188
2080
  };
2189
2081
  })
2190
2082
  );
2191
- const hits = mapped.filter((entry) => {
2083
+ const hits = mapped.filter((entry) => entry.keep).filter((entry) => {
2192
2084
  const uri = String(entry.hit.uri ?? "");
2193
2085
  if (uri.startsWith("code://oscar/include/") && !uri.toLowerCase().endsWith(".h")) return false;
2194
2086
  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);
2087
+ }).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
2088
  return {
2197
2089
  ok: true,
2198
2090
  data: {
@@ -2206,7 +2098,19 @@ var searchTool = createTool3({
2206
2098
  inputSchema: searchInputSchema,
2207
2099
  outputSchema: searchOutputSchema,
2208
2100
  execute: async (input) => {
2209
- const context = input?.context ?? input;
2101
+ const rawInput = input?.context ?? input;
2102
+ if (Object.prototype.hasOwnProperty.call(rawInput, "include_details")) {
2103
+ return {
2104
+ ok: false,
2105
+ error: {
2106
+ code: "INVALID_INPUT",
2107
+ message: "include_details is no longer supported.",
2108
+ hint: "Remove include_details and use classification_summary only.",
2109
+ recoverable: true
2110
+ }
2111
+ };
2112
+ }
2113
+ const context = rawInput;
2210
2114
  try {
2211
2115
  return await executeSearch(context);
2212
2116
  } 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.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {