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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +20 -5
- package/.codex-plugin/plugin.json +15 -5
- package/.cursor-plugin/plugin.json +15 -5
- package/.factory-plugin/plugin.json +2 -2
- package/.opencode/INSTALL.md +1 -1
- package/CHANGELOG.md +27 -6
- package/CLAUDE.md +2 -2
- package/GEMINI.md +2 -3
- package/README.md +82 -58
- package/_persona/summer/SKILL.md +5 -6
- package/commands/gameskill.md +103 -0
- package/dist/bin/summer.js +12 -4
- package/dist/commands/create.js +2 -0
- package/dist/commands/doctor.js +71 -1
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +63 -4
- package/dist/commands/memory.d.ts +14 -0
- package/dist/commands/memory.js +196 -0
- package/dist/commands/setup.js +12 -2
- package/dist/commands/skills.js +205 -8
- package/dist/commands/status.js +3 -0
- package/dist/lib/agent-config.d.ts +2 -2
- package/dist/lib/agent-config.js +298 -5
- package/dist/lib/api-client.d.ts +13 -0
- package/dist/lib/api-client.js +63 -26
- package/dist/lib/async-op-lifecycle.d.ts +60 -0
- package/dist/lib/async-op-lifecycle.js +83 -0
- package/dist/lib/project-memory.d.ts +33 -0
- package/dist/lib/project-memory.js +220 -0
- package/dist/lib/skills-registry.d.ts +134 -61
- package/dist/lib/skills-registry.js +106 -4
- package/dist/lib/version-check.d.ts +106 -0
- package/dist/lib/version-check.js +338 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +81 -0
- package/dist/mcp/tools/debug-tools.js +32 -2
- package/dist/mcp/tools/project-tools.js +18 -6
- package/dist/mcp/tools/with-engine.d.ts +14 -0
- package/dist/mcp/tools/with-engine.js +52 -7
- package/gemini-extension.json +2 -2
- package/hooks/pre-commit-doctor.sh +0 -0
- package/hooks/run-hook.cmd +0 -0
- package/hooks/session-start.sh +0 -0
- package/package.json +6 -4
- package/skills/2d-assets/character-portrait/SKILL.md +1 -1
- package/skills/2d-assets/concept-art/SKILL.md +1 -1
- package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
- package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
- package/skills/2d-assets/pixel-art/SKILL.md +1 -1
- package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
- package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
- package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
- package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
- package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
- package/skills/3d-assets/character-model/SKILL.md +1 -1
- package/skills/3d-assets/organic-model/SKILL.md +1 -1
- package/skills/3d-assets/prop-model/SKILL.md +1 -1
- package/skills/3d-assets/vehicle-model/SKILL.md +1 -1
- package/skills/README.md +37 -30
- package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
- package/skills/animation/animation-tree/SKILL.md +2 -2
- package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
- package/skills/animation/generate-motion/SKILL.md +1 -1
- package/skills/animation/procedural-animation/SKILL.md +2 -2
- package/skills/audio/adaptive-music/SKILL.md +1 -1
- package/skills/audio/audio-direction/SKILL.md +5 -5
- package/skills/audio/sound-effect/SKILL.md +2 -2
- package/skills/audio/voice-line/SKILL.md +25 -3
- package/skills/character-controllers/fps-controller/SKILL.md +5 -5
- package/skills/debugging/debug/SKILL.md +39 -1
- package/skills/deployment/export-and-ship/SKILL.md +4 -4
- package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
- package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
- package/skills/level-design/design-level/SKILL.md +5 -5
- package/skills/level-design/scene-to-level/SKILL.md +125 -0
- package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
- package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
- package/skills/performance/tune-performance/SKILL.md +5 -5
- package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
- package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
- package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
- package/skills/scene-and-project/play/SKILL.md +1 -1
- package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
- package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
- package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
- package/skills/video/animated-loop/SKILL.md +1 -1
- package/skills/video/cinematic-cutscene/SKILL.md +1 -1
- package/skills/video/trailer-shot/SKILL.md +1 -1
- package/skills/visual-effects/game-feel/SKILL.md +6 -6
- package/skills/workflow/brainstorming/SKILL.md +164 -0
- package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
- package/skills/workflow/brainstorming/scripts/helper.js +88 -0
- package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
- package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
- package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
- package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/skills/workflow/brainstorming/visual-companion.md +287 -0
- package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
- package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
- package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
- package/skills/workflow/gameskill/SKILL.md +111 -0
- package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
- package/skills/workflow/investigating-bugs/SKILL.md +296 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
- package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
- package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
- package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
- package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
- package/skills/workflow/investigating-bugs/test-academic.md +14 -0
- package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
- package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
- package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
- package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
- package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
- package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
- package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
- package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
- package/skills/workflow/verification-before-completion/SKILL.md +139 -0
- package/skills/workflow/writing-plans/SKILL.md +152 -0
- package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/workflow/writing-skills/SKILL.md +655 -0
- package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
- package/skills/workflow/writing-skills/render-graphs.js +168 -0
- package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
- package/dist/commands/assets.d.ts +0 -2
- package/dist/commands/assets.js +0 -189
- package/dist/commands/debug.d.ts +0 -2
- package/dist/commands/debug.js +0 -127
- package/dist/commands/doctor.test.d.ts +0 -1
- package/dist/commands/doctor.test.js +0 -29
- package/dist/commands/project.d.ts +0 -2
- package/dist/commands/project.js +0 -170
- package/dist/commands/scene.d.ts +0 -2
- package/dist/commands/scene.js +0 -290
- package/dist/lib/agent-config.test.d.ts +0 -1
- package/dist/lib/agent-config.test.js +0 -80
- package/dist/lib/format.test.d.ts +0 -1
- package/dist/lib/format.test.js +0 -36
- package/dist/lib/plugin-manifests.test.d.ts +0 -1
- package/dist/lib/plugin-manifests.test.js +0 -134
- package/dist/lib/with-engine.d.ts +0 -14
- package/dist/lib/with-engine.js +0 -109
- package/skills/_shared/collaborative-protocol.md +0 -50
- package/skills/_shared/gd-style.md +0 -121
- package/skills/_shared/godot-version.md +0 -32
- package/skills/_shared/mcp-tools-reference.md +0 -133
- package/skills/_shared/summer-folder.md +0 -44
- package/skills/_shared/template-registry.md +0 -40
- package/skills/_tests/runner.md +0 -90
- package/skills/_tests/specs/3d-lighting.md +0 -19
- package/skills/_tests/specs/art-direction.md +0 -115
- package/skills/_tests/specs/asset-strategy.md +0 -19
- package/skills/_tests/specs/audio-direction.md +0 -123
- package/skills/_tests/specs/brainstorm-game.md +0 -99
- package/skills/_tests/specs/debug.md +0 -94
- package/skills/_tests/specs/design-level.md +0 -113
- package/skills/_tests/specs/design-mechanic.md +0 -112
- package/skills/_tests/specs/design-npc.md +0 -143
- package/skills/_tests/specs/export-and-ship.md +0 -114
- package/skills/_tests/specs/fps-controller.md +0 -113
- package/skills/_tests/specs/gdscript-patterns.md +0 -19
- package/skills/_tests/specs/host-authoritative-state.md +0 -104
- package/skills/_tests/specs/make-game.md +0 -19
- package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
- package/skills/_tests/specs/play.md +0 -88
- package/skills/_tests/specs/scene-composition.md +0 -19
- package/skills/_tests/specs/setup-multiplayer.md +0 -106
- package/skills/_tests/specs/tune-performance.md +0 -104
- package/skills/_tests/specs/ui-basics.md +0 -19
- package/skills/_tests/specs/vfx.md +0 -116
- 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.
|
package/dist/bin/summer.js
CHANGED
|
@@ -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("
|
|
53
|
-
console.log(`
|
|
54
|
-
console.log(` ${c.
|
|
55
|
-
console.log(
|
|
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
|
}
|
package/dist/commands/create.js
CHANGED
|
@@ -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
|
package/dist/commands/doctor.js
CHANGED
|
@@ -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
|
+
}>;
|
package/dist/commands/install.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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}"
|
|
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
|
+
}
|