summer-engine 4.0.0 → 4.2.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 (199) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -2
  3. package/.codex-plugin/plugin.json +15 -11
  4. package/.cursor-plugin/plugin.json +5 -3
  5. package/.factory-plugin/plugin.json +3 -2
  6. package/AGENTS.md +1 -1
  7. package/CHANGELOG.md +37 -0
  8. package/README.md +20 -9
  9. package/dist/cli/commands/config.js +3 -2
  10. package/dist/cli/commands/create.js +12 -3
  11. package/dist/cli/commands/list.d.ts +5 -0
  12. package/dist/cli/commands/list.js +30 -13
  13. package/dist/cli/commands/memory.js +6 -2
  14. package/dist/cli/commands/ops.d.ts +20 -3
  15. package/dist/cli/commands/ops.js +55 -27
  16. package/dist/cli/commands/setup.d.ts +2 -0
  17. package/dist/cli/commands/setup.js +219 -16
  18. package/dist/cli/commands/skills.js +20 -7
  19. package/dist/cli/commands/status.d.ts +26 -0
  20. package/dist/cli/commands/status.js +36 -21
  21. package/dist/core/capabilities/apps.d.ts +10 -10
  22. package/dist/core/capabilities/audit/args.d.ts +6 -6
  23. package/dist/core/capabilities/fabricate-mesh.d.ts +2 -2
  24. package/dist/core/capabilities/game-task-plan.d.ts +2 -2
  25. package/dist/core/capabilities/hdri-import.d.ts +2 -2
  26. package/dist/core/capabilities/motion-generation.d.ts +2 -2
  27. package/dist/core/capabilities/placement.d.ts +23 -23
  28. package/dist/core/capabilities/runtime-control.d.ts +38 -38
  29. package/dist/core/capabilities/seeing/args.d.ts +16 -16
  30. package/dist/core/capabilities/ui-control.d.ts +2 -2
  31. package/dist/core/capabilities/world-snapshot.d.ts +4 -4
  32. package/dist/core/library-search.d.ts +2 -2
  33. package/dist/core/ops/areas.d.ts +11 -0
  34. package/dist/core/ops/areas.js +24 -0
  35. package/dist/core/ops/catalog.d.ts +4 -0
  36. package/dist/core/ops/catalog.js +13 -1
  37. package/dist/core/ops/cloud.d.ts +10 -2
  38. package/dist/core/ops/cloud.js +51 -28
  39. package/dist/core/ops/contract.d.ts +2 -1
  40. package/dist/core/ops/contract.js +2 -10
  41. package/dist/core/ops/public-catalog.d.ts +48 -0
  42. package/dist/core/ops/public-catalog.js +56 -0
  43. package/dist/core/ops/search.d.ts +0 -1
  44. package/dist/core/ops/search.js +169 -37
  45. package/dist/installer/agent-config.js +5 -0
  46. package/dist/installer/agent-table.d.ts +9 -0
  47. package/dist/installer/agent-table.js +23 -2
  48. package/dist/installer/cli-permissions.d.ts +65 -0
  49. package/dist/installer/cli-permissions.js +182 -0
  50. package/dist/installer/setup.d.ts +2 -0
  51. package/dist/installer/setup.js +3 -0
  52. package/dist/installer/skills-refresh.d.ts +29 -0
  53. package/dist/installer/skills-refresh.js +261 -0
  54. package/dist/installer/version-check.d.ts +22 -2
  55. package/dist/installer/version-check.js +66 -9
  56. package/dist/mcp/ops-surface.js +16 -6
  57. package/dist/mcp/server.d.ts +10 -0
  58. package/dist/mcp/server.js +32 -3
  59. package/dist/project-memory/agent-instructions.d.ts +47 -0
  60. package/dist/project-memory/agent-instructions.js +226 -0
  61. package/gemini-extension.json +3 -2
  62. package/integrations/README.md +1 -1
  63. package/integrations/claude/README.md +6 -0
  64. package/integrations/claude/manifest-target.json +4 -0
  65. package/integrations/gemini/README.md +11 -0
  66. package/integrations/windsurf/README.md +1 -1
  67. package/library/skills/3d-lighting/SKILL.md +16 -0
  68. package/library/skills/README.md +5 -4
  69. package/library/skills/adaptive-music/SKILL.md +21 -0
  70. package/library/skills/agent-playtesting/SKILL.md +22 -0
  71. package/library/skills/ai-characters-in-games/SKILL.md +15 -0
  72. package/library/skills/ambient-bed/SKILL.md +16 -0
  73. package/library/skills/animated-loop/SKILL.md +19 -0
  74. package/library/skills/animation-tree/SKILL.md +26 -0
  75. package/library/skills/art-direction/SKILL.md +14 -0
  76. package/library/skills/asset-strategy/SKILL.md +30 -0
  77. package/library/skills/audio-direction/SKILL.md +16 -0
  78. package/library/skills/auto-fire-targeting/SKILL.md +14 -0
  79. package/library/skills/brainstorm-game/SKILL.md +13 -0
  80. package/library/skills/browse-templates/SKILL.md +10 -0
  81. package/library/skills/camera-rigs/SKILL.md +20 -0
  82. package/library/skills/character-animation-wiring/SKILL.md +24 -0
  83. package/library/skills/character-model/SKILL.md +28 -0
  84. package/library/skills/character-movement/SKILL.md +24 -0
  85. package/library/skills/character-portrait/SKILL.md +15 -0
  86. package/library/skills/cinematic-cutscene/SKILL.md +21 -0
  87. package/library/skills/combat-basics/SKILL.md +19 -0
  88. package/library/skills/compatibility-renderer-traps/SKILL.md +11 -0
  89. package/library/skills/concept-art/SKILL.md +13 -0
  90. package/library/skills/concept-to-level/SKILL.md +20 -1
  91. package/library/skills/concept-to-level/resource.yaml +2 -2
  92. package/library/skills/create-asset-sheet/SKILL.md +18 -0
  93. package/library/skills/debug/SKILL.md +32 -0
  94. package/library/skills/debugging-game-feel/SKILL.md +12 -0
  95. package/library/skills/design-level/SKILL.md +17 -0
  96. package/library/skills/design-mechanic/SKILL.md +17 -0
  97. package/library/skills/design-npc/SKILL.md +21 -0
  98. package/library/skills/diagnosing-perf-regressions/SKILL.md +12 -0
  99. package/library/skills/driving-the-editor-ui/SKILL.md +23 -0
  100. package/library/skills/environment-kit/SKILL.md +23 -0
  101. package/library/skills/export-and-ship/SKILL.md +23 -0
  102. package/library/skills/fabricating-assets/SKILL.md +23 -0
  103. package/library/skills/facial-and-lipsync/SKILL.md +22 -0
  104. package/library/skills/fps-controller/SKILL.md +17 -0
  105. package/library/skills/game-feel/SKILL.md +26 -0
  106. package/library/skills/gameskill/SKILL.md +11 -0
  107. package/library/skills/gdscript-patterns/SKILL.md +15 -0
  108. package/library/skills/generate-motion/SKILL.md +21 -0
  109. package/library/skills/godogen-visual-proof-game-generation/SKILL.md +17 -0
  110. package/library/skills/grow-analytics/SKILL.md +10 -0
  111. package/library/skills/headless-scripting/SKILL.md +13 -0
  112. package/library/skills/in-game-purchases/SKILL.md +11 -0
  113. package/library/skills/instantiate-asset-pack/SKILL.md +17 -0
  114. package/library/skills/make-game/SKILL.md +29 -0
  115. package/library/skills/multiplayer/SKILL.md +12 -0
  116. package/library/skills/multiplayer-movement/SKILL.md +18 -0
  117. package/library/skills/multiplayer-project/SKILL.md +17 -0
  118. package/library/skills/multiplayer-publish/SKILL.md +17 -0
  119. package/library/skills/multiplayer-state/SKILL.md +16 -0
  120. package/library/skills/multiplayer-testing/SKILL.md +15 -0
  121. package/library/skills/music-track/SKILL.md +16 -0
  122. package/library/skills/navigate-summer/SKILL.md +15 -0
  123. package/library/skills/new-project/SKILL.md +11 -0
  124. package/library/skills/organic-model/SKILL.md +24 -0
  125. package/library/skills/pixel-art/SKILL.md +15 -0
  126. package/library/skills/play/SKILL.md +18 -0
  127. package/library/skills/playtesting-a-feature/SKILL.md +23 -0
  128. package/library/skills/procedural-animation/SKILL.md +18 -0
  129. package/library/skills/prop-model/SKILL.md +23 -0
  130. package/library/skills/publish-your-game/SKILL.md +27 -0
  131. package/library/skills/release-readiness/SKILL.md +12 -0
  132. package/library/skills/remote-deploy/SKILL.md +14 -0
  133. package/library/skills/retarget/SKILL.md +20 -0
  134. package/library/skills/running-in-the-cloud/SKILL.md +10 -0
  135. package/library/skills/save-load/SKILL.md +15 -0
  136. package/library/skills/scene-composition/SKILL.md +17 -0
  137. package/library/skills/scene-hierarchy-design/SKILL.md +24 -0
  138. package/library/skills/scene-scripting/SKILL.md +30 -0
  139. package/library/skills/scene-to-level/SKILL.md +22 -0
  140. package/library/skills/skill-test/SKILL.md +10 -0
  141. package/library/skills/skybox-panorama/SKILL.md +20 -0
  142. package/library/skills/sound-effect/SKILL.md +23 -0
  143. package/library/skills/spatial-placement/SKILL.md +30 -0
  144. package/library/skills/sprite-sheet/SKILL.md +23 -0
  145. package/library/skills/store-art/SKILL.md +11 -0
  146. package/library/skills/store-listing/SKILL.md +10 -0
  147. package/library/skills/summer-analytics/SKILL.md +15 -0
  148. package/library/skills/summer-apps/SKILL.md +29 -0
  149. package/library/skills/summer-assets/SKILL.md +31 -1
  150. package/library/skills/summer-assets/resource.yaml +2 -2
  151. package/library/skills/summer-cli/SKILL.md +133 -0
  152. package/library/skills/summer-cli/resource.yaml +31 -0
  153. package/library/skills/summer-friends/SKILL.md +14 -0
  154. package/library/skills/summer-leaderboards/SKILL.md +16 -0
  155. package/library/skills/summer-match-results/SKILL.md +17 -0
  156. package/library/skills/summer-matchmaking/SKILL.md +17 -0
  157. package/library/skills/summer-parties/SKILL.md +14 -0
  158. package/library/skills/summer-player-data/SKILL.md +14 -0
  159. package/library/skills/summer-store/SKILL.md +15 -0
  160. package/library/skills/summer-world-chat/SKILL.md +14 -0
  161. package/library/skills/summer-world-saves/SKILL.md +14 -0
  162. package/library/skills/text-to-motion/SKILL.md +15 -0
  163. package/library/skills/tileable-texture/SKILL.md +18 -0
  164. package/library/skills/trailer-shot/SKILL.md +18 -0
  165. package/library/skills/tune-performance/SKILL.md +26 -0
  166. package/library/skills/ui-basics/SKILL.md +14 -0
  167. package/library/skills/ui-graphics/SKILL.md +17 -0
  168. package/library/skills/use-widget-asset/SKILL.md +14 -0
  169. package/library/skills/using-summer/SKILL.md +26 -0
  170. package/library/skills/using-summer/resource.yaml +2 -1
  171. package/library/skills/vehicle-model/SKILL.md +27 -0
  172. package/library/skills/verifying-scenes/SKILL.md +29 -0
  173. package/library/skills/vfx-dissolve/SKILL.md +19 -0
  174. package/library/skills/vfx-fire/SKILL.md +26 -0
  175. package/library/skills/vfx-hit-spark/SKILL.md +23 -0
  176. package/library/skills/vfx-lightning/SKILL.md +23 -0
  177. package/library/skills/vfx-magic-glow/SKILL.md +23 -0
  178. package/library/skills/vfx-muzzle-flash/SKILL.md +23 -0
  179. package/library/skills/vfx-smoke/SKILL.md +26 -0
  180. package/library/skills/vfx-water-ripple/SKILL.md +23 -0
  181. package/library/skills/voice-line/SKILL.md +16 -0
  182. package/library/skills/web-and-touch-input/SKILL.md +19 -0
  183. package/library/skills/web-compatibility-look/SKILL.md +20 -0
  184. package/library/skills/world-building-3d/SKILL.md +28 -0
  185. package/package.json +3 -2
  186. package/registry/generated/counts.json +2 -2
  187. package/registry/generated/gemini-extension.json +3 -2
  188. package/registry/generated/index.json +157 -118
  189. package/registry/generated/marketplace.claude.json +2 -2
  190. package/registry/generated/plugin.claude-directory.json +168 -0
  191. package/registry/generated/plugin.claude.json +3 -2
  192. package/registry/generated/plugin.codex.json +15 -11
  193. package/registry/generated/plugin.cursor.json +5 -3
  194. package/registry/generated/plugin.factory.json +3 -2
  195. package/registry/generated/server.json +11 -2
  196. package/registry/generated/skills-index.md +5 -4
  197. package/registry/generated/skills-registry.json +16 -2
  198. package/registry/operations/legacy-names.json +35 -0
  199. package/registry/operations/public-catalog.json +33160 -0
@@ -0,0 +1,261 @@
1
+ import { closeSync, cpSync, existsSync, lstatSync, openSync, readdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { getSkillRegistry, resolveSkillDir } from "../core/skills-registry.js";
4
+ import { allAgentSpecs, defaultPathContext, resolveSkillPath } from "./agent-table.js";
5
+ import { compareSemver, parseSemver, readKeptSkills, readSkillMarker, SKILL_VERSION_MARKER_FILENAME } from "./version-check.js";
6
+ /**
7
+ * After an upgrade, bring the Summer skills already installed on this
8
+ * computer up to this CLI's version. Skills written for 3.x name 3.x tools,
9
+ * and while their marker says 3.x the MCP server keeps the legacy toolset on
10
+ * (legacy-toolset.ts), which gives back most of 4.0's smaller tool list.
11
+ *
12
+ * Only what Summer installed and nobody changed is replaced, in a skills
13
+ * folder that carries Summer's `.summer-version` marker older than this CLI:
14
+ * a real folder (not a link) whose `resource.yaml` says `id: skill/<name>`
15
+ * (every Summer release ships it; other packs reuse names such as
16
+ * `brainstorming`), with no file changed after the marker was written, and
17
+ * not one an earlier refresh already left for that reason (the marker lists
18
+ * them, so the protection lasts until `summer setup --force`). A copy that
19
+ * already matches the library counts as current, not as changed. Each
20
+ * replacement is a copy beside it and two renames, so a kill or a copy error
21
+ * leaves the old skill in place. One process per folder (a lock file).
22
+ * Nothing is removed. Missing library skills are added only to a folder that
23
+ * holds nearly the whole library, so an assets-only install stays one.
24
+ * Rule-file installs (Cline, Roo Code, old Cursor rules) keep the refresh
25
+ * command `summer doctor` prints.
26
+ */
27
+ const LOCK_FILE = ".summer-refresh.lock";
28
+ const STALE_LOCK_MS = 60_000;
29
+ /** Setup writes the marker right after its copy; a file changed later than this was changed by someone. */
30
+ const EDIT_GRACE_MS = 60_000;
31
+ /** A folder holding this share of the bundled library is a full install: new library skills join it. */
32
+ const FULL_INSTALL_SHARE = 0.9;
33
+ const TEMP_PREFIX = ".summer-refresh-";
34
+ const OLD_PREFIX = ".summer-old-";
35
+ /** User-scope skill folders with one folder per skill, each listed once. */
36
+ export function skillFolderLocations() {
37
+ const ctx = defaultPathContext();
38
+ const dirs = new Set([join(ctx.home, ".summer", "skills")]);
39
+ for (const spec of allAgentSpecs()) {
40
+ const home = resolveSkillPath(spec, "user", ctx);
41
+ if (home?.kind === "skill-dir")
42
+ dirs.add(home.path);
43
+ }
44
+ return [...dirs];
45
+ }
46
+ const isPrerelease = (version) => /^\d+\.\d+\.\d+-/.test(version.trim());
47
+ /** Refresh when the marker is older, or a pre-release of this release (4.2.0-next.1 -> 4.2.0, never back). */
48
+ function isBehind(marker, cliVersion) {
49
+ const installed = parseSemver(marker.version);
50
+ const current = parseSemver(cliVersion);
51
+ if (!installed || !current)
52
+ return false;
53
+ const order = compareSemver(installed, current);
54
+ return order < 0 || (order === 0 && isPrerelease(marker.version) && !isPrerelease(cliVersion));
55
+ }
56
+ const errorCode = (error) => error?.code ?? (error instanceof Error ? error.message : String(error));
57
+ /** Summer's own copy: a real folder (not a link) whose resource.yaml names this skill. */
58
+ function isSummersSkill(dest, name) {
59
+ try {
60
+ if (!lstatSync(dest).isDirectory())
61
+ return false;
62
+ const id = readFileSync(join(dest, "resource.yaml"), "utf-8").match(/^id:\s*["']?([^"'\s]+)["']?\s*$/m)?.[1];
63
+ return id === `skill/${name}`;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ /** The folder, or any file in it, changed after `since` (ms); the folder's own time covers a deleted file. Links are not followed. */
70
+ function changedSince(dir, since) {
71
+ if (lstatSync(dir).mtimeMs > since)
72
+ return true;
73
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
74
+ const path = join(dir, entry.name);
75
+ if (lstatSync(path).mtimeMs > since)
76
+ return true;
77
+ if (entry.isDirectory() && changedSince(path, since))
78
+ return true;
79
+ }
80
+ return false;
81
+ }
82
+ /** Relative paths of every file under `dir`. */
83
+ function filesUnder(dir, prefix = "") {
84
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? filesUnder(join(dir, entry.name), `${prefix}${entry.name}/`) : [`${prefix}${entry.name}`]);
85
+ }
86
+ /** The installed folder holds exactly the library's files with the library's bytes (a refresh a kill interrupted already wrote it). */
87
+ function sameAsLibrary(dest, src) {
88
+ try {
89
+ const installed = filesUnder(dest).sort();
90
+ const library = filesUnder(src).sort();
91
+ return (installed.length === library.length &&
92
+ installed.every((file, index) => file === library[index] && readFileSync(join(dest, file)).equals(readFileSync(join(src, file)))));
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
98
+ /** Copy beside, rename the old one aside, rename the new one in; the old one comes back on any error. */
99
+ function replaceSkill(dir, name, src) {
100
+ const dest = join(dir, name);
101
+ const temp = join(dir, `${TEMP_PREFIX}${name}-${process.pid}`);
102
+ const old = join(dir, `${OLD_PREFIX}${name}-${process.pid}`);
103
+ rmSync(temp, { recursive: true, force: true });
104
+ try {
105
+ cpSync(src, temp, { recursive: true });
106
+ }
107
+ catch (error) {
108
+ rmSync(temp, { recursive: true, force: true });
109
+ throw error;
110
+ }
111
+ const hadOld = existsSync(dest);
112
+ if (hadOld)
113
+ renameSync(dest, old);
114
+ try {
115
+ renameSync(temp, dest);
116
+ }
117
+ catch (error) {
118
+ if (hadOld)
119
+ renameSync(old, dest);
120
+ rmSync(temp, { recursive: true, force: true });
121
+ throw error;
122
+ }
123
+ if (hadOld)
124
+ rmSync(old, { recursive: true, force: true });
125
+ }
126
+ /** What a killed refresh left: half copies go, a skill renamed aside comes back. */
127
+ function recoverInterrupted(dir) {
128
+ for (const entry of readdirSync(dir)) {
129
+ const path = join(dir, entry);
130
+ if (entry.startsWith(TEMP_PREFIX))
131
+ rmSync(path, { recursive: true, force: true });
132
+ if (!entry.startsWith(OLD_PREFIX))
133
+ continue;
134
+ const name = entry.slice(OLD_PREFIX.length).replace(/-\d+$/, "");
135
+ if (existsSync(join(dir, name)))
136
+ rmSync(path, { recursive: true, force: true });
137
+ else
138
+ renameSync(path, join(dir, name));
139
+ }
140
+ }
141
+ /** The folder's lock, or null while another process holds a fresh one. */
142
+ function takeLock(dir) {
143
+ const path = join(dir, LOCK_FILE);
144
+ for (let attempt = 0; attempt < 2; attempt++) {
145
+ try {
146
+ closeSync(openSync(path, "wx"));
147
+ return () => rmSync(path, { force: true });
148
+ }
149
+ catch (error) {
150
+ if (errorCode(error) !== "EEXIST")
151
+ throw error;
152
+ try {
153
+ if (Date.now() - statSync(path).mtimeMs < STALE_LOCK_MS)
154
+ return null;
155
+ unlinkSync(path);
156
+ }
157
+ catch {
158
+ return null;
159
+ }
160
+ }
161
+ }
162
+ return null;
163
+ }
164
+ function refreshFolder(dir, marker, cliVersion, registry, failures) {
165
+ const release = takeLock(dir);
166
+ if (!release)
167
+ return null;
168
+ try {
169
+ recoverInterrupted(dir);
170
+ // The marker must be writable before anything is copied, or every start would copy again.
171
+ const markerTemp = join(dir, `${SKILL_VERSION_MARKER_FILENAME}.tmp`);
172
+ writeFileSync(markerTemp, "", "utf-8");
173
+ const installedAt = Date.parse(marker.installedAt);
174
+ const editedAfter = (Number.isFinite(installedAt) ? installedAt : 0) + EDIT_GRACE_MS;
175
+ const folder = { dir, from: marker.version, to: cliVersion, updated: [], added: [], kept: [] };
176
+ const keptBefore = new Set(readKeptSkills(dir));
177
+ const owned = registry.filter((entry) => isSummersSkill(join(dir, entry.name), entry.name));
178
+ for (const entry of owned) {
179
+ const dest = join(dir, entry.name);
180
+ if (sameAsLibrary(dest, resolveSkillDir(entry))) {
181
+ folder.updated.push(entry.name);
182
+ continue;
183
+ }
184
+ if (keptBefore.has(entry.name) || changedSince(dest, editedAfter)) {
185
+ folder.kept.push(entry.name);
186
+ continue;
187
+ }
188
+ try {
189
+ replaceSkill(dir, entry.name, resolveSkillDir(entry));
190
+ folder.updated.push(entry.name);
191
+ }
192
+ catch (error) {
193
+ failures.push({ dir, skill: entry.name, code: errorCode(error) });
194
+ }
195
+ }
196
+ const bundled = registry.filter((entry) => entry.bundled !== false && entry.status !== "deprecated");
197
+ if (bundled.length > 0 && owned.length >= FULL_INSTALL_SHARE * bundled.length) {
198
+ for (const entry of bundled) {
199
+ if (existsSync(join(dir, entry.name)))
200
+ continue;
201
+ try {
202
+ replaceSkill(dir, entry.name, resolveSkillDir(entry));
203
+ folder.added.push(entry.name);
204
+ }
205
+ catch (error) {
206
+ failures.push({ dir, skill: entry.name, code: errorCode(error) });
207
+ }
208
+ }
209
+ }
210
+ // Written last, through a rename: a kill before this leaves the old marker, and the next start finishes.
211
+ const next = { version: cliVersion, installedAt: new Date().toISOString(), ...(folder.kept.length > 0 ? { kept: folder.kept } : {}) };
212
+ writeFileSync(markerTemp, JSON.stringify(next, null, 2) + "\n", "utf-8");
213
+ renameSync(markerTemp, join(dir, SKILL_VERSION_MARKER_FILENAME));
214
+ return folder;
215
+ }
216
+ finally {
217
+ release();
218
+ }
219
+ }
220
+ export function refreshInstalledSkills(cliVersion, options = {}) {
221
+ const result = { refreshed: [], failures: [] };
222
+ if (!parseSemver(cliVersion))
223
+ return result;
224
+ for (const dir of options.dirs ?? skillFolderLocations()) {
225
+ const marker = readSkillMarker(dir);
226
+ if (!marker || !isBehind(marker, cliVersion))
227
+ continue;
228
+ try {
229
+ const folder = refreshFolder(dir, marker, cliVersion, options.registry ?? getSkillRegistry(), result.failures);
230
+ if (folder)
231
+ result.refreshed.push(folder);
232
+ }
233
+ catch (error) {
234
+ // A folder we cannot lock or write keeps its skills and its marker; doctor still names the refresh.
235
+ result.failures.push({ dir, code: errorCode(error) });
236
+ }
237
+ }
238
+ return result;
239
+ }
240
+ const listed = (names) => names.length > 5 ? `${names.slice(0, 5).join(", ")} and ${names.length - 5} more` : names.join(", ");
241
+ /** The one message a person and their agent see after a refresh, or null when nothing changed. */
242
+ export function refreshNotice(refreshed, failures = []) {
243
+ const skills = new Set(refreshed.flatMap((folder) => [...folder.updated, ...folder.added])).size;
244
+ const kept = [...new Set(refreshed.flatMap((folder) => folder.kept))];
245
+ const failed = [...new Set(failures.flatMap((failure) => (failure.skill ? [failure.skill] : [])))];
246
+ if (skills === 0 && kept.length === 0)
247
+ return null;
248
+ const changedFolders = refreshed.filter((folder) => folder.updated.length + folder.added.length > 0).length;
249
+ const from = [...new Set(refreshed.map((folder) => folder.from))].join(", ");
250
+ const to = refreshed[0].to;
251
+ return [
252
+ skills > 0 ? `Summer updated its ${skills} installed skills (${changedFolders === 1 ? "1 folder" : `${changedFolders} folders`}) from ${from} to ${to}.` : "",
253
+ kept.length > 0
254
+ ? `It left ${kept.length} Summer skills you changed as they are (${listed(kept)}); summer setup <agent> --force replaces them.`
255
+ : "",
256
+ failed.length > 0 ? `${failed.length} could not be updated (${listed(failed)}); summer setup <agent> --force retries them.` : "",
257
+ "3.x tool names still run through summer_call; summer mcp --toolsets legacy lists them again.",
258
+ ]
259
+ .filter(Boolean)
260
+ .join(" ");
261
+ }
@@ -79,9 +79,18 @@ export declare const SKILL_VERSION_MARKER_FILENAME = ".summer-version";
79
79
  export interface SkillVersionMarker {
80
80
  version: string;
81
81
  installedAt: string;
82
+ /** Summer skills the upgrade refresh left because someone changed them (installer/skills-refresh.ts). */
83
+ kept?: string[];
82
84
  }
83
85
  export declare function readSkillMarker(dir: string): SkillVersionMarker | null;
84
- export declare function writeSkillMarker(dir: string, version: string, now?: Date): string;
86
+ /** The skills the upgrade refresh left in this folder (the raw marker; readSkillMarker keeps setup's fields). */
87
+ export declare function readKeptSkills(dir: string): string[];
88
+ /**
89
+ * Write the folder's marker. The skills an upgrade refresh left stay listed
90
+ * (so later refreshes keep leaving them), except the ones this install just
91
+ * wrote: `installed` names them; leave it out to keep every one.
92
+ */
93
+ export declare function writeSkillMarker(dir: string, version: string, now?: Date, installed?: readonly string[]): string;
85
94
  /**
86
95
  * Candidate locations for skill-version markers. Listed roughly in order of
87
96
  * how likely a Summer user is to have them populated. We probe every candidate,
@@ -108,6 +117,10 @@ export interface RecordedInstall {
108
117
  /** Where the answer came from: the agent's config file, or — when it has
109
118
  * no readable Summer entry — the nature of the CLI running this check. */
110
119
  source: "agent-config" | "running-cli";
120
+ /** The agent's config lists no summer-engine server in any shape (`setup
121
+ * --cli`, or skills installed alone) and `setup --cli` can set the agent
122
+ * up: the refresh keeps it without an MCP server. */
123
+ cliOnly?: boolean;
111
124
  }
112
125
  /** An npm dist-tag (`latest`, `next`) or an exact version (`3.9.1`). */
113
126
  export type RefreshChannel = string;
@@ -153,7 +166,8 @@ export declare function buildSkillsVersionCheck(input: SkillsVersionCheckInput):
153
166
  * The command that refreshes an agent's skills without changing how its MCP
154
167
  * entry was recorded: the local-dev form re-links the same checkout
155
168
  * (`setup --local-dev --force` re-copies the checkout's skills); the npx form
156
- * is for installs that already run the published package.
169
+ * is for installs that already run the published package. An agent with no
170
+ * summer-engine entry refreshes with `--cli`, so the refresh adds no MCP server.
157
171
  */
158
172
  export declare function skillsRefreshCommand(agent: string, install: RecordedInstall | null, channel?: RefreshChannel): string;
159
173
  /** The shape `summer setup --local-dev` writes: `node <…>/dist/bin/summer.js mcp`
@@ -173,6 +187,12 @@ export declare function isLocalDevServerConfig(server: StdioMcpServerConfig): bo
173
187
  * Returns null when the file is missing, unparsable, or has no Summer entry.
174
188
  */
175
189
  export declare function readRecordedMcpServer(text: string, format: McpFormat): StdioMcpServerConfig | null;
190
+ /**
191
+ * Does the agent's config list a summer-engine server in any shape: the stdio
192
+ * entry setup writes, a URL entry under that name, or (Claude Code) a
193
+ * per-project entry under projects.<dir>.mcpServers?
194
+ */
195
+ export declare function listsSummerEngine(text: string, format: McpFormat): boolean;
176
196
  /** Is the CLI running this check a source checkout rather than an installed
177
197
  * package? npm/npx/global installs all live under a node_modules directory. */
178
198
  export declare function runningCliIsCheckout(packageRoot?: string): boolean;
@@ -13,7 +13,7 @@
13
13
  */
14
14
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
15
15
  import { parse as parseYaml } from "yaml";
16
- import { allAgentSpecs, defaultPathContext, resolveSkillPath } from "./agent-table.js";
16
+ import { agentSpec, allAgentSpecs, cliSetupRefusal, defaultPathContext, resolveSkillPath } from "./agent-table.js";
17
17
  import { join, sep } from "node:path";
18
18
  import { PACKAGE_ROOT } from "../core/package-root.js";
19
19
  import { configureAgentMcp, parseAgent, SUMMER_MCP_SERVER_NAME, topLevelKey, } from "./agent-config.js";
@@ -219,11 +219,28 @@ export function readSkillMarker(dir) {
219
219
  return null;
220
220
  }
221
221
  }
222
- export function writeSkillMarker(dir, version, now = new Date()) {
222
+ /** The skills the upgrade refresh left in this folder (the raw marker; readSkillMarker keeps setup's fields). */
223
+ export function readKeptSkills(dir) {
224
+ try {
225
+ const kept = JSON.parse(readFileSync(join(dir, SKILL_VERSION_MARKER_FILENAME), "utf-8")).kept;
226
+ return Array.isArray(kept) ? kept.filter((name) => typeof name === "string") : [];
227
+ }
228
+ catch {
229
+ return [];
230
+ }
231
+ }
232
+ /**
233
+ * Write the folder's marker. The skills an upgrade refresh left stay listed
234
+ * (so later refreshes keep leaving them), except the ones this install just
235
+ * wrote: `installed` names them; leave it out to keep every one.
236
+ */
237
+ export function writeSkillMarker(dir, version, now = new Date(), installed) {
223
238
  const path = join(dir, SKILL_VERSION_MARKER_FILENAME);
239
+ const kept = readKeptSkills(dir).filter((name) => !installed?.includes(name));
224
240
  const payload = {
225
241
  version,
226
242
  installedAt: now.toISOString(),
243
+ ...(kept.length > 0 ? { kept } : {}),
227
244
  };
228
245
  writeFileSync(path, JSON.stringify(payload, null, 2) + "\n", "utf-8");
229
246
  return path;
@@ -378,9 +395,16 @@ async function resolveRecordedInstall(resolver, agent) {
378
395
  * The command that refreshes an agent's skills without changing how its MCP
379
396
  * entry was recorded: the local-dev form re-links the same checkout
380
397
  * (`setup --local-dev --force` re-copies the checkout's skills); the npx form
381
- * is for installs that already run the published package.
398
+ * is for installs that already run the published package. An agent with no
399
+ * summer-engine entry refreshes with `--cli`, so the refresh adds no MCP server.
382
400
  */
383
401
  export function skillsRefreshCommand(agent, install, channel = "latest") {
402
+ if (install?.cliOnly) {
403
+ // No MCP entry to keep: --local-dev and --channel shape only that entry.
404
+ return install.localDev && install.cliPath
405
+ ? `node ${install.cliPath} setup ${agent} --cli --yes --force`
406
+ : `npx clear-npx-cache && npx -y summer-engine@${channel} setup ${agent} --cli --yes --force`;
407
+ }
384
408
  if (install?.localDev && install.cliPath) {
385
409
  return `node ${install.cliPath} setup ${agent} --local-dev --yes --force`;
386
410
  }
@@ -473,6 +497,37 @@ export function readRecordedMcpServer(text, format) {
473
497
  }
474
498
  return null;
475
499
  }
500
+ /**
501
+ * Does the agent's config list a summer-engine server in any shape: the stdio
502
+ * entry setup writes, a URL entry under that name, or (Claude Code) a
503
+ * per-project entry under projects.<dir>.mcpServers?
504
+ */
505
+ export function listsSummerEngine(text, format) {
506
+ if (readRecordedMcpServer(text, format))
507
+ return true;
508
+ if (!text.trim())
509
+ return false;
510
+ if (format === "toml")
511
+ return new RegExp(`^\\s*\\[mcp_servers\\.(?:"${SUMMER_MCP_SERVER_NAME}"|${SUMMER_MCP_SERVER_NAME})[\\].]`, "m").test(text);
512
+ if (format === "toml-array")
513
+ return new RegExp(`^\\s*name\\s*=\\s*"${SUMMER_MCP_SERVER_NAME}"`, "m").test(text);
514
+ let parsed;
515
+ try {
516
+ parsed = format === "yaml-goose" || format === "yaml-hermes" ? parseYaml(text) : JSON.parse(text);
517
+ }
518
+ catch {
519
+ return false;
520
+ }
521
+ if (!parsed || typeof parsed !== "object")
522
+ return false;
523
+ const root = parsed;
524
+ const projects = root.projects && typeof root.projects === "object" ? Object.values(root.projects) : [];
525
+ const maps = [
526
+ ...[topLevelKey(format), "mcpServers", "servers", "mcp", "context_servers", "extensions", "mcp_servers", "amp.mcpServers"].map((key) => root[key]),
527
+ ...projects.map((project) => (project && typeof project === "object" ? project.mcpServers : undefined)),
528
+ ];
529
+ return maps.some((map) => Boolean(map) && typeof map === "object" && SUMMER_MCP_SERVER_NAME in map);
530
+ }
476
531
  /** Is the CLI running this check a source checkout rather than an installed
477
532
  * package? npm/npx/global installs all live under a node_modules directory. */
478
533
  export function runningCliIsCheckout(packageRoot = PACKAGE_ROOT) {
@@ -488,16 +543,17 @@ export function runningCliIsCheckout(packageRoot = PACKAGE_ROOT) {
488
543
  */
489
544
  export async function detectRecordedInstall(agent) {
490
545
  const supported = parseAgent(agent);
546
+ let cliOnly = false;
491
547
  if (supported) {
492
548
  try {
493
549
  const target = await configureAgentMcp({ agent: supported, scope: "user", print: true });
494
- if (existsSync(target.path)) {
495
- const recorded = readRecordedMcpServer(readFileSync(target.path, "utf-8"), target.format);
496
- if (recorded) {
497
- const localDev = isLocalDevServerConfig(recorded);
498
- return { localDev, cliPath: localDev ? recorded.args[0] ?? null : null, source: "agent-config" };
499
- }
550
+ const text = existsSync(target.path) ? readFileSync(target.path, "utf-8") : "";
551
+ const recorded = readRecordedMcpServer(text, target.format);
552
+ if (recorded) {
553
+ const localDev = isLocalDevServerConfig(recorded);
554
+ return { localDev, cliPath: localDev ? recorded.args[0] ?? null : null, source: "agent-config" };
500
555
  }
556
+ cliOnly = !listsSummerEngine(text, target.format) && cliSetupRefusal(agentSpec(supported)) === null;
501
557
  }
502
558
  catch {
503
559
  // Unreadable config: fall through to the running-CLI answer.
@@ -508,6 +564,7 @@ export async function detectRecordedInstall(agent) {
508
564
  localDev: checkout,
509
565
  cliPath: checkout ? [PACKAGE_ROOT, "dist", "bin", "summer.js"].join(sep) : null,
510
566
  source: "running-cli",
567
+ ...(cliOnly ? { cliOnly } : {}),
511
568
  };
512
569
  }
513
570
  export function buildBootDriftNotice(installedVersion, registry, agent) {
@@ -1,7 +1,7 @@
1
1
  import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { z } from "zod";
3
3
  import { localEntries, localInputSchema, localWireForm, missingInput } from "../core/ops/catalog.js";
4
- import { answerOfHosted, hostedCallFailure, searchEverywhere } from "../core/ops/cloud.js";
4
+ import { answerOfHosted, describeFromShipped, hostedCallFailure, searchEverywhere } from "../core/ops/cloud.js";
5
5
  import { errorAnswer, isLocalOp, legacyNames, localToolsFor, mcpNameOf, resolveOpName } from "../core/ops/contract.js";
6
6
  import { ENGINE_SPLIT_OPS, engineOpDef, isSplitOp, renderOpRefs, splitToolArgs } from "../core/ops/engine.js";
7
7
  import { answerOfEngineResult, engineFailure, textsOf } from "../core/ops/engine-answer.js";
@@ -343,12 +343,20 @@ export function applyOpsSurface(server, options) {
343
343
  limit: z.number().int().min(1).max(20).optional().describe("Most results (default 5)."),
344
344
  }, { title: "Find a Summer operation", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }, (async ({ query, area, limit }) => toolResult(await search(query, area, limit ?? 5))));
345
345
  server.tool(SUMMER_CALL, "Run any Summer operation by name (from summer_search) with its input. Engine operations run here; cloud ones run on Summer's server with the same approvals: money, publishing and live changes answer needs_approval with a link. 3.x tool names work too.", {
346
- op: z.string().min(1).max(80).describe("The operation name, e.g. engine.add_node or pricing.set_base (or a 3.x tool name)."),
346
+ op: z.string().min(1).max(80).optional().describe("The operation name, e.g. engine.add_node or pricing.set_base (or a 3.x tool name)."),
347
+ // Agents reach for `name` (the field summer_search answers with); it means op.
348
+ name: z.string().min(1).max(80).optional().describe("Same as op."),
347
349
  input: z.record(z.unknown()).optional().describe("The operation's input, matching the schema summer_search returned."),
348
350
  dryRun: z.boolean().optional().describe("Check and describe the call without doing it."),
349
351
  idempotencyKey: z.string().min(1).max(128).optional().describe("Retry key for writes; reuse it only to retry the identical call."),
350
- }, { title: "Run a Summer operation", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, (async ({ op, input, dryRun, idempotencyKey }, extra) => {
351
- const name = op.trim();
352
+ }, { title: "Run a Summer operation", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }, (async ({ op, name: opAlias, input, dryRun, idempotencyKey }, extra) => {
353
+ const name = (op ?? opAlias ?? "").trim();
354
+ if (!name) {
355
+ return toolResult(errorAnswer("invalid_input", "Pass op: the operation name from summer_search, e.g. engine.add_node.", { next: { op: "ops.search" } }));
356
+ }
357
+ if (op !== undefined && opAlias !== undefined && op.trim() !== opAlias.trim()) {
358
+ return toolResult(errorAnswer("invalid_input", `op ("${op}") and name ("${opAlias}") differ; pass one operation name.`, { next: { op: "ops.search" }, details: { path: "name" } }));
359
+ }
352
360
  const args = input ?? {};
353
361
  // A split operation's own tool name, or a mixed 3.x tool whose action picks the operation.
354
362
  const action = typeof args.action === "string" ? args.action : typeof args.mode === "string" ? args.mode : undefined;
@@ -378,13 +386,15 @@ export function applyOpsSurface(server, options) {
378
386
  // `out` stays here: this package saves the files (core/ops/save-files.ts).
379
387
  const { input: sent, out } = options.files.take(resolved, args);
380
388
  let raw;
389
+ // Describing a cloud operation works signed out: the shipped catalog has it.
390
+ const describe = (answer) => (resolved === "ops.describe" && typeof args.name === "string" ? describeFromShipped(args.name, answer) : null) ?? answer;
381
391
  try {
382
392
  raw = await options.cloud.call(SUMMER_CALL, { op: name, ...(input ? { input: sent } : {}), ...(dryRun ? { dryRun } : {}), ...(idempotencyKey ? { idempotencyKey } : {}) });
383
393
  }
384
394
  catch (error) {
385
- return toolResult(hostedCallFailure(error, { op: resolved, dryRun: dryRun === true, idempotencyKey }));
395
+ return toolResult(describe(hostedCallFailure(error, { op: resolved, dryRun: dryRun === true, idempotencyKey })));
386
396
  }
387
- return toolResult(await options.files.finish(resolved, answerOfHosted(raw), out));
397
+ return toolResult(describe(await options.files.finish(resolved, answerOfHosted(raw), out)));
388
398
  }));
389
399
  }
390
400
  /** A split operation's input shape: its fields from the 3.x tool's shape, the required ones required. */
@@ -38,6 +38,8 @@ export interface CreateMcpServerOptions {
38
38
  cloud?: CloudCaller;
39
39
  /** v4 only: list every local 3.x name too (the legacy toolset, legacy-toolset.ts). */
40
40
  legacy?: boolean;
41
+ /** One line for this session only, after the instructions (e.g. the installed skills were refreshed). */
42
+ notice?: string | null;
41
43
  }
42
44
  export declare function createMcpServer(selection?: ToolSelection, options?: CreateMcpServerOptions): {
43
45
  server: McpServer;
@@ -45,6 +47,14 @@ export declare function createMcpServer(selection?: ToolSelection, options?: Cre
45
47
  /** v4: saves generated files on this machine (the mounted hosted tools share it). */
46
48
  files: FileSaver;
47
49
  };
50
+ /**
51
+ * First v4 start after an upgrade: bring the installed Summer skills to this
52
+ * version before the legacy decision, which stays on while they are 3.x (and
53
+ * whenever the config asks for it). Not on the v3 surface, whose people chose
54
+ * the 3.x tools, and not under summer doctor's boot probe, whose session would
55
+ * swallow the one notice. A fault here never stops the server.
56
+ */
57
+ export declare function refreshSkillsAfterUpgrade(surface: SurfaceVersion): string | null;
48
58
  export interface StartMcpServerOptions {
49
59
  projectPath?: string;
50
60
  instanceId?: string;
@@ -34,6 +34,7 @@ import { opForToolCall } from "../core/ops/contract.js";
34
34
  import { v4HostedUrl } from "../core/ops/cloud.js";
35
35
  import { serveProviderSafeToolSchemas } from "./provider-safe-schema.js";
36
36
  import { legacyHostedDescription, resolveLegacy, takeLegacyToolset } from "./legacy-toolset.js";
37
+ import { refreshInstalledSkills, refreshNotice } from "../installer/skills-refresh.js";
37
38
  import { OUTPUT_CAPS, PROFILE_TOOL_LIMIT, PROFILES, TOOLSETS, annotationsFor, registryAuthorities, resolveToolSelection, selectionIncludes, withMaxCharactersArg, } from "./tool-surface.js";
38
39
  import { shapeToolResult, singleEditCounter } from "./tool-results.js";
39
40
  import { recordLocalToolCall, recordMcpSession } from "../core/telemetry.js";
@@ -363,9 +364,10 @@ export function createMcpServer(selection = FULL_SELECTION, options = {}) {
363
364
  name: "summer-engine",
364
365
  version,
365
366
  }, {
366
- instructions: surface === "v4"
367
+ instructions: (surface === "v4"
367
368
  ? engine ? OPS_MCP_INSTRUCTIONS : OPS_MCP_ASSETS_INSTRUCTIONS
368
- : engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS,
369
+ : engine ? SUMMER_MCP_INSTRUCTIONS : SUMMER_MCP_ASSETS_INSTRUCTIONS) +
370
+ (options.notice ? ` ${options.notice} Tell the person once.` : ""),
369
371
  });
370
372
  const getWrappedToolCount = installToolCallWrapper(server, { surfaceVersion: surface, profile: selection.profile });
371
373
  // Requests to Summer's servers name the agent app from the initialize handshake.
@@ -426,6 +428,32 @@ function hostedToolsLabel(toolsets) {
426
428
  return "Tool Factory tools (summer_tool_*) and asset editing tools";
427
429
  return undefined;
428
430
  }
431
+ /**
432
+ * First v4 start after an upgrade: bring the installed Summer skills to this
433
+ * version before the legacy decision, which stays on while they are 3.x (and
434
+ * whenever the config asks for it). Not on the v3 surface, whose people chose
435
+ * the 3.x tools, and not under summer doctor's boot probe, whose session would
436
+ * swallow the one notice. A fault here never stops the server.
437
+ */
438
+ export function refreshSkillsAfterUpgrade(surface) {
439
+ if (surface !== "v4" || process.env.SUMMER_MCP_DOCTOR === "1")
440
+ return null;
441
+ try {
442
+ const { refreshed, failures } = refreshInstalledSkills(version);
443
+ if (failures.length > 0)
444
+ appendMcpLogEvent("mcp:skills_refresh_failed", { version, failures });
445
+ const notice = refreshNotice(refreshed, failures);
446
+ if (notice) {
447
+ appendMcpLogEvent("mcp:skills_refreshed", { version, notice });
448
+ process.stderr.write(`[summer-mcp] ${notice}\n`);
449
+ }
450
+ return notice;
451
+ }
452
+ catch (error) {
453
+ appendMcpLogEvent("mcp:skills_refresh_failed", { version, ...errorDetails(error) });
454
+ return null;
455
+ }
456
+ }
429
457
  export async function startMcpServer(options = {}) {
430
458
  configureMcpEngineSelection({
431
459
  instanceId: options.instanceId ?? process.env.SUMMER_ENGINE_INSTANCE_ID,
@@ -445,6 +473,7 @@ export async function startMcpServer(options = {}) {
445
473
  },
446
474
  });
447
475
  const surface = resolveSurface(options.surface);
476
+ const skillsNotice = refreshSkillsAfterUpgrade(surface);
448
477
  // `legacy` is not a toolset of its own: it lists the 3.x names on the v4 surface.
449
478
  const toolsetList = takeLegacyToolset(options.toolsets ?? process.env.SUMMER_TOOLSETS);
450
479
  const legacy = surface === "v4" ? resolveLegacy(toolsetList.requested) : { on: false, reason: "off" };
@@ -459,7 +488,7 @@ export async function startMcpServer(options = {}) {
459
488
  // v4 runs cloud operations through the hosted mount below, once it exists.
460
489
  let hostedCall = null;
461
490
  const cloud = { call: (name, args) => (hostedCall ? hostedCall(name, args) : NO_CLOUD.call(name, args)) };
462
- const { server, getRegisteredToolCount, files } = createMcpServer(selection, { surface, cloud, legacy: legacy.on });
491
+ const { server, getRegisteredToolCount, files } = createMcpServer(selection, { surface, cloud, legacy: legacy.on, notice: skillsNotice });
463
492
  // Fire-and-forget — never block tool registration on the npm registry.
464
493
  void probeBootDrift().catch((error) => {
465
494
  setCachedBootDriftNotice(null);
@@ -0,0 +1,47 @@
1
+ /**
2
+ * AGENTS.md and CLAUDE.md in a Summer project. AGENTS.md is the one file
3
+ * Codex, Cursor, Copilot, Amp and other agents read in every session;
4
+ * Claude Code reads CLAUDE.md, which here only imports AGENTS.md.
5
+ * `summer create` writes both; `summer setup --yes` inside a project adds
6
+ * them when missing.
7
+ *
8
+ * An existing file is never overwritten: Summer adds one marked block once,
9
+ * and later runs replace only that block. A link is followed only to a file
10
+ * inside the project, and a CLAUDE.md that is the same file as AGENTS.md
11
+ * gets the AGENTS.md block only. The five commands are the list in the
12
+ * summer-cli skill (between its project-commands markers), so the two
13
+ * cannot drift; the skill's test runs every command both of them teach.
14
+ */
15
+ export declare const SUMMER_BLOCK_START = "<!-- summer-engine:start. summer setup updates only this block; write your own notes outside it. -->";
16
+ export declare const SUMMER_BLOCK_END = "<!-- summer-engine:end -->";
17
+ /** The numbered command list from the summer-cli skill. */
18
+ export declare function projectCommands(skill?: string): string;
19
+ /** The Summer block of AGENTS.md, markers included. */
20
+ export declare function renderAgentsBlock(commands?: string): string;
21
+ export type InstructionAction = "created" | "added" | "updated" | "unchanged" | "skipped";
22
+ export interface InstructionFileResult {
23
+ file: "AGENTS.md" | "CLAUDE.md";
24
+ path: string;
25
+ action: InstructionAction;
26
+ /** Why a file was skipped or left as it is. */
27
+ reason?: string;
28
+ }
29
+ /**
30
+ * Write or update AGENTS.md and CLAUDE.md in the project root. A new
31
+ * AGENTS.md is the Summer block; a new CLAUDE.md is `@AGENTS.md` only. An
32
+ * existing CLAUDE.md that already imports AGENTS.md is left as it is. Each
33
+ * file is its own step: a file that cannot be read or written is skipped
34
+ * with the reason, and the other is still done.
35
+ */
36
+ export declare function writeAgentInstructions(projectDir: string, options?: {
37
+ dryRun?: boolean;
38
+ block?: string;
39
+ }): InstructionFileResult[];
40
+ /**
41
+ * The Summer project the working directory is in, for `summer setup`: the
42
+ * nearest folder with project.godot, looking up no further than the git
43
+ * top level, and never the home folder or the filesystem root (a stray
44
+ * project.godot there must not put an AGENTS.md or CLAUDE.md in every
45
+ * session). Null when there is none.
46
+ */
47
+ export declare function findSetupProjectRoot(cwd?: string): string | null;