summer-engine 2.2.0 → 2.5.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 (176) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +20 -5
  3. package/.codex-plugin/plugin.json +15 -5
  4. package/.cursor-plugin/plugin.json +15 -5
  5. package/.factory-plugin/plugin.json +2 -2
  6. package/.opencode/INSTALL.md +1 -1
  7. package/CHANGELOG.md +27 -6
  8. package/CLAUDE.md +2 -2
  9. package/GEMINI.md +2 -3
  10. package/README.md +82 -58
  11. package/_persona/summer/SKILL.md +5 -6
  12. package/commands/gameskill.md +103 -0
  13. package/dist/bin/summer.js +12 -4
  14. package/dist/commands/create.js +2 -0
  15. package/dist/commands/doctor.js +71 -1
  16. package/dist/commands/install.d.ts +17 -0
  17. package/dist/commands/install.js +63 -4
  18. package/dist/commands/memory.d.ts +14 -0
  19. package/dist/commands/memory.js +196 -0
  20. package/dist/commands/setup.js +12 -2
  21. package/dist/commands/skills.js +205 -8
  22. package/dist/commands/status.js +3 -0
  23. package/dist/lib/agent-config.d.ts +2 -2
  24. package/dist/lib/agent-config.js +298 -5
  25. package/dist/lib/api-client.d.ts +13 -0
  26. package/dist/lib/api-client.js +63 -26
  27. package/dist/lib/async-op-lifecycle.d.ts +60 -0
  28. package/dist/lib/async-op-lifecycle.js +83 -0
  29. package/dist/lib/project-memory.d.ts +33 -0
  30. package/dist/lib/project-memory.js +220 -0
  31. package/dist/lib/skills-registry.d.ts +134 -61
  32. package/dist/lib/skills-registry.js +106 -4
  33. package/dist/lib/version-check.d.ts +106 -0
  34. package/dist/lib/version-check.js +338 -0
  35. package/dist/mcp/server.d.ts +2 -0
  36. package/dist/mcp/server.js +81 -0
  37. package/dist/mcp/tools/debug-tools.js +32 -2
  38. package/dist/mcp/tools/project-tools.js +18 -6
  39. package/dist/mcp/tools/with-engine.d.ts +14 -0
  40. package/dist/mcp/tools/with-engine.js +52 -7
  41. package/gemini-extension.json +2 -2
  42. package/hooks/pre-commit-doctor.sh +0 -0
  43. package/hooks/run-hook.cmd +0 -0
  44. package/hooks/session-start.sh +0 -0
  45. package/package.json +6 -4
  46. package/skills/2d-assets/character-portrait/SKILL.md +1 -1
  47. package/skills/2d-assets/concept-art/SKILL.md +1 -1
  48. package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
  49. package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
  50. package/skills/2d-assets/pixel-art/SKILL.md +1 -1
  51. package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
  52. package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
  53. package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
  54. package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
  55. package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
  56. package/skills/3d-assets/character-model/SKILL.md +1 -1
  57. package/skills/3d-assets/organic-model/SKILL.md +1 -1
  58. package/skills/3d-assets/prop-model/SKILL.md +1 -1
  59. package/skills/3d-assets/vehicle-model/SKILL.md +1 -1
  60. package/skills/README.md +37 -30
  61. package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
  62. package/skills/animation/animation-tree/SKILL.md +2 -2
  63. package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
  64. package/skills/animation/generate-motion/SKILL.md +1 -1
  65. package/skills/animation/procedural-animation/SKILL.md +2 -2
  66. package/skills/audio/adaptive-music/SKILL.md +1 -1
  67. package/skills/audio/audio-direction/SKILL.md +5 -5
  68. package/skills/audio/sound-effect/SKILL.md +2 -2
  69. package/skills/audio/voice-line/SKILL.md +25 -3
  70. package/skills/character-controllers/fps-controller/SKILL.md +5 -5
  71. package/skills/debugging/debug/SKILL.md +39 -1
  72. package/skills/deployment/export-and-ship/SKILL.md +4 -4
  73. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
  74. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
  75. package/skills/level-design/design-level/SKILL.md +5 -5
  76. package/skills/level-design/scene-to-level/SKILL.md +125 -0
  77. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
  78. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
  79. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
  80. package/skills/performance/tune-performance/SKILL.md +5 -5
  81. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
  82. package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
  83. package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
  84. package/skills/scene-and-project/play/SKILL.md +1 -1
  85. package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
  86. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
  87. package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
  88. package/skills/video/animated-loop/SKILL.md +1 -1
  89. package/skills/video/cinematic-cutscene/SKILL.md +1 -1
  90. package/skills/video/trailer-shot/SKILL.md +1 -1
  91. package/skills/visual-effects/game-feel/SKILL.md +6 -6
  92. package/skills/workflow/brainstorming/SKILL.md +164 -0
  93. package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
  94. package/skills/workflow/brainstorming/scripts/helper.js +88 -0
  95. package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
  96. package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
  97. package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
  98. package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
  99. package/skills/workflow/brainstorming/visual-companion.md +287 -0
  100. package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
  101. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
  102. package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
  103. package/skills/workflow/gameskill/SKILL.md +111 -0
  104. package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
  105. package/skills/workflow/investigating-bugs/SKILL.md +296 -0
  106. package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
  107. package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
  108. package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
  109. package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
  110. package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
  111. package/skills/workflow/investigating-bugs/test-academic.md +14 -0
  112. package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
  113. package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
  114. package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
  115. package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
  116. package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
  117. package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
  118. package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
  119. package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
  120. package/skills/workflow/verification-before-completion/SKILL.md +139 -0
  121. package/skills/workflow/writing-plans/SKILL.md +152 -0
  122. package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
  123. package/skills/workflow/writing-skills/SKILL.md +655 -0
  124. package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
  125. package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  126. package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
  127. package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
  128. package/skills/workflow/writing-skills/render-graphs.js +168 -0
  129. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
  130. package/dist/commands/assets.d.ts +0 -2
  131. package/dist/commands/assets.js +0 -189
  132. package/dist/commands/debug.d.ts +0 -2
  133. package/dist/commands/debug.js +0 -127
  134. package/dist/commands/doctor.test.d.ts +0 -1
  135. package/dist/commands/doctor.test.js +0 -29
  136. package/dist/commands/project.d.ts +0 -2
  137. package/dist/commands/project.js +0 -170
  138. package/dist/commands/scene.d.ts +0 -2
  139. package/dist/commands/scene.js +0 -290
  140. package/dist/lib/agent-config.test.d.ts +0 -1
  141. package/dist/lib/agent-config.test.js +0 -80
  142. package/dist/lib/format.test.d.ts +0 -1
  143. package/dist/lib/format.test.js +0 -36
  144. package/dist/lib/plugin-manifests.test.d.ts +0 -1
  145. package/dist/lib/plugin-manifests.test.js +0 -134
  146. package/dist/lib/with-engine.d.ts +0 -14
  147. package/dist/lib/with-engine.js +0 -109
  148. package/skills/_shared/collaborative-protocol.md +0 -50
  149. package/skills/_shared/gd-style.md +0 -121
  150. package/skills/_shared/godot-version.md +0 -32
  151. package/skills/_shared/mcp-tools-reference.md +0 -133
  152. package/skills/_shared/summer-folder.md +0 -44
  153. package/skills/_shared/template-registry.md +0 -40
  154. package/skills/_tests/runner.md +0 -90
  155. package/skills/_tests/specs/3d-lighting.md +0 -19
  156. package/skills/_tests/specs/art-direction.md +0 -115
  157. package/skills/_tests/specs/asset-strategy.md +0 -19
  158. package/skills/_tests/specs/audio-direction.md +0 -123
  159. package/skills/_tests/specs/brainstorm-game.md +0 -99
  160. package/skills/_tests/specs/debug.md +0 -94
  161. package/skills/_tests/specs/design-level.md +0 -113
  162. package/skills/_tests/specs/design-mechanic.md +0 -112
  163. package/skills/_tests/specs/design-npc.md +0 -143
  164. package/skills/_tests/specs/export-and-ship.md +0 -114
  165. package/skills/_tests/specs/fps-controller.md +0 -113
  166. package/skills/_tests/specs/gdscript-patterns.md +0 -19
  167. package/skills/_tests/specs/host-authoritative-state.md +0 -104
  168. package/skills/_tests/specs/make-game.md +0 -19
  169. package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
  170. package/skills/_tests/specs/play.md +0 -88
  171. package/skills/_tests/specs/scene-composition.md +0 -19
  172. package/skills/_tests/specs/setup-multiplayer.md +0 -106
  173. package/skills/_tests/specs/tune-performance.md +0 -104
  174. package/skills/_tests/specs/ui-basics.md +0 -19
  175. package/skills/_tests/specs/vfx.md +0 -116
  176. package/skills/catalog.yaml +0 -991
@@ -0,0 +1,103 @@
1
+ You are capturing what we just learned in this game-development session into Summer Engine's skill library, so the next agent (and every Summer user) gets to start where we ended.
2
+
3
+ This is the meta-skill: the loop that turns ad-hoc fixes into durable, reusable expertise.
4
+
5
+ ## What "Summer Engine" is, so you're grounded
6
+
7
+ Three repos, one ecosystem:
8
+
9
+ 1. **`~/development/SummerEngine`** — the desktop engine (Godot fork in C++) + the canonical CLI. The engine source lives at `editor/`, `core/`, and the Summer-specific module at `modules/1summer_engine/`. The Summer CLI lives at `tools/summer-cli/`. It ships as `npm i -g summer-engine`. It does install/login/create/run for the engine, plus `summer skills install --as-claude-skill` which writes a curated subset of skills into `~/.claude/skills/` for any agent to auto-discover.
10
+ 2. **`~/development/PublicSummerEngine`** — the web app at summerengine.com. Has its own AI skill stores at `src/lib/ai/skills/bundled/` and `public/knowledge/summer/skills/` for the in-browser chat agent. ALSO has its own MCP-style consumers of the engine API (`src/lib/bridge/direct-executor.ts`, `src/lib/ai/tools/run-and-verify.ts`) which call the same engine endpoints the CLI does.
11
+ 3. **The active game project** — usually `~/development/<game-name>` (Godot 4.6) or whatever game we were just working on. Real working code in this repo is the gold standard for examples.
12
+
13
+ The skill system is the value flywheel: every game we ship teaches lessons → the lessons become skills → the next user (or AI) starts smarter. Your job here is to close that loop for what we just did.
14
+
15
+ ## Cross-repo change awareness — read this before any non-trivial fix
16
+
17
+ When the lesson is "the engine should expose more / behave differently," the change usually crosses repo boundaries. Map the layers before you edit:
18
+
19
+ ```
20
+ [active game] → calls MCP / web app
21
+ ↓
22
+ [summer-cli MCP server (TypeScript)] → calls /api/ops + /api/state/* on the engine
23
+ ↓
24
+ [Summer Engine binary (C++ in SummerEngine repo)] — source of truth for what the API can return
25
+
26
+ [PublicSummerEngine web app (TypeScript)] → also calls the same engine endpoints via direct-executor or Redis bridge
27
+ ```
28
+
29
+ Implications:
30
+
31
+ - **A C++ change in `SummerEngine/modules/1summer_engine/` or `SummerEngine/editor/` reaches everyone** (CLI MCP, web app, future tools). Most powerful, also highest blast radius.
32
+ - **A TypeScript change in `tools/summer-cli/src/mcp/`** only changes what the CLI exposes. The web app has its own TypeScript layer in `PublicSummerEngine/src/lib/bridge/` — same engine endpoints, different glue. Don't assume your CLI tool change is web-app-aware.
33
+ - **A skill update in `tools/summer-cli/skills/`** affects every agent that runs `summer skills install`. The web app's skill store is separate (`PublicSummerEngine/src/lib/ai/skills/bundled/`) and currently uses a different format (template wizards, not Markdown discipline guides). Don't try to mirror skills there blindly.
34
+
35
+ When you change an engine endpoint's response shape (add fields, rename fields, change semantics):
36
+
37
+ 1. Grep both `tools/summer-cli/src/` and `PublicSummerEngine/src/lib/` for consumers of that endpoint.
38
+ 2. Confirm the change is **additive** (new fields, no removed/renamed) so existing consumers still work without modification.
39
+ 3. If the change is breaking, update both consumers in the same logical commit set.
40
+ 4. Note in TOOLING-TODO.md what's now possible that wasn't before.
41
+
42
+ Engine binary changes ship via a rebuild + new release. The user has to pull the new engine for the change to take effect. CLI/web TypeScript changes ship via npm publish or web deploy. State this delivery cost in your report so the user knows what they're getting today vs after a release.
43
+
44
+ ## The four skill stores (probe each before doing anything)
45
+
46
+ 1. **Canonical source** — `~/development/SummerEngine/tools/summer-cli/skills/`
47
+ - Single source of truth. Domains: `ui-and-ux`, `gameplay-mechanics`, `character-controllers`, `scripting-patterns`, `visual-effects`, `debugging`, `rendering-and-lighting`, `scene-and-project`, `level-design`, `audio`, `ai-and-npcs`, `asset-pipeline`, `performance`, `deployment`, `multiplayer-and-networking`, plus `workflow` for process skills.
48
+ - The `skills:` array in `.claude-plugin/plugin.json` is the registry. Sibling shared docs live at top-level `references/`; test specs at top-level `tests/`.
49
+ 2. **Claude auto-discovery** — `~/.claude/skills/` (currently 7 skills, written here by `summer skills install --as-claude-skill`).
50
+ 3. **Web-app stores** — `~/development/PublicSummerEngine/src/lib/ai/skills/` and `public/knowledge/summer/skills/`. Mirror only if the learning applies to the web chat agent too — check what's there before assuming.
51
+ 4. **The active game** — read it as ground truth. Working code from the real project beats invented examples.
52
+
53
+ ## What to do, in order
54
+
55
+ 1. **One short sentence to the user.** "Capturing learnings from this session into the skill library." That's it. No plan dump.
56
+
57
+ 2. **Probe the stores.** Glob each path above. Note what exists and what's empty. Print a tight summary (one line per domain with skill count).
58
+
59
+ 3. **Recap the session.** Look back over the conversation. Pull out the learnings that are non-obvious AND general — things a future agent or user would benefit from but couldn't derive from reading the codebase. Examples that count:
60
+ - A working shader (the actual code) → `visual-effects`
61
+ - A GDScript idiom that beat the obvious approach → `scripting-patterns`
62
+ - A UI layout that survived the design pass → `ui-and-ux`
63
+ - A bug + root cause + fix that wasn't in any docs → `debugging`
64
+ - A Godot 4.6 quirk (type inference, `.keys()` weirdness, signal gotchas) → `scripting-patterns`
65
+ - A spawning/AI/enemy pattern that worked → `ai-and-npcs` or `gameplay-mechanics`
66
+ - A perf fix with measured before/after → `performance`
67
+ - An asset-creation workflow that beat alternatives → `asset-pipeline`
68
+
69
+ Examples that DON'T count:
70
+ - One-off code with no general lesson
71
+ - Stuff already documented in `CLAUDE.md` or an existing skill
72
+ - Personal preference with no reasoning behind it
73
+ - Anything you have to manufacture to fill space
74
+
75
+ 4. **Decide placement.** For each learning:
76
+ - **Update existing skill** — name the file, quote the section, draft the addition.
77
+ - **Create new skill** — pick the domain, name the file, draft the full skill markdown.
78
+ Match the format of existing skills in the same domain exactly. Read one if you're unsure.
79
+
80
+ 5. **One tight pause for the user.** Five-to-ten lines max: what files you'll touch, what each one captures, what gets left out. Wait for OK or redirect. Don't ask multiple questions.
81
+
82
+ 6. **Apply.**
83
+ - Write/edit `.md` files under `tools/summer-cli/skills/<domain>/`.
84
+ - Register a new skill in `.claude-plugin/plugin.json` (plus `.codex-plugin/`, `.cursor-plugin/`) AND in `src/lib/skills-registry.ts` `SKILL_REGISTRY`. Both are required.
85
+ - If the skill belongs in the curated Claude set, also write it to `~/.claude/skills/<name>/` so it's live in the current session — and tell the user to consider adding it to the CLI's default install set.
86
+ - If the learning applies to the web chat agent, mirror to `PublicSummerEngine` skill stores too.
87
+ - Run `git status` in `~/development/SummerEngine` (and `PublicSummerEngine` if touched) so the diff is visible.
88
+
89
+ 7. **Report.** One short message. Files changed, the future-agent payoff in one sentence per skill, and what (if anything) is still uncaptured.
90
+
91
+ ## Style rules — non-negotiable
92
+
93
+ - **Tight. No slop. No glazing.** Mathias hates filler.
94
+ - **Capture the why, not just the what.** "Use X" rots. "Use X because Y breaks under Z (we hit this in PRUT chest reveal, 2026-05)" survives. Always lead skill updates with the reason.
95
+ - **Real working code from the active project beats invented examples.** Lift from PRUT or whatever we were in.
96
+ - **VFX is code** — shaders + GDScript + node setup. Never image-generation pipelines or Meshy prompts.
97
+ - **No em dashes** (—) in any user-facing copy you write into skills. Use periods or restructure the sentence.
98
+ - **`[SUMMER]` markers on core Godot engine file edits** — but skill `.md` files don't need them.
99
+ - **Verify before claiming.** If a skill says "Summer Engine does X" or "Stripe is configured Y way", verify in the actual code/DB before writing it down.
100
+
101
+ ## When there's nothing worth capturing
102
+
103
+ It's totally fine to come back with "session was tactical, no durable learnings worth a skill update." Don't manufacture skills from thin air. Better to ship nothing into the skill library than ship noise.
@@ -10,6 +10,7 @@ import { openCommand } from "../commands/open.js";
10
10
  import { installCommand } from "../commands/install.js";
11
11
  import { createCommand } from "../commands/create.js";
12
12
  import { listCommand } from "../commands/list.js";
13
+ import { memoryCommand } from "../commands/memory.js";
13
14
  import { skillsCommand } from "../commands/skills.js";
14
15
  import { orchestratorCommand } from "../commands/orchestrator.js";
15
16
  import { setupCommand } from "../commands/setup.js";
@@ -34,6 +35,7 @@ program.addCommand(runCommand);
34
35
  program.addCommand(openCommand);
35
36
  program.addCommand(createCommand);
36
37
  program.addCommand(listCommand);
38
+ program.addCommand(memoryCommand);
37
39
  program.addCommand(skillsCommand);
38
40
  program.addCommand(mcpCommand);
39
41
  program.addCommand(setupCommand);
@@ -49,15 +51,21 @@ function printIntro(version) {
49
51
  console.log("");
50
52
  console.log(` ${sym.arrow()} ${c.bold("Summer Engine")} ${c.dim("v" + version)} ${c.dim("·")} AI-native game engine`);
51
53
  console.log("");
52
- console.log(` ${c.bold("Get started")}`);
53
- console.log(` ${c.dim("$")} summer setup claude-code ${c.dim("Configure Claude Code (or codex/cursor/windsurf)")}`);
54
- console.log(` ${c.dim("$")} summer create 3d-basic my-game ${c.dim("Start a project")}`);
55
- console.log(` ${c.dim("$")} summer doctor ${c.dim("Check setup")}`);
54
+ console.log(` ${c.bold("Setup wizard")}`);
55
+ console.log(` Open Claude Code, Cursor, Codex, Copilot, Windsurf, or another supported agent and paste:`);
56
+ console.log(` ${c.brand("\"Install Summer Engine and let's make a game.\"")}`);
57
+ console.log("");
58
+ console.log(` ${c.bold("Manual commands")}`);
59
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest setup claude-code --yes`);
60
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest create 3d-basic my-game`);
61
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest memory`);
62
+ console.log(` ${c.dim("$")} npx -y summer-engine@latest doctor`);
56
63
  console.log("");
57
64
  console.log(` ${c.bold("Slash commands")} ${c.dim("(in your AI agent)")}`);
58
65
  console.log(` ${c.brand("/debug")} ${c.dim("Triage and fix a bug end-to-end")}`);
59
66
  console.log(` ${c.brand("/play")} ${c.dim("Run the game and report state")}`);
60
67
  console.log("");
61
68
  console.log(` ${c.dim("Docs:")} https://summerengine.com/docs`);
69
+ console.log(` ${c.dim("Source:")} https://github.com/SummerEngine/summer`);
62
70
  console.log("");
63
71
  }
@@ -42,6 +42,7 @@ export const createCommand = new Command("create")
42
42
  console.log(`\nProject created at ${fullPath}`);
43
43
  console.log("\nNext steps:");
44
44
  console.log(` summer run ${dirName}`);
45
+ console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
45
46
  return;
46
47
  }
47
48
  // Fall back to remote template lookup
@@ -86,6 +87,7 @@ export const createCommand = new Command("create")
86
87
  console.log("Source: " + remote.url);
87
88
  console.log("\nNext steps:");
88
89
  console.log(` summer run ${dirName}`);
90
+ console.log(" Ask your agent to run summer:brainstorm-game to create .summer/GameSoul.md");
89
91
  });
90
92
  function projectGodot(name, mainScene) {
91
93
  return `; Summer Engine Project
@@ -6,6 +6,8 @@ import { Command } from "commander";
6
6
  import { getAuthToken, getUserInfo } from "../lib/auth.js";
7
7
  import { checkEngineHealth, getApiPort, getApiToken } from "../lib/engine.js";
8
8
  import { brandLine, c, pad, sym, tildeify } from "../lib/format.js";
9
+ import { getProjectMemorySummary } from "../lib/project-memory.js";
10
+ import { buildCliVersionCheck, buildSkillsVersionCheck, defaultSkillMarkerCandidates, fetchLatestRegistryVersion, } from "../lib/version-check.js";
9
11
  const require = createRequire(import.meta.url);
10
12
  const { version } = require("../../package.json");
11
13
  const MAC_ENGINE_PATHS = [
@@ -18,7 +20,7 @@ const WIN_ENGINE_PATHS = [
18
20
  `${process.env.PROGRAMFILES}\\Summer Engine\\Summer.exe`,
19
21
  ];
20
22
  export const doctorCommand = new Command("doctor")
21
- .description("Diagnose Node, login, engine, local API, and MCP boot")
23
+ .description("Diagnose Node, login, engine, project memory, local API, and MCP boot")
22
24
  .option("--json", "Print diagnostics as JSON")
23
25
  .action(async (opts) => {
24
26
  if (!opts.json) {
@@ -42,9 +44,12 @@ export async function runDoctor(options = {}) {
42
44
  status: "ok",
43
45
  message: `v${version}`,
44
46
  });
47
+ checks.push(await checkCliVersionCurrent());
48
+ checks.push(checkSkillsVersion());
45
49
  checks.push(await checkLogin());
46
50
  checks.push(checkEngineInstall());
47
51
  checks.push(await checkLocalApi());
52
+ checks.push(await checkProjectMemory());
48
53
  checks.push(await checkMcpBoot());
49
54
  const result = summarizeChecks(checks);
50
55
  if (!options.quiet) {
@@ -74,6 +79,33 @@ function checkNodeVersion() {
74
79
  message: `${process.version} (need 18+)`,
75
80
  };
76
81
  }
82
+ async function checkCliVersionCurrent() {
83
+ const registry = await fetchLatestRegistryVersion();
84
+ const result = buildCliVersionCheck({
85
+ installedVersion: version,
86
+ registry,
87
+ });
88
+ return {
89
+ id: "cli-version-current",
90
+ label: "CLI up to date",
91
+ status: result.status,
92
+ message: result.message,
93
+ details: result.details,
94
+ };
95
+ }
96
+ function checkSkillsVersion() {
97
+ const result = buildSkillsVersionCheck({
98
+ installedCliVersion: version,
99
+ candidates: defaultSkillMarkerCandidates(),
100
+ });
101
+ return {
102
+ id: "skills-version-stale",
103
+ label: "Skills up to date",
104
+ status: result.status,
105
+ message: result.message,
106
+ details: result.details,
107
+ };
108
+ }
77
109
  async function checkLogin() {
78
110
  const token = await getAuthToken();
79
111
  const user = await getUserInfo();
@@ -147,6 +179,44 @@ async function checkLocalApi() {
147
179
  },
148
180
  };
149
181
  }
182
+ async function checkProjectMemory() {
183
+ const port = await getApiPort();
184
+ const health = await checkEngineHealth(port);
185
+ if (!health?.project_path) {
186
+ return {
187
+ id: "project-memory",
188
+ label: "Project Memory",
189
+ status: "ok",
190
+ message: "no project open",
191
+ };
192
+ }
193
+ const summary = getProjectMemorySummary(health.project_path);
194
+ if (!summary.present) {
195
+ return {
196
+ id: "project-memory",
197
+ label: "Project Memory",
198
+ status: "warning",
199
+ message: ".summer not created yet",
200
+ details: {
201
+ projectPath: health.project_path,
202
+ reason: summary.reason,
203
+ recommendedAction: "Ask your agent to run summer:brainstorm-game.",
204
+ },
205
+ };
206
+ }
207
+ return {
208
+ id: "project-memory",
209
+ label: "Project Memory",
210
+ status: "ok",
211
+ message: `${summary.files.length} files, ${summary.structured.fileCount} memory, ${summary.structured.lockedCount} locked`,
212
+ details: {
213
+ projectPath: health.project_path,
214
+ files: summary.files.length,
215
+ memoryFiles: summary.structured.fileCount,
216
+ locked: summary.structured.lockedCount,
217
+ },
218
+ };
219
+ }
150
220
  async function checkMcpBoot() {
151
221
  const cliPath = process.argv[1];
152
222
  if (!cliPath) {
@@ -1,2 +1,19 @@
1
1
  import { Command } from "commander";
2
+ /** Guard a download URL from the releases endpoint. A missing/empty URL (an
3
+ * outdated or misconfigured endpoint) otherwise becomes a cryptic
4
+ * fetch(undefined) failure — surface it clearly with a manual fallback. */
5
+ export declare function requireDownloadUrl(value: string | undefined, label: string): string;
6
+ /** Verify a downloaded file's sha256 against the expected hash from the releases
7
+ * endpoint. Skipped when no expected hash is published. Catches a truncated or
8
+ * corrupted download (a ~1GB install over a flaky network) before it becomes a
9
+ * confusing DMG-mount / installer failure downstream. */
10
+ export declare function verifyChecksum(actual: string, expected: string | undefined, label: string): void;
11
+ /** Velopack Setup.exe install args. Velopack is NOT NSIS: silent install is
12
+ * `--silent` and a custom directory is `--installto <dir>` (per Velopack's
13
+ * setup.exe CLI). The old NSIS flags (/S, /D=) are rejected by the Velopack
14
+ * installer with "unexpected argument '/S' found" — this is the wizard bug. */
15
+ export declare function windowsInstallerArgs(customPath?: string): string;
2
16
  export declare const installCommand: Command;
17
+ export declare function downloadFile(url: string, dest: string): Promise<{
18
+ sha256: string;
19
+ }>;
@@ -1,11 +1,41 @@
1
1
  import { Command } from "commander";
2
2
  import { execSync } from "child_process";
3
+ import { createHash } from "crypto";
3
4
  import { createWriteStream, existsSync, mkdirSync } from "fs";
4
5
  import { pipeline } from "stream/promises";
5
6
  import { Readable } from "stream";
6
7
  import { tmpdir, platform } from "os";
7
8
  import { join } from "path";
8
9
  const RELEASES_URL = "https://www.summerengine.com/api/desktop/releases";
10
+ /** Guard a download URL from the releases endpoint. A missing/empty URL (an
11
+ * outdated or misconfigured endpoint) otherwise becomes a cryptic
12
+ * fetch(undefined) failure — surface it clearly with a manual fallback. */
13
+ export function requireDownloadUrl(value, label) {
14
+ if (!value || value.trim().length === 0) {
15
+ throw new Error(`Release info is missing the ${label} download URL — the releases endpoint may be ` +
16
+ `outdated. Download manually from https://summerengine.com/download`);
17
+ }
18
+ return value;
19
+ }
20
+ /** Verify a downloaded file's sha256 against the expected hash from the releases
21
+ * endpoint. Skipped when no expected hash is published. Catches a truncated or
22
+ * corrupted download (a ~1GB install over a flaky network) before it becomes a
23
+ * confusing DMG-mount / installer failure downstream. */
24
+ export function verifyChecksum(actual, expected, label) {
25
+ if (!expected || expected.trim().length === 0)
26
+ return; // nothing published to verify against
27
+ if (actual.toLowerCase() !== expected.toLowerCase()) {
28
+ throw new Error(`${label} checksum mismatch — the download was corrupted or incomplete ` +
29
+ `(expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…). Re-run \`summer install\`.`);
30
+ }
31
+ }
32
+ /** Velopack Setup.exe install args. Velopack is NOT NSIS: silent install is
33
+ * `--silent` and a custom directory is `--installto <dir>` (per Velopack's
34
+ * setup.exe CLI). The old NSIS flags (/S, /D=) are rejected by the Velopack
35
+ * installer with "unexpected argument '/S' found" — this is the wizard bug. */
36
+ export function windowsInstallerArgs(customPath) {
37
+ return customPath ? `--silent --installto "${customPath}"` : "--silent";
38
+ }
9
39
  export const installCommand = new Command("install")
10
40
  .description("Download and install Summer Engine")
11
41
  .option("--path <dir>", "Custom install directory")
@@ -42,6 +72,7 @@ async function installMac(releases, customPath) {
42
72
  console.error("No macOS release found.");
43
73
  process.exit(1);
44
74
  }
75
+ const dmgUrl = requireDownloadUrl(info.dmg_url, "macOS DMG");
45
76
  console.log(`Latest version: ${info.version}`);
46
77
  const destApp = customPath || "/Applications/Summer.app";
47
78
  if (existsSync(destApp)) {
@@ -50,7 +81,18 @@ async function installMac(releases, customPath) {
50
81
  }
51
82
  const dmgPath = join(tmpdir(), `Summer-v${info.version}.dmg`);
52
83
  console.log(`Downloading Summer Engine v${info.version} (~1 GB, includes bundled Git + runtime)...`);
53
- await downloadFile(info.dmg_url, dmgPath);
84
+ try {
85
+ const { sha256 } = await downloadFile(dmgUrl, dmgPath);
86
+ verifyChecksum(sha256, info.dmg_sha256, "DMG");
87
+ }
88
+ catch (err) {
89
+ try {
90
+ execSync(`rm -f "${dmgPath}"`, { stdio: "ignore" });
91
+ }
92
+ catch { /* best effort */ }
93
+ console.error(err instanceof Error ? err.message : String(err));
94
+ process.exit(1);
95
+ }
54
96
  console.log("Mounting DMG...");
55
97
  const mountOutput = execSync(`hdiutil attach "${dmgPath}" -nobrowse -noverify -noautoopen`, {
56
98
  encoding: "utf-8",
@@ -88,13 +130,25 @@ async function installWindows(releases, customPath) {
88
130
  console.error("No Windows release found.");
89
131
  process.exit(1);
90
132
  }
133
+ const exeUrl = requireDownloadUrl(info.url, "Windows installer");
91
134
  console.log(`Latest version: ${info.version}`);
92
135
  const exePath = join(tmpdir(), `Summer-v${info.version}.exe`);
93
136
  console.log(`Downloading Summer Engine v${info.version} (~1 GB, includes bundled Git + runtime)...`);
94
- await downloadFile(info.url, exePath);
137
+ try {
138
+ const { sha256 } = await downloadFile(exeUrl, exePath);
139
+ verifyChecksum(sha256, info.sha256, "installer");
140
+ }
141
+ catch (err) {
142
+ try {
143
+ execSync(`del "${exePath}"`, { stdio: "ignore" });
144
+ }
145
+ catch { /* best effort */ }
146
+ console.error(err instanceof Error ? err.message : String(err));
147
+ process.exit(1);
148
+ }
95
149
  console.log("Running installer...");
96
150
  try {
97
- execSync(`"${exePath}" /S${customPath ? ` /D=${customPath}` : ""}`, {
151
+ execSync(`"${exePath}" ${windowsInstallerArgs(customPath)}`, {
98
152
  stdio: "inherit",
99
153
  });
100
154
  }
@@ -111,7 +165,7 @@ async function installWindows(releases, customPath) {
111
165
  console.log(" summer login # Sign in to your account");
112
166
  console.log(" summer run # Launch the engine");
113
167
  }
114
- async function downloadFile(url, dest) {
168
+ export async function downloadFile(url, dest) {
115
169
  const res = await fetch(url);
116
170
  if (!res.ok || !res.body) {
117
171
  throw new Error(`Download failed: HTTP ${res.status}`);
@@ -121,9 +175,13 @@ async function downloadFile(url, dest) {
121
175
  mkdirSync(dir, { recursive: true });
122
176
  const fileStream = createWriteStream(dest);
123
177
  const readable = Readable.fromWeb(res.body);
178
+ // Hash while streaming (the 'data' listener and the pipe both see every chunk
179
+ // in flowing mode), so we get integrity for free without a second read pass.
180
+ const hash = createHash("sha256");
124
181
  let downloaded = 0;
125
182
  const total = parseInt(res.headers.get("content-length") || "0", 10);
126
183
  readable.on("data", (chunk) => {
184
+ hash.update(chunk);
127
185
  downloaded += chunk.length;
128
186
  if (total > 0) {
129
187
  const pct = Math.round((downloaded / total) * 100);
@@ -132,4 +190,5 @@ async function downloadFile(url, dest) {
132
190
  });
133
191
  await pipeline(readable, fileStream);
134
192
  process.stdout.write("\n");
193
+ return { sha256: hash.digest("hex") };
135
194
  }
@@ -0,0 +1,14 @@
1
+ import { Command } from "commander";
2
+ import { type ProjectMemorySummary } from "../lib/project-memory.js";
3
+ interface ResolvedMemoryProject {
4
+ projectPath: string | null;
5
+ source: "option" | "engine" | "cwd" | "none";
6
+ reason?: string;
7
+ }
8
+ export declare const memoryCommand: Command;
9
+ export declare function resolveMemoryProject(projectOption?: string): Promise<ResolvedMemoryProject>;
10
+ export declare function findProjectRoot(startDir: string): string | null;
11
+ export declare function resolveMemoryFilePath(projectPath: string, requested: string): string;
12
+ export declare function formatMemorySummary(projectPath: string, summary: ProjectMemorySummary): string;
13
+ export declare function formatStatusMemoryLine(summary: ProjectMemorySummary): string;
14
+ export {};
@@ -0,0 +1,196 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { Command } from "commander";
4
+ import { checkEngineHealth, getApiPort } from "../lib/engine.js";
5
+ import { getProjectMemorySummary, } from "../lib/project-memory.js";
6
+ import { c, pad, tildeify } from "../lib/format.js";
7
+ export const memoryCommand = new Command("memory")
8
+ .description("Inspect project memory in .summer")
9
+ .option("--project <path>", "Project directory to inspect")
10
+ .option("--json", "Print memory summary as JSON")
11
+ .action(async (opts) => {
12
+ const resolvedProject = await resolveMemoryProject(opts.project);
13
+ if (!resolvedProject.projectPath) {
14
+ if (opts.json) {
15
+ console.log(JSON.stringify({ ok: false, ...resolvedProject }, null, 2));
16
+ }
17
+ else {
18
+ console.error(resolvedProject.reason ?? "Could not find a Summer project.");
19
+ console.error("Run from a project directory, pass --project <path>, or start Summer Engine.");
20
+ }
21
+ process.exit(1);
22
+ }
23
+ const summary = getProjectMemorySummary(resolvedProject.projectPath);
24
+ if (opts.json) {
25
+ console.log(JSON.stringify({
26
+ ok: true,
27
+ projectPath: resolvedProject.projectPath,
28
+ source: resolvedProject.source,
29
+ memory: summary,
30
+ }, null, 2));
31
+ return;
32
+ }
33
+ console.log(formatMemorySummary(resolvedProject.projectPath, summary));
34
+ });
35
+ memoryCommand
36
+ .command("show <file>")
37
+ .description("Print a .summer memory Markdown file")
38
+ .option("--project <path>", "Project directory to inspect")
39
+ .action(async (file, opts) => {
40
+ const resolvedProject = await resolveMemoryProject(opts.project);
41
+ if (!resolvedProject.projectPath) {
42
+ console.error(resolvedProject.reason ?? "Could not find a Summer project.");
43
+ console.error("Run from a project directory, pass --project <path>, or start Summer Engine.");
44
+ process.exit(1);
45
+ }
46
+ const filePath = resolveMemoryFilePath(resolvedProject.projectPath, file);
47
+ console.log(readFileSync(filePath, "utf-8"));
48
+ });
49
+ memoryCommand
50
+ .command("path")
51
+ .description("Print the .summer directory path for the current project")
52
+ .option("--project <path>", "Project directory to inspect")
53
+ .action(async (opts) => {
54
+ const resolvedProject = await resolveMemoryProject(opts.project);
55
+ if (!resolvedProject.projectPath) {
56
+ console.error(resolvedProject.reason ?? "Could not find a Summer project.");
57
+ console.error("Run from a project directory, pass --project <path>, or start Summer Engine.");
58
+ process.exit(1);
59
+ }
60
+ console.log(join(resolvedProject.projectPath, ".summer"));
61
+ });
62
+ export async function resolveMemoryProject(projectOption) {
63
+ if (projectOption) {
64
+ const projectPath = resolve(projectOption);
65
+ if (!existsSync(projectPath)) {
66
+ return {
67
+ projectPath: null,
68
+ source: "option",
69
+ reason: `Project directory not found: ${projectPath}`,
70
+ };
71
+ }
72
+ if (!existsSync(join(projectPath, "project.godot"))) {
73
+ return {
74
+ projectPath: null,
75
+ source: "option",
76
+ reason: `No project.godot found in ${projectPath}.`,
77
+ };
78
+ }
79
+ return { projectPath, source: "option" };
80
+ }
81
+ const cwdProject = findProjectRoot(process.cwd());
82
+ if (cwdProject)
83
+ return { projectPath: cwdProject, source: "cwd" };
84
+ const port = await getApiPort();
85
+ const health = await checkEngineHealth(port);
86
+ if (health?.project_path && existsSync(join(health.project_path, "project.godot"))) {
87
+ return { projectPath: health.project_path, source: "engine" };
88
+ }
89
+ return {
90
+ projectPath: null,
91
+ source: "none",
92
+ reason: "No Summer project found from engine state or current directory.",
93
+ };
94
+ }
95
+ export function findProjectRoot(startDir) {
96
+ let current = resolve(startDir);
97
+ while (true) {
98
+ if (existsSync(join(current, "project.godot")))
99
+ return current;
100
+ const parent = dirname(current);
101
+ if (parent === current)
102
+ return null;
103
+ current = parent;
104
+ }
105
+ }
106
+ export function resolveMemoryFilePath(projectPath, requested) {
107
+ const summerDir = resolve(projectPath, ".summer");
108
+ const rawPath = requested.startsWith(".summer/")
109
+ ? resolve(projectPath, requested)
110
+ : resolve(summerDir, requested);
111
+ const withinSummer = isPathInside(summerDir, rawPath);
112
+ if (!withinSummer) {
113
+ throw new Error("Memory files must be inside the project's .summer directory.");
114
+ }
115
+ if (!existsSync(rawPath)) {
116
+ throw new Error(`Memory file not found: ${requested}`);
117
+ }
118
+ if (!statSync(rawPath).isFile()) {
119
+ throw new Error(`Memory path is not a file: ${requested}`);
120
+ }
121
+ return rawPath;
122
+ }
123
+ export function formatMemorySummary(projectPath, summary) {
124
+ const lines = [];
125
+ lines.push(c.bold("Project Memory"));
126
+ lines.push("");
127
+ lines.push(` Project: ${tildeify(projectPath)}`);
128
+ lines.push(` Folder: ${tildeify(join(projectPath, ".summer"))}`);
129
+ lines.push("");
130
+ if (!summary.present) {
131
+ lines.push(` ${summary.reason ?? "No .summer memory found yet."}`);
132
+ lines.push("");
133
+ lines.push(" Agents create this through Summer skills.");
134
+ lines.push(" Start with .summer/GameSoul.md for the project brief.");
135
+ lines.push(" Use .summer/memory/ for locked facts like voice IDs and world canon.");
136
+ return lines.join("\n");
137
+ }
138
+ const totalFiles = summary.files.length;
139
+ const structured = summary.structured;
140
+ lines.push(` Status: ${totalFiles} .summer Markdown file${totalFiles === 1 ? "" : "s"}, ` +
141
+ `${structured.fileCount} structured memory file${structured.fileCount === 1 ? "" : "s"}, ` +
142
+ `${structured.lockedCount} locked`);
143
+ lines.push("");
144
+ const canonicalRows = canonicalRowsFromSummary(summary);
145
+ if (canonicalRows.length > 0) {
146
+ lines.push(c.bold("Canonical"));
147
+ for (const [label, file] of canonicalRows) {
148
+ lines.push(formatMemoryFileLine(label, file));
149
+ }
150
+ lines.push("");
151
+ }
152
+ if (structured.files.length > 0) {
153
+ lines.push(c.bold("Structured"));
154
+ for (const file of structured.files) {
155
+ lines.push(formatMemoryFileLine(file.kind, file));
156
+ }
157
+ if (structured.truncated) {
158
+ lines.push(" ...more files omitted from summary");
159
+ }
160
+ lines.push("");
161
+ }
162
+ lines.push("Read a file:");
163
+ lines.push(" summer memory show .summer/memory/casting/voices.md");
164
+ lines.push("");
165
+ lines.push("Rule:");
166
+ lines.push(" Markdown is canonical. Changing priority: locked memory requires explicit user confirmation.");
167
+ return lines.join("\n");
168
+ }
169
+ export function formatStatusMemoryLine(summary) {
170
+ if (!summary.present)
171
+ return "Memory: No .summer folder yet";
172
+ const fileCount = summary.files.length;
173
+ const structuredCount = summary.structured.fileCount;
174
+ const lockedCount = summary.structured.lockedCount;
175
+ return `Memory: ${fileCount} .summer file${fileCount === 1 ? "" : "s"}, ${structuredCount} memory file${structuredCount === 1 ? "" : "s"}, ${lockedCount} locked`;
176
+ }
177
+ function canonicalRowsFromSummary(summary) {
178
+ const rows = [
179
+ ["brief", summary.canonical.gameSoul],
180
+ ["art", summary.canonical.artBible],
181
+ ["audio", summary.canonical.audioBible],
182
+ ["build", summary.canonical.buildPlan],
183
+ ["legacy-casting", summary.canonical.legacyVoiceCast],
184
+ ];
185
+ return rows.filter((row) => row[1] !== null);
186
+ }
187
+ function formatMemoryFileLine(label, file) {
188
+ const lock = file.locked ? "locked" : "";
189
+ const title = file.title ? ` - ${file.title}` : "";
190
+ return ` ${pad(label, 15)} ${pad(file.path, 42)} ${lock}${title}`;
191
+ }
192
+ function isPathInside(parent, child) {
193
+ const relativePath = relative(parent, child);
194
+ return (relativePath === "" ||
195
+ (!relativePath.startsWith("..") && !isAbsolute(relativePath)));
196
+ }