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.
Files changed (2) hide show
  1. package/dist/stdio.js +288 -177
  2. 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/core/source).",
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 path7 from "path";
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("Classification label produced by a matched rule."),
321
- reason: z.string().describe("Reason text explaining the matched rule."),
322
- weight: z.number().describe("Weighted contribution of this evidence item."),
323
- matched_on: z.string().describe("Rule identifier that produced this evidence.")
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("Primary track for routing or filtering this result."),
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("Relative confidence for the selected primary track."),
337
- evidence: z.array(classificationEvidenceSchema).describe("Evidence items supporting this classification.")
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
- primary_track: z.string().describe("Primary track for quick filtering."),
341
- domain: z.array(z.string()).describe("Top-level functional domains."),
342
- hardware: z.array(z.string()).describe("Relevant hardware domains."),
343
- technique: z.array(z.string()).describe("Top techniques for this result."),
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 path6 from "path";
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 path4 from "path";
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
- function addScore(scores, facet, label, value) {
660
- if (!scores.has(facet)) scores.set(facet, /* @__PURE__ */ new Map());
661
- const bucket = scores.get(facet);
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 sortedFacet(scores, facet) {
665
- const bucket = scores.get(facet);
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 haystack = [
697
- input.title,
698
- input.relPath ?? "",
699
- input.text ?? "",
700
- ...input.files ?? [],
701
- ...input.assetRefs ?? []
702
- ].join("\n").toLowerCase();
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.set(seeded.primaryTrack, (trackScores.get(seeded.primaryTrack) ?? 0) + 2);
708
- if (seeded.domain) addScore(facetScores, "domain", seeded.domain, 2);
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
- if (!regex.test(haystack)) continue;
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.set(emit.label, (trackScores.get(emit.label) ?? 0) + weighted);
853
+ addScore(trackScores, emit.label, weighted);
854
+ if (isDirect) addScore(trackDirectScores, emit.label, weighted);
720
855
  } else {
721
- addScore(facetScores, emit.kind, emit.label, weighted);
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 = trackScores.get(primaryTrack) ?? 0;
875
+ const topTrackScore = rankedTracks[0]?.[1] ?? 0;
738
876
  const secondTrackScore = rankedTracks[1]?.[1] ?? 0;
739
- const hasStrongEvidence = hasStrongPrimaryEvidence(evidence, primaryTrack);
740
- let confidence = trackTotal > 0 ? topTrackScore / trackTotal : 0.25;
741
- if (!hasStrongEvidence) {
742
- confidence = Math.min(confidence, 0.62);
743
- }
744
- if (secondTrackScore > 0) {
745
- const ratio = topTrackScore / secondTrackScore;
746
- if (ratio < 1.2) confidence = Math.min(confidence, 0.45);
747
- else if (ratio < 1.5) confidence = Math.min(confidence, 0.58);
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
- confidence = Math.min(confidence, 0.45);
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
- evidence: evidence.sort((a, b) => b.weight - a.weight || a.label.localeCompare(b.label)).slice(0, 40)
914
+ confidence: confidenceRounded,
915
+ confidenceBucket,
916
+ evidence: outputEvidence
772
917
  };
773
918
  }
774
919
 
775
920
  // src/search/minisearch.ts
776
- import path3 from "path";
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 codeUri(scope, relPath) {
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: path3.join(state.oscar64Root, "include"), uriPrefix: "include" }
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/") ? codeUri("oscar", uriRel) : codeUri(root.scope === "oscar" ? "oscar" : root.scope, uriRel),
907
- title: path3.basename(uriRel),
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: path3.basename(uriRel),
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 = path4.extname(relPath).toLowerCase();
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(path4.basename(f)));
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(path4.basename(rel, path4.extname(rel)))) {
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 = path4.join(root, folderName);
1149
+ const absDir = path5.join(root, folderName);
1004
1150
  const filesAbs = await listFilesRecursive(absDir);
1005
- const files = filesAbs.map((f) => path4.relative(absDir, f)).filter((f) => isExposedPath(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(path4.join(absDir, primaryFile));
1158
+ previewText = await readFirstLines(path5.join(absDir, primaryFile));
1013
1159
  } catch {
1014
1160
  previewText = "";
1015
1161
  }
1016
- const entryTitle = primaryFiles.length > 1 ? `${title} - ${path4.basename(primaryFile, path4.extname(primaryFile))}` : title;
1017
- const entryId = primaryFiles.length > 1 ? `${id}-${slugify(path4.basename(primaryFile, path4.extname(primaryFile)))}` : id;
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 path5 from "path";
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(path5.dirname(abs), { recursive: true });
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 = path5.join(META_DIR, `${cfg.key}.json`);
1150
- const lockPath = path5.join(META_DIR, `${cfg.key}.lock`);
1151
- const sourceRoot = path5.join(SOURCES_DIR, cfg.key, "current");
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 = path6.join(oscar64Root, "samples");
1276
- const manualPath = path6.join(oscar64Root, "oscar64.md");
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 = path7.join(state.oscar64Root, "include");
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 = path7.relative(state.oscar64Root, abs).replace(/\\/g, "/");
1508
+ const relFromOscar = path8.relative(state.oscar64Root, abs).replace(/\\/g, "/");
1363
1509
  if (!relFromOscar.toLowerCase().endsWith(".h")) continue;
1364
- const relFromInclude = path7.relative(includeRoot, abs).replace(/\\/g, "/");
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(codeUri2(scope, clean));
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 true;
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.5) return details;
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
- evidence: []
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
- primary_track: details.primary_track,
1917
- domain: details.facets.domain,
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
- technique: details.facets.technique,
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) => 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);
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.2",
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": "^0.10.7",
30
- "@mastra/mcp": "^0.10.7",
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": "^0.10.7",
37
+ "mastra": "^1.3.6",
38
38
  "tsup": "^8.3.5",
39
39
  "tsx": "^4.19.2",
40
40
  "typescript": "^5.6.3",