oscar64-mcp-docs 1.0.2 → 1.0.3
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/dist/stdio.js +288 -177
- package/package.json +4 -4
package/dist/stdio.js
CHANGED
|
@@ -118,6 +118,83 @@ async function listFilesRecursive(root) {
|
|
|
118
118
|
return out;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// src/mcp/tools/uri.ts
|
|
122
|
+
import path3 from "path";
|
|
123
|
+
function codeUri(scope, relPath) {
|
|
124
|
+
return `code://${scope}/${relPath.replace(/\\/g, "/")}`;
|
|
125
|
+
}
|
|
126
|
+
function extLower(filePath) {
|
|
127
|
+
return path3.extname(filePath).toLowerCase();
|
|
128
|
+
}
|
|
129
|
+
function isBinaryExposedPath(filePath) {
|
|
130
|
+
return BINARY_EXPOSED_EXTS.has(extLower(filePath));
|
|
131
|
+
}
|
|
132
|
+
function guessMimeType(filePath) {
|
|
133
|
+
switch (extLower(filePath)) {
|
|
134
|
+
case ".md":
|
|
135
|
+
return "text/markdown";
|
|
136
|
+
case ".txt":
|
|
137
|
+
return "text/plain";
|
|
138
|
+
case ".c":
|
|
139
|
+
case ".h":
|
|
140
|
+
case ".cpp":
|
|
141
|
+
case ".s":
|
|
142
|
+
case ".asm":
|
|
143
|
+
case ".inc":
|
|
144
|
+
return "text/plain";
|
|
145
|
+
case ".sid":
|
|
146
|
+
return "audio/prs.sid";
|
|
147
|
+
case ".spd":
|
|
148
|
+
case ".ctm":
|
|
149
|
+
case ".mcimg":
|
|
150
|
+
case ".bin":
|
|
151
|
+
return "application/octet-stream";
|
|
152
|
+
default:
|
|
153
|
+
return "application/octet-stream";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function uriToAbsPath(state, uri) {
|
|
157
|
+
if (uri.startsWith("code://oscar/")) {
|
|
158
|
+
return safeJoin(state.oscar64Root, uri.replace("code://oscar/", ""));
|
|
159
|
+
}
|
|
160
|
+
if (uri.startsWith("code://sample/")) {
|
|
161
|
+
return safeJoin(state.oscar64Root, uri.replace("code://sample/", ""));
|
|
162
|
+
}
|
|
163
|
+
if (uri.startsWith("code://tutorial/")) {
|
|
164
|
+
return safeJoin(state.tutorialsRoot, uri.replace("code://tutorial/", ""));
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function maybeParseAsFileUri(state, uri) {
|
|
169
|
+
if (uri.startsWith("code://oscar/")) {
|
|
170
|
+
const relPath = uri.replace("code://oscar/", "");
|
|
171
|
+
return {
|
|
172
|
+
absPath: safeJoin(state.oscar64Root, relPath),
|
|
173
|
+
relPath
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (uri.startsWith("code://sample/")) {
|
|
177
|
+
const relPath = uri.replace("code://sample/", "");
|
|
178
|
+
return {
|
|
179
|
+
absPath: safeJoin(state.oscar64Root, relPath),
|
|
180
|
+
relPath
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (uri.startsWith("code://tutorial/")) {
|
|
184
|
+
const relPath = uri.replace("code://tutorial/", "");
|
|
185
|
+
return {
|
|
186
|
+
absPath: safeJoin(state.tutorialsRoot, relPath),
|
|
187
|
+
relPath
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
function isOscarIncludeHeaderPath(relPath) {
|
|
193
|
+
const normalized = relPath.replace(/\\/g, "/").toLowerCase();
|
|
194
|
+
if (!normalized.startsWith("include/")) return true;
|
|
195
|
+
return normalized.endsWith(".h");
|
|
196
|
+
}
|
|
197
|
+
|
|
121
198
|
// src/mcp/resources.ts
|
|
122
199
|
function makeResources(getState) {
|
|
123
200
|
const resourceTemplates = async () => [
|
|
@@ -130,7 +207,7 @@ function makeResources(getState) {
|
|
|
130
207
|
{
|
|
131
208
|
uriTemplate: "code://oscar/{path}",
|
|
132
209
|
name: "Oscar64 Code File",
|
|
133
|
-
description: "A specific text file from oscar64 repository snapshot (headers
|
|
210
|
+
description: "A specific text file from the oscar64 repository snapshot (headers and other exposed non-include files).",
|
|
134
211
|
mimeType: "text/plain"
|
|
135
212
|
},
|
|
136
213
|
{
|
|
@@ -175,6 +252,7 @@ function makeResources(getState) {
|
|
|
175
252
|
if (uri.startsWith("code://oscar/")) {
|
|
176
253
|
const rel = uri.replace("code://oscar/", "");
|
|
177
254
|
const abs = safeJoin(state.oscar64Root, rel);
|
|
255
|
+
if (!isOscarIncludeHeaderPath(rel)) throw new Error("Implementation files under include/ are not exposed");
|
|
178
256
|
if (!isExposedPath(rel)) throw new Error("File type is not exposed");
|
|
179
257
|
const text = await safeReadText(abs);
|
|
180
258
|
return {
|
|
@@ -210,7 +288,7 @@ function makeResources(getState) {
|
|
|
210
288
|
|
|
211
289
|
// src/mcp/tools/list-indexes.tool.ts
|
|
212
290
|
import { createTool } from "@mastra/core/tools";
|
|
213
|
-
import
|
|
291
|
+
import path8 from "path";
|
|
214
292
|
|
|
215
293
|
// src/classification/system.ts
|
|
216
294
|
var SYSTEM_FAMILY_VALUES = [
|
|
@@ -317,13 +395,18 @@ var toolErrorSchema = z.object({
|
|
|
317
395
|
).optional().describe("Optional follow-up tool calls that can recover the workflow.")
|
|
318
396
|
});
|
|
319
397
|
var classificationEvidenceSchema = z.object({
|
|
320
|
-
label: z.string().describe("
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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.")
|
|
324
406
|
});
|
|
325
407
|
var classificationDetailsSchema = z.object({
|
|
326
|
-
primary_track: z.string().describe("
|
|
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."),
|
|
327
410
|
facets: z.object({
|
|
328
411
|
domain: z.array(z.string()).describe("Functional domains covered by this result."),
|
|
329
412
|
hardware: z.array(z.string()).describe("Hardware domains referenced by this result."),
|
|
@@ -333,14 +416,18 @@ var classificationDetailsSchema = z.object({
|
|
|
333
416
|
systems: z.array(systemFamilySchema).describe("Detected target systems for this result; empty means shared/common."),
|
|
334
417
|
scope: z.enum(["tutorial", "sample", "manual"]).describe("Content scope of this result.")
|
|
335
418
|
}),
|
|
336
|
-
confidence: z.number().describe("
|
|
337
|
-
|
|
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.")
|
|
338
422
|
});
|
|
339
423
|
var classificationSummarySchema = z.object({
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
424
|
+
track: z.string().describe("Primary track summary value for routing and filtering."),
|
|
425
|
+
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."),
|
|
344
431
|
systems: z.array(systemFamilySchema).describe("Detected target systems for this result; empty means shared/common."),
|
|
345
432
|
scope: z.enum(["tutorial", "sample", "manual"]).describe("Content scope of this result.")
|
|
346
433
|
});
|
|
@@ -429,7 +516,7 @@ var listIndexesOutputSchema = z.union([listIndexesSuccessEnvelopeSchema, errorEn
|
|
|
429
516
|
|
|
430
517
|
// src/state.ts
|
|
431
518
|
import fs4 from "fs/promises";
|
|
432
|
-
import
|
|
519
|
+
import path7 from "path";
|
|
433
520
|
|
|
434
521
|
// src/indexing/manual.ts
|
|
435
522
|
function parseManualSections(text) {
|
|
@@ -462,7 +549,7 @@ function parseManualSections(text) {
|
|
|
462
549
|
|
|
463
550
|
// src/indexing/code-collections.ts
|
|
464
551
|
import fs2 from "fs/promises";
|
|
465
|
-
import
|
|
552
|
+
import path5 from "path";
|
|
466
553
|
|
|
467
554
|
// src/config/classification-v2.ts
|
|
468
555
|
var PRIMARY_TRACK_PRECEDENCE = [
|
|
@@ -497,8 +584,8 @@ var CLASSIFICATION_RULES = [
|
|
|
497
584
|
id: "rasterirq_api",
|
|
498
585
|
appliesTo: ["tutorial", "sample", "manual"],
|
|
499
586
|
tests: [
|
|
500
|
-
{ pattern: "#include\\s*<c64/rasterirq\\.h>", flags: "i", reason: "includes raster IRQ API", weight: 3 },
|
|
501
|
-
{ pattern: "\\brirq_[a-z0-9_]+\\b", flags: "i", reason: "uses rirq_* routines", weight: 2 },
|
|
587
|
+
{ pattern: "#include\\s*<c64/rasterirq\\.h>", flags: "i", reason: "includes raster IRQ API", weight: 3, direct: true },
|
|
588
|
+
{ pattern: "\\brirq_[a-z0-9_]+\\b", flags: "i", reason: "uses rirq_* routines", weight: 2, direct: true },
|
|
502
589
|
{ pattern: "\\braster\\s*irq\\b|\\birq\\b", flags: "i", reason: "mentions IRQ timing", weight: 1 }
|
|
503
590
|
],
|
|
504
591
|
emits: [
|
|
@@ -512,7 +599,7 @@ var CLASSIFICATION_RULES = [
|
|
|
512
599
|
id: "sprite_mux",
|
|
513
600
|
appliesTo: ["tutorial", "sample", "manual"],
|
|
514
601
|
tests: [
|
|
515
|
-
{ pattern: "\\bvspr_[a-z0-9_]+\\b", flags: "i", reason: "uses virtual sprite multiplexer", weight: 3 },
|
|
602
|
+
{ pattern: "\\bvspr_[a-z0-9_]+\\b", flags: "i", reason: "uses virtual sprite multiplexer", weight: 3, direct: true },
|
|
516
603
|
{ pattern: "\\bmultiplex(er|ing)?\\b|sprmux", flags: "i", reason: "mentions sprite multiplexing", weight: 2 }
|
|
517
604
|
],
|
|
518
605
|
emits: [
|
|
@@ -526,7 +613,7 @@ var CLASSIFICATION_RULES = [
|
|
|
526
613
|
id: "sprites",
|
|
527
614
|
appliesTo: ["tutorial", "sample", "manual"],
|
|
528
615
|
tests: [
|
|
529
|
-
{ pattern: "#include\\s*<c64/sprites\\.h>", flags: "i", reason: "includes sprite API", weight: 3 },
|
|
616
|
+
{ pattern: "#include\\s*<c64/sprites\\.h>", flags: "i", reason: "includes sprite API", weight: 3, direct: true },
|
|
530
617
|
{ pattern: "\\bsprite\\b", flags: "i", reason: "mentions sprites", weight: 1 }
|
|
531
618
|
],
|
|
532
619
|
emits: [
|
|
@@ -555,7 +642,7 @@ var CLASSIFICATION_RULES = [
|
|
|
555
642
|
id: "audio_sid",
|
|
556
643
|
appliesTo: ["tutorial", "sample", "manual"],
|
|
557
644
|
tests: [
|
|
558
|
-
{ pattern: "#include\\s*<c64/sid\\.h>", flags: "i", reason: "includes SID API", weight: 3 },
|
|
645
|
+
{ pattern: "#include\\s*<c64/sid\\.h>", flags: "i", reason: "includes SID API", weight: 3, direct: true },
|
|
559
646
|
{ pattern: "\\.sid\\b|\\bsid\\b|music|sound|audio", flags: "i", reason: "mentions SID/music", weight: 2 }
|
|
560
647
|
],
|
|
561
648
|
emits: [
|
|
@@ -569,8 +656,8 @@ var CLASSIFICATION_RULES = [
|
|
|
569
656
|
id: "memory_map",
|
|
570
657
|
appliesTo: ["tutorial", "sample", "manual"],
|
|
571
658
|
tests: [
|
|
572
|
-
{ pattern: "#include\\s*<c64/memmap\\.h>", flags: "i", reason: "includes memory map API", weight: 3 },
|
|
573
|
-
{ pattern: "\\bmmap_[a-z0-9_]+\\b|#pragma\\s+(region|section|data|code|stacksize)", flags: "i", reason: "uses memory layout controls", weight: 2 },
|
|
659
|
+
{ pattern: "#include\\s*<c64/memmap\\.h>", flags: "i", reason: "includes memory map API", weight: 3, direct: true },
|
|
660
|
+
{ pattern: "\\bmmap_[a-z0-9_]+\\b|#pragma\\s+(region|section|data|code|stacksize)", flags: "i", reason: "uses memory layout controls", weight: 2, direct: true },
|
|
574
661
|
{ pattern: "\\b(overlay|inlay|bank|full memory|resource region|easyflash)\\b", flags: "i", reason: "mentions banked/overlay memory", weight: 2 }
|
|
575
662
|
],
|
|
576
663
|
emits: [
|
|
@@ -622,8 +709,8 @@ var CLASSIFICATION_RULES = [
|
|
|
622
709
|
id: "input_io",
|
|
623
710
|
appliesTo: ["tutorial", "sample", "manual"],
|
|
624
711
|
tests: [
|
|
625
|
-
{ pattern: "#include\\s*<c64/(joystick|keyboard|mouse)\\.h>", flags: "i", reason: "includes input APIs", weight: 2 },
|
|
626
|
-
{ pattern: "#include\\s*<c64/(kernalio|iecbus)\\.h>", flags: "i", reason: "includes storage I/O APIs", weight: 2 }
|
|
712
|
+
{ pattern: "#include\\s*<c64/(joystick|keyboard|mouse)\\.h>", flags: "i", reason: "includes input APIs", weight: 2, direct: true },
|
|
713
|
+
{ pattern: "#include\\s*<c64/(kernalio|iecbus)\\.h>", flags: "i", reason: "includes storage I/O APIs", weight: 2, direct: true }
|
|
627
714
|
],
|
|
628
715
|
emits: [
|
|
629
716
|
{ kind: "domain", label: "io" },
|
|
@@ -656,15 +743,27 @@ var CLASSIFICATION_RULES = [
|
|
|
656
743
|
];
|
|
657
744
|
|
|
658
745
|
// src/classification/v2.ts
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
746
|
+
var DIRECT_PRIMARY_MIN = 2.8;
|
|
747
|
+
var ASSERTED_CONFIDENCE_MIN = 0.62;
|
|
748
|
+
var DOMAIN_DIRECT_MIN = 1.8;
|
|
749
|
+
function addScore(bucket, label, value) {
|
|
662
750
|
bucket.set(label, (bucket.get(label) ?? 0) + value);
|
|
663
751
|
}
|
|
664
|
-
function
|
|
665
|
-
|
|
752
|
+
function addFacetScore(acc, facet, label, value, direct) {
|
|
753
|
+
if (!acc.has(facet)) {
|
|
754
|
+
acc.set(facet, {
|
|
755
|
+
scores: /* @__PURE__ */ new Map(),
|
|
756
|
+
directScores: /* @__PURE__ */ new Map()
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
const facetBucket = acc.get(facet);
|
|
760
|
+
addScore(facetBucket.scores, label, value);
|
|
761
|
+
if (direct) addScore(facetBucket.directScores, label, value);
|
|
762
|
+
}
|
|
763
|
+
function sortedFacet(facetScores, facet, minDirect) {
|
|
764
|
+
const bucket = facetScores.get(facet);
|
|
666
765
|
if (!bucket) return [];
|
|
667
|
-
return [...bucket.entries()].filter(([, score]) => score > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([label]) => label);
|
|
766
|
+
return [...bucket.scores.entries()].filter(([label, score]) => score > 0 && (bucket.directScores.get(label) ?? 0) >= minDirect).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([label]) => label);
|
|
668
767
|
}
|
|
669
768
|
function primaryTrackFromScores(trackScores) {
|
|
670
769
|
const ranked = [...trackScores.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
@@ -679,12 +778,8 @@ function primaryTrackFromScores(trackScores) {
|
|
|
679
778
|
}
|
|
680
779
|
function defaultTrack(scope) {
|
|
681
780
|
if (scope === "manual") return "compiler_language";
|
|
682
|
-
if (scope === "sample") return "fundamentals";
|
|
683
781
|
return "fundamentals";
|
|
684
782
|
}
|
|
685
|
-
function hasStrongPrimaryEvidence(evidence, primaryTrack) {
|
|
686
|
-
return evidence.some((item) => item.label === primaryTrack && item.weight >= 3);
|
|
687
|
-
}
|
|
688
783
|
function tutorialBandSeed(tutorialId) {
|
|
689
784
|
if (!tutorialId || !/^\d+$/.test(tutorialId)) return {};
|
|
690
785
|
const numericId = Number(tutorialId);
|
|
@@ -692,39 +787,83 @@ function tutorialBandSeed(tutorialId) {
|
|
|
692
787
|
if (!band) return {};
|
|
693
788
|
return { primaryTrack: band.primaryTrack, domain: band.domain };
|
|
694
789
|
}
|
|
790
|
+
function buildConfidenceBucket(confidence) {
|
|
791
|
+
if (confidence >= 0.8) return "high";
|
|
792
|
+
if (confidence >= 0.55) return "medium";
|
|
793
|
+
return "low";
|
|
794
|
+
}
|
|
795
|
+
function firstMatch(regex, sources) {
|
|
796
|
+
for (const source of sources) {
|
|
797
|
+
const match = regex.exec(source.text);
|
|
798
|
+
if (match) {
|
|
799
|
+
return {
|
|
800
|
+
field: source.field,
|
|
801
|
+
matchedText: String(match[0] ?? "").slice(0, 80)
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return null;
|
|
806
|
+
}
|
|
807
|
+
function clamp(n, min, max) {
|
|
808
|
+
return Math.max(min, Math.min(max, n));
|
|
809
|
+
}
|
|
810
|
+
function withNeutralEvidence(evidence, neutralTrack, status) {
|
|
811
|
+
if (status === "asserted") return evidence;
|
|
812
|
+
return [
|
|
813
|
+
{
|
|
814
|
+
label: neutralTrack,
|
|
815
|
+
facet: "primary_track",
|
|
816
|
+
reason: "Fallback to neutral track due to weak or conflicting direct evidence",
|
|
817
|
+
weight: 3,
|
|
818
|
+
matchedOn: "neutral_fallback",
|
|
819
|
+
sourceField: "derived",
|
|
820
|
+
matchedText: "confidence_guardrail",
|
|
821
|
+
direct: true
|
|
822
|
+
},
|
|
823
|
+
...evidence
|
|
824
|
+
];
|
|
825
|
+
}
|
|
695
826
|
function classifyV2(input) {
|
|
696
|
-
const
|
|
697
|
-
input.title,
|
|
698
|
-
input.relPath ?? "",
|
|
699
|
-
input.text ?? "",
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
]
|
|
827
|
+
const sources = [
|
|
828
|
+
{ field: "title", text: input.title },
|
|
829
|
+
{ field: "rel_path", text: input.relPath ?? "" },
|
|
830
|
+
{ field: "text", text: input.text ?? "" },
|
|
831
|
+
{ field: "files", text: (input.files ?? []).join("\n") },
|
|
832
|
+
{ field: "asset_refs", text: (input.assetRefs ?? []).join("\n") }
|
|
833
|
+
];
|
|
703
834
|
const evidence = [];
|
|
704
835
|
const facetScores = /* @__PURE__ */ new Map();
|
|
705
836
|
const trackScores = /* @__PURE__ */ new Map();
|
|
837
|
+
const trackDirectScores = /* @__PURE__ */ new Map();
|
|
706
838
|
const seeded = input.scope === "tutorial" ? tutorialBandSeed(input.tutorialId) : {};
|
|
707
|
-
if (seeded.primaryTrack) trackScores
|
|
708
|
-
if (seeded.domain)
|
|
839
|
+
if (seeded.primaryTrack) addScore(trackScores, seeded.primaryTrack, 0.6);
|
|
840
|
+
if (seeded.domain) addFacetScore(facetScores, "domain", seeded.domain, 0.4, false);
|
|
709
841
|
for (const rule of CLASSIFICATION_RULES) {
|
|
710
842
|
if (!rule.appliesTo.includes(input.scope)) continue;
|
|
711
843
|
for (const test of rule.tests) {
|
|
712
844
|
const regex = new RegExp(test.pattern, test.flags ?? "i");
|
|
713
|
-
|
|
845
|
+
const matched = firstMatch(regex, sources);
|
|
846
|
+
if (!matched) continue;
|
|
714
847
|
const testWeight = test.weight ?? 1;
|
|
848
|
+
const isDirect = test.direct ?? testWeight >= 3;
|
|
715
849
|
for (const emit of rule.emits) {
|
|
716
850
|
const emitWeight = emit.weight ?? 1;
|
|
717
851
|
const weighted = testWeight * emitWeight;
|
|
718
852
|
if (emit.kind === "primary_track") {
|
|
719
|
-
trackScores
|
|
853
|
+
addScore(trackScores, emit.label, weighted);
|
|
854
|
+
if (isDirect) addScore(trackDirectScores, emit.label, weighted);
|
|
720
855
|
} else {
|
|
721
|
-
|
|
856
|
+
addFacetScore(facetScores, emit.kind, emit.label, weighted, isDirect);
|
|
722
857
|
}
|
|
723
858
|
evidence.push({
|
|
724
859
|
label: emit.label,
|
|
860
|
+
facet: emit.kind,
|
|
725
861
|
reason: test.reason,
|
|
726
862
|
weight: weighted,
|
|
727
|
-
matchedOn: rule.id
|
|
863
|
+
matchedOn: rule.id,
|
|
864
|
+
sourceField: matched.field,
|
|
865
|
+
matchedText: matched.matchedText,
|
|
866
|
+
direct: isDirect
|
|
728
867
|
});
|
|
729
868
|
}
|
|
730
869
|
}
|
|
@@ -732,48 +871,54 @@ function classifyV2(input) {
|
|
|
732
871
|
const neutralTrack = defaultTrack(input.scope);
|
|
733
872
|
const inferredPrimaryTrack = primaryTrackFromScores(trackScores);
|
|
734
873
|
let primaryTrack = inferredPrimaryTrack ?? neutralTrack;
|
|
735
|
-
const trackTotal = [...trackScores.values()].reduce((acc, n) => acc + n, 0);
|
|
736
874
|
const rankedTracks = [...trackScores.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
737
|
-
const topTrackScore =
|
|
875
|
+
const topTrackScore = rankedTracks[0]?.[1] ?? 0;
|
|
738
876
|
const secondTrackScore = rankedTracks[1]?.[1] ?? 0;
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
}
|
|
749
|
-
if (primaryTrack !== neutralTrack && (!hasStrongEvidence || confidence < 0.5)) {
|
|
877
|
+
const totalTrackScore = rankedTracks.reduce((acc, [, score]) => acc + score, 0);
|
|
878
|
+
const directPrimaryScore = trackDirectScores.get(primaryTrack) ?? 0;
|
|
879
|
+
const precision = totalTrackScore > 0 ? topTrackScore / totalTrackScore : 0.2;
|
|
880
|
+
const margin = topTrackScore > 0 ? (topTrackScore - secondTrackScore) / topTrackScore : 0;
|
|
881
|
+
const directness = topTrackScore > 0 ? directPrimaryScore / topTrackScore : 0;
|
|
882
|
+
const conflictPenalty = secondTrackScore > 0 && topTrackScore > 0 ? clamp(secondTrackScore / topTrackScore, 0, 1) * 0.25 : 0;
|
|
883
|
+
let confidence = clamp(0.2 + precision * 0.4 + margin * 0.22 + directness * 0.28 - conflictPenalty, 0.05, 0.98);
|
|
884
|
+
let primaryTrackStatus = "asserted";
|
|
885
|
+
if (primaryTrack !== neutralTrack && (directPrimaryScore < DIRECT_PRIMARY_MIN || confidence < ASSERTED_CONFIDENCE_MIN)) {
|
|
750
886
|
primaryTrack = neutralTrack;
|
|
751
|
-
|
|
887
|
+
primaryTrackStatus = "neutral_fallback";
|
|
888
|
+
confidence = Math.min(confidence, 0.42);
|
|
752
889
|
}
|
|
753
|
-
confidence = Number(confidence.toFixed(3));
|
|
754
890
|
const systems = inferSystems({
|
|
755
891
|
relPath: input.relPath,
|
|
756
892
|
text: [input.title, input.text ?? "", ...input.files ?? [], ...input.assetRefs ?? []].join("\n"),
|
|
757
893
|
files: input.files
|
|
758
894
|
});
|
|
895
|
+
const outputEvidence = withNeutralEvidence(
|
|
896
|
+
evidence.sort((a, b) => b.weight - a.weight || a.label.localeCompare(b.label)).slice(0, 60),
|
|
897
|
+
neutralTrack,
|
|
898
|
+
primaryTrackStatus
|
|
899
|
+
);
|
|
900
|
+
const confidenceRounded = Number(confidence.toFixed(3));
|
|
901
|
+
const confidenceBucket = buildConfidenceBucket(confidenceRounded);
|
|
759
902
|
return {
|
|
760
903
|
primaryTrack,
|
|
904
|
+
primaryTrackStatus,
|
|
761
905
|
facets: {
|
|
762
|
-
domain: sortedFacet(facetScores, "domain"),
|
|
763
|
-
hardware: sortedFacet(facetScores, "hardware"),
|
|
764
|
-
technique: sortedFacet(facetScores, "technique"),
|
|
765
|
-
abstraction: sortedFacet(facetScores, "abstraction"),
|
|
766
|
-
artifact: sortedFacet(facetScores, "artifact"),
|
|
906
|
+
domain: sortedFacet(facetScores, "domain", DOMAIN_DIRECT_MIN),
|
|
907
|
+
hardware: sortedFacet(facetScores, "hardware", 0.75),
|
|
908
|
+
technique: sortedFacet(facetScores, "technique", 0),
|
|
909
|
+
abstraction: sortedFacet(facetScores, "abstraction", 0),
|
|
910
|
+
artifact: sortedFacet(facetScores, "artifact", 0),
|
|
767
911
|
systems,
|
|
768
912
|
scope: input.scope
|
|
769
913
|
},
|
|
770
|
-
confidence,
|
|
771
|
-
|
|
914
|
+
confidence: confidenceRounded,
|
|
915
|
+
confidenceBucket,
|
|
916
|
+
evidence: outputEvidence
|
|
772
917
|
};
|
|
773
918
|
}
|
|
774
919
|
|
|
775
920
|
// src/search/minisearch.ts
|
|
776
|
-
import
|
|
921
|
+
import path4 from "path";
|
|
777
922
|
import MiniSearch from "minisearch";
|
|
778
923
|
function oscarTokenizer(text) {
|
|
779
924
|
return text.toLowerCase().split(/[^0-9a-zA-Z_\-$]+/).filter(Boolean);
|
|
@@ -790,7 +935,7 @@ function oscarProcessTerm(term) {
|
|
|
790
935
|
function inferCombineMode(query) {
|
|
791
936
|
return query.trim().split(/\s+/).filter(Boolean).length <= 1 ? "OR" : "AND";
|
|
792
937
|
}
|
|
793
|
-
function
|
|
938
|
+
function codeUri2(scope, relPath) {
|
|
794
939
|
return `code://${scope}/${relPath.replace(/\\/g, "/")}`;
|
|
795
940
|
}
|
|
796
941
|
function extractDeclarationInfo(text) {
|
|
@@ -877,7 +1022,7 @@ async function buildSearchIndex(state) {
|
|
|
877
1022
|
const roots = [
|
|
878
1023
|
{ scope: "tutorial", absRoot: state.tutorialsRoot },
|
|
879
1024
|
{ scope: "sample", absRoot: state.samplesRoot, uriPrefix: "samples" },
|
|
880
|
-
{ scope: "oscar", absRoot:
|
|
1025
|
+
{ scope: "oscar", absRoot: path4.join(state.oscar64Root, "include"), uriPrefix: "include" }
|
|
881
1026
|
];
|
|
882
1027
|
for (const root of roots) {
|
|
883
1028
|
let files = [];
|
|
@@ -890,6 +1035,7 @@ async function buildSearchIndex(state) {
|
|
|
890
1035
|
const rel = abs.replace(root.absRoot + "/", "").replace(/\\/g, "/");
|
|
891
1036
|
const uriRel = root.uriPrefix ? `${root.uriPrefix}/${rel}` : rel;
|
|
892
1037
|
if (!isExposedPath(rel)) continue;
|
|
1038
|
+
if (root.scope === "oscar" && !uriRel.toLowerCase().endsWith(".h")) continue;
|
|
893
1039
|
let text;
|
|
894
1040
|
try {
|
|
895
1041
|
text = await safeReadText(abs);
|
|
@@ -903,8 +1049,8 @@ async function buildSearchIndex(state) {
|
|
|
903
1049
|
id: `${root.scope}:${uriRel}`,
|
|
904
1050
|
source: "code",
|
|
905
1051
|
resultType,
|
|
906
|
-
uri: root.scope === "sample" && !uriRel.startsWith("samples/") ?
|
|
907
|
-
title:
|
|
1052
|
+
uri: root.scope === "sample" && !uriRel.startsWith("samples/") ? codeUri2("oscar", uriRel) : codeUri2(root.scope === "oscar" ? "oscar" : root.scope, uriRel),
|
|
1053
|
+
title: path4.basename(uriRel),
|
|
908
1054
|
preview: {
|
|
909
1055
|
summary: makeExcerpt(text, 700),
|
|
910
1056
|
...declarationInfo.signature ? { signature: declarationInfo.signature } : {},
|
|
@@ -916,7 +1062,7 @@ ${text}`,
|
|
|
916
1062
|
referencedFiles: extractReferencedFiles(text),
|
|
917
1063
|
classification: collectionClassification ?? classifyV2({
|
|
918
1064
|
scope: root.scope === "oscar" ? "sample" : root.scope,
|
|
919
|
-
title:
|
|
1065
|
+
title: path4.basename(uriRel),
|
|
920
1066
|
relPath: uriRel,
|
|
921
1067
|
text
|
|
922
1068
|
})
|
|
@@ -940,7 +1086,7 @@ async function readFirstLines(filePath, maxLines = 120) {
|
|
|
940
1086
|
return text.split(/\r?\n/).slice(0, maxLines).join("\n");
|
|
941
1087
|
}
|
|
942
1088
|
function isPrimaryCodeFile(relPath) {
|
|
943
|
-
const ext =
|
|
1089
|
+
const ext = path5.extname(relPath).toLowerCase();
|
|
944
1090
|
return ext === ".c" || ext === ".cpp" || ext === ".s" || ext === ".asm";
|
|
945
1091
|
}
|
|
946
1092
|
function pickPrimaryFiles(files) {
|
|
@@ -954,7 +1100,7 @@ function pickPrimaryFiles(files) {
|
|
|
954
1100
|
});
|
|
955
1101
|
return mainFirst;
|
|
956
1102
|
}
|
|
957
|
-
const readme = normalized.filter((f) => /^readme(\.[^/]+)?$/i.test(
|
|
1103
|
+
const readme = normalized.filter((f) => /^readme(\.[^/]+)?$/i.test(path5.basename(f)));
|
|
958
1104
|
if (readme.length > 0) return readme;
|
|
959
1105
|
return normalized;
|
|
960
1106
|
}
|
|
@@ -971,7 +1117,7 @@ function buildKeywords(input) {
|
|
|
971
1117
|
for (const term of oscarProcessTerm(token)) if (term.length >= 3) keywords.add(term);
|
|
972
1118
|
}
|
|
973
1119
|
for (const rel of input.files) {
|
|
974
|
-
for (const token of oscarTokenizer(
|
|
1120
|
+
for (const token of oscarTokenizer(path5.basename(rel, path5.extname(rel)))) {
|
|
975
1121
|
for (const term of oscarProcessTerm(token)) if (term.length >= 3) keywords.add(term);
|
|
976
1122
|
}
|
|
977
1123
|
}
|
|
@@ -1000,21 +1146,21 @@ async function buildCodeCollectionIndex(root, options) {
|
|
|
1000
1146
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => !name.startsWith(".") && !exclude.has(name.toLowerCase())).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
1001
1147
|
const items = [];
|
|
1002
1148
|
for (const folderName of dirs) {
|
|
1003
|
-
const absDir =
|
|
1149
|
+
const absDir = path5.join(root, folderName);
|
|
1004
1150
|
const filesAbs = await listFilesRecursive(absDir);
|
|
1005
|
-
const files = filesAbs.map((f) =>
|
|
1151
|
+
const files = filesAbs.map((f) => path5.relative(absDir, f)).filter((f) => isExposedPath(f));
|
|
1006
1152
|
if (files.length === 0) continue;
|
|
1007
1153
|
const { id, title } = parseCollectionIdentity(options.scope, folderName);
|
|
1008
1154
|
const primaryFiles = pickPrimaryFiles(files);
|
|
1009
1155
|
for (const primaryFile of primaryFiles) {
|
|
1010
1156
|
let previewText = "";
|
|
1011
1157
|
try {
|
|
1012
|
-
previewText = await readFirstLines(
|
|
1158
|
+
previewText = await readFirstLines(path5.join(absDir, primaryFile));
|
|
1013
1159
|
} catch {
|
|
1014
1160
|
previewText = "";
|
|
1015
1161
|
}
|
|
1016
|
-
const entryTitle = primaryFiles.length > 1 ? `${title} - ${
|
|
1017
|
-
const entryId = primaryFiles.length > 1 ? `${id}-${slugify(
|
|
1162
|
+
const entryTitle = primaryFiles.length > 1 ? `${title} - ${path5.basename(primaryFile, path5.extname(primaryFile))}` : title;
|
|
1163
|
+
const entryId = primaryFiles.length > 1 ? `${id}-${slugify(path5.basename(primaryFile, path5.extname(primaryFile)))}` : id;
|
|
1018
1164
|
const assetRefs = extractAssetRefs(previewText);
|
|
1019
1165
|
const classification = classifyV2({
|
|
1020
1166
|
scope: options.scope,
|
|
@@ -1061,7 +1207,7 @@ async function buildSamplesIndex(samplesRoot) {
|
|
|
1061
1207
|
// src/sync/github-http.ts
|
|
1062
1208
|
import AdmZip from "adm-zip";
|
|
1063
1209
|
import fs3 from "fs/promises";
|
|
1064
|
-
import
|
|
1210
|
+
import path6 from "path";
|
|
1065
1211
|
var LOCK_TTL_MS = 30 * 60 * 1e3;
|
|
1066
1212
|
var LOCK_WAIT_MS = 5 * 60 * 1e3;
|
|
1067
1213
|
var LOCK_RETRY_MS = 250;
|
|
@@ -1107,7 +1253,7 @@ async function unpackZipToDir(zipBuf, targetDir) {
|
|
|
1107
1253
|
await fs3.mkdir(abs, { recursive: true });
|
|
1108
1254
|
continue;
|
|
1109
1255
|
}
|
|
1110
|
-
await fs3.mkdir(
|
|
1256
|
+
await fs3.mkdir(path6.dirname(abs), { recursive: true });
|
|
1111
1257
|
await fs3.writeFile(abs, entry.getData());
|
|
1112
1258
|
}
|
|
1113
1259
|
await fs3.rm(targetDir, { recursive: true, force: true });
|
|
@@ -1146,9 +1292,9 @@ async function acquireRepoLock(lockPath) {
|
|
|
1146
1292
|
async function syncRepo(cfg, force = false) {
|
|
1147
1293
|
await fs3.mkdir(META_DIR, { recursive: true });
|
|
1148
1294
|
await fs3.mkdir(SOURCES_DIR, { recursive: true });
|
|
1149
|
-
const metaPath =
|
|
1150
|
-
const lockPath =
|
|
1151
|
-
const sourceRoot =
|
|
1295
|
+
const metaPath = path6.join(META_DIR, `${cfg.key}.json`);
|
|
1296
|
+
const lockPath = path6.join(META_DIR, `${cfg.key}.lock`);
|
|
1297
|
+
const sourceRoot = path6.join(SOURCES_DIR, cfg.key, "current");
|
|
1152
1298
|
const existingBeforeLock = await readJson(metaPath);
|
|
1153
1299
|
const now = Date.now();
|
|
1154
1300
|
const stale = !existingBeforeLock?.lastCheckedAt || now - existingBeforeLock.lastCheckedAt > UPDATE_TTL_MS;
|
|
@@ -1272,8 +1418,8 @@ async function initState(forceRefresh = false) {
|
|
|
1272
1418
|
const repos = await syncAllRepos(forceRefresh);
|
|
1273
1419
|
const oscar64Root = repos.oscar64.rootDir;
|
|
1274
1420
|
const tutorialsRoot = repos.OscarTutorials.rootDir;
|
|
1275
|
-
const samplesRoot =
|
|
1276
|
-
const manualPath =
|
|
1421
|
+
const samplesRoot = path7.join(oscar64Root, "samples");
|
|
1422
|
+
const manualPath = path7.join(oscar64Root, "oscar64.md");
|
|
1277
1423
|
const manualText = await safeReadText(manualPath);
|
|
1278
1424
|
const { lines: manualLines, sections: manualSections } = parseManualSections(manualText);
|
|
1279
1425
|
const tutorials = await buildTutorialsIndex(tutorialsRoot);
|
|
@@ -1355,13 +1501,13 @@ ${body}`
|
|
|
1355
1501
|
}
|
|
1356
1502
|
}
|
|
1357
1503
|
if (type === "all" || type === "headers") {
|
|
1358
|
-
const includeRoot =
|
|
1504
|
+
const includeRoot = path8.join(state.oscar64Root, "include");
|
|
1359
1505
|
try {
|
|
1360
1506
|
const files = await listFilesRecursive(includeRoot);
|
|
1361
1507
|
for (const abs of files) {
|
|
1362
|
-
const relFromOscar =
|
|
1508
|
+
const relFromOscar = path8.relative(state.oscar64Root, abs).replace(/\\/g, "/");
|
|
1363
1509
|
if (!relFromOscar.toLowerCase().endsWith(".h")) continue;
|
|
1364
|
-
const relFromInclude =
|
|
1510
|
+
const relFromInclude = path8.relative(includeRoot, abs).replace(/\\/g, "/");
|
|
1365
1511
|
const systems = inferSystems({ relPath: relFromOscar });
|
|
1366
1512
|
if (!matchesSystemFilter(systems, system)) continue;
|
|
1367
1513
|
items.push({
|
|
@@ -1406,80 +1552,6 @@ var listIndexesTool = createTool({
|
|
|
1406
1552
|
// src/mcp/tools/read-uri.tool.ts
|
|
1407
1553
|
import fs5 from "fs/promises";
|
|
1408
1554
|
import { createTool as createTool2 } from "@mastra/core/tools";
|
|
1409
|
-
|
|
1410
|
-
// src/mcp/tools/uri.ts
|
|
1411
|
-
import path8 from "path";
|
|
1412
|
-
function codeUri2(scope, relPath) {
|
|
1413
|
-
return `code://${scope}/${relPath.replace(/\\/g, "/")}`;
|
|
1414
|
-
}
|
|
1415
|
-
function extLower(filePath) {
|
|
1416
|
-
return path8.extname(filePath).toLowerCase();
|
|
1417
|
-
}
|
|
1418
|
-
function isBinaryExposedPath(filePath) {
|
|
1419
|
-
return BINARY_EXPOSED_EXTS.has(extLower(filePath));
|
|
1420
|
-
}
|
|
1421
|
-
function guessMimeType(filePath) {
|
|
1422
|
-
switch (extLower(filePath)) {
|
|
1423
|
-
case ".md":
|
|
1424
|
-
return "text/markdown";
|
|
1425
|
-
case ".txt":
|
|
1426
|
-
return "text/plain";
|
|
1427
|
-
case ".c":
|
|
1428
|
-
case ".h":
|
|
1429
|
-
case ".cpp":
|
|
1430
|
-
case ".s":
|
|
1431
|
-
case ".asm":
|
|
1432
|
-
case ".inc":
|
|
1433
|
-
return "text/plain";
|
|
1434
|
-
case ".sid":
|
|
1435
|
-
return "audio/prs.sid";
|
|
1436
|
-
case ".spd":
|
|
1437
|
-
case ".ctm":
|
|
1438
|
-
case ".mcimg":
|
|
1439
|
-
case ".bin":
|
|
1440
|
-
return "application/octet-stream";
|
|
1441
|
-
default:
|
|
1442
|
-
return "application/octet-stream";
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
function uriToAbsPath(state, uri) {
|
|
1446
|
-
if (uri.startsWith("code://oscar/")) {
|
|
1447
|
-
return safeJoin(state.oscar64Root, uri.replace("code://oscar/", ""));
|
|
1448
|
-
}
|
|
1449
|
-
if (uri.startsWith("code://sample/")) {
|
|
1450
|
-
return safeJoin(state.oscar64Root, uri.replace("code://sample/", ""));
|
|
1451
|
-
}
|
|
1452
|
-
if (uri.startsWith("code://tutorial/")) {
|
|
1453
|
-
return safeJoin(state.tutorialsRoot, uri.replace("code://tutorial/", ""));
|
|
1454
|
-
}
|
|
1455
|
-
return null;
|
|
1456
|
-
}
|
|
1457
|
-
function maybeParseAsFileUri(state, uri) {
|
|
1458
|
-
if (uri.startsWith("code://oscar/")) {
|
|
1459
|
-
const relPath = uri.replace("code://oscar/", "");
|
|
1460
|
-
return {
|
|
1461
|
-
absPath: safeJoin(state.oscar64Root, relPath),
|
|
1462
|
-
relPath
|
|
1463
|
-
};
|
|
1464
|
-
}
|
|
1465
|
-
if (uri.startsWith("code://sample/")) {
|
|
1466
|
-
const relPath = uri.replace("code://sample/", "");
|
|
1467
|
-
return {
|
|
1468
|
-
absPath: safeJoin(state.oscar64Root, relPath),
|
|
1469
|
-
relPath
|
|
1470
|
-
};
|
|
1471
|
-
}
|
|
1472
|
-
if (uri.startsWith("code://tutorial/")) {
|
|
1473
|
-
const relPath = uri.replace("code://tutorial/", "");
|
|
1474
|
-
return {
|
|
1475
|
-
absPath: safeJoin(state.tutorialsRoot, relPath),
|
|
1476
|
-
relPath
|
|
1477
|
-
};
|
|
1478
|
-
}
|
|
1479
|
-
return null;
|
|
1480
|
-
}
|
|
1481
|
-
|
|
1482
|
-
// src/mcp/tools/read-uri.tool.ts
|
|
1483
1555
|
async function readResourceUriFromState(uri) {
|
|
1484
1556
|
const state = await getStateSnapshot();
|
|
1485
1557
|
const resources = makeResources(() => state);
|
|
@@ -1526,6 +1598,17 @@ async function executeReadUri(context) {
|
|
|
1526
1598
|
}
|
|
1527
1599
|
}
|
|
1528
1600
|
const rel = fileTarget.relPath;
|
|
1601
|
+
if (uri.startsWith("code://oscar/") && !isOscarIncludeHeaderPath(rel)) {
|
|
1602
|
+
return {
|
|
1603
|
+
ok: false,
|
|
1604
|
+
error: {
|
|
1605
|
+
code: "UNSUPPORTED_SCOPE",
|
|
1606
|
+
message: "Implementation files under include/ are not exposed.",
|
|
1607
|
+
hint: "Use the corresponding header file (for example .h) or tutorial/sample source instead.",
|
|
1608
|
+
recoverable: true
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1529
1612
|
const allowText = isExposedPath(rel);
|
|
1530
1613
|
const allowBinary = isBinaryExposedPath(rel);
|
|
1531
1614
|
if (!allowText && !allowBinary) {
|
|
@@ -1665,7 +1748,7 @@ async function resolveReferencedUris(state, sourceUri, refs) {
|
|
|
1665
1748
|
const addCandidate = (scope, rel) => {
|
|
1666
1749
|
const clean = rel.replace(/^\/+/, "");
|
|
1667
1750
|
if (!clean) return;
|
|
1668
|
-
candidates.push(
|
|
1751
|
+
candidates.push(codeUri(scope, clean));
|
|
1669
1752
|
};
|
|
1670
1753
|
if (ref.startsWith("./") || ref.startsWith("../")) {
|
|
1671
1754
|
addCandidate(parsed.scope, path9.posix.normalize(path9.posix.join(currentDir, ref)));
|
|
@@ -1806,7 +1889,7 @@ function hasImplementationIntent(query) {
|
|
|
1806
1889
|
}
|
|
1807
1890
|
function isActionableResult(result) {
|
|
1808
1891
|
const uri = String(result?.uri ?? "");
|
|
1809
|
-
if (uri.startsWith("code://oscar/include/")) return
|
|
1892
|
+
if (uri.startsWith("code://oscar/include/")) return uri.toLowerCase().endsWith(".h");
|
|
1810
1893
|
const signature = String(result?.preview?.signature ?? "");
|
|
1811
1894
|
if (signature.trim().length > 0) return true;
|
|
1812
1895
|
return uri.startsWith("code://tutorial/") || uri.startsWith("code://sample/");
|
|
@@ -1842,11 +1925,13 @@ function computeIntentBoost(result, query) {
|
|
|
1842
1925
|
}
|
|
1843
1926
|
function normalizePrimaryTrackForConfidence(details) {
|
|
1844
1927
|
const neutralTrack = details.facets.scope === "manual" ? "compiler_language" : "fundamentals";
|
|
1845
|
-
if (details.confidence >= 0.
|
|
1928
|
+
if (details.confidence >= 0.55) return details;
|
|
1846
1929
|
if (details.primary_track === neutralTrack) return details;
|
|
1847
1930
|
return {
|
|
1848
1931
|
...details,
|
|
1849
|
-
primary_track: neutralTrack
|
|
1932
|
+
primary_track: neutralTrack,
|
|
1933
|
+
primary_track_status: "neutral_fallback",
|
|
1934
|
+
confidence_bucket: "low"
|
|
1850
1935
|
};
|
|
1851
1936
|
}
|
|
1852
1937
|
function normalizeSearchResults(raw) {
|
|
@@ -1868,6 +1953,7 @@ async function executeSearch(context) {
|
|
|
1868
1953
|
const state = await getStateSnapshot();
|
|
1869
1954
|
const toFallbackClassification = () => ({
|
|
1870
1955
|
primary_track: "fundamentals",
|
|
1956
|
+
primary_track_status: "neutral_fallback",
|
|
1871
1957
|
facets: {
|
|
1872
1958
|
domain: [],
|
|
1873
1959
|
hardware: [],
|
|
@@ -1878,7 +1964,19 @@ async function executeSearch(context) {
|
|
|
1878
1964
|
scope: "sample"
|
|
1879
1965
|
},
|
|
1880
1966
|
confidence: 0.25,
|
|
1881
|
-
|
|
1967
|
+
confidence_bucket: "low",
|
|
1968
|
+
evidence: [
|
|
1969
|
+
{
|
|
1970
|
+
label: "fundamentals",
|
|
1971
|
+
facet: "primary_track",
|
|
1972
|
+
reason: "Fallback classification due to missing source metadata",
|
|
1973
|
+
weight: 3,
|
|
1974
|
+
matched_on: "fallback",
|
|
1975
|
+
source_field: "derived",
|
|
1976
|
+
matched_text: "fallback",
|
|
1977
|
+
direct: true
|
|
1978
|
+
}
|
|
1979
|
+
]
|
|
1882
1980
|
});
|
|
1883
1981
|
const toDetails = (classification) => {
|
|
1884
1982
|
const fallback = toFallbackClassification();
|
|
@@ -1894,6 +1992,7 @@ async function executeSearch(context) {
|
|
|
1894
1992
|
if (!classification || typeof classification !== "object") return fallback;
|
|
1895
1993
|
return normalizePrimaryTrackForConfidence({
|
|
1896
1994
|
primary_track: String(classification.primaryTrack ?? fallback.primary_track),
|
|
1995
|
+
primary_track_status: classification.primaryTrackStatus === "asserted" || classification.primaryTrackStatus === "neutral_fallback" ? classification.primaryTrackStatus : fallback.primary_track_status,
|
|
1897
1996
|
facets: {
|
|
1898
1997
|
domain: Array.isArray(classification.facets?.domain) ? classification.facets.domain.map(String) : [],
|
|
1899
1998
|
hardware: Array.isArray(classification.facets?.hardware) ? classification.facets.hardware.map(String) : [],
|
|
@@ -1904,19 +2003,27 @@ async function executeSearch(context) {
|
|
|
1904
2003
|
scope: classification.facets?.scope === "tutorial" || classification.facets?.scope === "sample" || classification.facets?.scope === "manual" ? classification.facets.scope : fallback.facets.scope
|
|
1905
2004
|
},
|
|
1906
2005
|
confidence: Number(classification.confidence ?? fallback.confidence),
|
|
2006
|
+
confidence_bucket: classification.confidenceBucket === "low" || classification.confidenceBucket === "medium" || classification.confidenceBucket === "high" ? classification.confidenceBucket : fallback.confidence_bucket,
|
|
1907
2007
|
evidence: Array.isArray(classification.evidence) ? classification.evidence.map((item) => ({
|
|
1908
2008
|
label: String(item?.label ?? ""),
|
|
2009
|
+
facet: item?.facet === "primary_track" || item?.facet === "domain" || item?.facet === "hardware" || item?.facet === "technique" || item?.facet === "abstraction" || item?.facet === "artifact" ? item.facet : "primary_track",
|
|
1909
2010
|
reason: String(item?.reason ?? ""),
|
|
1910
2011
|
weight: Number(item?.weight ?? 0),
|
|
1911
|
-
matched_on: String(item?.matchedOn ?? item?.matched_on ?? "")
|
|
2012
|
+
matched_on: String(item?.matchedOn ?? item?.matched_on ?? ""),
|
|
2013
|
+
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",
|
|
2014
|
+
matched_text: String(item?.matchedText ?? item?.matched_text ?? ""),
|
|
2015
|
+
direct: Boolean(item?.direct)
|
|
1912
2016
|
})) : []
|
|
1913
2017
|
});
|
|
1914
2018
|
};
|
|
1915
2019
|
const toSummary = (details) => ({
|
|
1916
|
-
|
|
1917
|
-
|
|
2020
|
+
track: details.primary_track,
|
|
2021
|
+
track_status: details.primary_track_status,
|
|
2022
|
+
confidence_bucket: details.confidence_bucket,
|
|
2023
|
+
confidence: details.confidence,
|
|
2024
|
+
domains: details.facets.domain,
|
|
1918
2025
|
hardware: details.facets.hardware,
|
|
1919
|
-
|
|
2026
|
+
techniques: details.facets.technique,
|
|
1920
2027
|
systems: details.facets.systems,
|
|
1921
2028
|
scope: details.facets.scope
|
|
1922
2029
|
});
|
|
@@ -1962,7 +2069,11 @@ async function executeSearch(context) {
|
|
|
1962
2069
|
};
|
|
1963
2070
|
})
|
|
1964
2071
|
);
|
|
1965
|
-
const hits = mapped.filter((entry) =>
|
|
2072
|
+
const hits = mapped.filter((entry) => {
|
|
2073
|
+
const uri = String(entry.hit.uri ?? "");
|
|
2074
|
+
if (uri.startsWith("code://oscar/include/") && !uri.toLowerCase().endsWith(".h")) return false;
|
|
2075
|
+
return true;
|
|
2076
|
+
}).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);
|
|
1966
2077
|
return {
|
|
1967
2078
|
ok: true,
|
|
1968
2079
|
data: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oscar64-mcp-docs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -26,15 +26,15 @@
|
|
|
26
26
|
"test:watch": "vitest"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@mastra/core": "^
|
|
30
|
-
"@mastra/mcp": "^0.
|
|
29
|
+
"@mastra/core": "^1.9.0",
|
|
30
|
+
"@mastra/mcp": "^1.0.3",
|
|
31
31
|
"adm-zip": "^0.5.16",
|
|
32
32
|
"minisearch": "^7.1.1",
|
|
33
33
|
"zod": "^3.23.8"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/adm-zip": "^0.5.5",
|
|
37
|
-
"mastra": "^
|
|
37
|
+
"mastra": "^1.3.6",
|
|
38
38
|
"tsup": "^8.3.5",
|
|
39
39
|
"tsx": "^4.19.2",
|
|
40
40
|
"typescript": "^5.6.3",
|