sceneview-mcp 4.0.14 → 4.0.15

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.
@@ -4,5 +4,5 @@
4
4
  // build time so the MCP server, telemetry, and the install-snippet
5
5
  // generators all report the actually-published versions instead of
6
6
  // stale hardcoded constants. See #941.
7
- export const PACKAGE_VERSION = "4.0.14";
8
- export const LATEST_SCENEVIEW_RELEASE = "4.22.0";
7
+ export const PACKAGE_VERSION = "4.0.15";
8
+ export const LATEST_SCENEVIEW_RELEASE = "4.25.0";
@@ -414,18 +414,14 @@ SceneView Flutter uses **PlatformView** to embed native SceneView (Android: Fila
414
414
 
415
415
  ### 1. Dependencies
416
416
 
417
- > **Note:** the 4.0 line of \`sceneview_flutter\` is not yet on pub.dev
418
- > (registry still holds an unrelated 0.0.1 demo) see #923. Use the
419
- > git dependency snippet below for now.
417
+ > **Note:** the plugin is published on pub.dev as \`flutter_sceneview\`
418
+ > (the pub.dev names \`sceneview\` and \`sceneview_flutter\` are unrelated
419
+ > third-party uploads do not use them).
420
420
 
421
421
  \`\`\`yaml
422
422
  # pubspec.yaml
423
423
  dependencies:
424
- sceneview_flutter:
425
- git:
426
- url: https://github.com/sceneview/sceneview
427
- path: flutter/sceneview_flutter
428
- ref: v${LATEST_SCENEVIEW_RELEASE}
424
+ flutter_sceneview: ^${LATEST_SCENEVIEW_RELEASE}
429
425
  \`\`\`
430
426
 
431
427
  ### 2. Android Setup
@@ -483,13 +479,9 @@ const FLUTTER_AR = `## SceneView Flutter — AR Setup
483
479
  ### 1. Dependencies
484
480
 
485
481
  \`\`\`yaml
486
- # pub.dev publish for 4.x is pending see #923. Use the git ref:
482
+ # pubspec.yaml published on pub.dev (the names sceneview / sceneview_flutter are unrelated third-party uploads)
487
483
  dependencies:
488
- sceneview_flutter:
489
- git:
490
- url: https://github.com/sceneview/sceneview
491
- path: flutter/sceneview_flutter
492
- ref: v${LATEST_SCENEVIEW_RELEASE}
484
+ flutter_sceneview: ^${LATEST_SCENEVIEW_RELEASE}
493
485
  \`\`\`
494
486
 
495
487
  ### 2. Android Manifest
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Symbol-existence lookups over the generated public-API index (#2760).
3
+ *
4
+ * The index (`src/generated/symbols.ts`) is parsed from the committed
5
+ * binary-compatibility `.api` dumps at build time — see
6
+ * `scripts/generate-symbols.js` for the full provenance chain. Everything
7
+ * here answers one question for the validator: "does this identifier exist
8
+ * in the real Android/KMP SceneView API?", with edit-distance suggestions
9
+ * when it doesn't.
10
+ *
11
+ * Scope, stated honestly: the dumps cover `sceneview`, `arsceneview` and
12
+ * `sceneview-core` (Android/KMP). SceneViewSwift and sceneview-web have no
13
+ * committed dump, so Swift/Web snippets are NOT symbol-checked.
14
+ */
15
+ import { SYMBOLS } from "./generated/symbols.js";
16
+ export const SCENEVIEW_NAMESPACE = "io.github.sceneview";
17
+ // ─── Lazily-built lookup sets ────────────────────────────────────────────────
18
+ let typeNamesCache = null;
19
+ let classBySimpleNameCache = null;
20
+ /**
21
+ * Every PascalCase identifier a Kotlin user can legitimately reference as a
22
+ * type or declarative factory:
23
+ * - class simple names (`ModelNode`, `WallPlacement`, …),
24
+ * - PascalCase top-level functions — the composables (`SceneView`,
25
+ * `ARSceneView`, `WallPlacementScene`, `DynamicSkyNode`, …),
26
+ * - PascalCase members of the `*Scope` classes — the declarative node
27
+ * factories (`SceneScope.ModelNode`, `ARSceneScope.…`).
28
+ */
29
+ export function knownTypeNames() {
30
+ if (typeNamesCache)
31
+ return typeNamesCache;
32
+ const names = new Set();
33
+ for (const fqcn of Object.keys(SYMBOLS.classes)) {
34
+ const simple = fqcn.slice(fqcn.lastIndexOf(".") + 1);
35
+ if (/^[A-Z]/.test(simple))
36
+ names.add(simple);
37
+ }
38
+ for (const name of Object.keys(SYMBOLS.topLevel)) {
39
+ if (/^[A-Z]/.test(name))
40
+ names.add(name);
41
+ }
42
+ for (const [fqcn, entry] of Object.entries(SYMBOLS.classes)) {
43
+ if (!fqcn.endsWith("Scope"))
44
+ continue;
45
+ for (const member of entry.members) {
46
+ if (/^[A-Z]/.test(member))
47
+ names.add(member);
48
+ }
49
+ }
50
+ typeNamesCache = names;
51
+ return names;
52
+ }
53
+ /** Class simple name → the FQCNs that declare it (usually exactly one). */
54
+ function classBySimpleName() {
55
+ if (classBySimpleNameCache)
56
+ return classBySimpleNameCache;
57
+ const map = new Map();
58
+ for (const fqcn of Object.keys(SYMBOLS.classes)) {
59
+ const simple = fqcn.slice(fqcn.lastIndexOf(".") + 1);
60
+ const list = map.get(simple);
61
+ if (list)
62
+ list.push(fqcn);
63
+ else
64
+ map.set(simple, [fqcn]);
65
+ }
66
+ classBySimpleNameCache = map;
67
+ return map;
68
+ }
69
+ export function isKnownTypeName(name) {
70
+ return knownTypeNames().has(name);
71
+ }
72
+ /** Top-level function (any case) — composables and extension helpers. */
73
+ export function isKnownTopLevelFunction(name) {
74
+ return name in SYMBOLS.topLevel;
75
+ }
76
+ /** Every real `remember*` composable helper — suggestion pool for typos. */
77
+ export function rememberHelperNames() {
78
+ return Object.keys(SYMBOLS.topLevel).filter((name) => name.startsWith("remember"));
79
+ }
80
+ /**
81
+ * Resolve an `io.github.sceneview.*` import target.
82
+ *
83
+ * Returns "class" for an exact class FQCN, "package" for a package that
84
+ * exists (covers `import io.github.sceneview.node.*`), "member" for a
85
+ * `Class.member` or package-level function import, and null when nothing
86
+ * in the index matches. Callers must only pass SceneView-namespace paths —
87
+ * other namespaces are out of index scope.
88
+ */
89
+ export function resolveImport(path) {
90
+ if (path in SYMBOLS.classes)
91
+ return "class";
92
+ if (SYMBOLS.packages.includes(path))
93
+ return "package";
94
+ const lastDot = path.lastIndexOf(".");
95
+ if (lastDot === -1)
96
+ return null;
97
+ const parent = path.slice(0, lastDot);
98
+ const leaf = path.slice(lastDot + 1);
99
+ // `import io.github.sceneview.ar.rememberARCameraStream` — a top-level
100
+ // function imported from a package.
101
+ if (SYMBOLS.packages.includes(parent) && leaf in SYMBOLS.topLevel) {
102
+ return "member";
103
+ }
104
+ // A nested class member — accept when the parent class exists and
105
+ // declares it.
106
+ const parentClass = SYMBOLS.classes[parent];
107
+ if (parentClass?.members.includes(leaf))
108
+ return "member";
109
+ // `import io.github.sceneview.node.ModelNode.Companion` — the generator
110
+ // folds companion members into the host class and drops the `.Companion`
111
+ // entry, so resolve the companion itself through its host.
112
+ if (leaf === "Companion" && parent in SYMBOLS.classes)
113
+ return "member";
114
+ return null;
115
+ }
116
+ /** Union of member/property names declared by classes with this simple name. */
117
+ export function membersOfClass(simpleName) {
118
+ const fqcns = classBySimpleName().get(simpleName);
119
+ if (!fqcns)
120
+ return null;
121
+ const members = new Set();
122
+ for (const fqcn of fqcns) {
123
+ for (const m of SYMBOLS.classes[fqcn].members)
124
+ members.add(m);
125
+ }
126
+ return members;
127
+ }
128
+ // ─── Edit-distance suggestions ───────────────────────────────────────────────
129
+ /** Classic Levenshtein with a band cutoff — returns Infinity past `max`. */
130
+ export function editDistance(a, b, max) {
131
+ if (Math.abs(a.length - b.length) > max)
132
+ return Number.POSITIVE_INFINITY;
133
+ const la = a.length;
134
+ const lb = b.length;
135
+ let prev = Array.from({ length: lb + 1 }, (_, i) => i);
136
+ let curr = new Array(lb + 1);
137
+ for (let i = 1; i <= la; i++) {
138
+ curr[0] = i;
139
+ let rowMin = i;
140
+ for (let j = 1; j <= lb; j++) {
141
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
142
+ curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);
143
+ if (curr[j] < rowMin)
144
+ rowMin = curr[j];
145
+ }
146
+ if (rowMin > max)
147
+ return Number.POSITIVE_INFINITY;
148
+ [prev, curr] = [curr, prev];
149
+ }
150
+ return prev[lb] <= max ? prev[lb] : Number.POSITIVE_INFINITY;
151
+ }
152
+ /** camelCase identifier → lowercased token set (`loadModelAsync` → load,model,async). */
153
+ function camelTokens(name) {
154
+ return new Set(name
155
+ .split(/(?=[A-Z])/)
156
+ .map((t) => t.toLowerCase())
157
+ .filter((t) => t.length > 0));
158
+ }
159
+ /** Jaccard similarity of the camelCase token sets of two identifiers. */
160
+ export function tokenSimilarity(a, b) {
161
+ const ta = camelTokens(a);
162
+ const tb = camelTokens(b);
163
+ let inter = 0;
164
+ for (const t of ta)
165
+ if (tb.has(t))
166
+ inter++;
167
+ const union = ta.size + tb.size - inter;
168
+ return union === 0 ? 0 : inter / union;
169
+ }
170
+ /**
171
+ * Closest candidates to `name`, nearest first, by a hybrid metric:
172
+ * - Levenshtein within a length-scaled band (2 for short names, 3 for long)
173
+ * catches character typos (`ModellNode` → `ModelNode`);
174
+ * - camelCase token overlap (Jaccard ≥ 0.5) catches structural
175
+ * hallucinations that raw edit distance can't reach —
176
+ * `createModelInstanceAsync` → `loadModelInstanceAsync` is distance 5,
177
+ * but shares 3 of 5 camelCase tokens.
178
+ * Token matches are scored `10 × (1 − similarity)` so an exact-ish edit
179
+ * match always outranks a merely structural one.
180
+ */
181
+ export function suggestClosest(name, candidates, limit = 3) {
182
+ const max = name.length < 12 ? 2 : 3;
183
+ const lower = name.toLowerCase();
184
+ const scored = [];
185
+ for (const candidate of candidates) {
186
+ const d = editDistance(lower, candidate.toLowerCase(), max);
187
+ if (d !== Number.POSITIVE_INFINITY) {
188
+ scored.push([candidate, d]);
189
+ continue;
190
+ }
191
+ const similarity = tokenSimilarity(name, candidate);
192
+ if (similarity >= 0.5)
193
+ scored.push([candidate, 10 * (1 - similarity)]);
194
+ }
195
+ scored.sort((x, y) => x[1] - y[1] || x[0].localeCompare(y[0]));
196
+ return scored.slice(0, limit).map(([candidate]) => candidate);
197
+ }
198
+ /** Human-readable " Did you mean …?" suffix, or "" when nothing is close. */
199
+ export function didYouMean(name, candidates) {
200
+ const close = suggestClosest(name, candidates);
201
+ if (close.length === 0)
202
+ return "";
203
+ return ` Did you mean ${close.map((c) => `\`${c}\``).join(", ")}?`;
204
+ }
@@ -72,7 +72,7 @@ export const TOOL_DEFINITIONS = [
72
72
  },
73
73
  {
74
74
  name: "validate_code",
75
- description: "Checks a Kotlin or Swift SceneView snippet for common mistakes. For Kotlin: threading violations, wrong destroy order, missing null-checks, LightNode trailing-lambda bug, deprecated 2.x APIs. For Swift: missing @MainActor, async/await patterns, missing imports, RealityKit mistakes. Language is auto-detected. Always call this before presenting generated SceneView code to the user.",
75
+ description: "Checks a Kotlin or Swift SceneView snippet for common mistakes. For Kotlin: symbol existence against the real public API (unknown imports, made-up node types, nonexistent loader methods — with did-you-mean suggestions), threading violations, wrong destroy order, missing null-checks, LightNode trailing-lambda bug, deprecated 2.x APIs. For Swift: missing @MainActor, async/await patterns, missing imports, RealityKit mistakes. Language is auto-detected. Always call this before presenting generated SceneView code to the user.",
76
76
  inputSchema: {
77
77
  type: "object",
78
78
  properties: {
@@ -697,7 +697,7 @@ export async function dispatchTool(toolName, args, _ctx = {}) {
697
697
  { platform: "visionOS", renderer: "RealityKit", framework: "SwiftUI", status: "Alpha", version: LATEST_SCENEVIEW_RELEASE, dependency: "SceneViewSwift (SPM)", features: ["3D", "Immersive spaces", "Hand tracking (planned)"] },
698
698
  { platform: "Web", renderer: "Filament.js (WASM)", framework: "Kotlin/JS", status: "Alpha", version: LATEST_SCENEVIEW_RELEASE, dependency: "sceneview-web (npm)", features: ["3D", "WebXR AR/VR", "GLB models", "WebGL2"] },
699
699
  { platform: "Desktop", renderer: "Software / Filament JNI", framework: "Compose Desktop", status: "Alpha", version: LATEST_SCENEVIEW_RELEASE, dependency: "sceneview-desktop (local)", features: ["3D", "Software renderer", "Wireframe"] },
700
- { platform: "Flutter", renderer: "Filament / RealityKit", framework: "PlatformView", status: "Alpha", version: LATEST_SCENEVIEW_RELEASE, dependency: "flutter pub: sceneview_flutter (git ref)", features: ["3D", "AR", "Android + iOS bridge"] },
700
+ { platform: "Flutter", renderer: "Filament / RealityKit", framework: "PlatformView", status: "Alpha", version: LATEST_SCENEVIEW_RELEASE, dependency: "flutter pub: flutter_sceneview (git ref until first publish, #2735)", features: ["3D", "AR", "Android + iOS bridge"] },
701
701
  ];
702
702
  const lines = [
703
703
  "## SceneView Supported Platforms\n",
package/dist/validator.js CHANGED
@@ -1,8 +1,20 @@
1
+ import { didYouMean, isKnownTopLevelFunction, isKnownTypeName, knownTypeNames, membersOfClass, rememberHelperNames, resolveImport, } from "./symbols.js";
1
2
  function findLines(lines, pattern) {
2
3
  return lines
3
4
  .map((l, i) => (pattern.test(l) ? i + 1 : -1))
4
5
  .filter((n) => n !== -1);
5
6
  }
7
+ // Symbols the snippet declares itself (`fun MyShowcaseScene(…)`, `class ShelfNode(…) :
8
+ // Node(engine)`) exist by definition — they are the caller's own wrappers, not calls into
9
+ // SceneView, and the SDK's naming idiom (`WallPlacementScene`, `PhysicsNode`) makes such
10
+ // suffixes natural. The optional `<…>` skips a generic parameter list before the name.
11
+ function locallyDeclaredNames(code) {
12
+ const names = new Set();
13
+ for (const m of code.matchAll(/\b(?:fun|class|interface|object)\s+(?:<[^>]*>\s+)?(\w+)/g)) {
14
+ names.add(m[1]);
15
+ }
16
+ return names;
17
+ }
6
18
  const RULES = [
7
19
  // ─── Threading ────────────────────────────────────────────────────────────
8
20
  {
@@ -450,6 +462,193 @@ const RULES = [
450
462
  return issues;
451
463
  },
452
464
  },
465
+ // ─── Symbol existence (#2760) — backed by the generated .api index ────────
466
+ //
467
+ // These rules answer "does this API actually exist?", the #1 failure mode
468
+ // of AI-generated snippets. The index covers the Android/KMP surface
469
+ // (sceneview, arsceneview, sceneview-core) — see `symbols.ts`. Each rule is
470
+ // deliberately narrow so its false-positive space is ~zero.
471
+ {
472
+ // `import io.github.sceneview.…` that resolves to nothing. Our own
473
+ // namespace: nothing outside the index can legitimately live there.
474
+ // Known tolerated false NEGATIVE: aliased imports (`import … as MN`)
475
+ // don't match the end-of-line anchor and are silently skipped — an
476
+ // unchecked alias can never become a false positive.
477
+ id: "symbols/unknown-import",
478
+ severity: "error",
479
+ check(code, lines) {
480
+ const issues = [];
481
+ const importRe = /^\s*import\s+(io\.github\.sceneview(?:\.\w+)*)(\.\*)?\s*$/;
482
+ lines.forEach((content, i) => {
483
+ const m = importRe.exec(content);
484
+ if (!m)
485
+ return;
486
+ const path = m[1];
487
+ const isWildcard = m[2] !== undefined;
488
+ const resolved = resolveImport(path);
489
+ const ok = isWildcard ? resolved === "package" : resolved !== null;
490
+ if (ok)
491
+ return;
492
+ const leaf = path.slice(path.lastIndexOf(".") + 1);
493
+ const suggestion = didYouMean(leaf, knownTypeNames());
494
+ issues.push({
495
+ severity: "error",
496
+ rule: "symbols/unknown-import",
497
+ message: `\`import ${path}${isWildcard ? ".*" : ""}\` does not exist in the SceneView public API.${suggestion} Check the class name and package against \`llms.txt\`.`,
498
+ line: i + 1,
499
+ });
500
+ });
501
+ return issues;
502
+ },
503
+ },
504
+ {
505
+ // A made-up `…Node(` / `…Node {` / `…Scene(` type or declarative factory.
506
+ id: "symbols/unknown-type",
507
+ severity: "error",
508
+ check(code, lines) {
509
+ const issues = [];
510
+ // Names already handled (with a better, migration-specific message) by
511
+ // `migration/old-api` — don't double-report them here.
512
+ const migrationCovered = new Set([
513
+ "Scene",
514
+ "ARScene",
515
+ "ArSceneView",
516
+ "PlacementNode",
517
+ "TransformableNode",
518
+ ]);
519
+ const callRe = /\b([A-Z]\w*(?:Node|Scene))\s*[({]/g;
520
+ const localNames = locallyDeclaredNames(code);
521
+ const seen = new Set();
522
+ for (const match of code.matchAll(callRe)) {
523
+ const name = match[1];
524
+ if (seen.has(name))
525
+ continue;
526
+ seen.add(name);
527
+ if (migrationCovered.has(name))
528
+ continue;
529
+ if (localNames.has(name))
530
+ continue;
531
+ if (isKnownTypeName(name))
532
+ continue;
533
+ const suggestion = didYouMean(name, knownTypeNames());
534
+ findLines(lines, new RegExp(`\\b${name}\\s*[({]`)).forEach((line) => issues.push({
535
+ severity: "error",
536
+ rule: "symbols/unknown-type",
537
+ message: `\`${name}\` does not exist in the SceneView public API.${suggestion} Node types and declarative factories are listed in \`llms.txt\`.`,
538
+ line,
539
+ }));
540
+ }
541
+ return issues;
542
+ },
543
+ },
544
+ {
545
+ // A nonexistent member called on a canonically-named loader variable —
546
+ // e.g. `modelLoader.createModelInstanceAsync(...)` (the real API is
547
+ // `loadModelInstanceAsync`). Heuristic on the receiver NAME, which the
548
+ // docs and every sample use consistently.
549
+ id: "symbols/unknown-member",
550
+ severity: "error",
551
+ check(code, lines) {
552
+ const issues = [];
553
+ const receivers = {
554
+ modelLoader: "ModelLoader",
555
+ materialLoader: "MaterialLoader",
556
+ environmentLoader: "EnvironmentLoader",
557
+ };
558
+ // Kotlin stdlib scope/extension functions callable on any receiver.
559
+ const stdlib = new Set([
560
+ "apply",
561
+ "also",
562
+ "let",
563
+ "run",
564
+ "takeIf",
565
+ "takeUnless",
566
+ "to",
567
+ "use",
568
+ "toString",
569
+ ]);
570
+ // A snippet may rebind a canonical name to its own type
571
+ // (`val modelLoader = MyRepository()`) — member-checking that variable
572
+ // against the SceneView loader would be a false positive
573
+ // (adversarial-review finding #3 on PR #2814). Keep checking only when
574
+ // the declaration is recognizably the real loader: a `remember<Class>`
575
+ // call, a direct constructor, a `: <Class>` type annotation, or a
576
+ // `.<receiver>` property access — or when there is no local
577
+ // declaration at all (the scope provides it, the canonical case).
578
+ const rebound = new Set();
579
+ for (const [receiver, className] of Object.entries(receivers)) {
580
+ const decl = new RegExp(`\\b(?:val|var)\\s+${receiver}\\s*(?::\\s*([^=\\n]+?))?\\s*=\\s*(.+)`).exec(code);
581
+ if (!decl)
582
+ continue;
583
+ const [, typeAnnotation, rhs] = decl;
584
+ const looksReal = (typeAnnotation?.includes(className) ?? false) ||
585
+ rhs.includes(`remember${className}`) ||
586
+ rhs.includes(`${className}(`) ||
587
+ rhs.trimEnd().endsWith(`.${receiver}`);
588
+ if (!looksReal)
589
+ rebound.add(receiver);
590
+ }
591
+ const callRe = /\b(modelLoader|materialLoader|environmentLoader)\.(\w+)\s*\(/g;
592
+ const reported = new Set();
593
+ for (const match of code.matchAll(callRe)) {
594
+ const [, receiver, member] = match;
595
+ const key = `${receiver}.${member}`;
596
+ if (reported.has(key))
597
+ continue;
598
+ reported.add(key);
599
+ if (rebound.has(receiver))
600
+ continue;
601
+ if (stdlib.has(member))
602
+ continue;
603
+ const members = membersOfClass(receivers[receiver]);
604
+ if (!members || members.has(member))
605
+ continue;
606
+ const suggestion = didYouMean(member, members);
607
+ findLines(lines, new RegExp(`\\b${receiver}\\.${member}\\s*\\(`)).forEach((line) => issues.push({
608
+ severity: "error",
609
+ rule: "symbols/unknown-member",
610
+ message: `\`${receivers[receiver]}.${member}()\` does not exist.${suggestion} The full member list is in \`llms.txt\`.`,
611
+ line,
612
+ }));
613
+ }
614
+ return issues;
615
+ },
616
+ },
617
+ {
618
+ // `remember<SceneView-ish>` helper that isn't a real top-level function —
619
+ // e.g. an invented `rememberModelInstanceAsync`. Warning, not error: a
620
+ // snippet may legitimately mix non-SceneView Compose libraries whose
621
+ // helpers match the keyword net (e.g. Maps `rememberCameraPositionState`).
622
+ id: "symbols/unknown-remember-helper",
623
+ severity: "warning",
624
+ check(code, lines) {
625
+ const issues = [];
626
+ const sceneViewish = /^(Engine|Model|Material|Environment|Scene|Node|Collision|Render|Skybox|View|AR|Camera|Splat)/;
627
+ const callRe = /\bremember([A-Z]\w*)\s*\(/g;
628
+ const localNames = locallyDeclaredNames(code);
629
+ const seen = new Set();
630
+ for (const match of code.matchAll(callRe)) {
631
+ const fullName = `remember${match[1]}`;
632
+ if (seen.has(fullName))
633
+ continue;
634
+ seen.add(fullName);
635
+ if (!sceneViewish.test(match[1]))
636
+ continue;
637
+ if (localNames.has(fullName))
638
+ continue;
639
+ if (isKnownTopLevelFunction(fullName))
640
+ continue;
641
+ const suggestion = didYouMean(fullName, rememberHelperNames());
642
+ findLines(lines, new RegExp(`\\b${fullName}\\s*\\(`)).forEach((line) => issues.push({
643
+ severity: "warning",
644
+ rule: "symbols/unknown-remember-helper",
645
+ message: `\`${fullName}\` is not a SceneView composable helper.${suggestion} If it comes from another library, add its import; otherwise check \`llms.txt\`.`,
646
+ line,
647
+ }));
648
+ }
649
+ return issues;
650
+ },
651
+ },
453
652
  ];
454
653
  // ─── Swift Validation Rules ──────────────────────────────────────────────────
455
654
  const SWIFT_RULES = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sceneview-mcp",
3
- "version": "4.0.14",
3
+ "version": "4.0.15",
4
4
  "mcpName": "io.github.sceneview/mcp",
5
5
  "description": "MCP server for SceneView — cross-platform 3D & AR SDK for Android and iOS. Give Claude the full SceneView SDK so it writes correct, compilable code.",
6
6
  "keywords": [
@@ -59,12 +59,12 @@
59
59
  "node": ">=18"
60
60
  },
61
61
  "scripts": {
62
- "prebuild": "node scripts/generate-llms-txt.js && node scripts/generate-version.js",
62
+ "prebuild": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && node scripts/generate-symbols.js",
63
63
  "build": "tsc",
64
- "prepare": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && tsc",
64
+ "prepare": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && node scripts/generate-symbols.js && tsc",
65
65
  "start": "node dist/index.js",
66
66
  "dev": "tsx src/index.ts",
67
- "test": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && vitest run",
67
+ "test": "node scripts/generate-llms-txt.js && node scripts/generate-version.js && node scripts/generate-symbols.js && vitest run",
68
68
  "biome": "cd .. && mcp/node_modules/.bin/biome check",
69
69
  "biome:fix": "cd .. && mcp/node_modules/.bin/biome check --write"
70
70
  },