dev-booster 1.10.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/README.md +36 -16
- package/package.json +2 -2
- package/src/index.js +319 -166
- package/template/.devbooster/MANIFEST.md +25 -14
- package/template/.devbooster/boosters/advisor.md +8 -4
- package/template/.devbooster/boosters/builder.md +65 -0
- package/template/.devbooster/boosters/code-audit.md +10 -7
- package/template/.devbooster/boosters/coder.md +50 -0
- 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 +199 -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 +14 -0
- package/template/.devbooster/rules/PROTOCOL.md +3 -19
- package/template/.devbooster/rules/TRIGGERS.md +48 -0
- package/template/DEVBOOSTER_INIT.md +1 -1
- package/src/sync-template.js +0 -55
package/src/index.js
CHANGED
|
@@ -1,181 +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
|
+
];
|
|
35
|
+
|
|
36
|
+
const PROTECTED_FILES = [
|
|
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
|
+
];
|
|
44
|
+
|
|
45
|
+
function safeCopyFile(src, dest, dryRun = false) {
|
|
46
|
+
const relativeDest = path.relative(TARGET_DIR, dest);
|
|
47
|
+
if (fs.existsSync(dest) && PROTECTED_FILES.includes(relativeDest)) {
|
|
48
|
+
return false; // Preserved
|
|
49
|
+
}
|
|
50
|
+
if (!dryRun) {
|
|
51
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
52
|
+
fs.copyFileSync(src, dest);
|
|
53
|
+
}
|
|
54
|
+
return true; // Copied/Updated
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function copyDirSafe(src, dest, dryRun = false) {
|
|
58
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
59
|
+
|
|
60
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
61
|
+
if (entry.name === ".DS_Store") continue;
|
|
62
|
+
|
|
63
|
+
const srcPath = path.join(src, entry.name);
|
|
64
|
+
const destPath = path.join(dest, entry.name);
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
copyDirSafe(srcPath, destPath, dryRun);
|
|
68
|
+
} else {
|
|
69
|
+
safeCopyFile(srcPath, destPath, dryRun);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
38
73
|
|
|
39
74
|
function copyDir(src, dest) {
|
|
40
|
-
fs.mkdirSync(dest, { recursive: true })
|
|
75
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
41
76
|
|
|
42
77
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
43
|
-
if (entry.name ===
|
|
78
|
+
if (entry.name === ".DS_Store") continue;
|
|
44
79
|
|
|
45
|
-
const srcPath = path.join(src, entry.name)
|
|
46
|
-
const destPath = path.join(dest, entry.name)
|
|
80
|
+
const srcPath = path.join(src, entry.name);
|
|
81
|
+
const destPath = path.join(dest, entry.name);
|
|
47
82
|
|
|
48
83
|
if (entry.isDirectory()) {
|
|
49
|
-
copyDir(srcPath, destPath)
|
|
84
|
+
copyDir(srcPath, destPath);
|
|
50
85
|
} else {
|
|
51
|
-
fs.copyFileSync(srcPath, destPath)
|
|
86
|
+
fs.copyFileSync(srcPath, destPath);
|
|
52
87
|
}
|
|
53
88
|
}
|
|
54
89
|
}
|
|
55
90
|
|
|
56
91
|
function removeDirContents(targetDir) {
|
|
57
|
-
if (!fs.existsSync(targetDir)) return
|
|
92
|
+
if (!fs.existsSync(targetDir)) return;
|
|
58
93
|
|
|
59
94
|
for (const entry of fs.readdirSync(targetDir, { withFileTypes: true })) {
|
|
60
|
-
const entryPath = path.join(targetDir, entry.name)
|
|
61
|
-
fs.rmSync(entryPath, { recursive: true, force: true })
|
|
95
|
+
const entryPath = path.join(targetDir, entry.name);
|
|
96
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
62
97
|
}
|
|
63
98
|
}
|
|
64
99
|
|
|
65
100
|
function syncDir(src, dest) {
|
|
66
|
-
fs.mkdirSync(dest, { recursive: true })
|
|
67
|
-
removeDirContents(dest)
|
|
68
|
-
copyDir(src, dest)
|
|
101
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
102
|
+
removeDirContents(dest);
|
|
103
|
+
copyDir(src, dest);
|
|
69
104
|
}
|
|
70
105
|
|
|
71
106
|
function isUpdateMode() {
|
|
72
|
-
return args.includes(
|
|
107
|
+
return args.includes("--update") || args.includes("update");
|
|
73
108
|
}
|
|
74
109
|
|
|
75
110
|
function isDryRunMode() {
|
|
76
|
-
return args.includes(
|
|
111
|
+
return args.includes("--dry-run") || args.includes("dry-run");
|
|
77
112
|
}
|
|
78
113
|
|
|
79
114
|
function ensureTrailingNewline(content) {
|
|
80
|
-
return content.endsWith(
|
|
115
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
81
116
|
}
|
|
82
117
|
|
|
83
118
|
function appendUniqueBlock(filePath, blockLines, marker, dryRun = false) {
|
|
84
|
-
const existing = fs.existsSync(filePath)
|
|
119
|
+
const existing = fs.existsSync(filePath)
|
|
120
|
+
? fs.readFileSync(filePath, "utf8")
|
|
121
|
+
: "";
|
|
85
122
|
if (existing.includes(marker)) {
|
|
86
|
-
return false
|
|
123
|
+
return false;
|
|
87
124
|
}
|
|
88
125
|
|
|
89
126
|
if (!dryRun) {
|
|
90
|
-
const trimmedEnd = existing.replace(/\s*$/,
|
|
91
|
-
const prefix = trimmedEnd.length > 0 ? `${trimmedEnd}\n\n` :
|
|
92
|
-
const nextContent = `${prefix}${blockLines.join(
|
|
93
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
94
|
-
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);
|
|
95
132
|
}
|
|
96
|
-
return true
|
|
133
|
+
return true;
|
|
97
134
|
}
|
|
98
135
|
|
|
99
136
|
async function askYesNo(question, defaultYes = true) {
|
|
100
137
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
101
|
-
return defaultYes
|
|
138
|
+
return defaultYes;
|
|
102
139
|
}
|
|
103
140
|
|
|
104
141
|
const rl = readline.createInterface({
|
|
105
142
|
input: process.stdin,
|
|
106
143
|
output: process.stdout,
|
|
107
|
-
})
|
|
144
|
+
});
|
|
108
145
|
|
|
109
146
|
try {
|
|
110
|
-
const suffix = defaultYes ?
|
|
111
|
-
const answer = (await rl.question(`${question}${suffix}`))
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (answer
|
|
116
|
-
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;
|
|
117
156
|
} finally {
|
|
118
|
-
rl.close()
|
|
157
|
+
rl.close();
|
|
119
158
|
}
|
|
120
159
|
}
|
|
121
160
|
|
|
122
161
|
async function maybeAddDevBoosterToGitignore(dryRun) {
|
|
123
|
-
const shouldIgnore = await askYesNo(
|
|
162
|
+
const shouldIgnore = await askYesNo(
|
|
163
|
+
"Add Dev Booster to your .gitignore?",
|
|
164
|
+
true,
|
|
165
|
+
);
|
|
124
166
|
|
|
125
167
|
if (!shouldIgnore) {
|
|
126
|
-
console.log(
|
|
127
|
-
console.log(
|
|
128
|
-
return
|
|
168
|
+
console.log("▸ .gitignore");
|
|
169
|
+
console.log(" status: skipped by user\n");
|
|
170
|
+
return;
|
|
129
171
|
}
|
|
130
172
|
|
|
131
|
-
const gitignorePath = path.join(TARGET_DIR,
|
|
132
|
-
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
|
+
);
|
|
133
180
|
|
|
134
|
-
console.log(
|
|
181
|
+
console.log("▸ .gitignore");
|
|
135
182
|
if (changed) {
|
|
136
|
-
console.log(` status: ${dryRun ?
|
|
137
|
-
console.log(
|
|
183
|
+
console.log(` status: ${dryRun ? "would be updated" : "updated"}`);
|
|
184
|
+
console.log(" entries: # DEV-BOOSTER, .devbooster/, DEVBOOSTER_INIT.md\n");
|
|
138
185
|
} else {
|
|
139
|
-
console.log(
|
|
140
|
-
console.log(
|
|
186
|
+
console.log(" status: already configured");
|
|
187
|
+
console.log(" entries: # DEV-BOOSTER, .devbooster/, DEVBOOSTER_INIT.md\n");
|
|
141
188
|
}
|
|
142
189
|
}
|
|
143
190
|
|
|
144
191
|
function writeIdeBridgeFallbackFlag() {
|
|
145
|
-
const flagPath = path.join(
|
|
146
|
-
|
|
147
|
-
|
|
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");
|
|
148
199
|
}
|
|
149
200
|
|
|
150
201
|
function clearIdeBridgeFallbackFlag() {
|
|
151
|
-
const flagPath = path.join(
|
|
202
|
+
const flagPath = path.join(
|
|
203
|
+
TARGET_DIR,
|
|
204
|
+
".devbooster",
|
|
205
|
+
IDE_BRIDGE_FALLBACK_FLAG,
|
|
206
|
+
);
|
|
152
207
|
if (fs.existsSync(flagPath)) {
|
|
153
|
-
fs.rmSync(flagPath, { force: true })
|
|
208
|
+
fs.rmSync(flagPath, { force: true });
|
|
154
209
|
}
|
|
155
210
|
}
|
|
156
211
|
|
|
157
212
|
function setupIdeBridgeFiles(dryRun) {
|
|
158
|
-
const touchedFiles = []
|
|
213
|
+
const touchedFiles = [];
|
|
159
214
|
|
|
160
215
|
for (const relativeTarget of IDE_BRIDGE_TARGETS) {
|
|
161
|
-
const targetPath = path.join(TARGET_DIR, relativeTarget)
|
|
162
|
-
if (!fs.existsSync(targetPath)) continue
|
|
163
|
-
|
|
164
|
-
const wouldChange = appendUniqueBlock(
|
|
165
|
-
|
|
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);
|
|
166
226
|
}
|
|
167
227
|
|
|
168
|
-
console.log(
|
|
228
|
+
console.log("▸ IDE bridge files");
|
|
169
229
|
if (touchedFiles.length > 0) {
|
|
170
|
-
if (!dryRun) clearIdeBridgeFallbackFlag()
|
|
171
|
-
console.log(
|
|
172
|
-
|
|
173
|
-
|
|
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;
|
|
174
236
|
}
|
|
175
237
|
|
|
176
|
-
if (!dryRun) writeIdeBridgeFallbackFlag()
|
|
177
|
-
console.log(
|
|
178
|
-
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
|
+
}
|
|
179
272
|
}
|
|
180
273
|
|
|
181
274
|
function printHeader(subtitle) {
|
|
@@ -184,45 +277,77 @@ function printHeader(subtitle) {
|
|
|
184
277
|
│ DEV BOOSTER │
|
|
185
278
|
│ ${subtitle.padEnd(44)} │
|
|
186
279
|
╰──────────────────────────────────────────────╯
|
|
187
|
-
`)
|
|
280
|
+
`);
|
|
188
281
|
}
|
|
189
282
|
|
|
190
283
|
async function runInstall() {
|
|
191
|
-
const dryRun = isDryRunMode()
|
|
192
|
-
printHeader(
|
|
193
|
-
if (dryRun) console.log(
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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);
|
|
303
|
+
|
|
304
|
+
if (isOverlap) {
|
|
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
|
+
);
|
|
204
321
|
} else {
|
|
205
|
-
|
|
206
|
-
console.log(
|
|
207
|
-
|
|
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");
|
|
208
331
|
}
|
|
209
332
|
|
|
210
|
-
|
|
211
|
-
const initSrc = path.join(TEMPLATE_DIR, 'DEVBOOSTER_INIT.md')
|
|
212
|
-
const initDest = path.join(TARGET_DIR, 'DEVBOOSTER_INIT.md')
|
|
333
|
+
copyDirSafe(agentSrc, agentDest, dryRun);
|
|
213
334
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
335
|
+
// 2. Drop DEVBOOSTER_INIT.md at the root of the user's project safely
|
|
336
|
+
const initSrc = path.join(TEMPLATE_DIR, "DEVBOOSTER_INIT.md");
|
|
337
|
+
const initDest = path.join(TARGET_DIR, "DEVBOOSTER_INIT.md");
|
|
338
|
+
|
|
339
|
+
const copiedInit = safeCopyFile(initSrc, initDest, dryRun);
|
|
340
|
+
console.log("▸ DEVBOOSTER_INIT.md");
|
|
341
|
+
if (copiedInit) {
|
|
342
|
+
console.log(
|
|
343
|
+
` status: ${dryRun ? "would be created" : "created"} at project root\n`,
|
|
344
|
+
);
|
|
218
345
|
} else {
|
|
219
|
-
|
|
220
|
-
console.log('▸ DEVBOOSTER_INIT.md')
|
|
221
|
-
console.log(` status: ${dryRun ? 'would be created' : 'created'} at project root\n`)
|
|
346
|
+
console.log(" status: already exists (preserved)\n");
|
|
222
347
|
}
|
|
223
348
|
|
|
224
|
-
await maybeAddDevBoosterToGitignore(dryRun)
|
|
225
|
-
setupIdeBridgeFiles(dryRun)
|
|
349
|
+
await maybeAddDevBoosterToGitignore(dryRun);
|
|
350
|
+
setupIdeBridgeFiles(dryRun);
|
|
226
351
|
|
|
227
352
|
console.log(`
|
|
228
353
|
╭──────────────────────────────────────────────╮
|
|
@@ -234,58 +359,84 @@ Open your AI assistant in this project and send:
|
|
|
234
359
|
"Read DEVBOOSTER_INIT.md and execute all bootstrap steps."
|
|
235
360
|
|
|
236
361
|
The kit will configure itself based on your project's stack.
|
|
237
|
-
`)
|
|
362
|
+
`);
|
|
238
363
|
}
|
|
239
364
|
|
|
240
365
|
function runUpdate() {
|
|
241
|
-
const dryRun = isDryRunMode()
|
|
242
|
-
printHeader(
|
|
243
|
-
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");
|
|
244
369
|
|
|
245
|
-
const kitRoot = path.join(TARGET_DIR,
|
|
246
|
-
const templateRoot = path.join(TEMPLATE_DIR,
|
|
370
|
+
const kitRoot = path.join(TARGET_DIR, ".devbooster");
|
|
371
|
+
const templateRoot = path.join(TEMPLATE_DIR, ".devbooster");
|
|
247
372
|
|
|
248
373
|
if (!fs.existsSync(kitRoot)) {
|
|
249
|
-
console.log(
|
|
250
|
-
console.log(
|
|
251
|
-
console.log(
|
|
252
|
-
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;
|
|
253
378
|
}
|
|
254
379
|
|
|
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;
|
|
390
|
+
|
|
391
|
+
// 1. Sync boosters and hub (fully wiped and overwritten)
|
|
255
392
|
const updateTargets = [
|
|
256
|
-
{
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
{
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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:");
|
|
265
406
|
|
|
266
407
|
for (const target of updateTargets) {
|
|
267
|
-
if (!dryRun) syncDir(target.src, target.dest)
|
|
268
|
-
console.log(`▸ .devbooster/${target.name}/`)
|
|
269
|
-
console.log(` status: ${dryRun ? 'would be updated' : 'updated successfully'}`)
|
|
408
|
+
if (!dryRun) syncDir(target.src, target.dest);
|
|
270
409
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
410
|
+
console.log(
|
|
411
|
+
` ├── boosters/ ➔ ${boostersCount} expert activators synced & updated`,
|
|
412
|
+
);
|
|
413
|
+
console.log(
|
|
414
|
+
` ├── hub/ ➔ ${hubCount} operational scripts synced & updated`,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
// 2. Safe-copy rules directory recursively (overwriting core rules, preserving custom ones)
|
|
418
|
+
const rulesSrc = path.join(templateRoot, "rules");
|
|
419
|
+
const rulesDest = path.join(kitRoot, "rules");
|
|
420
|
+
copyDirSafe(rulesSrc, rulesDest, dryRun);
|
|
421
|
+
|
|
422
|
+
// 3. Safe-copy core files
|
|
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");
|
|
289
440
|
|
|
290
441
|
console.log(`
|
|
291
442
|
╭──────────────────────────────────────────────╮
|
|
@@ -294,14 +445,16 @@ function runUpdate() {
|
|
|
294
445
|
|
|
295
446
|
Review the updated kit files in your project and, if you want,
|
|
296
447
|
reopen your AI assistant to keep working with the refreshed boosters.
|
|
297
|
-
`)
|
|
448
|
+
`);
|
|
298
449
|
}
|
|
299
450
|
|
|
300
451
|
export async function run() {
|
|
452
|
+
checkLocalInstall();
|
|
453
|
+
|
|
301
454
|
if (isUpdateMode()) {
|
|
302
|
-
runUpdate()
|
|
303
|
-
return
|
|
455
|
+
runUpdate();
|
|
456
|
+
return;
|
|
304
457
|
}
|
|
305
458
|
|
|
306
|
-
await runInstall()
|
|
459
|
+
await runInstall();
|
|
307
460
|
}
|