@boopkit/cli 0.6.9 → 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 +13 -3
- package/dist/capability-versions.json +10 -10
- 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 +2 -0
- package/package.json +6 -2
- package/profiles/development/profile.md +79 -0
- package/profiles/web-app/profile.md +6 -6
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
|
@@ -37,6 +37,7 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
|
|
|
37
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.
|
|
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
|
+
- **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`.
|
|
40
41
|
|
|
41
42
|
### `testing` — Jest testing setup
|
|
42
43
|
|
|
@@ -62,6 +63,7 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
|
|
|
62
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.
|
|
63
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.
|
|
64
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.
|
|
65
67
|
|
|
66
68
|
## Application capabilities
|
|
67
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",
|
|
@@ -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
|