From 34cae69ac12b580dc066e68d6b5679ca915e886d Mon Sep 17 00:00:00 2001 From: CC Evans Date: Thu, 20 Aug 2026 00:03:04 -0400 Subject: [PATCH 01/50] Move the bobbycode board into the bobby studio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v1 .bobbyrc.yml here shadowed the studio at ~/Repos/bobby: findProjectRoot stopped at this file and never walked up, so this repo kept a private TKT board while bobbycode-pro already resolved to the studio. Two boards numbered from 001 independently, which put 14 ticket ids in collision — `bobby ticket view TKT-001` silently resolved to whichever matched first, and both epics' children carried an ambiguous `parent: TKT-001`. Board, design, docs, sessions, architecture and decisions now live in the studio project at .bobby/bobby/ under a single BOB prefix. Deleting .bobbyrc.yml is what makes this repo resolve up, exactly as bobbycode-pro does. Co-Authored-By: Claude Opus 5 (1M context) --- .bobby/.scaffold-version | 1 - .bobby/architecture-wakeup.md | 37 - .bobby/architecture.md | 334 ------- .bobby/decisions.yaml | 316 ------- .bobby/design/.gitignore | 12 - .bobby/design/design-spec-feature-view.md | 412 --------- .bobby/design/design-spec.md | 101 --- .bobby/design/mockups/app-palette.html | 727 --------------- .bobby/design/mockups/cassette-retro.html | 833 ------------------ .bobby/design/mockups/clean-minimal.html | 566 ------------ .bobby/design/mockups/devin-white-fin-1.html | 336 ------- .bobby/design/mockups/devin-white-fin-2.html | 343 -------- .bobby/design/mockups/devin-white-fin-3.html | 346 -------- .bobby/design/mockups/devin-white-flag-a.html | 339 ------- .bobby/design/mockups/devin-white-flag-b.html | 333 ------- .bobby/design/mockups/devin-white.html | 329 ------- .bobby/design/mockups/f1-timing.html | 719 --------------- .bobby/design/mockups/mobile-jazz.html | 675 -------------- .bobby/design/mockups/racing-check.html | 529 ----------- .bobby/design/mockups/racing-subtle.html | 489 ---------- .bobby/design/mockups/the-line.html | 715 --------------- .bobby/design/references-feature-view.md | 314 ------- .bobby/design/references.md | 39 - .bobby/design/sam-brief.md | 64 -- .bobby/design/sam-references.md | 38 - .../design/teardown-ableton-learningmusic.md | 46 - .bobby/design/teardown-apollo-mocr.md | 50 -- .bobby/design/teardown-beck-tube.md | 55 -- .bobby/design/teardown-buildkite.md | 42 - .bobby/design/teardown-dcmetro-live.md | 49 -- .bobby/design/teardown-github-actions.md | 43 - .bobby/design/teardown-gitlab-pipelines.md | 57 -- .bobby/design/teardown-goose-board.md | 43 - .bobby/design/teardown-gumroad.md | 56 -- .bobby/design/teardown-hill-charts.md | 43 - .bobby/design/teardown-order-tracking.md | 42 - .bobby/design/teardown-plausible-live.md | 46 - .bobby/design/teardown-posthog.md | 60 -- .bobby/design/teardown-rfeasley.md | 97 -- .bobby/design/teardown-solari-board.md | 53 -- .bobby/design/teardown-statuspage.md | 45 - .bobby/design/teardown-timercoffee.md | 47 - .bobby/design/teardown-useportal.md | 89 -- .bobby/docs/README.md | 4 - .bobby/tickets/.counter | 1 - .bobby/tickets/README.md | 3 - .../plan.md | 117 --- .../test-cases.md | 10 - .../ticket.md | 42 - .../test-cases.md | 10 - .../ticket.md | 40 - .../test-cases.md | 10 - .../ticket.md | 38 - .../test-cases.md | 10 - .../ticket.md | 64 -- .../test-cases.md | 10 - .../ticket.md | 60 -- .../test-cases.md | 10 - .../ticket.md | 40 - .../test-cases.md | 10 - .../ticket.md | 56 -- .../test-cases.md | 10 - .../ticket.md | 40 - .../test-cases.md | 10 - .../ticket.md | 40 - .../test-cases.md | 10 - .../ticket.md | 54 -- .../test-cases.md | 10 - .../ticket.md | 47 - .../test-cases.md | 10 - .../ticket.md | 44 - .../test-cases.md | 10 - .../ticket.md | 56 -- .../test-cases.md | 10 - .../ticket.md | 37 - .../test-cases.md | 10 - .../ticket.md | 51 -- .../test-cases.md | 10 - .../ticket.md | 38 - .../test-cases.md | 10 - .../ticket.md | 49 -- .../test-cases.md | 10 - .../ticket.md | 32 - .../test-cases.md | 10 - .../ticket.md | 37 - .../test-cases.md | 10 - .../ticket.md | 46 - .../test-cases.md | 10 - .../ticket.md | 38 - .../test-cases.md | 10 - .../ticket.md | 38 - .../test-cases.md | 10 - .../ticket.md | 49 -- .../test-cases.md | 10 - .../ticket.md | 37 - .../test-cases.md | 10 - .../ticket.md | 78 -- .../test-cases.md | 10 - .../ticket.md | 35 - .../test-cases.md | 10 - .../ticket.md | 40 - .../test-cases.md | 10 - .../ticket.md | 80 -- .../test-cases.md | 10 - .../ticket.md | 36 - .../test-cases.md | 10 - .../ticket.md | 37 - .../test-cases.md | 10 - .../ticket.md | 34 - .../test-cases.md | 10 - .../ticket.md | 32 - .../test-cases.md | 10 - .../ticket.md | 34 - .../feature-plan.md | 93 -- .../test-cases.md | 10 - .../ticket.md | 36 - .../plan.md | 137 --- .../review.md | 47 - .../test-cases.md | 100 --- .../test-evidence/results.md | 70 -- .../ticket.md | 42 - .../plan.md | 139 --- .../review.md | 204 ----- .../test-cases.md | 95 -- .../ticket.md | 81 -- .../review.md | 341 ------- .../test-cases.md | 10 - .../screenshots/local-01-boot.png | Bin 53072 -> 0 bytes .../screenshots/local-02-board.png | Bin 683196 -> 0 bytes .../screenshots/local-03-ideas.png | Bin 28039 -> 0 bytes .../screenshots/local-projectname.png | Bin 56680 -> 0 bytes .../screenshots/relay-01-home.png | Bin 42936 -> 0 bytes .../screenshots/relay-02-board.png | Bin 547940 -> 0 bytes .../screenshots/relay-03-ticket.png | Bin 786793 -> 0 bytes .../screenshots/relay-04-feature.png | Bin 115079 -> 0 bytes .../screenshots/relay-projectname.png | Bin 55772 -> 0 bytes .../ticket.md | 57 -- .../plan.md | 151 ---- .../test-cases.md | 105 --- .../ticket.md | 37 - .../test-cases.md | 10 - .../ticket.md | 34 - .../plan.md | 136 --- .../test-cases.md | 71 -- .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 64 -- .../test-cases.md | 10 - .../ticket.md | 61 -- .../test-cases.md | 10 - .../ticket.md | 61 -- .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 39 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 41 - .../test-cases.md | 10 - .../ticket.md | 114 --- .../test-cases.md | 10 - .../ticket.md | 68 -- .../test-cases.md | 10 - .../ticket.md | 78 -- .../test-cases.md | 10 - .../ticket.md | 66 -- .../test-cases.md | 10 - .../ticket.md | 138 --- .../test-cases.md | 10 - .../ticket.md | 83 -- .../test-cases.md | 10 - .../ticket.md | 57 -- .../test-cases.md | 10 - .../ticket.md | 65 -- .../test-cases.md | 10 - .../ticket.md | 64 -- .../test-cases.md | 10 - .../ticket.md | 64 -- .../test-cases.md | 10 - .../ticket.md | 83 -- .../test-cases.md | 10 - .../ticket.md | 78 -- .../test-cases.md | 10 - .../ticket.md | 77 -- .../test-cases.md | 10 - .../ticket.md | 63 -- .../plan.md | 143 --- .../test-cases.md | 91 -- .../ticket.md | 82 -- .../review.md | 38 - .../test-cases.md | 10 - .../test-evidence/results.md | 100 --- .../ticket.md | 99 --- .../test-cases.md | 10 - .../ticket.md | 119 --- .../test-cases.md | 10 - .../ticket.md | 89 -- .../test-cases.md | 10 - .../ticket.md | 69 -- .../test-cases.md | 10 - .../ticket.md | 59 -- .../plan.md | 147 ---- .../test-cases.md | 88 -- .../ticket.md | 46 - .../plan.md | 240 ----- .../test-cases.md | 119 --- .../ticket.md | 81 -- .../plan.md | 188 ---- .../test-cases.md | 161 ---- .../ticket.md | 90 -- .../test-cases.md | 10 - .../ticket.md | 37 - .../test-cases.md | 10 - .../ticket.md | 40 - .../test-cases.md | 10 - .../ticket.md | 79 -- .bobby/tickets/WORKFLOW.md | 94 -- .bobbyrc.yml | 127 --- 245 files changed, 18698 deletions(-) delete mode 100644 .bobby/.scaffold-version delete mode 100644 .bobby/architecture-wakeup.md delete mode 100644 .bobby/architecture.md delete mode 100644 .bobby/decisions.yaml delete mode 100644 .bobby/design/.gitignore delete mode 100644 .bobby/design/design-spec-feature-view.md delete mode 100644 .bobby/design/design-spec.md delete mode 100644 .bobby/design/mockups/app-palette.html delete mode 100644 .bobby/design/mockups/cassette-retro.html delete mode 100644 .bobby/design/mockups/clean-minimal.html delete mode 100644 .bobby/design/mockups/devin-white-fin-1.html delete mode 100644 .bobby/design/mockups/devin-white-fin-2.html delete mode 100644 .bobby/design/mockups/devin-white-fin-3.html delete mode 100644 .bobby/design/mockups/devin-white-flag-a.html delete mode 100644 .bobby/design/mockups/devin-white-flag-b.html delete mode 100644 .bobby/design/mockups/devin-white.html delete mode 100644 .bobby/design/mockups/f1-timing.html delete mode 100644 .bobby/design/mockups/mobile-jazz.html delete mode 100644 .bobby/design/mockups/racing-check.html delete mode 100644 .bobby/design/mockups/racing-subtle.html delete mode 100644 .bobby/design/mockups/the-line.html delete mode 100644 .bobby/design/references-feature-view.md delete mode 100644 .bobby/design/references.md delete mode 100644 .bobby/design/sam-brief.md delete mode 100644 .bobby/design/sam-references.md delete mode 100644 .bobby/design/teardown-ableton-learningmusic.md delete mode 100644 .bobby/design/teardown-apollo-mocr.md delete mode 100644 .bobby/design/teardown-beck-tube.md delete mode 100644 .bobby/design/teardown-buildkite.md delete mode 100644 .bobby/design/teardown-dcmetro-live.md delete mode 100644 .bobby/design/teardown-github-actions.md delete mode 100644 .bobby/design/teardown-gitlab-pipelines.md delete mode 100644 .bobby/design/teardown-goose-board.md delete mode 100644 .bobby/design/teardown-gumroad.md delete mode 100644 .bobby/design/teardown-hill-charts.md delete mode 100644 .bobby/design/teardown-order-tracking.md delete mode 100644 .bobby/design/teardown-plausible-live.md delete mode 100644 .bobby/design/teardown-posthog.md delete mode 100644 .bobby/design/teardown-rfeasley.md delete mode 100644 .bobby/design/teardown-solari-board.md delete mode 100644 .bobby/design/teardown-statuspage.md delete mode 100644 .bobby/design/teardown-timercoffee.md delete mode 100644 .bobby/design/teardown-useportal.md delete mode 100644 .bobby/docs/README.md delete mode 100644 .bobby/tickets/.counter delete mode 100644 .bobby/tickets/README.md delete mode 100644 .bobby/tickets/TKT-001--multi-harness-support-bobby-works-first-class-beyond-claude-code/plan.md delete mode 100644 .bobby/tickets/TKT-001--multi-harness-support-bobby-works-first-class-beyond-claude-code/test-cases.md delete mode 100644 .bobby/tickets/TKT-001--multi-harness-support-bobby-works-first-class-beyond-claude-code/ticket.md delete mode 100644 .bobby/tickets/TKT-001--the-define-pipeline-idea-brief-personas-journeys-feature-map-traceable-tickets/test-cases.md delete mode 100644 .bobby/tickets/TKT-001--the-define-pipeline-idea-brief-personas-journeys-feature-map-traceable-tickets/ticket.md delete mode 100644 .bobby/tickets/TKT-002--run-the-define-pipeline-on-a-real-bobbycode-feature-with-cc-at-the-gates/test-cases.md delete mode 100644 .bobby/tickets/TKT-002--run-the-define-pipeline-on-a-real-bobbycode-feature-with-cc-at-the-gates/ticket.md delete mode 100644 .bobby/tickets/TKT-002--target-matrix-invariant-test-suite-for-all-harness-adapters/test-cases.md delete mode 100644 .bobby/tickets/TKT-002--target-matrix-invariant-test-suite-for-all-harness-adapters/ticket.md delete mode 100644 .bobby/tickets/TKT-003--codex-cli-target-adapter-agents-md-rules-codex-skills/test-cases.md delete mode 100644 .bobby/tickets/TKT-003--codex-cli-target-adapter-agents-md-rules-codex-skills/ticket.md delete mode 100644 .bobby/tickets/TKT-003--phase-2-mockups-stage-wire-bobby-design-as-define-stage-5-using-personas-journeys-as-inputs/test-cases.md delete mode 100644 .bobby/tickets/TKT-003--phase-2-mockups-stage-wire-bobby-design-as-define-stage-5-using-personas-journeys-as-inputs/ticket.md delete mode 100644 .bobby/tickets/TKT-004--codex-dashboard-executor-drive-codex-exec-json/test-cases.md delete mode 100644 .bobby/tickets/TKT-004--codex-dashboard-executor-drive-codex-exec-json/ticket.md delete mode 100644 .bobby/tickets/TKT-004--phase-2-data-model-forward-architecture-stages-data-model-md-architecture-md-adrs/test-cases.md delete mode 100644 .bobby/tickets/TKT-004--phase-2-data-model-forward-architecture-stages-data-model-md-architecture-md-adrs/ticket.md delete mode 100644 .bobby/tickets/TKT-005--feature-view-design-app-ui-light-theme/test-cases.md delete mode 100644 .bobby/tickets/TKT-005--feature-view-design-app-ui-light-theme/ticket.md delete mode 100644 .bobby/tickets/TKT-005--generic-agents-md-target-for-the-agents-md-ecosystem/test-cases.md delete mode 100644 .bobby/tickets/TKT-005--generic-agents-md-target-for-the-agents-md-ecosystem/ticket.md delete mode 100644 .bobby/tickets/TKT-006--harness-support-matrix-document-tiers-and-verification-status/test-cases.md delete mode 100644 .bobby/tickets/TKT-006--harness-support-matrix-document-tiers-and-verification-status/ticket.md delete mode 100644 .bobby/tickets/TKT-006--light-redesign-the-four-remaining-app-views/test-cases.md delete mode 100644 .bobby/tickets/TKT-006--light-redesign-the-four-remaining-app-views/ticket.md delete mode 100644 .bobby/tickets/TKT-007--github-copilot-target-adapter-github-prompts-agents-md/test-cases.md delete mode 100644 .bobby/tickets/TKT-007--github-copilot-target-adapter-github-prompts-agents-md/ticket.md delete mode 100644 .bobby/tickets/TKT-007--redesign-home-for-the-light-system/test-cases.md delete mode 100644 .bobby/tickets/TKT-007--redesign-home-for-the-light-system/ticket.md delete mode 100644 .bobby/tickets/TKT-008--opencode-target-adapter-agents-md-opencode-command/test-cases.md delete mode 100644 .bobby/tickets/TKT-008--opencode-target-adapter-agents-md-opencode-command/ticket.md delete mode 100644 .bobby/tickets/TKT-008--redesign-board-for-the-light-system/test-cases.md delete mode 100644 .bobby/tickets/TKT-008--redesign-board-for-the-light-system/ticket.md delete mode 100644 .bobby/tickets/TKT-009--opencode-dashboard-executor-drive-opencode-run/test-cases.md delete mode 100644 .bobby/tickets/TKT-009--opencode-dashboard-executor-drive-opencode-run/ticket.md delete mode 100644 .bobby/tickets/TKT-009--redesign-ticket-detail-for-the-light-system/test-cases.md delete mode 100644 .bobby/tickets/TKT-009--redesign-ticket-detail-for-the-light-system/ticket.md delete mode 100644 .bobby/tickets/TKT-010--redesign-workspace-live-log-diff-for-the-light-system/test-cases.md delete mode 100644 .bobby/tickets/TKT-010--redesign-workspace-live-log-diff-for-the-light-system/ticket.md delete mode 100644 .bobby/tickets/TKT-010--windsurf-target-adapter-windsurf-rules/test-cases.md delete mode 100644 .bobby/tickets/TKT-010--windsurf-target-adapter-windsurf-rules/ticket.md delete mode 100644 .bobby/tickets/TKT-011--give-look-first-a-home-on-the-feature-view/test-cases.md delete mode 100644 .bobby/tickets/TKT-011--give-look-first-a-home-on-the-feature-view/ticket.md delete mode 100644 .bobby/tickets/TKT-011--zed-target-adapter-rules/test-cases.md delete mode 100644 .bobby/tickets/TKT-011--zed-target-adapter-rules/ticket.md delete mode 100644 .bobby/tickets/TKT-012--gemini-cli-antigravity-target-re-evaluate-after-rename-settles/test-cases.md delete mode 100644 .bobby/tickets/TKT-012--gemini-cli-antigravity-target-re-evaluate-after-rename-settles/ticket.md delete mode 100644 .bobby/tickets/TKT-012--show-owner-repo-in-the-feature-sublabel-expose-git-remote-in-the-api/test-cases.md delete mode 100644 .bobby/tickets/TKT-012--show-owner-repo-in-the-feature-sublabel-expose-git-remote-in-the-api/ticket.md delete mode 100644 .bobby/tickets/TKT-013--built-in-library-workflow-cli-library-projects-strand-at-the-live-app-test-stage/test-cases.md delete mode 100644 .bobby/tickets/TKT-013--built-in-library-workflow-cli-library-projects-strand-at-the-live-app-test-stage/ticket.md delete mode 100644 .bobby/tickets/TKT-013--merge-timestamps-on-child-tickets-so-rows-can-say-merged-2h-ago/test-cases.md delete mode 100644 .bobby/tickets/TKT-013--merge-timestamps-on-child-tickets-so-rows-can-say-merged-2h-ago/ticket.md delete mode 100644 .bobby/tickets/TKT-014--createreporun-run-freeform-agents-against-the-main-checkout/test-cases.md delete mode 100644 .bobby/tickets/TKT-014--createreporun-run-freeform-agents-against-the-main-checkout/ticket.md delete mode 100644 .bobby/tickets/TKT-014--scheduled-ci-real-cli-flag-drift-canary-for-every-executor/test-cases.md delete mode 100644 .bobby/tickets/TKT-014--scheduled-ci-real-cli-flag-drift-canary-for-every-executor/ticket.md delete mode 100644 .bobby/tickets/TKT-015--cap-concurrent-agents-with-dashboard-max-concurrent/test-cases.md delete mode 100644 .bobby/tickets/TKT-015--cap-concurrent-agents-with-dashboard-max-concurrent/ticket.md delete mode 100644 .bobby/tickets/TKT-016--commit-the-playwright-e2e-suite-for-the-app/test-cases.md delete mode 100644 .bobby/tickets/TKT-016--commit-the-playwright-e2e-suite-for-the-app/ticket.md delete mode 100644 .bobby/tickets/TKT-017--api-runs-run-history-as-a-first-class-resource/test-cases.md delete mode 100644 .bobby/tickets/TKT-017--api-runs-run-history-as-a-first-class-resource/ticket.md delete mode 100644 .bobby/tickets/TKT-018--ideas-tab-surface-bobby-idea-in-the-app/test-cases.md delete mode 100644 .bobby/tickets/TKT-018--ideas-tab-surface-bobby-idea-in-the-app/ticket.md delete mode 100644 .bobby/tickets/TKT-019--per-run-cost-from-claude-total-cost-usd/test-cases.md delete mode 100644 .bobby/tickets/TKT-019--per-run-cost-from-claude-total-cost-usd/ticket.md delete mode 100644 .bobby/tickets/TKT-020--phase-3-the-app-beyond-a-single-project/feature-plan.md delete mode 100644 .bobby/tickets/TKT-020--phase-3-the-app-beyond-a-single-project/test-cases.md delete mode 100644 .bobby/tickets/TKT-020--phase-3-the-app-beyond-a-single-project/ticket.md delete mode 100644 .bobby/tickets/TKT-021--vet-chat-conversational-planning-with-executor-resume/plan.md delete mode 100644 .bobby/tickets/TKT-021--vet-chat-conversational-planning-with-executor-resume/review.md delete mode 100644 .bobby/tickets/TKT-021--vet-chat-conversational-planning-with-executor-resume/test-cases.md delete mode 100644 .bobby/tickets/TKT-021--vet-chat-conversational-planning-with-executor-resume/test-evidence/results.md delete mode 100644 .bobby/tickets/TKT-021--vet-chat-conversational-planning-with-executor-resume/ticket.md delete mode 100644 .bobby/tickets/TKT-022--studio-mode-switch-projects-from-inside-the-app/plan.md delete mode 100644 .bobby/tickets/TKT-022--studio-mode-switch-projects-from-inside-the-app/review.md delete mode 100644 .bobby/tickets/TKT-022--studio-mode-switch-projects-from-inside-the-app/test-cases.md delete mode 100644 .bobby/tickets/TKT-022--studio-mode-switch-projects-from-inside-the-app/ticket.md delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/review.md delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-cases.md delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/local-01-boot.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/local-02-board.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/local-03-ideas.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/local-projectname.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/relay-01-home.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/relay-02-board.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/relay-03-ticket.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/relay-04-feature.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/test-evidence/screenshots/relay-projectname.png delete mode 100644 .bobby/tickets/TKT-023--relaytransport-the-same-frontend-over-the-encrypted-relay/ticket.md delete mode 100644 .bobby/tickets/TKT-024--non-dev-onboarding-what-do-you-want-to-build-stack-cards/plan.md delete mode 100644 .bobby/tickets/TKT-024--non-dev-onboarding-what-do-you-want-to-build-stack-cards/test-cases.md delete mode 100644 .bobby/tickets/TKT-024--non-dev-onboarding-what-do-you-want-to-build-stack-cards/ticket.md delete mode 100644 .bobby/tickets/TKT-025--extract-createproject-from-the-commands-new-js-closure-into-lib-project-js/test-cases.md delete mode 100644 .bobby/tickets/TKT-025--extract-createproject-from-the-commands-new-js-closure-into-lib-project-js/ticket.md delete mode 100644 .bobby/tickets/TKT-026--delete-classic-once-the-app-is-the-default/plan.md delete mode 100644 .bobby/tickets/TKT-026--delete-classic-once-the-app-is-the-default/test-cases.md delete mode 100644 .bobby/tickets/TKT-026--delete-classic-once-the-app-is-the-default/ticket.md delete mode 100644 .bobby/tickets/TKT-027--home-the-send-back-sheet-is-off-system-and-still-red-4-spec-conformance-failures-one-tap-into-the-home-flow/test-cases.md delete mode 100644 .bobby/tickets/TKT-027--home-the-send-back-sheet-is-off-system-and-still-red-4-spec-conformance-failures-one-tap-into-the-home-flow/ticket.md delete mode 100644 .bobby/tickets/TKT-028--home-sse-log-lines-trigger-full-re-renders-that-destroy-keyboard-focus-store-lastlog-is-now-write-only/test-cases.md delete mode 100644 .bobby/tickets/TKT-028--home-sse-log-lines-trigger-full-re-renders-that-destroy-keyboard-focus-store-lastlog-is-now-write-only/ticket.md delete mode 100644 .bobby/tickets/TKT-029--home-error-toast-renders-edge-to-edge-outside-the-440px-column-and-above-the-top-bar/test-cases.md delete mode 100644 .bobby/tickets/TKT-029--home-error-toast-renders-edge-to-edge-outside-the-440px-column-and-above-the-top-bar/ticket.md delete mode 100644 .bobby/tickets/TKT-030--home-empty-state-says-nothing-is-happening-four-times-and-offers-no-action/test-cases.md delete mode 100644 .bobby/tickets/TKT-030--home-empty-state-says-nothing-is-happening-four-times-and-offers-no-action/ticket.md delete mode 100644 .bobby/tickets/TKT-031--home-a-ready-to-merge-row-below-the-top-row-shows-a-decision-that-cannot-be-taken-from-home/test-cases.md delete mode 100644 .bobby/tickets/TKT-031--home-a-ready-to-merge-row-below-the-top-row-shows-a-decision-that-cannot-be-taken-from-home/ticket.md delete mode 100644 .bobby/tickets/TKT-032--tab-order-puts-the-bottom-nav-bar-before-page-content-on-every-view/test-cases.md delete mode 100644 .bobby/tickets/TKT-032--tab-order-puts-the-bottom-nav-bar-before-page-content-on-every-view/ticket.md delete mode 100644 .bobby/tickets/TKT-033--btn-quiet-border-is-1-22-1-against-the-ground-on-both-home-and-feature/test-cases.md delete mode 100644 .bobby/tickets/TKT-033--btn-quiet-border-is-1-22-1-against-the-ground-on-both-home-and-feature/ticket.md delete mode 100644 .bobby/tickets/TKT-034--home-the-top-bar-panel-glyph-reads-as-toggle-sidebar-but-navigates-to-the-board/test-cases.md delete mode 100644 .bobby/tickets/TKT-034--home-the-top-bar-panel-glyph-reads-as-toggle-sidebar-but-navigates-to-the-board/ticket.md delete mode 100644 .bobby/tickets/TKT-035--board-row-sublabel-repeats-the-lane-s-stage-word-and-drops-the-ticket-id-on-rows-in-motion/test-cases.md delete mode 100644 .bobby/tickets/TKT-035--board-row-sublabel-repeats-the-lane-s-stage-word-and-drops-the-ticket-id-on-rows-in-motion/ticket.md delete mode 100644 .bobby/tickets/TKT-036--blocked-tickets-are-indistinguishable-from-settled-ones-on-the-board/test-cases.md delete mode 100644 .bobby/tickets/TKT-036--blocked-tickets-are-indistinguishable-from-settled-ones-on-the-board/ticket.md delete mode 100644 .bobby/tickets/TKT-037--feature-progress-bar-renders-invisible-at-0-percent-1-17-1-track-on-white/test-cases.md delete mode 100644 .bobby/tickets/TKT-037--feature-progress-bar-renders-invisible-at-0-percent-1-17-1-track-on-white/ticket.md delete mode 100644 .bobby/tickets/TKT-038--ticket-detail-never-re-renders-on-store-change-live-status-goes-stale/test-cases.md delete mode 100644 .bobby/tickets/TKT-038--ticket-detail-never-re-renders-on-store-change-live-status-goes-stale/ticket.md delete mode 100644 .bobby/tickets/TKT-039--confirm-sheet-names-the-wrong-workflow-stages-on-a-non-default-ticket/test-cases.md delete mode 100644 .bobby/tickets/TKT-039--confirm-sheet-names-the-wrong-workflow-stages-on-a-non-default-ticket/ticket.md delete mode 100644 .bobby/tickets/TKT-040--ticket-body-prose-outranks-the-section-heading-above-it/test-cases.md delete mode 100644 .bobby/tickets/TKT-040--ticket-body-prose-outranks-the-section-heading-above-it/ticket.md delete mode 100644 .bobby/tickets/TKT-041--acceptance-criteria-render-as-raw-unstyled-markdown-list/test-cases.md delete mode 100644 .bobby/tickets/TKT-041--acceptance-criteria-render-as-raw-unstyled-markdown-list/ticket.md delete mode 100644 .bobby/tickets/TKT-042--ticket-file-scaffolding-leaks-into-page-headings-and-metadata-values/test-cases.md delete mode 100644 .bobby/tickets/TKT-042--ticket-file-scaffolding-leaks-into-page-headings-and-metadata-values/ticket.md delete mode 100644 .bobby/tickets/TKT-043--ticket-re-render-orphans-the-confirm-sheet-trigger-so-focus-returns-to-body/test-cases.md delete mode 100644 .bobby/tickets/TKT-043--ticket-re-render-orphans-the-confirm-sheet-trigger-so-focus-returns-to-body/ticket.md delete mode 100644 .bobby/tickets/TKT-044--workspace-the-diff-pane-s-non-diff-lines-bypass-ph-empty-state-and-every-fetch-error-render-as-mono-diff-context/test-cases.md delete mode 100644 .bobby/tickets/TKT-044--workspace-the-diff-pane-s-non-diff-lines-bypass-ph-empty-state-and-every-fetch-error-render-as-mono-diff-context/ticket.md delete mode 100644 .bobby/tickets/TKT-045--workspace-a-failed-diff-fetch-has-no-error-treatment-renders-in-ink-2-while-the-log-pane-right-above-it-uses-bad/test-cases.md delete mode 100644 .bobby/tickets/TKT-045--workspace-a-failed-diff-fetch-has-no-error-treatment-renders-in-ink-2-while-the-log-pane-right-above-it-uses-bad/ticket.md delete mode 100644 .bobby/tickets/TKT-046--workspace-show-diff-has-no-loading-state-and-stays-enabled-during-the-fetch-the-page-is-inert-and-a-second-click-shells-out-to-git-again/test-cases.md delete mode 100644 .bobby/tickets/TKT-046--workspace-show-diff-has-no-loading-state-and-stays-enabled-during-the-fetch-the-page-is-inert-and-a-second-click-shells-out-to-git-again/ticket.md delete mode 100644 .bobby/tickets/TKT-047--orchestrator-resolvenextagent-looks-off-by-one-the-app-s-approve-may-skip-the-build-stage/test-cases.md delete mode 100644 .bobby/tickets/TKT-047--orchestrator-resolvenextagent-looks-off-by-one-the-app-s-approve-may-skip-the-build-stage/ticket.md delete mode 100644 .bobby/tickets/TKT-048--agents-hardcode-their-exit-stage-so-any-workflow-but-default-silently-truncates/test-cases.md delete mode 100644 .bobby/tickets/TKT-048--agents-hardcode-their-exit-stage-so-any-workflow-but-default-silently-truncates/ticket.md delete mode 100644 .bobby/tickets/TKT-049--the-built-in-secure-workflow-cannot-get-past-its-security-stage/test-cases.md delete mode 100644 .bobby/tickets/TKT-049--the-built-in-secure-workflow-cannot-get-past-its-security-stage/ticket.md delete mode 100644 .bobby/tickets/TKT-050--design-tickets-vanish-from-the-app-board-board-order-omits-the-design-stages/test-cases.md delete mode 100644 .bobby/tickets/TKT-050--design-tickets-vanish-from-the-app-board-board-order-omits-the-design-stages/ticket.md delete mode 100644 .bobby/tickets/TKT-051--a-ticket-that-isn-t-on-main-cannot-be-run-through-the-app-worktrees-fork-from-main/test-cases.md delete mode 100644 .bobby/tickets/TKT-051--a-ticket-that-isn-t-on-main-cannot-be-run-through-the-app-worktrees-fork-from-main/ticket.md delete mode 100644 .bobby/tickets/TKT-052--agent-prompts-use-a-relative-tickets-path-that-does-not-resolve-inside-a-worktree/test-cases.md delete mode 100644 .bobby/tickets/TKT-052--agent-prompts-use-a-relative-tickets-path-that-does-not-resolve-inside-a-worktree/ticket.md delete mode 100644 .bobby/tickets/TKT-053--sprint-prompts-use-a-relative-sprints-path-that-does-not-resolve-inside-a-worktree/test-cases.md delete mode 100644 .bobby/tickets/TKT-053--sprint-prompts-use-a-relative-sprints-path-that-does-not-resolve-inside-a-worktree/ticket.md delete mode 100644 .bobby/tickets/TKT-054--board-two-lanes-can-share-one-heading-and-one-id-breaking-aria-labelledby/test-cases.md delete mode 100644 .bobby/tickets/TKT-054--board-two-lanes-can-share-one-heading-and-one-id-breaking-aria-labelledby/ticket.md delete mode 100644 .bobby/tickets/TKT-055--feature-view-pipeline-cannot-represent-the-four-design-stages/test-cases.md delete mode 100644 .bobby/tickets/TKT-055--feature-view-pipeline-cannot-represent-the-four-design-stages/ticket.md delete mode 100644 .bobby/tickets/TKT-056--board-rows-repeat-the-lane-heading-in-every-sublabel/test-cases.md delete mode 100644 .bobby/tickets/TKT-056--board-rows-repeat-the-lane-heading-in-every-sublabel/ticket.md delete mode 100644 .bobby/tickets/TKT-057--feature-pipeline-draws-steps-done-and-paints-the-road-blue-while-a-row-beneath-says-that-stage-is-in-progress/test-cases.md delete mode 100644 .bobby/tickets/TKT-057--feature-pipeline-draws-steps-done-and-paints-the-road-blue-while-a-row-beneath-says-that-stage-is-in-progress/ticket.md delete mode 100644 .bobby/tickets/TKT-058--board-lane-id-collides-with-the-page-s-own-blocked-and-features-sections-so-aria-labelledby-still-announces-one-lane-as-another/test-cases.md delete mode 100644 .bobby/tickets/TKT-058--board-lane-id-collides-with-the-page-s-own-blocked-and-features-sections-so-aria-labelledby-still-announces-one-lane-as-another/ticket.md delete mode 100644 .bobby/tickets/TKT-059--pipeline-step-and-ticket-row-use-two-different-words-for-the-same-default-workflow-stage-build-vs-building/test-cases.md delete mode 100644 .bobby/tickets/TKT-059--pipeline-step-and-ticket-row-use-two-different-words-for-the-same-default-workflow-stage-build-vs-building/ticket.md delete mode 100644 .bobby/tickets/TKT-060--top-bar-project-pill-is-28px-tall-and-the-desktop-nav-items-are-43px-under-the-spec-s-44px-tap-floor/test-cases.md delete mode 100644 .bobby/tickets/TKT-060--top-bar-project-pill-is-28px-tall-and-the-desktop-nav-items-are-43px-under-the-spec-s-44px-tap-floor/ticket.md delete mode 100644 .bobby/tickets/TKT-061--bobby-learn-commits-unrelated-in-flight-work-as-a-side-effect/plan.md delete mode 100644 .bobby/tickets/TKT-061--bobby-learn-commits-unrelated-in-flight-work-as-a-side-effect/test-cases.md delete mode 100644 .bobby/tickets/TKT-061--bobby-learn-commits-unrelated-in-flight-work-as-a-side-effect/ticket.md delete mode 100644 .bobby/tickets/TKT-062--out-of-the-box-the-app-s-agents-cannot-write-they-burn-tokens-retrying-a-permission-prompt-nobody-can-answer/review.md delete mode 100644 .bobby/tickets/TKT-062--out-of-the-box-the-app-s-agents-cannot-write-they-burn-tokens-retrying-a-permission-prompt-nobody-can-answer/test-cases.md delete mode 100644 .bobby/tickets/TKT-062--out-of-the-box-the-app-s-agents-cannot-write-they-burn-tokens-retrying-a-permission-prompt-nobody-can-answer/test-evidence/results.md delete mode 100644 .bobby/tickets/TKT-062--out-of-the-box-the-app-s-agents-cannot-write-they-burn-tokens-retrying-a-permission-prompt-nobody-can-answer/ticket.md delete mode 100644 .bobby/tickets/TKT-063--bobby-learn-corrupts-decisions-yaml-appends-an-entry-that-inherits-the-previous-entry-s-trailing-keys/test-cases.md delete mode 100644 .bobby/tickets/TKT-063--bobby-learn-corrupts-decisions-yaml-appends-an-entry-that-inherits-the-previous-entry-s-trailing-keys/ticket.md delete mode 100644 .bobby/tickets/TKT-064--bobby-remote-says-team-is-reachable-before-it-is-and-for-urls-no-phone-can-ever-use/test-cases.md delete mode 100644 .bobby/tickets/TKT-064--bobby-remote-says-team-is-reachable-before-it-is-and-for-urls-no-phone-can-ever-use/ticket.md delete mode 100644 .bobby/tickets/TKT-065--bobby-remote-should-bring-its-own-relay-and-tunnel-today-it-takes-four-manual-steps-to-get-a-working-phone-link/test-cases.md delete mode 100644 .bobby/tickets/TKT-065--bobby-remote-should-bring-its-own-relay-and-tunnel-today-it-takes-four-manual-steps-to-get-a-working-phone-link/ticket.md delete mode 100644 .bobby/tickets/TKT-066--pairings-accumulate-with-no-way-to-list-or-revoke-them-one-on-this-machine-was-9-days-old-and-still-live/test-cases.md delete mode 100644 .bobby/tickets/TKT-066--pairings-accumulate-with-no-way-to-list-or-revoke-them-one-on-this-machine-was-9-days-old-and-still-live/ticket.md delete mode 100644 .bobby/tickets/TKT-067--pair-once-addressing-over-relaytransport-one-pairing-reaches-every-project-bobby-remote-studio-serves/plan.md delete mode 100644 .bobby/tickets/TKT-067--pair-once-addressing-over-relaytransport-one-pairing-reaches-every-project-bobby-remote-studio-serves/test-cases.md delete mode 100644 .bobby/tickets/TKT-067--pair-once-addressing-over-relaytransport-one-pairing-reaches-every-project-bobby-remote-studio-serves/ticket.md delete mode 100644 .bobby/tickets/TKT-068--converge-feat-bobby-app-and-feat-workspace-projects-the-app-and-the-studio-are-on-unmerged-branches-neither-has-the-other-s-code/plan.md delete mode 100644 .bobby/tickets/TKT-068--converge-feat-bobby-app-and-feat-workspace-projects-the-app-and-the-studio-are-on-unmerged-branches-neither-has-the-other-s-code/test-cases.md delete mode 100644 .bobby/tickets/TKT-068--converge-feat-bobby-app-and-feat-workspace-projects-the-app-and-the-studio-are-on-unmerged-branches-neither-has-the-other-s-code/ticket.md delete mode 100644 .bobby/tickets/TKT-069--the-app-orchestrator-worktrees-a-ticket-in-its-target-repo-not-always-the-launch-repo/plan.md delete mode 100644 .bobby/tickets/TKT-069--the-app-orchestrator-worktrees-a-ticket-in-its-target-repo-not-always-the-launch-repo/test-cases.md delete mode 100644 .bobby/tickets/TKT-069--the-app-orchestrator-worktrees-a-ticket-in-its-target-repo-not-always-the-launch-repo/ticket.md delete mode 100644 .bobby/tickets/TKT-070--delete-the-orphaned-commands-dashboard-js-superseded-by-commands-app-js-after-the-app-studio-merge/test-cases.md delete mode 100644 .bobby/tickets/TKT-070--delete-the-orphaned-commands-dashboard-js-superseded-by-commands-app-js-after-the-app-studio-merge/ticket.md delete mode 100644 .bobby/tickets/TKT-071--dashboard-plugins-receive-boot-config-board-not-the-active-project-s/test-cases.md delete mode 100644 .bobby/tickets/TKT-071--dashboard-plugins-receive-boot-config-board-not-the-active-project-s/ticket.md delete mode 100644 .bobby/tickets/TKT-072--ci-is-red-on-main-createproject-s-initial-commit-fails-on-linux-runners/test-cases.md delete mode 100644 .bobby/tickets/TKT-072--ci-is-red-on-main-createproject-s-initial-commit-fails-on-linux-runners/ticket.md delete mode 100644 .bobby/tickets/WORKFLOW.md delete mode 100644 .bobbyrc.yml diff --git a/.bobby/.scaffold-version b/.bobby/.scaffold-version deleted file mode 100644 index f0bb29e..0000000 --- a/.bobby/.scaffold-version +++ /dev/null @@ -1 +0,0 @@ -1.3.0 diff --git a/.bobby/architecture-wakeup.md b/.bobby/architecture-wakeup.md deleted file mode 100644 index 20ca07f..0000000 --- a/.bobby/architecture-wakeup.md +++ /dev/null @@ -1,37 +0,0 @@ -# Architecture Wakeup - -``` -PLATFORM: bobbycode — MIT Node18+ ESM CLI + local web app that runs AI agents - through a ticket workflow. No DB, no framework, no bundler. State = files. - -LAYOUT: - bin/bobby.js + commands/ → CLI (commander) - lib/tickets|stages|workflow|brief|agent-registry → the loop - lib/dashboard/ → orchestrator, worktree, executor, state, sse, server (~31 routes) - lib/targets/ → claude-code | cursor | cline lib/remote/ → E2E relay - APP UI IS NOT HERE → @bobbycode/pro-dashboard app/ , via BOBBY_APP_DIR or Pro plugin. - API is here (lib/dashboard/server.js). Visual contract: .bobby/design/design-spec-feature-view.md - -FLOW: browser → /api/* → Orchestrator → git worktree → spawn `claude -p` → stdout JSONL - → session .jsonl + SSE. Ticket stage re-read FROM THE SHARED BOARD on exit. - -AUTH: none. 127.0.0.1 only. `bobby remote` = outbound WS, AES-256-GCM, GET/POST /api/* only. - -STATE: .bobby/tickets/*/ticket.md frontmatter · .bobby/workspaces.json · .bobby/sessions/*.jsonl - -TESTS: npm test (jest, ESM flag) · npm run lint · no Docker · CI = lint+test on 18/20/22 - Use fs.mkdtempSync + real git; inject `spawn` into runAgent; never launch a real CLI. - -PITFALLS: - - Tickets are SHARED state: always the MAIN worktree root, from anywhere. Worktrees - isolate CODE only — never read /.bobby/tickets, it is frozen at fork. - - New worktrees fork from main/master, not your branch. Unmerged CODE is invisible. - - Stage done ≠ agent exited. Success = exit 0 AND stage changed, then awaits approval. - - mergeToMain stashes + checks out main IN the main checkout — races any repo work. - - _resolveNextAgent is likely off-by-one (skips build); AGENT_STAGE_MAP is dead code. - - No concurrency cap. This repo's `default` workflow ends at review (no live app). - - Never hand-edit .bobby/tickets/.counter — IDs are claimed by atomic mkdir. - - UI is light-only, 13px type floor, no pulse, no shadows. - -DECISIONS: see .bobby/decisions.yaml FULL: .bobby/architecture.md -``` diff --git a/.bobby/architecture.md b/.bobby/architecture.md deleted file mode 100644 index 7fb48f6..0000000 --- a/.bobby/architecture.md +++ /dev/null @@ -1,334 +0,0 @@ -# Architecture - -_Generated by bobby-arch. Re-run: `bobby run arch`_ - -**Read this if you are about to change the orchestrator, the dashboard server, the -worktree layer, or anything that decides which agent runs next.** It records the data -flow and the invariants — the places where being wrong breaks something quietly. - -## Overview - -Bobby is a CLI plus a local web app that runs a team of AI coding agents through a -ticket workflow for a solo developer. `bobbycode` is the MIT npm package: the CLI -(`bin/bobby.js`), the ticket/workflow engine (`lib/`), the agent prompt builders, the -scaffolding templates, and the whole local HTTP API. It has no runtime service, no -database, and no network dependency — everything is files on disk plus child processes. - -Two consumers drive the same engine: the CLI (a human at a terminal) and the local -server (`bobby app`, and `bobby remote` for a phone). Both build the same prompts and -both spawn the same `claude` subprocess. - -## Domain Glossary - -**Ticket** — a directory `.bobby/tickets/TKT-NNN--slug/` containing `ticket.md` -(YAML frontmatter + markdown body), usually `plan.md` and `test-cases.md`. The -frontmatter *is* the database; there is no other store. - -**Stage** — where a ticket is in its life: `backlog → planning → building → reviewing → -testing → shipping → done`, plus `blocked` and the four design stages -(`design-research`, `design-analyze`, `design-mockup`, `design-spec`). Defined in -`lib/stages.js`. Stage lives in `ticket.md` frontmatter and nowhere else. - -**Transition alias** — the short word a human types: `plan`, `build`, `review`, `test`, -`ship`, plus the three specials `reject` / `block` / `unblock` handled outside the -table (`lib/stages.js` `TRANSITIONS`, `lib/dashboard/actions.js` `moveWithAlias`). - -**Workflow** (internally sometimes "pipeline") — the ordered stage list a ticket runs -through. Built-ins `default`, `secure`, `quick`, `design` in `lib/workflow.js`; users -add or override under `workflows:` in `.bobbyrc.yml`. Resolution precedence: explicit -`--workflow` > ticket frontmatter `workflow:` > named workflow > `DEFAULT_WORKFLOW`. - -**Agent** — an entry in `AGENT_REGISTRY` (`lib/agent-registry.js`). Flags on the entry -decide dispatch: `requiresTicket`, `cowork` (works with or without a ticket), -`freeform` (never needs a ticket), `custom` (has a bespoke builder in `workflow.js`). -A ticket-less agent that operates on the repo is what TKT-014 calls a *repo run*. - -**Epic / feature** — a ticket with `type: epic`; children point at it via `parent:`. -`bobby run feature` plans and builds all children on one branch. - -**Workspace** — the app's unit of work: one ticket, one git worktree, one branch, a -status, and a run history. Record shape in `lib/dashboard/state.js`; persisted to -`.bobby/workspaces.json`. - -**Run** — one execution of one agent inside a workspace. Appended to `workspace.runs`. -There is no `/api/runs` yet (that is TKT-017). - -**Checkpoint** — a commit the orchestrator makes in the worktree after every run so the -diff viewer always shows committed state (`commitCheckpoint`). - -**Session** — a JSONL log at `.bobby/sessions/ses-.jsonl`. Every executor -event is mirrored into it; the server tails it into SSE. - -**Studio** — the machine-wide project registry at `~/.bobby/projects.yml` -(`lib/studio.js`). Any command run inside a project upserts it. (On the newer -`feat/workspace-projects` branch this word is reused for a different concept — see -"Branch topology".) - -**Target** — the AI harness the scaffold is written for (`claude-code`, `cursor`, -`cline`), deciding where agents/skills/commands files land (`lib/targets/`). - -**Executor** — the CLI the server spawns (`claude` or `cursor-agent`), a separate axis -from target (`lib/dashboard/executor.js`). - -**Overlay** — `X.ext` is Bobby's and is regenerated on upgrade; `X.local.ext` is yours, -seeded once and never rewritten. Pinned by `test/lib/overlay.test.js`. - -## Repositories & Tech Stack - -| Repo | Purpose | Stack | -|---|---|---| -| `bobbycode` (this repo, MIT) | CLI, ticket engine, workflow/prompt builders, orchestrator, HTTP API, scaffolding templates, classic dashboard UI | Node ≥18, ESM only (`"type": "module"`). `commander`, `gray-matter`, `yaml`, `inquirer`, `chalk`, `ejs`, `ws`, `qrcode-terminal`. No framework, no bundler, no DB | -| `@bobbycode/pro-dashboard` (private, paid) at `/Users/ccevans/Desktop/bobbycode-pro/app/` | **The Bobby App UI.** `app/` is the App (index.html, app.js, style.css, `lib/{store,transport,ui}.js`, `views/{home,board,feature,workspace}.js`); `ui/` is a separate add-on for the classic dashboard; `index.js` exports `register(context)` | Dependency-free browser ES modules | -| Bobby HQ (phone client) + relay | Talk to `bobby remote` over the encrypted channel | Out of this repo | - -### The App-UI seam — read this before touching the UI - -**The app UI is not in this repository.** `commands/app.js` `resolveAppDir()` picks the -directory served at `/`, in this order: - -1. `BOBBY_APP_DIR` — an explicit path (how the App is developed). If it has no - `index.html`, it falls back to classic and says so. -2. Pro active + `@bobbycode/pro-dashboard` found → `/app/`. Discovery order for - the package: `$BOBBY_PRO_DASHBOARD` → `~/.bobby/pro/node_modules/` → - `/node_modules/` (`lib/dashboard/plugins.js`). -3. Otherwise `null` → the free classic dashboard in `templates/dashboard/`. - -When an app dir is active, the classic UI is still mounted at `/classic/`. **The API the -App consumes is entirely in this repo** (`lib/dashboard/server.js`) and is MIT. If you -are asked to "change the app", decide first whether the change is API (here) or view -(the Pro repo). Its visual contract is `.bobby/design/design-spec-feature-view.md` — -read that file, do not restate it. - -## Request Flow - -``` -CLI: bobby go ─▶ buildBrief() ─▶ nextAction.argv ─▶ re-invokes `bobby run …` - bobby run [ids] ─▶ resolveWorkflow ─▶ buildPromptFor ─▶ PRINTS the prompt - (the CLI never spawns claude) - -App: browser ─▶ http://127.0.0.1:7777 - static ─▶ appDir (Pro/BOBBY_APP_DIR) or templates/dashboard, /classic/ alias - /api/* ─▶ lib/dashboard/server.js - ├─ reads/writes tickets ─▶ lib/tickets.js ─▶ .bobby/tickets/*/ticket.md - ├─ POST /api/go ─▶ actions.executeGoAction ─▶ orchestrator - └─ workspace verbs ─▶ Orchestrator - createWorkspace ─▶ worktree.createWorktree (git worktree add) - runAgent ─▶ buildPromptFor ─▶ executor.runAgent - spawn `claude -p - --output-format stream-json --verbose` - cwd = the worktree - each stdout line ─▶ session JSONL + SSEHub.broadcast - on exit ─▶ re-read ticket stage FROM THE SHARED BOARD - commitCheckpoint, set status, maybe auto-approve - /api/events, /api/workspaces/:id/events ─▶ SSE (lib/dashboard/sse.js) - -Phone: bobby remote ─▶ same server on 127.0.0.1: - ─▶ ONE outbound WebSocket to the relay - RemoteTunnel proxies decrypted {req,sub} frames into /api/* -``` - -There is no database. State lives in: `.bobby/tickets/**/ticket.md` (frontmatter), -`.bobby/workspaces.json`, `.bobby/sessions/*.jsonl`, `.bobbyrc.yml`, and git itself. - -## State Machines - -### Workspace (`lib/dashboard/state.js`, driven by `lib/dashboard/orchestrator.js`) - -```mermaid -stateDiagram-v2 - [*] --> idle: createWorkspace (git worktree add) - idle --> running: runAgent / approve - running --> awaiting_approval: exit 0 AND ticket stage advanced on the shared board - running --> idle: exit 0, stage unchanged - running --> ready_to_merge: exit 0 AND new stage is shipping|done - running --> failed: non-zero exit - running --> stopped: SIGTERM/SIGKILL - awaiting_approval --> running: approve (next agent) / reject (re-run build) - awaiting_approval --> ready_to_merge: approve with no next stage - ready_to_merge --> merged: merge (git merge --no-ff into the main checkout) - running --> unknown: dashboard restart (reconcileAfterRestart) -``` - -The transition rule lives in `_onExit`: **exit code 0 alone is not success.** Success is -`exitCode === 0` *and* the ticket's stage on the shared board differing from -`workspace.stage`. A clean exit with no stage change lands back in `idle`, not -`awaiting_approval` — which is what an agent that silently did nothing looks like. - -### Ticket - -`backlog → planning → building → reviewing → testing → shipping → done`, with `blocked` -recording `previous_stage` for `unblock`. `moveTicket` also **auto-advances the parent -epic** when every non-blocked child has reached a later stage (`lib/tickets.js`, end of -`moveTicket`) — a change to one child can rewrite the epic's frontmatter. - -## Key Patterns - -- **Add an agent = add a registry entry.** `AGENT_REGISTRY` with `promptHeader` + - `promptSteps` is enough; `buildPromptFor` and `run.js` dispatch generically. Only - `ship`, `workflow`, `feature`, `next` have bespoke builders. -- **`buildPromptFor(agent, ticketIds, ctx)` is the one prompt door.** The CLI and the - orchestrator both go through it, so they can never disagree about what an agent is - told. Anything that builds a prompt elsewhere is a bug waiting to happen. -- **`executeGoAction(argv, ctx)` is the one action door.** `brief.nextAction.argv` is - machine-readable; the CLI executes it by re-invoking itself, the server maps the same - argv onto the orchestrator. Same reason. -- **`route(method, pattern, handler)`** in `server.js` is a hand-rolled matcher — - `:param` becomes `([^/]+)`. Routes are matched in registration order and core routes - are registered before extensions. Extension routes are forced to start with - `/api/pro/`. -- **Nothing in the server throws to the client.** Handlers wrap in try/catch and return - `{ error, details }`. Logging and SSE writes are wrapped so they can never crash a run. -- **The classic UI is dependency-free ES modules** served straight from - `templates/dashboard/`, with mtime cache-busting injected into the HTML at serve time. -- **Overlay everywhere.** Wherever you read `X.ext`, also read `X.local.ext` and let it - win. `isUserOwned` in `lib/template.js` decides what upgrade may overwrite. - -## Data / Storage - -| Path | What | -|---|---| -| `.bobbyrc.yml` | project config; `readConfig` merges over `DEFAULTS`, deep-merging `git_conventions` and `dashboard` | -| `.bobby/tickets/TKT-NNN--slug/` | `ticket.md` (frontmatter = the record), `plan.md`, `test-cases.md` | -| `.bobby/tickets/.counter` | last claimed number. IDs are claimed by `fs.mkdirSync` — EEXIST means another agent won the race, retry the next number (`lib/counter.js`). **Never write this file by hand.** | -| `.bobby/workspaces.json` | workspace store; tmp-file + rename so a crash mid-write leaves the old state intact | -| `.bobby/sessions/*.jsonl` | append-only run logs, tailed into SSE | -| `~/.bobby/projects.yml` | machine-wide project registry | -| `~/.bobby/licenses.yml` | Pro key (ed25519, verified offline) | -| `~/.bobby/pro/node_modules/` | where `bobby pro install` puts paid packages | -| `~/.bobby/remote/.yml` | remote pairing, mode 0600, **never in the repo** | - -No migrations. Schema changes are frontmatter changes; `readConfig` defaults and -tolerant readers are the compatibility layer. - -## Remote (`lib/remote/`) - -- `crypto.js` — AES-256-GCM per frame, fresh 12-byte nonce, `nonce|tag|ciphertext` - base64url. The 256-bit key travels to the phone *inside the pairing code* (QR or - paste), never through the relay. No handshake, no accounts, nothing server-side. -- `tunnel.js` — one outbound WebSocket; nothing listens on the network. The phone may - reach **only** `/^\/api\/[A-Za-z0-9/_.-]*$/` with **only** GET or POST. Frame verbs: - `req` / `sub` / `unsub` in, `res` / `ev` / `end` / `hi` out. A frame that fails - decryption is dropped in silence. -- `pairing-store.js` — one channel **per project root** (the filename is a hash of the - resolved root), not per machine. `--new-code` rotates and cuts old phones off. - -## Testing Approach - -- `npm test` → `NODE_OPTIONS='--experimental-vm-modules' jest`. `npm run lint` → eslint. - CI runs lint then test on Node 18/20/22 (`.github/workflows/ci.yml`). -- `jest.config.js` sets `roots: ['/test']` deliberately — starter templates - under `templates/starters/**` carry `node:test` files that must not be discovered. -- `test/setup.js` sets `BOBBY_NO_REGISTRY=1` (so tests never touch the real - `~/.bobby/projects.yml`) and a git identity (so worktree/commit tests pass on a bare - runner). Studio tests override `HOME` instead. -- Style: real temp dirs (`fs.mkdtempSync`) and real `git`, not mocks. `test/e2e/` - shells out to `bin/bobby.js` against a scaffolded project. -- `runAgent` takes a `spawn` override so executor tests never launch a real CLI. - `Orchestrator` methods that need no I/O are tested by constructing a bare prototype - (`test/lib/dashboard/orchestrator-pipeline.test.js`) — do the same rather than - building a full orchestrator. -- No Docker. No browser suite yet — committing one is TKT-016. - -## Critical Files - -| File | Why it matters | -|---|---| -| `lib/dashboard/orchestrator.js` | workspace lifecycle, the FSM, stage detection, approve/reject/merge/discard | -| `lib/dashboard/worktree.js` | every git operation; `mergeToMain` is the one thing that mutates the main checkout | -| `lib/dashboard/executor.js` | argv per CLI flavour, stream-json line parsing, SIGTERM→SIGKILL | -| `lib/dashboard/server.js` | ~31 routes, static serving, the extension seam, SSE wiring | -| `lib/dashboard/state.js` | workspace shape, statuses, atomic persistence, restart reconciliation | -| `lib/dashboard/actions.js` | the server-side twin of `bobby go` | -| `lib/workflow.js` | workflow resolution + **every** prompt builder (`buildPromptFor`) | -| `lib/agent-registry.js` | the list of agents and their dispatch flags | -| `lib/tickets.js` | ticket CRUD, stage moves, epic auto-advance | -| `lib/config.js` | `readConfig`, `findProjectRoot`, `findMainWorktreeRoot`, `resolveTicketsDir` | -| `lib/brief.js` | "where was I" + `nextAction` — the brain behind `bobby go` and `/api/go` | -| `commands/app.js` | server wiring and the App-UI seam | -| `lib/remote/tunnel.js` | the phone trust boundary | -| `.bobby/design/design-spec-feature-view.md` | the App's visual contract (light-only) | - -## Common Pitfalls - -1. **Tickets are shared state; only CODE is isolated per worktree.** `resolveTicketsDir` - sends every CLI write to the **main checkout**, and since TKT-051 the orchestrator - reads there too — prompt building, the existence check, feature children, and the - stage re-read on exit all go through `this.ticketsDir`. A worktree's own - `.bobby/tickets` is a checkout frozen at fork time; nothing may read it. It used to, - which is why a ticket not merged to main could not be run at all (`Ticket X not - found`) and why no real agent's stage change was ever detected. `/api/features/:id` - and `/api/workspaces/:id/feature` now return the same data from the same board; the - latter is a workspace-keyed alias kept only because the Pro UI calls it first. - -2. **Tickets always resolve to the main worktree root, whatever directory you are in.** - `resolveTicketsDir` / `resolveSessionsDir` call `findMainWorktreeRoot` and redirect. - Run `bobby ticket create` inside a worktree and the ticket appears in the main - checkout's board, on the main checkout's branch. (`findProjectRoot` itself does not - do this — it just walks up to the nearest `.bobbyrc.yml`.) - -3. **New worktrees fork from `main`/`master`, not from your current branch.** - `Orchestrator.createWorkspace` calls `createWorktree` without `baseBranch`, so - `detectMainBranch(repoRoot)` decides. Anything not yet on `main` — including this - file — is invisible to every agent the app launches until it lands there. - -4. **A stage is not done when the agent exits.** Success = exit 0 **and** a stage change - read from the shared board. Status then sits at `awaiting_approval` until `approve()` (or - a `dashboard.auto_approve_stages` entry) advances it. Do not treat `run_end` as - completion. - -5. **`mergeToMain` mutates the main checkout: stash → `checkout main` → `merge --no-ff` - → stash pop.** Anything else reading or writing the main checkout during those - seconds sees a different branch and possibly a stashed tree. This is exactly the race - TKT-014's repo-run guard has to close, and the reason `merge()` refuses a running - workspace. - -6. **`_resolveNextAgent` may be off by one — verify before relying on it.** `_onExit` - writes `workspace.stage = ` the stage the ticket **moved to** (`bobby-plan` ends with - `bobby ticket move build`, so `stage: 'building'`). `_resolveNextAgent` then - takes `pipeline[indexOf(ws.stage) + 1]`, yielding `review` — skipping `build`. - `lib/workflow.js` `resolveNextAgent()` uses the other convention (stage → the agent - that works *on* it) and would yield `build`. The unit test encodes the `+1` form, so - the two readings of `ws.stage` are genuinely in conflict. Decide the convention - before adding logic on top. - -7. **Dead constants in the orchestrator.** `AGENT_STAGE_MAP` and `PIPELINE_ORDER` are - declared and never read. Do not treat them as the source of truth for stage mapping. - -8. **Nothing counts or caps concurrent agents.** `runningProcesses` is a `Map` guarding - only "this workspace is already running". A cap is TKT-015 and must count workspace - runs *and* repo runs. - -9. **The local server has no authentication.** 127.0.0.1 by default, with an explicit - warning on any other host: whoever reaches it can run agents as you. Do not add a - route that widens that surface, and remember `RemoteTunnel` re-exposes every `/api/*` - GET/POST to a paired phone. - -10. **This project's `default` workflow ends at `review`, not `test`.** `.bobbyrc.yml` - overrides it: bobbycode is a CLI library with no live app, and the built-in `test` - stage forbids running specs, so tickets would strand in `testing`. TKT-013 tracks a - built-in `library` workflow upstream. - -11. **Never hand-write `.bobby/tickets/.counter` or a ticket directory.** IDs are - claimed by atomic `mkdir`; a hand-edited counter reintroduces the collision it - exists to prevent. - -12. **Extension routes must start with `/api/pro/`** or `register()` throws (and the - plugin is skipped with a console error — a paid add-on can never take the free - dashboard down). - -## Branch topology (as of 2026-08-07 — read before you trust a path) - -Three lines of work have diverged and no single checkout has all of it: - -- `feat/bobby-app` (worktree `/Users/ccevans/Repos/bobby-wt/bobby-app`) — **this file's - subject.** Has `commands/app.js`, the App seam, `bobby remote`, per-workspace - workflows, the design specs. -- `feat/workspace-projects` (main checkout `/Users/ccevans/Repos/bobbycode`) — the live - board (TKT-001…046, including TKT-014…017). Has `lib/blueprint*.js`, `lib/registry.js`, - `lib/skills.js`, `commands/studio.js`, and a *different* `lib/studio.js` (studio = - many projects over a shared repo group). **Has no `commands/app.js`.** -- `feat/multi-harness` and `feature/tkt-00*` worktrees — the codex/opencode/agents-md - target adapters. `lib/targets/` here is still only claude-code / cursor / cline. - -Because of pitfall 2, the board you see from any of these is the main checkout's. -Re-run `bobby run arch` once these merge. diff --git a/.bobby/decisions.yaml b/.bobby/decisions.yaml deleted file mode 100644 index f1d5544..0000000 --- a/.bobby/decisions.yaml +++ /dev/null @@ -1,316 +0,0 @@ -# Bobby Architectural Decision Log -# -# Seeded by `bobby run arch`. Appended by `bobby decision add` — use the command -# rather than editing this file by hand. Hand-edits are how entries lost their -# trailing keys and how this format block got deleted once already (TKT-063). -# -# bobby decision add \ -# --id no-direct-db-in-components \ -# --fact "Never call the database directly from UI components." \ -# --why "Breaks separation of concerns and makes testing hard. Use a service layer." \ -# --ticket arch -# -# Entry format — the command writes all seven keys for you: -# id: kebab-case unique identifier -# fact: the decision or constraint as a declarative statement -# decided: ISO date (defaults to today) -# ticket: TKT-XXXX if traceable, "arch" for structural decisions -# why: the reason — include the incident or constraint that drove it -# supersedes: id of the decision this replaces (null if none) -# invalidated: ISO date this decision was overturned (null if still active) -# -# Which looks like this on disk: -# -# - id: no-direct-db-in-components -# fact: "Never call the database directly from UI components." -# decided: "2025-01-01" -# ticket: arch -# why: "Breaks separation of concerns and makes testing hard. Use a service layer." -# supersedes: null -# invalidated: null -# -# To retire a decision: set its `invalidated` to today's date, then add the -# replacement with `--supersedes `. -# -# `bobby run arch` seeds this file; `bobby-review` checks changed code against -# every entry whose `invalidated` is still null. ---- -- id: decisions-log-has-one-writer - fact: "Entries in .bobby/decisions.yaml are appended only by `bobby decision add`, which round-trips the parsed YAML document. Agents never edit the file directly. Both `bobby init` and studio project creation seed it from templates/bobby/decisions.yaml, so there is one schema — a bare top-level list." - decided: "2026-08-09" - ticket: TKT-063 - why: "The log had no writer at all: init seeded it, bobby-review read it in prose, and every entry was hand-typed by an agent. The seed's own header pointed them at `bobby learn`, which has never opened the file. Freehand edits to a structured file did what they always do — one append deleted the commented format block that documented the entry shape, the next had to repair the previous entry's missing supersedes/invalidated keys. The format documentation now lives above the '---' marker where an append cannot reach it. lib/studio.js separately seeded 'decisions: []' (a mapping) while init seeded a list, two shapes under one filename that nothing noticed because nothing parsed the file; legacy mapping files are still read and appended in place rather than rewritten. bobby decision add deliberately does NOT call autoSync — that stages every Bobby-managed path and would sweep up unrelated in-flight work (TKT-061)." - supersedes: null - invalidated: null -- id: one-frontend-two-transports - fact: "Views speak to the machine only through the transport seam — request(method, path, body) → {status, body}, subscribe(path, onEvent) → unsubscribe, on('status'|'presence', fn). LocalTransport and RelayTransport are the only implementations, and no behavioural difference between them may be observable from a view: same resolution shape, same error semantics, and streams that recover on their own in both." - decided: "2026-08-09" - ticket: TKT-023 - why: "One frontend serves the desktop and the phone (TKT-023) precisely because the views cannot tell which side of the seam they are on. HQ's separate frontend existed only because this seam did not — any transport-observable difference recreates the drift the seam was built to end." -- id: worktree-per-workspace - fact: "Every dashboard/app workspace runs in its own git worktree on its own branch; nothing an agent does touches the main checkout until an explicit merge." - decided: "2026-07-24" - ticket: arch - why: "Parallel agents in one checkout collide on the index and on each other's edits. Isolation also means every run produces a reviewable branch diff instead of unreviewable in-place changes, which is what the approve gate reviews. Evidence: lib/dashboard/worktree.js header; Orchestrator.createWorkspace." - supersedes: null - invalidated: null - -- id: worktrees-fork-from-main - fact: "Workspace worktrees are always created from the repo's main/master branch, never from the branch you are standing on." - decided: "2026-07-24" - ticket: arch - why: "Orchestrator.createWorkspace calls createWorktree without baseBranch, so detectMainBranch(repoRoot) decides. It makes every workspace diff comparable against one base — and it means unmerged work (including .bobby/architecture-wakeup.md itself) is invisible to every agent the app launches until it lands on main." - supersedes: null - invalidated: null - -- id: tickets-resolve-to-main-worktree - fact: "resolveTicketsDir and resolveSessionsDir always redirect to the MAIN worktree's copy, whatever directory the command was invoked from." - decided: "2026-07-24" - ticket: arch - why: "One board per repository. Without it, every worktree would fork the ticket board and IDs would collide. The cost is a real trap: `bobby ticket create` run inside a worktree writes to the main checkout's branch, and `bobby ticket move` cannot reach an agent that is re-reading its own worktree copy. Evidence: lib/config.js findMainWorktreeRoot / resolveTicketsDir." - supersedes: null - invalidated: null - -- id: stage-advance-is-the-success-signal - fact: "An agent run counts as successful only when it exits 0 AND the ticket's stage in the worktree differs from the workspace's recorded stage; a clean exit with no stage change returns the workspace to idle." - decided: "2026-07-24" - ticket: arch - why: "The executor stream is passed through untouched and nothing downstream inspects it, so exit code is the only signal the CLI gives — and an agent that did nothing also exits 0. Reading the stage off disk is the one check that distinguishes work from a no-op. Evidence: Orchestrator._onExit; executor.js header." - supersedes: null - invalidated: "2026-08-08" - -- id: orchestrator-reads-tickets-from-the-shared-board - fact: "The orchestrator reads every ticket through the resolved, main-worktree-rooted tickets dir (`this.ticketsDir`) — prompt building, the existence check, feature children, and the stage re-read on exit. A worktree's own .bobby/tickets is never consulted. A run is successful only when it exits 0 AND the ticket's stage ON THAT SHARED BOARD differs from the workspace's recorded stage." - decided: "2026-08-08" - ticket: TKT-051 - why: "Tickets are shared state; worktrees isolate CODE only. That is not a new choice — resolveTicketsDir has always redirected to the main checkout, so `bobby ticket move` run inside a worktree writes there and an agent has no way to write its own worktree's copy. The orchestrator contradicted it in two places and both were bugs. Reading the worktree when BUILDING the prompt meant any ticket not merged to main threw `Ticket X not found` — i.e. every ticket created on a feature branch, the normal way anyone works. Reading it when DETECTING advancement was worse and silent: the copy is a checkout frozen at fork time, so stageAdvanced was always false for a real run, awaiting_approval was unreachable, and the approve → next-agent chain had never once fired outside tests whose stub wrote into the worktree file. The trade-off accepted: two workspaces on one ticket now see each other's stage moves, and a run's ticket state is not rolled back by discarding its worktree — the price of one board per repository, which is the same trade-off tickets-resolve-to-main-worktree already made. Evidence: Orchestrator.runAgent/_onExit/featureProgress/_requireTicket; test/lib/dashboard/orchestrator-fsm.test.js, whose fake agent now moves tickets only via moveTicket on the shared board." - supersedes: stage-advance-is-the-success-signal - invalidated: "2026-08-16" - -- id: prompts-name-the-tickets-dir-absolutely - fact: "Every generated agent prompt names the tickets directory as the RESOLVED, main-worktree-rooted ABSOLUTE path — `ticketsPath` in buildPromptFor's ctx, supplied as `this.ticketsDir` by the orchestrator and `resolveTicketsDir(root, config)` by the CLI. Never `config.tickets_dir`. Prompts are therefore machine-specific, which is safe: they are built per run, handed to a local subprocess, and never stored or shared." - decided: "2026-08-08" - ticket: TKT-052 - why: "TKT-051 made the orchestrator READ tickets from the shared board, so a run starts for an unmerged ticket — but buildPromptFor still received the relative `.bobby/tickets`, so step 1 of every agent prompt (\"read the ticket\") pointed at a path that does not exist in the agent's cwd. The agent's cwd is its worktree; a worktree forks from main and has no copy of a ticket created on a feature branch, so the agent started blind on exactly the tickets the app exists to run. Two alternatives were rejected. Copying the ticket folder into the worktree keeps the sandbox real but goes stale mid-run and gives the agent a second copy it can write and lose. Instructing `bobby ticket view` instead of a file path is the shape of the TKT-048 fix and was the ticket's own preference, but it is read-ONLY: bobby-plan writes plan.md and test-cases.md and bobby-build writes and deletes progress.md, and no CLI covers those writes. An absolute path is not a new hole in the isolation model — resolveTicketsDir already sends every `bobby ticket` command to the main root (tickets-resolve-to-main-worktree), so the prompt now merely states what was already true: tickets are shared state, worktrees isolate CODE only. The trade-off accepted: the sandbox boundary is advisory for the ticket folder specifically, and a prompt is no longer portable between machines. The CLI path was moved to the same absolute value even though its relative path happened to work (cwd is usually the main checkout) — one behaviour beats two. Evidence: lib/workflow.js buildPromptFor; Orchestrator.runAgent; commands/run.js, commands/sprint.js; test/lib/dashboard/orchestrator-fsm.test.js and test/e2e/lifecycle.test.js, which open the file the prompt names rather than matching its text." - supersedes: null - invalidated: null - -- id: approval-gate-before-next-agent - fact: "The next agent never starts automatically; a run that advanced a stage parks in awaiting_approval until approve() or a dashboard.auto_approve_stages entry releases it." - decided: "2026-07-24" - ticket: arch - why: "Every agent run spends real tokens on the user's own Claude subscription, and a wrong plan multiplied down a workflow is expensive. The human gate is opt-out per stage, not opt-in per run. Evidence: Orchestrator._onExit / approve; the App's confirm sheet is the same guardrail on the client." - supersedes: null - invalidated: null - -- id: merge-mutates-the-main-checkout - fact: "mergeToMain stashes the main checkout, checks out main, merges --no-ff, then pops — so the main checkout changes branch and working tree for the duration of a merge." - decided: "2026-07-24" - ticket: arch - why: "A no-ff merge in the real checkout is what makes the result inspectable with ordinary git afterwards, and the auto-stash stops a merge clobbering in-progress work. The consequence is a hard invariant: nothing else may read or write the main checkout while a merge runs. merge() already refuses a running workspace for this reason; TKT-014's repo runs need the same guard." - supersedes: null - invalidated: null - -- id: one-prompt-door - fact: "buildPromptFor() in lib/workflow.js is the only place an agent prompt is constructed, and executeGoAction() is the only place a brief nextAction is executed." - decided: "2026-07-24" - ticket: arch - why: "The CLI and the local server are two front ends over one engine. When each built its own prompt or its own action mapping they drifted, and a stage meant something different depending on where you started it. One function each means the CLI and the app can disagree about display but never about meaning. Evidence: lib/dashboard/actions.js header." - supersedes: null - invalidated: null - -- id: agents-live-in-the-registry - fact: "A new agent is an entry in AGENT_REGISTRY with promptHeader + promptSteps; dispatch flags (requiresTicket, cowork, freeform, custom) drive everything else." - decided: "2026-07-24" - ticket: arch - why: "Adding an agent used to mean touching the registry, the prompt builder, and the CLI dispatch. The generic builder collapsed that to one edit, which is why only ship/workflow/feature/next still have bespoke builders." - supersedes: null - invalidated: null - -- id: ticket-ids-claimed-by-mkdir - fact: "Ticket IDs are claimed by fs.mkdirSync on the ticket directory; EEXIST means another agent won the race and the next number is tried. .counter is a hint, repairable from directory names." - decided: "2026-07-24" - ticket: arch - why: "Multiple agents create tickets concurrently (bobby-ux and bobby-pm both file findings). A read-increment-write counter loses IDs under that; mkdir is the atomic primitive the filesystem already provides. Never hand-write .counter or a ticket directory — that reintroduces the collision. Evidence: lib/counter.js." - supersedes: null - invalidated: null - -- id: paid-code-never-ships-in-the-mit-package - fact: "No paid capability's code is in bobbycode. Paid features live in separately distributed packages (@bobbycode/pro-dashboard) loaded through the plugins seam; the App UI is one of them." - decided: "2026-07-29" - ticket: arch - why: "Verbatim from lib/dashboard/plugins.js: gating code that ships in an MIT npm tarball is theatre — the source is on disk and `npm i bobbycode@` is a version pin away. Distribution is the lock, not the license check. Corollary the code enforces: everything already published as MIT stays free forever, so a paywall may only ever be net-new content." - supersedes: null - invalidated: null - -- id: extension-routes-namespaced-and-fail-soft - fact: "Dashboard extensions may only register routes under /api/pro/, and a plugin that throws during register() is skipped with a console error rather than taking the server down." - decided: "2026-07-29" - ticket: arch - why: "Reserving the namespace means a future core route can never collide with a paid add-on's, and core routes are registered first so they always win the match. Fail-soft because a paid add-on must never be able to break the free dashboard the user is entitled to. Evidence: lib/dashboard/server.js plugin loop; loadDashboardPlugins never throws." - supersedes: null - invalidated: null - -- id: local-server-is-loopback-and-unauthenticated - fact: "The dashboard/app HTTP server has no authentication and binds 127.0.0.1 by default; binding elsewhere prints an explicit warning." - decided: "2026-07-24" - ticket: arch - why: "Every /api/* verb can start an agent with write access to the repo, so reachability IS authorization. Rather than build an auth system for a single-user local tool, the surface is kept on loopback and the phone case is solved by an outbound encrypted tunnel instead of by opening a port. Evidence: commands/app.js host warning." - supersedes: null - invalidated: null - -- id: relay-is-a-dumb-pipe - fact: "bobby remote opens ONE outbound WebSocket; every frame is AES-256-GCM under a key delivered out of band inside the pairing code, and the tunnel accepts only GET/POST on /^\\/api\\/[A-Za-z0-9\\/_.-]*$/. Inference never leaves the user's machine or subscription." - decided: "2026-07-31" - ticket: arch - why: "The channel can run agents on the user's machine, so the relay operator must not be a party you have to trust. No handshake, no accounts, nothing stored server-side; a frame that fails authentication is dropped in silence so a prober learns nothing. Nothing listens on the network, so NAT stays closed. Evidence: lib/remote/crypto.js and tunnel.js headers." - supersedes: null - invalidated: null - -- id: pairing-key-lives-in-home-per-project - fact: "A remote pairing is stored at ~/.bobby/remote/.yml with mode 0600 — one channel per project root, never inside the repo." - decided: "2026-07-31" - ticket: arch - why: ".bobby/ is committed on purpose (tickets are meant to be shared), so a channel key placed there would eventually be pushed. Keying the file by project root also means each project gets its own channel and `--new-code` can cut old phones off one project at a time. Evidence: lib/remote/pairing-store.js." - supersedes: null - invalidated: null - -- id: overlay-shipped-vs-local - fact: "For every scaffolded file, X.ext is Bobby's and is regenerated on upgrade; X.local.ext is the user's, seeded once and never written again. Readers must load both and let .local win." - decided: "2026-07-27" - ticket: arch - why: "Upgrades used to overwrite customized skills and agent files silently, destroying work with no diff to notice. The contract is pinned by test/lib/overlay.test.js precisely because breaking it is invisible until a user's edits are already gone." - supersedes: null - invalidated: null - -- id: app-ui-is-light-only - fact: "The Bobby App UI is light-only — no dark mode and no dark panes, including the live log and the diff, which are drawn as recessed #F5F5F5 instrument panels." - decided: "2026-08-07" - ticket: TKT-010 - why: "CC: 'I hate dark mode', and the vetted section of the spec drops dark mode entirely. The log/diff panes were argued for as instruments, but two black rectangles on an otherwise white page read as the parts nobody converted — which is what they were. #F5F5F5 over #EDEDED is a measurement, not a preference: on #EDEDED the diff's --ok lands at 4.57:1 and log context at 4.36:1, under AA. Evidence: .bobby/design/design-spec-feature-view.md." - supersedes: null - invalidated: null - -- id: app-motion-is-near-none - fact: "The App has one 120ms background-color transition on row hover. No transforms, no pulsing status dots, no shadows, no gradients, no tinted status fills." - decided: "2026-08-06" - ticket: arch - why: "Five reference teardowns all record 'no pulse'; pulsing was retired by construction rather than by taste. Shadows and tinted fills were dropped in the same pass — CC on the richer options: 'little too much'. Evidence: .bobby/design/design-spec-feature-view.md, Motion and Vetted sections." - supersedes: null - invalidated: null - -- id: app-type-floor-13px - fact: "Nothing in the App renders below 13px, and blue (#467AF6) is never a button fill — it means live / needs attention only." - decided: "2026-08-06" - ticket: arch - why: "The reference runs 12px; our floor wins, and the spec logs the consequences it forces (57px ticket rows rather than the reference's 44px) as deviations rather than silently absorbing them. Blue is the single accent and is reserved for state; primary buttons are --btn #363636. Evidence: .bobby/design/design-spec-feature-view.md, Type and Colour." - supersedes: null - invalidated: null - -- id: bobbycode-default-workflow-ends-at-review - fact: "In this repository, the `default` workflow is [plan, build, review] — the built-in trailing `test` stage is removed." - decided: "2026-08-05" - ticket: TKT-013 - why: "bobbycode is a CLI library with no live app. The built-in test stage verifies through a running app and its skill forbids running specs, so it has nothing to observe and would strand every ticket in testing. review runs the suite independently, which is the correct verification for a library. TKT-013 tracks a built-in `library` workflow upstream so other CLI projects need no override. Evidence: .bobbyrc.yml workflows block." - supersedes: null - invalidated: null - -- id: workspace-stage-is-the-stage-now-in - fact: "`workspace.stage` means the stage the ticket is NOW IN — the stage whose agent runs next — never the stage the ticket just left. The next agent is therefore a direct lookup (the step whose stage === workspace.stage), never an index offset." - decided: "2026-08-08" - ticket: TKT-047 - why: "Orchestrator._onExit copies the stage straight off the worktree's ticket.md, and every agent hands the ticket to the stage the NEXT agent works in (plan lands it in building, build lands it in reviewing). The two conventions were mixed: _onExit wrote 'the stage now in' while _resolveNextAgent read it as 'the stage just completed' and returned pipeline[idx + 1], which skipped an agent at every step of the default workflow and returned null after review — reporting ready-to-merge without test ever running. lib/workflow.js resolveNextAgent already did the direct lookup, so the orchestrator now delegates to it and there is one definition of the convention. Evidence: Orchestrator._resolveNextAgent; test/lib/dashboard/orchestrator-fsm.test.js." - supersedes: null - invalidated: null - -- id: forward-stage-comes-from-the-workflow-not-the-agent-file - fact: "An agent file may never name the stage it moves a ticket forward to; that stage is computed from the ticket's resolved workflow and injected into the agent's task prompt, which wins over anything the agent file or its skill says. Non-forward transitions (reject, block, done) stay as literals." - decided: "2026-08-08" - ticket: TKT-048 - why: "A literal is only correct for `default`. On `quick` (planning -> building -> testing) a bobby-build that hardcodes `move {ID} review` parks the ticket in a stage the workflow does not contain, the stage lookup misses, and the orchestrator declares the run finished — so a quick feature never tests and still reports ready-to-merge. Same breakage for `secure` and every user workflow under `workflows:`. nextStageForAgent derives the target from the AGENT's position in the workflow rather than the ticket's current stage, so a build re-run after a rejection still targets correctly. Evidence: lib/workflow.js nextStageForAgent / buildSingleAgentPrompt; templates/agents/*.md.ejs Handoff sections." - supersedes: null - invalidated: null - -- id: concurrency-cap-refuses-per-server-process - fact: "dashboard.max_concurrent (default 4) caps agents in flight per Orchestrator — one per `bobby app`/`bobby dashboard` process serving one repo, so per project per server process. Exceeding it REFUSES the run with an error naming what is already running; it never queues." - decided: "2026-08-08" - ticket: TKT-015 - why: "Every running agent is a CLI subprocess spending real tokens on the user's own subscription, and a mis-click on a large epic could start ten. A queue is worse than a refusal here: it starts work minutes later, unattended, after the user has forgotten they asked. Refusal keeps the human in the loop, so the message names the holders of the slots and the config key to raise. Known gap: nothing coordinates across processes, so two servers on the same repo each get their own budget. Evidence: Orchestrator._assertConcurrencyHeadroom; surfaced as a 400 through POST /api/workspaces/:id/run." - supersedes: null - invalidated: "2026-08-16" - -- id: main-checkout-guarded-by-a-lock-file - fact: "Anything that touches the main checkout's working tree takes an exclusive lock at `.bobby/main-checkout.lock` — repo runs (kind 'repo', no worktree) and merges (mergeToMain stashes and swaps branches there). Ordinary worktree runs never take it. The lock is reclaimed when EITHER the holder's pid is dead on this host OR the record is older than 6 hours; failure to acquire is a refusal naming the holder, never a queue." - decided: "2026-08-08" - ticket: TKT-014 - why: "TKT-014 gave freeform agents (ux, pm, qe, docs, arch, ship, design-*, performance, watchdog) a path through the app, and they have no worktree — they work in the main checkout, which is exactly the directory mergeToMain stashes, checks out main in, and merges into (`merge-mutates-the-main-checkout`). Overlap is not a glitch, it is an agent's half-written files being stashed and the branch swapped underneath it. A file rather than an in-process mutex because Bobby is a CLI AND an app: a module-level guard is scoped to one Node process, so it cannot see a second `bobby app` serving the same repo. That does not close the gap `concurrency-cap-refuses-per-server-process` records — two servers still get two token budgets — but it does coordinate across processes the one case where a collision corrupts a working tree rather than merely costing money. Same reasoning that made ticket IDs an atomic `mkdir` claim rather than an in-memory counter. BOTH staleness checks are needed and neither alone: pid liveness reclaims the lock the instant a crashed holder dies, so a `kill -9` does not cost the repo for hours, but it means nothing for a lock written by another host and can be fooled by pid reuse — the age ceiling is the backstop for exactly those two. The recorded pid is the SERVER's, not the agent subprocess's, because the server is what will release it. Worktree runs are deliberately outside the lock: they never touch the main checkout, and blocking them would make the app feel broken for its main case. Two known limitations, both accepted rather than missed. (1) `bobby run ux` on the CLI only PRINTS a prompt and exits, so it cannot hold the lock for the agent a human then runs — the lock covers what Bobby itself launches. (2) Taking a FREE lock is atomic (`wx` creates only if absent, so exactly one of two racers wins); RECLAIMING a stale one is not, because read-judge-delete-create has a window in which two processes can reach the same verdict about the same corpse. Closing it needs an acquire that can wait and re-verify, i.e. an async lock, to protect a case that only arises after something died holding it. Evidence: lib/dashboard/main-checkout-lock.js; Orchestrator._runInMainCheckout and merge; test/lib/dashboard/repo-run.test.js." - supersedes: null - invalidated: null - -- id: repo-runs-have-no-worktree-so-worktree-verbs-mean-something-else - fact: "A repo run is `kind: 'repo'` on the same workspace record. diff/files read the main checkout's working tree against HEAD instead of a branch against main; merge, approve, reject and featureProgress refuse with a reason; discard drops the record ONLY and never reverts the edits; and no checkpoint commit is ever made." - decided: "2026-08-08" - ticket: TKT-014 - why: "Each verb was decided, not defaulted. Checkpointing is the sharpest: commitCheckpoint runs `git add -A && git commit`, harmless bookkeeping in a throwaway worktree but a sweep of the user's unrelated in-progress work into a commit on whatever branch they are standing on if aimed at the main checkout. discard is the second: the run's output IS the working tree now, so a 'discard' that reverted it would be a delete button disguised as a cleanup. merge/approve/reject/featureProgress have no meaning without a branch or a ticket and say so, because a route that throws a stack trace teaches the user nothing. diff/files DO have a meaning — the same question asked of the only place the work exists — so they answer it rather than refuse; untracked files are listed with null counts because `git diff` cannot show content git has never seen and `add -N` would mutate the user's index to fake it. `kind` is an explicit field: nothing infers repo-ness from a null worktreePath, and records written before it existed default to 'worktree', which is what they all were. Evidence: lib/dashboard/state.js newRepoRun/isRepoRun; Orchestrator._onRepoRunExit, discard, getDiff." - supersedes: null - invalidated: null - -- id: auto-sync-scoped-paths - fact: "autoSync stages only caller-declared paths, not all Bobby-managed paths." - decided: "2026-08-08" - ticket: TKT-061 - why: > - autoSync previously staged all Bobby-managed paths (getBobbyPaths), which silently committed unrelated in-flight work by other agents. New callers must declare which files they wrote. Forgetting to pass a path means that file won't be auto-committed — preferable to sweeping up someone else's work. - supersedes: null - invalidated: null - -- id: permission-posture-differs-by-run-kind - fact: "Permission posture is set per KIND of run, never once for both. `dashboard.worktree_permission_mode` defaults to 'bypassPermissions' for ticket runs, which happen inside a throwaway git worktree; `dashboard.repo_permission_mode` defaults to 'acceptEdits' for repo runs, which happen in the user's real checkout. Either is overridable, an explicit value always wins, and a null in the config counts as unset. The older single `permission_mode` still overrides both when set." - decided: "2026-08-08" - ticket: TKT-062 - why: "Shipping ONE key meant shipping one wrong answer. Unset, `claude -p` asks before writing, and a headless subprocess has nobody to ask: a real run spent 88 turns, 9m14s and $2.97 being refused, wrote nothing, never moved the ticket, and exited 0. `acceptEdits` got further and still failed — the agent could write but not run bash, so instead of `bobby ticket move` it hand-edited ticket.md inside its own worktree, which the orchestrator correctly ignores (orchestrator-reads-tickets-from-the-shared-board). Only `bypassPermissions` completed plan -> awaiting_approval -> build. THE ASYMMETRY IS THE DECISION, and it is the thing a future agent will most want to 'simplify' back into one setting: a WORKTREE run is safe at bypassPermissions BECAUSE of the isolation — worktree-per-workspace makes that copy disposable, so a wrong agent costs a deleted directory and the main checkout never saw it. A REPO run (TKT-014: ux, arch, docs, ship, …) has no worktree at all; it edits the main checkout by design, so none of that reasoning transfers and the same value there would be granting an unattended agent a shell in the user's real tree. Edits are the line drawn: git can review and revert a file the agent wrote, it cannot undo an arbitrary command. The accepted cost is that a repo agent needing a shell — ship, for one — does not work until the user raises the key on purpose, and the app says so instead of failing silently. Collapsing these back into one key reintroduces either the silent no-op (too strict for worktrees) or an unattended shell in the user's checkout (too loose for repo runs). Evidence: resolvePermissionMode in lib/dashboard/executor.js; DEFAULTS.dashboard in lib/config.js; Orchestrator._launch; test/lib/dashboard/permissions.test.js." - supersedes: null - invalidated: null - -- id: a-run-that-changed-nothing-is-not-completed - fact: "A worktree run that exits 0 having moved no stage AND left its branch on the same commit it started on is recorded as `no_op`, not `completed` — on the run record and on the workspace — with a message naming permission posture and the config key. Three refusals in the executor stream stop the run outright. Repo runs are exempt from the no-op verdict, and 'cannot tell' never counts as 'nothing'." - decided: "2026-08-08" - ticket: TKT-062 - why: "Exit 0 with is_error false is indistinguishable from success at the orchestrator boundary, which is the only reason a $2.97 no-op could be displayed as a finished stage for nine minutes. The head-sha comparison is the check an exit code cannot fake, and it is taken AFTER the checkpoint commit so that work the agent left uncommitted and work it committed itself both count — 'nothing was committed just now' would have called every build agent a no-op. 'no_op' is its own status rather than a flavour of 'failed' precisely because this bug was invisible while a clean exit meant success: 'failed' is a CLI that blew up and says so, 'no_op' is a CLI that reported success and achieved nothing; different causes, different fixes. Repo runs are excluded on purpose — ux, pm and qe are SUPPOSED to write nothing, their output is the report in the log, so a blanket verdict there would cry wolf on every review, and there is no branch of their own to judge. When git cannot say where the branch stood, the verdict is 'not a no-op': a missed no-op costs what the status quo cost, a wrong one tells a user their working agent did nothing. Only clean exits are judged, so a run stopped for refusals keeps the more specific refusal message rather than having it overwritten. Evidence: Orchestrator._producedNothing/_noOpReason/_notePermissionDenial; runOutcome in lib/dashboard/state.js; PERMISSION_DENIAL_PATTERNS in executor.js, taken verbatim from the refusals in .bobby/sessions/ses-20260808-192056.jsonl." - supersedes: null - invalidated: null -- id: one-repo-per-ticket-v1 - fact: "A workspace ships in exactly one repo. If a ticket's `repos` frontmatter names more than one repo, Orchestrator.createWorkspace throws before any worktree or token is spent, naming the ticket, the repos, and the way out (split the ticket, or narrow `repos`). The target repo is resolved once at createWorkspace from ticket.repos, then project_repos[0], then the launch repo (this.repoRoot); resolution is skipped entirely off-studio so the single-repo case is byte-identical. The resolved repoRoot/lockFile are stored on the workspace record and every per-workspace git op (worktree, branch, lock, diff, merge, discard) acts against them, with a `|| this.repoRoot`/`|| this.lockFile` fallback for pre-upgrade records." - decided: "2026-08-12" - ticket: TKT-069 - why: "This ticket (TKT-069) exists to ship in the RIGHT repo. A two-repo ticket has no single right repo; taking the first and proceeding would ship only repo A's work while the ticket claims both — the exact silent-wrong-repo failure this ticket kills (it is what made TKT-023 manufacture a dead branch in the launch repo). Refusing early with an actionable message is the established pattern here (_assertConcurrencyHeadroom, the main-checkout lock's heldMessage, _assertRepoRunnable): refuse before you spend, name what is wrong, name the way out. Take-first was rejected because a workspace that runs anyway is the not-quite-silent half-ship the no-op guard (TKT-062) was added to stop. Two-worktrees-one-workspace is deferred out of v1. Evidence: Orchestrator._resolveTargetRepo/createWorkspace; newWorkspace repoRoot/lockFile in lib/dashboard/state.js; test/lib/dashboard/orchestrator-repo-target.test.js." - supersedes: null - invalidated: null -- id: a-run-is-pinned-to-the-board-it-started-on - fact: "A workspace records the board it was created on (`ticketsDir`/`sessionsDir` on the record) and every per-workspace read goes through `_ticketsDirFor(ws)`/`_sessionsDirFor(ws)` — prompt building, the existence check, feature children, the stage re-read on exit, the session log, the mergedAt stamp. The orchestrator's own `this.ticketsDir` getter stays LIVE and is only for the UI's board and for picking a NEW ticket off it. Records with no pin (single-project dashboards, records written before the field) fall back to the live getter, which is the only board they have." - decided: "2026-08-15" - ticket: TKT-022 - why: "In a studio the live getter moves when the user selects another project, and a run takes minutes — so switching projects mid-run, the exact thing switching is for, moved the board out from under the running agent's bookkeeping. On exit the orchestrator asked the NEWLY selected project's board how the run went: absent id -> newStage null -> stageAdvanced false, and a successful run that had moved its ticket was recorded as a no-op the user could not approve. Where both boards held the same id (two projects, one prefix) it was worse and silent — an unrelated ticket's stage was compared to this workspace's, which can reach ready_to_merge or auto-approve the next agent against the wrong project's board. Session logs split too: header in project A, tail in project B, both files incomplete. This does NOT weaken orchestrator-reads-tickets-from-the-shared-board — the pinned dir IS a shared main-rooted board, and it names WHICH shared board rather than trusting the moment. Evidence: Orchestrator._ticketsDirFor/_sessionsDirFor, createWorkspace's pin, scopeToProject in server.js; test/lib/dashboard/orchestrator-project-pin.test.js, whose alpha run exits while the UI sits on beta." - supersedes: null - invalidated: "2026-08-16" -- id: orchestrator-reads-tickets-from-the-workspaces-own-board - fact: "A worktree's own .bobby/tickets is NEVER consulted — tickets are shared state, worktrees isolate code only. WHICH shared board depends on the reader. Per-workspace reads (prompt building, the existence check, feature children, the stage re-read on exit, session init and logging, mergedAt) go through `_ticketsDirFor(ws)`/`_sessionsDirFor(ws)`: the board pinned on the workspace record at creation, so a studio project switch cannot move it mid-run. Reads that are about the CURRENT VIEW — the API's board, picking a new ticket off it in createWorkspace — go through the live `this.ticketsDir`/`this.sessionsDir` getters, which follow the selected project. Unpinned records fall back to the live getters. A run is successful only when it exits 0 AND the ticket's stage ON ITS OWN BOARD differs from the workspace's recorded stage." - decided: "2026-08-16" - ticket: TKT-022 - why: "Supersedes orchestrator-reads-tickets-from-the-shared-board (TKT-051), whose fact literally named `this.ticketsDir` for four reads that are no longer live — the log contradicted the code, and a reviewer enforcing it verbatim would have flagged the fix that made it wrong. TKT-051's substance is unchanged and restated here: reading the worktree copy broke both ends of a run (prompt unbuildable for any ticket not on main, stageAdvanced always false so awaiting_approval was unreachable). TKT-022 added the studio, where the live getters move when the user selects another project — and a run takes minutes, so switching mid-run pointed the exit bookkeeping at the wrong project's board: a successful run read as a no-op, or, on a shared prefix, an unrelated same-id ticket deciding nextStatus and auto-approving the next agent against the wrong board. The distinction to preserve is not 'shared vs worktree' but 'the workspace's board vs the moment's board' — a new per-workspace read that reaches for `this.ticketsDir` is the bug this decision exists to stop. Absorbs a-run-is-pinned-to-the-board-it-started-on, now invalidated, so there is one active decision on which board is read. Evidence: Orchestrator._ticketsDirFor/_sessionsDirFor and createWorkspace/createRepoRun's pin; scopeToProject and sessionsBoardDir in server.js; test/lib/dashboard/orchestrator-project-pin.test.js and orchestrator-fsm.test.js." - supersedes: orchestrator-reads-tickets-from-the-shared-board - invalidated: null -- id: concurrency-cap-refuses-per-orchestrator - fact: "dashboard.max_concurrent (default 4) caps agents in flight PER ORCHESTRATOR — one per `bobby app` process — and an orchestrator now spans every project in a studio, so the budget is shared across projects, not per project. Exceeding it REFUSES the run with an error naming what is already running; it never queues. The value itself is read live off the ACTIVE project's config, so switching projects can change the cap while runs from another project are still counted against it." - decided: "2026-08-16" - ticket: TKT-022 - why: "Re-recorded, not changed: the cap's behaviour is exactly as TKT-015 set it, but the sentence 'so per project per server process' became false when TKT-022 let one server switch projects. One Orchestrator, one process map, one budget — start three agents on alpha, switch to beta, and only one slot remains, which is correct (they are all subprocesses on the same machine spending the same subscription) but is NOT what the old wording promised. Reading the cap off the active project's config is the deliberate half: a studio where one project sets max_concurrent: 8 should honour that while you are working in it. The known cross-process gap is unchanged — two servers on the same repo still get two budgets. Evidence: Orchestrator._maxConcurrent and _assertConcurrencyHeadroom, now reading the live `config` getter; surfaced as a 400 through POST /api/workspaces/:id/run." - supersedes: concurrency-cap-refuses-per-server-process - invalidated: null -- id: run-scoped-reads-pin-to-the-workspaces-project - fact: "Everything a run does AFTER launch resolves against the project pinned on its workspace record, not the live UI project: the board via _ticketsDirFor(ws)/_sessionsDirFor(ws), and the CONFIG via _configFor(ws) — which feeds permission posture, executor, model, the whole prompt context, auto_approve_stages in _onExit, and _pipelineFor. this.config / this.ticketsDir (the live getters) are only for what the user is looking at: picking a new ticket off the board and creation-time resolution in createWorkspace. A field DERIVED from the boot config in the constructor (this.pipeline = resolveWorkflow(bootConfig)) is not exempt — it must be re-resolved per run through _configFor(ws), never read raw." - decided: "2026-08-16" - ticket: TKT-022 - why: "A studio orchestrator spans every project and the UI can switch mid-run. Any run-scoped read left on the live getter — or on a constructor-captured derivative of it — takes a decision from the project the user happened to switch TO: an agent auto-launched that the run's own project forbids, or a workflow stage (e.g. security) silently skipped. This class recurred across six review rounds (B3/C1/C2/D1/D2); the invariant makes 'is this read run-scoped?' the review question." - supersedes: null - invalidated: null diff --git a/.bobby/design/.gitignore b/.bobby/design/.gitignore deleted file mode 100644 index a514e47..0000000 --- a/.bobby/design/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -# Design research captures — reference material, not source. -# -# The design process screenshots references and its own builds (~180MB of PNGs). -# They are worth keeping locally while a design is in flight, and worth nothing -# in git history forever. What IS committed: the spec, the teardowns that record -# what each reference taught us, and the mockup HTML the build was made from. -# -# If you need the captures, they can be re-gathered — every teardown names its -# source URL. -shots/ -inspiration/ -mockups/shots/ diff --git a/.bobby/design/design-spec-feature-view.md b/.bobby/design/design-spec-feature-view.md deleted file mode 100644 index 29e101d..0000000 --- a/.bobby/design/design-spec-feature-view.md +++ /dev/null @@ -1,412 +0,0 @@ -# Design Spec — Bobby App, Feature view - -**Locked:** 2026-08-06 · **Direction:** "Track to finish" · **Status:** approved by CC -**Scope:** the Feature view (an epic + its child tickets moving through a workflow), -and — since 2026-08-07 — **Home**, the **Board**, **ticket detail** and the -**Workspace** (live log + diff), built as its siblings from the same parts. All five -share one CSS class (`.appview`) rather than parallel copies, so they cannot drift. -This spec does **not** govern the marketing homepage — that is `design-spec.md` -(direction R1 "Stage"), a separate surface with its own tokens. - -Source of truth for the build: `.bobby/design/mockups/devin-white-fin-2.html`. -**Values are copied from this file, never retyped from memory.** - ---- - -## Decided - -| Field | Value | -|---|---| -| **Direction** | "Track to finish" — Devin's white mobile register, pipeline drawn as a route | -| **Reference** | app.devin.ai on iPhone, photographed by CC (4 screenshots, see Provenance) | -| **Canvas** | Phone-first at 390px; app column `max-width: 440px` centred on the ground at any width | -| **Signature move** | A hairline connector runs down through the step glyphs and **terminates in a chequered finish** at Merge. The pipeline is a route, not a checklist. The route's *length* is the epic's own workflow (five steps on `default`, seven on `design`); the connector and the chequered terminus are what do not vary. | -| **Structure** | Top bar → title + repo sublabel → status line → Pipeline → note → decision buttons → Tickets | - -### Colour — every value pixel-sampled from the reference photos - -| Token | Value | Sampled from | -|---|---|---| -| `--ground` | `#F8F8F8` | IMG_5805, ground behind the list container | -| `--surface` | `#FFFFFF` | IMG_5805 list container; IMG_5808 composer | -| `--hairline` | `#E2E2E2` | IMG_5807 card border + chip border | -| `--fill-active` | `#F5F5F5` | IMG_5805 selected session row | -| `--fill-track` | `#EDEDED` | IMG_5808 progress track | -| `--fill-hover` | `#FAFAFA` | derived | -| `--ink` | `#191919` | IMG_5805/5807/5808 primary text | -| `--ink-2` | `#6E6E6E` | **deviation** — sampled `#7D7D7D`, darkened for AA | -| `--blue` | `#467AF6` | IMG_5808 check circle + progress fill; IMG_5805 status dots | -| `--btn` | `#363636` | IMG_5807 "Create automation" fill | -| `--dot-muted` | `#8F8F8F` | **deviation** — sampled `#E2E2E2` ring, lifted to ≥3:1 | - -The greys are **pure neutral (R=G=B)**, not warm. Verified by sampling; do not -"warm them up" — that was an earlier wrong assumption taken from the marketing site. - -Blue is the only accent. It means *live / needs attention*. Black (`--btn`) is for -primary buttons and ink. **The blue is never a button fill.** - -### Type - -- Family: system stack (`-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`). One face for the whole page — reference-backed. -- Body 15px / 1.35 · H1 **22px/600** · row title 14px · sublabel 13px · section heads 14px/600 -- **Floor: 13px.** Nothing smaller anywhere. (Reference runs 12px; our floor wins.) -- `font-variant-numeric: tabular-nums` on all counts, times and IDs. - -### Shape & surface - -- Radii: container `12px` · row `8px` · button `8px` · pill `999px` -- Border: `1px solid var(--hairline)` -- **No shadows anywhere.** No gradients. No tinted status fills. -- **No dividers between ticket rows** — verified by pixel scan of the reference. - Rows are separated by space; the active row carries a `--fill-active` inset fill. - -### The pipeline (the signature) - -- **One step per stage of the epic's own workflow, then `Merge` as the terminus.** - `default` draws the five this spec was written against — Plan · Build · Review · - Test · **Merge**. `secure` draws six, `quick` four, `design` seven (Design - Research · Design Analyze · Design Mockup · Design Spec · Design Build · Design - Check · **Merge**). - The list is vertical, so length costs height and never width — seven steps at - the 34px pitch is 238px, and 390px is unaffected. Nothing is condensed, - wrapped, scrolled or truncated. -- **A step is named after the step, not after the stage it parks a ticket in** — - spelled out as words, never as an id: "Design Research", not `design-research`. - The stage cannot do this job: two `design` steps park their tickets in stages - that are not theirs (`design-build` writes `building`, `design-check` writes - `reviewing` — `STAGE_MAP` in `lib/workflow.js`), so reading the stage would - print "Building" and "Reviewing" in the middle of the design route and lose - two of the seven names listed above. **The rows beneath take their word from - the step**, so one stage is one word everywhere on this view — the pipeline - said "Build" over a row reading "Building · in progress" for every `default` - feature until TKT-059. Off the track — backlog, done, shipping, or a stage - this workflow has no step for — no step has named it and the stage's own - words stand. Every word on the page goes through one formatter (`stageWords`). - The Board cannot do this and does not: it draws every workflow at once, so - there a stage has no single step and the stage's own words are the only - honest name. -- Glyphs, 18px box, 34px row pitch: - - done → filled `--blue` circle, white check - - current → `--blue` ring with `--blue` centre dot - - not started → hollow `--dot-muted` ring, 1.5px - - **finish (Merge) → 20×20 chequer, 5×5 grid at 4px cells, `--btn` `#363636`** -- **"Current" means work sits at this step** — a run is on it, or a ticket is - parked there. More than one step may be current at once, because children can - be spread across stages, and when they are the drawing says so. A step a ticket - is standing on is never drawn "done" and never drawn "not started": that test - runs before the count is consulted, because the pipeline contradicting the rows - an inch beneath it is the one failure this section exists to prevent. - Exactly one step carries `aria-current="step"` — the run's, or the earliest - parked one. -- **The count is steps *cleared*, and a step is cleared only when the least - advanced ticket has left it.** That is the whole rule, and the **tickets** are - what answer it: the count is the minimum over the children, never the epic's - own stage. An epic can run ahead of a child — a send-back, or a run that - advanced the epic while a child stayed put — and when it did, the page put - done checks on steps no ticket had reached and ran the blue road out of a - glyph it was simultaneously drawing as current (TKT-057). A blocked child is - not counted: its row says "Blocked", not "in progress", and one stuck ticket - must not freeze the route at zero. The epic's stage stands in only when no - ticket can answer — no children yet, or none at a stage this workflow has a - step for. Whatever number arrives, it is finally clamped to the first step a - ticket is standing on, so the count, the bar and the blue length cannot - contradict the glyphs even in principle. -- **Where the *run* is is a different number, and it is the epic's own stage.** - That is the field a run advances, so it is what the decision button and the - note read — "Approve — send to review" is a statement about the run, not about - how far the least advanced ticket has got. Keeping the two apart is what lets - the count be honest without the button going wrong; collapsing them into one - number is what produced the contradiction above. -- **Connector:** hairline running down the glyph column, terminating at the chequer. - Completed segments `--blue` (3.68:1); segments ahead `--hairline` (decorative - connective tissue — the glyphs carry state, so it is not a state carrier). - Requires `z-index: 0` on segments, `z-index: 1` on rows, and ground-coloured ring - fills, or the line paints through the glyphs. -- Grid parity matters: **odd grids only.** 5×5 has filled corners and reads as a - flag; 6×6 leaves opposite corners empty and serrates into a diamond. -- Cells on whole-pixel boundaries, `shape-rendering="crispEdges"`. - -### Rows and the headings above them (the Board) - -- **A row names its stage unless the heading directly above it already has.** - Stage is never carried by position or colour alone — but inside a stage lane - the `

` is the stage, so the sublabel opens with the id instead - (`TKT-002`, not `Design Research · TKT-002`). Outside a stage lane the word - stays, because nothing else supplies it: **Blocked** (whose heading is not a - stage), **Features**, Home, and the ticket page. -- **Every section heading on the Board is unique in words and in `id` — across - the whole page, not just among the lanes.** Stage ids come off disk and both - the prettifier and the slugifier are many-to-one, so uniqueness is made rather - than assumed: the `id` takes a numeric suffix on collision, and two lanes that - prettify to the same phrase both fall back to the raw stage string - (`design-spec` beside `Design Spec`). `aria-labelledby` resolves to the first - element with an id — a duplicate is one lane announced as another. - **`Features` and `Blocked` are in that set.** They are the page's two fixed - sections and their ids are constants, so a lane is measured against them too: - a hand-typed stage of `Blocked` slugs onto `lane-blocked-head`, which the - Blocked section already owns, and the lane was announced as that section - (TKT-058). Uniqueness is decided against the sections actually drawn, so a - board with no epics reserves nothing for Features. - Against a fixed section the raw-stage fallback is no help — `Blocked` reads as - `Blocked` however it was written — so there the **lane** is qualified, - `Blocked (stage)`, and the section is left alone: the section is a state the - board groups by, the lane is a stage someone typed, and the qualifier is the - page saying which is which. It never appears on a board nobody has hand-edited. - -### Motion - -Near-none. One `background-color` transition at 120ms linear on row hover. -No transforms. No pulsing status dots — retired by construction -(see `references-feature-view.md`; five teardowns record "no pulse"). - ---- - -## Vetted — from the user - -**Keep** -- White theme, simple layout (CC: *"I like the white theme and simple layout"*) -- The chequered flag as the one racing grace note (CC: *"let's do subtle check flags"*) -- The connector-line treatment (CC chose "Track version" over the bolder flag) - -**Drop** -- Dark mode entirely (CC: *"I hate dark mode"*) — this surface is light-only -- The circuit-line drawing, car dot, sector labels, lap counter, livery stripe (CC: *"little too much"*) -- Cards around status; tinted row fills; left-border stripes - ---- - -## Deviations (each needs a reason) - -- `--ink-2 #6E6E6E` instead of sampled `#7D7D7D` — reference is **4.12:1**, fails AA. -- Not-started ring `#8F8F8F` @1.5px instead of `#E2E2E2` — reference is **1.30:1** and - is the sole carrier of "not started". -- Sublabels 13px instead of reference 12px — below our floor. -- Ticket rows ~57px instead of reference ~44px — consequence of the 13px floor plus - the ≥44px tap-target rule. -- Current-step glyph (blue ring + centre dot) is **invented** — Devin's checklist has - only done and not-started. This is the one glyph without provenance. -- **`--mono` on a literal shell command** (Home's "Next" section, `.cmd`) despite the - one-face rule. The rule bans a decorative display/body pairing; quoting a terminal - string is content, and monospace is what makes the ticket id and the argument - boundaries legible. Exactly one node on the page. Set in `--ink`, not `--ink-2` — - it is the payload of the button beneath it, so it must not be quieter than the - plain-language reason above it. -- **`.btn-quiet` border is `--dot-muted` `#8F8F8F`, not `--hairline`.** The edge is what - identifies the secondary as a control; `--hairline` is 1.22:1 on the ground and left - "Send back" reading as floating text. Same reason the spec already lifts this colour - for the not-started ring. -- **Form-control borders are `--dot-muted` `#8F8F8F`, not `--hairline`** (`textarea`, - `input[type=text]`, `select`). Identical reasoning to `.btn-quiet` one line above, plus - WCAG 1.4.11, which requires 3:1 on the boundary of an input. `#8F8F8F` measures - **3.23:1** on `--surface`; `--hairline` measures 1.22:1 and left a field reading as - empty space. -- **Sheet titles are 15px/600, not the 22px H1.** A sheet is a page head by role, but its - title is prose the caller passes in (`Build ${epic.id} — ${epic.title}?`), which ran to - three lines and 38% of the panel at 22px. Hierarchy is carried by weight and colour - against the `--ink-2` body — which is what the flat 13/14/15 scale is for. -- **Form controls are 15px, below the 16px at which iOS zooms on focus.** 15px is the - spec's body size; 16px is not on the scale. Recorded rather than silently resolved: - the scale wins, and the zoom is the cost. See Open below. - -### The live log and the diff — the instrument decision (TKT-010) - -The Workspace view's two panes were the last near-black surfaces in the app -(`#0b0e11` with syntax tints). A terminal log and a code diff are the one place a dark -surface can honestly be argued for inside a light product: they are instruments, not -documents, and every tool the user already knows draws them dark. - -**Decision: they go light.** The reasoning, in the order it decided the question: - -1. This surface is light *by decision, not by default* — CC: *"I hate dark mode"*, and - the Vetted section drops dark mode entirely. A dark pane is not an exception to a - preference; on a page that has none, it is a second theme. -2. Two black rectangles halfway down an otherwise white page do not read as deliberate - instruments. They read as the parts nobody converted — which is exactly what they - were, and a design that looks unfinished is unfinished. -3. The claim "a log must be dark" is about a *terminal*, where the surface is the - application. Here the log is one section of a page, sitting between a decision block - and a facts grid. Its neighbours set the ground; it does not get to set its own. - -**But not white either.** A wall of log output on `#FFFFFF` is harsh at length, and a -white pane would be the same material as the `.list` containers, which are rows you tap. -The panes are drawn as **quiet instrument panels**, recessed rather than raised: - -| Property | Value | Why | -|---|---|---| -| Surface | `--fill-active` `#F5F5F5` | One step under the `#F8F8F8` ground, so the pane sinks. Not `--surface`: white is the material of things you tap. | -| Edge | `1px solid --hairline` `#E2E2E2` | What separates every container in this system. A white `.list` on the ground measures 1.06:1; fill contrast has never drawn these edges. | -| Radius | `--r-container` `12px` | Container, not row. | -| Type | `--mono`, **13px**/1.5, `tabular-nums` | The floor. The old pane ran 11.5px. | -| Log — context | `--ink-2` `#6E6E6E` | **4.68:1** on `#F5F5F5`. | -| Log — tool calls | `--ink` `#191919` | **16.13:1**. Tool calls are the structural beats of a run, so they take full strength rather than a hue. | -| Log — errors | `--bad` `#B42318` | **6.03:1**. | -| Diff — adds | `--ok` `#0F7B3E` | **4.91:1**. | -| Diff — removes | `--bad` `#B42318` | **6.03:1**. | -| Diff — hunk headers | `--ink` | **16.13:1**. | - -- **`--fill-active` `#F5F5F5` over `--fill-track` `#EDEDED`** — the other honest - candidate, and the choice is a measurement, not a preference. On `#EDEDED` the diff's - `--ok` lands at **4.57:1** and the log's `--ink-2` context lines at **4.36:1**, which is - under the AA floor. On `#F5F5F5` they are 4.91 and 4.68. `#EDEDED` is the token for a - *track a bar runs in*, where nothing is set in type; these panes are nothing but type. - No new token was invented. -- **No tinted add/remove row fills in the diff**, though the brief permitted them. - Three reasons: the spec bans tinted status fills; `--ok` has 0.4 of headroom over AA on - this surface, so any tint darker than the pane breaks it and any tint lighter re-lights - the recess and makes one pane read as two grounds; and the `+` / `-` git already put at - the head of every line is a non-colour carrier that is *in the content*, so nothing here - is carried by colour alone. -- **No `--blue` in the log**, though the brief named it for tool lines. It measures - **3.58:1** on `#F5F5F5` — correct for a graphic under the spec's ≥3:1 rule, and below - the 4.5 floor for text. This system has never set blue as type, and the exception is not - worth making for a rank that `--ink` already carries. Recorded rather than silently - dropped. No pulse either — the spec retired those by construction. -- **Pane height is `340px` (`480px` above 900px), not the old `52dvh`.** The reason is the - phone, not embedding: at 420px the pane ran to the bottom edge of a 390×844 screen, so - the page appeared to end at the log and the "Changes" heading below it was never seen. - 340px is ~17 lines and leaves the next section reachable. A fixed height also makes the - pane's own size independent of the window, which is what a scrolling instrument wants — - `52dvh` meant the log grew when you resized and the number of lines you could see was - never the same twice. The desktop step-up is the enhancement; the phone value is the - base. *(An earlier draft of this entry justified it by frame embedding. That was wrong - on two counts — `dvh` resolves against the viewport whatever the ancestor sizing, and - nothing in this app is framed. The decision stands on the reason above.)* -- **The Workspace's back glyph is a chevron, not the siblings' board panel.** The panel - is a *destination* glyph — on Feature and ticket detail it always means "the board". A - workspace's honest "up" is the ticket or the feature it is working on (you arrive here - from one of those or from Home, never from the board), so painting the panel and landing - somewhere else would be a control that lies. A chevron claims only "back", and the page - it returns to is the one this page's own h1 names. Home stays the fallback. -- **The Workspace's log and diff panes are focusable (`tabindex="0"`, `role="region"`).** - They scroll; a region that scrolls and cannot be reached by keyboard cannot be read by - keyboard. `min-height: 44px` keeps an empty one off the tap floor. - ---- - -## Accessibility floor (verified, not asserted) - -- All text AA. Worst case `--ink-2` at **5.10:1** on white. -- Meaningful non-text graphics ≥3:1: blue graphics 3.91 vs white; muted dot 3.23; - chequer 11.38 on ground. -- Tap targets ≥44px — **the rule, with two measured exceptions still open**: - `a.pill` in the top bar is 62×28 / 94×28 on every view, and the desktop rail's - `.nav-btn` / `.nav-link` are 199×43 above 1000px. Everything else clears it - (ticket rows 57px, the blocked row 74px, every button). Recorded here rather - than claimed as verified, because this section is only worth anything if what - it says is measured — see **TKT-060** under Open. -- Visible `:focus-visible` on every interactive element. -- `scrollWidth == clientWidth` at 375 / 390 / 768 / 1440. -- Renders fully with **JavaScript disabled**. `prefers-reduced-motion` honoured. - ---- - -## Open — known, not yet resolved - -- **iOS zooms on focusing a control below 16px.** Ours are 15px, because 16px is not on - the scale. Either the scale gains a control-only size or the zoom is accepted. -- **The top-bar pill (28px) and the desktop nav items (43px) are under the 44px tap - floor** — TKT-060, filed and not yet worked. The nav's 43px is a padding value rather - than a decision and is a one-line fix; the pill is not, because a 44px lozenge changes - the proportion of the top bar on all five views, and that is a design decision with a - round of its own rather than something to slip into a conformance fix. Left whole, and - the floor above stops claiming to be verified until it is done. -- **Pre-existing values outside this spec's surfaces**, found during the TKT-027 review. - Most are now resolved, because the Workspace was the last surface off the system and - taking it on made the code that held them dead: `.next-reason` (16px), `.backlink` - (39px, under the tap floor), `--radius: 10px`, `.card-head` / `.card-id` / `.card-stage` - / `.lamp`, `.meta-grid`, `.detail-actions`, `.rail-head`, `.btn-row`, `.log-pane`, - `.diff-pane`, the `--lamp-*` trio, `--attn`, and the `--surface-2` / `--ink-dim` / - `--ink-faint` aliases are all deleted, along with `STAGE_LAMP`, `workspaceLamp` and - `STATUS_LABEL` in `lib/ui.js`. Still open: `#2B2B2B` / `#1F1F1F` / `#F0F0F0` untokenised - greys; the global `:focus-visible { border-radius: 4px }`; the base `.btn` block, which - renders 15px and is now reached by nothing (every button in the app is inside `.appview` - or `.sheet`) but is left as the default for anything added outside them. -- **A code diff in a 440px column scrolls horizontally.** The spec's canvas is 440px at - any width, which leaves the diff pane ~408px — narrow for code. It scrolls inside its - own container so the page never does, but a wide diff on a 1440px screen is a worse read - than it needs to be. Widening the column for one section would break the five-view - register, so the canvas wins and the cost is recorded. If it is ever revisited, the fix - is a full-width reading mode for the diff, not a wider page. -- **"Renders fully with JavaScript disabled"** in the floor below is aspirational for this - surface — the app is client-rendered and ships a `