@boopkit/cli 0.8.0 → 0.8.2

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.
Files changed (43) hide show
  1. package/README.md +8 -6
  2. package/dist/capability-catalog.json +10 -3
  3. package/dist/capability-versions.json +13 -12
  4. package/dist/cli.js +0 -0
  5. package/dist/commands/add.js +35 -7
  6. package/dist/commands/adopt.js +1 -1
  7. package/dist/commands/init.d.ts +5 -0
  8. package/dist/commands/init.js +12 -5
  9. package/dist/commands/loop-merge.d.ts +11 -0
  10. package/dist/commands/loop-merge.js +135 -14
  11. package/dist/commands/loop.d.ts +5 -1
  12. package/dist/commands/loop.js +38 -19
  13. package/dist/commands/migrate-skill-edits.js +2 -2
  14. package/dist/commands/status.d.ts +10 -0
  15. package/dist/commands/status.js +27 -2
  16. package/dist/commands/update.js +157 -21
  17. package/dist/lib/capability.js +4 -1
  18. package/dist/lib/execute.js +30 -3
  19. package/dist/lib/install-layout.d.ts +5 -0
  20. package/dist/lib/install-layout.js +8 -0
  21. package/dist/lib/manifest.d.ts +1 -1
  22. package/dist/lib/moves.d.ts +88 -0
  23. package/dist/lib/moves.js +381 -0
  24. package/dist/lib/original-text.d.ts +2 -0
  25. package/dist/lib/original-text.js +15 -7
  26. package/dist/lib/preflight.d.ts +14 -1
  27. package/dist/lib/preflight.js +53 -17
  28. package/dist/lib/project.d.ts +17 -0
  29. package/dist/lib/project.js +26 -0
  30. package/dist/lib/scaffold.d.ts +12 -1
  31. package/dist/lib/scaffold.js +43 -4
  32. package/dist/lib/stale-scripts.js +54 -14
  33. package/dist/lib/update.d.ts +36 -2
  34. package/dist/lib/update.js +82 -4
  35. package/docs/init.md +22 -0
  36. package/docs/loop.md +13 -3
  37. package/docs/skills.md +23 -15
  38. package/package.json +1 -1
  39. package/profiles/base/profile.md +13 -10
  40. package/profiles/development/profile.md +10 -8
  41. package/profiles/web-app/profile.md +6 -4
  42. package/profiles/web-app/reference/message-queue-patterns.ref.md +8 -13
  43. package/src/templates/kernel/tests/app.test.ts.hbs +17 -0
@@ -43,6 +43,7 @@ exports.SKILL_EDITS_INSTRUCTION = exports.TRIAGE_SKILL = void 0;
43
43
  exports.compareFileHash = compareFileHash;
44
44
  exports.planCapabilityUpdate = planCapabilityUpdate;
45
45
  exports.executeUpdatePlan = executeUpdatePlan;
46
+ exports.refreshCapabilityEnv = refreshCapabilityEnv;
46
47
  exports.countSkillEdits = countSkillEdits;
47
48
  exports.formatUpdateSummary = formatUpdateSummary;
48
49
  const fs = __importStar(require("fs"));
@@ -71,7 +72,7 @@ function compareFileHash(filePath, storedHash) {
71
72
  * Plan an update for a single capability. Returns null if the capability
72
73
  * is not installed or has no package.
73
74
  */
74
- function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
75
+ function planCapabilityUpdate(targetDir, capabilityName, projectManifest, options = {}) {
75
76
  // Capability must be installed
76
77
  if (!projectManifest.capabilities.includes(capabilityName)) {
77
78
  return null;
@@ -102,8 +103,14 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
102
103
  for (const placement of (0, install_layout_1.capabilityPlacementsFromDir)(pkgDir)) {
103
104
  packageFiles.set(placement.dest, placement.source);
104
105
  }
106
+ const arriving = options.arriving ?? [];
107
+ const moves = options.moves ?? [];
108
+ // A held move is one action for a whole folder, named without its trailing slash
109
+ const coveredByMove = (relPath) => moves.some(m => (0, install_layout_1.isUnderAny)(relPath, [m.relativePath, m.relativePath + '/']));
105
110
  // For each file in the package, determine action
106
111
  for (const [relPath, srcPath] of packageFiles) {
112
+ if ((0, install_layout_1.isUnderAny)(relPath, arriving))
113
+ continue;
107
114
  const destPath = path.join(targetDir, relPath);
108
115
  const storedHash = storedHashes[relPath];
109
116
  const newHash = (0, scaffold_1.hashFile)(srcPath);
@@ -210,7 +217,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
210
217
  }
211
218
  // Check for files removed from package (in stored hashes but not in package)
212
219
  for (const relPath of Object.keys(storedHashes)) {
213
- if (!packageFiles.has(relPath)) {
220
+ if (!packageFiles.has(relPath) && !coveredByMove(relPath)) {
214
221
  actions.push({
215
222
  relativePath: relPath,
216
223
  action: 'deprecate',
@@ -269,6 +276,7 @@ function planCapabilityUpdate(targetDir, capabilityName, projectManifest) {
269
276
  // oldState === 'missing': old file already gone, add new files normally
270
277
  }
271
278
  }
279
+ actions.push(...moves);
272
280
  // A capability that ships a new command ships it as a file and a script; an
273
281
  // update that delivered only the file left nothing able to run it (boop-ldou).
274
282
  const scriptActions = (0, script_merge_1.planScriptUpdates)(capJson.scripts, (0, script_merge_1.readProjectScripts)(targetDir));
@@ -304,6 +312,9 @@ function executeUpdatePlan(plan, options = {}) {
304
312
  scriptsKept: [],
305
313
  unchangedCount: plan.unchangedCount,
306
314
  instructions: [],
315
+ moved: [],
316
+ removed: [],
317
+ movesWaiting: [],
307
318
  };
308
319
  const pkgJson = JSON.parse(fs.readFileSync(path.join(plan.packageDir, 'package.json'), 'utf-8'));
309
320
  const boopkit = pkgJson.boopkit;
@@ -335,7 +346,13 @@ function executeUpdatePlan(plan, options = {}) {
335
346
  break;
336
347
  case 'skip': {
337
348
  const isLocallyDeleted = action.reason === 'Locally deleted';
338
- if (isLocallyDeleted && options.restore) {
349
+ if (action.movedTo) {
350
+ // A move waiting on something only a person or another command can settle; --force and --restore do not apply
351
+ result.skipped.push(action.relativePath);
352
+ result.movesWaiting.push(action.relativePath);
353
+ result.instructions.push({ relativePath: action.relativePath, instruction: action.instruction });
354
+ }
355
+ else if (isLocallyDeleted && options.restore) {
339
356
  // Restore mode: re-add locally deleted files
340
357
  const srcPath = resolveSourcePath(plan.packageDir, action.relativePath);
341
358
  if (srcPath) {
@@ -428,15 +445,27 @@ function executeUpdatePlan(plan, options = {}) {
428
445
  if (!manifest.capabilityMeta) {
429
446
  manifest.capabilityMeta = {};
430
447
  }
448
+ const previous = manifest.capabilityMeta[plan.capability];
431
449
  manifest.capabilityMeta[plan.capability] = {
432
450
  installedAt: new Date().toISOString(),
433
451
  version: plan.newVersion,
434
452
  fileHashes: newHashes,
453
+ ...movedFromStill(previous, newHashes),
435
454
  };
436
455
  (0, project_1.writeProjectManifest)(plan.targetDir, manifest);
437
456
  }
438
457
  return result;
439
458
  }
459
+ /**
460
+ * The `movedFrom` entries still worth keeping: once this capability writes a
461
+ * file's hash itself, the base is a file it shipped, and the old path no longer
462
+ * leads to the original text.
463
+ */
464
+ function movedFromStill(previous, newHashes) {
465
+ const kept = Object.entries(previous?.movedFrom ?? {})
466
+ .filter(([p]) => newHashes[p] !== undefined && newHashes[p] === previous.fileHashes[p]);
467
+ return kept.length > 0 ? { movedFrom: Object.fromEntries(kept) } : {};
468
+ }
440
469
  /**
441
470
  * What to tell whoever ran the update about a file it skipped, or nothing.
442
471
  * `drift` is absent for a file boop has no stored hash for.
@@ -486,6 +515,40 @@ function displayPath(targetDir, filePath) {
486
515
  const relative = path.relative(targetDir, filePath);
487
516
  return relative.startsWith('..') || path.isAbsolute(relative) ? filePath : relative;
488
517
  }
518
+ /**
519
+ * Rewrite each capability's stored env record (capabilityConfig[cap].env) from
520
+ * the env its installed package declares. A variable the package still declares
521
+ * keeps its `set` flag; one it no longer declares is dropped, and a capability
522
+ * that declares none loses its record. `boop status` prefers this record over
523
+ * the package, so a stale one would list a moved variable under both capabilities.
524
+ */
525
+ function refreshCapabilityEnv(targetDir, capabilities) {
526
+ const manifest = (0, project_1.readProjectManifest)(targetDir);
527
+ if (!manifest)
528
+ return;
529
+ const config = manifest.capabilityConfig ?? {};
530
+ for (const cap of capabilities) {
531
+ const meta = (0, capability_1.getCapability)(cap, [targetDir]);
532
+ if (!meta)
533
+ continue;
534
+ const stored = config[cap]?.env ?? {};
535
+ if (meta.env && meta.env.length > 0) {
536
+ const env = {};
537
+ for (const e of meta.env) {
538
+ env[e.name] = { required: e.required, set: stored[e.name]?.set ?? false };
539
+ }
540
+ config[cap] = { env };
541
+ }
542
+ else {
543
+ delete config[cap];
544
+ }
545
+ }
546
+ if (Object.keys(config).length > 0)
547
+ manifest.capabilityConfig = config;
548
+ else
549
+ delete manifest.capabilityConfig;
550
+ (0, project_1.writeProjectManifest)(targetDir, manifest);
551
+ }
489
552
  /**
490
553
  * Count the skills a capability's project copy has edited in SKILL.md, and how
491
554
  * many of those boop has changed since — from the same comparison `boop update`
@@ -523,6 +586,17 @@ function resolveSourcePath(pkgDir, relativePath) {
523
586
  */
524
587
  function formatUpdateSummary(result) {
525
588
  const parts = [];
589
+ const movedTo = new Map();
590
+ for (const m of result.moved ?? [])
591
+ movedTo.set(m.toCapability, [...(movedTo.get(m.toCapability) ?? []), m]);
592
+ for (const [capability, moves] of movedTo) {
593
+ const noun = moves.every(m => m.from.endsWith('/')) ? 'skill' : 'file';
594
+ parts.push(`moved ${moves.length} ${noun}${moves.length === 1 ? '' : 's'} to ${capability}`);
595
+ }
596
+ const removed = result.removed?.length || 0;
597
+ if (removed > 0) {
598
+ parts.push(`removed ${removed} skill${removed === 1 ? '' : 's'}`);
599
+ }
526
600
  if (result.migrated && result.migrated.length > 0) {
527
601
  parts.push(`migrated ${result.migrated.length} skill${result.migrated.length === 1 ? '' : 's'}`);
528
602
  }
@@ -533,7 +607,11 @@ function formatUpdateSummary(result) {
533
607
  if (result.forcedOverwrite.length > 0) {
534
608
  parts.push(`force-overwrote ${result.forcedOverwrite.length} modified file${result.forcedOverwrite.length === 1 ? '' : 's'}`);
535
609
  }
536
- const skippedModified = result.skipped.length - (result.skippedDeleted?.length || 0);
610
+ const waiting = result.movesWaiting?.length || 0;
611
+ if (waiting > 0) {
612
+ parts.push(`${waiting} move${waiting === 1 ? '' : 's'} waiting`);
613
+ }
614
+ const skippedModified = result.skipped.length - (result.skippedDeleted?.length || 0) - waiting;
537
615
  if (skippedModified > 0) {
538
616
  parts.push(`skipped ${skippedModified} (locally modified)`);
539
617
  }
package/docs/init.md CHANGED
@@ -66,6 +66,7 @@ It has neither a kernel nor templates, so there is nothing for `boop init` to wr
66
66
  and `boop init --profile development` is refused with a pointer to the two ways it is meant to be used:
67
67
  naming it as the `profile` in an existing project's `.boop/config.json`,
68
68
  or `boop add` for the capabilities you want.
69
+ The second is the usual one; see [Bringing Boop Into an Existing Project](#bringing-boop-into-an-existing-project).
69
70
 
70
71
  ## What It Creates
71
72
 
@@ -207,6 +208,27 @@ The counts change as capabilities grow.
207
208
  A `base` project's summary counts template files rather than kernel files,
208
209
  and its next steps stop at `boop status`, because there is no development server to start.
209
210
 
211
+ ## Bringing Boop Into an Existing Project
212
+
213
+ `boop init` only creates new projects: it refuses a folder that is not empty.
214
+ To bring boop's development capabilities into a project something else made —
215
+ an app from `create-expo-app` or Xcode, a CLI, a library —
216
+ run `boop add` in that project for each capability you want:
217
+
218
+ ```bash
219
+ cd my-expo-app
220
+ boop add project-mgmt
221
+ boop add coding
222
+ boop status
223
+ ```
224
+
225
+ The first `boop add` creates the `.boop/config.json` the project lacks and says so in its output.
226
+ The project is named after the `name` in its `package.json`, or after its folder when there is none,
227
+ and its profile is recorded as `development`.
228
+ A project with no `package.json` at all, such as a native iOS or Android app, also gets a minimal one,
229
+ the same one the `base` profile writes, because boop delivers every capability as an npm package recorded there.
230
+ If an add is refused — a capability that needs another one added first, for example — it creates neither file.
231
+
210
232
  ## What To Do After Init
211
233
 
212
234
  Run the commands under "Next steps".
package/docs/loop.md CHANGED
@@ -68,6 +68,8 @@ If `--prefix` is also provided, it is passed to the planning session as a naming
68
68
  | `--no-skip-perms` | off | Don't skip Claude permission prompts |
69
69
  | `--json` | off | Output summary as JSON |
70
70
 
71
+ Every Claude session the loop spawns, planning and issue sessions alike, runs with `CLAUDE_CODE_ENTRYPOINT=boop-loop`, which marks its transcript as machine-driven, and `CLAUDE_CODE_DISABLE_AUTO_MEMORY=1`, which keeps the operator's auto-memory out of it.
72
+
71
73
  ## Worktree Isolation (default)
72
74
 
73
75
  By default, `boop loop` runs **inside its own git worktree** so that N loops can run concurrently without colliding. On start (with `--run`) the loop:
@@ -115,14 +117,22 @@ For each ready branch (one at a time, with `main` as the integration branch):
115
117
 
116
118
  1. **Readiness** — a branch is ready when every beads issue in its scope is closed (scope is read from the worktree's `.boop-worktree.json`). `bd ready` is insufficient here — it only lists *unblocked* issues — so `--merge` lists by status and filters by scope. An explicit `boop loop --merge work/<scope>` skips this check.
117
119
  2. **Rebase onto main** inside the worktree. Only **`package.json` dependency conflicts** are auto-resolved (union of dependencies; same-key version conflicts take the higher semver). Any other conflict aborts the rebase, files a beads bug, and skips to the next branch.
118
- 3. **Run `npm test`** in the worktree. On failure the branch is reset to its pre-rebase commit, a beads bug is filed, and the branch is skipped.
120
+ A rebase that does not complete for any other reason is handled the same way, before any test runs: the branch is skipped, its worktree and `main` are left as they were, and the filed bug says why.
121
+ The common case is a worktree with uncommitted changes to tracked files (the loop's `npm install` can rewrite `package-lock.json`); the bug then names those files, and you commit or discard them in the worktree before running `--merge` again.
122
+ When git refuses the rebase for another reason, such as an untracked file that `main` would overwrite, the bug carries git's own message.
123
+ 3. **Run `npm test`** in the worktree. On failure the branch is reset to its pre-rebase commit, a beads bug is filed, and the branch is skipped. The bug names the branch and the restored commit, and carries the first failures and the Jest summary, each capped in length; the full test output is saved to `labs/claude-runs/_merge_<branch>_<timestamp>.log` and the bug gives its path.
119
124
  4. **Fast-forward merge** into `main`, then remove the worktree and delete the merged branch.
120
125
 
121
- **Safety contract:** `main` is never left dirty. The integration tree must be on a clean `main` before merging; a rebase conflict aborts cleanly, and a post-rebase test failure resets the feature branch. Every merge runs the test suite before completing, so no merge lands untested.
126
+ **Safety contract:** `main` is never left dirty. The integration tree must be on a clean `main` before merging; a rebase conflict aborts cleanly, a rebase that does not complete is never tested or merged, and a post-rebase test failure resets the feature branch. Every merge runs the test suite before completing, so no merge lands untested.
127
+
128
+ **Beads export files do not block a merge.** `bd` rewrites `.beads/interactions.jsonl` in the main checkout whenever it runs, including every `bd` command a loop session runs from inside its own worktree, so after any loop `main` shows that file modified.
129
+ `--merge` treats an uncommitted change to `.beads/interactions.jsonl` or `.beads/issues.jsonl` as beads bookkeeping rather than work in progress: it merges past it and leaves the change in place for you to commit when you choose.
130
+ Any other uncommitted change still stops the merge, and the message names the files.
131
+ One exception keeps the fast-forward from running over your change: if a branch itself changes an export file that is uncommitted in `main`, that branch is skipped with a message saying so, and the other ready branches still merge. Commit the file in `main` and run `--merge` again.
122
132
 
123
133
  ## Logs
124
134
 
125
- Session logs are written to `labs/claude-runs/` in the project directory. Each issue produces a log file named `<issue-id>_<timestamp>.log` containing the full Claude stream-json output.
135
+ Session logs are written to `labs/claude-runs/` in the project directory. Each issue produces a log file named `<issue-id>_<timestamp>.log` containing the full Claude stream-json output. A `--merge` test failure saves the full test output as `_merge_<branch>_<timestamp>.log`.
126
136
 
127
137
  ## Safety
128
138
 
package/docs/skills.md CHANGED
@@ -20,15 +20,20 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
20
20
  - **boop-product-design-feature-documentation** — When you need to document a complex feature, function, or system — including when you frame the task as 'write the docs', 'write a README', 'write up how X works', or 'explain this for the next reader who picks it up cold'.
21
21
  - **boop-product-design-feature-requirements** — When you need to write a feature spec, draft a spec document, or turn a brief or vague directive into requirements — i.e. define what a feature should accomplish before designing how to build it. This fires even when you frame the task to yourself as just 'writing a spec' or 'matching the house spec format': reading an existing spec to imitate its shape is not a substitute for the method, and the more competent you feel at the raw task the more this applies. Start with the user's jobs to be done, in frequency order; take and defend a stance; then derive goals, non-goals, and success metrics.
22
22
  - **boop-product-design-reference-integration** — When you need to integrate new knowledge into the project's `reference/` directory after completing a feature, fixing a bug that changes behavior, or adding a capability.
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.
23
+ - **boop-product-design-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.
24
+ - **boop-product-design-docs-sweep-workflow** — When `specs/` has piled up with documents nobody has looked at in months and you need to know which are finished, which have gone stale, and which are the only surviving record of real work, follow this process: measure the set rather than read it, classify each document against what the issue tracker says (the close reason, not the status), pass the gate before archiving anything, and make the mechanical fixes while surfacing the judgement calls. The document-set sibling of issue-sweep. Not for folding one shipped feature into `reference/`.
25
+ - **boop-product-design-reference-restructure-workflow** — When `reference/` has grown by accretion and subjects a reader would look up have no page of their own (they live as sections inside pages about something else, or in pieces across several pages), follow this process: measure the whole set rather than read it, write down the candidate subjects, then promote each one in a worktree, one commit per subject, for the operator to review as a diff. The whole-set counterpart of reference-integration, which promotes a buried subject one change at a time.
26
+ - **boop-product-design-thought-partnership** — When the user wants to think out loud through a design — a feature, a capability, a strategic direction — or to drive a set of features forward with you interactively, through design, code, review and adjustment, be a thought partner, not an implementer: hold the conversation at user-and-value altitude, honor uncertainty with hypotheses rather than pretend-decisions, record each ruling where the next reader will find it, and keep software-shaped concerns downstream of the user's lived problems.
24
27
  - **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
28
  - **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
29
  - **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.
27
30
 
28
- ### `project-mgmt` — Project management with beads
31
+ ### `project-mgmt` — Project management with beads: planning, issue sweeps and stray disposition
29
32
 
30
33
  - **boop-project-mgmt-beads-planning** — When you need to write and organize beads issues so that any fresh agent can pick up work.
31
34
  - **boop-project-mgmt-engineering-planning** — When you need to decompose a design into an executable plan of work — including when you frame the task as 'make a plan', 'break this into tasks or steps', 'figure out the order of work', or planning a build before you start writing issues. Order the work by uncertainty reduction, not just demonstrable value.
35
+ - **boop-project-mgmt-issue-sweep-workflow** — When the bd queue has accumulated a noisy backlog of lingering P1/P2 items — paper-cuts, small refactors, dead code, cosmetic bugs, taxonomy drift — and you want to clear them autonomously in a sequence that supports the week's goals, follow this process: inventory → cluster by theme → pick a cluster with the operator → run autonomously in one sweep worktree → hand off for a single UAT pass → log. The work lands in a worktree for one operator review rather than on main; it arrives with no decisions left in it.
36
+ - **boop-project-mgmt-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.
32
37
 
33
38
  ### `skills` — Skill authoring
34
39
 
@@ -40,31 +45,29 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
40
45
  - **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
46
  - **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.
42
47
 
43
- ### `testing` — Jest testing setup
48
+ ### `testing` — Testing method: the testing principles and the TDD-Lite workflow
44
49
 
45
50
  - **boop-testing-tdd-light** — When you need to build features, implement changes, or fix bugs, follow the Light TDD Flow. This includes bug fixes found during UAT — write the failing test *before* applying the fix.
46
- - **boop-testing-testing-express-web** — When you need to write or run tests for the Express server — services, controllers, middleware, and HTTP routes.
47
51
  - **boop-testing-testing** — When you need to write, run, or reason about tests in this project.
48
52
 
53
+ ### `typescript` — TypeScript toolchain: Jest with ts-jest, and the whole-program type check
54
+
55
+ - **boop-typescript-testing** — When you need to run, configure, or debug tests in a TypeScript project — how `npm test` and `npm run typecheck` behave under Jest, ts-jest and `tsc`.
56
+
49
57
  ### `ux` — UX design judgement and the discovery/delivery workflows that operate it
50
58
 
51
- - **boop-ux-purpose-judgement** — When you are evaluating a rendered page and need to judge whether each thing on it belongs there, in the right shape, for the right job. Asks the structural question that the visual lens does not — is this affordance the right idea, not just rendered well. Pairs with ux-visual-judgement (the question of how things look and scan).
52
- - **boop-ux-visual-judgement** — When you are evaluating a rendered page and need to form a principled judgement about how it looks and scans. Visual lens only — eye-path, register, anatomy, scan-line, gestalt, density, voice. Pairs with ux-purpose-judgement (the question of whether each element belongs at all) and with the CSS skill (the grammar of how things are rendered).
53
- - **boop-ux-eval-and-refine-workflow** — When you are tasked with iteratively evaluating and improving an existing rendered page (discovery, not delivery), follow this workflow. Inner loop: capture → eval → recommend → implement → re-capture. Outer: knowledge integration.
54
- - **boop-ux-app-ui-dev-workflow** — When you are tasked with improving an existing app-side page (app UX, not marketing) end-to-end and the input is a target page rather than a written spec, follow this beads-issue-per-step workflow. Sibling to feature-dev-workflow but stripped of backend phases. The judgement lenses are the planning input.
59
+ - **boop-ux-purpose-judgement** — When you are evaluating a rendered screen or page and need to judge whether each thing on it belongs there, in the right shape, for the right job. Asks the structural question that the visual lens does not — is this affordance the right idea, not just rendered well. Pairs with ux-visual-judgement (the question of how things look and scan).
60
+ - **boop-ux-visual-judgement** — When you are evaluating a rendered screen or page and need to form a principled judgement about how it looks and scans. Visual lens only — eye-path, loudness, anatomy, scan-line, whitespace, density, voice. Pairs with ux-purpose-judgement (the question of whether each element belongs at all) and, when the project has the web capability, with boop-web-css (the grammar of how things are rendered).
61
+ - **boop-ux-eval-and-refine-workflow** — When you are tasked with iteratively evaluating and improving an existing rendered screen or page (discovery, not delivery), follow this workflow. Inner loop: capture → eval → recommend → implement → re-capture. Outer: knowledge integration.
62
+ - **boop-ux-app-ui-dev-workflow** — When you are tasked with improving an existing app-side screen or page (app UX, not marketing) end-to-end and the input is a target screen or page rather than a written spec, follow this beads-issue-per-step workflow. Sibling to feature-dev-workflow but stripped of backend phases. The judgement lenses are the planning input.
55
63
 
56
- ### `workflows` — Reusable end-to-end development and writing workflows
64
+ ### `workflows` — Reusable end-to-end development and audit workflows for code
57
65
 
58
- - **boop-feature-dev-workflow** — When you are tasked with building a feature end-to-end, follow this guidance.
66
+ - **boop-feature-dev-workflow** — When you are tasked with building a feature end-to-end, follow this beads-issue-per-step workflow: epic root → understand → plan → decompose → implement → review → punch-list → closeout → reconcile-and-finalize.
59
67
  - **boop-light-dev-workflow** — When you are extending an existing system (front-end, back-end, or full-stack) toward a goal — wiring something new from data and patterns that already exist, not building greenfield — follow this grounded flow: goal → investigate the real system → design → critique → decompose into one scoped epic + child issues → run them → close out. Loop-drivable via boop loop --work --flow; lighter than feature-dev-workflow.
60
68
  - **boop-refactor-dev-workflow** — When you need to change a contract, cardinality, or shape that existing code already depends on — usually to make a feature buildable — follow this workflow instead of the feature workflow.
61
69
  - **boop-test-audit-workflow** — Use when a set of tests is not testing the purpose of the code: mocks that verify data the test itself fed in, tests that exercise the platform rather than our code, or a stubbed seam where the integration is the actual risk. Also use when tests are so coupled to the shape of the implementation that they check a label, an exact string, or a call sequence rather than a behaviour. This workflow removes tests that cannot go red for a real reason, rewrites the ones that check a real goal at the wrong level, merges duplicates, and does not lose coverage. It explicitly does not target good tests merely because a coming change will make them need updating. The sibling of light-dev-workflow for reworking tests rather than building features.
62
70
  - **boop-architecture-audit-workflow** — When you need to judge whether an area of shipped code is structurally healthy and decide what is worth fixing — undesired coupling, concerns bleeding across layers or domain boundaries, interfaces that are not composable because they are use-case specific or unparameterized — follow this process: scope one area and name the properties it should uphold → check the tests can catch you → measure before reading → grade every property and classify every finding against a named change → critique → operator gate → decompose → execute via refactor-dev-workflow. The sibling of test-audit-workflow for production code rather than tests. Not for finding bugs, and not for executing a structural change you have already decided on.
63
- - **boop-issue-sweep-workflow** — When the bd queue has accumulated a noisy backlog of lingering P1/P2 items — paper-cuts, small refactors, dead code, cosmetic bugs, taxonomy drift — and you want to clear them autonomously in a sequence that supports the week's goals, follow this process: inventory → cluster by theme → pick a cluster with the operator → run autonomously in one sweep worktree → hand off for a single UAT pass → log. The work lands in a worktree for one operator review rather than on main; it arrives with no decisions left in it.
64
- - **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.
65
- - **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.
66
- - **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.
67
- - **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.
68
71
 
69
72
  ## Application capabilities
70
73
 
@@ -101,6 +104,11 @@ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop
101
104
  ### `web` — Web UI with Handlebars + Tailwind
102
105
 
103
106
  - **boop-web-web-ui-design** — When you need to design and implement user interfaces — including when you frame the task as 'build a page', 'add a component', 'style this surface', or 'wire up the UI' — following established patterns and philosophy.
107
+ - **boop-web-css** — When you need to write or change CSS — adding or renaming a class, writing a selector or a stylesheet rule, or editing a class attribute in a template — name classes for what their elements are for, nest each block's children and variants inside it, and never use BEM's __element or --modifier separators.
108
+ - **boop-web-testing** — When you need to write or run tests for the Express server — services, controllers, middleware, and HTTP routes — or to confirm in a browser that a change to something a person looks at does what its issue said, since a green suite proves behaviour but not what renders.
109
+ - **boop-web-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.
110
+ - **boop-web-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.
111
+ - **boop-web-design-handoff-bundle** — When the operator points at a design canvas (an artifact of artboards) and asks for it to be bundled, turned into a handoff, or made ready for the coding agent, turn it into a design handoff bundle under specs/design-handoffs/: a README, drop-in templates and CSS in the project's conventions, the decisions, and an eval checklist. The producing side of boop-web-design-handoff-workflow; the system wins over the prototype, and every departure is written down.
104
112
 
105
113
  ---
106
114
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
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": [
@@ -31,8 +31,8 @@ and if a file only makes sense for an application, it does not belong here.
31
31
  Three capabilities, all of them `type: development`,
32
32
  whose skills apply to work that isn't code without any edits:
33
33
 
34
- - **project-mgmt** — beads issue tracking and planning skills
35
- - **product-design** — requirements, design, documentation and writing skills
34
+ - **project-mgmt** — beads issue tracking, planning, issue sweeps and stray disposition
35
+ - **product-design** — requirements, design, documentation, knowledge backfill and writing skills
36
36
  - **skills** — skill authoring, discovery and maintenance
37
37
 
38
38
  The profile also ships its own `templates/` folder,
@@ -53,25 +53,28 @@ and `boop update` would have nothing to update.
53
53
  ## Three Deliberate Omissions
54
54
 
55
55
  The development capabilities that every other profile carries —
56
- `coding`, `testing` and `workflows` —
56
+ `coding`, `testing` and `workflows`, and the `typescript` toolchain beside them —
57
57
  are left out on purpose.
58
58
  An agent takes the skills and files in a project as a hint about what kind of work happens there,
59
59
  so every code-shaped thing in the scaffold is an invitation to do code-shaped work.
60
60
 
61
61
  ### No testing
62
62
 
63
- `testing` installs Jest, `ts-jest`, a `tsconfig.test.json` and a `tests/` folder.
64
- A project with nothing to test would carry a test runner nobody runs,
65
- and an agent that finds one will reasonably suggest writing tests for it.
63
+ `testing` installs the testing skills, and `typescript`, which comes with it in the other profiles,
64
+ installs Jest, `ts-jest`, a `tsconfig.test.json` and a `tests/` folder.
65
+ A project with nothing to test would carry testing guidance and a test runner nobody uses,
66
+ and an agent that finds them will reasonably suggest writing tests.
66
67
 
67
68
  ### No workflows
68
69
 
69
- Seven of the ten `workflows` skills are for code:
70
- feature development, refactoring, test audits, website work and so on.
70
+ Every `workflows` skill is for code:
71
+ feature development, refactoring, test audits and architecture audits.
71
72
  An agent asked to "set up the network" could reasonably pick one of them up
72
73
  and start a feature branch for something that has no code in it.
73
- The three that suit any project — issue sweeps, knowledge backfill and stray disposition —
74
- can join this profile once `workflows` is split (tracked in `boop-4n87`).
74
+ The workflows that suit any project that tracks its work in beads —
75
+ issue sweeps, stray disposition and knowledge backfill —
76
+ ship in `project-mgmt` and `product-design` instead,
77
+ so this profile receives them without `workflows`.
75
78
 
76
79
  ### No coding
77
80
 
@@ -4,6 +4,7 @@ description: Development skills only — for a project with no application domai
4
4
  defaultCapabilities:
5
5
  # Development
6
6
  - coding
7
+ - typescript
7
8
  - testing
8
9
  - project-mgmt
9
10
  - product-design
@@ -31,16 +32,17 @@ but they are all developed the same way.
31
32
 
32
33
  ## What You Get
33
34
 
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:
35
+ Seven capabilities, all of them `type: development` —
36
+ skill bundles that guide agent behaviour,
37
+ plus the TypeScript toolchain those skills assume:
37
38
 
38
39
  - **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
40
+ - **typescript** — the TypeScript toolchain: Jest with ts-jest, `tsconfig.test.json`, and the `test` and `typecheck` scripts
41
+ - **testing** — the testing principles and the TDD-Lite workflow
42
+ - **project-mgmt** — beads issue tracking, planning, issue sweeps and stray disposition
43
+ - **product-design** — feature requirements, engineering design, documentation and knowledge backfill skills
42
44
  - **skills** — skill authoring, discovery and maintenance
43
- - **workflows** — the end-to-end development and writing workflows
45
+ - **workflows** — the end-to-end code development and audit workflows
44
46
 
45
47
  No web layer, no API layer, no service layer, no database.
46
48
  Add any of those with `boop add` if the project grows an application domain.
@@ -72,7 +74,7 @@ and the consequence is deliberate: `boop init --profile development` is refused
72
74
  with a message pointing at the two ways this profile is actually meant to be used —
73
75
  name it in an existing project's `.boop/config.json`,
74
76
  or `boop add` these capabilities one at a time.
75
- The capabilities carry their own dependencies (`jest`, `ts-jest`, `beads`),
77
+ The capabilities carry their own dependencies (`typescript` brings `jest` and `ts-jest`, and `project-mgmt` brings `beads`),
76
78
  so nothing is lost by having no kernel to merge them from.
77
79
 
78
80
  ### No reference/
@@ -9,6 +9,7 @@ defaultCapabilities:
9
9
  - database
10
10
  # Development
11
11
  - coding
12
+ - typescript
12
13
  - testing
13
14
  - project-mgmt
14
15
  - product-design
@@ -86,11 +87,12 @@ The `web-app` profile ships all of the following capabilities by default:
86
87
  - **services** — Service layer scaffolding, auto-discovery, and design skills
87
88
  - **database** — PostgreSQL with Prisma ORM, migrations, and seeding
88
89
 
89
- **Development capabilities** — pure skill bundles that guide agent behavior:
90
+ **Development capabilities** — skill bundles that guide agent behavior, and the toolchain they assume:
90
91
  - **coding** — Development conventions and code style skills
91
- - **testing** — Jest setup, test utilities, TDD workflow skills
92
- - **project-mgmt** — Beads issue tracking, planning skills
93
- - **product-design** — Feature requirements, design thinking skills
92
+ - **typescript** — The TypeScript toolchain: Jest with ts-jest, `tsconfig.test.json`, and the `test` and `typecheck` scripts
93
+ - **testing** — Testing principles and the TDD-Lite workflow skills
94
+ - **project-mgmt** — Beads issue tracking, planning, issue sweeps and stray disposition
95
+ - **product-design** — Feature requirements, design thinking and knowledge backfill skills
94
96
  - **skills** — Skill authoring and maintenance
95
97
 
96
98
  **Demo:**
@@ -26,28 +26,23 @@ await MessageQueueService.publish('batch-ready', [message1, message2]);
26
26
  - **Error recovery**: If `publish()` catches an error (e.g., channel closed), it resets connection state and returns `false` — the next call will reconnect
27
27
  - **Shutdown**: The publish connection is tracked and closed by `close()` alongside the listener connection
28
28
 
29
- ### Publishing to a queue that does not exist
29
+ ### Publishing to a queue that does not exist yet
30
30
 
31
- `publish()` does not declare the queue; only `listen()` does.
32
- A broker confirms a message that it cannot put in any queue and then discards it,
33
- so a publish to a name that no consumer has ever listened on returns `true` and the message is lost.
31
+ `publish()` declares the queue's topology (main, `.retry` and `.failed` queues and their exchanges) before its first publish to it on a connection, exactly as `listen()` does,
32
+ so a message published before the consumer has ever run is kept until the consumer attaches.
34
33
 
35
- Pass `{ mandatory: true }` as the third argument when the publisher cannot be sure the queue exists:
36
- when the name is composed at run time (for example with `targetPrefix`),
37
- or when the publisher can start before the consumer ever has.
34
+ - Each queue is declared once per publish connection; concurrent first publishes share the declaration, and a queue a consumer in the same process already declared with the same arguments is not declared again
35
+ - The declaration runs on a short-lived channel, so a refused one (`PRECONDITION_FAILED`, when the arguments differ from the existing queue's) fails that publish alone: it is logged with the queue's name and `publish()` returns `false`
36
+ - Pass the consumer's `queueArguments` in the third argument whenever the consumer declares any:
38
37
 
39
38
  ```typescript
40
- const queued = await MessageQueueService.publish('batch-ready', message, { targetPrefix, mandatory: true });
39
+ const queued = await MessageQueueService.publish('batch-ready', message, { targetPrefix, queueArguments: BATCH_QUEUE_ARGUMENTS });
41
40
  if (!queued) {
42
41
  // Not queued anywhere: record or report it.
43
42
  }
44
43
  ```
45
44
 
46
- - The message is sent with the AMQP `mandatory` flag, and the broker returns it when no queue has the destination's name
47
- - `publish()` then logs `publish to "<queue>" was returned by the broker: no queue has that name` and returns `false`
48
- - The connection state is **not** reset in this case — the connection is healthy, and the next `publish()` reuses it
49
- - A queue that exists but has no consumer running still takes the message; the option reports a missing queue, not a missing worker
50
- - Without the option, `publish()` is unchanged
45
+ - `{ mandatory: true }` still makes the broker return a message no queue took; after the declaration that means a real fault, such as a queue deleted by hand, and `publish()` returns `false` without resetting the connection
51
46
 
52
47
  ### Why persistent (not per-call)
53
48
 
@@ -0,0 +1,17 @@
1
+ import app from '../src/app';
2
+
3
+ describe('app', () => {
4
+ it('exports a valid Express application', () => {
5
+ expect(app).toBeDefined();
6
+ expect(typeof app.get).toBe('function');
7
+ expect(typeof app.post).toBe('function');
8
+ expect(typeof app.use).toBe('function');
9
+ });
10
+
11
+ it('has JSON and URL-encoded body parsing enabled', () => {
12
+ const stack = (app as any).router?.stack ?? (app as any)._router?.stack ?? [];
13
+ const layerNames = stack.map((layer: any) => layer.name);
14
+ expect(layerNames).toContain('jsonParser');
15
+ expect(layerNames).toContain('urlencodedParser');
16
+ });
17
+ });