@tmhs/godot-correctness-mcp 0.1.0

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 (100) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +118 -0
  3. package/data/godot-4.4.symbols.json.gz +0 -0
  4. package/data/godot-4.5.symbols.json.gz +0 -0
  5. package/data/tree-sitter-gdscript.wasm +0 -0
  6. package/dist/capabilities.d.ts +21 -0
  7. package/dist/capabilities.js +24 -0
  8. package/dist/capabilities.js.map +1 -0
  9. package/dist/cli.d.ts +12 -0
  10. package/dist/cli.js +105 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/gdscript/ast.d.ts +25 -0
  13. package/dist/gdscript/ast.js +82 -0
  14. package/dist/gdscript/ast.js.map +1 -0
  15. package/dist/gdscript/parser.d.ts +7 -0
  16. package/dist/gdscript/parser.js +44 -0
  17. package/dist/gdscript/parser.js.map +1 -0
  18. package/dist/godot/text-format.d.ts +35 -0
  19. package/dist/godot/text-format.js +105 -0
  20. package/dist/godot/text-format.js.map +1 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +38 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/lint/config.d.ts +83 -0
  25. package/dist/lint/config.js +55 -0
  26. package/dist/lint/config.js.map +1 -0
  27. package/dist/lint/engine.d.ts +7 -0
  28. package/dist/lint/engine.js +33 -0
  29. package/dist/lint/engine.js.map +1 -0
  30. package/dist/lint/project.d.ts +12 -0
  31. package/dist/lint/project.js +49 -0
  32. package/dist/lint/project.js.map +1 -0
  33. package/dist/lint/rules/await-misuse.d.ts +3 -0
  34. package/dist/lint/rules/await-misuse.js +34 -0
  35. package/dist/lint/rules/await-misuse.js.map +1 -0
  36. package/dist/lint/rules/connect-without-disconnect.d.ts +7 -0
  37. package/dist/lint/rules/connect-without-disconnect.js +38 -0
  38. package/dist/lint/rules/connect-without-disconnect.js.map +1 -0
  39. package/dist/lint/rules/delta-misuse.d.ts +7 -0
  40. package/dist/lint/rules/delta-misuse.js +78 -0
  41. package/dist/lint/rules/delta-misuse.js.map +1 -0
  42. package/dist/lint/rules/float-grid-equality.d.ts +6 -0
  43. package/dist/lint/rules/float-grid-equality.js +39 -0
  44. package/dist/lint/rules/float-grid-equality.js.map +1 -0
  45. package/dist/lint/rules/get-node-in-process.d.ts +3 -0
  46. package/dist/lint/rules/get-node-in-process.js +31 -0
  47. package/dist/lint/rules/get-node-in-process.js.map +1 -0
  48. package/dist/lint/rules/index.d.ts +4 -0
  49. package/dist/lint/rules/index.js +21 -0
  50. package/dist/lint/rules/index.js.map +1 -0
  51. package/dist/lint/rules/missing-onready.d.ts +3 -0
  52. package/dist/lint/rules/missing-onready.js +52 -0
  53. package/dist/lint/rules/missing-onready.js.map +1 -0
  54. package/dist/lint/rules/stringly-nodepath.d.ts +3 -0
  55. package/dist/lint/rules/stringly-nodepath.js +35 -0
  56. package/dist/lint/rules/stringly-nodepath.js.map +1 -0
  57. package/dist/lint/rules/untyped-declaration.d.ts +7 -0
  58. package/dist/lint/rules/untyped-declaration.js +25 -0
  59. package/dist/lint/rules/untyped-declaration.js.map +1 -0
  60. package/dist/lint/types.d.ts +36 -0
  61. package/dist/lint/types.js +2 -0
  62. package/dist/lint/types.js.map +1 -0
  63. package/dist/resources/skills.d.ts +12 -0
  64. package/dist/resources/skills.js +45 -0
  65. package/dist/resources/skills.js.map +1 -0
  66. package/dist/scenes/paths.d.ts +14 -0
  67. package/dist/scenes/paths.js +38 -0
  68. package/dist/scenes/paths.js.map +1 -0
  69. package/dist/scenes/report.d.ts +17 -0
  70. package/dist/scenes/report.js +25 -0
  71. package/dist/scenes/report.js.map +1 -0
  72. package/dist/scenes/validate.d.ts +16 -0
  73. package/dist/scenes/validate.js +185 -0
  74. package/dist/scenes/validate.js.map +1 -0
  75. package/dist/server-info.d.ts +13 -0
  76. package/dist/server-info.js +21 -0
  77. package/dist/server-info.js.map +1 -0
  78. package/dist/symbols/db.d.ts +11 -0
  79. package/dist/symbols/db.js +42 -0
  80. package/dist/symbols/db.js.map +1 -0
  81. package/dist/symbols/lookup.d.ts +89 -0
  82. package/dist/symbols/lookup.js +271 -0
  83. package/dist/symbols/lookup.js.map +1 -0
  84. package/dist/symbols/schema.d.ts +1419 -0
  85. package/dist/symbols/schema.js +109 -0
  86. package/dist/symbols/schema.js.map +1 -0
  87. package/dist/tools/api.d.ts +3 -0
  88. package/dist/tools/api.js +91 -0
  89. package/dist/tools/api.js.map +1 -0
  90. package/dist/tools/lint.d.ts +3 -0
  91. package/dist/tools/lint.js +45 -0
  92. package/dist/tools/lint.js.map +1 -0
  93. package/dist/tools/scenes.d.ts +3 -0
  94. package/dist/tools/scenes.js +43 -0
  95. package/dist/tools/scenes.js.map +1 -0
  96. package/package.json +57 -0
  97. package/skills/data-driven-resources.md +78 -0
  98. package/skills/resource-preload.md +43 -0
  99. package/skills/signal-hygiene.md +46 -0
  100. package/skills/typed-gdscript.md +47 -0
@@ -0,0 +1,21 @@
1
+ import { awaitMisuse } from "./await-misuse.js";
2
+ import { connectWithoutDisconnect } from "./connect-without-disconnect.js";
3
+ import { deltaMisuse } from "./delta-misuse.js";
4
+ import { floatGridEquality } from "./float-grid-equality.js";
5
+ import { getNodeInProcess } from "./get-node-in-process.js";
6
+ import { missingOnready } from "./missing-onready.js";
7
+ import { stringlyNodepath } from "./stringly-nodepath.js";
8
+ import { untypedDeclaration } from "./untyped-declaration.js";
9
+ /** The default GDScript ruleset, in stable reporting order. */
10
+ export const DEFAULT_RULES = [
11
+ deltaMisuse,
12
+ getNodeInProcess,
13
+ missingOnready,
14
+ untypedDeclaration,
15
+ awaitMisuse,
16
+ stringlyNodepath,
17
+ connectWithoutDisconnect,
18
+ floatGridEquality,
19
+ ];
20
+ export const RULES_BY_ID = new Map(DEFAULT_RULES.map((r) => [r.id, r]));
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lint/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,+DAA+D;AAC/D,MAAM,CAAC,MAAM,aAAa,GAAW;IACnC,WAAW;IACX,gBAAgB;IAChB,cAAc;IACd,kBAAkB;IAClB,WAAW;IACX,gBAAgB;IAChB,wBAAwB;IACxB,iBAAiB;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAsB,IAAI,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../types.js";
2
+ /** Flags class-level node-path members initialized without @onready. */
3
+ export declare const missingOnready: Rule;
@@ -0,0 +1,52 @@
1
+ import { calleeName, text, walk } from "../../gdscript/ast.js";
2
+ import { findAll } from "../../gdscript/ast.js";
3
+ const NODE_GETTERS = new Set(["get_node", "get_node_or_null"]);
4
+ function isNodeLookup(value, source) {
5
+ if (!value)
6
+ return false;
7
+ if (value.type === "get_node")
8
+ return true;
9
+ if (value.type === "call" && NODE_GETTERS.has(calleeName(value, source) ?? ""))
10
+ return true;
11
+ return false;
12
+ }
13
+ function hasOnready(vs, source) {
14
+ for (const c of vs.namedChildren) {
15
+ if (c?.type !== "annotations")
16
+ continue;
17
+ for (const a of walk(c)) {
18
+ if (a.type === "annotation") {
19
+ const id = a.namedChild(0);
20
+ if (id && text(id, source) === "onready")
21
+ return true;
22
+ }
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+ /** Flags class-level node-path members initialized without @onready. */
28
+ export const missingOnready = {
29
+ id: "missing-onready",
30
+ description: "Class-level node-path member initialized without @onready.",
31
+ defaultSeverity: "warning",
32
+ check(ctx) {
33
+ const out = [];
34
+ for (const vs of findAll(ctx.root, "variable_statement")) {
35
+ // Class body only: a member var is a direct child of the source node.
36
+ if (vs.parent?.type !== "source")
37
+ continue;
38
+ const value = vs.childForFieldName("value");
39
+ if (!isNodeLookup(value, ctx.source))
40
+ continue;
41
+ if (hasOnready(vs, ctx.source))
42
+ continue;
43
+ out.push({
44
+ node: vs,
45
+ message: "Node-path member initialized without @onready; the child may not exist when the initializer runs.",
46
+ suggestion: "Prefix the declaration with @onready so it resolves after the node enters the tree.",
47
+ });
48
+ }
49
+ return out;
50
+ },
51
+ };
52
+ //# sourceMappingURL=missing-onready.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-onready.js","sourceRoot":"","sources":["../../../src/lint/rules/missing-onready.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGhD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC;AAE/D,SAAS,YAAY,CAAC,KAAkB,EAAE,MAAc;IACtD,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5F,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,EAAQ,EAAE,MAAc;IAC1C,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,CAAC;QACjC,IAAI,CAAC,EAAE,IAAI,KAAK,aAAa;YAAE,SAAS;QACxC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC5B,MAAM,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;gBAC3B,IAAI,EAAE,IAAI,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,SAAS;oBAAE,OAAO,IAAI,CAAC;YACxD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,MAAM,cAAc,GAAS;IAClC,EAAE,EAAE,iBAAiB;IACrB,WAAW,EAAE,4DAA4D;IACzE,eAAe,EAAE,SAAS;IAC1B,KAAK,CAAC,GAAG;QACP,MAAM,GAAG,GAAG,EAAE,CAAC;QACf,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,oBAAoB,CAAC,EAAE,CAAC;YACzD,sEAAsE;YACtE,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,KAAK,QAAQ;gBAAE,SAAS;YAC3C,MAAM,KAAK,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;YAC5C,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS;YAC/C,IAAI,UAAU,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS;YACzC,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,mGAAmG;gBAC5G,UAAU,EAAE,qFAAqF;aAClG,CAAC,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "../types.js";
2
+ /** Flags string-literal node paths (rename-fragile). */
3
+ export declare const stringlyNodepath: Rule;
@@ -0,0 +1,35 @@
1
+ import { calleeName, walk } from "../../gdscript/ast.js";
2
+ const PATH_FNS = new Set([
3
+ "get_node",
4
+ "get_node_or_null",
5
+ "has_node",
6
+ "find_child",
7
+ "get_node_and_resource",
8
+ "NodePath",
9
+ ]);
10
+ /** Flags string-literal node paths (rename-fragile). */
11
+ export const stringlyNodepath = {
12
+ id: "stringly-nodepath",
13
+ description: "String-literal node path passed to a node lookup or NodePath().",
14
+ defaultSeverity: "info",
15
+ check(ctx) {
16
+ const out = [];
17
+ for (const n of walk(ctx.root)) {
18
+ if (n.type !== "call" && n.type !== "attribute_call")
19
+ continue;
20
+ const callee = calleeName(n, ctx.source);
21
+ if (!callee || !PATH_FNS.has(callee))
22
+ continue;
23
+ const first = n.childForFieldName("arguments")?.namedChild(0);
24
+ if (first && first.type === "string") {
25
+ out.push({
26
+ node: first,
27
+ message: "String node path is rename-fragile; a renamed node breaks it silently at runtime.",
28
+ suggestion: "Prefer the $NodePath shorthand or an @export var of type NodePath.",
29
+ });
30
+ }
31
+ }
32
+ return out;
33
+ },
34
+ };
35
+ //# sourceMappingURL=stringly-nodepath.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stringly-nodepath.js","sourceRoot":"","sources":["../../../src/lint/rules/stringly-nodepath.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAW,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAGlE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC;IACvB,UAAU;IACV,kBAAkB;IAClB,UAAU;IACV,YAAY;IACZ,uBAAuB;IACvB,UAAU;CACX,CAAC,CAAC;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,gBAAgB,GAAS;IACpC,EAAE,EAAE,mBAAmB;IACvB,WAAW,EAAE,iEAAiE;IAC9E,eAAe,EAAE,MAAM;IACvB,KAAK,CAAC,GAAG;QACP,MAAM,GAAG,GAAG,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/B,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB;gBAAE,SAAS;YAC/D,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;YACzC,IAAI,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAS;YAC/C,MAAM,KAAK,GAAG,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;YAC9D,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,GAAG,CAAC,IAAI,CAAC;oBACP,IAAI,EAAE,KAAK;oBACX,OAAO,EAAE,mFAAmF;oBAC5F,UAAU,EAAE,oEAAoE;iBACjF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { Rule } from "../types.js";
2
+ /**
3
+ * Flags var declarations with neither a type hint nor `:=` inference. Both
4
+ * `var x: int = 1` (explicit) and `var x := 1` (inferred, grammar field
5
+ * `type: inferred_type`) carry a `type` field; a bare `var x = 1` does not.
6
+ */
7
+ export declare const untypedDeclaration: Rule;
@@ -0,0 +1,25 @@
1
+ import { findAll } from "../../gdscript/ast.js";
2
+ /**
3
+ * Flags var declarations with neither a type hint nor `:=` inference. Both
4
+ * `var x: int = 1` (explicit) and `var x := 1` (inferred, grammar field
5
+ * `type: inferred_type`) carry a `type` field; a bare `var x = 1` does not.
6
+ */
7
+ export const untypedDeclaration = {
8
+ id: "untyped-declaration",
9
+ description: "Variable declared without a type hint or ':=' inference.",
10
+ defaultSeverity: "warning",
11
+ check(ctx) {
12
+ const out = [];
13
+ for (const vs of findAll(ctx.root, "variable_statement")) {
14
+ if (vs.childForFieldName("type"))
15
+ continue; // typed or `:=` inferred
16
+ out.push({
17
+ node: vs,
18
+ message: "Variable declared without a type hint or ':=' inference.",
19
+ suggestion: "Use ':=' to infer from the initializer, or annotate an explicit ': <Type>'.",
20
+ });
21
+ }
22
+ return out;
23
+ },
24
+ };
25
+ //# sourceMappingURL=untyped-declaration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"untyped-declaration.js","sourceRoot":"","sources":["../../../src/lint/rules/untyped-declaration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAS;IACtC,EAAE,EAAE,qBAAqB;IACzB,WAAW,EAAE,0DAA0D;IACvE,eAAe,EAAE,SAAS;IAC1B,KAAK,CAAC,GAAG;QACP,MAAM,GAAG,GAAG,EAAE,CAAC;QACf,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,oBAAoB,CAAC,EAAE,CAAC;YACzD,IAAI,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC;gBAAE,SAAS,CAAC,yBAAyB;YACrE,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,EAAE;gBACR,OAAO,EAAE,0DAA0D;gBACnE,UAAU,EAAE,6EAA6E;aAC1F,CAAC,CAAC;QACL,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { Node } from "web-tree-sitter";
2
+ export type Severity = "error" | "warning" | "info";
3
+ /** A resolved finding, ready for JSON output. Line/col are 1-based. */
4
+ export interface Finding {
5
+ file: string;
6
+ line: number;
7
+ col: number;
8
+ ruleId: string;
9
+ severity: Severity;
10
+ message: string;
11
+ suggestion: string;
12
+ }
13
+ /** What a rule emits: a node to anchor at, a message, and a fix suggestion. */
14
+ export interface RawFinding {
15
+ node: Node;
16
+ message: string;
17
+ suggestion: string;
18
+ /**
19
+ * Intrinsic severity for this specific finding, used for rules that emit more
20
+ * than one severity (e.g. delta-misuse: error for the main pattern, info for
21
+ * the softer sub-pattern). Takes precedence over config/default severity.
22
+ */
23
+ severity?: Severity;
24
+ }
25
+ export interface RuleContext {
26
+ root: Node;
27
+ source: string;
28
+ /** Per-rule options merged from config. */
29
+ options: Record<string, unknown>;
30
+ }
31
+ export interface Rule {
32
+ id: string;
33
+ description: string;
34
+ defaultSeverity: Severity;
35
+ check(ctx: RuleContext): RawFinding[];
36
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/lint/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export interface SkillEntry {
3
+ name: string;
4
+ title: string;
5
+ description: string;
6
+ }
7
+ /** Curated Godot 4.x GDScript skill snippets, exposed as MCP resources. */
8
+ export declare const SKILLS: SkillEntry[];
9
+ export declare function skillUri(name: string): string;
10
+ export declare function readSkill(name: string): string;
11
+ /** Register each skill as a static MCP resource (list + read for free). */
12
+ export declare function registerSkillResources(server: McpServer): void;
@@ -0,0 +1,45 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ // dist/resources/skills.js and src/resources/skills.ts both sit two levels
5
+ // below the package root, so skills/ resolves the same either way.
6
+ const skillsDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
7
+ /** Curated Godot 4.x GDScript skill snippets, exposed as MCP resources. */
8
+ export const SKILLS = [
9
+ {
10
+ name: "data-driven-resources",
11
+ title: "Data-driven resources and registry loading",
12
+ description: "Keep custom Resource types data-only, load them via a validated registry, " +
13
+ "and pair with the validate_registries tool to catch dangling paths.",
14
+ },
15
+ {
16
+ name: "signal-hygiene",
17
+ title: "Signal hygiene",
18
+ description: "Typed connect API, disconnect/one-shot discipline, and awaiting signals.",
19
+ },
20
+ {
21
+ name: "typed-gdscript",
22
+ title: "Typed GDScript patterns",
23
+ description: "Type every declaration, cache nodes with @onready, prefer $ over string paths.",
24
+ },
25
+ {
26
+ name: "resource-preload",
27
+ title: "preload vs load",
28
+ description: "When to use compile-time preload versus runtime load for scenes and resources.",
29
+ },
30
+ ];
31
+ export function skillUri(name) {
32
+ return `skill://${name}`;
33
+ }
34
+ export function readSkill(name) {
35
+ return readFileSync(join(skillsDir, `${name}.md`), "utf-8");
36
+ }
37
+ /** Register each skill as a static MCP resource (list + read for free). */
38
+ export function registerSkillResources(server) {
39
+ for (const s of SKILLS) {
40
+ server.registerResource(s.name, skillUri(s.name), { title: s.title, description: s.description, mimeType: "text/markdown" }, async (uri) => ({
41
+ contents: [{ uri: uri.href, mimeType: "text/markdown", text: readSkill(s.name) }],
42
+ }));
43
+ }
44
+ }
45
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/resources/skills.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,2EAA2E;AAC3E,mEAAmE;AACnE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAQtF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,MAAM,GAAiB;IAClC;QACE,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,4CAA4C;QACnD,WAAW,EACT,4EAA4E;YAC5E,qEAAqE;KACxE;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,0EAA0E;KACxF;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,yBAAyB;QAChC,WAAW,EAAE,gFAAgF;KAC9F;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,gFAAgF;KAC9F;CACF,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,WAAW,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,IAAI,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,CAAC,gBAAgB,CACrB,CAAC,CAAC,IAAI,EACN,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,EAChB,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,eAAe,EAAE,EACzE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACd,QAAQ,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;SAClF,CAAC,CACH,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { GodotValue } from "../godot/text-format.js";
2
+ /**
3
+ * Resolve a Godot path to an absolute filesystem path against the project root.
4
+ * `res://x` maps to `<root>/x`. `uid://` and other schemes return null (not
5
+ * resolvable without the editor's uid cache). A bare relative path resolves
6
+ * against the project root.
7
+ */
8
+ export declare function resolveResPath(projectRoot: string, p: string): string | null;
9
+ /**
10
+ * Whether a value should be treated as a resource path worth validating:
11
+ * a non-empty string that is res://-prefixed, ends in a resource extension, or
12
+ * whose property name matches one of the configured path-property patterns.
13
+ */
14
+ export declare function looksLikeResourcePath(value: GodotValue, propName: string, pathPropertyPatterns: string[]): boolean;
@@ -0,0 +1,38 @@
1
+ import { join } from "node:path";
2
+ import picomatch from "picomatch";
3
+ /**
4
+ * Resolve a Godot path to an absolute filesystem path against the project root.
5
+ * `res://x` maps to `<root>/x`. `uid://` and other schemes return null (not
6
+ * resolvable without the editor's uid cache). A bare relative path resolves
7
+ * against the project root.
8
+ */
9
+ export function resolveResPath(projectRoot, p) {
10
+ if (p.startsWith("res://"))
11
+ return join(projectRoot, p.slice("res://".length));
12
+ if (p.includes("://"))
13
+ return null; // uid://, http://, user:// - not statically resolvable
14
+ if (p === "")
15
+ return null;
16
+ return join(projectRoot, p);
17
+ }
18
+ const RESOURCE_EXT = /\.(tscn|tres|gd|res|scn)$/i;
19
+ /**
20
+ * Whether a value should be treated as a resource path worth validating:
21
+ * a non-empty string that is res://-prefixed, ends in a resource extension, or
22
+ * whose property name matches one of the configured path-property patterns.
23
+ */
24
+ export function looksLikeResourcePath(value, propName, pathPropertyPatterns) {
25
+ if (value.kind !== "string" || !value.string)
26
+ return false;
27
+ const s = value.string;
28
+ if (s.startsWith("res://"))
29
+ return true;
30
+ if (RESOURCE_EXT.test(s))
31
+ return true;
32
+ if (pathPropertyPatterns.length > 0 && picomatch(pathPropertyPatterns)(propName)) {
33
+ // Only if the value plausibly is a path (not an arbitrary string).
34
+ return s.includes("/") || RESOURCE_EXT.test(s) || s.startsWith("res://");
35
+ }
36
+ return false;
37
+ }
38
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/scenes/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,SAAS,MAAM,WAAW,CAAC;AAGlC;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,WAAmB,EAAE,CAAS;IAC3D,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/E,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,uDAAuD;IAC3F,IAAI,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,YAAY,GAAG,4BAA4B,CAAC;AAElD;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAiB,EACjB,QAAgB,EAChB,oBAA8B;IAE9B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC3D,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IACvB,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,oBAAoB,CAAC,MAAM,GAAG,CAAC,IAAI,SAAS,CAAC,oBAAoB,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjF,mEAAmE;QACnE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { type ResolvedConfig } from "../lint/config.js";
2
+ import type { Finding } from "../lint/types.js";
3
+ import { type RegistryEntry } from "./validate.js";
4
+ export interface ProjectReport {
5
+ root: string;
6
+ scenes: Finding[];
7
+ project: Finding[];
8
+ registries: Finding[];
9
+ lint: Finding[];
10
+ registryEntries: RegistryEntry[];
11
+ errorCount: number;
12
+ warningCount: number;
13
+ infoCount: number;
14
+ totalFindings: number;
15
+ }
16
+ /** Aggregate every static check (scenes, project config, registries, lint). */
17
+ export declare function projectReport(root: string, config?: ResolvedConfig): Promise<ProjectReport>;
@@ -0,0 +1,25 @@
1
+ import { loadConfig } from "../lint/config.js";
2
+ import { lintProject } from "../lint/project.js";
3
+ import { validateProjectConfig, validateRegistries, validateScenes, } from "./validate.js";
4
+ /** Aggregate every static check (scenes, project config, registries, lint). */
5
+ export async function projectReport(root, config) {
6
+ const cfg = config ?? loadConfig(root);
7
+ const scenes = validateScenes(root, cfg);
8
+ const project = validateProjectConfig(root);
9
+ const reg = validateRegistries(root, cfg);
10
+ const lint = (await lintProject(root, cfg)).findings;
11
+ const all = [...scenes, ...project, ...reg.findings, ...lint];
12
+ return {
13
+ root,
14
+ scenes,
15
+ project,
16
+ registries: reg.findings,
17
+ lint,
18
+ registryEntries: reg.entries,
19
+ errorCount: all.filter((f) => f.severity === "error").length,
20
+ warningCount: all.filter((f) => f.severity === "warning").length,
21
+ infoCount: all.filter((f) => f.severity === "info").length,
22
+ totalFindings: all.length,
23
+ };
24
+ }
25
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/scenes/report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAuB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,GAEf,MAAM,eAAe,CAAC;AAevB,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAY,EACZ,MAAuB;IAEvB,MAAM,GAAG,GAAG,MAAM,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,CAAC,MAAM,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;IAErD,MAAM,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,OAAO,EAAE,GAAG,GAAG,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAAC;IAC9D,OAAO;QACL,IAAI;QACJ,MAAM;QACN,OAAO;QACP,UAAU,EAAE,GAAG,CAAC,QAAQ;QACxB,IAAI;QACJ,eAAe,EAAE,GAAG,CAAC,OAAO;QAC5B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM;QAC5D,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;QAChE,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM;QAC1D,aAAa,EAAE,GAAG,CAAC,MAAM;KAC1B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { type ResolvedConfig } from "../lint/config.js";
2
+ import type { Finding } from "../lint/types.js";
3
+ /** validate_scenes: ext_resource paths resolve, ext refs declared, no dup ids. */
4
+ export declare function validateScenes(root: string, config?: ResolvedConfig): Finding[];
5
+ /** validate_project_config: main scene, autoloads, input map. */
6
+ export declare function validateProjectConfig(root: string): Finding[];
7
+ export interface RegistryEntry {
8
+ file: string;
9
+ scriptClass: string | null;
10
+ }
11
+ export interface RegistryResult {
12
+ findings: Finding[];
13
+ entries: RegistryEntry[];
14
+ }
15
+ /** validate_registries: parse, script resolution + class_name, dangling paths. */
16
+ export declare function validateRegistries(root: string, config?: ResolvedConfig): RegistryResult;
@@ -0,0 +1,185 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join, relative, sep } from "node:path";
3
+ import picomatch from "picomatch";
4
+ import { parseGodotFile, sectionsOfKind, } from "../godot/text-format.js";
5
+ import { loadConfig } from "../lint/config.js";
6
+ import { looksLikeResourcePath, resolveResPath } from "./paths.js";
7
+ function toPosix(p) {
8
+ return p.split(sep).join("/");
9
+ }
10
+ /** Recursively collect files with any of the given extensions under `dir`. */
11
+ function collectByExt(dir, exts, exclude, root) {
12
+ const out = [];
13
+ if (!existsSync(dir))
14
+ return out;
15
+ const walk = (d) => {
16
+ for (const entry of readdirSync(d, { withFileTypes: true })) {
17
+ const full = join(d, entry.name);
18
+ const rel = toPosix(relative(root, full));
19
+ if (entry.isDirectory()) {
20
+ if (entry.name === ".git" || exclude(rel))
21
+ continue;
22
+ walk(full);
23
+ }
24
+ else if (entry.isFile() && exts.some((e) => entry.name.endsWith(e)) && !exclude(rel)) {
25
+ out.push(full);
26
+ }
27
+ }
28
+ };
29
+ walk(dir);
30
+ return out;
31
+ }
32
+ function finding(file, line, ruleId, severity, message, suggestion) {
33
+ return { file, line, col: 1, ruleId, severity, message, suggestion };
34
+ }
35
+ /** Map of ext_resource id -> its declaration, plus duplicate detection. */
36
+ function extResourceIndex(file) {
37
+ const byId = new Map();
38
+ const duplicates = [];
39
+ for (const s of sectionsOfKind(file, "ext_resource")) {
40
+ const id = s.attributes["id"]?.string ?? s.attributes["id"]?.raw;
41
+ if (!id)
42
+ continue;
43
+ if (byId.has(id))
44
+ duplicates.push({ id, section: s });
45
+ else
46
+ byId.set(id, s);
47
+ }
48
+ return { byId, duplicates };
49
+ }
50
+ /** Collect all ExtResource("id") references across a section's properties. */
51
+ function extRefs(section) {
52
+ const refs = [];
53
+ for (const [key, v] of Object.entries(section.properties)) {
54
+ if (v.kind === "extResource" && v.ref)
55
+ refs.push({ key, ref: v.ref, line: v.line });
56
+ }
57
+ return refs;
58
+ }
59
+ /** validate_scenes: ext_resource paths resolve, ext refs declared, no dup ids. */
60
+ export function validateScenes(root, config) {
61
+ const cfg = config ?? loadConfig(root);
62
+ const exclude = picomatch([...cfg.exclude, "**/.godot/**"], { dot: true });
63
+ const findings = [];
64
+ for (const full of collectByExt(root, [".tscn"], exclude, root)) {
65
+ const rel = toPosix(relative(root, full));
66
+ const file = parseGodotFile(readFileSync(full, "utf-8"));
67
+ const { byId, duplicates } = extResourceIndex(file);
68
+ for (const { section } of duplicates) {
69
+ findings.push(finding(rel, section.line, "scene-duplicate-resource-id", "error", `Duplicate ext_resource id "${section.attributes["id"]?.string ?? ""}".`, "Give each ext_resource a unique id."));
70
+ }
71
+ for (const s of sectionsOfKind(file, "ext_resource")) {
72
+ const path = s.attributes["path"]?.string;
73
+ if (!path)
74
+ continue; // uid-only reference; not statically resolvable
75
+ const abs = resolveResPath(root, path);
76
+ if (abs && !existsSync(abs)) {
77
+ findings.push(finding(rel, s.line, "scene-ext-resource-missing", "error", `ext_resource path does not exist: ${path}`, "Fix the path or restore the missing file."));
78
+ }
79
+ }
80
+ for (const section of [...sectionsOfKind(file, "node"), ...sectionsOfKind(file, "resource")]) {
81
+ for (const { ref, line } of extRefs(section)) {
82
+ if (!byId.has(ref)) {
83
+ findings.push(finding(rel, line, "scene-missing-ext-resource-id", "error", `ExtResource("${ref}") is not declared in this scene.`, "Add the ext_resource declaration or fix the id."));
84
+ }
85
+ }
86
+ }
87
+ }
88
+ return findings;
89
+ }
90
+ /** validate_project_config: main scene, autoloads, input map. */
91
+ export function validateProjectConfig(root) {
92
+ const projectPath = join(root, "project.godot");
93
+ if (!existsSync(projectPath)) {
94
+ return [finding("project.godot", 1, "project-missing", "error", "No project.godot found at the project root.", "Point the tool at a Godot project root.")];
95
+ }
96
+ const rel = "project.godot";
97
+ const file = parseGodotFile(readFileSync(projectPath, "utf-8"));
98
+ const findings = [];
99
+ const app = file.sections.find((s) => s.kind === "application");
100
+ const mainScene = app?.properties["run/main_scene"];
101
+ if (mainScene?.kind === "string" && mainScene.string) {
102
+ const abs = resolveResPath(root, mainScene.string);
103
+ if (abs && !existsSync(abs)) {
104
+ findings.push(finding(rel, mainScene.line, "project-main-scene-missing", "error", `Main scene does not exist: ${mainScene.string}`, "Fix run/main_scene or restore the scene."));
105
+ }
106
+ }
107
+ const autoload = file.sections.find((s) => s.kind === "autoload");
108
+ if (autoload) {
109
+ for (const [name, v] of Object.entries(autoload.properties)) {
110
+ if (v.kind !== "string" || !v.string)
111
+ continue;
112
+ const p = v.string.replace(/^\*/, ""); // leading * marks an enabled singleton
113
+ const abs = resolveResPath(root, p);
114
+ if (abs && !existsSync(abs)) {
115
+ findings.push(finding(rel, v.line, "project-autoload-missing", "error", `Autoload "${name}" points at a missing file: ${p}`, "Fix the autoload path or restore the file."));
116
+ }
117
+ }
118
+ }
119
+ const input = file.sections.find((s) => s.kind === "input");
120
+ if (input) {
121
+ for (const [name, v] of Object.entries(input.properties)) {
122
+ if (!v.raw.trimStart().startsWith("{")) {
123
+ findings.push(finding(rel, v.line, "project-input-malformed", "warning", `Input action "${name}" is not a well-formed action dictionary.`, "Each input action should be a { \"deadzone\": ..., \"events\": [...] } dictionary."));
124
+ }
125
+ }
126
+ }
127
+ return findings;
128
+ }
129
+ function readClassName(gdAbsPath) {
130
+ try {
131
+ const m = /^\s*class_name\s+(\w+)/m.exec(readFileSync(gdAbsPath, "utf-8"));
132
+ return m ? m[1] : null;
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ /** validate_registries: parse, script resolution + class_name, dangling paths. */
139
+ export function validateRegistries(root, config) {
140
+ const cfg = config ?? loadConfig(root);
141
+ const exclude = picomatch([...cfg.exclude, "**/.godot/**"], { dot: true });
142
+ const findings = [];
143
+ const entries = [];
144
+ for (const dirRel of cfg.registryDirs) {
145
+ const dirAbs = join(root, dirRel);
146
+ for (const full of collectByExt(dirAbs, [".tres"], exclude, root)) {
147
+ const rel = toPosix(relative(root, full));
148
+ const file = parseGodotFile(readFileSync(full, "utf-8"));
149
+ if (!file.header || file.header.kind !== "gd_resource") {
150
+ findings.push(finding(rel, 1, "registry-parse-error", "error", "File does not parse as a Godot text resource (missing gd_resource header).", "Ensure the .tres is a valid, uncorrupted Godot resource."));
151
+ continue;
152
+ }
153
+ const { byId } = extResourceIndex(file);
154
+ const resource = file.sections.find((s) => s.kind === "resource");
155
+ let scriptClass = null;
156
+ // Script resolution + class_name capture.
157
+ const scriptRef = resource?.properties["script"];
158
+ if (scriptRef?.kind === "extResource" && scriptRef.ref) {
159
+ const decl = byId.get(scriptRef.ref);
160
+ const path = decl?.attributes["path"]?.string;
161
+ const abs = path ? resolveResPath(root, path) : null;
162
+ if (!decl || !abs || !existsSync(abs)) {
163
+ findings.push(finding(rel, scriptRef.line, "registry-script-missing", "error", `Registry script target does not exist: ${path ?? scriptRef.ref}`, "Fix the script ext_resource path or restore the .gd."));
164
+ }
165
+ else {
166
+ scriptClass = readClassName(abs);
167
+ }
168
+ }
169
+ entries.push({ file: rel, scriptClass });
170
+ // Dangling resource-path properties across every section.
171
+ for (const section of [resource, ...sectionsOfKind(file, "sub_resource")].filter(Boolean)) {
172
+ for (const [key, v] of Object.entries(section.properties)) {
173
+ if (!looksLikeResourcePath(v, key, cfg.pathPropertyPatterns))
174
+ continue;
175
+ const abs = resolveResPath(root, v.string ?? "");
176
+ if (abs && !existsSync(abs)) {
177
+ findings.push(finding(rel, v.line, "registry-dangling-path", "error", `Property "${key}" points at a missing resource: ${v.string}`, "Fix the path or restore the referenced file. These fail only at runtime scene load."));
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ return { findings, entries };
184
+ }
185
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/scenes/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type PillarAvailability } from "./capabilities.js";
2
+ export declare const SERVER_NAME = "godot-correctness-mcp";
3
+ /** Read the package version from the shipped package.json. */
4
+ export declare function readVersion(): string;
5
+ export interface ServerInfo {
6
+ name: string;
7
+ version: string;
8
+ /** Godot minor versions this server has data for (4.4, 4.5). */
9
+ godotVersions: string[];
10
+ pillars: PillarAvailability;
11
+ }
12
+ /** Health payload: version, pinned Godot versions, and pillar availability. */
13
+ export declare function getServerInfo(): ServerInfo;