dev-booster 1.11.0 → 1.13.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/README.md +20 -13
- package/package.json +2 -2
- package/src/index.js +289 -192
- package/template/.devbooster/MANIFEST.md +23 -15
- package/template/.devbooster/boosters/advisor.md +7 -4
- package/template/.devbooster/boosters/code-audit.md +10 -7
- package/template/.devbooster/boosters/coder.md +1 -1
- package/template/.devbooster/boosters/context.md +9 -3
- package/template/.devbooster/boosters/debug.md +7 -4
- package/template/.devbooster/boosters/deploy.md +7 -4
- package/template/.devbooster/boosters/diff-review.md +201 -0
- package/template/.devbooster/boosters/discovery.md +7 -4
- package/template/.devbooster/boosters/global-documentation.md +7 -1
- package/template/.devbooster/boosters/implementation.md +9 -3
- package/template/.devbooster/boosters/internal-documentation.md +8 -2
- package/template/.devbooster/boosters/investigation.md +7 -4
- package/template/.devbooster/boosters/planning.md +7 -4
- package/template/.devbooster/boosters/save-context.md +106 -0
- package/template/.devbooster/boosters/security.md +7 -4
- package/template/.devbooster/hub/scripts/doctor_parser.py +5 -5
- package/template/.devbooster/rules/GUIDE.md +3 -0
- package/template/.devbooster/rules/PROTOCOL.md +1 -1
- package/template/.devbooster/rules/TRIGGERS.md +5 -3
- package/src/sync-template.js +0 -55
package/src/index.js
CHANGED
|
@@ -1,219 +1,274 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
]
|
|
25
|
-
const IDE_BRIDGE_MARKER =
|
|
26
|
-
const IDE_BRIDGE_FALLBACK_FLAG = path.join(
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
107
|
+
return args.includes("--update") || args.includes("update");
|
|
111
108
|
}
|
|
112
109
|
|
|
113
110
|
function isDryRunMode() {
|
|
114
|
-
return args.includes(
|
|
111
|
+
return args.includes("--dry-run") || args.includes("dry-run");
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
function ensureTrailingNewline(content) {
|
|
118
|
-
return content.endsWith(
|
|
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)
|
|
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(
|
|
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 ?
|
|
149
|
-
const answer = (await rl.question(`${question}${suffix}`))
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (answer
|
|
154
|
-
return
|
|
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(
|
|
162
|
+
const shouldIgnore = await askYesNo(
|
|
163
|
+
"Add Dev Booster to your .gitignore?",
|
|
164
|
+
true,
|
|
165
|
+
);
|
|
162
166
|
|
|
163
167
|
if (!shouldIgnore) {
|
|
164
|
-
console.log(
|
|
165
|
-
console.log(
|
|
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,
|
|
170
|
-
const changed = appendUniqueBlock(
|
|
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(
|
|
181
|
+
console.log("▸ .gitignore");
|
|
173
182
|
if (changed) {
|
|
174
|
-
console.log(` status: ${dryRun ?
|
|
175
|
-
console.log(
|
|
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(
|
|
178
|
-
console.log(
|
|
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(
|
|
184
|
-
|
|
185
|
-
|
|
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(
|
|
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(
|
|
203
|
-
|
|
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(
|
|
228
|
+
console.log("▸ IDE bridge files");
|
|
207
229
|
if (touchedFiles.length > 0) {
|
|
208
|
-
if (!dryRun) clearIdeBridgeFallbackFlag()
|
|
209
|
-
console.log(
|
|
210
|
-
|
|
211
|
-
|
|
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(
|
|
216
|
-
console.log(
|
|
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(
|
|
231
|
-
if (dryRun) console.log(
|
|
232
|
-
|
|
233
|
-
const agentSrc = path.join(TEMPLATE_DIR,
|
|
234
|
-
const agentDest = path.join(TARGET_DIR,
|
|
235
|
-
|
|
236
|
-
const boostersCount = fs.existsSync(path.join(agentSrc,
|
|
237
|
-
? fs
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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(
|
|
247
|
-
console.log(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
console.log(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
console.log(
|
|
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(
|
|
256
|
-
console.log(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
console.log(
|
|
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,
|
|
266
|
-
const initDest = path.join(TARGET_DIR,
|
|
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(
|
|
339
|
+
const copiedInit = safeCopyFile(initSrc, initDest, dryRun);
|
|
340
|
+
console.log("▸ DEVBOOSTER_INIT.md");
|
|
270
341
|
if (copiedInit) {
|
|
271
|
-
console.log(
|
|
342
|
+
console.log(
|
|
343
|
+
` status: ${dryRun ? "would be created" : "created"} at project root\n`,
|
|
344
|
+
);
|
|
272
345
|
} else {
|
|
273
|
-
console.log(
|
|
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(
|
|
295
|
-
if (dryRun) console.log(
|
|
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,
|
|
298
|
-
const templateRoot = path.join(TEMPLATE_DIR,
|
|
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(
|
|
302
|
-
console.log(
|
|
303
|
-
console.log(
|
|
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,
|
|
308
|
-
? fs
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
{
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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(
|
|
326
|
-
|
|
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,
|
|
330
|
-
const rulesDest = path.join(kitRoot,
|
|
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(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
console.log(
|
|
341
|
-
console.log(
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
console.log(
|
|
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
|
}
|