summer-engine 2.6.6 → 2.8.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 (168) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/plugin.json +6 -2
  4. package/.cursor-plugin/plugin.json +5 -1
  5. package/.opencode/INSTALL.md +1 -1
  6. package/.opencode/plugins/summer.js +2 -2
  7. package/AGENTS.md +11 -7
  8. package/CHANGELOG.md +40 -0
  9. package/GEMINI.md +9 -7
  10. package/README.md +63 -21
  11. package/_persona/summer/SKILL.md +1 -0
  12. package/assets/autopilot/README.md +51 -0
  13. package/assets/autopilot/autopilot.gd +222 -0
  14. package/assets/autopilot/probe_base.gd +262 -0
  15. package/assets/autopilot/run.sh +78 -0
  16. package/commands/gameskill.md +11 -2
  17. package/commands/summer.md +13 -0
  18. package/dist/bin/summer.js +12 -0
  19. package/dist/commands/config.d.ts +2 -0
  20. package/dist/commands/config.js +53 -0
  21. package/dist/commands/create.d.ts +1 -0
  22. package/dist/commands/create.js +38 -6
  23. package/dist/commands/debug.d.ts +2 -0
  24. package/dist/commands/debug.js +44 -0
  25. package/dist/commands/doctor.js +112 -8
  26. package/dist/commands/list.js +1 -1
  27. package/dist/commands/login.d.ts +19 -0
  28. package/dist/commands/login.js +123 -36
  29. package/dist/commands/logout.js +3 -16
  30. package/dist/commands/logs.d.ts +2 -0
  31. package/dist/commands/logs.js +19 -0
  32. package/dist/commands/mcp.js +7 -2
  33. package/dist/commands/open.js +1 -1
  34. package/dist/commands/publish.d.ts +2 -0
  35. package/dist/commands/publish.js +20 -0
  36. package/dist/commands/releases.d.ts +2 -0
  37. package/dist/commands/releases.js +20 -0
  38. package/dist/commands/run.js +1 -1
  39. package/dist/commands/skills.js +1 -1
  40. package/dist/lib/api-client.d.ts +15 -4
  41. package/dist/lib/api-client.js +138 -13
  42. package/dist/lib/async-op-lifecycle.d.ts +7 -2
  43. package/dist/lib/async-op-lifecycle.js +32 -9
  44. package/dist/lib/auth.d.ts +40 -12
  45. package/dist/lib/auth.js +173 -37
  46. package/dist/lib/cloud/engine-bridge.d.ts +17 -4
  47. package/dist/lib/cloud/engine-bridge.js +32 -6
  48. package/dist/lib/config.d.ts +20 -0
  49. package/dist/lib/config.js +142 -0
  50. package/dist/lib/creator.d.ts +93 -0
  51. package/dist/lib/creator.js +393 -0
  52. package/dist/lib/debug-report.d.ts +50 -0
  53. package/dist/lib/debug-report.js +308 -0
  54. package/dist/lib/engine.d.ts +35 -2
  55. package/dist/lib/engine.js +240 -6
  56. package/dist/lib/game-task-plan.js +4 -4
  57. package/dist/lib/mcp-boot-notice.d.ts +3 -0
  58. package/dist/lib/mcp-boot-notice.js +7 -0
  59. package/dist/lib/mcp-log.d.ts +3 -0
  60. package/dist/lib/mcp-log.js +72 -0
  61. package/dist/lib/public-surface-paths.d.ts +13 -0
  62. package/dist/lib/public-surface-paths.js +42 -0
  63. package/dist/lib/skills-registry.d.ts +33 -1
  64. package/dist/lib/skills-registry.js +55 -0
  65. package/dist/lib/store.d.ts +14 -0
  66. package/dist/lib/store.js +145 -0
  67. package/dist/lib/summer-compatibility.d.ts +6 -0
  68. package/dist/lib/summer-compatibility.js +22 -0
  69. package/dist/mcp/server.d.ts +10 -3
  70. package/dist/mcp/server.js +104 -11
  71. package/dist/mcp/tools/asset-tools.js +33 -12
  72. package/dist/mcp/tools/cloud-tools.js +15 -15
  73. package/dist/mcp/tools/creator-tools.d.ts +2 -0
  74. package/dist/mcp/tools/creator-tools.js +105 -0
  75. package/dist/mcp/tools/debug-tools.js +44 -1
  76. package/dist/mcp/tools/file-tools.d.ts +2 -0
  77. package/dist/mcp/tools/file-tools.js +154 -0
  78. package/dist/mcp/tools/generate-tools.js +159 -11
  79. package/dist/mcp/tools/project-tools.js +37 -27
  80. package/dist/mcp/tools/scene-tools.js +106 -29
  81. package/dist/mcp/tools/visual-tools.js +4 -3
  82. package/dist/mcp/tools/with-engine.js +4 -4
  83. package/hooks/session-start.sh +5 -5
  84. package/package.json +3 -1
  85. package/references/collaborative-protocol.md +50 -0
  86. package/references/gd-style.md +121 -0
  87. package/references/godot-version.md +51 -0
  88. package/references/mcp-tools-reference.md +199 -0
  89. package/references/public-surface-language.md +30 -0
  90. package/references/summer-folder.md +90 -0
  91. package/references/template-registry.md +73 -0
  92. package/skills/2d-assets/character-portrait/SKILL.md +17 -12
  93. package/skills/2d-assets/concept-art/SKILL.md +10 -9
  94. package/skills/2d-assets/create-asset-sheet/SKILL.md +17 -15
  95. package/skills/2d-assets/pixel-art/SKILL.md +36 -20
  96. package/skills/2d-assets/skybox-panorama/SKILL.md +32 -37
  97. package/skills/2d-assets/sprite-sheet/SKILL.md +40 -35
  98. package/skills/2d-assets/tileable-texture/SKILL.md +28 -22
  99. package/skills/2d-assets/ui-graphics/SKILL.md +46 -32
  100. package/skills/2d-assets/use-widget-asset/SKILL.md +8 -6
  101. package/skills/3d-assets/character-model/SKILL.md +34 -19
  102. package/skills/3d-assets/environment-kit/SKILL.md +11 -5
  103. package/skills/3d-assets/organic-model/SKILL.md +8 -5
  104. package/skills/3d-assets/prop-model/SKILL.md +9 -3
  105. package/skills/3d-assets/vehicle-model/SKILL.md +32 -19
  106. package/skills/README.md +1 -1
  107. package/skills/ai-and-npcs/design-npc/SKILL.md +31 -13
  108. package/skills/animation/animation-tree/SKILL.md +39 -15
  109. package/skills/animation/facial-and-lipsync/SKILL.md +25 -8
  110. package/skills/animation/generate-motion/SKILL.md +47 -15
  111. package/skills/animation/procedural-animation/SKILL.md +31 -13
  112. package/skills/animation/retarget/SKILL.md +127 -79
  113. package/skills/audio/adaptive-music/SKILL.md +24 -20
  114. package/skills/audio/ambient-bed/SKILL.md +22 -16
  115. package/skills/audio/audio-direction/SKILL.md +15 -10
  116. package/skills/audio/music-track/SKILL.md +18 -12
  117. package/skills/audio/sound-effect/SKILL.md +17 -12
  118. package/skills/audio/voice-line/SKILL.md +42 -42
  119. package/skills/character-controllers/fps-controller/SKILL.md +26 -16
  120. package/skills/debugging/debug/SKILL.md +74 -20
  121. package/skills/deployment/export-and-ship/SKILL.md +105 -38
  122. package/skills/deployment/remote-deploy/SKILL.md +140 -0
  123. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +26 -1
  124. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +23 -15
  125. package/skills/level-design/design-level/SKILL.md +22 -8
  126. package/skills/level-design/scene-to-level/SKILL.md +13 -7
  127. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +8 -8
  128. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +2 -7
  129. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +26 -18
  130. package/skills/performance/tune-performance/SKILL.md +51 -32
  131. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +34 -5
  132. package/skills/rendering-and-lighting/art-direction/SKILL.md +6 -8
  133. package/skills/scene-and-project/brainstorm-game/SKILL.md +7 -9
  134. package/skills/scene-and-project/make-game/SKILL.md +2 -1
  135. package/skills/scene-and-project/play/SKILL.md +10 -4
  136. package/skills/scene-and-project/scene-composition/SKILL.md +21 -8
  137. package/skills/scene-and-project/summer-cloud/SKILL.md +92 -0
  138. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +1 -1
  139. package/skills/ui-and-ux/ui-basics/SKILL.md +16 -7
  140. package/skills/video/animated-loop/SKILL.md +63 -39
  141. package/skills/video/cinematic-cutscene/SKILL.md +66 -30
  142. package/skills/video/trailer-shot/SKILL.md +30 -21
  143. package/skills/visual-effects/_building-blocks/additive-billboard-particles.md +25 -16
  144. package/skills/visual-effects/_building-blocks/trauma-shake-snippet.md +5 -3
  145. package/skills/visual-effects/game-feel/SKILL.md +51 -33
  146. package/skills/visual-effects/recipes/dissolve/SKILL.md +51 -9
  147. package/skills/visual-effects/recipes/fire/SKILL.md +136 -49
  148. package/skills/visual-effects/recipes/hit-spark/SKILL.md +45 -8
  149. package/skills/visual-effects/recipes/lightning/SKILL.md +59 -27
  150. package/skills/visual-effects/recipes/magic-glow/SKILL.md +59 -26
  151. package/skills/visual-effects/recipes/muzzle-flash/SKILL.md +68 -25
  152. package/skills/visual-effects/recipes/smoke/SKILL.md +89 -28
  153. package/skills/visual-effects/recipes/water-ripple/SKILL.md +83 -23
  154. package/skills/workflow/brainstorming/SKILL.md +1 -1
  155. package/skills/workflow/debugging-game-feel/SKILL.md +29 -9
  156. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +46 -21
  157. package/skills/workflow/gameskill/SKILL.md +11 -2
  158. package/skills/workflow/headless-scripting/SKILL.md +407 -0
  159. package/skills/workflow/investigating-bugs/SKILL.md +4 -2
  160. package/skills/workflow/mcpupdate/SKILL.md +9 -9
  161. package/skills/workflow/playtesting-a-feature/SKILL.md +94 -21
  162. package/skills/workflow/skill-create/SKILL.md +7 -7
  163. package/skills/workflow/skill-test/SKILL.md +3 -3
  164. package/skills/workflow/using-summer/SKILL.md +32 -3
  165. package/skills/workflow/verification-before-completion/SKILL.md +2 -0
  166. package/skills/workflow/writing-plans/SKILL.md +7 -7
  167. package/skills/workflow/writing-skills/SKILL.md +7 -5
  168. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +1 -1
@@ -2,6 +2,27 @@ import { z } from "zod";
2
2
  import { withEngine } from "./with-engine.js";
3
3
  import { readFile } from "fs/promises";
4
4
  import { join } from "path";
5
+ function sceneMutationOps(ops) {
6
+ const saveIndexes = ops
7
+ .map((op, index) => op.op === "SaveScene" ? index : -1)
8
+ .filter((index) => index >= 0);
9
+ if (saveIndexes.length > 1) {
10
+ throw new Error("A scene mutation batch may contain only one SaveScene");
11
+ }
12
+ if (saveIndexes.length === 1) {
13
+ if (saveIndexes[0] !== ops.length - 1) {
14
+ throw new Error("SaveScene must be the final operation in a scene mutation batch");
15
+ }
16
+ return ops;
17
+ }
18
+ return [...ops, { op: "SaveScene" }];
19
+ }
20
+ function executeSceneMutation(client, scenePath, ops, options) {
21
+ return client.executeIdentityBoundOps(sceneMutationOps(ops), {
22
+ ...(options ?? {}),
23
+ scenePath,
24
+ });
25
+ }
5
26
  async function readMainSceneFromProject(projectPath) {
6
27
  if (!projectPath)
7
28
  return null;
@@ -14,6 +35,20 @@ async function readMainSceneFromProject(projectPath) {
14
35
  return null;
15
36
  }
16
37
  }
38
+ function requireSuccessfulOps(result, context) {
39
+ const receipt = (result ?? {});
40
+ const results = Array.isArray(receipt.results)
41
+ ? receipt.results
42
+ : [];
43
+ const failed = results.find((entry) => entry?.ok === false);
44
+ if (receipt.status === "error" || failed) {
45
+ const error = (typeof failed?.error === "string" && failed.error) ||
46
+ (typeof receipt.error === "string" && receipt.error) ||
47
+ `${context} failed`;
48
+ throw new Error(error);
49
+ }
50
+ return receipt;
51
+ }
17
52
  export function registerSceneTools(server) {
18
53
  server.tool("summer_create_scene", `Create a new empty scene file safely.
19
54
 
@@ -45,19 +80,22 @@ Recommended workflow:
45
80
  throw new Error("No scene open and could not resolve main scene. Call summer_get_project_context first, then open a known scene.");
46
81
  }
47
82
  await client.executeOps([{ op: "OpenScene", path: templateScene }]);
48
- const tree = (await client.getSceneState());
83
+ const tree = (await client.getSceneState(templateScene));
49
84
  const children = tree.data?.children ?? tree.children ?? [];
50
85
  const removeOps = children
51
86
  .map((c) => c.path)
52
87
  .filter((p) => typeof p === "string" && p.length > 0)
53
88
  .map((p) => ({ op: "RemoveNode", path: `./${p}` }));
54
- if (removeOps.length > 0) {
55
- await client.executeOps(removeOps, { groupUndo: true });
56
- }
57
- await client.executeOps([{ op: "SetProp", path: ".", key: "name", value: rootName }]);
58
- await client.executeOps([{ op: "SaveScene", path }]);
89
+ const createOps = [
90
+ ...removeOps,
91
+ { op: "SetProp", path: ".", key: "name", value: rootName },
92
+ { op: "SaveScene", path },
93
+ ];
94
+ const createReceipt = requireSuccessfulOps(await executeSceneMutation(client, templateScene, createOps, {
95
+ groupUndo: true,
96
+ }), `Creating ${path}`);
59
97
  if (currentScene && currentScene !== path) {
60
- await client.executeOps([{ op: "OpenScene", path: currentScene }]);
98
+ requireSuccessfulOps(await client.executeOps([{ op: "OpenScene", path: currentScene }]), `Restoring ${currentScene}`);
61
99
  }
62
100
  return {
63
101
  ok: true,
@@ -65,14 +103,13 @@ Recommended workflow:
65
103
  rootName,
66
104
  templateScene,
67
105
  restoredScene: currentScene,
106
+ receipt: createReceipt,
68
107
  };
69
108
  }));
70
109
  server.tool("summer_add_node", `Add a new node to the scene tree.
71
110
 
72
- Preflight (fresh chat):
73
- 1) summer_get_project_context
74
- 2) summer_open_main_scene (if no scene open)
75
- 3) summer_get_scene_tree
111
+ Pass the exact res:// scenePath to mutate. The scene does not need to be the
112
+ active editor tab.
76
113
 
77
114
  Common node types:
78
115
  - 3D: Node3D, MeshInstance3D, CharacterBody3D, RigidBody3D, StaticBody3D, Camera3D, DirectionalLight3D, OmniLight3D, SpotLight3D, WorldEnvironment, CollisionShape3D, Area3D
@@ -81,10 +118,11 @@ Common node types:
81
118
  - Audio: AudioStreamPlayer, AudioStreamPlayer3D
82
119
 
83
120
  The parent path uses "./" prefix for relative paths from scene root. E.g., "./World" means the "World" child of the root node.`, {
121
+ scenePath: z.string().describe("Target scene path, e.g. 'res://main.tscn'"),
84
122
  parent: z.string().describe("Parent node path, e.g. './World' or './World/Enemies'"),
85
- type: z.string().describe("Godot node type, e.g. 'MeshInstance3D', 'CharacterBody3D'"),
123
+ type: z.string().describe("Summer Engine node type, e.g. 'MeshInstance3D', 'CharacterBody3D'"),
86
124
  name: z.string().describe("Name for the new node, e.g. 'Player', 'MainCamera'"),
87
- }, async ({ parent, type, name }) => withEngine(async (client) => client.executeOps([{ op: "AddNode", parent, type, name }])));
125
+ }, async ({ scenePath, parent, type, name }) => withEngine(async (client) => executeSceneMutation(client, scenePath, [{ op: "AddNode", parent, type, name }])));
88
126
  server.tool("summer_set_prop", `Set a property on a node. This is the primary way to configure nodes after adding them.
89
127
 
90
128
  VALUE FORMAT — Godot string syntax for complex types:
@@ -106,29 +144,37 @@ COMMON PROPERTIES:
106
144
  - shadow_enabled: true — for lights
107
145
  - light_energy: 1.5 — light intensity
108
146
  - fov: 75.0 — camera field of view`, {
147
+ scenePath: z.string().describe("Target scene path, e.g. 'res://main.tscn'"),
109
148
  path: z.string().describe("Node path, e.g. './World/Player'"),
110
149
  key: z.string().describe("Property name, e.g. 'position', 'mesh', 'visible'"),
111
- value: z.union([z.string(), z.number(), z.boolean()]).describe("Value in Godot string format for complex types, native JSON for primitives"),
112
- }, async ({ path, key, value }) => withEngine(async (client) => client.executeOps([{ op: "SetProp", path, key, value }])));
150
+ value: z.union([z.string(), z.number(), z.boolean()]).describe("Value in Summer Engine variant-string format for complex types, native JSON for primitives"),
151
+ }, async ({ scenePath, path, key, value }) => withEngine(async (client) => executeSceneMutation(client, scenePath, [{ op: "SetProp", path, key, value }])));
113
152
  server.tool("summer_set_resource_property", `Set a nested property on a resource attached to a node.
114
153
 
115
154
  Use when you need to modify a sub-property of a resource, like:
116
155
  - CollisionShape3D shape size: nodePath="./Player/CollisionShape3D", resourceProperty="shape", subProperty="size", value="Vector3(1, 2, 1)"
117
156
  - Material albedo color: nodePath="./Floor", resourceProperty="material_override", subProperty="albedo_color", value="Color(0.2, 0.5, 0.2, 1)"
118
157
  - Mesh size: nodePath="./Box", resourceProperty="mesh", subProperty="size", value="Vector3(2, 2, 2)"`, {
158
+ scenePath: z.string().describe("Target scene path, e.g. 'res://main.tscn'"),
119
159
  nodePath: z.string().describe("Node path, e.g. './Player/CollisionShape3D'"),
120
160
  resourceProperty: z.string().describe("Resource property on the node, e.g. 'shape', 'mesh', 'material_override'"),
121
161
  subProperty: z.string().describe("Property on the resource, e.g. 'size', 'radius', 'albedo_color'"),
122
- value: z.union([z.string(), z.number(), z.boolean()]).describe("Value in Godot string format"),
123
- }, async ({ nodePath, resourceProperty, subProperty, value }) => withEngine(async (client) => client.executeOps([
162
+ value: z.union([z.string(), z.number(), z.boolean()]).describe("Value in Summer Engine variant-string format"),
163
+ }, async ({ scenePath, nodePath, resourceProperty, subProperty, value }) => withEngine(async (client) => executeSceneMutation(client, scenePath, [
124
164
  { op: "SetResourceProperty", nodePath, resourceProperty, subProperty, value },
125
165
  ])));
126
- server.tool("summer_remove_node", "Remove a node from the scene tree. All children are removed too. Cannot remove the root node. Supports undo. Destructive operation: do not delete multiple top-level nodes unless the user explicitly requests destructive changes.", { path: z.string().describe("Node path to remove, e.g. './World/OldEnemy'") }, async ({ path }) => withEngine(async (client) => client.executeOps([{ op: "RemoveNode", path }])));
127
- server.tool("summer_save_scene", "Save the current scene to disk. Call this after making scene changes to persist them. Without saving, changes only exist in the editor's memory.", { path: z.string().optional().describe("Save-as path for creating a new scene file, e.g. 'res://levels/level2.tscn'") }, async ({ path }) => withEngine(async (client) => {
166
+ server.tool("summer_remove_node", "Remove a node from the scene tree. All children are removed too. Cannot remove the root node. Supports undo. Destructive operation: do not delete multiple top-level nodes unless the user explicitly requests destructive changes.", {
167
+ scenePath: z.string().describe("Target scene path, e.g. 'res://main.tscn'"),
168
+ path: z.string().describe("Node path to remove, e.g. './World/OldEnemy'"),
169
+ }, async ({ scenePath, path }) => withEngine(async (client) => executeSceneMutation(client, scenePath, [{ op: "RemoveNode", path }])));
170
+ server.tool("summer_save_scene", "Save an explicit scene to disk. Mutation tools already append one save; use this for a standalone save or save-as.", {
171
+ scenePath: z.string().describe("Scene to save, e.g. 'res://main.tscn'"),
172
+ path: z.string().optional().describe("Optional save-as path, e.g. 'res://levels/level2.tscn'"),
173
+ }, async ({ scenePath, path }) => withEngine(async (client) => {
128
174
  const op = { op: "SaveScene" };
129
175
  if (path)
130
176
  op.path = path;
131
- return client.executeOps([op]);
177
+ return executeSceneMutation(client, scenePath, [op]);
132
178
  }));
133
179
  server.tool("summer_open_scene", `Open a scene file in the editor. Use this to switch between scenes.
134
180
 
@@ -142,14 +188,15 @@ Do not guess paths. Prefer:
142
188
  - Compose scenes from smaller scenes (e.g., add a "Player" scene into a "Level" scene)
143
189
 
144
190
  The scene must already exist in the project. Use summer_import_from_url first if importing from external sources.`, {
191
+ scenePath: z.string().describe("Target scene to receive the instance, e.g. 'res://main.tscn'"),
145
192
  parent: z.string().describe("Parent node path, e.g. './World'"),
146
193
  scene: z.string().describe("Scene/model path, e.g. 'res://player.tscn' or 'res://models/tree.glb'"),
147
194
  name: z.string().optional().describe("Override the instance name"),
148
- }, async ({ parent, scene, name }) => withEngine(async (client) => {
195
+ }, async ({ scenePath, parent, scene, name }) => withEngine(async (client) => {
149
196
  const op = { op: "InstantiateScene", parent, scene };
150
197
  if (name)
151
198
  op.name = name;
152
- return client.executeOps([op]);
199
+ return executeSceneMutation(client, scenePath, [op]);
153
200
  }));
154
201
  server.tool("summer_connect_signal", `Connect a signal between two nodes. Signals are Godot's event system — they notify when something happens.
155
202
 
@@ -161,11 +208,12 @@ Common signals:
161
208
  - "input_event" — CollisionObject received input
162
209
 
163
210
  The receiver node must have a script with the specified method.`, {
211
+ scenePath: z.string().describe("Target scene path, e.g. 'res://main.tscn'"),
164
212
  emitter: z.string().describe("Node that fires the signal, e.g. './Player/HitArea'"),
165
213
  signal: z.string().describe("Signal name, e.g. 'body_entered'"),
166
214
  receiver: z.string().describe("Node with the handler script, e.g. './Player'"),
167
215
  method: z.string().describe("Method name in the receiver's script, e.g. '_on_hit_area_body_entered'"),
168
- }, async ({ emitter, signal, receiver, method }) => withEngine(async (client) => client.executeOps([
216
+ }, async ({ scenePath, emitter, signal, receiver, method }) => withEngine(async (client) => executeSceneMutation(client, scenePath, [
169
217
  { op: "ConnectSignal", emitter, signal, receiver, method },
170
218
  ])));
171
219
  server.tool("summer_select_node", "Select a node in the editor's scene tree and show it in the inspector panel. Useful for focusing the editor on a specific node.", {
@@ -178,16 +226,17 @@ The receiver node must have a script with the specified method.`, {
178
226
  return client.executeOps([op]);
179
227
  }));
180
228
  server.tool("summer_replace_node", "Replace a node with a different type or scene, preserving its position in the tree and its children. Useful for changing a StaticBody3D to a RigidBody3D, or swapping a placeholder with a proper prefab.", {
229
+ scenePath: z.string().describe("Target scene path, e.g. 'res://main.tscn'"),
181
230
  path: z.string().describe("Node path to replace"),
182
231
  type: z.string().optional().describe("New node type, e.g. 'RigidBody3D'"),
183
232
  scene: z.string().optional().describe("Scene to replace with, e.g. 'res://enemies/boss.tscn'"),
184
- }, async ({ path, type, scene }) => withEngine(async (client) => {
233
+ }, async ({ scenePath, path, type, scene }) => withEngine(async (client) => {
185
234
  const op = { op: "ReplaceNode", path };
186
235
  if (type)
187
236
  op.type = type;
188
237
  if (scene)
189
238
  op.scene = scene;
190
- return client.executeOps([op]);
239
+ return executeSceneMutation(client, scenePath, [op]);
191
240
  }));
192
241
  server.tool("summer_inspect_node", `Get all editable properties of a node with their current values, types, and resource info.
193
242
 
@@ -211,9 +260,37 @@ Each op in the array uses the same format as the individual tools:
211
260
  - {"op": "SetProp", "path": "Floor", "key": "mesh", "value": "PlaneMesh"}
212
261
  - {"op": "SetResourceProperty", "nodePath": "Floor", "resourceProperty": "mesh", "subProperty": "size", "value": "Vector2(20, 20)"}
213
262
 
214
- RAW RUNTIME OPS (interactive verification — engine-build dependent; structured failure_reason incl "unsupported" passes through verbatim):
215
- - SimulateInput — drive the RUNNING game (summer_play first): {"op": "SimulateInput", "type": "action", "action": "jump", "pressed": true}. type is "action" | "key" | "mouse_click" | "axis".
216
- - RunVerification — spawn a hidden, disposable game instance that runs a GDScript probe and dies (never touches the editor): {"op": "RunVerification", "probe_source": "extends SummerProbeBase\\nfunc _ready(): await super._ready(); report('ok', true); finish()", "max_seconds": 20}. Returns {ok, results, frames, out_dir}. Probe API: report()/save_frame()/press()/key()/finish().`, {
263
+ RAW RUNTIME OPS (interactive verification — structured failure_reason passes through verbatim):
264
+ - RunVerification — spawn a hidden, disposable game instance that runs a GDScript probe and dies (never touches the editor): {"op": "RunVerification", "probe_source": "extends SummerProbeBase\\nfunc _ready(): await super._ready(); report('ok', true); finish()", "max_seconds": 20}. Returns {ok, results, frames, out_dir}. Probe API: report()/save_frame()/press()/key()/finish(). This is the ONLY way to drive input from MCP, and unlike the editor it renders real pixels, so save_frame() produces a real image.
265
+ - SimulateInput is NOT reachable from MCP or the CLI. It requires the in-editor chat bridge's async reply channel; every queued caller gets {"ok": false, "failure_reason": "unsupported_transport"} on every engine build. Do not send it and do not treat that reply as a version problem — use RunVerification's press()/key() instead.
266
+
267
+ Do not mix OpenScene with scene mutations in one batch. OpenScene is a UI action;
268
+ send it separately. scenePath selects every mutation target. The tool appends one
269
+ final SaveScene when the batch mutates a scene; if supplied explicitly, SaveScene
270
+ must appear exactly once and be the final operation.`, {
271
+ scenePath: z.string().optional().describe("Required when ops contains scene mutations; exact res:// target scene path"),
217
272
  ops: z.array(z.record(z.unknown())).describe("Array of operation objects, each with 'op' plus its parameters"),
218
- }, async ({ ops }) => withEngine(async (client) => client.executeOps(ops, { groupUndo: true })));
273
+ }, async ({ scenePath, ops }) => withEngine(async (client) => {
274
+ const rawFileMutation = ops.find((op) => {
275
+ const kind = String(op.op ?? "");
276
+ return kind === "WriteFile" || kind === "ReplaceText";
277
+ });
278
+ if (rawFileMutation) {
279
+ throw new Error(`summer_batch does not accept raw ${String(rawFileMutation.op)} operations. ` +
280
+ "Use summer_write_file or summer_replace_text so project identity, content guards, and same-file ordering are enforced.");
281
+ }
282
+ const sceneMutations = new Set([
283
+ "AddNode", "RemoveNode", "MoveNode", "ReparentNode", "ReplaceNode",
284
+ "SetProp", "SetResourceProperty", "ConnectSignal", "DisconnectSignal",
285
+ "InstantiateScene", "SaveScene", "Undo",
286
+ ]);
287
+ const needsScenePath = ops.some((op) => sceneMutations.has(String(op.op ?? "")));
288
+ if (needsScenePath && !scenePath) {
289
+ throw new Error("summer_batch requires scenePath when ops contains scene mutations");
290
+ }
291
+ const options = { groupUndo: true, ...(scenePath ? { scenePath } : {}) };
292
+ return needsScenePath
293
+ ? executeSceneMutation(client, scenePath, ops, options)
294
+ : client.executeOps(ops, options);
295
+ }));
219
296
  }
@@ -10,14 +10,15 @@ import { withEngine } from "./with-engine.js";
10
10
  export function registerVisualTools(server) {
11
11
  server.tool("summer_screenshot", `Capture a frame from Summer Engine and return it as an image you can look at directly.
12
12
 
13
- Use this to visually verify your work: scene layout, asset placement, scale, framing, lighting, materials, missing/untextured assets, or runtime gameplay state. You see the actual pixels — no description layer in between.
13
+ Use this to visually verify your work: scene layout, asset placement, scale, framing, missing/untextured assets, or runtime gameplay state. You see the actual pixels — no description layer in between. Lighting and materials are only truthfully shown by the "viewport" and "game" targets — see the note on "scene" below.
14
14
 
15
15
  target:
16
16
  "viewport" (default) — the editor's CURRENT view (whatever scene/tab is open). No game boot. Use for edit-time checks of how the scene looks right now.
17
- "scene" — an OFFSCREEN render of a scene file (no game boot; physics/particles/animations are static at t=0). Optionally pass scenePath/framing/size/nodePath. Use for a quick composed look at a scene without touching the editor's open tab.
17
+ "scene" — an OFFSCREEN render of a scene file (no game boot; physics/particles/animations are static at t=0). Optionally pass scenePath/framing/size/nodePath. Use for COMPOSITION, SCALE and FRAMING without touching the editor's open tab.
18
+ It does NOT use the scene's environment/sky, and it injects a synthetic camera and light when the scene has none. The scene's WorldEnvironment — sky, fog, tonemap, glow, SSAO, ambient — is replaced by a flat preview environment. So this target CANNOT verify lighting, mood, or any material property that depends on the environment: change them and the frame comes back identical. For those, boot the game or run a RunVerification probe, whose instance renders the real environment.
18
19
  "game" — a frame from the RUNNING game (real runtime state). Start the game first (summer_play). Not available over a plain local connection — needs the Summer desktop app bridge.
19
20
 
20
- Static frame only — one moment, not motion. If a game capture is unavailable, use "viewport" or "scene", or ask the user to share a screenshot of the running game.`, {
21
+ Static frame only — one moment, not motion. For a SEQUENCE of frames over time, or for anything lighting-dependent when the desktop bridge is unavailable, use a RunVerification probe's save_frame() — its instance has a real renderer.`, {
21
22
  target: z
22
23
  .enum(["viewport", "scene", "game"])
23
24
  .optional()
@@ -19,7 +19,10 @@ const SUCCESS_TERMINAL_STATES = new Set(["applied", "no_op"]);
19
19
  // `error` string (queue-full / lease-reject / identity-mismatch / no-progress
20
20
  // timeout frequently arrive with terminalState set and results[] absent).
21
21
  const TERMINAL_STATE_MESSAGES = {
22
- timed_out: "Engine operation timed out (terminalState: timed_out). Nothing was applied.",
22
+ timed_out: "Engine operation timed out. Its final state is unknown; inspect the target before retrying.",
23
+ still_queued: "Summer Engine accepted the operation, but it was still queued when the client stopped waiting. It may still run; do not retry blindly.",
24
+ still_running: "Summer Engine accepted and started the operation, but no final receipt arrived. It may still be running or may already have applied; inspect the target before retrying.",
25
+ uncertain: "Summer Engine did not provide a final operation receipt. The current state is uncertain; inspect the target before retrying.",
23
26
  not_connected: "Summer Engine is not connected (terminalState: not_connected). Nothing was applied.",
24
27
  identity_mismatch: "Operation rejected — wrong project/instance (terminalState: identity_mismatch). Nothing was mutated.",
25
28
  content_mismatch: "Operation rejected — content changed since last read (terminalState: content_mismatch). Nothing was applied.",
@@ -117,9 +120,6 @@ function buildActionHint(message) {
117
120
  if (normalized.includes("failed to open scene")) {
118
121
  return "Scene path could not be opened. Call `summer_get_project_context` to get `mainScene`, then open that exact path. Avoid guessing scene filenames.";
119
122
  }
120
- if (normalized.includes("writefile cannot edit .tscn/.scn")) {
121
- return "Write .gd/.cs/.json/docs/simple config files with normal file-edit tools. Use Summer MCP for live scene state, inspector/node edits, imports, play/stop, diagnostics, and visual verification.";
122
- }
123
123
  return null;
124
124
  }
125
125
  export async function withEngine(fn, opts) {
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # Event: SessionStart (matcher: startup|clear|compact)
5
5
  # Purpose: Orient Claude on every fresh Summer-enabled session.
6
- # Detect Godot project + GameSoul + doctor health, surface a one-line
6
+ # Detect Summer project + GameSoul + doctor health, surface a one-line
7
7
  # Bob-style greeting as additional context.
8
8
  #
9
9
  # Input: JSON on stdin with { session_id, source, model, cwd, ... }
@@ -36,7 +36,7 @@ else
36
36
  LINES+=("Summer ready.")
37
37
  fi
38
38
 
39
- # Line 2: Godot project + GameSoul one-liner (only if project.godot exists)
39
+ # Line 2: Summer project + GameSoul one-liner (only if project.godot exists)
40
40
  if [ -f "${PROJECT_DIR}/project.godot" ]; then
41
41
  PROJECT_NAME=$(grep -E '^config/name=' "${PROJECT_DIR}/project.godot" 2>/dev/null \
42
42
  | head -1 | sed 's/^config\/name=//;s/^"//;s/"$//')
@@ -48,12 +48,12 @@ if [ -f "${PROJECT_DIR}/project.godot" ]; then
48
48
  SOUL_LINE=$(awk 'BEGIN{p=""} /^[^#[:space:]]/ && NF>0 {p=$0; exit} END{print p}' \
49
49
  "$SOUL_FILE" 2>/dev/null | cut -c1-120)
50
50
  if [ -n "$SOUL_LINE" ]; then
51
- LINES+=("Godot project: ${PROJECT_NAME}. GameSoul: ${SOUL_LINE}")
51
+ LINES+=("Summer project: ${PROJECT_NAME}. GameSoul: ${SOUL_LINE}")
52
52
  else
53
- LINES+=("Godot project: ${PROJECT_NAME}.")
53
+ LINES+=("Summer project: ${PROJECT_NAME}.")
54
54
  fi
55
55
  else
56
- LINES+=("Godot project: ${PROJECT_NAME}.")
56
+ LINES+=("Summer project: ${PROJECT_NAME}.")
57
57
  fi
58
58
  fi
59
59
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "summer-engine",
3
- "version": "2.6.6",
3
+ "version": "2.8.0",
4
4
  "description": "Local Summer CLI and MCP server for Summer Engine. Install and run the engine, connect Claude Code, Cursor, Codex, Gemini, and other agents, and build real games with bundled skills, hooks, and plugins.",
5
5
  "keywords": [
6
6
  "summer-engine",
@@ -41,6 +41,7 @@
41
41
  "files": [
42
42
  "dist/",
43
43
  "skills/",
44
+ "references/",
44
45
  "commands/",
45
46
  "hooks/",
46
47
  "assets/",
@@ -65,6 +66,7 @@
65
66
  "dev": "tsc --watch",
66
67
  "summer": "node dist/bin/summer.js",
67
68
  "test": "vitest run",
69
+ "test:public-language": "vitest run src/lib/public-surface-language.test.ts",
68
70
  "test:watch": "vitest",
69
71
  "prepublishOnly": "npm run build && npm test"
70
72
  },
@@ -0,0 +1,50 @@
1
+ # Collaborative Protocol — "May I write?"
2
+
3
+ > Every Summer skill must follow this. The agent works *with* the user, not around them.
4
+
5
+ ## The rule
6
+
7
+ **Before any user-visible write step, the agent must explicitly state what it is about to do and ask permission to proceed.**
8
+
9
+ This applies to:
10
+ - Creating files (`.gd`, `.tscn`, `.tres`, JSON, configs)
11
+ - Modifying existing files (especially `.gd` and `.tscn`)
12
+ - Running irreversible operations (deletion, reset, force-overwrite)
13
+ - Setting up project-wide settings (`project.godot`)
14
+ - Installing tools, libraries, or templates
15
+
16
+ Reading is unconditional. Inspection, scene-tree walks, diagnostics — never need permission.
17
+
18
+ ## Phrasing
19
+
20
+ Plain, short, action-first. Examples:
21
+
22
+ > May I create `scenes/player.tscn` and add a CharacterBody3D + Camera3D?
23
+
24
+ > I'm about to overwrite `levels/level_01.tscn`. The current version has 14 nodes — I'll preserve them and add the new lighting setup. Continue?
25
+
26
+ > May I attach `scripts/player_controller.gd` to `./World/Player`?
27
+
28
+ > I'm going to bind `move_forward`, `move_back`, `move_left`, `move_right`, `jump`. OK?
29
+
30
+ ## What this prevents
31
+
32
+ 1. **Silent overwrites.** The agent stomps the user's hand-edited script.
33
+ 2. **Cascading scope creep.** A "small fix" turns into a 30-file refactor.
34
+ 3. **Unclear state.** The user has no idea what changed when something breaks.
35
+ 4. **Trust loss.** One unauthorized edit and the user stops trusting the agent.
36
+
37
+ ## What this is NOT
38
+
39
+ - Not a confirmation dialog for every property change in a single skill execution. Group related writes into one ask: "I'm about to add the player root, attach the controller script, and bind 5 input actions. OK?"
40
+ - Not a request for design input. The skill should know what to do; the ask is for permission, not direction.
41
+ - Not a stop on simple read-only ops or single-property tweaks the user clearly directed.
42
+
43
+ ## Linter check
44
+
45
+ The `workflow/skill-test/SKILL.md` static linter looks for the phrases:
46
+ - "May I" / "May I write" / "May I create" / "May I attach"
47
+ - "I'm about to"
48
+ - "Continue?" / "OK?" / "Proceed?"
49
+
50
+ A skill that mutates files but never says any of these fails the linter.
@@ -0,0 +1,121 @@
1
+ # GDScript Style — Summer Conventions
2
+
3
+ > The bar for GDScript in Summer. Skills should produce code that matches this. Where Godot's official style guide is silent, this is the tiebreaker.
4
+
5
+ ## File structure
6
+
7
+ ```gdscript
8
+ class_name PlayerController
9
+ extends CharacterBody3D
10
+
11
+ # 1. Constants
12
+ const SPEED := 5.0
13
+ const JUMP_VELOCITY := 4.5
14
+ const GRAVITY := 9.8
15
+
16
+ # 2. Exports (designer-tweakable in inspector)
17
+ @export var move_speed: float = 5.0
18
+ @export var jump_height: float = 1.0
19
+ @export_range(0.1, 10.0) var look_sensitivity: float = 1.0
20
+
21
+ # 3. Signals (events this node emits)
22
+ signal died
23
+ signal health_changed(new_value: int)
24
+
25
+ # 4. Onready references (cached node lookups)
26
+ @onready var camera: Camera3D = $Camera3D
27
+ @onready var collision: CollisionShape3D = $CollisionShape3D
28
+
29
+ # 5. Private state (snake_case, leading underscore optional)
30
+ var _is_grounded: bool = false
31
+ var _velocity := Vector3.ZERO
32
+
33
+ # 6. Lifecycle (in canonical order)
34
+ func _ready() -> void:
35
+ pass
36
+
37
+ func _process(delta: float) -> void:
38
+ pass
39
+
40
+ func _physics_process(delta: float) -> void:
41
+ pass
42
+
43
+ func _input(event: InputEvent) -> void:
44
+ pass
45
+
46
+ # 7. Public API (PascalCase or snake_case methods, snake_case for funcs)
47
+ func take_damage(amount: int) -> void:
48
+ pass
49
+
50
+ # 8. Private helpers (leading underscore)
51
+ func _calculate_movement(delta: float) -> Vector3:
52
+ return Vector3.ZERO
53
+ ```
54
+
55
+ ## Type hints
56
+
57
+ **Always type-hint.** No bare `var x = 5`. Use `:=` for inferred typing or `:` for explicit.
58
+
59
+ ```gdscript
60
+ # RIGHT
61
+ var speed: float = 5.0
62
+ var name := "Player"
63
+ func attack(target: Node3D, amount: int) -> bool:
64
+ return true
65
+
66
+ # WRONG
67
+ var speed = 5.0
68
+ func attack(target, amount):
69
+ return true
70
+ ```
71
+
72
+ ## Naming
73
+
74
+ | Thing | Convention | Example |
75
+ |---|---|---|
76
+ | Class | PascalCase | `class_name PlayerController` |
77
+ | File | snake_case | `player_controller.gd` |
78
+ | Variable, function | snake_case | `move_speed`, `take_damage()` |
79
+ | Constant | SCREAMING_SNAKE_CASE | `const MAX_HEALTH := 100` |
80
+ | Signal | snake_case (past tense for events) | `died`, `health_changed` |
81
+ | Private | leading underscore | `_is_grounded`, `_calculate_movement` |
82
+ | Node in scene | PascalCase | `Player`, `Camera3D`, `CollisionShape3D` |
83
+
84
+ ## Signals
85
+
86
+ Define with typed args. Connect in `_ready()` or in scene `.tscn` directly.
87
+
88
+ ```gdscript
89
+ signal health_changed(new_value: int, max_value: int)
90
+
91
+ func _ready() -> void:
92
+ health_changed.connect(_on_health_changed)
93
+
94
+ func _on_health_changed(new_value: int, max_value: int) -> void:
95
+ pass
96
+ ```
97
+
98
+ ## Tweens
99
+
100
+ Use `create_tween()` for animation. Don't manually lerp in `_process` unless you need fine control.
101
+
102
+ ```gdscript
103
+ var tween := create_tween()
104
+ tween.tween_property(self, "modulate", Color.WHITE, 0.3)
105
+ ```
106
+
107
+ ## What to avoid
108
+
109
+ - Bare `var x = something` (no type)
110
+ - `Object` typed parameters (use specific class)
111
+ - `match` for two-branch logic (use `if`)
112
+ - Recreating node lookups in `_process` (cache via `@onready`)
113
+ - Hard-coded strings for node paths in code (use `@onready` + scene-relative paths)
114
+ - Setting `position` in `_process` for physics objects (use `_physics_process` and `velocity`)
115
+
116
+ ## See also
117
+
118
+ - [Official Godot GDScript style guide](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html)
119
+ - `scripting-patterns/gdscript-patterns/SKILL.md` — patterns and idioms
120
+ - `scripting-patterns/signal-patterns/SKILL.md` — signal usage
121
+ - `scripting-patterns/state-machine-patterns/SKILL.md` — state machine patterns
@@ -0,0 +1,51 @@
1
+ # Summer Engine Compatibility Reference
2
+
3
+ > Read this before writing version-sensitive code, especially shaders,
4
+ > rendering code, extensions, or export configuration.
5
+
6
+ ## Current technical base
7
+
8
+ Summer Engine is the product. Its current upstream technical base is **4.6.1**
9
+ (stable, Mono), the approved next target is **4.7.1**, and Summer follows
10
+ upstream continuously. These are compatibility and lineage facts. They are not
11
+ the Summer product name or a permanent SDK version.
12
+
13
+ The repository source of truth is
14
+ `compatibility/summer-engine.json`. The running engine also reports its measured
15
+ technical version:
16
+
17
+ ```bash
18
+ <engineBinaryPath> --version # measured: 4.6.1.stable.mono.custom_build.b708b1182
19
+ ```
20
+
21
+ Get `engineBinaryPath` from `summer_get_project_context`. Prefer live
22
+ inspection to a copied version string. Creator onboarding and headlines should
23
+ say Summer Engine, Summer game, Summer SDK, and GDScript. Use the upstream name
24
+ only where technical compatibility, migration, extension APIs, contribution
25
+ routing, attribution, or licensing requires it.
26
+
27
+ ## LLM training cutoff versus upstream releases
28
+
29
+ | Domain | Godot 4.x churn | Action |
30
+ |---|---|---|
31
+ | Scripting (GDScript core) | low | Trust your training. Spot-check signatures only when something feels off. |
32
+ | Scene / node tree APIs | low | Trust your training. |
33
+ | Renderer (`RenderingServer`, `Compositor`) | high in 4.4 / 4.5 | Verify before writing. Check `MovieMaker`, `Compositor` effects, `RenderSceneBuffers` — these renamed/added recently. |
34
+ | Shaders (`canvas_item`, `spatial`, `compute`) | medium | Built-in functions stable, but `hint_*` flags shifted. Verify `hint_screen_texture` / `hint_depth_texture` syntax. |
35
+ | Animation (`AnimationTree`, `AnimationMixer`) | high (4.0 → 4.3 rename) | Use `AnimationMixer` for new code, not deprecated `AnimationPlayer`-as-mixer. |
36
+ | Multiplayer (`MultiplayerAPI`) | medium | High-level API stable. `SceneReplicationConfig` settled in 4.0+. |
37
+ | Editor / EditorPlugin | medium | Spot-check. |
38
+ | `Tween` / `SceneTreeTween` | low | Stable since 4.0. |
39
+
40
+ ## Summer-specific deltas from the upstream engine
41
+
42
+ - Local API server runs on `localhost:6550` (set per machine via `~/.summer/api-token`).
43
+ - Engine ships a webview module in `modules/1summer_engine/`. Do not assume the
44
+ unmodified upstream editor for UI patches.
45
+ - Project root contains `.summer/` for project memory; do not delete it.
46
+
47
+ ## When in doubt
48
+
49
+ 1. Use Summer MCP tools (`summer_inspect_node`, `summer_inspect_resource`) to read live editor state instead of guessing.
50
+ 2. Use `summer_get_diagnostics` after every scene change.
51
+ 3. Check `references/mcp-tools-reference.md` for the canonical tool list.