summer-engine 2.2.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +20 -5
  3. package/.codex-plugin/plugin.json +15 -5
  4. package/.cursor-plugin/plugin.json +15 -5
  5. package/.factory-plugin/plugin.json +2 -2
  6. package/.opencode/INSTALL.md +1 -1
  7. package/CHANGELOG.md +27 -6
  8. package/CLAUDE.md +2 -2
  9. package/GEMINI.md +2 -3
  10. package/README.md +82 -58
  11. package/_persona/summer/SKILL.md +5 -6
  12. package/commands/gameskill.md +103 -0
  13. package/dist/bin/summer.js +12 -4
  14. package/dist/commands/create.js +2 -0
  15. package/dist/commands/doctor.js +71 -1
  16. package/dist/commands/install.d.ts +17 -0
  17. package/dist/commands/install.js +63 -4
  18. package/dist/commands/memory.d.ts +14 -0
  19. package/dist/commands/memory.js +196 -0
  20. package/dist/commands/setup.js +12 -2
  21. package/dist/commands/skills.js +205 -8
  22. package/dist/commands/status.js +3 -0
  23. package/dist/lib/agent-config.d.ts +2 -2
  24. package/dist/lib/agent-config.js +298 -5
  25. package/dist/lib/api-client.d.ts +13 -0
  26. package/dist/lib/api-client.js +63 -26
  27. package/dist/lib/async-op-lifecycle.d.ts +60 -0
  28. package/dist/lib/async-op-lifecycle.js +83 -0
  29. package/dist/lib/project-memory.d.ts +33 -0
  30. package/dist/lib/project-memory.js +220 -0
  31. package/dist/lib/skills-registry.d.ts +134 -61
  32. package/dist/lib/skills-registry.js +106 -4
  33. package/dist/lib/version-check.d.ts +106 -0
  34. package/dist/lib/version-check.js +338 -0
  35. package/dist/mcp/server.d.ts +2 -0
  36. package/dist/mcp/server.js +81 -0
  37. package/dist/mcp/tools/debug-tools.js +32 -2
  38. package/dist/mcp/tools/project-tools.js +18 -6
  39. package/dist/mcp/tools/with-engine.d.ts +14 -0
  40. package/dist/mcp/tools/with-engine.js +52 -7
  41. package/gemini-extension.json +2 -2
  42. package/hooks/pre-commit-doctor.sh +0 -0
  43. package/hooks/run-hook.cmd +0 -0
  44. package/hooks/session-start.sh +0 -0
  45. package/package.json +6 -4
  46. package/skills/2d-assets/character-portrait/SKILL.md +1 -1
  47. package/skills/2d-assets/concept-art/SKILL.md +1 -1
  48. package/skills/2d-assets/create-asset-sheet/SKILL.md +163 -0
  49. package/skills/2d-assets/instantiate-asset-pack/SKILL.md +245 -0
  50. package/skills/2d-assets/pixel-art/SKILL.md +1 -1
  51. package/skills/2d-assets/skybox-panorama/SKILL.md +1 -1
  52. package/skills/2d-assets/sprite-sheet/SKILL.md +1 -1
  53. package/skills/2d-assets/tileable-texture/SKILL.md +1 -1
  54. package/skills/2d-assets/ui-graphics/SKILL.md +1 -1
  55. package/skills/2d-assets/use-widget-asset/SKILL.md +217 -0
  56. package/skills/3d-assets/character-model/SKILL.md +1 -1
  57. package/skills/3d-assets/organic-model/SKILL.md +1 -1
  58. package/skills/3d-assets/prop-model/SKILL.md +1 -1
  59. package/skills/3d-assets/vehicle-model/SKILL.md +1 -1
  60. package/skills/README.md +37 -30
  61. package/skills/ai-and-npcs/design-npc/SKILL.md +6 -6
  62. package/skills/animation/animation-tree/SKILL.md +2 -2
  63. package/skills/animation/facial-and-lipsync/SKILL.md +1 -1
  64. package/skills/animation/generate-motion/SKILL.md +1 -1
  65. package/skills/animation/procedural-animation/SKILL.md +2 -2
  66. package/skills/audio/adaptive-music/SKILL.md +1 -1
  67. package/skills/audio/audio-direction/SKILL.md +5 -5
  68. package/skills/audio/sound-effect/SKILL.md +2 -2
  69. package/skills/audio/voice-line/SKILL.md +25 -3
  70. package/skills/character-controllers/fps-controller/SKILL.md +5 -5
  71. package/skills/debugging/debug/SKILL.md +39 -1
  72. package/skills/deployment/export-and-ship/SKILL.md +4 -4
  73. package/skills/gameplay-mechanics/auto-fire-targeting/SKILL.md +170 -0
  74. package/skills/gameplay-mechanics/design-mechanic/SKILL.md +8 -8
  75. package/skills/level-design/design-level/SKILL.md +5 -5
  76. package/skills/level-design/scene-to-level/SKILL.md +125 -0
  77. package/skills/multiplayer-and-networking/host-authoritative-state/SKILL.md +4 -4
  78. package/skills/multiplayer-and-networking/peer-to-peer-multiplayer/SKILL.md +4 -4
  79. package/skills/multiplayer-and-networking/setup-multiplayer/SKILL.md +7 -7
  80. package/skills/performance/tune-performance/SKILL.md +5 -5
  81. package/skills/rendering-and-lighting/3d-lighting/SKILL.md +1 -1
  82. package/skills/rendering-and-lighting/art-direction/SKILL.md +5 -5
  83. package/skills/scene-and-project/brainstorm-game/SKILL.md +4 -4
  84. package/skills/scene-and-project/play/SKILL.md +1 -1
  85. package/skills/scene-and-project/scene-composition/SKILL.md +30 -1
  86. package/skills/scripting-patterns/gdscript-patterns/SKILL.md +53 -0
  87. package/skills/ui-and-ux/ui-basics/SKILL.md +1 -1
  88. package/skills/video/animated-loop/SKILL.md +1 -1
  89. package/skills/video/cinematic-cutscene/SKILL.md +1 -1
  90. package/skills/video/trailer-shot/SKILL.md +1 -1
  91. package/skills/visual-effects/game-feel/SKILL.md +6 -6
  92. package/skills/workflow/brainstorming/SKILL.md +164 -0
  93. package/skills/workflow/brainstorming/scripts/frame-template.html +214 -0
  94. package/skills/workflow/brainstorming/scripts/helper.js +88 -0
  95. package/skills/workflow/brainstorming/scripts/server.cjs +354 -0
  96. package/skills/workflow/brainstorming/scripts/start-server.sh +148 -0
  97. package/skills/workflow/brainstorming/scripts/stop-server.sh +56 -0
  98. package/skills/workflow/brainstorming/spec-document-reviewer-prompt.md +49 -0
  99. package/skills/workflow/brainstorming/visual-companion.md +287 -0
  100. package/skills/workflow/debugging-game-feel/SKILL.md +174 -0
  101. package/skills/workflow/diagnosing-perf-regressions/SKILL.md +190 -0
  102. package/skills/workflow/dispatching-parallel-agents/SKILL.md +182 -0
  103. package/skills/workflow/gameskill/SKILL.md +111 -0
  104. package/skills/workflow/investigating-bugs/CREATION-LOG.md +119 -0
  105. package/skills/workflow/investigating-bugs/SKILL.md +296 -0
  106. package/skills/workflow/investigating-bugs/condition-based-waiting-example.ts +158 -0
  107. package/skills/workflow/investigating-bugs/condition-based-waiting.md +115 -0
  108. package/skills/workflow/investigating-bugs/defense-in-depth.md +122 -0
  109. package/skills/workflow/investigating-bugs/find-polluter.sh +63 -0
  110. package/skills/workflow/investigating-bugs/root-cause-tracing.md +169 -0
  111. package/skills/workflow/investigating-bugs/test-academic.md +14 -0
  112. package/skills/workflow/investigating-bugs/test-pressure-1.md +58 -0
  113. package/skills/workflow/investigating-bugs/test-pressure-2.md +68 -0
  114. package/skills/workflow/investigating-bugs/test-pressure-3.md +69 -0
  115. package/skills/workflow/playtesting-a-feature/SKILL.md +173 -0
  116. package/skills/{_meta → workflow}/skill-create/SKILL.md +32 -22
  117. package/skills/{_meta → workflow}/skill-improve/SKILL.md +9 -10
  118. package/skills/{_meta → workflow}/skill-test/SKILL.md +12 -12
  119. package/skills/{_meta → workflow}/using-summer/SKILL.md +5 -2
  120. package/skills/workflow/verification-before-completion/SKILL.md +139 -0
  121. package/skills/workflow/writing-plans/SKILL.md +152 -0
  122. package/skills/workflow/writing-plans/plan-document-reviewer-prompt.md +49 -0
  123. package/skills/workflow/writing-skills/SKILL.md +655 -0
  124. package/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
  125. package/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  126. package/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
  127. package/skills/workflow/writing-skills/persuasion-principles.md +187 -0
  128. package/skills/workflow/writing-skills/render-graphs.js +168 -0
  129. package/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
  130. package/dist/commands/assets.d.ts +0 -2
  131. package/dist/commands/assets.js +0 -189
  132. package/dist/commands/debug.d.ts +0 -2
  133. package/dist/commands/debug.js +0 -127
  134. package/dist/commands/doctor.test.d.ts +0 -1
  135. package/dist/commands/doctor.test.js +0 -29
  136. package/dist/commands/project.d.ts +0 -2
  137. package/dist/commands/project.js +0 -170
  138. package/dist/commands/scene.d.ts +0 -2
  139. package/dist/commands/scene.js +0 -290
  140. package/dist/lib/agent-config.test.d.ts +0 -1
  141. package/dist/lib/agent-config.test.js +0 -80
  142. package/dist/lib/format.test.d.ts +0 -1
  143. package/dist/lib/format.test.js +0 -36
  144. package/dist/lib/plugin-manifests.test.d.ts +0 -1
  145. package/dist/lib/plugin-manifests.test.js +0 -134
  146. package/dist/lib/with-engine.d.ts +0 -14
  147. package/dist/lib/with-engine.js +0 -109
  148. package/skills/_shared/collaborative-protocol.md +0 -50
  149. package/skills/_shared/gd-style.md +0 -121
  150. package/skills/_shared/godot-version.md +0 -32
  151. package/skills/_shared/mcp-tools-reference.md +0 -133
  152. package/skills/_shared/summer-folder.md +0 -44
  153. package/skills/_shared/template-registry.md +0 -40
  154. package/skills/_tests/runner.md +0 -90
  155. package/skills/_tests/specs/3d-lighting.md +0 -19
  156. package/skills/_tests/specs/art-direction.md +0 -115
  157. package/skills/_tests/specs/asset-strategy.md +0 -19
  158. package/skills/_tests/specs/audio-direction.md +0 -123
  159. package/skills/_tests/specs/brainstorm-game.md +0 -99
  160. package/skills/_tests/specs/debug.md +0 -94
  161. package/skills/_tests/specs/design-level.md +0 -113
  162. package/skills/_tests/specs/design-mechanic.md +0 -112
  163. package/skills/_tests/specs/design-npc.md +0 -143
  164. package/skills/_tests/specs/export-and-ship.md +0 -114
  165. package/skills/_tests/specs/fps-controller.md +0 -113
  166. package/skills/_tests/specs/gdscript-patterns.md +0 -19
  167. package/skills/_tests/specs/host-authoritative-state.md +0 -104
  168. package/skills/_tests/specs/make-game.md +0 -19
  169. package/skills/_tests/specs/peer-to-peer-multiplayer.md +0 -99
  170. package/skills/_tests/specs/play.md +0 -88
  171. package/skills/_tests/specs/scene-composition.md +0 -19
  172. package/skills/_tests/specs/setup-multiplayer.md +0 -106
  173. package/skills/_tests/specs/tune-performance.md +0 -104
  174. package/skills/_tests/specs/ui-basics.md +0 -19
  175. package/skills/_tests/specs/vfx.md +0 -116
  176. package/skills/catalog.yaml +0 -991
@@ -4,9 +4,15 @@ export const AGENT_CLIENTS = [
4
4
  "claude-code",
5
5
  "cursor",
6
6
  "windsurf",
7
+ "cline",
8
+ "roo-code",
9
+ "gemini",
10
+ "github-copilot",
11
+ "vscode-copilot",
12
+ "opencode",
7
13
  ];
8
14
  export const SKILL_CATEGORIES = [
9
- "_meta",
15
+ "workflow",
10
16
  "2d-assets",
11
17
  "3d-assets",
12
18
  "video",
@@ -33,9 +39,10 @@ export const SKILL_CATEGORIES = [
33
39
  ];
34
40
  const ALL_CLIENTS = AGENT_CLIENTS;
35
41
  /**
36
- * Skills with `status: HAVE` in catalog.yaml. The catalog is the planning source
37
- * of truth (~85 skills); this TS registry is the install-time list (only skills
38
- * with shippable content). When a NEXT skill ships, mirror it here.
42
+ * Install-time skill registry — every skill installable via `summer skills install`.
43
+ * Must stay in sync with `.claude-plugin/plugin.json` (and sibling manifests) for
44
+ * Claude Code / Cursor / Codex plugin discovery. The `plugin-manifests.test.ts`
45
+ * test fails the build if a skill exists on disk but not in the manifest.
39
46
  */
40
47
  export const SKILL_REGISTRY = [
41
48
  {
@@ -900,4 +907,99 @@ export const SKILL_REGISTRY = [
900
907
  ],
901
908
  testScenario: "User wants 'a magical glow on a rune' — skill creates OmniLight3D with pulse-on-sin script + GPUParticles3D motes, uses additive billboard material from building blocks.",
902
909
  },
910
+ {
911
+ name: "brainstorming",
912
+ category: "workflow",
913
+ public: true,
914
+ clients: ALL_CLIENTS,
915
+ recommended: true,
916
+ requiresMcpTools: [],
917
+ testScenario: "User says 'let's make a game' — skill explores intent, scope, and references through one-question-at-a-time dialogue before any code or scene work begins.",
918
+ },
919
+ {
920
+ name: "investigating-bugs",
921
+ category: "workflow",
922
+ public: true,
923
+ clients: ALL_CLIENTS,
924
+ recommended: true,
925
+ requiresMcpTools: [],
926
+ testScenario: "Encountering a bug whose first fix didn't stick — skill forces hypothesis-driven root cause analysis before another patch attempt.",
927
+ },
928
+ {
929
+ name: "verification-before-completion",
930
+ category: "workflow",
931
+ public: true,
932
+ clients: ALL_CLIENTS,
933
+ recommended: true,
934
+ requiresMcpTools: [],
935
+ testScenario: "About to claim a feature is done — skill blocks the claim until verification commands have been run and their output reviewed.",
936
+ },
937
+ {
938
+ name: "writing-plans",
939
+ category: "workflow",
940
+ public: true,
941
+ clients: ALL_CLIENTS,
942
+ recommended: false,
943
+ requiresMcpTools: [],
944
+ testScenario: "User has a multi-step spec — skill produces a written plan with checkpoints before any implementation begins.",
945
+ },
946
+ {
947
+ name: "dispatching-parallel-agents",
948
+ category: "workflow",
949
+ public: true,
950
+ clients: ALL_CLIENTS,
951
+ recommended: false,
952
+ requiresMcpTools: [],
953
+ testScenario: "Facing 2+ independent tasks — skill prepares isolated, self-contained agent prompts and dispatches them in parallel.",
954
+ },
955
+ {
956
+ name: "writing-skills",
957
+ category: "workflow",
958
+ public: true,
959
+ clients: ALL_CLIENTS,
960
+ recommended: false,
961
+ requiresMcpTools: [],
962
+ testScenario: "User wants a new Summer skill — skill walks through frontmatter, body structure, and the auto-trigger discipline required for the skill to fire reliably.",
963
+ },
964
+ {
965
+ name: "playtesting-a-feature",
966
+ category: "workflow",
967
+ public: true,
968
+ clients: ALL_CLIENTS,
969
+ recommended: true,
970
+ requiresMcpTools: [
971
+ "summer_play",
972
+ "summer_stop",
973
+ "summer_get_console",
974
+ "summer_get_debugger_errors",
975
+ "summer_get_diagnostics",
976
+ ],
977
+ testScenario: "Just finished a gameplay feature — skill drives a golden-path + edge-case playthrough with diagnostics capture before the feature can be marked done.",
978
+ },
979
+ {
980
+ name: "debugging-game-feel",
981
+ category: "workflow",
982
+ public: true,
983
+ clients: ALL_CLIENTS,
984
+ recommended: false,
985
+ requiresMcpTools: [
986
+ "summer_play",
987
+ "summer_stop",
988
+ "summer_inspect_node",
989
+ ],
990
+ testScenario: "User says 'the jump feels floaty' — skill isolates one variable at a time against a reference anchor, avoids compounding tweaks.",
991
+ },
992
+ {
993
+ name: "diagnosing-perf-regressions",
994
+ category: "workflow",
995
+ public: true,
996
+ clients: ALL_CLIENTS,
997
+ recommended: false,
998
+ requiresMcpTools: [
999
+ "summer_get_diagnostics",
1000
+ "summer_play",
1001
+ "summer_stop",
1002
+ ],
1003
+ testScenario: "FPS dropped from 60 to 30 — skill bisects recent changes, captures before/after diagnostics, identifies the regression cause before tuning.",
1004
+ },
903
1005
  ];
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Version drift detection helpers used by `summer doctor` and the MCP server
3
+ * boot path.
4
+ *
5
+ * Two checks are exposed:
6
+ * - cli-version-current: compares installed package.json version against the
7
+ * latest published `summer-engine` on the npm registry.
8
+ * - skills-version-stale: reads a `.summer-version` marker written into the
9
+ * skill install dir (per-agent) and compares against the current CLI.
10
+ *
11
+ * Both are designed to be silent on offline / network errors — we never want
12
+ * to false-alarm a user just because their wifi is flaky.
13
+ */
14
+ export interface SemverParts {
15
+ major: number;
16
+ minor: number;
17
+ patch: number;
18
+ }
19
+ export type DriftSeverity = "ok" | "warning" | "fail";
20
+ export interface DriftClassification {
21
+ severity: DriftSeverity;
22
+ reason: "current" | "ahead" | "patch-only" | "minor-drift" | "major-drift" | "behind";
23
+ }
24
+ export declare function parseSemver(version: string): SemverParts | null;
25
+ export declare function compareSemver(a: SemverParts, b: SemverParts): number;
26
+ /**
27
+ * Classify drift between an installed and a target version.
28
+ *
29
+ * ok installed >= target, OR installed is behind only on patch
30
+ * OR installed differs only in pre-release/build metadata.
31
+ * warning one minor version behind on the same major.
32
+ * fail two or more minors behind, or any major-version drift behind.
33
+ */
34
+ export declare function classifyDrift(installed: SemverParts, target: SemverParts): DriftClassification;
35
+ export interface RegistryFetchOptions {
36
+ /** Override the default fetch (used in tests). Must implement the global fetch shape. */
37
+ fetchImpl?: typeof fetch;
38
+ /** Override the timeout. */
39
+ timeoutMs?: number;
40
+ /** Override the URL (used in tests). */
41
+ url?: string;
42
+ }
43
+ export interface RegistryFetchResult {
44
+ ok: true;
45
+ version: string;
46
+ }
47
+ export interface RegistryFetchFailure {
48
+ ok: false;
49
+ reason: "registry-unreachable";
50
+ message?: string;
51
+ }
52
+ export declare function fetchLatestRegistryVersion(options?: RegistryFetchOptions): Promise<RegistryFetchResult | RegistryFetchFailure>;
53
+ /**
54
+ * Build the `cli-version-current` doctor check. Pure helper so tests can drive
55
+ * registry responses through `fetchImpl` rather than touching the network.
56
+ */
57
+ export interface CliVersionCheckInput {
58
+ installedVersion: string;
59
+ registry: RegistryFetchResult | RegistryFetchFailure;
60
+ }
61
+ export interface CliVersionCheckOutput {
62
+ status: DriftSeverity;
63
+ message: string;
64
+ details: Record<string, unknown>;
65
+ }
66
+ export declare function buildCliVersionCheck(input: CliVersionCheckInput): CliVersionCheckOutput;
67
+ export declare const SKILL_VERSION_MARKER_FILENAME = ".summer-version";
68
+ export interface SkillVersionMarker {
69
+ version: string;
70
+ installedAt: string;
71
+ }
72
+ export declare function readSkillMarker(dir: string): SkillVersionMarker | null;
73
+ export declare function writeSkillMarker(dir: string, version: string, now?: Date): string;
74
+ /**
75
+ * Candidate locations for skill-version markers. Listed roughly in order of
76
+ * how likely a Summer user is to have them populated. We probe every candidate,
77
+ * not just the first one — if multiple agents have markers, the most-stale one
78
+ * wins so we surface the worst case to the user.
79
+ */
80
+ export interface SkillMarkerCandidate {
81
+ agent: string;
82
+ dir: string;
83
+ }
84
+ export declare function defaultSkillMarkerCandidates(): SkillMarkerCandidate[];
85
+ export interface SkillsVersionCheckInput {
86
+ installedCliVersion: string;
87
+ candidates: SkillMarkerCandidate[];
88
+ }
89
+ export interface SkillsVersionCheckOutput {
90
+ status: DriftSeverity;
91
+ message: string;
92
+ details: Record<string, unknown>;
93
+ }
94
+ export declare function buildSkillsVersionCheck(input: SkillsVersionCheckInput): SkillsVersionCheckOutput;
95
+ /**
96
+ * Helper used by the MCP server boot path to render a one-line drift notice.
97
+ * Returns null when no notice is needed (fresh, ahead, patch-only, offline).
98
+ */
99
+ export interface BootDriftNotice {
100
+ installedVersion: string;
101
+ latestVersion: string;
102
+ agent?: string;
103
+ text: string;
104
+ }
105
+ export declare function buildBootDriftNotice(installedVersion: string, registry: RegistryFetchResult | RegistryFetchFailure, agent?: string): BootDriftNotice | null;
106
+ export declare function markerMtime(dir: string): number | null;
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Version drift detection helpers used by `summer doctor` and the MCP server
3
+ * boot path.
4
+ *
5
+ * Two checks are exposed:
6
+ * - cli-version-current: compares installed package.json version against the
7
+ * latest published `summer-engine` on the npm registry.
8
+ * - skills-version-stale: reads a `.summer-version` marker written into the
9
+ * skill install dir (per-agent) and compares against the current CLI.
10
+ *
11
+ * Both are designed to be silent on offline / network errors — we never want
12
+ * to false-alarm a user just because their wifi is flaky.
13
+ */
14
+ import { existsSync, readFileSync, writeFileSync, statSync } from "node:fs";
15
+ import { homedir, platform } from "node:os";
16
+ import { join } from "node:path";
17
+ const NPM_REGISTRY_LATEST_URL = "https://registry.npmjs.org/summer-engine/latest";
18
+ const REGISTRY_FETCH_TIMEOUT_MS = 3000;
19
+ export function parseSemver(version) {
20
+ if (!version)
21
+ return null;
22
+ // Strip any leading "v" and trim build / pre-release tags after the first hyphen.
23
+ const cleaned = version.trim().replace(/^v/, "").split(/[-+]/)[0];
24
+ const match = cleaned.match(/^(\d+)\.(\d+)\.(\d+)$/);
25
+ if (!match)
26
+ return null;
27
+ const [, maj, min, pat] = match;
28
+ return {
29
+ major: Number(maj),
30
+ minor: Number(min),
31
+ patch: Number(pat),
32
+ };
33
+ }
34
+ export function compareSemver(a, b) {
35
+ if (a.major !== b.major)
36
+ return a.major - b.major;
37
+ if (a.minor !== b.minor)
38
+ return a.minor - b.minor;
39
+ return a.patch - b.patch;
40
+ }
41
+ /**
42
+ * Classify drift between an installed and a target version.
43
+ *
44
+ * ok installed >= target, OR installed is behind only on patch
45
+ * OR installed differs only in pre-release/build metadata.
46
+ * warning one minor version behind on the same major.
47
+ * fail two or more minors behind, or any major-version drift behind.
48
+ */
49
+ export function classifyDrift(installed, target) {
50
+ const cmp = compareSemver(installed, target);
51
+ if (cmp === 0)
52
+ return { severity: "ok", reason: "current" };
53
+ if (cmp > 0)
54
+ return { severity: "ok", reason: "ahead" };
55
+ // installed < target
56
+ if (installed.major < target.major) {
57
+ return { severity: "fail", reason: "major-drift" };
58
+ }
59
+ if (installed.minor === target.minor) {
60
+ // Same major + minor, target patch is higher → patch-only drift.
61
+ return { severity: "ok", reason: "patch-only" };
62
+ }
63
+ const minorDelta = target.minor - installed.minor;
64
+ if (minorDelta >= 2) {
65
+ return { severity: "fail", reason: "minor-drift" };
66
+ }
67
+ return { severity: "warning", reason: "minor-drift" };
68
+ }
69
+ export async function fetchLatestRegistryVersion(options = {}) {
70
+ const url = options.url ?? NPM_REGISTRY_LATEST_URL;
71
+ const timeoutMs = options.timeoutMs ?? REGISTRY_FETCH_TIMEOUT_MS;
72
+ const fetchImpl = options.fetchImpl ?? globalThis.fetch;
73
+ if (!fetchImpl) {
74
+ return { ok: false, reason: "registry-unreachable", message: "fetch unavailable" };
75
+ }
76
+ try {
77
+ const response = await fetchImpl(url, {
78
+ signal: AbortSignal.timeout(timeoutMs),
79
+ headers: { accept: "application/json" },
80
+ });
81
+ if (!response.ok) {
82
+ return {
83
+ ok: false,
84
+ reason: "registry-unreachable",
85
+ message: `registry responded ${response.status}`,
86
+ };
87
+ }
88
+ const body = (await response.json());
89
+ if (typeof body.version !== "string") {
90
+ return {
91
+ ok: false,
92
+ reason: "registry-unreachable",
93
+ message: "registry payload missing version",
94
+ };
95
+ }
96
+ return { ok: true, version: body.version };
97
+ }
98
+ catch (error) {
99
+ return {
100
+ ok: false,
101
+ reason: "registry-unreachable",
102
+ message: error instanceof Error ? error.message : String(error),
103
+ };
104
+ }
105
+ }
106
+ const RECOMMENDED_REFRESH_COMMAND = "npx clear-npx-cache && npx -y summer-engine@latest setup <agent> --yes --force";
107
+ export function buildCliVersionCheck(input) {
108
+ if (!input.registry.ok) {
109
+ return {
110
+ status: "ok",
111
+ message: "registry unreachable, skipping check",
112
+ details: {
113
+ installedVersion: input.installedVersion,
114
+ reason: input.registry.reason,
115
+ },
116
+ };
117
+ }
118
+ const installed = parseSemver(input.installedVersion);
119
+ const latest = parseSemver(input.registry.version);
120
+ if (!installed || !latest) {
121
+ // If we can't parse either side, don't false-alarm.
122
+ return {
123
+ status: "ok",
124
+ message: "could not compare versions",
125
+ details: {
126
+ installedVersion: input.installedVersion,
127
+ latestVersion: input.registry.version,
128
+ reason: "unparseable-version",
129
+ },
130
+ };
131
+ }
132
+ const drift = classifyDrift(installed, latest);
133
+ const baseDetails = {
134
+ installedVersion: input.installedVersion,
135
+ latestVersion: input.registry.version,
136
+ };
137
+ switch (drift.severity) {
138
+ case "ok":
139
+ return {
140
+ status: "ok",
141
+ message: drift.reason === "patch-only"
142
+ ? `v${input.installedVersion} (latest ${input.registry.version}, patch-only)`
143
+ : drift.reason === "ahead"
144
+ ? `v${input.installedVersion} (ahead of latest ${input.registry.version})`
145
+ : `v${input.installedVersion}`,
146
+ details: { ...baseDetails, reason: drift.reason },
147
+ };
148
+ case "warning":
149
+ return {
150
+ status: "warning",
151
+ message: `v${input.installedVersion} → v${input.registry.version} available`,
152
+ details: {
153
+ ...baseDetails,
154
+ recommendedAction: RECOMMENDED_REFRESH_COMMAND,
155
+ },
156
+ };
157
+ case "fail":
158
+ return {
159
+ status: "fail",
160
+ message: `v${input.installedVersion} is significantly behind v${input.registry.version}`,
161
+ details: {
162
+ ...baseDetails,
163
+ recommendedAction: RECOMMENDED_REFRESH_COMMAND,
164
+ },
165
+ };
166
+ }
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // Skill marker (.summer-version) helpers
170
+ // ---------------------------------------------------------------------------
171
+ export const SKILL_VERSION_MARKER_FILENAME = ".summer-version";
172
+ export function readSkillMarker(dir) {
173
+ const path = join(dir, SKILL_VERSION_MARKER_FILENAME);
174
+ if (!existsSync(path))
175
+ return null;
176
+ try {
177
+ const raw = readFileSync(path, "utf-8");
178
+ const parsed = JSON.parse(raw);
179
+ if (typeof parsed.version !== "string" || typeof parsed.installedAt !== "string") {
180
+ return null;
181
+ }
182
+ return { version: parsed.version, installedAt: parsed.installedAt };
183
+ }
184
+ catch {
185
+ return null;
186
+ }
187
+ }
188
+ export function writeSkillMarker(dir, version, now = new Date()) {
189
+ const path = join(dir, SKILL_VERSION_MARKER_FILENAME);
190
+ const payload = {
191
+ version,
192
+ installedAt: now.toISOString(),
193
+ };
194
+ writeFileSync(path, JSON.stringify(payload, null, 2) + "\n", "utf-8");
195
+ return path;
196
+ }
197
+ export function defaultSkillMarkerCandidates() {
198
+ const home = homedir();
199
+ const candidates = [
200
+ { agent: "claude-code", dir: join(home, ".claude", "skills") },
201
+ { agent: "codex", dir: join(home, ".agents", "skills") },
202
+ { agent: "summer", dir: join(home, ".summer", "skills") },
203
+ { agent: "cline", dir: join(home, "Documents", "Cline", "Rules") },
204
+ { agent: "roo-code", dir: join(home, "Documents", "Roo", "Rules") },
205
+ {
206
+ agent: "gemini",
207
+ dir: join(home, ".gemini", "extensions", "summer-engine", "skills"),
208
+ },
209
+ ];
210
+ // OpenCode user-scope agent dir is OS-dependent.
211
+ if (platform() === "win32") {
212
+ const appData = process.env.APPDATA ?? join(home, "AppData", "Roaming");
213
+ candidates.push({
214
+ agent: "opencode",
215
+ dir: join(appData, "opencode", "agents", "summer"),
216
+ });
217
+ }
218
+ else {
219
+ const xdg = process.env.XDG_CONFIG_HOME ?? join(home, ".config");
220
+ candidates.push({
221
+ agent: "opencode",
222
+ dir: join(xdg, "opencode", "agents", "summer"),
223
+ });
224
+ }
225
+ return candidates;
226
+ }
227
+ export function buildSkillsVersionCheck(input) {
228
+ const cliParsed = parseSemver(input.installedCliVersion);
229
+ if (!cliParsed) {
230
+ return {
231
+ status: "ok",
232
+ message: "could not parse CLI version",
233
+ details: { reason: "unparseable-cli-version" },
234
+ };
235
+ }
236
+ const resolutions = [];
237
+ for (const candidate of input.candidates) {
238
+ const marker = readSkillMarker(candidate.dir);
239
+ if (!marker)
240
+ continue;
241
+ const markerSemver = parseSemver(marker.version);
242
+ if (!markerSemver)
243
+ continue;
244
+ const drift = classifyDrift(markerSemver, cliParsed);
245
+ const installedAtMs = Date.parse(marker.installedAt);
246
+ resolutions.push({
247
+ agent: candidate.agent,
248
+ dir: candidate.dir,
249
+ marker,
250
+ drift,
251
+ installedAtMs: Number.isFinite(installedAtMs) ? installedAtMs : 0,
252
+ });
253
+ }
254
+ if (resolutions.length === 0) {
255
+ return {
256
+ status: "ok",
257
+ message: "no skill marker yet (treated as fresh install)",
258
+ details: { reason: "no-marker" },
259
+ };
260
+ }
261
+ // Pick the worst (most-stale) marker. Severity rank fail > warning > ok;
262
+ // tiebreak by oldest installedAt.
263
+ const severityRank = { fail: 2, warning: 1, ok: 0 };
264
+ const worst = [...resolutions].sort((a, b) => {
265
+ const sevDiff = severityRank[b.drift.severity] - severityRank[a.drift.severity];
266
+ if (sevDiff !== 0)
267
+ return sevDiff;
268
+ return a.installedAtMs - b.installedAtMs;
269
+ })[0];
270
+ const baseDetails = {
271
+ agent: worst.agent,
272
+ skillsDir: worst.dir,
273
+ markerVersion: worst.marker.version,
274
+ installedAt: worst.marker.installedAt,
275
+ cliVersion: input.installedCliVersion,
276
+ };
277
+ switch (worst.drift.severity) {
278
+ case "ok":
279
+ return {
280
+ status: "ok",
281
+ message: `skills v${worst.marker.version} match CLI`,
282
+ details: baseDetails,
283
+ };
284
+ case "warning":
285
+ return {
286
+ status: "warning",
287
+ message: `skills v${worst.marker.version} are one minor version behind v${input.installedCliVersion}`,
288
+ details: {
289
+ ...baseDetails,
290
+ recommendedAction: skillsRefreshCommand(worst.agent),
291
+ },
292
+ };
293
+ case "fail":
294
+ return {
295
+ status: "fail",
296
+ message: `skills v${worst.marker.version} are significantly behind v${input.installedCliVersion}`,
297
+ details: {
298
+ ...baseDetails,
299
+ recommendedAction: skillsRefreshCommand(worst.agent),
300
+ },
301
+ };
302
+ }
303
+ }
304
+ function skillsRefreshCommand(agent) {
305
+ return `npx clear-npx-cache && npx -y summer-engine@latest setup ${agent} --yes --force`;
306
+ }
307
+ export function buildBootDriftNotice(installedVersion, registry, agent) {
308
+ if (!registry.ok)
309
+ return null;
310
+ const installed = parseSemver(installedVersion);
311
+ const latest = parseSemver(registry.version);
312
+ if (!installed || !latest)
313
+ return null;
314
+ const drift = classifyDrift(installed, latest);
315
+ if (drift.severity === "ok")
316
+ return null;
317
+ const agentSlug = agent ?? "<agent>";
318
+ const text = `[summer] CLI ${installedVersion} — latest ${registry.version}; ` +
319
+ `run \`npx clear-npx-cache && npx -y summer-engine@latest setup ${agentSlug} --yes --force\` to update.`;
320
+ return {
321
+ installedVersion,
322
+ latestVersion: registry.version,
323
+ agent,
324
+ text,
325
+ };
326
+ }
327
+ // Re-export for tests / callers that want to know the install marker's mtime.
328
+ export function markerMtime(dir) {
329
+ const path = join(dir, SKILL_VERSION_MARKER_FILENAME);
330
+ if (!existsSync(path))
331
+ return null;
332
+ try {
333
+ return statSync(path).mtimeMs;
334
+ }
335
+ catch {
336
+ return null;
337
+ }
338
+ }
@@ -1,4 +1,6 @@
1
1
  import { EngineApiClient } from "../lib/api-client.js";
2
+ import { type BootDriftNotice } from "../lib/version-check.js";
3
+ export declare function getCachedBootDriftNotice(): BootDriftNotice | null;
2
4
  export declare function getClient(): Promise<EngineApiClient>;
3
5
  export declare function resetClient(): void;
4
6
  export declare function startMcpServer(): Promise<void>;
@@ -7,8 +7,25 @@ import { registerDebugTools } from "./tools/debug-tools.js";
7
7
  import { registerProjectTools } from "./tools/project-tools.js";
8
8
  import { registerAssetTools } from "./tools/asset-tools.js";
9
9
  import { registerGenerateTools } from "./tools/generate-tools.js";
10
+ import { buildBootDriftNotice, fetchLatestRegistryVersion, } from "../lib/version-check.js";
10
11
  const require = createRequire(import.meta.url);
11
12
  const { version } = require("../../package.json");
13
+ let cachedBootDriftNotice = null;
14
+ export function getCachedBootDriftNotice() {
15
+ return cachedBootDriftNotice;
16
+ }
17
+ /**
18
+ * Fire-and-forget probe of the npm registry on MCP boot. Caches the result for
19
+ * the session. We never await this in startup so a slow / offline registry
20
+ * never blocks tool registration. Tools that surface the notice should call
21
+ * `getCachedBootDriftNotice()` and skip silently when null.
22
+ */
23
+ async function probeBootDrift() {
24
+ const registry = await fetchLatestRegistryVersion();
25
+ // Agent is unknown at the MCP layer; use a generic placeholder. Doctor still
26
+ // prescribes the correct agent-aware command via setup recommendations.
27
+ cachedBootDriftNotice = buildBootDriftNotice(version, registry);
28
+ }
12
29
  let cachedClient = null;
13
30
  export async function getClient() {
14
31
  if (cachedClient) {
@@ -26,16 +43,80 @@ export async function getClient() {
26
43
  export function resetClient() {
27
44
  cachedClient = null;
28
45
  }
46
+ /**
47
+ * Passive result-size observability. Monkey-patches server.tool() so every
48
+ * registered handler's serialized text payload is measured and logged to
49
+ * stderr in the existing `[summer-mcp]` JSON shape. The result itself is
50
+ * never modified — external coding agents (Claude Code, Cursor, Codex)
51
+ * manage their own context budgets, and silently truncating their tool
52
+ * results is the wrong shape (see #102 revert). If a specific tool turns
53
+ * out to produce pathological payloads in practice, the right fix is
54
+ * adding range/depth/filter parameters to that tool — not a blanket cap.
55
+ */
56
+ function installResultSizeLogger(server) {
57
+ const original = server.tool.bind(server);
58
+ server.tool = function patchedTool(...args) {
59
+ if (args.length < 2)
60
+ return original(...args);
61
+ const name = args[0];
62
+ if (typeof name !== "string")
63
+ return original(...args);
64
+ const lastIdx = args.length - 1;
65
+ const handler = args[lastIdx];
66
+ if (typeof handler !== "function")
67
+ return original(...args);
68
+ const wrapped = async (...handlerArgs) => {
69
+ const result = await handler(...handlerArgs);
70
+ try {
71
+ if (result &&
72
+ typeof result === "object" &&
73
+ "content" in result &&
74
+ Array.isArray(result.content)) {
75
+ let chars = 0;
76
+ for (const entry of result
77
+ .content) {
78
+ if (entry && entry.type === "text" && typeof entry.text === "string") {
79
+ chars += entry.text.length;
80
+ }
81
+ }
82
+ const bytes = Buffer.byteLength(result.content
83
+ .filter((e) => e && e.type === "text" && typeof e.text === "string")
84
+ .map((e) => e.text)
85
+ .join(""), "utf8");
86
+ const line = JSON.stringify({
87
+ event: "mcp:result_size",
88
+ tool_name: name,
89
+ chars,
90
+ bytes,
91
+ });
92
+ process.stderr.write(`[summer-mcp] ${line}\n`);
93
+ }
94
+ }
95
+ catch {
96
+ // Telemetry must never break the tool call.
97
+ }
98
+ return result;
99
+ };
100
+ const newArgs = [...args];
101
+ newArgs[lastIdx] = wrapped;
102
+ return original(...newArgs);
103
+ };
104
+ }
29
105
  export async function startMcpServer() {
30
106
  const server = new McpServer({
31
107
  name: "summer-engine",
32
108
  version,
33
109
  });
110
+ // Passive observability: log result-size to stderr but do not modify
111
+ // results. See installResultSizeLogger above.
112
+ installResultSizeLogger(server);
34
113
  registerSceneTools(server);
35
114
  registerDebugTools(server);
36
115
  registerProjectTools(server);
37
116
  registerAssetTools(server);
38
117
  registerGenerateTools(server);
118
+ // Fire-and-forget — never block tool registration on the npm registry.
119
+ void probeBootDrift();
39
120
  const transport = new StdioServerTransport();
40
121
  await server.connect(transport);
41
122
  process.stderr.write(`[summer-mcp] MCP server running v${version}.\n`);