@boopkit/cli 0.6.0 → 0.6.7

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 CHANGED
@@ -102,8 +102,39 @@ Pick what the product needs. `boop add <name>` handles the wiring — npm depend
102
102
  | `coding` | Development conventions and coding skills for AI agents |
103
103
  | `testing` | Jest setup and testing skills (TDD workflow, Express testing patterns) |
104
104
  | `project-mgmt` | Beads issue-tracking and planning skills |
105
- | `product-design` | Feature-requirements and product-design skills |
106
- | `skills` | Skill-authoring guidance for extending the skill library |
105
+ | `product-design` | Product design skills — feature requirements, engineering design, documentation, design thought-partnership, and technical prose |
106
+ | `ux` | UX judgement (purpose + visual) and the app-UI evaluation and delivery workflows |
107
+ | `skills` | Skill-authoring and skill-discovery guidance for extending the skill library |
108
+
109
+ ### Skills each capability ships
110
+
111
+ Adding a capability installs its skills into your project's `skills/` directory. Here's the full set, grouped by capability (one-line descriptions for each are in the bundled `docs/skills.md`):
112
+
113
+ <!-- SKILLS-INDEX:START -->
114
+ <!-- Generated by `npm run skills:index`. Do not edit by hand. -->
115
+
116
+ **Development capabilities (skills only — no runtime code)**
117
+
118
+ - **`coding`** — boop-coding-beads-flow, boop-coding-development, boop-coding-commit-changes, boop-coding-review-changes, boop-coding-debugging
119
+ - **`product-design`** — boop-product-design-engineering-design, boop-product-design-feature-documentation, boop-product-design-feature-requirements, boop-product-design-reference-integration, boop-product-design-thought-partnership, boop-writing-technical-prose
120
+ - **`project-mgmt`** — boop-project-mgmt-beads-planning, boop-project-mgmt-engineering-planning
121
+ - **`skills`** — boop-skill-authoring, boop-skills-discovery, boop-skills-agent-surface-design
122
+ - **`testing`** — boop-testing-tdd-light, boop-testing-testing-express-web, boop-testing-testing
123
+ - **`ux`** — boop-ux-purpose-judgement, boop-ux-visual-judgement, boop-ux-eval-and-refine-workflow, boop-ux-app-ui-dev-workflow
124
+
125
+ **Application capabilities**
126
+
127
+ - **`api`** — boop-api-rest-api-design
128
+ - **`background-jobs`** — boop-background-jobs-background-jobs-design
129
+ - **`cloud-storage`** — boop-cloud-storage-cloud-storage
130
+ - **`database`** — boop-database-prisma, boop-database-data-model
131
+ - **`email`** — boop-email-email-design
132
+ - **`services`** — boop-services-controllers, boop-services-services
133
+ - **`users-and-accounts`** — boop-users-user-data-modeling
134
+ - **`web`** — boop-web-web-ui-design
135
+
136
+ _One-line description of each skill: see the bundled `docs/skills.md`._
137
+ <!-- SKILLS-INDEX:END -->
107
138
 
108
139
  ### Demo
109
140
 
@@ -138,11 +169,11 @@ Flags:
138
169
 
139
170
  Boop is built for agent-assisted work — the skills and conventions ship with the code so your agent has real guidance, not just a blank repo. Every scaffolded project includes:
140
171
 
141
- - **Skills** — every capability installs skill folders into `skills/`. These are focused, task-specific instructions (each folder contains a `SKILL.md`, e.g. `skills/rest-api-design/SKILL.md`, `skills/tdd-light/SKILL.md`) that tell an agent the conventions this project uses.
172
+ - **Skills** — every capability installs skill folders into `skills/`. These are focused, task-specific instructions (each folder contains a `SKILL.md`, e.g. `skills/boop-api-rest-api-design/SKILL.md`, `skills/boop-testing-tdd-light/SKILL.md`) that tell an agent the conventions this project uses. See the bundled **`docs/skills.md`** for the full index of what each capability ships.
142
173
  - **`boop status`** — after `boop init` or `boop add`, run `boop status`. It prints every capability's post-install steps. Hand this to your agent and say "finish setup."
143
174
  - **`boop loop`** — Uses Claude for 'unattended work', building working code and UI from your specifications.
144
- - `prefix` mode: Plan work as a tree of issues, then `boop loop --prefix <id>` processes them one at a time, each in its own Claude Code session. Dry-run by default; budget-capped; resumes unfinished work. See [docs/loop.md](docs/loop.md).
145
- - `workflow` mode: Provide your spec and the bundled `feature-dev-workflow` skill to `boop loop --work <spec> --flow <skill>`. It processes the spec requirements into a technical design, a set of linked tasks and then continuously processes issues until the work is complete. See [docs/loop.md](docs/loop.md#workflow-mode).
175
+ - `prefix` mode: Plan work as a tree of issues, then `boop loop --prefix <id>` processes them one at a time, each in its own Claude Code session. Dry-run by default; budget-capped; resumes unfinished work. See the bundled `docs/loop.md`.
176
+ - `workflow` mode: Provide your spec and the bundled `feature-dev-workflow` skill to `boop loop --work <spec> --flow <skill>`. It processes the spec requirements into a technical design, a set of linked tasks and then continuously processes issues until the work is complete. See the bundled `docs/loop.md`.
146
177
 
147
178
  The workflow is designed so a human sets direction, an agent does the implementation, and boop provides the rails.
148
179
 
@@ -170,17 +201,19 @@ Boop itself is light — Node and npm. The other requirements come from whicheve
170
201
 
171
202
  - **Node.js 24+** (the `web-app` profile enforces this)
172
203
  - **npm** (or any package manager that respects workspaces)
204
+ - **beads (`bd`) 1.1.2+** — if you use the agent workflows or `boop loop`. The tree-building workflows depend on `bd gate` and `bd epic close-eligible` (1.1.x) and on 1.1.2 dependency semantics (plain `blocks` edges; `waits-for` is not used), and `boop loop` needs beads in Dolt server mode. See [beads](https://github.com/gastownhall/beads).
173
205
  - **PostgreSQL** — if the `database` capability is installed
174
206
  - **RabbitMQ / CloudAMQP account** — if `background-jobs` is installed
175
207
  - **SendGrid account** — if `email` is installed
176
208
  - **AWS S3 bucket** — if `cloud-storage` is installed
177
209
 
178
- ## Links
210
+ ## Bundled docs
211
+
212
+ These ship inside the package (under `docs/`) — open them after install in `node_modules/@boopkit/cli/docs/`:
179
213
 
180
- - **[docs/init.md](docs/init.md)** — detailed `boop init` walkthrough
181
- - **[docs/loop.md](docs/loop.md)** — automated agent workflow
182
- - **[GitHub repo](https://github.com/boop-labs/boop)** — source and issues
183
- - **[Contributing](https://github.com/boop-labs/boop/blob/main/CONTRIBUTING.md)** — dev setup, architecture, how to add a capability
214
+ - **`docs/init.md`** — detailed `boop init` walkthrough
215
+ - **`docs/loop.md`** — automated agent workflow
216
+ - **`docs/skills.md`** — index of every capability's agent skills
184
217
 
185
218
  ## License
186
219
 
@@ -1,19 +1,19 @@
1
1
  {
2
- "@boopkit/cap-api": "0.4.1",
3
- "@boopkit/cap-background-jobs": "0.4.0",
2
+ "@boopkit/cap-api": "0.4.2",
3
+ "@boopkit/cap-background-jobs": "0.4.2",
4
4
  "@boopkit/cap-cloud-storage": "0.4.0",
5
- "@boopkit/cap-coding": "0.4.1",
5
+ "@boopkit/cap-coding": "0.4.2",
6
6
  "@boopkit/cap-database": "0.4.3",
7
- "@boopkit/cap-email": "0.4.1",
7
+ "@boopkit/cap-email": "0.4.2",
8
8
  "@boopkit/cap-hello": "0.4.0",
9
9
  "@boopkit/cap-noticeboard": "0.4.0",
10
- "@boopkit/cap-product-design": "0.6.1",
11
- "@boopkit/cap-project-mgmt": "0.4.1",
10
+ "@boopkit/cap-product-design": "0.6.4",
11
+ "@boopkit/cap-project-mgmt": "0.4.4",
12
12
  "@boopkit/cap-services": "0.4.2",
13
13
  "@boopkit/cap-skills": "0.5.1",
14
- "@boopkit/cap-testing": "0.5.0",
15
- "@boopkit/cap-users": "0.4.2",
16
- "@boopkit/cap-ux": "0.2.0",
17
- "@boopkit/cap-web": "0.5.0",
14
+ "@boopkit/cap-testing": "0.5.1",
15
+ "@boopkit/cap-users": "0.4.3",
16
+ "@boopkit/cap-ux": "0.2.2",
17
+ "@boopkit/cap-web": "0.5.2",
18
18
  "@boopkit/logger": "0.4.0"
19
19
  }
package/dist/cli.js CHANGED
@@ -119,11 +119,12 @@ program
119
119
  .option('--run', 'actually execute (dry-run by default)')
120
120
  .option('--max <n>', 'stop after N issues')
121
121
  .option('--budget <amount>', `budget cap per issue in USD (default: ${loop_1.DEFAULT_BUDGET_USD})`)
122
- .option('--model <model>', 'Claude model to use, applied to every spawned session and nested loop (falls back to $ANTHROPIC_MODEL, then claude-opus-4-8). Sub-agents keep their own declared model unless they opt into model: inherit.')
122
+ .option('--model <model>', 'Claude model to use, applied to every spawned session and nested loop (falls back to $ANTHROPIC_MODEL, then claude-opus-5). Sub-agents keep their own declared model unless they opt into model: inherit.')
123
123
  .option('--poll', 'keep polling when no ready issues found')
124
124
  .option('--poll-interval <seconds>', 'seconds between polls (default: 60)')
125
125
  .option('--resume-limit <n>', 'max resume attempts per issue (default: 3)')
126
126
  .option('--per-issue-branch', 'create a separate branch per issue')
127
+ .option('--reuse-session', 'issue-driven only: reuse one Claude session across a coherent --prefix run instead of a fresh one per issue, rotating to a fresh session when its context fills. Asserts the scope is coherent enough for carried context to help. Not valid with --work/--flow; mutually exclusive with --per-issue-branch')
127
128
  .option('--no-branch', 'skip branch/worktree creation; run in place on the current branch')
128
129
  .option('--no-skip-perms', 'do not skip permission prompts')
129
130
  .option('--bare', 'force API key auth; skip OAuth/keychain, use ANTHROPIC_API_KEY strictly')
@@ -133,11 +134,13 @@ program
133
134
  'Examples:',
134
135
  ' boop loop --work specs/feature.spec.md --flow skills/my-flow/SKILL.md --run',
135
136
  ' boop loop --prefix boop-abc --run --features',
137
+ ' boop loop --prefix boop-abc --run --reuse-session # drain a coherent cluster on one session',
136
138
  ' boop loop --merge --run --watch',
137
139
  '',
138
140
  'Notes:',
139
141
  ' • Dry-run by default; add --run to execute.',
140
142
  ' • --work and --flow must be used together; --features and --watch only apply to their own mode.',
143
+ ' • --reuse-session is issue-driven (--prefix) only and cannot combine with --per-issue-branch; the loop rotates to a fresh session on its own when context fills, so issues are never split. Off by default (a fresh session per issue).',
141
144
  ' • Merge mode rebases ready work/<id> branches onto main, runs tests, ff-merges, and cleans up worktrees.',
142
145
  ].join('\n'))
143
146
  .action((options) => (options.merge ? (0, loop_merge_1.runMerge)(options) : (0, loop_1.runLoop)(options)));
@@ -17,10 +17,15 @@ export interface LoopOptions {
17
17
  json?: boolean;
18
18
  merge?: boolean | string;
19
19
  watch?: boolean;
20
+ reuseSession?: boolean;
20
21
  }
21
22
  interface ClaudeRunResult {
22
23
  exitCode: number;
23
24
  limitHit: boolean;
25
+ /** Peak context-window occupancy observed on this run, in tokens. */
26
+ peakContext?: number;
27
+ /** The model's context window (tokens), from the result's modelUsage. */
28
+ contextWindow?: number;
24
29
  }
25
30
  export interface PlanResult {
26
31
  featureId: string | null;
@@ -67,6 +72,15 @@ export declare function discoverDoltServer(projectDir: string, deps?: {
67
72
  * branch, reuse it (and skip install if node_modules is present); if the path is
68
73
  * occupied by something else, throw a clear error rather than silently suffixing.
69
74
  */
75
+ /**
76
+ * Best-effort resolution of the repo's default branch, used to move a launch dir
77
+ * off a loop feature branch it should never have been parked on (boop-6iw).
78
+ * Prefers `origin/HEAD`, then a local `main`/`master`, then `main`.
79
+ */
80
+ export declare function resolveDefaultBranch(execSyncFn: (cmd: string, opts: {
81
+ encoding: string;
82
+ cwd?: string;
83
+ }) => string, cwd: string): string;
70
84
  export declare function createWorktree(scope: string, mode: WorktreeMode, projectDir: string, deps?: {
71
85
  execSyncFn?: (cmd: string, opts: {
72
86
  encoding: string;
@@ -136,7 +150,7 @@ export declare function isStandaloneUsageLimitMessage(text: string): boolean;
136
150
  * the `ANTHROPIC_MODEL` environment variable, then the built-in default. The env
137
151
  * fallback is what lets a nested `boop loop` (spawned by a Claude session that
138
152
  * the parent loop launched with ANTHROPIC_MODEL set) inherit the same model
139
- * instead of silently dropping to the default. Keep the default (claude-opus-4-8)
153
+ * instead of silently dropping to the default. Keep the default (claude-opus-5)
140
154
  * in sync with the `--model` help text in cli.ts.
141
155
  */
142
156
  export declare function resolveModel(optionModel: string | undefined, env?: NodeJS.ProcessEnv): string;
@@ -168,12 +182,13 @@ export declare function isClaimableReadyIssue(issue: {
168
182
  issue_type: string;
169
183
  }, hasChildren: (id: string) => boolean): boolean;
170
184
  /**
171
- * Decide whether the loop should close a feature root itself, as a safety net,
185
+ * Decide whether the loop should close a container root itself, as a safety net,
172
186
  * when its workflow chain has completed. The intended owner of this close is the
173
- * closeout child task (see boop-feature-dev-workflow SKILL step 6), but if that
174
- * task never closed the root — or no closeout task exists — the feature would be
175
- * orphaned open forever once filtered from claimable work. Only close when the
176
- * feature is still open and every child is closed.
187
+ * closeout child task (see the flow SKILL's closeout step), but if that task never
188
+ * closed the root — or no closeout task exists — the container would be orphaned
189
+ * open forever once filtered from claimable work. Applies to both container types
190
+ * the loop supports: a `feature` (feature-dev flow) or an `epic` (light-dev flow).
191
+ * Only close when the container is still open and every child is closed.
177
192
  */
178
193
  export declare function shouldCloseFeatureRoot(feature: {
179
194
  status: string;
@@ -201,5 +216,11 @@ export declare const DEFAULT_BUDGET_USD = "25.00";
201
216
  * — this clause is the belt-and-suspenders for a block raised mid-resume.
202
217
  */
203
218
  export declare const RESUME_PROMPT = "The previous session ended without closing this issue. Reload the state and continue.\n\nIMPORTANT: If the previous session marked this issue blocked with a diagnostic, the block stands. Do not re-open it or work around it. Read the issue's notes and the previous session's commits, confirm the block is still appropriate, and stop without taking further action.\n\nOtherwise, complete the workflow:\n- Finish any remaining implementation\n- Run tests to verify\n- Commit changes referencing the issue ID\n- Close the issue with bd close\n- Push with: git pull --rebase && git push\n\nDo not pause for review of completed work. Complete everything now.";
219
+ /**
220
+ * Decide whether a reused session should rotate before taking the next issue.
221
+ * Rotates once peak observed occupancy reaches ROTATE_FRACTION of the window.
222
+ * Pure so it is unit-testable without a running loop.
223
+ */
224
+ export declare function shouldRotateSession(peakContext: number, contextWindow: number, fraction?: number): boolean;
204
225
  export declare function runLoop(options: LoopOptions): Promise<void>;
205
226
  export {};
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.RESUME_PROMPT = exports.DEFAULT_BUDGET_USD = void 0;
37
37
  exports.assertDoltServerMode = assertDoltServerMode;
38
38
  exports.discoverDoltServer = discoverDoltServer;
39
+ exports.resolveDefaultBranch = resolveDefaultBranch;
39
40
  exports.createWorktree = createWorktree;
40
41
  exports.writeWorktreeMetadata = writeWorktreeMetadata;
41
42
  exports.buildPlanningPrompt = buildPlanningPrompt;
@@ -50,6 +51,7 @@ exports.isIssueInScope = isIssueInScope;
50
51
  exports.isClaimableReadyIssue = isClaimableReadyIssue;
51
52
  exports.shouldCloseFeatureRoot = shouldCloseFeatureRoot;
52
53
  exports.getMatchingReadyIssues = getMatchingReadyIssues;
54
+ exports.shouldRotateSession = shouldRotateSession;
53
55
  exports.runLoop = runLoop;
54
56
  const child_process_1 = require("child_process");
55
57
  const fs = __importStar(require("fs"));
@@ -130,6 +132,31 @@ function discoverDoltServer(projectDir, deps) {
130
132
  * branch, reuse it (and skip install if node_modules is present); if the path is
131
133
  * occupied by something else, throw a clear error rather than silently suffixing.
132
134
  */
135
+ /**
136
+ * Best-effort resolution of the repo's default branch, used to move a launch dir
137
+ * off a loop feature branch it should never have been parked on (boop-6iw).
138
+ * Prefers `origin/HEAD`, then a local `main`/`master`, then `main`.
139
+ */
140
+ function resolveDefaultBranch(execSyncFn, cwd) {
141
+ try {
142
+ const ref = execSyncFn('git symbolic-ref --short refs/remotes/origin/HEAD', { encoding: 'utf8', cwd }).trim();
143
+ if (ref)
144
+ return ref.replace(/^origin\//, '');
145
+ }
146
+ catch {
147
+ // origin/HEAD not set — fall through to local candidates.
148
+ }
149
+ for (const cand of ['main', 'master']) {
150
+ try {
151
+ execSyncFn(`git rev-parse --verify --quiet refs/heads/${cand}`, { encoding: 'utf8', cwd });
152
+ return cand;
153
+ }
154
+ catch {
155
+ // not present — try next
156
+ }
157
+ }
158
+ return 'main';
159
+ }
133
160
  function createWorktree(scope, mode, projectDir, deps) {
134
161
  const execSyncFn = deps?.execSyncFn ||
135
162
  ((cmd, o) => (0, child_process_1.execSync)(cmd, o));
@@ -281,6 +308,32 @@ function createWorktree(scope, mode, projectDir, deps) {
281
308
  branchExists = false;
282
309
  }
283
310
  if (branchExists) {
311
+ // Guard (boop-6iw): if the loop branch is checked out in the launch dir — e.g. a
312
+ // planning phase that ran `git checkout -b work/<id>` in place before the worktree
313
+ // existed — then `git worktree add <path> <branch>` fails with "already checked
314
+ // out at <launch-dir>". It also leaves the launch dir parked on the feature branch,
315
+ // so a concurrent session's next commit lands on the wrong branch. Move the launch
316
+ // dir back to its default branch first: it both unblocks the worktree add and undoes
317
+ // the mis-parking. (Flows should not create the branch during planning — see the
318
+ // light-dev-workflow skill — but this makes any that slip through non-fatal.)
319
+ let launchBranch = '';
320
+ try {
321
+ launchBranch = execSyncFn('git branch --show-current', { encoding: 'utf8', cwd: projectDir }).trim();
322
+ }
323
+ catch {
324
+ // Detached HEAD or no git — nothing of ours is parked; proceed.
325
+ }
326
+ if (launchBranch === branch) {
327
+ const defaultBranch = resolveDefaultBranch(execSyncFn, projectDir);
328
+ try {
329
+ execSyncFn(`git checkout ${defaultBranch}`, { encoding: 'utf8', cwd: projectDir });
330
+ }
331
+ catch {
332
+ throw new Error(`Loop branch "${branch}" is checked out in the launch directory (${projectDir}), which blocks ` +
333
+ `creating its worktree. Moving the launch dir to "${defaultBranch}" failed — likely uncommitted ` +
334
+ `changes there. Commit or stash them (or run \`git checkout ${defaultBranch}\`) and retry.`);
335
+ }
336
+ }
284
337
  execSyncFn(`git worktree add ${worktreePath} ${branch}`, { encoding: 'utf8', cwd: projectDir });
285
338
  }
286
339
  else {
@@ -355,19 +408,18 @@ ${workContent}
355
408
  ${flowContent}
356
409
 
357
410
  ## Instructions
358
- 1. Read the work specification carefully
359
- 2. Create a feature issue: \`bd create "<title>" -t feature -d "<description>"\`
360
- - Derive the title from the work specification
361
- - Include breadcrumbs in the description (links to the work file, relevant specs, skills, and code)
362
- 3. Follow the workflow process to create child issues under this feature
363
- (use \`--parent <feature-id>\` so they are children of the feature)
364
- 4. Review all created issues: ensure each has breadcrumbs, then set
365
- appropriate priorities and blocking deps between tasks
411
+ 1. Read the work specification carefully.
412
+ 2. Follow the Workflow Process above to set up the work. The Workflow Process
413
+ governs the stages, the container issue (its type and title), and the child
414
+ issues created under it — use \`--parent <container-id>\` so they are its children.
415
+ 3. The setup MUST produce a single container issue with its child issues, each
416
+ carrying breadcrumbs (links to the work file, relevant specs, skills, and code),
417
+ with appropriate priorities and blocking deps.
366
418
 
367
- When finished, output exactly one line:
368
- FEATURE_ID=<the-feature-issue-id>`;
419
+ When finished, output exactly one line with the id of the container issue you created:
420
+ FEATURE_ID=<the-container-issue-id>`;
369
421
  if (prefix) {
370
- prompt += `\n\nNaming hint: Use \`${prefix}\` as a naming hint for the feature title.`;
422
+ prompt += `\n\nNaming hint: Use \`${prefix}\` as a naming hint for the container title.`;
371
423
  }
372
424
  return prompt;
373
425
  }
@@ -540,11 +592,11 @@ function findClaudeBinary() {
540
592
  * the `ANTHROPIC_MODEL` environment variable, then the built-in default. The env
541
593
  * fallback is what lets a nested `boop loop` (spawned by a Claude session that
542
594
  * the parent loop launched with ANTHROPIC_MODEL set) inherit the same model
543
- * instead of silently dropping to the default. Keep the default (claude-opus-4-8)
595
+ * instead of silently dropping to the default. Keep the default (claude-opus-5)
544
596
  * in sync with the `--model` help text in cli.ts.
545
597
  */
546
598
  function resolveModel(optionModel, env = process.env) {
547
- return optionModel || env.ANTHROPIC_MODEL || 'claude-opus-4-8';
599
+ return optionModel || env.ANTHROPIC_MODEL || 'claude-opus-5';
548
600
  }
549
601
  /**
550
602
  * Whether the loop should resume an issue that a session left open. A `blocked`
@@ -604,15 +656,16 @@ function isClaimableReadyIssue(issue, hasChildren) {
604
656
  return true;
605
657
  }
606
658
  /**
607
- * Decide whether the loop should close a feature root itself, as a safety net,
659
+ * Decide whether the loop should close a container root itself, as a safety net,
608
660
  * when its workflow chain has completed. The intended owner of this close is the
609
- * closeout child task (see boop-feature-dev-workflow SKILL step 6), but if that
610
- * task never closed the root — or no closeout task exists — the feature would be
611
- * orphaned open forever once filtered from claimable work. Only close when the
612
- * feature is still open and every child is closed.
661
+ * closeout child task (see the flow SKILL's closeout step), but if that task never
662
+ * closed the root — or no closeout task exists — the container would be orphaned
663
+ * open forever once filtered from claimable work. Applies to both container types
664
+ * the loop supports: a `feature` (feature-dev flow) or an `epic` (light-dev flow).
665
+ * Only close when the container is still open and every child is closed.
613
666
  */
614
667
  function shouldCloseFeatureRoot(feature, children) {
615
- if (!feature || feature.issue_type !== 'feature') {
668
+ if (!feature || (feature.issue_type !== 'feature' && feature.issue_type !== 'epic')) {
616
669
  return false;
617
670
  }
618
671
  if (feature.status === 'closed') {
@@ -793,6 +846,33 @@ Otherwise, complete the workflow:
793
846
  - Push with: git pull --rebase && git push
794
847
 
795
848
  Do not pause for review of completed work. Complete everything now.`;
849
+ /**
850
+ * Fraction of the context window at which a reused session is rotated for a fresh
851
+ * one. Deliberately well below Claude Code's auto-compaction point — compaction
852
+ * silently discards the context the reuse exists to keep, so we rotate first.
853
+ * The one knob a trial run should tune (specs/loop-session-reuse.spec.md).
854
+ */
855
+ const ROTATE_FRACTION = 0.55;
856
+ /** Fallback context window (tokens) before the stream has reported a real one. */
857
+ const DEFAULT_CONTEXT_WINDOW = 1000000;
858
+ /**
859
+ * Live context-window occupancy for one turn, from a stream-json `usage` object:
860
+ * everything the model had to read this turn (fresh input + cached prompt).
861
+ */
862
+ function contextOccupancy(usage) {
863
+ if (!usage)
864
+ return 0;
865
+ return (usage.input_tokens ?? 0) + (usage.cache_read_input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
866
+ }
867
+ /**
868
+ * Decide whether a reused session should rotate before taking the next issue.
869
+ * Rotates once peak observed occupancy reaches ROTATE_FRACTION of the window.
870
+ * Pure so it is unit-testable without a running loop.
871
+ */
872
+ function shouldRotateSession(peakContext, contextWindow, fraction = ROTATE_FRACTION) {
873
+ const window = contextWindow > 0 ? contextWindow : DEFAULT_CONTEXT_WINDOW;
874
+ return peakContext >= fraction * window;
875
+ }
796
876
  function runClaude(prompt, opts, logFile, readableLogFile) {
797
877
  return new Promise((resolve) => {
798
878
  const args = [
@@ -837,6 +917,11 @@ function runClaude(prompt, opts, logFile, readableLogFile) {
837
917
  : process.env;
838
918
  const child = (0, child_process_1.spawn)(claudeBin, args, { cwd: opts.cwd, stdio: ['pipe', 'pipe', 'pipe'], env: childEnv });
839
919
  let limitHit = false;
920
+ // Track context-window pressure from outside the agent (spec: the loop reads
921
+ // the stream it already parses; the agent is never told). Peak occupancy over
922
+ // the run drives session rotation; contextWindow comes from the result.
923
+ let peakContext = 0;
924
+ let contextWindow;
840
925
  child.stdin.write(prompt);
841
926
  child.stdin.end();
842
927
  child.stdout.on('data', (chunk) => {
@@ -846,6 +931,10 @@ function runClaude(prompt, opts, logFile, readableLogFile) {
846
931
  try {
847
932
  const msg = JSON.parse(line);
848
933
  if (msg.type === 'assistant') {
934
+ // Live context occupancy for this turn (read from outside the agent).
935
+ const occ = contextOccupancy(msg.message?.usage);
936
+ if (occ > peakContext)
937
+ peakContext = occ;
849
938
  const texts = (msg.message?.content || [])
850
939
  .filter((c) => c.type === 'text')
851
940
  .map((c) => c.text);
@@ -875,6 +964,16 @@ function runClaude(prompt, opts, logFile, readableLogFile) {
875
964
  if ((msg.is_error || msg.subtype === 'error') && isUsageLimitText(resultText)) {
876
965
  limitHit = true;
877
966
  }
967
+ // The result reports the model's context window under modelUsage.<model>.
968
+ const mu = msg.modelUsage;
969
+ if (mu) {
970
+ for (const m of Object.values(mu)) {
971
+ if (typeof m?.contextWindow === 'number' && m.contextWindow > 0) {
972
+ contextWindow = m.contextWindow;
973
+ break;
974
+ }
975
+ }
976
+ }
878
977
  const cost = msg.total_cost_usd || '?';
879
978
  const dur = Math.round((msg.duration_ms || 0) / 1000);
880
979
  const turns = msg.num_turns || '?';
@@ -901,12 +1000,12 @@ function runClaude(prompt, opts, logFile, readableLogFile) {
901
1000
  logStream.end();
902
1001
  if (readableStream)
903
1002
  readableStream.end();
904
- resolve({ exitCode: code ?? 1, limitHit });
1003
+ resolve({ exitCode: code ?? 1, limitHit, peakContext, contextWindow });
905
1004
  });
906
1005
  });
907
1006
  }
908
1007
  async function processIssue(issue, opts) {
909
- const sessionId = (0, crypto_1.randomUUID)();
1008
+ const sessionId = opts.session?.id ?? (0, crypto_1.randomUUID)();
910
1009
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-').substring(0, 19);
911
1010
  const logFile = path.join(opts.logDir, `${issue.id}_${timestamp}.log`);
912
1011
  const readableLogFile = path.join(opts.logDir, `${issue.id}_${timestamp}.readable.log`);
@@ -944,9 +1043,13 @@ async function processIssue(issue, opts) {
944
1043
  skipPerms: opts.skipPerms,
945
1044
  bare: opts.bare,
946
1045
  sessionId,
1046
+ // Continue a carried session for this new issue (its normal prompt via --resume).
1047
+ resume: opts.session?.resume,
947
1048
  cwd: opts.cwd,
948
1049
  }, logFile, readableLogFile);
949
1050
  let anyLimitHit = initialRun.limitHit;
1051
+ let peakContext = initialRun.peakContext ?? 0;
1052
+ let contextWindow = initialRun.contextWindow;
950
1053
  if (initialRun.limitHit) {
951
1054
  console.error('\x1b[31m[bd-loop]\x1b[0m Claude usage limit reached; stopping retries for this issue');
952
1055
  }
@@ -977,6 +1080,10 @@ async function processIssue(issue, opts) {
977
1080
  resume: true,
978
1081
  cwd: opts.cwd,
979
1082
  }, logFile, readableLogFile);
1083
+ if (resumeRun.peakContext && resumeRun.peakContext > peakContext)
1084
+ peakContext = resumeRun.peakContext;
1085
+ if (resumeRun.contextWindow)
1086
+ contextWindow = resumeRun.contextWindow;
980
1087
  if (resumeRun.limitHit) {
981
1088
  anyLimitHit = true;
982
1089
  console.error('\x1b[31m[bd-loop]\x1b[0m Claude usage limit reached during resume; stopping retries for this issue');
@@ -999,7 +1106,7 @@ async function processIssue(issue, opts) {
999
1106
  // Best effort
1000
1107
  }
1001
1108
  }
1002
- return { issueId: issue.id, closed, resumes: resumeCount, logFile, limitHit: anyLimitHit };
1109
+ return { issueId: issue.id, closed, resumes: resumeCount, logFile, limitHit: anyLimitHit, sessionId, peakContext, contextWindow };
1003
1110
  }
1004
1111
  function sleep(ms) {
1005
1112
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -1062,6 +1169,19 @@ async function runLoop(options) {
1062
1169
  const skipPerms = options.skipPerms !== false; // default true
1063
1170
  const bare = options.bare || false;
1064
1171
  const poll = options.poll || false;
1172
+ const reuseSession = options.reuseSession || false;
1173
+ // --reuse-session is a --prefix-mode knob, mutually exclusive with the two things
1174
+ // that imply one-session-per-issue. Errors are plain, not silent winners.
1175
+ if (reuseSession && (work || flow)) {
1176
+ console.error('\x1b[31mERR\x1b[0m --reuse-session is only for --prefix runs, not --work/--flow (spec-driven children are already session-sized)');
1177
+ process.exitCode = 1;
1178
+ return;
1179
+ }
1180
+ if (reuseSession && perIssueBranch) {
1181
+ console.error('\x1b[31mERR\x1b[0m --reuse-session and --per-issue-branch are mutually exclusive (a branch per issue implies a session per issue)');
1182
+ process.exitCode = 1;
1183
+ return;
1184
+ }
1065
1185
  const projectDir = process.cwd();
1066
1186
  const logDir = path.join(projectDir, 'labs', 'claude-runs');
1067
1187
  fs.mkdirSync(logDir, { recursive: true });
@@ -1171,6 +1291,14 @@ async function runLoop(options) {
1171
1291
  let totalProcessed = 0;
1172
1292
  let totalClosed = 0;
1173
1293
  const results = [];
1294
+ // --reuse-session state: a healthy session survives to the next issue; a fresh
1295
+ // one is minted only on rotation (context full) or death. Off → activeSession
1296
+ // stays null and every issue mints its own, exactly as before.
1297
+ let activeSession = null;
1298
+ let sessionCount = 0;
1299
+ // Issue id at each session START (the first entry is the initial session, not a
1300
+ // rotation). Actual rotations are everything after the first — see the summary.
1301
+ const sessionStarts = [];
1174
1302
  while (true) {
1175
1303
  const nextId = prefix ? findNextIssue(prefix, includeFeatures) : null;
1176
1304
  if (!nextId) {
@@ -1238,6 +1366,24 @@ async function runLoop(options) {
1238
1366
  console.error(`\x1b[31m[bd-loop]\x1b[0m Failed to get issue details for ${nextId}`);
1239
1367
  continue;
1240
1368
  }
1369
+ // Decide the session this issue runs under. Reuse a healthy carried session;
1370
+ // otherwise (first issue, or the last one filled up) mint a fresh one. The
1371
+ // decision lands here, between issues, so rotation can never split an issue.
1372
+ let session;
1373
+ if (reuseSession) {
1374
+ if (activeSession && !shouldRotateSession(activeSession.peakContext, activeSession.contextWindow)) {
1375
+ session = { id: activeSession.id, resume: true };
1376
+ }
1377
+ else {
1378
+ if (activeSession) {
1379
+ console.log(`\x1b[36m[bd-loop]\x1b[0m Session context full (${Math.round(activeSession.peakContext / 1000)}k/${Math.round(activeSession.contextWindow / 1000)}k) — rotating to a fresh session at ${issue.id}`);
1380
+ }
1381
+ session = { id: (0, crypto_1.randomUUID)(), resume: false };
1382
+ activeSession = { id: session.id, peakContext: 0, contextWindow: DEFAULT_CONTEXT_WINDOW };
1383
+ sessionCount++;
1384
+ sessionStarts.push(issue.id);
1385
+ }
1386
+ }
1241
1387
  const result = await processIssue(issue, {
1242
1388
  budget,
1243
1389
  model,
@@ -1248,10 +1394,22 @@ async function runLoop(options) {
1248
1394
  noBranch,
1249
1395
  logDir,
1250
1396
  cwd: loopCwd,
1397
+ session,
1251
1398
  });
1252
1399
  results.push(result);
1253
1400
  if (result.closed)
1254
1401
  totalClosed++;
1402
+ // Fold this issue's observed context back into the carried session so the next
1403
+ // iteration's rotation decision is current. A limit/death drops the session so
1404
+ // the next issue starts clean (recovery lands between issues, same as rotation).
1405
+ if (reuseSession && activeSession) {
1406
+ if (result.peakContext)
1407
+ activeSession.peakContext = Math.max(activeSession.peakContext, result.peakContext);
1408
+ if (result.contextWindow)
1409
+ activeSession.contextWindow = result.contextWindow;
1410
+ if (result.limitHit)
1411
+ activeSession = null;
1412
+ }
1255
1413
  if (result.limitHit) {
1256
1414
  console.error('\x1b[31m[bd-loop]\x1b[0m Credit/usage limit hit — stopping loop');
1257
1415
  break;
@@ -1270,6 +1428,14 @@ async function runLoop(options) {
1270
1428
  if (totalProcessed > totalClosed) {
1271
1429
  console.log(`\x1b[33m[bd-loop]\x1b[0m Not closed: ${totalProcessed - totalClosed}`);
1272
1430
  }
1431
+ if (reuseSession) {
1432
+ console.log(`\x1b[32m[bd-loop]\x1b[0m Sessions: ${sessionCount} (--reuse-session)`);
1433
+ // The first session start is not a rotation; only entries after it are.
1434
+ const rotatedAt = sessionStarts.slice(1);
1435
+ if (rotatedAt.length > 0) {
1436
+ console.log(`\x1b[32m[bd-loop]\x1b[0m Rotated at: ${rotatedAt.join(', ')}`);
1437
+ }
1438
+ }
1273
1439
  }
1274
1440
  console.log(`\x1b[32m[bd-loop]\x1b[0m Logs: ${logDir}/`);
1275
1441
  if (worktreeCtx) {
@@ -1283,6 +1449,7 @@ async function runLoop(options) {
1283
1449
  totalClosed,
1284
1450
  results,
1285
1451
  worktree: worktreeCtx ? { path: worktreeCtx.path, branch: worktreeCtx.branch, mode: worktreeCtx.mode } : null,
1452
+ sessions: reuseSession ? { count: sessionCount, rotatedAt: sessionStarts.slice(1) } : null,
1286
1453
  }, null, 2));
1287
1454
  }
1288
1455
  }
package/docs/init.md ADDED
@@ -0,0 +1,179 @@
1
+ # boop init
2
+
3
+ `boop init` is the command you run when you want a real starting point quickly. It creates a new project directory, applies a boop profile, writes a `.boop.json` manifest, and, unless you opt out, installs npm dependencies. The goal is simple: get you to a local project you can run, extend, and keep clean as both humans and agents start contributing.
4
+
5
+ ## Synopsis
6
+
7
+ ```bash
8
+ boop init <name> --profile <profile>
9
+ ```
10
+
11
+ If you omit `--profile`, Boop uses `web-app`.
12
+
13
+ ## Before You Run It
14
+
15
+ The current `web-app` profile requires Node 24 or newer. If your Node version is too old, `boop init` exits before creating the directory and prints an upgrade hint.
16
+
17
+ ## Available Profiles
18
+
19
+ List the profiles Boop knows about with:
20
+
21
+ ```bash
22
+ boop list profiles
23
+ ```
24
+
25
+ Current output:
26
+
27
+ ```text
28
+ Available profiles:
29
+
30
+ web-app
31
+ Full-stack web application (Express + Handlebars + Tailwind + Postgres)
32
+ capabilities: web, api, database
33
+ runtime: node | deploy: heroku | port: 3000
34
+ ```
35
+
36
+ Right now the built-in profile is `web-app`.
37
+
38
+ ## What It Creates
39
+
40
+ Running `boop init my-saas --profile web-app --no-install` creates a project like this:
41
+
42
+ ```text
43
+ my-saas/
44
+ .boop.json
45
+ .gitignore
46
+ Procfile
47
+ README.md
48
+ package.json
49
+ reference/
50
+ architecture.ref.md
51
+ message-queue-patterns.ref.md
52
+ skills/
53
+ boop-api-rest-api-design/
54
+ SKILL.md
55
+ boop-coding-beads-flow/
56
+ SKILL.md
57
+ boop-coding-commit-changes/
58
+ SKILL.md
59
+ boop-coding-development/
60
+ SKILL.md
61
+ boop-coding-review-changes/
62
+ SKILL.md
63
+ boop-database-data-model/
64
+ SKILL.md
65
+ boop-database-prisma/
66
+ SKILL.md
67
+ boop-product-design-engineering-design/
68
+ SKILL.md
69
+ boop-product-design-feature-documentation/
70
+ SKILL.md
71
+ boop-product-design-feature-requirements/
72
+ SKILL.md
73
+ boop-product-design-reference-integration/
74
+ SKILL.md
75
+ boop-project-mgmt-beads-planning/
76
+ SKILL.md
77
+ boop-project-mgmt-engineering-planning/
78
+ SKILL.md
79
+ boop-services-controllers/
80
+ SKILL.md
81
+ boop-services-services/
82
+ SKILL.md
83
+ boop-skill-authoring/
84
+ SKILL.md
85
+ boop-testing-tdd-light/
86
+ SKILL.md
87
+ boop-testing-testing/
88
+ SKILL.md
89
+ boop-testing-testing-express-web/
90
+ SKILL.md
91
+ boop-web-web-ui-design/
92
+ SKILL.md
93
+ src/
94
+ lib/
95
+ .gitkeep
96
+ server.ts
97
+ tsconfig.json
98
+ ```
99
+
100
+ That scaffold breaks down into a few parts:
101
+
102
+ - Kernel files: `.gitignore`, `Procfile`, `README.md`, `package.json`, `tsconfig.json`, `src/server.ts`, and `src/lib/.gitkeep`
103
+ - Profile reference docs: copied into `reference/`
104
+ - Profile and capability skills: copied into `skills/`
105
+ - Project manifest: `.boop.json` records the project name, profile, boop version, installed capabilities, and creation timestamp
106
+ - Capability install effects: default capability dependencies and scripts are merged into `package.json`, and the installed capability names are recorded in `.boop.json`
107
+
108
+ For the `web-app` profile, the default capabilities are:
109
+
110
+ - `web`
111
+ - `api`
112
+ - `database`
113
+
114
+ At the moment, those capabilities show up most clearly in `package.json`, `skills/`, and `.boop.json`. The initial source tree is still small: the kernel server entry point plus the copied reference and skill material.
115
+
116
+ Beyond the defaults, optional capabilities are available to add later with `boop add <capability>`. For example, `users-and-accounts` adds user management and authentication, and `noticeboard` provides a fuller service-layer reference demo. See the profile documentation for the complete list.
117
+
118
+ ## Options
119
+
120
+ - `--profile <profile>`: choose which profile to initialize. Defaults to `web-app`.
121
+ - `--no-install`: skip `npm install`. This is useful if you want to inspect or adjust the scaffold before installing dependencies.
122
+
123
+ If you do not pass `--no-install`, Boop runs `npm install` after scaffolding and then prints the same next-step summary.
124
+
125
+ ## Example: Initialize Without Installing
126
+
127
+ ```bash
128
+ boop init my-saas --profile web-app --no-install
129
+ ```
130
+
131
+ Expected output:
132
+
133
+ ```text
134
+ Created project "my-saas" with profile "web-app"
135
+
136
+ Kernel files:
137
+ .gitignore
138
+ Procfile
139
+ README.md
140
+ package.json
141
+ src/lib/.gitkeep
142
+ src/server.ts
143
+ tsconfig.json
144
+
145
+ Capabilities: web, api, database
146
+
147
+ Scripts added:
148
+ tailwind:build: npx @tailwindcss/cli -i src/public/css/input.css -o src/public/css/style.css --minify
149
+ tailwind:watch: npx @tailwindcss/cli -i src/public/css/input.css -o src/public/css/style.css --watch
150
+ db:migrate: prisma migrate dev --schema=prisma/schema/
151
+ db:deploy: prisma migrate deploy --schema=prisma/schema/
152
+ db:seed: prisma db seed --schema=prisma/schema/
153
+ db:generate: prisma generate --schema=prisma/schema/
154
+ db:studio: prisma studio --schema=prisma/schema/
155
+
156
+ Next steps:
157
+ cd my-saas
158
+ npm install
159
+ npm run dev
160
+ ```
161
+
162
+ ## What To Do After Init
163
+
164
+ If you used `--no-install`, run:
165
+
166
+ ```bash
167
+ cd my-saas
168
+ npm install
169
+ npm run dev
170
+ ```
171
+
172
+ If you let Boop install dependencies for you, the shorter version is:
173
+
174
+ ```bash
175
+ cd my-saas
176
+ npm run dev
177
+ ```
178
+
179
+ That gets you to a local project quickly, with the reference material and agentic skills already in place so the next changes do not have to start from scratch.
package/docs/loop.md ADDED
@@ -0,0 +1,205 @@
1
+ # boop loop
2
+
3
+ `boop loop` continuously processes ready beads issues through Claude Code sessions. It finds issues matching a prefix, launches Claude to work each one, and resumes if the issue isn't closed — all with dry-run safety by default. By default each loop runs inside its own git worktree (see [Worktree Isolation](#worktree-isolation-default)) so several loops can run concurrently without colliding.
4
+
5
+ It also supports a **planning mode** via `--work` and `--flow` flags, which bootstraps a feature issue tree from a spec file before looping through the created tasks.
6
+
7
+ ## Synopsis
8
+
9
+ ```bash
10
+ # Process existing issues by prefix
11
+ boop loop --prefix <prefix> [--run] [--max N] [--budget N.NN]
12
+
13
+ # Plan-then-execute: create a feature tree from a spec, then process tasks
14
+ boop loop --work <spec-file> --flow <workflow-file> [--run] [--prefix <hint>]
15
+ ```
16
+
17
+ Without `--run`, the command shows what it would do without executing anything.
18
+
19
+ ## How It Works
20
+
21
+ ### Standard mode (`--prefix`)
22
+
23
+ 1. Queries `bd ready` for leaf tasks in `--prefix` scope. Scope is two-layer: an issue matches if its ID starts with the prefix string, **or** if any ancestor in its parent chain has an ID that does — so issues re-parented under a matching epic or feature are processed even when their own IDs are unrelated. To run a group of related issues that don't share a prefix, create an epic, `bd update <id> --parent <epic-id>` each one, and pass the epic's ID as `--prefix`. Always pass a complete issue ID: matching is raw `startsWith`, so a truncated prefix like `boop-4` also matches `boop-45j`. Epics themselves and feature issues that own children are never claimed as work.
24
+ 2. Picks the first ready issue and launches a Claude Code session
25
+ 3. When Claude exits, checks if the issue was closed
26
+ 4. If not closed, resumes the session with a continuation prompt (up to `--resume-limit` times)
27
+ 5. Moves to the next ready issue and repeats
28
+ 6. Exits when no more ready issues exist (or keeps polling with `--poll`)
29
+
30
+ Each Claude session gets a full prompt with issue details and instructions to work autonomously through the complete workflow: claim, implement, test, commit, close, and push.
31
+
32
+ ### Planning mode (`--work` + `--flow`)
33
+
34
+ When `--work` and `--flow` are provided, boop loop runs a **plan-then-execute pipeline**:
35
+
36
+ 1. **Planning phase** — Reads the spec file (`--work`) and workflow file (`--flow`), builds a planning prompt, and launches a Claude session that creates a feature issue with child tasks in beads.
37
+ 2. **Feature ID extraction** — Parses the planning session output for `FEATURE_ID=<id>`. If not found, falls back to querying `bd list` for recently created features matching the spec's title.
38
+ 3. **Loop phase** — Uses the extracted feature ID as the prefix and processes all child tasks through the standard loop, with `--features` automatically enabled.
39
+
40
+ If `--prefix` is also provided, it is passed to the planning session as a naming hint for the feature title (e.g., `--prefix boop-abc` suggests the feature should use that prefix).
41
+
42
+ ## Flags
43
+
44
+ | Flag | Default | Description |
45
+ |------|---------|-------------|
46
+ | `--prefix <prefix>` | — | Issue ID prefix to filter. Required unless `--work`/`--flow` are used |
47
+ | `--work <path>` | — | Spec file for planning phase. Must be used with `--flow` |
48
+ | `--flow <path>` | — | Workflow file for planning phase. Must be used with `--work` |
49
+ | `--run` | off | Actually execute. Without this flag, dry-run mode shows what would happen |
50
+ | `--max <n>` | unlimited | Stop after processing N issues |
51
+ | `--budget <amount>` | `5.00` | Budget cap in USD, applied to both planning and per-issue phases |
52
+ | `--resume-limit <n>` | `3` | Maximum resume attempts if issue isn't closed |
53
+ | `--poll` | off | Keep polling when no ready issues found |
54
+ | `--poll-interval <s>` | `60` | Seconds between polls |
55
+ | `--model <model>` | `claude-opus-5` | Claude model to use (falls back to `$ANTHROPIC_MODEL`, then this default) |
56
+ | `--per-issue-branch` | off | Create a `fix/<issue-id>` branch per issue (nested inside the worktree) |
57
+ | `--reuse-session` | off | (`--prefix` only) Reuse one Claude session across a coherent run of issues, rotating to a fresh session when its context fills. Mutually exclusive with `--per-issue-branch` |
58
+ | `--no-branch` | off | Opt out of worktree isolation; run in-place on the current branch |
59
+ | `--features` | off | Include feature-type issues (auto-enabled in planning mode) |
60
+ | `--no-skip-perms` | off | Don't skip Claude permission prompts |
61
+ | `--json` | off | Output summary as JSON |
62
+
63
+ ## Worktree Isolation (default)
64
+
65
+ 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:
66
+
67
+ 1. Creates a worktree at `../boop-<scope>` on a new branch `work/<scope>`, where `<scope>` is the feature ID (planning mode) or the `--prefix` value.
68
+ 2. Runs `npm install --prefer-offline` in the worktree to pre-warm dependencies (so the agent doesn't spend budget on a deterministic step).
69
+ 3. Writes `.boop-worktree.json` into the worktree root recording the beads scope, branch, and provenance (read by future integration tooling).
70
+ 4. Runs the whole loop — every Claude session, commit, and `git push` — with its working directory set to the worktree.
71
+
72
+ **Your main working tree is never checked out or mutated by the loop.** When the loop finishes it **leaves the worktree and branch in place** for integration. Use `boop loop --merge` (see [Merging completed work](#merging-completed-work-boop-loop---merge)) for automated, test-gated integration, or merge by hand:
73
+
74
+ ```bash
75
+ git -C <main-repo> merge work/<scope> # or open a PR from the branch
76
+ git worktree remove ../boop-<scope> # clean up when done
77
+ ```
78
+
79
+ If you re-run a loop for the same scope, an existing worktree on the matching `work/<scope>` branch is reused; a path occupied by something else is reported as an error rather than silently renamed.
80
+
81
+ ### Dolt server-mode requirement
82
+
83
+ Concurrent worktrees share **one** beads issue database over a TCP Dolt server. The loop therefore requires beads to be in **Dolt server mode**:
84
+
85
+ - It reads `.beads/metadata.json` and refuses to start unless `dolt_mode` is `"server"`, pointing you to `skills/boop-dolt-recovery` otherwise.
86
+ - It discovers the running server's port from `.beads/dolt-server.port` (it never starts a server itself) and copies the `dolt-server.port`/`dolt-server.pid` discovery files into the new worktree's `.beads/` so `bd` inside the worktree connects to the shared server instead of auto-starting a rogue one.
87
+
88
+ Start the shared server with `bd dolt start` before running the loop.
89
+
90
+ ### Branching alternatives
91
+
92
+ - **`--no-branch`** — Opts out of worktree isolation entirely and runs **in-place on the current branch** (the original single-tenant behavior). Use this when you've already set up the branch/tree you want and are running only one loop.
93
+ - **`--per-issue-branch`** — Creates a separate `fix/<issue-id>` branch for each issue. Under worktree isolation these branches are created **inside** the loop's worktree (nested under `work/<scope>`), keeping main untouched. Combine with `--no-branch` to get the old in-place per-issue behavior.
94
+
95
+ ## Merging completed work (`boop loop --merge`)
96
+
97
+ Once feature loops finish, `boop loop --merge` integrates their branches back into `main` sequentially. No individual loop merges its own branch — this dedicated integrator does it, so failures in one feature never block others.
98
+
99
+ ```bash
100
+ boop loop --merge # dry-run: list branches ready to merge
101
+ boop loop --merge --run # integrate every ready branch
102
+ boop loop --merge work/<scope> --run # merge one named branch, skipping the readiness check
103
+ boop loop --merge --run --watch # poll and merge branches as they become ready
104
+ ```
105
+
106
+ For each ready branch (one at a time, with `main` as the integration branch):
107
+
108
+ 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.
109
+ 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.
110
+ 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.
111
+ 4. **Fast-forward merge** into `main`, then remove the worktree and delete the merged branch.
112
+
113
+ **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.
114
+
115
+ ## Logs
116
+
117
+ 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.
118
+
119
+ ## Safety
120
+
121
+ - **Dry-run by default** — nothing executes without `--run`
122
+ - **Budget caps** — each issue is capped at `--budget` USD
123
+ - **Resume limits** — prevents infinite retry loops (default: 3 attempts)
124
+ - **Permission skipping** — enabled by default for unattended operation; use `--no-skip-perms` to require approval for each tool call
125
+
126
+ ## Planning Mode
127
+
128
+ ### Dry-run
129
+
130
+ Without `--run`, planning mode shows the prompt that would be sent to Claude and exits:
131
+
132
+ ```bash
133
+ boop loop --work specs/foo.spec.md --flow skills/bar.skill.md
134
+ ```
135
+
136
+ Output:
137
+ ```
138
+ [bd-loop] PLANNING MODE (dry-run)
139
+ [bd-loop] Work file: specs/foo.spec.md
140
+ [bd-loop] Flow file: skills/bar.skill.md
141
+ [bd-loop] Planning prompt that would be sent:
142
+ ---
143
+ <planning prompt content>
144
+ ---
145
+ [bd-loop] After planning, loop would process tasks under the created feature.
146
+ ```
147
+
148
+ ### Full run
149
+
150
+ ```bash
151
+ boop loop --work specs/foo.spec.md --flow skills/bar.skill.md --run
152
+ ```
153
+
154
+ This launches a Claude session to create the feature tree, extracts the feature ID, then loops through all child tasks.
155
+
156
+ ### Using `--prefix` as a naming hint
157
+
158
+ ```bash
159
+ boop loop --work specs/foo.spec.md --flow skills/bar.skill.md --prefix boop-foo --run
160
+ ```
161
+
162
+ The prefix is included in the planning prompt as a suggestion for naming the feature. After planning, the prefix is overridden with the actual feature ID for the loop phase.
163
+
164
+ ### Error handling
165
+
166
+ - **Missing files** — If the `--work` or `--flow` file doesn't exist, the command exits with an error before launching any session.
167
+ - **Flags used separately** — `--work` without `--flow` (or vice versa) prints an error. They must be used together.
168
+ - **Feature ID not found** — If the planning session doesn't output `FEATURE_ID=<id>` and the fallback query finds no matching recent feature, the command exits with an error and points to the session log for debugging.
169
+ - **Planning session limit hit** — If the Claude session hits a usage limit, the command exits without proceeding to the loop phase.
170
+
171
+ Planning session logs are written to `labs/claude-runs/` with the prefix `_planning_`.
172
+
173
+ ### Budget and planning
174
+
175
+ The `--budget` value is a **per-session cap**, applied identically to the planning phase and to each task in the loop phase. A complex spec can consume a large portion of the budget during planning, leaving the same cap for each task — but if tasks are simpler than the plan, this is usually fine since each task gets its own independent budget allowance.
176
+
177
+ However, be aware:
178
+ - A spec that requires extensive decomposition (many tasks, deep dependency trees) may use most of the budget during planning.
179
+ - If the planning session hits the budget limit, it exits without creating the full task tree and the loop phase does not start.
180
+ - At runtime, boop loop warns if the planning phase consumes more than 50% of the budget.
181
+
182
+ To mitigate, increase `--budget` when using complex specs, or break large specs into smaller ones.
183
+
184
+ ### Interaction with other flags
185
+
186
+ - **`--budget`** — The same budget value is used for both the planning phase and each task in the loop phase.
187
+ - **`--features`** — Automatically enabled after planning, so the feature issue itself is included in the loop queue.
188
+ - **`--model`**, **`--no-skip-perms`** — Applied to both planning and task sessions.
189
+ - **`--poll`**, **`--max`**, **`--resume-limit`** — Apply only to the loop phase (not planning).
190
+
191
+ ## Workflow Integration
192
+
193
+ `boop loop` works with the beads issue tracker. Typical workflows:
194
+
195
+ ### Manual issue setup
196
+ 1. Plan work with `bd create` and set up dependencies
197
+ 2. Run `boop loop --prefix <prefix>` to preview the queue
198
+ 3. Run `boop loop --prefix <prefix> --run` to execute
199
+ 4. After completion, integrate with `boop loop --merge --run` (or review the `work/<prefix>` branch in the `../boop-<prefix>` worktree and merge it by hand)
200
+
201
+ ### Automated planning
202
+ 1. Write a spec in `specs/` and choose a workflow skill from `skills/`
203
+ 2. Preview: `boop loop --work specs/my-feature.spec.md --flow skills/dev-workflow.skill.md`
204
+ 3. Execute: `boop loop --work specs/my-feature.spec.md --flow skills/dev-workflow.skill.md --run`
205
+ 4. The planning phase creates the feature tree, then the loop processes each task
package/docs/skills.md ADDED
@@ -0,0 +1,88 @@
1
+ # boop skills index
2
+
3
+ > Generated by `tools/generate-skills-index.js` (`npm run skills:index`). Do not edit by hand.
4
+
5
+ Every capability ships **agent skills** — focused `SKILL.md` guides that `boop add` installs into your project's `skills/` directory. This index lists them by capability so you can see what guidance each one brings before installing it.
6
+
7
+ ## Development capabilities (skills only — no runtime code)
8
+
9
+ ### `coding` — Development tools and conventions
10
+
11
+ - **boop-coding-beads-flow** — When you need to work through the beads issue lifecycle from claiming work to pushing final code.
12
+ - **boop-coding-development** — When you need to build features or implement changes, follow these core development principles and workflows.
13
+ - **boop-coding-commit-changes** — When you need to commit code changes, use **Conventional Commits** to write clear, machine-readable commit messages that communicate the nature of each change.
14
+ - **boop-coding-review-changes** — When you need to review uncommitted code changes, use this skill to perform a judgment-level code review using git diffs.
15
+ - **boop-coding-debugging** — When you need to diagnose a bug, crash, or failing test, debug from the data and the observation — not from the first familiar mechanism.
16
+
17
+ ### `product-design` — Product design skills
18
+
19
+ - **boop-product-design-engineering-design** — When you need to design a feature or system and communicate the technical decisions — including when you frame the task as writing a design doc, drafting the technical or engineering section of a spec, deciding the architecture, or choosing how to build something. Reaching for an existing design to imitate its structure is not a substitute for the method. For UI features, includes a per-affordance interaction-shapes pass that derives page/surface count from job-anchored mechanism choices rather than asserting it up front.
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
+ - **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
+ - **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.
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
+
26
+ ### `project-mgmt` — Project management with beads
27
+
28
+ - **boop-project-mgmt-beads-planning** — When you need to write and organize beads issues so that any fresh agent can pick up work.
29
+ - **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.
30
+
31
+ ### `skills` — Skill authoring
32
+
33
+ - **boop-skill-authoring** — When you need to add or update an agent skill in this project, follow this format and checklist.
34
+ - **boop-skills-discovery** — When you pick up a non-trivial task, sweep the available skills for ones relevant to the work before you start, as a backstop for an issue that missed a breadcrumb.
35
+ - **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.
36
+
37
+ ### `testing` — Jest testing setup
38
+
39
+ - **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.
40
+ - **boop-testing-testing-express-web** — When you need to write or run tests for the Express server — services, controllers, middleware, and HTTP routes.
41
+ - **boop-testing-testing** — When you need to write, run, or reason about tests in this project.
42
+
43
+ ### `ux` — UX design judgement and the discovery/delivery workflows that operate it
44
+
45
+ - **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).
46
+ - **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).
47
+ - **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.
48
+ - **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.
49
+
50
+ ## Application capabilities
51
+
52
+ ### `api` — REST API with Express
53
+
54
+ - **boop-api-rest-api-design** — When you need to design or implement REST-style HTTP APIs — including when you frame the task as 'add an endpoint', 'add a route', 'build the API', or 'decide the request and response shape' — follow these principles.
55
+
56
+ ### `background-jobs` — Background job processing with RabbitMQ via CloudAMQP
57
+
58
+ - **boop-background-jobs-background-jobs-design** — When you need to add a background job consumer, publish messages, or work with the message queue infrastructure, follow these principles.
59
+
60
+ ### `cloud-storage` — Cloud storage service for files and assets (AWS S3)
61
+
62
+ - **boop-cloud-storage-cloud-storage** — When you need to work with cloud storage for files and assets in a boop project (using AWS S3).
63
+
64
+ ### `database` — Postgres via Prisma ORM
65
+
66
+ - **boop-database-prisma** — When you need to design, modify, or migrate the app database schema.
67
+ - **boop-database-data-model** — When you need to design data models for new features or extend existing entities.
68
+
69
+ ### `email` — Transactional email via Sendgrid with Handlebars templates
70
+
71
+ - **boop-email-email-design** — When you need to design, implement, or work with transactional email, follow these guidelines.
72
+
73
+ ### `services` — Service layer with controllers and business logic
74
+
75
+ - **boop-services-controllers** — When you need to design, implement, or review a controller, follow these principles.
76
+ - **boop-services-services** — When you need to design, implement, or review a service, follow these principles.
77
+
78
+ ### `users-and-accounts` — Users, identities, properties, and authentication via Prisma
79
+
80
+ - **boop-users-user-data-modeling** — When you need to add, store, or query a field related to users, identities, or accounts in a boop project, decide *where* the field belongs before writing the schema or service.
81
+
82
+ ### `web` — Web UI with Handlebars + Tailwind
83
+
84
+ - **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.
85
+
86
+ ---
87
+
88
+ Standalone workflow skills (e.g. `boop-feature-dev-workflow`, `boop-light-dev-workflow`, `boop-issue-sweep-workflow`, `boop-design-handoff-workflow`) live in the repo's top-level `skills/` directory rather than in a capability package, so they are not listed above.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.7",
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": [
@@ -11,6 +11,9 @@
11
11
  },
12
12
  "files": [
13
13
  "dist/",
14
+ "docs/init.md",
15
+ "docs/loop.md",
16
+ "docs/skills.md",
14
17
  "profiles/",
15
18
  "src/templates/"
16
19
  ],
@@ -19,6 +22,7 @@
19
22
  },
20
23
  "scripts": {
21
24
  "build": "tsc && node tools/generate-version-manifest.js",
25
+ "skills:index": "node tools/generate-skills-index.js",
22
26
  "build:caps": "./tools/build-caps.sh",
23
27
  "publish": "./tools/publish-caps.sh",
24
28
  "release": "./tools/publish-caps.sh --release",