dev-booster 1.11.0 → 1.12.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/src/index.js CHANGED
@@ -1,219 +1,274 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
- import readline from 'readline/promises'
4
- import { fileURLToPath } from 'url'
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { execSync } from "child_process";
4
+ import readline from "readline/promises";
5
+ import { fileURLToPath } from "url";
5
6
 
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
8
 
8
9
  // Source: the `template/` folder shipped inside the npm package
9
- const TEMPLATE_DIR = path.resolve(__dirname, '..', 'template')
10
+ const TEMPLATE_DIR = path.resolve(__dirname, "..", "template");
10
11
 
11
12
  // Destination: the user's current working directory (where they ran npx)
12
- const TARGET_DIR = process.cwd()
13
- const args = process.argv.slice(2)
14
- const GITIGNORE_BLOCK = [
15
- '# DEV-BOOSTER',
16
- '.devbooster/',
17
- 'DEVBOOSTER_INIT.md',
18
- ]
19
- const GITIGNORE_MARKER = '# DEV-BOOSTER'
13
+ const TARGET_DIR = process.cwd();
14
+ const args = process.argv.slice(2);
15
+ const GITIGNORE_BLOCK = ["# DEV-BOOSTER", ".devbooster/", "DEVBOOSTER_INIT.md"];
16
+ const GITIGNORE_MARKER = "# DEV-BOOSTER";
20
17
  const IDE_BRIDGE_BLOCK = [
21
- '# 🤖 DEV BOOSTER — AGENTIC KIT BOOTSTRAP',
22
- '',
23
- 'Before any action, read `.devbooster/rules/PROTOCOL.md` for full governance rules.',
24
- ]
25
- const IDE_BRIDGE_MARKER = '.devbooster/rules/PROTOCOL.md'
26
- const IDE_BRIDGE_FALLBACK_FLAG = path.join('hub', 'flags', 'needs-ide-bridge')
18
+ "# 🤖 DEV BOOSTER — AGENTIC KIT BOOTSTRAP",
19
+ "",
20
+ "Before any action, read `.devbooster/rules/PROTOCOL.md` for full governance rules.",
21
+ ];
22
+ const IDE_BRIDGE_MARKER = ".devbooster/rules/PROTOCOL.md";
23
+ const IDE_BRIDGE_FALLBACK_FLAG = path.join("hub", "flags", "needs-ide-bridge");
27
24
  const IDE_BRIDGE_TARGETS = [
28
- '.rules',
29
- '.cursorrules',
30
- '.windsurfrules',
31
- '.clinerules',
32
- 'AGENT.md',
33
- 'AGENTS.md',
34
- 'CLAUDE.md',
35
- 'GEMINI.md',
36
- '.github/copilot-instructions.md',
37
- ]
25
+ ".rules",
26
+ ".cursorrules",
27
+ ".windsurfrules",
28
+ ".clinerules",
29
+ "AGENT.md",
30
+ "AGENTS.md",
31
+ "CLAUDE.md",
32
+ "GEMINI.md",
33
+ ".github/copilot-instructions.md",
34
+ ];
38
35
 
39
36
  const PROTECTED_FILES = [
40
- 'DEVBOOSTER_INIT.md',
41
- '.devbooster/rules/PROJECT.md',
42
- '.devbooster/rules/FRONTEND.md',
43
- '.devbooster/rules/BACKEND.md',
44
- '.devbooster/rules/COMMERCIAL.md',
45
- '.devbooster/rules/USER_PREFERENCES.md',
46
- ]
37
+ "DEVBOOSTER_INIT.md",
38
+ ".devbooster/rules/PROJECT.md",
39
+ ".devbooster/rules/FRONTEND.md",
40
+ ".devbooster/rules/BACKEND.md",
41
+ ".devbooster/rules/COMMERCIAL.md",
42
+ ".devbooster/rules/USER_PREFERENCES.md",
43
+ ];
47
44
 
48
45
  function safeCopyFile(src, dest, dryRun = false) {
49
- const relativeDest = path.relative(TARGET_DIR, dest)
46
+ const relativeDest = path.relative(TARGET_DIR, dest);
50
47
  if (fs.existsSync(dest) && PROTECTED_FILES.includes(relativeDest)) {
51
- return false // Preserved
48
+ return false; // Preserved
52
49
  }
53
50
  if (!dryRun) {
54
- fs.mkdirSync(path.dirname(dest), { recursive: true })
55
- fs.copyFileSync(src, dest)
51
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
52
+ fs.copyFileSync(src, dest);
56
53
  }
57
- return true // Copied/Updated
54
+ return true; // Copied/Updated
58
55
  }
59
56
 
60
57
  function copyDirSafe(src, dest, dryRun = false) {
61
- fs.mkdirSync(dest, { recursive: true })
58
+ fs.mkdirSync(dest, { recursive: true });
62
59
 
63
60
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
64
- if (entry.name === '.DS_Store') continue
61
+ if (entry.name === ".DS_Store") continue;
65
62
 
66
- const srcPath = path.join(src, entry.name)
67
- const destPath = path.join(dest, entry.name)
63
+ const srcPath = path.join(src, entry.name);
64
+ const destPath = path.join(dest, entry.name);
68
65
 
69
66
  if (entry.isDirectory()) {
70
- copyDirSafe(srcPath, destPath, dryRun)
67
+ copyDirSafe(srcPath, destPath, dryRun);
71
68
  } else {
72
- safeCopyFile(srcPath, destPath, dryRun)
69
+ safeCopyFile(srcPath, destPath, dryRun);
73
70
  }
74
71
  }
75
72
  }
76
73
 
77
74
  function copyDir(src, dest) {
78
- fs.mkdirSync(dest, { recursive: true })
75
+ fs.mkdirSync(dest, { recursive: true });
79
76
 
80
77
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
81
- if (entry.name === '.DS_Store') continue
78
+ if (entry.name === ".DS_Store") continue;
82
79
 
83
- const srcPath = path.join(src, entry.name)
84
- const destPath = path.join(dest, entry.name)
80
+ const srcPath = path.join(src, entry.name);
81
+ const destPath = path.join(dest, entry.name);
85
82
 
86
83
  if (entry.isDirectory()) {
87
- copyDir(srcPath, destPath)
84
+ copyDir(srcPath, destPath);
88
85
  } else {
89
- fs.copyFileSync(srcPath, destPath)
86
+ fs.copyFileSync(srcPath, destPath);
90
87
  }
91
88
  }
92
89
  }
93
90
 
94
91
  function removeDirContents(targetDir) {
95
- if (!fs.existsSync(targetDir)) return
92
+ if (!fs.existsSync(targetDir)) return;
96
93
 
97
94
  for (const entry of fs.readdirSync(targetDir, { withFileTypes: true })) {
98
- const entryPath = path.join(targetDir, entry.name)
99
- fs.rmSync(entryPath, { recursive: true, force: true })
95
+ const entryPath = path.join(targetDir, entry.name);
96
+ fs.rmSync(entryPath, { recursive: true, force: true });
100
97
  }
101
98
  }
102
99
 
103
100
  function syncDir(src, dest) {
104
- fs.mkdirSync(dest, { recursive: true })
105
- removeDirContents(dest)
106
- copyDir(src, dest)
101
+ fs.mkdirSync(dest, { recursive: true });
102
+ removeDirContents(dest);
103
+ copyDir(src, dest);
107
104
  }
108
105
 
109
106
  function isUpdateMode() {
110
- return args.includes('--update') || args.includes('update')
107
+ return args.includes("--update") || args.includes("update");
111
108
  }
112
109
 
113
110
  function isDryRunMode() {
114
- return args.includes('--dry-run') || args.includes('dry-run')
111
+ return args.includes("--dry-run") || args.includes("dry-run");
115
112
  }
116
113
 
117
114
  function ensureTrailingNewline(content) {
118
- return content.endsWith('\n') ? content : `${content}\n`
115
+ return content.endsWith("\n") ? content : `${content}\n`;
119
116
  }
120
117
 
121
118
  function appendUniqueBlock(filePath, blockLines, marker, dryRun = false) {
122
- const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : ''
119
+ const existing = fs.existsSync(filePath)
120
+ ? fs.readFileSync(filePath, "utf8")
121
+ : "";
123
122
  if (existing.includes(marker)) {
124
- return false
123
+ return false;
125
124
  }
126
125
 
127
126
  if (!dryRun) {
128
- const trimmedEnd = existing.replace(/\s*$/, '')
129
- const prefix = trimmedEnd.length > 0 ? `${trimmedEnd}\n\n` : ''
130
- const nextContent = `${prefix}${blockLines.join('\n')}\n`
131
- fs.mkdirSync(path.dirname(filePath), { recursive: true })
132
- fs.writeFileSync(filePath, nextContent)
127
+ const trimmedEnd = existing.replace(/\s*$/, "");
128
+ const prefix = trimmedEnd.length > 0 ? `${trimmedEnd}\n\n` : "";
129
+ const nextContent = `${prefix}${blockLines.join("\n")}\n`;
130
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
131
+ fs.writeFileSync(filePath, nextContent);
133
132
  }
134
- return true
133
+ return true;
135
134
  }
136
135
 
137
136
  async function askYesNo(question, defaultYes = true) {
138
137
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
139
- return defaultYes
138
+ return defaultYes;
140
139
  }
141
140
 
142
141
  const rl = readline.createInterface({
143
142
  input: process.stdin,
144
143
  output: process.stdout,
145
- })
144
+ });
146
145
 
147
146
  try {
148
- const suffix = defaultYes ? ' (Y/n) ' : ' (y/N) '
149
- const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase()
150
-
151
- if (!answer) return defaultYes
152
- if (answer === 'y' || answer === 'yes') return true
153
- if (answer === 'n' || answer === 'no') return false
154
- return defaultYes
147
+ const suffix = defaultYes ? " (Y/n) " : " (y/N) ";
148
+ const answer = (await rl.question(`${question}${suffix}`))
149
+ .trim()
150
+ .toLowerCase();
151
+
152
+ if (!answer) return defaultYes;
153
+ if (answer === "y" || answer === "yes") return true;
154
+ if (answer === "n" || answer === "no") return false;
155
+ return defaultYes;
155
156
  } finally {
156
- rl.close()
157
+ rl.close();
157
158
  }
158
159
  }
159
160
 
160
161
  async function maybeAddDevBoosterToGitignore(dryRun) {
161
- const shouldIgnore = await askYesNo('Add Dev Booster to your .gitignore?', true)
162
+ const shouldIgnore = await askYesNo(
163
+ "Add Dev Booster to your .gitignore?",
164
+ true,
165
+ );
162
166
 
163
167
  if (!shouldIgnore) {
164
- console.log('▸ .gitignore')
165
- console.log(' status: skipped by user\n')
166
- return
168
+ console.log("▸ .gitignore");
169
+ console.log(" status: skipped by user\n");
170
+ return;
167
171
  }
168
172
 
169
- const gitignorePath = path.join(TARGET_DIR, '.gitignore')
170
- const changed = appendUniqueBlock(gitignorePath, GITIGNORE_BLOCK, GITIGNORE_MARKER, dryRun)
173
+ const gitignorePath = path.join(TARGET_DIR, ".gitignore");
174
+ const changed = appendUniqueBlock(
175
+ gitignorePath,
176
+ GITIGNORE_BLOCK,
177
+ GITIGNORE_MARKER,
178
+ dryRun,
179
+ );
171
180
 
172
- console.log('▸ .gitignore')
181
+ console.log("▸ .gitignore");
173
182
  if (changed) {
174
- console.log(` status: ${dryRun ? 'would be updated' : 'updated'}`)
175
- console.log(' entries: # DEV-BOOSTER, .devbooster/, DEVBOOSTER_INIT.md\n')
183
+ console.log(` status: ${dryRun ? "would be updated" : "updated"}`);
184
+ console.log(" entries: # DEV-BOOSTER, .devbooster/, DEVBOOSTER_INIT.md\n");
176
185
  } else {
177
- console.log(' status: already configured')
178
- console.log(' entries: # DEV-BOOSTER, .devbooster/, DEVBOOSTER_INIT.md\n')
186
+ console.log(" status: already configured");
187
+ console.log(" entries: # DEV-BOOSTER, .devbooster/, DEVBOOSTER_INIT.md\n");
179
188
  }
180
189
  }
181
190
 
182
191
  function writeIdeBridgeFallbackFlag() {
183
- const flagPath = path.join(TARGET_DIR, '.devbooster', IDE_BRIDGE_FALLBACK_FLAG)
184
- fs.mkdirSync(path.dirname(flagPath), { recursive: true })
185
- fs.writeFileSync(flagPath, 'create AGENTS.md fallback bridge\n')
192
+ const flagPath = path.join(
193
+ TARGET_DIR,
194
+ ".devbooster",
195
+ IDE_BRIDGE_FALLBACK_FLAG,
196
+ );
197
+ fs.mkdirSync(path.dirname(flagPath), { recursive: true });
198
+ fs.writeFileSync(flagPath, "create AGENTS.md fallback bridge\n");
186
199
  }
187
200
 
188
201
  function clearIdeBridgeFallbackFlag() {
189
- const flagPath = path.join(TARGET_DIR, '.devbooster', IDE_BRIDGE_FALLBACK_FLAG)
202
+ const flagPath = path.join(
203
+ TARGET_DIR,
204
+ ".devbooster",
205
+ IDE_BRIDGE_FALLBACK_FLAG,
206
+ );
190
207
  if (fs.existsSync(flagPath)) {
191
- fs.rmSync(flagPath, { force: true })
208
+ fs.rmSync(flagPath, { force: true });
192
209
  }
193
210
  }
194
211
 
195
212
  function setupIdeBridgeFiles(dryRun) {
196
- const touchedFiles = []
213
+ const touchedFiles = [];
197
214
 
198
215
  for (const relativeTarget of IDE_BRIDGE_TARGETS) {
199
- const targetPath = path.join(TARGET_DIR, relativeTarget)
200
- if (!fs.existsSync(targetPath)) continue
201
-
202
- const wouldChange = appendUniqueBlock(targetPath, IDE_BRIDGE_BLOCK, IDE_BRIDGE_MARKER, dryRun)
203
- if (wouldChange) touchedFiles.push(relativeTarget)
216
+ const targetPath = path.join(TARGET_DIR, relativeTarget);
217
+ if (!fs.existsSync(targetPath)) continue;
218
+
219
+ const wouldChange = appendUniqueBlock(
220
+ targetPath,
221
+ IDE_BRIDGE_BLOCK,
222
+ IDE_BRIDGE_MARKER,
223
+ dryRun,
224
+ );
225
+ if (wouldChange) touchedFiles.push(relativeTarget);
204
226
  }
205
227
 
206
- console.log('▸ IDE bridge files')
228
+ console.log("▸ IDE bridge files");
207
229
  if (touchedFiles.length > 0) {
208
- if (!dryRun) clearIdeBridgeFallbackFlag()
209
- console.log(` status: bridge instructions ${dryRun ? 'would be appended' : 'appended'} where applicable`)
210
- console.log(` files: ${touchedFiles.join(', ')}\n`)
211
- return
230
+ if (!dryRun) clearIdeBridgeFallbackFlag();
231
+ console.log(
232
+ ` status: bridge instructions ${dryRun ? "would be appended" : "appended"} where applicable`,
233
+ );
234
+ console.log(` files: ${touchedFiles.join(", ")}\n`);
235
+ return;
212
236
  }
213
237
 
214
- if (!dryRun) writeIdeBridgeFallbackFlag()
215
- console.log(' status: no known IDE instruction file found')
216
- console.log(` action: bootstrap fallback flag ${dryRun ? 'would be created' : 'created'} for DEVBOOSTER_INIT.md\n`)
238
+ if (!dryRun) writeIdeBridgeFallbackFlag();
239
+ console.log(" status: no known IDE instruction file found");
240
+ console.log(
241
+ ` action: bootstrap fallback flag ${dryRun ? "would be created" : "created"} for DEVBOOSTER_INIT.md\n`,
242
+ );
243
+ }
244
+
245
+ function checkLocalInstall() {
246
+ try {
247
+ const localPkg = path.join(
248
+ TARGET_DIR,
249
+ "node_modules",
250
+ "dev-booster",
251
+ "package.json",
252
+ );
253
+ if (fs.existsSync(localPkg)) {
254
+ const pkg = JSON.parse(fs.readFileSync(localPkg, "utf8"));
255
+ console.log(
256
+ `\n⚠️ Dev Booster detected as a local dependency (npm i dev-booster) version ${pkg.version}.`,
257
+ );
258
+ console.log(" Removing local dependency to avoid version conflicts...");
259
+ console.log();
260
+ execSync("npm uninstall dev-booster", {
261
+ cwd: TARGET_DIR,
262
+ stdio: "inherit",
263
+ });
264
+ console.log("\n✔ Local dependency removed successfully.");
265
+ console.log(
266
+ " Always use npx --yes dev-booster@latest to get the latest version.\n",
267
+ );
268
+ }
269
+ } catch {
270
+ // Silently ignore if we cannot read the local package or uninstall fails
271
+ }
217
272
  }
218
273
 
219
274
  function printHeader(subtitle) {
@@ -222,59 +277,77 @@ function printHeader(subtitle) {
222
277
  │ DEV BOOSTER │
223
278
  │ ${subtitle.padEnd(44)} │
224
279
  ╰──────────────────────────────────────────────╯
225
- `)
280
+ `);
226
281
  }
227
282
 
228
283
  async function runInstall() {
229
- const dryRun = isDryRunMode()
230
- printHeader('agentic kit installer')
231
- if (dryRun) console.log('🔍 [DRY RUN MODE] No real changes will be made.\n')
232
-
233
- const agentSrc = path.join(TEMPLATE_DIR, '.devbooster')
234
- const agentDest = path.join(TARGET_DIR, '.devbooster')
235
-
236
- const boostersCount = fs.existsSync(path.join(agentSrc, 'boosters'))
237
- ? fs.readdirSync(path.join(agentSrc, 'boosters')).filter(f => f !== '.DS_Store' && f !== 'templates').length
238
- : 28
239
- const hubCount = fs.existsSync(path.join(agentSrc, 'hub', 'scripts'))
240
- ? fs.readdirSync(path.join(agentSrc, 'hub', 'scripts')).filter(f => f !== '.DS_Store').length
241
- : 21
242
-
243
- const isOverlap = fs.existsSync(agentDest)
284
+ const dryRun = isDryRunMode();
285
+ printHeader("agentic kit installer");
286
+ if (dryRun) console.log("🔍 [DRY RUN MODE] No real changes will be made.\n");
287
+
288
+ const agentSrc = path.join(TEMPLATE_DIR, ".devbooster");
289
+ const agentDest = path.join(TARGET_DIR, ".devbooster");
290
+
291
+ const boostersCount = fs.existsSync(path.join(agentSrc, "boosters"))
292
+ ? fs
293
+ .readdirSync(path.join(agentSrc, "boosters"))
294
+ .filter((f) => f !== ".DS_Store" && f !== "templates").length
295
+ : 28;
296
+ const hubCount = fs.existsSync(path.join(agentSrc, "hub", "scripts"))
297
+ ? fs
298
+ .readdirSync(path.join(agentSrc, "hub", "scripts"))
299
+ .filter((f) => f !== ".DS_Store").length
300
+ : 21;
301
+
302
+ const isOverlap = fs.existsSync(agentDest);
244
303
 
245
304
  if (isOverlap) {
246
- console.log('▸ Existing Kit Detected:')
247
- console.log(` ├── boosters/ ➔ ${boostersCount} expert activators updated successfully`)
248
- console.log(` ├── hub/ ➔ ${hubCount} operational scripts updated successfully`)
249
- console.log(' └── rules/ ➔ Core rules updated, project rules preserved')
250
- console.log(' ✔ PROTOCOL.md (updated)')
251
- console.log(' ✔ GUIDE.md (updated)')
252
- console.log(' ✔ TRIGGERS.md (updated)')
253
- console.log(' ℹ PROJECT.md, FRONTEND.md, BACKEND.md, etc. (preserved)\n')
305
+ console.log("▸ Existing Kit Detected:");
306
+ console.log(
307
+ ` ├── boosters/ ➔ ${boostersCount} expert activators updated successfully`,
308
+ );
309
+ console.log(
310
+ ` ├── hub/ ➔ ${hubCount} operational scripts updated successfully`,
311
+ );
312
+ console.log(
313
+ " └── rules/ ➔ Core rules updated, project rules preserved",
314
+ );
315
+ console.log(" ✔ PROTOCOL.md (updated)");
316
+ console.log(" ✔ GUIDE.md (updated)");
317
+ console.log(" ✔ TRIGGERS.md (updated)");
318
+ console.log(
319
+ " ℹ PROJECT.md, FRONTEND.md, BACKEND.md, etc. (preserved)\n",
320
+ );
254
321
  } else {
255
- console.log('▸ Installing Kit Structure:')
256
- console.log(` ├── boosters/ ➔ ${boostersCount} expert activators created successfully`)
257
- console.log(` ├── hub/ ➔ ${hubCount} operational scripts created successfully`)
258
- console.log(' └── rules/ ➔ Core rules and templates initialized')
259
- console.log(' ✔ 8 rules created\n')
322
+ console.log("▸ Installing Kit Structure:");
323
+ console.log(
324
+ ` ├── boosters/ ➔ ${boostersCount} expert activators created successfully`,
325
+ );
326
+ console.log(
327
+ ` ├── hub/ ➔ ${hubCount} operational scripts created successfully`,
328
+ );
329
+ console.log(" └── rules/ ➔ Core rules and templates initialized");
330
+ console.log(" ✔ 8 rules created\n");
260
331
  }
261
332
 
262
- copyDirSafe(agentSrc, agentDest, dryRun)
333
+ copyDirSafe(agentSrc, agentDest, dryRun);
263
334
 
264
335
  // 2. Drop DEVBOOSTER_INIT.md at the root of the user's project safely
265
- const initSrc = path.join(TEMPLATE_DIR, 'DEVBOOSTER_INIT.md')
266
- const initDest = path.join(TARGET_DIR, 'DEVBOOSTER_INIT.md')
336
+ const initSrc = path.join(TEMPLATE_DIR, "DEVBOOSTER_INIT.md");
337
+ const initDest = path.join(TARGET_DIR, "DEVBOOSTER_INIT.md");
267
338
 
268
- const copiedInit = safeCopyFile(initSrc, initDest, dryRun)
269
- console.log('▸ DEVBOOSTER_INIT.md')
339
+ const copiedInit = safeCopyFile(initSrc, initDest, dryRun);
340
+ console.log("▸ DEVBOOSTER_INIT.md");
270
341
  if (copiedInit) {
271
- console.log(` status: ${dryRun ? 'would be created' : 'created'} at project root\n`)
342
+ console.log(
343
+ ` status: ${dryRun ? "would be created" : "created"} at project root\n`,
344
+ );
272
345
  } else {
273
- console.log(' status: already exists (preserved)\n')
346
+ console.log(" status: already exists (preserved)\n");
274
347
  }
275
348
 
276
- await maybeAddDevBoosterToGitignore(dryRun)
277
- setupIdeBridgeFiles(dryRun)
349
+ await maybeAddDevBoosterToGitignore(dryRun);
350
+ setupIdeBridgeFiles(dryRun);
278
351
 
279
352
  console.log(`
280
353
  ╭──────────────────────────────────────────────╮
@@ -286,62 +359,84 @@ Open your AI assistant in this project and send:
286
359
  "Read DEVBOOSTER_INIT.md and execute all bootstrap steps."
287
360
 
288
361
  The kit will configure itself based on your project's stack.
289
- `)
362
+ `);
290
363
  }
291
364
 
292
365
  function runUpdate() {
293
- const dryRun = isDryRunMode()
294
- printHeader('safe kit update')
295
- if (dryRun) console.log('🔍 [DRY RUN MODE] No real changes will be made.\n')
366
+ const dryRun = isDryRunMode();
367
+ printHeader("safe kit update");
368
+ if (dryRun) console.log("🔍 [DRY RUN MODE] No real changes will be made.\n");
296
369
 
297
- const kitRoot = path.join(TARGET_DIR, '.devbooster')
298
- const templateRoot = path.join(TEMPLATE_DIR, '.devbooster')
370
+ const kitRoot = path.join(TARGET_DIR, ".devbooster");
371
+ const templateRoot = path.join(TEMPLATE_DIR, ".devbooster");
299
372
 
300
373
  if (!fs.existsSync(kitRoot)) {
301
- console.log('▸ .devbooster/')
302
- console.log(' status: not found in this project')
303
- console.log(' action: run `npx dev-booster` first to install the kit\n')
304
- return
374
+ console.log("▸ .devbooster/");
375
+ console.log(" status: not found in this project");
376
+ console.log(" action: run `npx dev-booster` first to install the kit\n");
377
+ return;
305
378
  }
306
379
 
307
- const boostersCount = fs.existsSync(path.join(templateRoot, 'boosters'))
308
- ? fs.readdirSync(path.join(templateRoot, 'boosters')).filter(f => f !== '.DS_Store' && f !== 'templates').length
309
- : 28
310
- const hubCount = fs.existsSync(path.join(templateRoot, 'hub', 'scripts'))
311
- ? fs.readdirSync(path.join(templateRoot, 'hub', 'scripts')).filter(f => f !== '.DS_Store').length
312
- : 21
380
+ const boostersCount = fs.existsSync(path.join(templateRoot, "boosters"))
381
+ ? fs
382
+ .readdirSync(path.join(templateRoot, "boosters"))
383
+ .filter((f) => f !== ".DS_Store" && f !== "templates").length
384
+ : 28;
385
+ const hubCount = fs.existsSync(path.join(templateRoot, "hub", "scripts"))
386
+ ? fs
387
+ .readdirSync(path.join(templateRoot, "hub", "scripts"))
388
+ .filter((f) => f !== ".DS_Store").length
389
+ : 21;
313
390
 
314
391
  // 1. Sync boosters and hub (fully wiped and overwritten)
315
392
  const updateTargets = [
316
- { name: 'boosters', src: path.join(templateRoot, 'boosters'), dest: path.join(kitRoot, 'boosters') },
317
- { name: 'hub', src: path.join(templateRoot, 'hub'), dest: path.join(kitRoot, 'hub') },
318
- ]
319
-
320
- console.log('▸ Updating Kit Core:')
393
+ {
394
+ name: "boosters",
395
+ src: path.join(templateRoot, "boosters"),
396
+ dest: path.join(kitRoot, "boosters"),
397
+ },
398
+ {
399
+ name: "hub",
400
+ src: path.join(templateRoot, "hub"),
401
+ dest: path.join(kitRoot, "hub"),
402
+ },
403
+ ];
404
+
405
+ console.log("▸ Updating Kit Core:");
321
406
 
322
407
  for (const target of updateTargets) {
323
- if (!dryRun) syncDir(target.src, target.dest)
408
+ if (!dryRun) syncDir(target.src, target.dest);
324
409
  }
325
- console.log(` ├── boosters/ ➔ ${boostersCount} expert activators synced & updated`)
326
- console.log(` ├── hub/ ➔ ${hubCount} operational scripts synced & updated`)
410
+ console.log(
411
+ ` ├── boosters/ ➔ ${boostersCount} expert activators synced & updated`,
412
+ );
413
+ console.log(
414
+ ` ├── hub/ ➔ ${hubCount} operational scripts synced & updated`,
415
+ );
327
416
 
328
417
  // 2. Safe-copy rules directory recursively (overwriting core rules, preserving custom ones)
329
- const rulesSrc = path.join(templateRoot, 'rules')
330
- const rulesDest = path.join(kitRoot, 'rules')
331
- copyDirSafe(rulesSrc, rulesDest, dryRun)
332
-
418
+ const rulesSrc = path.join(templateRoot, "rules");
419
+ const rulesDest = path.join(kitRoot, "rules");
420
+ copyDirSafe(rulesSrc, rulesDest, dryRun);
421
+
333
422
  // 3. Safe-copy core files
334
- safeCopyFile(path.join(templateRoot, 'MANIFEST.md'), path.join(kitRoot, 'MANIFEST.md'), dryRun)
335
-
336
- console.log(' ├── MANIFEST.md ➔ Inventory updated')
337
- console.log(' └── rules/ ➔ Core rules updated, customized rules preserved')
338
- console.log(' ✔ PROTOCOL.md (updated)')
339
- console.log(' ✔ GUIDE.md (updated)')
340
- console.log(' ✔ TRIGGERS.md (updated)')
341
- console.log(' ℹ Whitelabel stack rules (preserved)\n')
342
-
343
- console.log('▸ DEVBOOSTER_INIT.md')
344
- console.log(' status: preserved (no changes made)\n')
423
+ safeCopyFile(
424
+ path.join(templateRoot, "MANIFEST.md"),
425
+ path.join(kitRoot, "MANIFEST.md"),
426
+ dryRun,
427
+ );
428
+
429
+ console.log(" ├── MANIFEST.md ➔ Inventory updated");
430
+ console.log(
431
+ " └── rules/ ➔ Core rules updated, customized rules preserved",
432
+ );
433
+ console.log(" ✔ PROTOCOL.md (updated)");
434
+ console.log(" ✔ GUIDE.md (updated)");
435
+ console.log(" ✔ TRIGGERS.md (updated)");
436
+ console.log(" ℹ Whitelabel stack rules (preserved)\n");
437
+
438
+ console.log("▸ DEVBOOSTER_INIT.md");
439
+ console.log(" status: preserved (no changes made)\n");
345
440
 
346
441
  console.log(`
347
442
  ╭──────────────────────────────────────────────╮
@@ -350,14 +445,16 @@ function runUpdate() {
350
445
 
351
446
  Review the updated kit files in your project and, if you want,
352
447
  reopen your AI assistant to keep working with the refreshed boosters.
353
- `)
448
+ `);
354
449
  }
355
450
 
356
451
  export async function run() {
452
+ checkLocalInstall();
453
+
357
454
  if (isUpdateMode()) {
358
- runUpdate()
359
- return
455
+ runUpdate();
456
+ return;
360
457
  }
361
458
 
362
- await runInstall()
459
+ await runInstall();
363
460
  }