@boopkit/cli 0.6.8 → 0.6.10
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 +14 -4
- package/dist/capability-catalog.json +10 -0
- package/dist/capability-versions.json +12 -11
- package/dist/cli.js +1 -0
- package/dist/commands/init.js +19 -6
- package/dist/commands/list.js +5 -1
- package/dist/commands/update.d.ts +1 -0
- package/dist/commands/update.js +42 -2
- package/dist/lib/capability.d.ts +11 -0
- package/dist/lib/capability.js +26 -0
- package/dist/lib/config.d.ts +6 -1
- package/dist/lib/install-layout.d.ts +86 -0
- package/dist/lib/install-layout.js +250 -0
- package/dist/lib/preflight.js +7 -57
- package/dist/lib/scaffold.d.ts +12 -1
- package/dist/lib/scaffold.js +92 -113
- package/dist/lib/stale-scripts.d.ts +53 -0
- package/dist/lib/stale-scripts.js +160 -0
- package/dist/lib/update.js +14 -57
- package/docs/init.md +6 -1
- package/docs/skills.md +3 -0
- package/package.json +15 -2
- package/profiles/development/profile.md +79 -0
- package/profiles/web-app/profile.md +7 -6
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect package.json scripts that contradict the runner settings a capability
|
|
3
|
+
* now ships, and offer the edit that reconciles them.
|
|
4
|
+
*
|
|
5
|
+
* The case this exists for: `cap-testing` used to ship `"test": "jest --runInBand"`.
|
|
6
|
+
* It now ships a `jest.config.js` carrying `maxWorkers` and `workerIdleMemoryLimit`
|
|
7
|
+
* instead, because in-band execution runs every test file through one process that
|
|
8
|
+
* Jest never recycles, and a long suite dies as that process's heap grows.
|
|
9
|
+
*
|
|
10
|
+
* `boop update` cannot simply fix those projects. Scripts are merged only when
|
|
11
|
+
* absent (see `merge-scripts` in lib/execute.ts), which is the right default —
|
|
12
|
+
* clobbering a script somebody customised would be worse than leaving it stale.
|
|
13
|
+
* So the old command survives the update, and because a CLI `--runInBand` is the
|
|
14
|
+
* first and unconditional branch of Jest's `shouldRunInBand()`, it overrides the
|
|
15
|
+
* new config entirely. That is the state worth warning about: the project now has
|
|
16
|
+
* a config file that looks like the fix, reads like the fix in review, and does
|
|
17
|
+
* nothing at all.
|
|
18
|
+
*
|
|
19
|
+
* Warning rather than prompting is deliberate. `boop update` is usually run by an
|
|
20
|
+
* agent, often inside `boop loop`, where there is no terminal to answer a question
|
|
21
|
+
* and a blocking prompt would hang the run. The warning therefore has to carry
|
|
22
|
+
* everything needed to act: which script, what is wrong with it, and the exact
|
|
23
|
+
* command that fixes it.
|
|
24
|
+
*/
|
|
25
|
+
export interface StaleScript {
|
|
26
|
+
/** The script's key in package.json, e.g. `test`. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** The command as it stands today. */
|
|
29
|
+
command: string;
|
|
30
|
+
/** What the command becomes once the offending flags are removed. */
|
|
31
|
+
rewritten: string;
|
|
32
|
+
/** The flags that triggered the finding, in the order they appeared. */
|
|
33
|
+
flags: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Pure: given a package.json `scripts` object, return the scripts whose command
|
|
37
|
+
* forces in-band Jest execution and therefore defeats the shipped config.
|
|
38
|
+
*/
|
|
39
|
+
export declare function findStaleRunnerScripts(scripts: Record<string, string> | undefined): StaleScript[];
|
|
40
|
+
/**
|
|
41
|
+
* Apply the findings to the project's package.json.
|
|
42
|
+
*
|
|
43
|
+
* A finding is only applied when the script still holds exactly the command the
|
|
44
|
+
* finding was made against. Anything else means the file changed underneath us,
|
|
45
|
+
* and rewriting it then would discard somebody's edit. Returns the findings that
|
|
46
|
+
* were actually written.
|
|
47
|
+
*/
|
|
48
|
+
export declare function rewriteStaleRunnerScripts(targetDir: string, stale: StaleScript[]): StaleScript[];
|
|
49
|
+
/**
|
|
50
|
+
* The warning shown after an update. Written to be actionable by an agent reading
|
|
51
|
+
* it in a transcript with no other context.
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatStaleScriptWarning(stale: StaleScript[]): string;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Detect package.json scripts that contradict the runner settings a capability
|
|
4
|
+
* now ships, and offer the edit that reconciles them.
|
|
5
|
+
*
|
|
6
|
+
* The case this exists for: `cap-testing` used to ship `"test": "jest --runInBand"`.
|
|
7
|
+
* It now ships a `jest.config.js` carrying `maxWorkers` and `workerIdleMemoryLimit`
|
|
8
|
+
* instead, because in-band execution runs every test file through one process that
|
|
9
|
+
* Jest never recycles, and a long suite dies as that process's heap grows.
|
|
10
|
+
*
|
|
11
|
+
* `boop update` cannot simply fix those projects. Scripts are merged only when
|
|
12
|
+
* absent (see `merge-scripts` in lib/execute.ts), which is the right default —
|
|
13
|
+
* clobbering a script somebody customised would be worse than leaving it stale.
|
|
14
|
+
* So the old command survives the update, and because a CLI `--runInBand` is the
|
|
15
|
+
* first and unconditional branch of Jest's `shouldRunInBand()`, it overrides the
|
|
16
|
+
* new config entirely. That is the state worth warning about: the project now has
|
|
17
|
+
* a config file that looks like the fix, reads like the fix in review, and does
|
|
18
|
+
* nothing at all.
|
|
19
|
+
*
|
|
20
|
+
* Warning rather than prompting is deliberate. `boop update` is usually run by an
|
|
21
|
+
* agent, often inside `boop loop`, where there is no terminal to answer a question
|
|
22
|
+
* and a blocking prompt would hang the run. The warning therefore has to carry
|
|
23
|
+
* everything needed to act: which script, what is wrong with it, and the exact
|
|
24
|
+
* command that fixes it.
|
|
25
|
+
*/
|
|
26
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
29
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
30
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
31
|
+
}
|
|
32
|
+
Object.defineProperty(o, k2, desc);
|
|
33
|
+
}) : (function(o, m, k, k2) {
|
|
34
|
+
if (k2 === undefined) k2 = k;
|
|
35
|
+
o[k2] = m[k];
|
|
36
|
+
}));
|
|
37
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
38
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
39
|
+
}) : function(o, v) {
|
|
40
|
+
o["default"] = v;
|
|
41
|
+
});
|
|
42
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
43
|
+
var ownKeys = function(o) {
|
|
44
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
45
|
+
var ar = [];
|
|
46
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
47
|
+
return ar;
|
|
48
|
+
};
|
|
49
|
+
return ownKeys(o);
|
|
50
|
+
};
|
|
51
|
+
return function (mod) {
|
|
52
|
+
if (mod && mod.__esModule) return mod;
|
|
53
|
+
var result = {};
|
|
54
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
55
|
+
__setModuleDefault(result, mod);
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
})();
|
|
59
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
60
|
+
exports.findStaleRunnerScripts = findStaleRunnerScripts;
|
|
61
|
+
exports.rewriteStaleRunnerScripts = rewriteStaleRunnerScripts;
|
|
62
|
+
exports.formatStaleScriptWarning = formatStaleScriptWarning;
|
|
63
|
+
const fs = __importStar(require("fs"));
|
|
64
|
+
const path = __importStar(require("path"));
|
|
65
|
+
/** Jest's in-band flag and its single-letter alias. */
|
|
66
|
+
const IN_BAND_FLAGS = new Set(['--runInBand', '--runInBand=true', '-i']);
|
|
67
|
+
/**
|
|
68
|
+
* Does this command invoke Jest? Matched on the token's basename so that `jest`,
|
|
69
|
+
* `npx jest` and `node_modules/.bin/jest` all count, while a command that merely
|
|
70
|
+
* contains the same characters somewhere does not.
|
|
71
|
+
*/
|
|
72
|
+
function invokesJest(tokens) {
|
|
73
|
+
return tokens.some((token) => {
|
|
74
|
+
if (token.startsWith('-'))
|
|
75
|
+
return false;
|
|
76
|
+
const base = path.basename(token);
|
|
77
|
+
return base === 'jest' || base === 'jest.js';
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Pure: given a package.json `scripts` object, return the scripts whose command
|
|
82
|
+
* forces in-band Jest execution and therefore defeats the shipped config.
|
|
83
|
+
*/
|
|
84
|
+
function findStaleRunnerScripts(scripts) {
|
|
85
|
+
if (!scripts)
|
|
86
|
+
return [];
|
|
87
|
+
const findings = [];
|
|
88
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
89
|
+
if (typeof command !== 'string')
|
|
90
|
+
continue;
|
|
91
|
+
const tokens = command.split(/\s+/).filter(Boolean);
|
|
92
|
+
if (!invokesJest(tokens))
|
|
93
|
+
continue;
|
|
94
|
+
const flags = tokens.filter((t) => IN_BAND_FLAGS.has(t));
|
|
95
|
+
if (flags.length === 0)
|
|
96
|
+
continue;
|
|
97
|
+
findings.push({
|
|
98
|
+
name,
|
|
99
|
+
command,
|
|
100
|
+
rewritten: tokens.filter((t) => !IN_BAND_FLAGS.has(t)).join(' '),
|
|
101
|
+
flags,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return findings;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Apply the findings to the project's package.json.
|
|
108
|
+
*
|
|
109
|
+
* A finding is only applied when the script still holds exactly the command the
|
|
110
|
+
* finding was made against. Anything else means the file changed underneath us,
|
|
111
|
+
* and rewriting it then would discard somebody's edit. Returns the findings that
|
|
112
|
+
* were actually written.
|
|
113
|
+
*/
|
|
114
|
+
function rewriteStaleRunnerScripts(targetDir, stale) {
|
|
115
|
+
if (stale.length === 0)
|
|
116
|
+
return [];
|
|
117
|
+
const pkgPath = path.join(targetDir, 'package.json');
|
|
118
|
+
if (!fs.existsSync(pkgPath))
|
|
119
|
+
return [];
|
|
120
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
121
|
+
if (!pkg.scripts)
|
|
122
|
+
return [];
|
|
123
|
+
const applied = [];
|
|
124
|
+
for (const finding of stale) {
|
|
125
|
+
if (pkg.scripts[finding.name] !== finding.command)
|
|
126
|
+
continue;
|
|
127
|
+
pkg.scripts[finding.name] = finding.rewritten;
|
|
128
|
+
applied.push(finding);
|
|
129
|
+
}
|
|
130
|
+
if (applied.length > 0) {
|
|
131
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
132
|
+
}
|
|
133
|
+
return applied;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The warning shown after an update. Written to be actionable by an agent reading
|
|
137
|
+
* it in a transcript with no other context.
|
|
138
|
+
*/
|
|
139
|
+
function formatStaleScriptWarning(stale) {
|
|
140
|
+
if (stale.length === 0)
|
|
141
|
+
return '';
|
|
142
|
+
const lines = [];
|
|
143
|
+
lines.push(`\x1b[33mWARN\x1b[0m ${stale.length === 1 ? 'A package.json script forces' : `${stale.length} package.json scripts force`} Jest to run in band, which overrides the settings this capability ships.`);
|
|
144
|
+
for (const finding of stale) {
|
|
145
|
+
lines.push(` ${finding.name}: ${finding.command}`);
|
|
146
|
+
lines.push(` ${' '.repeat(finding.name.length)} \x1b[32m→ ${finding.rewritten}\x1b[0m`);
|
|
147
|
+
}
|
|
148
|
+
lines.push('');
|
|
149
|
+
lines.push(' Why it matters: in band, every test file runs through one process that Jest');
|
|
150
|
+
lines.push(' never recycles, so the heap grows for the whole run until a long suite dies.');
|
|
151
|
+
lines.push(' The scaffolded jest.config.js sets maxWorkers and workerIdleMemoryLimit to');
|
|
152
|
+
lines.push(' bound that, but a command-line --runInBand wins over both, so the config is');
|
|
153
|
+
lines.push(' present and inert until this script changes.');
|
|
154
|
+
lines.push('');
|
|
155
|
+
lines.push(' boop did not change it for you because it does not overwrite scripts you may');
|
|
156
|
+
lines.push(' have customised. To apply the edit above:');
|
|
157
|
+
lines.push('');
|
|
158
|
+
lines.push(' \x1b[36mboop update --fix-scripts\x1b[0m');
|
|
159
|
+
return lines.join('\n');
|
|
160
|
+
}
|
package/dist/lib/update.js
CHANGED
|
@@ -49,6 +49,7 @@ const scaffold_1 = require("./scaffold");
|
|
|
49
49
|
const capability_1 = require("./capability");
|
|
50
50
|
const project_1 = require("./project");
|
|
51
51
|
const safe_io_1 = require("./safe-io");
|
|
52
|
+
const install_layout_1 = require("./install-layout");
|
|
52
53
|
/**
|
|
53
54
|
* Compare a file's current hash against a stored hash.
|
|
54
55
|
*/
|
|
@@ -84,30 +85,11 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
|
|
|
84
85
|
const storedHashes = meta?.fileHashes || {};
|
|
85
86
|
const actions = [];
|
|
86
87
|
let unchangedCount = 0;
|
|
87
|
-
//
|
|
88
|
+
// Where each of the package's files lands — the same answer `boop add` used to
|
|
89
|
+
// put them there, so an update compares like with like.
|
|
88
90
|
const packageFiles = new Map(); // relPath → absolute source path
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (fs.existsSync(scaffoldDir)) {
|
|
92
|
-
collectFiles(scaffoldDir, scaffoldDir, packageFiles);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Collect files from package skills dir (both flat files and folder-based skills)
|
|
96
|
-
if (boopkit.skillsDir) {
|
|
97
|
-
const skillsDir = path.join(pkgDir, boopkit.skillsDir);
|
|
98
|
-
if (fs.existsSync(skillsDir)) {
|
|
99
|
-
const entries = fs.readdirSync(skillsDir);
|
|
100
|
-
for (const entry of entries) {
|
|
101
|
-
const srcPath = path.join(skillsDir, entry);
|
|
102
|
-
if (fs.statSync(srcPath).isDirectory()) {
|
|
103
|
-
const subFiles = new Map();
|
|
104
|
-
collectFiles(srcPath, srcPath, subFiles);
|
|
105
|
-
for (const [subRel, subAbs] of subFiles) {
|
|
106
|
-
packageFiles.set(path.join('skills', entry, subRel), subAbs);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
91
|
+
for (const placement of (0, install_layout_1.capabilityPlacementsFromDir)(pkgDir)) {
|
|
92
|
+
packageFiles.set(placement.dest, placement.source);
|
|
111
93
|
}
|
|
112
94
|
// For each file in the package, determine action
|
|
113
95
|
for (const [relPath, srcPath] of packageFiles) {
|
|
@@ -276,7 +258,7 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
276
258
|
const destPath = path.join(plan.targetDir, action.relativePath);
|
|
277
259
|
switch (action.action) {
|
|
278
260
|
case 'overwrite': {
|
|
279
|
-
const srcPath = resolveSourcePath(plan.packageDir,
|
|
261
|
+
const srcPath = resolveSourcePath(plan.packageDir, action.relativePath);
|
|
280
262
|
if (srcPath) {
|
|
281
263
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
282
264
|
fs.copyFileSync(srcPath, destPath);
|
|
@@ -289,7 +271,7 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
289
271
|
const isLocallyDeleted = action.reason === 'Locally deleted';
|
|
290
272
|
if (isLocallyDeleted && options.restore) {
|
|
291
273
|
// Restore mode: re-add locally deleted files
|
|
292
|
-
const srcPath = resolveSourcePath(plan.packageDir,
|
|
274
|
+
const srcPath = resolveSourcePath(plan.packageDir, action.relativePath);
|
|
293
275
|
if (srcPath) {
|
|
294
276
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
295
277
|
fs.copyFileSync(srcPath, destPath);
|
|
@@ -299,7 +281,7 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
299
281
|
}
|
|
300
282
|
else if (!isLocallyDeleted && options.force) {
|
|
301
283
|
// Force mode: backup and overwrite modified files
|
|
302
|
-
const srcPath = resolveSourcePath(plan.packageDir,
|
|
284
|
+
const srcPath = resolveSourcePath(plan.packageDir, action.relativePath);
|
|
303
285
|
if (srcPath) {
|
|
304
286
|
if (fs.existsSync(destPath)) {
|
|
305
287
|
(0, safe_io_1.createBackup)(destPath);
|
|
@@ -322,7 +304,7 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
322
304
|
break;
|
|
323
305
|
}
|
|
324
306
|
case 'add': {
|
|
325
|
-
const srcPath = resolveSourcePath(plan.packageDir,
|
|
307
|
+
const srcPath = resolveSourcePath(plan.packageDir, action.relativePath);
|
|
326
308
|
if (srcPath) {
|
|
327
309
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
328
310
|
fs.copyFileSync(srcPath, destPath);
|
|
@@ -374,37 +356,12 @@ function executeUpdatePlan(plan, options = {}) {
|
|
|
374
356
|
return result;
|
|
375
357
|
}
|
|
376
358
|
/**
|
|
377
|
-
* Resolve the source path
|
|
378
|
-
|
|
379
|
-
function resolveSourcePath(pkgDir, boopkit, relativePath) {
|
|
380
|
-
// Skills are stored as skills/<filename> or skills/<folder>/<file> in relPath
|
|
381
|
-
if (relativePath.startsWith('skills/') && boopkit.skillsDir) {
|
|
382
|
-
const subPath = relativePath.slice('skills/'.length);
|
|
383
|
-
const srcPath = path.join(pkgDir, boopkit.skillsDir, subPath);
|
|
384
|
-
return fs.existsSync(srcPath) ? srcPath : null;
|
|
385
|
-
}
|
|
386
|
-
// Scaffold files
|
|
387
|
-
if (boopkit.scaffoldDir) {
|
|
388
|
-
const srcPath = path.join(pkgDir, boopkit.scaffoldDir, relativePath);
|
|
389
|
-
return fs.existsSync(srcPath) ? srcPath : null;
|
|
390
|
-
}
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Recursively collect files from a directory into a map of relPath → absPath.
|
|
359
|
+
* Resolve the source path a project-relative file was installed from — the
|
|
360
|
+
* placement list read backwards, so it cannot disagree with what was copied.
|
|
395
361
|
*/
|
|
396
|
-
function
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
const fullPath = path.join(dir, entry.name);
|
|
400
|
-
if (entry.isDirectory()) {
|
|
401
|
-
collectFiles(baseDir, fullPath, result);
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
const relPath = path.relative(baseDir, fullPath);
|
|
405
|
-
result.set(relPath, fullPath);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
362
|
+
function resolveSourcePath(pkgDir, relativePath) {
|
|
363
|
+
const placement = (0, install_layout_1.capabilityPlacementsFromDir)(pkgDir).find(p => p.dest === relativePath);
|
|
364
|
+
return placement && fs.existsSync(placement.source) ? placement.source : null;
|
|
408
365
|
}
|
|
409
366
|
/**
|
|
410
367
|
* Format an update result as a human-readable summary line.
|
package/docs/init.md
CHANGED
|
@@ -33,7 +33,12 @@ Available profiles:
|
|
|
33
33
|
runtime: node | deploy: heroku | port: 3000
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
`web-app` is the only profile `boop init` can scaffold from.
|
|
37
|
+
The other built-in profile, `development`, carries development capabilities and no application domain,
|
|
38
|
+
so it has no runtime, port or deploy target to scaffold with —
|
|
39
|
+
`boop init --profile development` is refused with a pointer to the two ways it is meant to be used:
|
|
40
|
+
naming it as the `profile` in an existing project's `.boop/config.json`,
|
|
41
|
+
or `boop add` for the capabilities you want.
|
|
37
42
|
|
|
38
43
|
## What It Creates
|
|
39
44
|
|
package/docs/skills.md
CHANGED
|
@@ -23,6 +23,7 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
|
|
|
23
23
|
- **boop-product-design-thought-partnership** — When the user wants to think out loud through a design — a feature, a capability, a strategic direction — be a thought partner, not an implementer: hold the conversation at user-and-value altitude, honor uncertainty with hypotheses rather than pretend-decisions, and keep software-shaped concerns downstream of the user's lived problems.
|
|
24
24
|
- **boop-writing-technical-prose** — When writing chat responses, summaries, bd issue bodies, specifications, specs, or any artifact a fresh reader will pick up cold, write grammatical conversational English. Avoid the agent-shorthand patterns that compress meaning at the cost of legibility.
|
|
25
25
|
- **boop-writing-documents** — When you need to write a document someone will read cold and act on — a proposal, strategy memo, analysis, recommendation, or status write-up — and want the discipline of the Amazon six-pager: narrative prose, a declared length budget, claims that carry their evidence, and the hard questions answered inside the document.
|
|
26
|
+
- **boop-writing-semantic-line-breaks** — When you need to write or edit prose in a markdown file under version control — a spec, a reference page, a README, a skill — break lines at semantic boundaries rather than at a fixed column, so that edits produce readable diffs.
|
|
26
27
|
|
|
27
28
|
### `project-mgmt` — Project management with beads
|
|
28
29
|
|
|
@@ -36,6 +37,7 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
|
|
|
36
37
|
- **boop-skills-agent-surface-design** — When you design or audit tool surfaces (tools, schemas, APIs, conventions) that LLM agents will reach for, decide where to put the friction — adapt the system to the agent, or make the existing thing more discoverable. Last resort: prompt-engineer.
|
|
37
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.
|
|
38
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
|
+
- **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`.
|
|
39
41
|
|
|
40
42
|
### `testing` — Jest testing setup
|
|
41
43
|
|
|
@@ -61,6 +63,7 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
|
|
|
61
63
|
- **boop-website-dev-workflow** — When you are tasked with implementing website changes end-to-end (new pages, page rewrites, IA changes, content/CTA updates), follow this guidance.
|
|
62
64
|
- **boop-design-handoff-workflow** — When the operator hands off a design package (a README plus a drop-in view template and CSS) for a redesigned surface, follow this process: read the README, VERIFY its claims against the real code, surface any buried decision to the operator, file an issue, run it as a sub-agent (branch for UAT or merge-on-clean-visual-check), then close out with knowledge integration and archival. The README is confident; confidence is not correctness — verify before you run.
|
|
63
65
|
- **boop-knowledge-backfill** — When you need to integrate new knowledge from a set of specs or completed work that was not documented in reference at the time it shipped. This often happens when a feature is developed under a tight deadline and the team prioritizes shipping over documentation, or when work was done before the reference system was fully established.
|
|
66
|
+
- **boop-stray-disposition** — When an autonomous run (a loop, a sweep, a sub-agent session) has finished and filed follow-up issues along the way, settle each one before you write the closing summary. Every stray gets a recorded disposition — re-parented and reachable from the roadmap, folded into a spec and closed, or triaged away. Use this whenever a run ends with issues in the queue that nobody has decided to do.
|
|
64
67
|
|
|
65
68
|
## Application capabilities
|
|
66
69
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boopkit/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
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": [
|
|
@@ -22,13 +22,17 @@
|
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsc && node tools/generate-version-manifest.js",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
25
26
|
"skills:index": "node tools/generate-skills-index.js",
|
|
27
|
+
"skills:layout": "tsx tools/generate-installed-layout.ts",
|
|
28
|
+
"skills:audit": "npm run --silent skills:layout -- --check && node tools/audit-skill-links.js",
|
|
26
29
|
"build:caps": "./tools/build-caps.sh",
|
|
27
30
|
"publish": "./tools/publish-caps.sh",
|
|
28
31
|
"release": "./tools/publish-caps.sh --release",
|
|
29
32
|
"dev": "tsx src/cli.ts",
|
|
30
33
|
"start": "node dist/cli.js",
|
|
31
|
-
"test": "jest"
|
|
34
|
+
"test": "jest",
|
|
35
|
+
"pretest": "npm run skills:layout"
|
|
32
36
|
},
|
|
33
37
|
"keywords": [],
|
|
34
38
|
"license": "ISC",
|
|
@@ -43,9 +47,18 @@
|
|
|
43
47
|
"@types/cookie-parser": "^1.4.10",
|
|
44
48
|
"@types/jest": "^30.0.0",
|
|
45
49
|
"@types/node": "^25.5.0",
|
|
50
|
+
"@types/passport": "^1.0.17",
|
|
51
|
+
"@types/passport-google-oauth20": "^2.0.17",
|
|
52
|
+
"@types/passport-microsoft": "^2.1.1",
|
|
46
53
|
"@types/supertest": "^7.2.0",
|
|
47
54
|
"cookie-parser": "^1.4.7",
|
|
55
|
+
"cors": "^2.8.6",
|
|
56
|
+
"dotenv": "^16.6.1",
|
|
57
|
+
"helmet": "^8.3.0",
|
|
48
58
|
"jest": "^30.3.0",
|
|
59
|
+
"passport": "^0.7.0",
|
|
60
|
+
"passport-google-oauth20": "^2.0.0",
|
|
61
|
+
"passport-microsoft": "^2.1.0",
|
|
49
62
|
"supertest": "^7.2.2",
|
|
50
63
|
"ts-jest": "^29.4.6",
|
|
51
64
|
"tsx": "^4.19.4",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: development
|
|
3
|
+
description: Development skills only — for a project with no application domain (a CLI, a library, a data pipeline)
|
|
4
|
+
defaultCapabilities:
|
|
5
|
+
# Development
|
|
6
|
+
- coding
|
|
7
|
+
- testing
|
|
8
|
+
- project-mgmt
|
|
9
|
+
- product-design
|
|
10
|
+
- skills
|
|
11
|
+
- workflows
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Development Profile
|
|
15
|
+
|
|
16
|
+
The development capabilities on their own,
|
|
17
|
+
with no application domain attached.
|
|
18
|
+
|
|
19
|
+
A project has two independent things going on:
|
|
20
|
+
what it *is* — a web app, a CLI, a library, a data pipeline —
|
|
21
|
+
and what its team *does with it* — write code, test it, plan it, design it, document it.
|
|
22
|
+
The `web-app` profile bundles both,
|
|
23
|
+
because a full-stack web application needs both.
|
|
24
|
+
This profile is the second half by itself,
|
|
25
|
+
for a project whose domain boop has nothing to say about.
|
|
26
|
+
|
|
27
|
+
That is what makes it reusable past boop.
|
|
28
|
+
A TypeScript CLI, a published library and an ETL job
|
|
29
|
+
share no application layers at all,
|
|
30
|
+
but they are all developed the same way.
|
|
31
|
+
|
|
32
|
+
## What You Get
|
|
33
|
+
|
|
34
|
+
Six capabilities, all of them `type: development` —
|
|
35
|
+
pure skill bundles that guide agent behaviour,
|
|
36
|
+
plus the handful of dev dependencies those skills assume:
|
|
37
|
+
|
|
38
|
+
- **coding** — development conventions, code style, commit and debugging skills
|
|
39
|
+
- **testing** — Jest setup, test utilities, the TDD-Lite workflow
|
|
40
|
+
- **project-mgmt** — beads issue tracking and planning skills
|
|
41
|
+
- **product-design** — feature requirements, engineering design, documentation skills
|
|
42
|
+
- **skills** — skill authoring, discovery and maintenance
|
|
43
|
+
- **workflows** — the end-to-end development and writing workflows
|
|
44
|
+
|
|
45
|
+
No web layer, no API layer, no service layer, no database.
|
|
46
|
+
Add any of those with `boop add` if the project grows an application domain.
|
|
47
|
+
|
|
48
|
+
## Two Deliberate Omissions
|
|
49
|
+
|
|
50
|
+
### No kernel
|
|
51
|
+
|
|
52
|
+
`web-app` carries a `kernel` block: a node runtime, a Heroku deploy target,
|
|
53
|
+
port 3000, an Express dependency set and the scripts that start a server.
|
|
54
|
+
Every field in it exists to parameterise the scaffolding of a running web process.
|
|
55
|
+
|
|
56
|
+
This profile scaffolds no application,
|
|
57
|
+
so it has no runtime to name, no port to listen on and no deploy target.
|
|
58
|
+
Filling those in with plausible-looking values
|
|
59
|
+
would be inventing facts the profile does not have,
|
|
60
|
+
and it would make `boop init --profile development`
|
|
61
|
+
emit an Express `src/app.ts` that imports a dependency the profile never installs.
|
|
62
|
+
|
|
63
|
+
So `kernel` is absent, and `ProfileMetadata.kernel` is optional to allow it.
|
|
64
|
+
The consequence is deliberate: `boop init --profile development` is refused
|
|
65
|
+
with a message pointing at the two ways this profile is actually meant to be used —
|
|
66
|
+
name it in an existing project's `.boop/config.json`,
|
|
67
|
+
or `boop add` these capabilities one at a time.
|
|
68
|
+
The capabilities carry their own dependencies (`jest`, `ts-jest`, `beads`),
|
|
69
|
+
so nothing is lost by having no kernel to merge them from.
|
|
70
|
+
|
|
71
|
+
### No reference/
|
|
72
|
+
|
|
73
|
+
`web-app` ships `reference/architecture.ref.md` and `reference/message-queue-patterns.ref.md`,
|
|
74
|
+
because it has an architecture to describe.
|
|
75
|
+
This profile has none — the reference pages it would want
|
|
76
|
+
already ship with the capabilities themselves.
|
|
77
|
+
There is no `profiles/development/reference/` directory rather than an empty one,
|
|
78
|
+
because the installed-layout generator walks every profile directory
|
|
79
|
+
and an empty directory is a promise of content that never arrives.
|
|
@@ -25,16 +25,16 @@ kernel:
|
|
|
25
25
|
nodeVersion: ">=24.0.0"
|
|
26
26
|
dependencies: []
|
|
27
27
|
devDependencies:
|
|
28
|
-
- typescript
|
|
29
|
-
- tsx
|
|
30
|
-
- nodemon
|
|
31
|
-
- "@types/express"
|
|
32
|
-
- "@types/node"
|
|
28
|
+
- typescript@^5
|
|
29
|
+
- tsx@^4
|
|
30
|
+
- nodemon@^3
|
|
31
|
+
- "@types/express@^5"
|
|
32
|
+
- "@types/node@^25"
|
|
33
33
|
scripts:
|
|
34
34
|
start: "tsx src/server.ts"
|
|
35
35
|
dev: "nodemon --exec tsx src/server.ts"
|
|
36
36
|
build: "tsc"
|
|
37
|
-
test: "jest
|
|
37
|
+
test: "jest"
|
|
38
38
|
---
|
|
39
39
|
|
|
40
40
|
# Web App Profile
|
|
@@ -104,6 +104,7 @@ These capabilities are not included by default but can be added to a web-app pro
|
|
|
104
104
|
|
|
105
105
|
- **users-and-accounts** — User management, authentication, and account settings. Add with `boop add users-and-accounts`.
|
|
106
106
|
- **noticeboard** — A richer reference demo showcasing the full service-layer pattern. Add with `boop add noticeboard`.
|
|
107
|
+
- **oauth-client** — Sign in with Google or Microsoft, reconciled onto one user, with provider tokens held for API calls. Requires `users-and-accounts`. Add with `boop add oauth-client`.
|
|
107
108
|
|
|
108
109
|
## Reference & Skills
|
|
109
110
|
|