@boopkit/cli 0.6.12 → 0.6.14
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 +27 -6
- package/dist/capability-versions.json +16 -16
- package/dist/cli.js +9 -1
- package/dist/commands/add.js +49 -2
- package/dist/commands/init.d.ts +17 -1
- package/dist/commands/init.js +151 -26
- package/dist/commands/loop-merge.d.ts +1 -0
- package/dist/commands/loop-merge.js +4 -2
- package/dist/commands/loop.d.ts +21 -2
- package/dist/commands/loop.js +105 -16
- package/dist/commands/loop.js.orig-pre-maxbuffer +1455 -0
- package/dist/commands/migrate-skill-edits.d.ts +24 -0
- package/dist/commands/migrate-skill-edits.js +190 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +13 -0
- package/dist/commands/update.js +55 -7
- package/dist/lib/apply-skill-edits.d.ts +76 -0
- package/dist/lib/apply-skill-edits.js +307 -0
- package/dist/lib/execute.js +1 -0
- package/dist/lib/original-text.d.ts +43 -0
- package/dist/lib/original-text.js +165 -0
- package/dist/lib/scaffold.d.ts +41 -7
- package/dist/lib/scaffold.js +96 -15
- package/dist/lib/skill-edits.d.ts +88 -0
- package/dist/lib/skill-edits.js +293 -0
- package/dist/lib/update.d.ts +38 -2
- package/dist/lib/update.js +149 -6
- package/docs/init.md +132 -86
- package/docs/loop.md +16 -0
- package/docs/skills.md +1 -0
- package/package.json +3 -2
- package/profiles/base/profile.md +105 -0
- package/profiles/base/templates/.github/copilot-instructions.md +3 -0
- package/profiles/base/templates/.gitignore.hbs +6 -0
- package/profiles/base/templates/AGENTS.md.hbs +50 -0
- package/profiles/base/templates/CLAUDE.md.hbs +3 -0
- package/profiles/base/templates/README.md.hbs +15 -0
- package/profiles/base/templates/docs/README.md +12 -0
- package/profiles/base/templates/package.json.hbs +7 -0
- package/profiles/base/templates/reference/README.md +16 -0
- package/profiles/base/templates/specs/README.md +12 -0
- package/profiles/development/profile.md +8 -1
- package/src/templates/skill-project/PROJECT.md +28 -0
package/dist/lib/update.js
CHANGED
|
@@ -39,9 +39,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
};
|
|
40
40
|
})();
|
|
41
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.SKILL_EDITS_INSTRUCTION = exports.TRIAGE_SKILL = void 0;
|
|
42
43
|
exports.compareFileHash = compareFileHash;
|
|
43
44
|
exports.planCapabilityUpdate = planCapabilityUpdate;
|
|
44
45
|
exports.executeUpdatePlan = executeUpdatePlan;
|
|
46
|
+
exports.countSkillEdits = countSkillEdits;
|
|
45
47
|
exports.formatUpdateSummary = formatUpdateSummary;
|
|
46
48
|
const fs = __importStar(require("fs"));
|
|
47
49
|
const path = __importStar(require("path"));
|
|
@@ -50,6 +52,10 @@ const capability_1 = require("./capability");
|
|
|
50
52
|
const project_1 = require("./project");
|
|
51
53
|
const safe_io_1 = require("./safe-io");
|
|
52
54
|
const install_layout_1 = require("./install-layout");
|
|
55
|
+
/** The skill that splits a drifted SKILL.md — keep in step with the folder name boop-rm2l.8 gives it */
|
|
56
|
+
exports.TRIAGE_SKILL = 'boop-skills-edit-triage';
|
|
57
|
+
/** What to do about skills edited in SKILL.md, for a report that counts them */
|
|
58
|
+
exports.SKILL_EDITS_INSTRUCTION = `Move each skill's edits into the PROJECT.md beside it: run \`boop migrate-skill-edits\` and follow the ${exports.TRIAGE_SKILL} skill.`;
|
|
53
59
|
/**
|
|
54
60
|
* Compare a file's current hash against a stored hash.
|
|
55
61
|
*/
|
|
@@ -96,9 +102,22 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
96
102
|
const destPath = path.join(targetDir, relPath);
|
|
97
103
|
const storedHash = storedHashes[relPath];
|
|
98
104
|
const newHash = (0, scaffold_1.hashFile)(srcPath);
|
|
105
|
+
const fileKind = fileKindOf(relPath);
|
|
99
106
|
if (!storedHash) {
|
|
100
107
|
// New file — not in stored hashes
|
|
101
|
-
if (fs.existsSync(destPath)) {
|
|
108
|
+
if (fs.existsSync(destPath) && (0, scaffold_1.hashFile)(destPath) === newHash) {
|
|
109
|
+
// Untracked but already holds what boop ships, as a hand-split skill does — start tracking it
|
|
110
|
+
actions.push({
|
|
111
|
+
relativePath: relPath,
|
|
112
|
+
action: 'record-hash',
|
|
113
|
+
reason: 'Locally matches package',
|
|
114
|
+
currentHash: newHash,
|
|
115
|
+
newHash,
|
|
116
|
+
drift: 'already-upstream',
|
|
117
|
+
fileKind,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
else if (fs.existsSync(destPath)) {
|
|
102
121
|
// File exists but wasn't tracked — treat as modified (skip)
|
|
103
122
|
actions.push({
|
|
104
123
|
relativePath: relPath,
|
|
@@ -106,6 +125,8 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
106
125
|
reason: 'File exists but has no stored hash (untracked)',
|
|
107
126
|
currentHash: (0, scaffold_1.hashFile)(destPath),
|
|
108
127
|
newHash,
|
|
128
|
+
fileKind,
|
|
129
|
+
...skipInstruction(targetDir, relPath, srcPath, fileKind),
|
|
109
130
|
});
|
|
110
131
|
}
|
|
111
132
|
else {
|
|
@@ -114,6 +135,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
114
135
|
action: 'add',
|
|
115
136
|
reason: 'New file from package',
|
|
116
137
|
newHash,
|
|
138
|
+
fileKind,
|
|
117
139
|
});
|
|
118
140
|
}
|
|
119
141
|
continue;
|
|
@@ -134,19 +156,40 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
134
156
|
storedHash,
|
|
135
157
|
currentHash: storedHash,
|
|
136
158
|
newHash,
|
|
159
|
+
fileKind,
|
|
160
|
+
instruction: fileKind === 'skill' ? offeredRulesInstruction(targetDir, relPath) : undefined,
|
|
137
161
|
});
|
|
138
162
|
break;
|
|
139
|
-
case 'modified':
|
|
140
|
-
|
|
163
|
+
case 'modified': {
|
|
164
|
+
const currentHash = (0, scaffold_1.hashFile)(destPath);
|
|
165
|
+
if (currentHash === newHash) {
|
|
166
|
+
// Already holds what boop ships — move the base forward, or it reads as modified forever
|
|
167
|
+
actions.push({
|
|
168
|
+
relativePath: relPath,
|
|
169
|
+
action: 'record-hash',
|
|
170
|
+
reason: 'Locally matches package',
|
|
171
|
+
storedHash,
|
|
172
|
+
currentHash,
|
|
173
|
+
newHash,
|
|
174
|
+
drift: 'already-upstream',
|
|
175
|
+
fileKind,
|
|
176
|
+
});
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
const drift = newHash === storedHash ? 'project-only' : 'both-changed';
|
|
141
180
|
actions.push({
|
|
142
181
|
relativePath: relPath,
|
|
143
182
|
action: 'skip',
|
|
144
183
|
reason: 'Locally modified',
|
|
145
184
|
storedHash,
|
|
146
|
-
currentHash
|
|
185
|
+
currentHash,
|
|
147
186
|
newHash,
|
|
187
|
+
drift,
|
|
188
|
+
fileKind,
|
|
189
|
+
...skipInstruction(targetDir, relPath, srcPath, fileKind, drift),
|
|
148
190
|
});
|
|
149
191
|
break;
|
|
192
|
+
}
|
|
150
193
|
case 'missing':
|
|
151
194
|
// File was deleted locally — respect user's deletion, skip by default
|
|
152
195
|
actions.push({
|
|
@@ -155,6 +198,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
155
198
|
reason: 'Locally deleted',
|
|
156
199
|
storedHash,
|
|
157
200
|
newHash,
|
|
201
|
+
fileKind,
|
|
158
202
|
});
|
|
159
203
|
break;
|
|
160
204
|
}
|
|
@@ -185,18 +229,25 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
185
229
|
const oldFilePath = path.join(targetDir, oldRelPath);
|
|
186
230
|
const oldState = compareFileHash(oldFilePath, oldStoredHash);
|
|
187
231
|
if (oldState === 'modified') {
|
|
188
|
-
// User modified the old file —
|
|
232
|
+
// User modified the old file — keep it, and don't install the folder that replaces it.
|
|
233
|
+
// A folder file already installed has its own comparison, so only an install is held back.
|
|
189
234
|
for (const action of actions) {
|
|
190
|
-
if (action.relativePath.startsWith(newPrefix) || action.relativePath === path.join('skills', newSkillName)) {
|
|
235
|
+
if (action.action === 'add' && (action.relativePath.startsWith(newPrefix) || action.relativePath === path.join('skills', newSkillName))) {
|
|
191
236
|
action.action = 'skip';
|
|
192
237
|
action.reason = `Skipped: old skill file '${oldRelPath}' was locally modified`;
|
|
238
|
+
action.oldPath = oldRelPath;
|
|
239
|
+
action.instruction = `boop installs this once ${oldRelPath} is gone; see what to do about that file.`;
|
|
193
240
|
}
|
|
194
241
|
}
|
|
195
242
|
// Change deprecate to skip for the old file
|
|
196
243
|
const oldAction = actions.find(a => a.relativePath === oldRelPath);
|
|
197
244
|
if (oldAction) {
|
|
245
|
+
const projectFile = path.join('skills', newSkillName, 'PROJECT.md');
|
|
198
246
|
oldAction.action = 'skip';
|
|
199
247
|
oldAction.reason = 'Locally modified (migration skipped)';
|
|
248
|
+
oldAction.replacedBy = newSkillName;
|
|
249
|
+
oldAction.instruction = `This is the old single-file form of the ${newSkillName} skill, and this project edited it. ` +
|
|
250
|
+
`Move the rules this project needs from it into ${projectFile}, following the ${exports.TRIAGE_SKILL} skill, then delete it and run \`boop update\`.`;
|
|
200
251
|
}
|
|
201
252
|
}
|
|
202
253
|
else if (oldState === 'match') {
|
|
@@ -245,6 +296,7 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
245
296
|
pruned: [],
|
|
246
297
|
migrated: [],
|
|
247
298
|
unchangedCount: plan.unchangedCount,
|
|
299
|
+
instructions: [],
|
|
248
300
|
};
|
|
249
301
|
const pkgJson = JSON.parse(fs.readFileSync(path.join(plan.packageDir, 'package.json'), 'utf-8'));
|
|
250
302
|
const boopkit = pkgJson.boopkit;
|
|
@@ -264,9 +316,16 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
264
316
|
fs.copyFileSync(srcPath, destPath);
|
|
265
317
|
newHashes[action.relativePath] = (0, scaffold_1.hashFile)(destPath);
|
|
266
318
|
result.overwritten.push(action.relativePath);
|
|
319
|
+
if (action.instruction) {
|
|
320
|
+
result.instructions.push({ relativePath: action.relativePath, instruction: action.instruction });
|
|
321
|
+
}
|
|
267
322
|
}
|
|
268
323
|
break;
|
|
269
324
|
}
|
|
325
|
+
case 'record-hash':
|
|
326
|
+
newHashes[action.relativePath] = action.newHash;
|
|
327
|
+
result.unchangedCount++;
|
|
328
|
+
break;
|
|
270
329
|
case 'skip': {
|
|
271
330
|
const isLocallyDeleted = action.reason === 'Locally deleted';
|
|
272
331
|
if (isLocallyDeleted && options.restore) {
|
|
@@ -291,6 +350,10 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
291
350
|
newHashes[action.relativePath] = (0, scaffold_1.hashFile)(destPath);
|
|
292
351
|
result.overwritten.push(action.relativePath);
|
|
293
352
|
result.forcedOverwrite.push(action.relativePath);
|
|
353
|
+
const instruction = action.fileKind === 'skill' ? offeredRulesInstruction(plan.targetDir, action.relativePath) : undefined;
|
|
354
|
+
if (instruction) {
|
|
355
|
+
result.instructions.push({ relativePath: action.relativePath, instruction });
|
|
356
|
+
}
|
|
294
357
|
console.warn(`\x1b[33mWARN\x1b[0m Force-overwriting modified file: ${action.relativePath} (backup saved as ${action.relativePath}.bak)`);
|
|
295
358
|
}
|
|
296
359
|
}
|
|
@@ -299,6 +362,13 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
299
362
|
if (isLocallyDeleted) {
|
|
300
363
|
result.skippedDeleted.push(action.relativePath);
|
|
301
364
|
}
|
|
365
|
+
if (action.instruction) {
|
|
366
|
+
result.instructions.push({
|
|
367
|
+
relativePath: action.relativePath,
|
|
368
|
+
instruction: action.instruction,
|
|
369
|
+
upstreamPath: action.upstreamPath,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
302
372
|
// Keep existing hash unchanged
|
|
303
373
|
}
|
|
304
374
|
break;
|
|
@@ -355,6 +425,79 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
355
425
|
}
|
|
356
426
|
return result;
|
|
357
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* What to tell whoever ran the update about a file it skipped, or nothing.
|
|
430
|
+
* `drift` is absent for a file boop has no stored hash for.
|
|
431
|
+
*
|
|
432
|
+
* An edited SKILL.md always gets an instruction: any edit there is project
|
|
433
|
+
* content in boop's file. An edited reference page or code file is doing what
|
|
434
|
+
* those files are for, so it gets one only when boop has changed it too.
|
|
435
|
+
*/
|
|
436
|
+
function skipInstruction(targetDir, relPath, srcPath, fileKind, drift) {
|
|
437
|
+
const upstreamPath = displayPath(targetDir, srcPath);
|
|
438
|
+
if (!drift) {
|
|
439
|
+
const untracked = `boop has no record of installing this file, so it cannot tell this project's changes from its own. boop's current copy is at ${upstreamPath}`;
|
|
440
|
+
if (fileKind === 'skill') {
|
|
441
|
+
const projectFile = path.join(path.dirname(relPath), 'PROJECT.md');
|
|
442
|
+
return {
|
|
443
|
+
instruction: `${untracked}. Split it by hand: move this project's rules into ${projectFile}, following the ${exports.TRIAGE_SKILL} skill, then copy boop's copy into place, and the next update tracks it.`,
|
|
444
|
+
upstreamPath,
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return { instruction: `${untracked}; compare the two by hand.`, upstreamPath };
|
|
448
|
+
}
|
|
449
|
+
if (fileKind === 'skill') {
|
|
450
|
+
const projectFile = path.join(path.dirname(relPath), 'PROJECT.md');
|
|
451
|
+
return {
|
|
452
|
+
instruction: `Move this project's edits into ${projectFile}: run \`boop migrate-skill-edits\` and follow the ${exports.TRIAGE_SKILL} skill.`,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
if (drift !== 'both-changed')
|
|
456
|
+
return {};
|
|
457
|
+
const advice = fileKind === 'reference'
|
|
458
|
+
? 'Blend in what applies to this project, following the boop-product-design-reference-integration skill.'
|
|
459
|
+
: 'Compare the two and take what applies.';
|
|
460
|
+
return { instruction: `boop's current copy is at ${upstreamPath}. ${advice}`, upstreamPath };
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* When an update replaces a SKILL.md whose PROJECT.md offers rules to boop,
|
|
464
|
+
* boop's new text may now cover them, and a rule left behind would duplicate it.
|
|
465
|
+
*/
|
|
466
|
+
function offeredRulesInstruction(targetDir, relPath) {
|
|
467
|
+
const projectFile = path.join(path.dirname(relPath), 'PROJECT.md');
|
|
468
|
+
if (!(0, scaffold_1.hasOfferedRules)(path.join(targetDir, projectFile)))
|
|
469
|
+
return undefined;
|
|
470
|
+
return `boop replaced this skill, and ${projectFile} offers rules to boop. Check whether boop's new text covers each rule under "Offered to boop", and delete any it does.`;
|
|
471
|
+
}
|
|
472
|
+
/** A path relative to the project when it lies inside it, as a package in node_modules does */
|
|
473
|
+
function displayPath(targetDir, filePath) {
|
|
474
|
+
const relative = path.relative(targetDir, filePath);
|
|
475
|
+
return relative.startsWith('..') || path.isAbsolute(relative) ? filePath : relative;
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Count the skills a capability's project copy has edited in SKILL.md, and how
|
|
479
|
+
* many of those boop has changed since — from the same comparison `boop update`
|
|
480
|
+
* makes. Null when there are none, or the capability has no package to compare with.
|
|
481
|
+
*/
|
|
482
|
+
function countSkillEdits(targetDir, capabilityName, projectManifest) {
|
|
483
|
+
const plan = planCapabilityUpdate(targetDir, capabilityName, projectManifest);
|
|
484
|
+
const edited = (plan?.actions ?? []).filter(a => a.action === 'skip' && a.fileKind === 'skill' && (a.drift === 'project-only' || a.drift === 'both-changed'));
|
|
485
|
+
if (edited.length === 0)
|
|
486
|
+
return null;
|
|
487
|
+
return { edited: edited.length, changesWaiting: edited.filter(a => a.drift === 'both-changed').length };
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Classify a project-relative destination path: a skill's SKILL.md, a
|
|
491
|
+
* reference page, or anything else (code, views, config).
|
|
492
|
+
*/
|
|
493
|
+
function fileKindOf(relativePath) {
|
|
494
|
+
const segments = relativePath.split(path.sep);
|
|
495
|
+
if (segments.length === 3 && segments[0] === 'skills' && segments[2] === 'SKILL.md')
|
|
496
|
+
return 'skill';
|
|
497
|
+
if (relativePath.endsWith('.ref.md'))
|
|
498
|
+
return 'reference';
|
|
499
|
+
return 'code';
|
|
500
|
+
}
|
|
358
501
|
/**
|
|
359
502
|
* Resolve the source path a project-relative file was installed from — the
|
|
360
503
|
* placement list read backwards, so it cannot disagree with what was copied.
|
package/docs/init.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# boop init
|
|
2
2
|
|
|
3
|
-
`boop init` is the command you run when you want a real starting point quickly.
|
|
3
|
+
`boop init` is the command you run when you want a real starting point quickly.
|
|
4
|
+
It creates a new project directory, applies a boop profile, writes a `.boop/config.json` manifest, and, unless you opt out, installs npm dependencies and sets up the project's beads issue tracker.
|
|
5
|
+
The goal is simple: get you to a local project you can work in, extend, and keep clean as both humans and agents start contributing.
|
|
4
6
|
|
|
5
7
|
## Synopsis
|
|
6
8
|
|
|
@@ -10,9 +12,15 @@ boop init <name> --profile <profile>
|
|
|
10
12
|
|
|
11
13
|
If you omit `--profile`, Boop uses `web-app`.
|
|
12
14
|
|
|
15
|
+
`<name>` can be a plain name or a path such as `~/work/aws-setup`.
|
|
16
|
+
Either way, the project is named after its folder, `aws-setup` in that example:
|
|
17
|
+
that is the name in `package.json`, `.boop/config.json` and the README heading, and the beads issue prefix.
|
|
18
|
+
|
|
13
19
|
## Before You Run It
|
|
14
20
|
|
|
15
|
-
The
|
|
21
|
+
The `web-app` profile requires Node 24 or newer.
|
|
22
|
+
If your Node version is too old, `boop init` exits before creating the directory and prints an upgrade hint.
|
|
23
|
+
The `base` profile names no runtime, so it has no version to check.
|
|
16
24
|
|
|
17
25
|
## Available Profiles
|
|
18
26
|
|
|
@@ -27,110 +35,154 @@ Current output:
|
|
|
27
35
|
```text
|
|
28
36
|
Available profiles:
|
|
29
37
|
|
|
38
|
+
base
|
|
39
|
+
Planning, writing and issue tracking for a project that isn't an application
|
|
40
|
+
capabilities:
|
|
41
|
+
Development: project-mgmt, product-design, skills
|
|
42
|
+
|
|
43
|
+
development
|
|
44
|
+
Development skills only — for a project with no application domain (a CLI, a library, a data pipeline)
|
|
45
|
+
capabilities:
|
|
46
|
+
Development: coding, testing, project-mgmt, product-design, skills, workflows
|
|
47
|
+
|
|
30
48
|
web-app
|
|
31
|
-
Full-stack web application (Express + Handlebars + Tailwind
|
|
32
|
-
capabilities: web, api, database
|
|
49
|
+
Full-stack web application (Express + Handlebars + Tailwind)
|
|
33
50
|
runtime: node | deploy: heroku | port: 3000
|
|
51
|
+
capabilities:
|
|
52
|
+
Application: web, api, services, database
|
|
53
|
+
Development: coding, testing, project-mgmt, product-design, skills, workflows
|
|
54
|
+
Demo: hello
|
|
34
55
|
```
|
|
35
56
|
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
`boop init` can scaffold from two of these.
|
|
58
|
+
`web-app` carries a kernel, the configuration for a running application, and renders an Express server from it.
|
|
59
|
+
`base` has no kernel, but it ships templates of its own,
|
|
60
|
+
so it renders a project for planning, writing and tracking work instead:
|
|
61
|
+
agent instructions, a README in each of `specs/`, `reference/` and `docs/`,
|
|
62
|
+
and the planning and writing skills, with no application code at all.
|
|
63
|
+
|
|
64
|
+
The third profile, `development`, carries development capabilities and no application domain.
|
|
65
|
+
It has neither a kernel nor templates, so there is nothing for `boop init` to write,
|
|
66
|
+
and `boop init --profile development` is refused with a pointer to the two ways it is meant to be used:
|
|
40
67
|
naming it as the `profile` in an existing project's `.boop/config.json`,
|
|
41
68
|
or `boop add` for the capabilities you want.
|
|
42
69
|
|
|
43
70
|
## What It Creates
|
|
44
71
|
|
|
45
|
-
Running `boop init my-saas --profile web-app --no-install` creates a project like this:
|
|
72
|
+
Running `boop init my-saas --profile web-app --no-install --no-beads` creates a project like this, shown to two levels:
|
|
46
73
|
|
|
47
74
|
```text
|
|
48
75
|
my-saas/
|
|
49
|
-
.boop
|
|
76
|
+
.boop/
|
|
77
|
+
config.json
|
|
78
|
+
.env
|
|
79
|
+
.env.example
|
|
80
|
+
.github/
|
|
81
|
+
copilot-instructions.md
|
|
50
82
|
.gitignore
|
|
83
|
+
AGENTS.md
|
|
84
|
+
CLAUDE.md
|
|
51
85
|
Procfile
|
|
52
86
|
README.md
|
|
87
|
+
jest.config.js
|
|
53
88
|
package.json
|
|
89
|
+
prisma/
|
|
90
|
+
schema/
|
|
91
|
+
public/
|
|
92
|
+
css/
|
|
93
|
+
favicon.svg
|
|
54
94
|
reference/
|
|
55
|
-
architecture
|
|
56
|
-
message-queue-patterns.ref.md
|
|
95
|
+
(architecture, database, REST API, service layer and web UI pages, among others)
|
|
57
96
|
skills/
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
SKILL.md
|
|
62
|
-
boop-coding-commit-changes/
|
|
63
|
-
SKILL.md
|
|
64
|
-
boop-coding-development/
|
|
65
|
-
SKILL.md
|
|
66
|
-
boop-coding-review-changes/
|
|
67
|
-
SKILL.md
|
|
68
|
-
boop-database-data-model/
|
|
69
|
-
SKILL.md
|
|
70
|
-
boop-database-prisma/
|
|
71
|
-
SKILL.md
|
|
72
|
-
boop-product-design-engineering-design/
|
|
73
|
-
SKILL.md
|
|
74
|
-
boop-product-design-feature-documentation/
|
|
75
|
-
SKILL.md
|
|
76
|
-
boop-product-design-feature-requirements/
|
|
77
|
-
SKILL.md
|
|
78
|
-
boop-product-design-reference-integration/
|
|
79
|
-
SKILL.md
|
|
80
|
-
boop-project-mgmt-beads-planning/
|
|
81
|
-
SKILL.md
|
|
82
|
-
boop-project-mgmt-engineering-planning/
|
|
83
|
-
SKILL.md
|
|
84
|
-
boop-services-controllers/
|
|
85
|
-
SKILL.md
|
|
86
|
-
boop-services-services/
|
|
87
|
-
SKILL.md
|
|
88
|
-
boop-skill-authoring/
|
|
89
|
-
SKILL.md
|
|
90
|
-
boop-testing-tdd-light/
|
|
91
|
-
SKILL.md
|
|
92
|
-
boop-testing-testing/
|
|
93
|
-
SKILL.md
|
|
94
|
-
boop-testing-testing-express-web/
|
|
95
|
-
SKILL.md
|
|
96
|
-
boop-web-web-ui-design/
|
|
97
|
-
SKILL.md
|
|
97
|
+
(40 skill folders from the installed capabilities)
|
|
98
|
+
specs/
|
|
99
|
+
README.md
|
|
98
100
|
src/
|
|
101
|
+
app.ts
|
|
102
|
+
controllers/
|
|
103
|
+
db.ts
|
|
99
104
|
lib/
|
|
100
|
-
|
|
105
|
+
middleware/
|
|
106
|
+
routes/
|
|
107
|
+
scripts/
|
|
101
108
|
server.ts
|
|
109
|
+
services/
|
|
110
|
+
styles/
|
|
111
|
+
tailwind.config.ts
|
|
112
|
+
tests/
|
|
102
113
|
tsconfig.json
|
|
114
|
+
tsconfig.test.json
|
|
115
|
+
views/
|
|
103
116
|
```
|
|
104
117
|
|
|
105
118
|
That scaffold breaks down into a few parts:
|
|
106
119
|
|
|
107
|
-
- Kernel files: `.gitignore`, `Procfile`, `README.md`, `package.json`, `tsconfig.json`, `src/server.ts`,
|
|
120
|
+
- Kernel files, rendered from boop's kernel templates: `.github/copilot-instructions.md`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `Procfile`, `README.md`, `package.json`, `tsconfig.json`, `src/app.ts`, `src/server.ts`, `src/lib/.gitkeep`, `specs/README.md` and `reference/boop-guide.ref.md`
|
|
108
121
|
- Profile reference docs: copied into `reference/`
|
|
109
|
-
-
|
|
110
|
-
- Project manifest: `.boop.json` records the project name, profile, boop version, installed capabilities, and creation timestamp
|
|
111
|
-
- Capability install effects: default capability dependencies and scripts are merged into `package.json`, and
|
|
122
|
+
- Capability files: each capability's skills are copied into `skills/`, and its scaffold files (`src/`, `views/`, `styles/`, `prisma/`, `tests/` and so on) into the project
|
|
123
|
+
- Project manifest: `.boop/config.json` records the project name, profile, boop version, installed capabilities, the hashes of the files each capability wrote, and the creation timestamp
|
|
124
|
+
- Capability install effects: default capability dependencies and scripts are merged into `package.json`, and any environment variables a capability needs are written to `.env` and `.env.example`
|
|
112
125
|
|
|
113
126
|
For the `web-app` profile, the default capabilities are:
|
|
114
127
|
|
|
115
|
-
- `web`
|
|
116
|
-
- `
|
|
117
|
-
- `
|
|
118
|
-
|
|
119
|
-
At the moment, those capabilities show up most clearly in `package.json`, `skills/`, and `.boop.json`. The initial source tree is still small: the kernel server entry point plus the copied reference and skill material.
|
|
128
|
+
- Application: `web`, `api`, `services`, `database`
|
|
129
|
+
- Development: `coding`, `testing`, `project-mgmt`, `product-design`, `skills`, `workflows`
|
|
130
|
+
- Demo: `hello`
|
|
120
131
|
|
|
121
132
|
Beyond the defaults, optional capabilities are available to add later with `boop add <capability>`. For example, `users-and-accounts` adds user management and authentication, and `noticeboard` provides a fuller service-layer reference demo. See the profile documentation for the complete list.
|
|
122
133
|
|
|
134
|
+
The `base` profile makes a much smaller project.
|
|
135
|
+
Running `boop init notes --profile base --no-install --no-beads` creates this:
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
notes/
|
|
139
|
+
.boop/
|
|
140
|
+
config.json
|
|
141
|
+
.github/
|
|
142
|
+
copilot-instructions.md
|
|
143
|
+
.gitignore
|
|
144
|
+
AGENTS.md
|
|
145
|
+
CLAUDE.md
|
|
146
|
+
README.md
|
|
147
|
+
docs/
|
|
148
|
+
README.md
|
|
149
|
+
package.json
|
|
150
|
+
reference/
|
|
151
|
+
README.md
|
|
152
|
+
skills/
|
|
153
|
+
(16 skill folders from project-mgmt, product-design and skills)
|
|
154
|
+
specs/
|
|
155
|
+
README.md
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
There is no `src/`, no test setup and no server to start.
|
|
159
|
+
The `package.json` holds only the capability packages, the `bd` dependency and two `bd:` scripts;
|
|
160
|
+
it is there because it is how boop records the capabilities and refreshes their skills with `boop update`.
|
|
161
|
+
Without `--no-beads`, the project also has a `.beads/` folder and a git repository with two commits, as described under `--no-beads` below.
|
|
162
|
+
|
|
123
163
|
## Options
|
|
124
164
|
|
|
125
165
|
- `--profile <profile>`: choose which profile to initialize. Defaults to `web-app`.
|
|
126
166
|
- `--no-install`: skip `npm install`. This is useful if you want to inspect or adjust the scaffold before installing dependencies.
|
|
167
|
+
- `--no-beads`: skip the beads setup.
|
|
168
|
+
By default, when the profile installs `project-mgmt` (as `web-app` and `base` both do), Boop's last step is `bd init --non-interactive --server -p <name>`,
|
|
169
|
+
where the issue prefix is the project folder's name even when you pass a path such as `~/work/aws-setup`.
|
|
170
|
+
That creates the project's beads issue database in Dolt server mode and starts its Dolt server,
|
|
171
|
+
so `bd create` works straight away and `boop loop` can run in the project without first migrating it to server mode.
|
|
172
|
+
Pass `--no-beads` for a throwaway scaffold or a CI run; the command is printed under "Next steps" so you can run it later.
|
|
173
|
+
If `bd` is not installed, or `bd init` fails, init still succeeds and prints the same command.
|
|
174
|
+
|
|
175
|
+
Just before `bd init`, Boop creates a git repository in the new project and commits the scaffold, so the project starts with two commits (the scaffold, then beads) and a clean `git status`.
|
|
176
|
+
With `--no-beads`, Boop leaves git alone.
|
|
177
|
+
If git has no `user.name` and `user.email` configured, Boop skips its commit and tells you; it never sets an identity for you.
|
|
178
|
+
If the new project is inside another git repository, Boop skips both the commit and `bd init` and tells you why, because `bd init` would commit into that repository and redirect its git hooks.
|
|
127
179
|
|
|
128
180
|
If you do not pass `--no-install`, Boop runs `npm install` after scaffolding and then prints the same next-step summary.
|
|
129
181
|
|
|
130
|
-
## Example: Initialize Without Installing
|
|
182
|
+
## Example: Initialize Without Installing or Setting Up Beads
|
|
131
183
|
|
|
132
184
|
```bash
|
|
133
|
-
boop init my-saas --profile web-app --no-install
|
|
185
|
+
boop init my-saas --profile web-app --no-install --no-beads
|
|
134
186
|
```
|
|
135
187
|
|
|
136
188
|
Expected output:
|
|
@@ -138,43 +190,37 @@ Expected output:
|
|
|
138
190
|
```text
|
|
139
191
|
Created project "my-saas" with profile "web-app"
|
|
140
192
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
package.json
|
|
146
|
-
src/lib/.gitkeep
|
|
147
|
-
src/server.ts
|
|
148
|
-
tsconfig.json
|
|
149
|
-
|
|
150
|
-
Capabilities: web, api, database
|
|
151
|
-
|
|
152
|
-
Scripts added:
|
|
153
|
-
tailwind:build: npx @tailwindcss/cli -i src/public/css/input.css -o src/public/css/style.css --minify
|
|
154
|
-
tailwind:watch: npx @tailwindcss/cli -i src/public/css/input.css -o src/public/css/style.css --watch
|
|
155
|
-
db:migrate: prisma migrate dev --schema=prisma/schema/
|
|
156
|
-
db:deploy: prisma migrate deploy --schema=prisma/schema/
|
|
157
|
-
db:seed: prisma db seed --schema=prisma/schema/
|
|
158
|
-
db:generate: prisma generate --schema=prisma/schema/
|
|
159
|
-
db:studio: prisma studio --schema=prisma/schema/
|
|
193
|
+
13 kernel files · 11 capabilities · 68 capability files · 13 scripts
|
|
194
|
+
Application: web, api, services, database
|
|
195
|
+
Development: coding, testing, project-mgmt, product-design, skills, workflows
|
|
196
|
+
Demo: hello
|
|
160
197
|
|
|
161
198
|
Next steps:
|
|
162
199
|
cd my-saas
|
|
163
200
|
npm install
|
|
164
|
-
|
|
201
|
+
bd init --non-interactive --server -p my-saas
|
|
202
|
+
boop status ← check what needs configuration
|
|
203
|
+
npm run dev ← start development server
|
|
165
204
|
```
|
|
166
205
|
|
|
206
|
+
The counts change as capabilities grow.
|
|
207
|
+
A `base` project's summary counts template files rather than kernel files,
|
|
208
|
+
and its next steps stop at `boop status`, because there is no development server to start.
|
|
209
|
+
|
|
167
210
|
## What To Do After Init
|
|
168
211
|
|
|
169
|
-
|
|
212
|
+
Run the commands under "Next steps".
|
|
213
|
+
If you used `--no-install` and `--no-beads`, that is:
|
|
170
214
|
|
|
171
215
|
```bash
|
|
172
216
|
cd my-saas
|
|
173
217
|
npm install
|
|
218
|
+
bd init --non-interactive --server -p my-saas
|
|
219
|
+
boop status
|
|
174
220
|
npm run dev
|
|
175
221
|
```
|
|
176
222
|
|
|
177
|
-
If you let Boop install dependencies for you, the shorter version is:
|
|
223
|
+
If you let Boop install dependencies and set up beads for you, the shorter version is:
|
|
178
224
|
|
|
179
225
|
```bash
|
|
180
226
|
cd my-saas
|
package/docs/loop.md
CHANGED
|
@@ -122,6 +122,21 @@ Session logs are written to `labs/claude-runs/` in the project directory. Each i
|
|
|
122
122
|
- **Budget caps** — each issue is capped at `--budget` USD
|
|
123
123
|
- **Resume limits** — prevents infinite retry loops (default: 3 attempts)
|
|
124
124
|
- **Permission skipping** — enabled by default for unattended operation; use `--no-skip-perms` to require approval for each tool call
|
|
125
|
+
- **A failed queue read is never a clean finish** — if the loop cannot read the ready queue it says so and stops with exit code 1, rather than reporting "No more ready issues" and exiting 0. See [Reading the ready queue](#reading-the-ready-queue).
|
|
126
|
+
|
|
127
|
+
## Reading the ready queue
|
|
128
|
+
|
|
129
|
+
The loop reads claimable work with `bd ready --json -n 0`, projected through `jq` to the only three fields its filters use: `id`, `issue_type` and `parent`.
|
|
130
|
+
|
|
131
|
+
The projection matters because `bd ready --json` returns every ready issue *with its full description*. In a repo with 112 ready issues that is 274,529 bytes, roughly 70% of it description the loop never looks at; projected, the same read is 5,941 bytes. Node captures at most 1 MiB from a subprocess by default and fails past it, so an unprojected read on a mature backlog dies — and a dead read used to be indistinguishable from an empty queue, which is how a loop could stop early with work still ready.
|
|
132
|
+
|
|
133
|
+
**`jq` is recommended but not required.** It ships with neither stock macOS nor stock Debian, so when it is missing the loop warns once and falls back to the unprojected read. That stays safe because every `bd` read is capped at 256 MiB rather than the 1 MiB default. Install `jq` to keep the read small:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
brew install jq
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
If the ready queue cannot be read at all, the loop says which command failed and stops with exit code 1 rather than reporting a clean finish. Under `--poll` it logs the failure and retries on the next interval.
|
|
125
140
|
|
|
126
141
|
## Planning Mode
|
|
127
142
|
|
|
@@ -167,6 +182,7 @@ The prefix is included in the planning prompt as a suggestion for naming the fea
|
|
|
167
182
|
- **Flags used separately** — `--work` without `--flow` (or vice versa) prints an error. They must be used together.
|
|
168
183
|
- **Feature ID not found** — If the planning session doesn't output `FEATURE_ID=<id>` and the fallback query finds no matching recent feature, the command exits with an error and points to the session log for debugging.
|
|
169
184
|
- **Planning session limit hit** — If the Claude session hits a usage limit, the command exits without proceeding to the loop phase.
|
|
185
|
+
- **Ready-queue read failure** — If `bd ready` cannot be read, the loop reports the failing command and stops with exit code 1; it never reports a clean finish on a read it could not make.
|
|
170
186
|
|
|
171
187
|
Planning session logs are written to `labs/claude-runs/` with the prefix `_planning_`.
|
|
172
188
|
|
package/docs/skills.md
CHANGED
|
@@ -38,6 +38,7 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
|
|
|
38
38
|
- **boop-get-smart** — When there is a recurring fault in your own behaviour during a chat, run the five whys to find its root cause, and agree a change with the operator that makes it less likely.
|
|
39
39
|
- **boop-so-what** — When the operator asks for the so-what, or says some version of 'thanks for the essay', compress what you just produced into three sentences about what they should care about.
|
|
40
40
|
- **boop-skills-hyperlinking** — When you need to add, review, or repair a `## Related` section that points one skill at another, follow these rules for what earns a link, how to phrase it, which way it points, how many are too many, and how to write a path that survives `boop add`.
|
|
41
|
+
- **boop-skills-edit-triage** — When you need to move a project's edits out of a boop skill's SKILL.md — because `boop update` or `boop status` reports the skill as edited here, or `boop migrate-skill-edits` wrote a plan for it — decide whether each edit is kept in this project, offered to boop, or dropped, and rewrite each kept edit as a standalone rule for the PROJECT.md beside it. Not for reference pages, which are blended rather than split.
|
|
41
42
|
|
|
42
43
|
### `testing` — Jest testing setup
|
|
43
44
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boopkit/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.14",
|
|
4
4
|
"description": "boop: the opinionated bootstrap for founder friendly, AI-first web apps - code plus agent skills, composable from day one.",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"workspaces": [
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsc && node tools/generate-version-manifest.js",
|
|
24
|
+
"build": "npm run build:caps && tsc && node tools/generate-version-manifest.js",
|
|
25
|
+
"build:cli": "tsc && node tools/generate-version-manifest.js",
|
|
25
26
|
"typecheck": "tsc -p tsconfig.test.json",
|
|
26
27
|
"skills:index": "node tools/generate-skills-index.js",
|
|
27
28
|
"skills:layout": "tsx tools/generate-installed-layout.ts",
|