summer-engine 2.3.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 +51 -30
- 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 +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/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 +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,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.5.34 Block E — async tool-lifecycle consumer for the CLI/MCP.
|
|
3
|
+
*
|
|
4
|
+
* Ported from the web orchestrator (publicsummerengine
|
|
5
|
+
* src/lib/bridge/async-op-lifecycle.ts) so the CLI speaks the SAME contract as
|
|
6
|
+
* the engine's responsive tool server (Block E):
|
|
7
|
+
* - NEW path: `202 {requestId, status:"queued"}` -> long-poll
|
|
8
|
+
* GET /api/ops/result?requestId=&wait=ms until terminal (done|failed|canceled).
|
|
9
|
+
* - LEGACY path (dormant/older engine): a synchronous `200` with the full apply
|
|
10
|
+
* result, exactly as before.
|
|
11
|
+
*
|
|
12
|
+
* It inspects the RESPONSE (status/body), not a flag, so it stays compatible with
|
|
13
|
+
* both an async and a synchronous engine. Pure + dependency-injected (now/sleep/
|
|
14
|
+
* pollOnce) so it is unit-tested without HTTP.
|
|
15
|
+
*/
|
|
16
|
+
const TERMINAL_STATUSES = new Set(["done", "failed", "canceled"]);
|
|
17
|
+
export function isTerminalStatus(status) {
|
|
18
|
+
return status != null && TERMINAL_STATUSES.has(status);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Decide whether a POST /api/ops (or GET /api/snapshot/*) response is the new
|
|
22
|
+
* async ack (`202`) or the legacy synchronous result (`200`). Inspecting the HTTP
|
|
23
|
+
* status (not a flag) is what keeps this compatible with both engines.
|
|
24
|
+
*/
|
|
25
|
+
export function classifyOpsResponse(httpStatus, body) {
|
|
26
|
+
const b = (body ?? {});
|
|
27
|
+
const looksQueued = b.accepted === true && b.status === "queued" && typeof b.requestId === "string";
|
|
28
|
+
if (httpStatus === 202 || looksQueued) {
|
|
29
|
+
return { mode: "queued", requestId: String(b.requestId ?? "") };
|
|
30
|
+
}
|
|
31
|
+
return { mode: "legacy", legacyResult: body };
|
|
32
|
+
}
|
|
33
|
+
const TIMED_OUT_RESULT = {
|
|
34
|
+
status: "error",
|
|
35
|
+
error: "Tool timeout - Summer Engine may be unresponsive",
|
|
36
|
+
terminalState: "timed_out",
|
|
37
|
+
errorClass: "transient",
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Drive an async op to a terminal result by polling. `pollOnce(waitMs)` performs
|
|
41
|
+
* one GET /api/ops/result (long-poll up to waitMs). Returns the engine's apply
|
|
42
|
+
* dict with terminalState/errorClass/appliedSeq merged on top, or a synthetic
|
|
43
|
+
* `timed_out` result if the budget is exhausted.
|
|
44
|
+
*/
|
|
45
|
+
export async function pollOpToTerminal(pollOnce, opts) {
|
|
46
|
+
const now = opts.now ?? (() => Date.now());
|
|
47
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
48
|
+
const pollWaitMs = opts.pollWaitMs ?? 5000;
|
|
49
|
+
const pacingMs = opts.pacingMs ?? 250;
|
|
50
|
+
const noProgressMs = opts.noProgressTimeoutMs ?? opts.totalTimeoutMs;
|
|
51
|
+
const start = now();
|
|
52
|
+
let lastAdvanceAt = start;
|
|
53
|
+
let lastStatus;
|
|
54
|
+
let lastSeq = -1;
|
|
55
|
+
let lastProgress = "";
|
|
56
|
+
for (;;) {
|
|
57
|
+
const env = await pollOnce(pollWaitMs);
|
|
58
|
+
if (isTerminalStatus(env.status)) {
|
|
59
|
+
const out = { ...(env.result ?? {}) };
|
|
60
|
+
if (env.terminalState)
|
|
61
|
+
out.terminalState = env.terminalState;
|
|
62
|
+
if (env.errorClass)
|
|
63
|
+
out.errorClass = env.errorClass;
|
|
64
|
+
if (env.appliedSeq != null)
|
|
65
|
+
out.appliedSeq = env.appliedSeq;
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
const progressJson = env.progress != null ? JSON.stringify(env.progress) : "";
|
|
69
|
+
const seq = env.appliedSeq ?? -1;
|
|
70
|
+
const advanced = env.status !== lastStatus || seq !== lastSeq || progressJson !== lastProgress;
|
|
71
|
+
const t = now();
|
|
72
|
+
if (advanced) {
|
|
73
|
+
lastAdvanceAt = t;
|
|
74
|
+
lastStatus = env.status;
|
|
75
|
+
lastSeq = seq;
|
|
76
|
+
lastProgress = progressJson;
|
|
77
|
+
}
|
|
78
|
+
if (t - start >= opts.totalTimeoutMs || t - lastAdvanceAt >= noProgressMs) {
|
|
79
|
+
return { ...TIMED_OUT_RESULT };
|
|
80
|
+
}
|
|
81
|
+
await sleep(pacingMs);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ProjectMemoryFileSummary {
|
|
2
|
+
path: string;
|
|
3
|
+
kind: string;
|
|
4
|
+
title: string | null;
|
|
5
|
+
modifiedTime: string;
|
|
6
|
+
bytes: number;
|
|
7
|
+
locked: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ProjectMemorySummary {
|
|
10
|
+
present: boolean;
|
|
11
|
+
root: ".summer";
|
|
12
|
+
reason?: string;
|
|
13
|
+
canonical: {
|
|
14
|
+
gameSoul: ProjectMemoryFileSummary | null;
|
|
15
|
+
artBible: ProjectMemoryFileSummary | null;
|
|
16
|
+
audioBible: ProjectMemoryFileSummary | null;
|
|
17
|
+
buildPlan: ProjectMemoryFileSummary | null;
|
|
18
|
+
legacyVoiceCast: ProjectMemoryFileSummary | null;
|
|
19
|
+
};
|
|
20
|
+
structured: {
|
|
21
|
+
present: boolean;
|
|
22
|
+
indexPresent: boolean;
|
|
23
|
+
fileCount: number;
|
|
24
|
+
lockedCount: number;
|
|
25
|
+
files: ProjectMemoryFileSummary[];
|
|
26
|
+
truncated: boolean;
|
|
27
|
+
};
|
|
28
|
+
files: ProjectMemoryFileSummary[];
|
|
29
|
+
guidance: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function getProjectMemorySummary(projectPath: string | null | undefined, options?: {
|
|
32
|
+
maxFiles?: number;
|
|
33
|
+
}): ProjectMemorySummary;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { closeSync, existsSync, openSync, readSync, readdirSync, statSync, } from "node:fs";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
const CANONICAL_FILES = [
|
|
4
|
+
{ key: "gameSoul", relativePath: ".summer/GameSoul.md", kind: "brief" },
|
|
5
|
+
{ key: "artBible", relativePath: ".summer/art-bible.md", kind: "art" },
|
|
6
|
+
{ key: "audioBible", relativePath: ".summer/audio-bible.md", kind: "audio" },
|
|
7
|
+
{ key: "buildPlan", relativePath: ".summer/build-plan.md", kind: "build-plan" },
|
|
8
|
+
{ key: "legacyVoiceCast", relativePath: ".summer/voice-cast.md", kind: "legacy-casting" },
|
|
9
|
+
];
|
|
10
|
+
const SKIPPED_DIRS = new Set(["skills", "local"]);
|
|
11
|
+
const DEFAULT_MAX_FILES = 30;
|
|
12
|
+
const MAX_TITLE_BYTES = 16_384;
|
|
13
|
+
export function getProjectMemorySummary(projectPath, options = {}) {
|
|
14
|
+
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
|
|
15
|
+
const empty = createEmptySummary();
|
|
16
|
+
if (!projectPath) {
|
|
17
|
+
return {
|
|
18
|
+
...empty,
|
|
19
|
+
reason: "Project path is not available from the running engine.",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const summerDir = join(projectPath, ".summer");
|
|
23
|
+
if (!existsSync(summerDir)) {
|
|
24
|
+
return {
|
|
25
|
+
...empty,
|
|
26
|
+
reason: "No .summer directory found yet.",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const canonical = createCanonicalSummary(projectPath);
|
|
30
|
+
const allFiles = scanMarkdownFiles(projectPath, summerDir, maxFiles);
|
|
31
|
+
const memoryDir = join(projectPath, ".summer", "memory");
|
|
32
|
+
const structuredFiles = existsSync(memoryDir)
|
|
33
|
+
? scanMarkdownFiles(projectPath, memoryDir, maxFiles)
|
|
34
|
+
: [];
|
|
35
|
+
const indexPath = join(projectPath, ".summer", "memory", "index.json");
|
|
36
|
+
return {
|
|
37
|
+
present: true,
|
|
38
|
+
root: ".summer",
|
|
39
|
+
canonical,
|
|
40
|
+
structured: {
|
|
41
|
+
present: existsSync(memoryDir),
|
|
42
|
+
indexPresent: existsSync(indexPath),
|
|
43
|
+
fileCount: Math.min(structuredFiles.length, maxFiles),
|
|
44
|
+
lockedCount: structuredFiles.filter((file) => file.locked).length,
|
|
45
|
+
files: structuredFiles.slice(0, maxFiles),
|
|
46
|
+
truncated: structuredFiles.length > maxFiles,
|
|
47
|
+
},
|
|
48
|
+
files: allFiles.slice(0, maxFiles),
|
|
49
|
+
guidance: "Read relevant .summer Markdown before creative, audio, dialogue, level, or character work. Ask before writing .summer files. Changing priority: locked memory requires explicit user confirmation.",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function createEmptySummary() {
|
|
53
|
+
return {
|
|
54
|
+
present: false,
|
|
55
|
+
root: ".summer",
|
|
56
|
+
canonical: {
|
|
57
|
+
gameSoul: null,
|
|
58
|
+
artBible: null,
|
|
59
|
+
audioBible: null,
|
|
60
|
+
buildPlan: null,
|
|
61
|
+
legacyVoiceCast: null,
|
|
62
|
+
},
|
|
63
|
+
structured: {
|
|
64
|
+
present: false,
|
|
65
|
+
indexPresent: false,
|
|
66
|
+
fileCount: 0,
|
|
67
|
+
lockedCount: 0,
|
|
68
|
+
files: [],
|
|
69
|
+
truncated: false,
|
|
70
|
+
},
|
|
71
|
+
files: [],
|
|
72
|
+
guidance: "Create .summer/GameSoul.md for the project brief. Use .summer/memory/ for locked facts such as character voice IDs, world canon, and provider bindings.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function createCanonicalSummary(projectPath) {
|
|
76
|
+
const canonical = createEmptySummary().canonical;
|
|
77
|
+
for (const spec of CANONICAL_FILES) {
|
|
78
|
+
const absolutePath = join(projectPath, ...spec.relativePath.split("/"));
|
|
79
|
+
canonical[spec.key] = summarizeFile(projectPath, absolutePath, spec.kind);
|
|
80
|
+
}
|
|
81
|
+
return canonical;
|
|
82
|
+
}
|
|
83
|
+
function scanMarkdownFiles(projectPath, startDir, maxFiles) {
|
|
84
|
+
const files = [];
|
|
85
|
+
walkSummerDir(projectPath, startDir, startDir, files, maxFiles + 1, 0);
|
|
86
|
+
return files;
|
|
87
|
+
}
|
|
88
|
+
function walkSummerDir(projectPath, summerDir, currentDir, files, maxFiles, depth) {
|
|
89
|
+
if (files.length >= maxFiles || depth > 5)
|
|
90
|
+
return;
|
|
91
|
+
let entries;
|
|
92
|
+
try {
|
|
93
|
+
entries = readdirSync(currentDir).sort();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (files.length >= maxFiles)
|
|
100
|
+
return;
|
|
101
|
+
const absolutePath = join(currentDir, entry);
|
|
102
|
+
let stat;
|
|
103
|
+
try {
|
|
104
|
+
stat = statSync(absolutePath);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (stat.isDirectory()) {
|
|
110
|
+
const relativeToSummer = relative(summerDir, absolutePath);
|
|
111
|
+
if (depth === 0 && SKIPPED_DIRS.has(relativeToSummer))
|
|
112
|
+
continue;
|
|
113
|
+
walkSummerDir(projectPath, summerDir, absolutePath, files, maxFiles, depth + 1);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!stat.isFile() || !entry.endsWith(".md"))
|
|
117
|
+
continue;
|
|
118
|
+
const summary = summarizeFile(projectPath, absolutePath, kindForPath(projectPath, absolutePath));
|
|
119
|
+
if (summary)
|
|
120
|
+
files.push(summary);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function summarizeFile(projectPath, absolutePath, kind) {
|
|
124
|
+
if (!existsSync(absolutePath))
|
|
125
|
+
return null;
|
|
126
|
+
let stat;
|
|
127
|
+
try {
|
|
128
|
+
stat = statSync(absolutePath);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
if (!stat.isFile())
|
|
134
|
+
return null;
|
|
135
|
+
const preview = readPreview(absolutePath);
|
|
136
|
+
return {
|
|
137
|
+
path: normalizePath(relative(projectPath, absolutePath)),
|
|
138
|
+
kind,
|
|
139
|
+
title: extractTitle(preview),
|
|
140
|
+
modifiedTime: stat.mtime.toISOString(),
|
|
141
|
+
bytes: stat.size,
|
|
142
|
+
locked: isLockedMemory(preview),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function readPreview(absolutePath) {
|
|
146
|
+
let fd = null;
|
|
147
|
+
try {
|
|
148
|
+
fd = openSync(absolutePath, "r");
|
|
149
|
+
const buffer = Buffer.alloc(MAX_TITLE_BYTES);
|
|
150
|
+
const bytesRead = readSync(fd, buffer, 0, MAX_TITLE_BYTES, 0);
|
|
151
|
+
return buffer.toString("utf-8", 0, bytesRead);
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
if (fd !== null) {
|
|
158
|
+
try {
|
|
159
|
+
closeSync(fd);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Best-effort summary only.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function extractTitle(markdown) {
|
|
168
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
169
|
+
const heading = line.match(/^#\s+(.+?)\s*$/);
|
|
170
|
+
if (heading)
|
|
171
|
+
return heading[1];
|
|
172
|
+
}
|
|
173
|
+
for (const line of markdown.split(/\r?\n/)) {
|
|
174
|
+
const trimmed = line.trim();
|
|
175
|
+
if (trimmed && trimmed !== "---" && !trimmed.includes(":")) {
|
|
176
|
+
return trimmed.slice(0, 120);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
function isLockedMemory(markdown) {
|
|
182
|
+
return (/^priority:\s*locked\s*$/im.test(markdown) ||
|
|
183
|
+
/^stable:\s*true\s*$/im.test(markdown) ||
|
|
184
|
+
/\|\s*locked\s*\|/i.test(markdown));
|
|
185
|
+
}
|
|
186
|
+
function kindForPath(projectPath, absolutePath) {
|
|
187
|
+
const path = normalizePath(relative(projectPath, absolutePath));
|
|
188
|
+
if (path === ".summer/GameSoul.md")
|
|
189
|
+
return "brief";
|
|
190
|
+
if (path === ".summer/art-bible.md")
|
|
191
|
+
return "art";
|
|
192
|
+
if (path === ".summer/audio-bible.md")
|
|
193
|
+
return "audio";
|
|
194
|
+
if (path === ".summer/build-plan.md")
|
|
195
|
+
return "build-plan";
|
|
196
|
+
if (path === ".summer/voice-cast.md")
|
|
197
|
+
return "legacy-casting";
|
|
198
|
+
if (path.startsWith(".summer/memory/casting/"))
|
|
199
|
+
return "casting";
|
|
200
|
+
if (path.startsWith(".summer/memory/characters/"))
|
|
201
|
+
return "character";
|
|
202
|
+
if (path.startsWith(".summer/memory/world/"))
|
|
203
|
+
return "world";
|
|
204
|
+
if (path.startsWith(".summer/memory/systems/"))
|
|
205
|
+
return "system";
|
|
206
|
+
if (path.startsWith(".summer/memory/decisions/"))
|
|
207
|
+
return "decision";
|
|
208
|
+
if (path.startsWith(".summer/memory/conflicts/"))
|
|
209
|
+
return "conflict";
|
|
210
|
+
if (path.startsWith(".summer/mechanics/"))
|
|
211
|
+
return "mechanic";
|
|
212
|
+
if (path.startsWith(".summer/levels/"))
|
|
213
|
+
return "level";
|
|
214
|
+
if (path.startsWith(".summer/npcs/"))
|
|
215
|
+
return "npc";
|
|
216
|
+
return "note";
|
|
217
|
+
}
|
|
218
|
+
function normalizePath(path) {
|
|
219
|
+
return path.split(sep).join("/");
|
|
220
|
+
}
|