Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/skills/bobby-test/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ argument-hint: "<ticket ID to test>"

## Health Check

**No live app?** If `health_checks` in .bobbyrc.yml is empty or missing, there is
no live app to test — this project should not be running a test stage at all. Do
NOT emit BLOCKED test cases one by one. Move the ticket to blocked and stop:
`bobby ticket move <id> block "no live app to test — use the library workflow"`.
The fix is `default_workflow: library` in .bobbyrc.yml (the library workflow ends
at review, which runs the test suite), or add `health_checks` if this project
does have a live app.

Verify the dev environment is running before testing:

- `curl -s -o /dev/null -w "%{http_code}" <your dev server URL>` (app — configure `health_checks` in .bobbyrc.yml)
Expand Down
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ max_retries: 3
<summary>Optional configuration (commented out in generated file)</summary>

```yaml
# Custom + override workflows (built-in: default, secure, quick, freewill, design, define)
# Custom + override workflows (built-in: default, secure, quick, library, library-secure, freewill, design, define)
workflows:
default: [plan, build, review, test]
secure: [plan, build, security, review, test]
Expand Down Expand Up @@ -1020,7 +1020,7 @@ Bobby scaffolds Claude Code slash commands in `.claude/commands/` so you can inv

## Custom Workflows

Bobby ships built-in workflows — `default`, `secure`, `quick`, `freewill`, plus the `design` and `define` pipelines. Define your own (or override a built-in) in `.bobbyrc.yml`:
Bobby ships built-in workflows — `default`, `secure`, `quick`, `library`, `library-secure`, `freewill`, plus the `design` and `define` pipelines. Define your own (or override a built-in) in `.bobbyrc.yml`:

```yaml
workflows:
Expand All @@ -1033,6 +1033,22 @@ Run a named workflow:
bobby run workflow TKT-001 --workflow secure
```

### The library workflow

CLI tools, libraries, and npm packages have no live app, and the test stage is a
live-app stage — bobby-test verifies through a running server, never by running
specs. The `library` workflow ends at review instead: `plan → build → review`
(review runs the test suite independently, the correct verification for a
library). `library-secure` adds a security stage: `plan → build → security → review`.

Bobby picks `library` automatically: when `bobby init` (or `bobby new`) detects
no dev server and no health checks, it writes `default_workflow: library` into
`.bobbyrc.yml` with a comment explaining why. Delete that line to return to the
standard default, or add `health_checks` when the project grows a live app.
Note that `default_workflow` redirects the *name* `default` — on a library
project, run the four-stage flow by defining it under another name
(e.g. `workflows: { live: [plan, build, review, test] }`).

## Contributing

Contributions are welcome! To get started:
Expand Down
8 changes: 8 additions & 0 deletions commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { createRequire } from 'module';
import { success, warn, error, bold, dim } from '../lib/colors.js';
import { getTarget, TARGETS } from '../lib/targets/index.js';
import { detectServices, aggregateAreas, aggregateHealthChecks } from '../lib/services.js';
import { deriveDefaultWorkflow } from '../lib/workflow.js';
import { runLocalProfileWizard, saveLocalProfile } from './local-init.js';
import { detectProjectContext, detectGitIdentity } from '../lib/detect.js';
import { mergeRulesContent, isBobbyGenerated } from '../lib/rules-merge.js';
Expand Down Expand Up @@ -733,6 +734,13 @@ export function registerInit(program) {
health_checks: aggregateHealthChecks(configBase),
};

// BOB-090: no live app (no health checks, no dev command) means the
// test stage has nothing to exercise — default such projects to the
// `library` workflow, visibly in .bobbyrc.yml. Derived HERE and not in
// scaffoldProject: refresh/re-scaffold never rewrites config (BOB-123).
const derivedWorkflow = deriveDefaultWorkflow(stack, config);
if (derivedWorkflow) config.default_workflow = derivedWorkflow;

const scaffoldResult = scaffoldProject(rootDir, config);
const gitInitialized = gitInitializedEarly || scaffoldResult?.gitInitialized;

Expand Down
4 changes: 2 additions & 2 deletions commands/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function registerRun(program) {
.description('Run an agent on ticket(s) — workflow, feature, next, plan, build, review, test, ship, and more')
.addHelpText('after', `
Modes:
Workflow: bobby run workflow <id> — auto-chains the default workflow (or: secure, quick)
Workflow: bobby run workflow <id> — auto-chains the default workflow (or: secure, quick, library)
Feature: bobby run feature [epic] — full epic workflow on one branch
Slow mode: bobby run next <id> — runs next agent for current stage
Batch: bobby run plan — runs agent on all tickets in matching stage
Expand All @@ -43,7 +43,7 @@ Modes:
Debug: bobby run debug <id> — root-cause investigation
Freewill: bobby run freewill <id> — one agent, whole ticket, few instructions (Opus 5 / Fable 5)
Freeform: bobby run docs|performance|watchdog — no ticket required
Workflow: bobby run <workflow-name> <id> — run a named workflow (default, secure, quick, or your own)`)
Workflow: bobby run <workflow-name> <id> — run a named workflow (default, secure, quick, library, or your own)`)
.option('--max-retries <n>', 'Max retry loops on rejection per ticket', '3')
.option('--max-iterations <n>', 'Max total agent invocations across all tickets')
.option('--workflow <name>', 'Named workflow to use (built-in or from .bobbyrc.yml workflows)', 'default')
Expand Down
3 changes: 3 additions & 0 deletions commands/sprint.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ export function registerSprint(program) {
const sprintPlanPath = path.join(sprintsDir, path.basename(sprint.path), 'sprint-plan.md');
const prompt = buildSprintPrompt(d, sprintTickets, pipeline, {
maxRetries,
// BOB-090: no health checks = no live app for the testing stage —
// the prompt blocks the ticket loudly instead of launching bobby-test.
hasHealthChecks: (config.health_checks ?? []).length > 0,
// Absolute, main-rooted: a sprint runs in an isolated worktree, which
// does not contain tickets created on a feature branch (TKT-052).
ticketsDir,
Expand Down
13 changes: 11 additions & 2 deletions commands/workflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ export function registerWorkflow(program) {
console.log('');
console.log(` ${dim('Built-in:')}`);
for (const [name, steps] of Object.entries(BUILT_IN_WORKFLOWS)) {
const overridden = userDefined[name] ? dim(' (overridden)') : '';
console.log(` ${bold(name)}${overridden} ${dim(steps.join(' → '))}`);
const ov = userDefined[name];
const overridden = ov ? dim(' (overridden)') : '';
// Show the EFFECTIVE stages: an overridden built-in runs the user's
// steps, and printing the built-in list next to "(overridden)"
// misstated what the workflow actually does (BOB-090).
const effective = Array.isArray(ov) ? ov : steps;
console.log(` ${bold(name)}${overridden} ${dim(effective.join(' → '))}`);
}
const custom = Object.entries(userDefined).filter(([n]) => !BUILT_IN_WORKFLOWS[n]);
if (custom.length > 0) {
Expand All @@ -49,6 +54,10 @@ export function registerWorkflow(program) {
console.log(` ${bold(name)} ${dim(stepList)}`);
}
}
if (config.default_workflow) {
console.log('');
console.log(` Default for this project: ${bold(config.default_workflow)} ${dim('(default_workflow in .bobbyrc.yml)')}`);
}
console.log('');
console.log(` ${dim('Add one: bobby workflow add fast build test')}`);
console.log('');
Expand Down
14 changes: 13 additions & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,17 @@ export function writeConfigCommented(rootDir, config) {
lines.push(`ticket_prefix: ${config.ticket_prefix || 'TKT'}`);
lines.push('');

// Default workflow (BOB-090) — near the top so the choice is visible.
if (config.default_workflow) {
lines.push('# Default workflow. This project has no dev server and no health checks —');
lines.push('# there is no live app for the test stage to exercise, so tickets run the');
lines.push('# `library` workflow: plan → build → review (review runs the test suite).');
lines.push('# Delete this line to return to the standard default, or add health_checks');
lines.push('# if this project does grow a live app.');
lines.push(`default_workflow: ${config.default_workflow}`);
lines.push('');
}

// Health checks
lines.push('# Health check URLs verified before agents start work.');
lines.push('# Add your dev server URL(s) so agents can confirm the app is running.');
Expand Down Expand Up @@ -328,7 +339,8 @@ export function writeConfigCommented(rootDir, config) {

if (!config.workflows && !config.pipelines) {
lines.push('# Workflows — the ordered stages a ticket runs through. Built-in: default,');
lines.push('# secure, quick. Add or override your own here, then: bobby run <name> <ticket>');
lines.push('# secure, quick, library, library-secure. Add or override your own here,');
lines.push('# then: bobby run <name> <ticket>');
lines.push('# workflows:');
lines.push('# default: [plan, build, review, test]');
lines.push('# secure: [plan, build, security, review, test]');
Expand Down
6 changes: 6 additions & 0 deletions lib/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import path from 'path';
import { execSync } from 'child_process';
import { scaffoldProject, loadStack } from '../commands/init.js';
import { aggregateAreas, aggregateHealthChecks } from './services.js';
import { deriveDefaultWorkflow } from './workflow.js';
import { renderTemplate } from './template.js';
import { createTicket, slugify, readTicket, writeTicket } from './tickets.js';
import { applyStarter } from './starters.js';
Expand Down Expand Up @@ -114,6 +115,11 @@ export function createProject(idea, { dir, stack: stackName = 'node', cwd = proc
health_checks: aggregateHealthChecks(configBase),
};

// BOB-090: same derivation as `bobby init` — a stack with no live app
// defaults to the `library` workflow, written visibly into .bobbyrc.yml.
const derivedWorkflow = deriveDefaultWorkflow(stack, config);
if (derivedWorkflow) config.default_workflow = derivedWorkflow;

// Scaffold Bobby (identical to `bobby init`): .bobby/, .claude/, config, git init.
scaffoldProject(root, config);

Expand Down
Loading