summer-engine 2.3.0 → 2.6.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 +2 -2
- package/.claude-plugin/plugin.json +24 -8
- package/.codex-plugin/plugin.json +19 -8
- package/.cursor-plugin/plugin.json +18 -7
- package/.factory-plugin/plugin.json +3 -3
- package/.opencode/INSTALL.md +1 -1
- package/AGENTS.md +6 -4
- package/CHANGELOG.md +46 -7
- package/CLAUDE.md +2 -2
- package/GEMINI.md +5 -6
- package/README.md +116 -109
- package/_persona/summer/SKILL.md +5 -6
- package/commands/gameskill.md +103 -0
- package/dist/bin/summer.js +17 -4
- package/dist/commands/cloud.d.ts +2 -0
- package/dist/commands/cloud.js +80 -0
- package/dist/commands/create.js +2 -0
- package/dist/commands/doctor.js +90 -1
- package/dist/commands/install.d.ts +17 -0
- package/dist/commands/install.js +63 -4
- package/dist/commands/login.js +4 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/memory.d.ts +14 -0
- package/dist/commands/memory.js +196 -0
- package/dist/commands/open.js +1 -1
- package/dist/commands/plan.d.ts +2 -0
- package/dist/commands/plan.js +60 -0
- package/dist/commands/run.js +132 -35
- package/dist/commands/setup.js +8 -2
- package/dist/commands/skills.js +90 -6
- package/dist/commands/status.js +3 -0
- package/dist/lib/agent-config.d.ts +2 -2
- package/dist/lib/agent-config.js +134 -7
- 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/auth.d.ts +2 -0
- package/dist/lib/auth.js +13 -0
- package/dist/lib/cloud/api.d.ts +79 -0
- package/dist/lib/cloud/api.js +107 -0
- package/dist/lib/cloud/apply.d.ts +32 -0
- package/dist/lib/cloud/apply.js +294 -0
- package/dist/lib/cloud/checkpoint.d.ts +26 -0
- package/dist/lib/cloud/checkpoint.js +142 -0
- package/dist/lib/cloud/conflicts.d.ts +55 -0
- package/dist/lib/cloud/conflicts.js +131 -0
- package/dist/lib/cloud/containment.d.ts +8 -0
- package/dist/lib/cloud/containment.js +52 -0
- package/dist/lib/cloud/diff.d.ts +39 -0
- package/dist/lib/cloud/diff.js +138 -0
- package/dist/lib/cloud/engine-bridge.d.ts +21 -0
- package/dist/lib/cloud/engine-bridge.js +105 -0
- package/dist/lib/cloud/hash-cache.d.ts +23 -0
- package/dist/lib/cloud/hash-cache.js +44 -0
- package/dist/lib/cloud/hash.d.ts +10 -0
- package/dist/lib/cloud/hash.js +111 -0
- package/dist/lib/cloud/http.d.ts +17 -0
- package/dist/lib/cloud/http.js +47 -0
- package/dist/lib/cloud/lock-path.d.ts +1 -0
- package/dist/lib/cloud/lock-path.js +5 -0
- package/dist/lib/cloud/lock.d.ts +14 -0
- package/dist/lib/cloud/lock.js +143 -0
- package/dist/lib/cloud/manifest.d.ts +6 -0
- package/dist/lib/cloud/manifest.js +41 -0
- package/dist/lib/cloud/paths.d.ts +20 -0
- package/dist/lib/cloud/paths.js +90 -0
- package/dist/lib/cloud/project-godot-merge.d.ts +32 -0
- package/dist/lib/cloud/project-godot-merge.js +185 -0
- package/dist/lib/cloud/rules.d.ts +39 -0
- package/dist/lib/cloud/rules.js +159 -0
- package/dist/lib/cloud/sync.d.ts +21 -0
- package/dist/lib/cloud/sync.js +518 -0
- package/dist/lib/cloud/transfer.d.ts +23 -0
- package/dist/lib/cloud/transfer.js +184 -0
- package/dist/lib/cloud/types.d.ts +62 -0
- package/dist/lib/cloud/validate-path.d.ts +23 -0
- package/dist/lib/cloud/validate-path.js +103 -0
- package/dist/lib/engine.d.ts +6 -0
- package/dist/lib/engine.js +36 -1
- package/dist/lib/game-task-plan.d.ts +42 -0
- package/dist/lib/game-task-plan.js +376 -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 +102 -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 +83 -0
- package/dist/mcp/tools/asset-tools.js +265 -45
- package/dist/mcp/tools/cloud-tools.d.ts +2 -0
- package/dist/mcp/tools/cloud-tools.js +42 -0
- package/dist/mcp/tools/debug-tools.js +32 -2
- package/dist/mcp/tools/generate-tools.js +101 -13
- package/dist/mcp/tools/project-tools.js +76 -21
- package/dist/mcp/tools/with-engine.d.ts +14 -0
- package/dist/mcp/tools/with-engine.js +52 -7
- package/gemini-extension.json +3 -3
- 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 +20 -7
- 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 +28 -6
- package/skills/3d-assets/environment-kit/SKILL.md +7 -2
- package/skills/3d-assets/organic-model/SKILL.md +5 -4
- package/skills/3d-assets/prop-model/SKILL.md +17 -9
- package/skills/3d-assets/vehicle-model/SKILL.md +5 -4
- package/skills/README.md +37 -30
- package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
- package/skills/animation/animation-tree/SKILL.md +4 -2
- package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
- package/skills/animation/generate-motion/SKILL.md +26 -5
- package/skills/animation/procedural-animation/SKILL.md +2 -2
- package/skills/asset-pipeline/asset-strategy/SKILL.md +3 -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/mcpupdate/SKILL.md +134 -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.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
- /package/dist/{commands/doctor.test.d.ts → lib/cloud/types.js} +0 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { mkdir, readFile } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { getCloudApi } from "./api.js";
|
|
4
|
+
import { getBlobTransport } from "./http.js";
|
|
5
|
+
import { sha256Hex } from "./manifest.js";
|
|
6
|
+
import { stagingDir } from "./paths.js";
|
|
7
|
+
/** Server batch caps (spec 7.5 to 7.8); the server 400s above these. */
|
|
8
|
+
export const CHECK_BATCH = 10_000;
|
|
9
|
+
export const PRESIGN_BATCH = 500;
|
|
10
|
+
export const COMPLETE_BATCH = 500;
|
|
11
|
+
export const PRESIGN_GET_BATCH = 500;
|
|
12
|
+
const UPLOAD_CONCURRENCY = 16;
|
|
13
|
+
const DOWNLOAD_CONCURRENCY = 32;
|
|
14
|
+
const RETRY_BACKOFF_MS = [1_000, 2_000, 4_000];
|
|
15
|
+
export async function uploadBlobs(projectId, blobs) {
|
|
16
|
+
const api = getCloudApi();
|
|
17
|
+
const hashes = [...blobs.keys()];
|
|
18
|
+
const outcome = { uploaded: [], requeued: [] };
|
|
19
|
+
if (!hashes.length)
|
|
20
|
+
return outcome;
|
|
21
|
+
const missing = new Set();
|
|
22
|
+
for (const chunk of chunks(hashes, CHECK_BATCH)) {
|
|
23
|
+
for (const hash of (await api.checkBlobs(projectId, chunk)).missing)
|
|
24
|
+
missing.add(hash);
|
|
25
|
+
}
|
|
26
|
+
if (!missing.size)
|
|
27
|
+
return outcome;
|
|
28
|
+
// JIT presign loop: presign in batches as the upload queue drains so URLs
|
|
29
|
+
// stay fresh on slow links.
|
|
30
|
+
const queue = [...missing];
|
|
31
|
+
while (queue.length) {
|
|
32
|
+
const batch = queue.splice(0, PRESIGN_BATCH);
|
|
33
|
+
const toPresign = batch.map((sha256) => {
|
|
34
|
+
const source = blobs.get(sha256);
|
|
35
|
+
if (!source)
|
|
36
|
+
throw new Error(`Missing upload source for ${sha256}`);
|
|
37
|
+
return { sha256, size: source.size };
|
|
38
|
+
});
|
|
39
|
+
const signed = await api.presignBlobs(projectId, toPresign);
|
|
40
|
+
const completed = [];
|
|
41
|
+
await runWithConcurrency(signed.blobs, UPLOAD_CONCURRENCY, async (item) => {
|
|
42
|
+
if (item.status === "exists")
|
|
43
|
+
return;
|
|
44
|
+
if (item.status !== "upload" || !item.url || !item.uploadId || !item.headers) {
|
|
45
|
+
throw new Error(item.message || `Could not presign ${item.sha256}: ${item.status}`);
|
|
46
|
+
}
|
|
47
|
+
const result = await putWithRetry(projectId, item, blobs.get(item.sha256));
|
|
48
|
+
if (result === "uploaded") {
|
|
49
|
+
completed.push({ sha256: item.sha256, uploadId: item.uploadId });
|
|
50
|
+
outcome.uploaded.push(item.sha256);
|
|
51
|
+
}
|
|
52
|
+
else if (result === "already-verified") {
|
|
53
|
+
outcome.uploaded.push(item.sha256);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
outcome.requeued.push(item.sha256);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
for (const completeChunk of chunks(completed, COMPLETE_BATCH)) {
|
|
60
|
+
if (!completeChunk.length)
|
|
61
|
+
continue;
|
|
62
|
+
const result = await api.completeBlobs(projectId, completeChunk);
|
|
63
|
+
const failed = result.uploads.filter((upload) => upload.status !== "verified");
|
|
64
|
+
if (failed.length) {
|
|
65
|
+
throw new Error(`Cloud upload completion failed: ${failed.map((f) => `${f.sha256}:${f.status}`).join(", ")}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return outcome;
|
|
70
|
+
}
|
|
71
|
+
async function putWithRetry(projectId, item, source) {
|
|
72
|
+
const api = getCloudApi();
|
|
73
|
+
const transport = getBlobTransport();
|
|
74
|
+
// Re-hash the bytes that are actually sent (spec 8.6.4); on divergence from
|
|
75
|
+
// the diff-time hash, abort this file and requeue it for the next run.
|
|
76
|
+
const bytes = source.bytes ?? (await readFile(source.path));
|
|
77
|
+
if (sha256Hex(bytes) !== item.sha256) {
|
|
78
|
+
return "requeued";
|
|
79
|
+
}
|
|
80
|
+
for (let attempt = 0;; attempt += 1) {
|
|
81
|
+
const status = await transport.put(item.url, item.headers, bytes);
|
|
82
|
+
if (status >= 200 && status < 300)
|
|
83
|
+
return "uploaded";
|
|
84
|
+
if (status === 429) {
|
|
85
|
+
// R2 per-key write throttling: back off 1s and re-check; verified means
|
|
86
|
+
// someone else won the race with identical bytes.
|
|
87
|
+
await sleep(1_000);
|
|
88
|
+
const recheck = await api.checkBlobs(projectId, [item.sha256]);
|
|
89
|
+
if (!recheck.missing.includes(item.sha256))
|
|
90
|
+
return "already-verified";
|
|
91
|
+
}
|
|
92
|
+
if (attempt >= RETRY_BACKOFF_MS.length) {
|
|
93
|
+
throw new Error(`R2 upload failed for ${item.sha256}: ${status}`);
|
|
94
|
+
}
|
|
95
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Two-phase pull, phase 1 (spec 8.7): stream every needed blob to
|
|
100
|
+
* .summer/local/cloud/staging/{sha256}, verify its sha256, fsync. Returns the
|
|
101
|
+
* staged file path per hash. Already-staged blobs with a verified hash are
|
|
102
|
+
* reused (journal resume).
|
|
103
|
+
*/
|
|
104
|
+
export async function downloadBlobs(projectRoot, projectId, hashes) {
|
|
105
|
+
const api = getCloudApi();
|
|
106
|
+
const transport = getBlobTransport();
|
|
107
|
+
const unique = [...new Set(hashes)];
|
|
108
|
+
const staged = new Map();
|
|
109
|
+
await mkdir(stagingDir(projectRoot), { recursive: true, mode: 0o700 });
|
|
110
|
+
const needed = [];
|
|
111
|
+
for (const hash of unique) {
|
|
112
|
+
const path = join(stagingDir(projectRoot), hash);
|
|
113
|
+
if (await isStagedAndValid(path, hash)) {
|
|
114
|
+
staged.set(hash, path);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
needed.push(hash);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const chunk of chunks(needed, PRESIGN_GET_BATCH)) {
|
|
121
|
+
const signed = await api.presignGet(projectId, chunk);
|
|
122
|
+
if (signed.missing.length) {
|
|
123
|
+
throw new Error(`Cloud is missing referenced blobs: ${signed.missing.join(", ")}`);
|
|
124
|
+
}
|
|
125
|
+
await runWithConcurrency(chunk, DOWNLOAD_CONCURRENCY, async (hash) => {
|
|
126
|
+
const url = signed.urls[hash];
|
|
127
|
+
if (!url)
|
|
128
|
+
throw new Error(`No URL for ${hash}`);
|
|
129
|
+
const path = join(stagingDir(projectRoot), hash);
|
|
130
|
+
for (let attempt = 0;; attempt += 1) {
|
|
131
|
+
const result = await transport.getToFile(url, path);
|
|
132
|
+
if (result.status >= 200 && result.status < 300) {
|
|
133
|
+
if (result.sha256 !== hash)
|
|
134
|
+
throw new Error(`Downloaded blob hash mismatch: ${hash}`);
|
|
135
|
+
staged.set(hash, path);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (attempt >= RETRY_BACKOFF_MS.length) {
|
|
139
|
+
throw new Error(`Download failed for ${hash}: ${result.status}`);
|
|
140
|
+
}
|
|
141
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return staged;
|
|
146
|
+
}
|
|
147
|
+
async function isStagedAndValid(path, hash) {
|
|
148
|
+
try {
|
|
149
|
+
const bytes = await readFile(path);
|
|
150
|
+
return sha256Hex(bytes) === hash;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function runWithConcurrency(items, limit, worker) {
|
|
157
|
+
const pending = [...items];
|
|
158
|
+
const errors = [];
|
|
159
|
+
const runners = Array.from({ length: Math.min(limit, pending.length) }, async () => {
|
|
160
|
+
while (pending.length) {
|
|
161
|
+
const item = pending.shift();
|
|
162
|
+
if (item === undefined)
|
|
163
|
+
return;
|
|
164
|
+
try {
|
|
165
|
+
await worker(item);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
errors.push(err);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
await Promise.all(runners);
|
|
173
|
+
if (errors.length)
|
|
174
|
+
throw errors[0];
|
|
175
|
+
}
|
|
176
|
+
function chunks(items, size) {
|
|
177
|
+
const out = [];
|
|
178
|
+
for (let i = 0; i < items.length; i += size)
|
|
179
|
+
out.push(items.slice(i, i + size));
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
function sleep(ms) {
|
|
183
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
184
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type HashHex = string;
|
|
2
|
+
export interface ManifestFile {
|
|
3
|
+
sha256: HashHex;
|
|
4
|
+
size: number;
|
|
5
|
+
}
|
|
6
|
+
export interface CloudManifest {
|
|
7
|
+
schemaVersion: 1;
|
|
8
|
+
projectId: string;
|
|
9
|
+
rulesVersion: number;
|
|
10
|
+
files: Record<string, ManifestFile>;
|
|
11
|
+
}
|
|
12
|
+
export interface CloudBinding {
|
|
13
|
+
schemaVersion: 1;
|
|
14
|
+
projectId: string;
|
|
15
|
+
pinnedVersion?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface BaseState extends CloudManifest {
|
|
18
|
+
version: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PullJournal {
|
|
21
|
+
syncId: string;
|
|
22
|
+
targetVersion: number;
|
|
23
|
+
phase: "staging" | "applying";
|
|
24
|
+
pending: Array<{
|
|
25
|
+
path: string;
|
|
26
|
+
sha256: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export interface LocalFileStat {
|
|
30
|
+
size: number;
|
|
31
|
+
mtimeNs: string;
|
|
32
|
+
inode: string;
|
|
33
|
+
}
|
|
34
|
+
export interface WalkResult {
|
|
35
|
+
files: Record<string, ManifestFile>;
|
|
36
|
+
fileByHash: Map<string, string>;
|
|
37
|
+
/** NFC manifest key to actual absolute on-disk path (byte-name map for I/O). */
|
|
38
|
+
diskPathByKey: Map<string, string>;
|
|
39
|
+
/** Pre-image stats captured at hash time, used by the apply-time stat check. */
|
|
40
|
+
statByKey: Map<string, LocalFileStat>;
|
|
41
|
+
skippedSymlinks: string[];
|
|
42
|
+
/** Paths excluded from THIS run because they kept changing while hashing. */
|
|
43
|
+
unstablePaths: string[];
|
|
44
|
+
}
|
|
45
|
+
export type BootstrapChoice = "keep-cloud" | "keep-local" | "merge";
|
|
46
|
+
export interface SyncOptions {
|
|
47
|
+
project?: string;
|
|
48
|
+
json?: boolean;
|
|
49
|
+
confirmDeletes?: boolean;
|
|
50
|
+
bootstrap?: BootstrapChoice;
|
|
51
|
+
/** Accept that the project folder moved and update .summer/local/.project_path. */
|
|
52
|
+
adoptPath?: boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface SyncResult {
|
|
55
|
+
ok: boolean;
|
|
56
|
+
action: string;
|
|
57
|
+
projectId?: string;
|
|
58
|
+
version?: number;
|
|
59
|
+
message: string;
|
|
60
|
+
notices?: string[];
|
|
61
|
+
details?: Record<string, unknown>;
|
|
62
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unicode case folding approximation without ICU: lower, upper, lower. The
|
|
3
|
+
* round trip folds one-to-many mappings that plain toLowerCase misses
|
|
4
|
+
* (eszett to ss, long s to s, final sigma to sigma).
|
|
5
|
+
*/
|
|
6
|
+
export declare function casefoldKey(path: string): string;
|
|
7
|
+
export declare function validateCloudPath(path: string): {
|
|
8
|
+
ok: true;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
reason: string;
|
|
12
|
+
};
|
|
13
|
+
export interface PathCollision {
|
|
14
|
+
kind: "casefold" | "nfc" | "prefix";
|
|
15
|
+
a: string;
|
|
16
|
+
b: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Pairwise key collision checks (spec section 9): two keys equal after NFC,
|
|
20
|
+
* two keys equal under casefold(NFC(key)), or one key being a directory
|
|
21
|
+
* prefix of another (file vs directory collision).
|
|
22
|
+
*/
|
|
23
|
+
export declare function findPathCollisions(keys: readonly string[]): PathCollision[];
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const WINDOWS_RESERVED_BASENAMES = new Set([
|
|
2
|
+
"con",
|
|
3
|
+
"prn",
|
|
4
|
+
"aux",
|
|
5
|
+
"nul",
|
|
6
|
+
"com0",
|
|
7
|
+
"com1",
|
|
8
|
+
"com2",
|
|
9
|
+
"com3",
|
|
10
|
+
"com4",
|
|
11
|
+
"com5",
|
|
12
|
+
"com6",
|
|
13
|
+
"com7",
|
|
14
|
+
"com8",
|
|
15
|
+
"com9",
|
|
16
|
+
"com¹",
|
|
17
|
+
"com²",
|
|
18
|
+
"com³",
|
|
19
|
+
"lpt0",
|
|
20
|
+
"lpt1",
|
|
21
|
+
"lpt2",
|
|
22
|
+
"lpt3",
|
|
23
|
+
"lpt4",
|
|
24
|
+
"lpt5",
|
|
25
|
+
"lpt6",
|
|
26
|
+
"lpt7",
|
|
27
|
+
"lpt8",
|
|
28
|
+
"lpt9",
|
|
29
|
+
"lpt¹",
|
|
30
|
+
"lpt²",
|
|
31
|
+
"lpt³",
|
|
32
|
+
]);
|
|
33
|
+
/** Spec section 9: full key at most 1024 bytes, each segment at most 255. */
|
|
34
|
+
const MAX_KEY_BYTES = 1024;
|
|
35
|
+
const MAX_SEGMENT_BYTES = 255;
|
|
36
|
+
/**
|
|
37
|
+
* Unicode case folding approximation without ICU: lower, upper, lower. The
|
|
38
|
+
* round trip folds one-to-many mappings that plain toLowerCase misses
|
|
39
|
+
* (eszett to ss, long s to s, final sigma to sigma).
|
|
40
|
+
*/
|
|
41
|
+
export function casefoldKey(path) {
|
|
42
|
+
return path.normalize("NFC").toLowerCase().toUpperCase().toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
export function validateCloudPath(path) {
|
|
45
|
+
if (!path || path !== path.normalize("NFC"))
|
|
46
|
+
return { ok: false, reason: "Path must be non-empty and NFC-normalized" };
|
|
47
|
+
if (path.startsWith("/") || path.startsWith("\\") || /^[a-zA-Z]:/.test(path))
|
|
48
|
+
return { ok: false, reason: "Path must be relative" };
|
|
49
|
+
if (path.includes("\\") || path.includes("//") || path.endsWith("/") || /[\u0000-\u001f]/.test(path)) {
|
|
50
|
+
return { ok: false, reason: "Path contains invalid characters" };
|
|
51
|
+
}
|
|
52
|
+
if (Buffer.byteLength(path, "utf8") > MAX_KEY_BYTES)
|
|
53
|
+
return { ok: false, reason: `Path exceeds ${MAX_KEY_BYTES} bytes` };
|
|
54
|
+
for (const segment of path.split("/")) {
|
|
55
|
+
if (!segment || segment === "." || segment === "..")
|
|
56
|
+
return { ok: false, reason: "Path contains dot or empty segment" };
|
|
57
|
+
if (Buffer.byteLength(segment, "utf8") > MAX_SEGMENT_BYTES)
|
|
58
|
+
return { ok: false, reason: `Path segment exceeds ${MAX_SEGMENT_BYTES} bytes` };
|
|
59
|
+
if (segment.endsWith(".") || segment.endsWith(" "))
|
|
60
|
+
return { ok: false, reason: "Path segment ends with dot or space" };
|
|
61
|
+
if (/[<>:"|?*]/.test(segment))
|
|
62
|
+
return { ok: false, reason: "Path segment contains a reserved character" };
|
|
63
|
+
if (WINDOWS_RESERVED_BASENAMES.has(casefoldKey(segment.split(".")[0] || ""))) {
|
|
64
|
+
return { ok: false, reason: "Path segment uses a Windows device name" };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { ok: true };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Pairwise key collision checks (spec section 9): two keys equal after NFC,
|
|
71
|
+
* two keys equal under casefold(NFC(key)), or one key being a directory
|
|
72
|
+
* prefix of another (file vs directory collision).
|
|
73
|
+
*/
|
|
74
|
+
export function findPathCollisions(keys) {
|
|
75
|
+
const collisions = [];
|
|
76
|
+
const byNfc = new Map();
|
|
77
|
+
const byFold = new Map();
|
|
78
|
+
for (const key of keys) {
|
|
79
|
+
const nfc = key.normalize("NFC");
|
|
80
|
+
const priorNfc = byNfc.get(nfc);
|
|
81
|
+
if (priorNfc !== undefined && priorNfc !== key) {
|
|
82
|
+
collisions.push({ kind: "nfc", a: priorNfc, b: key });
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
byNfc.set(nfc, key);
|
|
86
|
+
}
|
|
87
|
+
const folded = casefoldKey(key);
|
|
88
|
+
const priorFold = byFold.get(folded);
|
|
89
|
+
if (priorFold !== undefined && priorFold !== key) {
|
|
90
|
+
collisions.push({ kind: "casefold", a: priorFold, b: key });
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
byFold.set(folded, key);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const sorted = [...keys].sort();
|
|
97
|
+
for (let i = 0; i + 1 < sorted.length; i += 1) {
|
|
98
|
+
if (sorted[i + 1].startsWith(`${sorted[i]}/`)) {
|
|
99
|
+
collisions.push({ kind: "prefix", a: sorted[i], b: sorted[i + 1] });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return collisions;
|
|
103
|
+
}
|
package/dist/lib/engine.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ export interface EngineHealth {
|
|
|
5
5
|
engine: string;
|
|
6
6
|
version: string;
|
|
7
7
|
port: number;
|
|
8
|
+
pid?: number;
|
|
9
|
+
instanceId?: string;
|
|
10
|
+
projectId?: string;
|
|
11
|
+
projectIdHash?: string;
|
|
12
|
+
mainAliveMs?: number;
|
|
13
|
+
queueDepth?: number;
|
|
8
14
|
project_name?: string;
|
|
9
15
|
project_path?: string;
|
|
10
16
|
scene?: string;
|
package/dist/lib/engine.js
CHANGED
|
@@ -21,6 +21,17 @@ export async function getApiPort() {
|
|
|
21
21
|
return DEFAULT_PORT;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
function asRecord(value) {
|
|
25
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: null;
|
|
28
|
+
}
|
|
29
|
+
function stringFrom(value) {
|
|
30
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
function numberFrom(value) {
|
|
33
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
34
|
+
}
|
|
24
35
|
export async function checkEngineHealth(port) {
|
|
25
36
|
try {
|
|
26
37
|
const res = await fetch(`http://127.0.0.1:${port}/api/health`, {
|
|
@@ -28,7 +39,31 @@ export async function checkEngineHealth(port) {
|
|
|
28
39
|
});
|
|
29
40
|
if (!res.ok)
|
|
30
41
|
return null;
|
|
31
|
-
|
|
42
|
+
const data = asRecord(await res.json());
|
|
43
|
+
if (!data)
|
|
44
|
+
return null;
|
|
45
|
+
const ok = data.ok === true;
|
|
46
|
+
const engine = stringFrom(data.engine);
|
|
47
|
+
const version = stringFrom(data.version);
|
|
48
|
+
const instanceId = stringFrom(data.instanceId);
|
|
49
|
+
if (!ok || engine !== "summer" || !version || !instanceId) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
ok,
|
|
54
|
+
engine,
|
|
55
|
+
version,
|
|
56
|
+
port: numberFrom(data.port) ?? port,
|
|
57
|
+
pid: numberFrom(data.pid),
|
|
58
|
+
instanceId,
|
|
59
|
+
projectId: stringFrom(data.projectId),
|
|
60
|
+
projectIdHash: stringFrom(data.projectIdHash),
|
|
61
|
+
mainAliveMs: numberFrom(data.mainAliveMs),
|
|
62
|
+
queueDepth: numberFrom(data.queueDepth),
|
|
63
|
+
project_name: stringFrom(data.project_name),
|
|
64
|
+
project_path: stringFrom(data.project_path),
|
|
65
|
+
scene: stringFrom(data.scene),
|
|
66
|
+
};
|
|
32
67
|
}
|
|
33
68
|
catch {
|
|
34
69
|
return null;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare const GAME_TASK_MODES: readonly ["auto", "new-game", "feature", "asset", "debug", "playtest", "polish", "ship"];
|
|
2
|
+
export declare const GAME_TASK_TARGETS: readonly ["auto", "2d", "3d", "ui", "audio", "animation", "level", "npc", "multiplayer"];
|
|
3
|
+
export declare const ASSET_POLICIES: readonly ["reuse-first", "ask-before-paid-generation", "no-paid-generation", "generate-when-clearly-needed"];
|
|
4
|
+
export declare const VERIFICATION_LEVELS: readonly ["none", "fast", "full"];
|
|
5
|
+
export type GameTaskMode = (typeof GAME_TASK_MODES)[number];
|
|
6
|
+
export type GameTaskTarget = (typeof GAME_TASK_TARGETS)[number];
|
|
7
|
+
export type AssetPolicy = (typeof ASSET_POLICIES)[number];
|
|
8
|
+
export type VerificationLevel = (typeof VERIFICATION_LEVELS)[number];
|
|
9
|
+
export interface GameTaskPlanOptions {
|
|
10
|
+
goal: string;
|
|
11
|
+
mode?: GameTaskMode;
|
|
12
|
+
target?: GameTaskTarget;
|
|
13
|
+
assetPolicy?: AssetPolicy;
|
|
14
|
+
verification?: VerificationLevel;
|
|
15
|
+
}
|
|
16
|
+
export interface SkillRoute {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
category: string;
|
|
20
|
+
why: string;
|
|
21
|
+
requiresMcpTools: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export interface GameTaskPlan {
|
|
24
|
+
goal: string;
|
|
25
|
+
mode: Exclude<GameTaskMode, "auto">;
|
|
26
|
+
target: Exclude<GameTaskTarget, "auto"> | "general";
|
|
27
|
+
assetPolicy: AssetPolicy;
|
|
28
|
+
verification: VerificationLevel;
|
|
29
|
+
principles: string[];
|
|
30
|
+
recommendedSkills: SkillRoute[];
|
|
31
|
+
mcpToolPlan: {
|
|
32
|
+
start: string[];
|
|
33
|
+
mutate: string[];
|
|
34
|
+
assets: string[];
|
|
35
|
+
verify: string[];
|
|
36
|
+
};
|
|
37
|
+
hostFileWork: string[];
|
|
38
|
+
userGates: string[];
|
|
39
|
+
nextAgentSteps: string[];
|
|
40
|
+
antiPatterns: string[];
|
|
41
|
+
}
|
|
42
|
+
export declare function buildGameTaskPlan(options: GameTaskPlanOptions): GameTaskPlan;
|