@cluesmith/codev 2.0.0-rc.9 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/af.js +2 -2
- package/bin/consult.js +1 -1
- package/dashboard/dist/assets/index-4n9zpWLY.css +32 -0
- package/dashboard/dist/assets/index-b38SaXk5.js +136 -0
- package/dashboard/dist/assets/index-b38SaXk5.js.map +1 -0
- package/dashboard/dist/index.html +14 -0
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +179 -104
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/architect.d.ts +3 -3
- package/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +20 -147
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/attach.d.ts +13 -0
- package/dist/agent-farm/commands/attach.d.ts.map +1 -0
- package/dist/agent-farm/commands/attach.js +144 -0
- package/dist/agent-farm/commands/attach.js.map +1 -0
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +35 -19
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/consult.d.ts +3 -4
- package/dist/agent-farm/commands/consult.d.ts.map +1 -1
- package/dist/agent-farm/commands/consult.js +27 -37
- package/dist/agent-farm/commands/consult.js.map +1 -1
- package/dist/agent-farm/commands/index.d.ts +2 -2
- package/dist/agent-farm/commands/index.d.ts.map +1 -1
- package/dist/agent-farm/commands/index.js +2 -2
- package/dist/agent-farm/commands/index.js.map +1 -1
- package/dist/agent-farm/commands/open.d.ts +4 -2
- package/dist/agent-farm/commands/open.d.ts.map +1 -1
- package/dist/agent-farm/commands/open.js +33 -83
- package/dist/agent-farm/commands/open.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +1 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +70 -79
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/shell.d.ts +15 -0
- package/dist/agent-farm/commands/shell.d.ts.map +1 -0
- package/dist/agent-farm/commands/shell.js +50 -0
- package/dist/agent-farm/commands/shell.js.map +1 -0
- package/dist/agent-farm/commands/spawn-roles.d.ts +80 -0
- package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -0
- package/dist/agent-farm/commands/spawn-roles.js +278 -0
- package/dist/agent-farm/commands/spawn-roles.js.map +1 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts +96 -0
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -0
- package/dist/agent-farm/commands/spawn-worktree.js +305 -0
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -0
- package/dist/agent-farm/commands/spawn.d.ts +5 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +241 -561
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts +10 -20
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +45 -449
- package/dist/agent-farm/commands/start.js.map +1 -1
- package/dist/agent-farm/commands/status.d.ts +2 -0
- package/dist/agent-farm/commands/status.d.ts.map +1 -1
- package/dist/agent-farm/commands/status.js +75 -24
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/stop.d.ts +6 -0
- package/dist/agent-farm/commands/stop.d.ts.map +1 -1
- package/dist/agent-farm/commands/stop.js +49 -109
- package/dist/agent-farm/commands/stop.js.map +1 -1
- package/dist/agent-farm/commands/tower-cloud.d.ts +48 -0
- package/dist/agent-farm/commands/tower-cloud.d.ts.map +1 -0
- package/dist/agent-farm/commands/tower-cloud.js +293 -0
- package/dist/agent-farm/commands/tower-cloud.js.map +1 -0
- package/dist/agent-farm/commands/tower.d.ts +9 -0
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +59 -19
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts +6 -2
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +301 -19
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/migrate.d.ts +0 -4
- package/dist/agent-farm/db/migrate.d.ts.map +1 -1
- package/dist/agent-farm/db/migrate.js +6 -55
- package/dist/agent-farm/db/migrate.js.map +1 -1
- package/dist/agent-farm/db/schema.d.ts +3 -3
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +25 -19
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +3 -13
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js +3 -11
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/hq-connector.d.ts +2 -6
- package/dist/agent-farm/hq-connector.d.ts.map +1 -1
- package/dist/agent-farm/hq-connector.js +2 -17
- package/dist/agent-farm/hq-connector.js.map +1 -1
- package/dist/agent-farm/lib/cloud-config.d.ts +59 -0
- package/dist/agent-farm/lib/cloud-config.d.ts.map +1 -0
- package/dist/agent-farm/lib/cloud-config.js +143 -0
- package/dist/agent-farm/lib/cloud-config.js.map +1 -0
- package/dist/agent-farm/lib/device-name.d.ts +25 -0
- package/dist/agent-farm/lib/device-name.d.ts.map +1 -0
- package/dist/agent-farm/lib/device-name.js +46 -0
- package/dist/agent-farm/lib/device-name.js.map +1 -0
- package/dist/agent-farm/lib/nonce-store.d.ts +28 -0
- package/dist/agent-farm/lib/nonce-store.d.ts.map +1 -0
- package/dist/agent-farm/lib/nonce-store.js +60 -0
- package/dist/agent-farm/lib/nonce-store.js.map +1 -0
- package/dist/agent-farm/lib/token-exchange.d.ts +18 -0
- package/dist/agent-farm/lib/token-exchange.d.ts.map +1 -0
- package/dist/agent-farm/lib/token-exchange.js +48 -0
- package/dist/agent-farm/lib/token-exchange.js.map +1 -0
- package/dist/agent-farm/lib/tower-client.d.ts +163 -0
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -0
- package/dist/agent-farm/lib/tower-client.js +233 -0
- package/dist/agent-farm/lib/tower-client.js.map +1 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts +117 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -0
- package/dist/agent-farm/lib/tunnel-client.js +504 -0
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -0
- package/dist/agent-farm/servers/tower-instances.d.ts +82 -0
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-instances.js +454 -0
- package/dist/agent-farm/servers/tower-instances.js.map +1 -0
- package/dist/agent-farm/servers/tower-routes.d.ts +34 -0
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-routes.js +1445 -0
- package/dist/agent-farm/servers/tower-routes.js.map +1 -0
- package/dist/agent-farm/servers/tower-server.d.ts +5 -2
- package/dist/agent-farm/servers/tower-server.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +157 -475
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +119 -0
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-terminals.js +629 -0
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -0
- package/dist/agent-farm/servers/tower-tunnel.d.ts +34 -0
- package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-tunnel.js +480 -0
- package/dist/agent-farm/servers/tower-tunnel.js.map +1 -0
- package/dist/agent-farm/servers/tower-types.d.ts +86 -0
- package/dist/agent-farm/servers/tower-types.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-types.js +6 -0
- package/dist/agent-farm/servers/tower-types.js.map +1 -0
- package/dist/agent-farm/servers/tower-utils.d.ts +58 -0
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-utils.js +182 -0
- package/dist/agent-farm/servers/tower-utils.js.map +1 -0
- package/dist/agent-farm/servers/tower-websocket.d.ts +25 -0
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -0
- package/dist/agent-farm/servers/tower-websocket.js +171 -0
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -0
- package/dist/agent-farm/state.d.ts +6 -2
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +34 -25
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +49 -26
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.d.ts +0 -5
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +12 -44
- package/dist/agent-farm/utils/config.js.map +1 -1
- package/dist/agent-farm/utils/deps.d.ts.map +1 -1
- package/dist/agent-farm/utils/deps.js +0 -32
- package/dist/agent-farm/utils/deps.js.map +1 -1
- package/dist/agent-farm/utils/file-tabs.d.ts +27 -0
- package/dist/agent-farm/utils/file-tabs.d.ts.map +1 -0
- package/dist/agent-farm/utils/file-tabs.js +46 -0
- package/dist/agent-farm/utils/file-tabs.js.map +1 -0
- package/dist/agent-farm/utils/gate-status.d.ts +16 -0
- package/dist/agent-farm/utils/gate-status.d.ts.map +1 -0
- package/dist/agent-farm/utils/gate-status.js +79 -0
- package/dist/agent-farm/utils/gate-status.js.map +1 -0
- package/dist/agent-farm/utils/gate-watcher.d.ts +38 -0
- package/dist/agent-farm/utils/gate-watcher.d.ts.map +1 -0
- package/dist/agent-farm/utils/gate-watcher.js +122 -0
- package/dist/agent-farm/utils/gate-watcher.js.map +1 -0
- package/dist/agent-farm/utils/index.d.ts +0 -1
- package/dist/agent-farm/utils/index.d.ts.map +1 -1
- package/dist/agent-farm/utils/index.js +0 -1
- package/dist/agent-farm/utils/index.js.map +1 -1
- package/dist/agent-farm/utils/notifications.d.ts +30 -0
- package/dist/agent-farm/utils/notifications.d.ts.map +1 -0
- package/dist/agent-farm/utils/notifications.js +121 -0
- package/dist/agent-farm/utils/notifications.js.map +1 -0
- package/dist/agent-farm/utils/server-utils.d.ts +5 -5
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +5 -16
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/agent-farm/utils/session.d.ts +32 -0
- package/dist/agent-farm/utils/session.d.ts.map +1 -0
- package/dist/agent-farm/utils/session.js +57 -0
- package/dist/agent-farm/utils/session.js.map +1 -0
- package/dist/agent-farm/utils/shell.d.ts +9 -22
- package/dist/agent-farm/utils/shell.d.ts.map +1 -1
- package/dist/agent-farm/utils/shell.js +34 -34
- package/dist/agent-farm/utils/shell.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -37
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +33 -4
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/consult/index.d.ts +13 -2
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +244 -29
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +96 -79
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +36 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/porch/build-counter.d.ts +5 -0
- package/dist/commands/porch/build-counter.d.ts.map +1 -0
- package/dist/commands/porch/build-counter.js +5 -0
- package/dist/commands/porch/build-counter.js.map +1 -0
- package/dist/commands/porch/checks.d.ts +3 -2
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +8 -2
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/index.d.ts +4 -0
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +109 -70
- package/dist/commands/porch/index.js.map +1 -1
- package/dist/commands/porch/next.d.ts +22 -0
- package/dist/commands/porch/next.d.ts.map +1 -0
- package/dist/commands/porch/next.js +571 -0
- package/dist/commands/porch/next.js.map +1 -0
- package/dist/commands/porch/plan.d.ts +11 -1
- package/dist/commands/porch/plan.d.ts.map +1 -1
- package/dist/commands/porch/plan.js +33 -5
- package/dist/commands/porch/plan.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +44 -26
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/commands/porch/protocol.d.ts +6 -4
- package/dist/commands/porch/protocol.d.ts.map +1 -1
- package/dist/commands/porch/protocol.js +59 -15
- package/dist/commands/porch/protocol.js.map +1 -1
- package/dist/commands/porch/state.d.ts +29 -2
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +71 -3
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/porch/types.d.ts +45 -2
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/commands/porch/verdict.d.ts +31 -0
- package/dist/commands/porch/verdict.d.ts.map +1 -0
- package/dist/commands/porch/verdict.js +59 -0
- package/dist/commands/porch/verdict.js.map +1 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +18 -6
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/scaffold.d.ts +13 -0
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +36 -0
- package/dist/lib/scaffold.js.map +1 -1
- package/dist/terminal/index.d.ts +8 -0
- package/dist/terminal/index.d.ts.map +1 -0
- package/dist/terminal/index.js +5 -0
- package/dist/terminal/index.js.map +1 -0
- package/dist/terminal/pty-manager.d.ts +69 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -0
- package/dist/terminal/pty-manager.js +377 -0
- package/dist/terminal/pty-manager.js.map +1 -0
- package/dist/terminal/pty-session.d.ts +104 -0
- package/dist/terminal/pty-session.d.ts.map +1 -0
- package/dist/terminal/pty-session.js +327 -0
- package/dist/terminal/pty-session.js.map +1 -0
- package/dist/terminal/ring-buffer.d.ts +34 -0
- package/dist/terminal/ring-buffer.d.ts.map +1 -0
- package/dist/terminal/ring-buffer.js +94 -0
- package/dist/terminal/ring-buffer.js.map +1 -0
- package/dist/terminal/session-manager.d.ts +115 -0
- package/dist/terminal/session-manager.d.ts.map +1 -0
- package/dist/terminal/session-manager.js +582 -0
- package/dist/terminal/session-manager.js.map +1 -0
- package/dist/terminal/shellper-client.d.ts +66 -0
- package/dist/terminal/shellper-client.d.ts.map +1 -0
- package/dist/terminal/shellper-client.js +234 -0
- package/dist/terminal/shellper-client.js.map +1 -0
- package/dist/terminal/shellper-main.d.ts +19 -0
- package/dist/terminal/shellper-main.d.ts.map +1 -0
- package/dist/terminal/shellper-main.js +153 -0
- package/dist/terminal/shellper-main.js.map +1 -0
- package/dist/terminal/shellper-process.d.ts +75 -0
- package/dist/terminal/shellper-process.d.ts.map +1 -0
- package/dist/terminal/shellper-process.js +279 -0
- package/dist/terminal/shellper-process.js.map +1 -0
- package/dist/terminal/shellper-protocol.d.ts +115 -0
- package/dist/terminal/shellper-protocol.d.ts.map +1 -0
- package/dist/terminal/shellper-protocol.js +214 -0
- package/dist/terminal/shellper-protocol.js.map +1 -0
- package/dist/terminal/shellper-replay-buffer.d.ts +38 -0
- package/dist/terminal/shellper-replay-buffer.d.ts.map +1 -0
- package/dist/terminal/shellper-replay-buffer.js +94 -0
- package/dist/terminal/shellper-replay-buffer.js.map +1 -0
- package/dist/terminal/ws-protocol.d.ts +27 -0
- package/dist/terminal/ws-protocol.d.ts.map +1 -0
- package/dist/terminal/ws-protocol.js +44 -0
- package/dist/terminal/ws-protocol.js.map +1 -0
- package/package.json +17 -5
- package/skeleton/.claude/skills/af/SKILL.md +89 -0
- package/skeleton/.claude/skills/codev/SKILL.md +41 -0
- package/skeleton/.claude/skills/consult/SKILL.md +81 -0
- package/skeleton/.claude/skills/generate-image/SKILL.md +56 -0
- package/skeleton/DEPENDENCIES.md +4 -62
- package/skeleton/builders.md +1 -1
- package/skeleton/consult-types/impl-review.md +18 -9
- package/skeleton/consult-types/integration-review.md +1 -1
- package/skeleton/consult-types/plan-review.md +1 -1
- package/skeleton/consult-types/pr-ready.md +1 -1
- package/skeleton/consult-types/spec-review.md +1 -1
- package/skeleton/porch/prompts/defend.md +1 -1
- package/skeleton/porch/prompts/evaluate.md +2 -2
- package/skeleton/porch/prompts/implement.md +1 -1
- package/skeleton/porch/prompts/plan.md +1 -1
- package/skeleton/porch/prompts/review.md +4 -4
- package/skeleton/porch/prompts/specify.md +1 -1
- package/skeleton/porch/prompts/understand.md +2 -2
- package/skeleton/protocol-schema.json +282 -0
- package/skeleton/protocols/bugfix/builder-prompt.md +60 -0
- package/skeleton/protocols/bugfix/prompts/fix.md +77 -0
- package/skeleton/protocols/bugfix/prompts/investigate.md +77 -0
- package/skeleton/protocols/bugfix/prompts/pr.md +84 -0
- package/skeleton/protocols/bugfix/protocol.json +20 -33
- package/skeleton/protocols/experiment/builder-prompt.md +52 -0
- package/skeleton/protocols/experiment/protocol.json +101 -0
- package/skeleton/protocols/experiment/protocol.md +3 -3
- package/skeleton/protocols/experiment/templates/notes.md +1 -1
- package/skeleton/protocols/maintain/builder-prompt.md +46 -0
- package/skeleton/protocols/maintain/prompts/audit.md +111 -0
- package/skeleton/protocols/maintain/prompts/clean.md +91 -0
- package/skeleton/protocols/maintain/prompts/sync.md +113 -0
- package/skeleton/protocols/maintain/prompts/verify.md +110 -0
- package/skeleton/protocols/maintain/protocol.json +141 -0
- package/skeleton/protocols/maintain/protocol.md +17 -11
- package/skeleton/protocols/protocol-schema.json +54 -1
- package/skeleton/protocols/spir/builder-prompt.md +66 -0
- package/skeleton/protocols/spir/prompts/implement.md +208 -0
- package/skeleton/protocols/{spider → spir}/prompts/plan.md +6 -70
- package/skeleton/protocols/{spider → spir}/prompts/review.md +20 -39
- package/skeleton/protocols/{spider → spir}/prompts/specify.md +24 -59
- package/skeleton/protocols/{spider → spir}/protocol.json +30 -10
- package/skeleton/protocols/{spider → spir}/protocol.md +35 -21
- package/skeleton/protocols/spir/templates/review.md +89 -0
- package/skeleton/protocols/tick/builder-prompt.md +56 -0
- package/skeleton/protocols/tick/protocol.json +7 -2
- package/skeleton/protocols/tick/protocol.md +18 -18
- package/skeleton/protocols/tick/templates/review.md +1 -1
- package/skeleton/resources/commands/agent-farm.md +63 -46
- package/skeleton/resources/commands/codev.md +0 -2
- package/skeleton/resources/commands/overview.md +7 -17
- package/skeleton/resources/workflow-reference.md +4 -4
- package/skeleton/roles/architect.md +151 -306
- package/skeleton/roles/builder.md +115 -332
- package/skeleton/roles/consultant.md +6 -6
- package/skeleton/templates/AGENTS.md +2 -2
- package/skeleton/templates/CLAUDE.md +2 -2
- package/skeleton/templates/cheatsheet.md +7 -5
- package/skeleton/templates/projectlist.md +1 -1
- package/templates/dashboard/index.html +17 -16
- package/templates/dashboard/js/dialogs.js +7 -7
- package/templates/dashboard/js/files.js +2 -2
- package/templates/dashboard/js/main.js +4 -4
- package/templates/dashboard/js/projects.js +3 -3
- package/templates/dashboard/js/tabs.js +1 -1
- package/templates/dashboard/js/utils.js +22 -1
- package/templates/open.html +26 -0
- package/templates/tower.html +731 -91
- package/dist/agent-farm/commands/kickoff.d.ts +0 -20
- package/dist/agent-farm/commands/kickoff.d.ts.map +0 -1
- package/dist/agent-farm/commands/kickoff.js +0 -273
- package/dist/agent-farm/commands/kickoff.js.map +0 -1
- package/dist/agent-farm/commands/rename.d.ts +0 -13
- package/dist/agent-farm/commands/rename.d.ts.map +0 -1
- package/dist/agent-farm/commands/rename.js +0 -33
- package/dist/agent-farm/commands/rename.js.map +0 -1
- package/dist/agent-farm/commands/tutorial.d.ts +0 -10
- package/dist/agent-farm/commands/tutorial.d.ts.map +0 -1
- package/dist/agent-farm/commands/tutorial.js +0 -49
- package/dist/agent-farm/commands/tutorial.js.map +0 -1
- package/dist/agent-farm/commands/util.d.ts +0 -15
- package/dist/agent-farm/commands/util.d.ts.map +0 -1
- package/dist/agent-farm/commands/util.js +0 -108
- package/dist/agent-farm/commands/util.js.map +0 -1
- package/dist/agent-farm/servers/dashboard-server.d.ts +0 -7
- package/dist/agent-farm/servers/dashboard-server.d.ts.map +0 -1
- package/dist/agent-farm/servers/dashboard-server.js +0 -1858
- package/dist/agent-farm/servers/dashboard-server.js.map +0 -1
- package/dist/agent-farm/servers/open-server.d.ts +0 -7
- package/dist/agent-farm/servers/open-server.d.ts.map +0 -1
- package/dist/agent-farm/servers/open-server.js +0 -315
- package/dist/agent-farm/servers/open-server.js.map +0 -1
- package/dist/agent-farm/tutorial/index.d.ts +0 -8
- package/dist/agent-farm/tutorial/index.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/index.js +0 -8
- package/dist/agent-farm/tutorial/index.js.map +0 -1
- package/dist/agent-farm/tutorial/prompts.d.ts +0 -57
- package/dist/agent-farm/tutorial/prompts.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/prompts.js +0 -147
- package/dist/agent-farm/tutorial/prompts.js.map +0 -1
- package/dist/agent-farm/tutorial/runner.d.ts +0 -52
- package/dist/agent-farm/tutorial/runner.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/runner.js +0 -204
- package/dist/agent-farm/tutorial/runner.js.map +0 -1
- package/dist/agent-farm/tutorial/state.d.ts +0 -26
- package/dist/agent-farm/tutorial/state.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/state.js +0 -89
- package/dist/agent-farm/tutorial/state.js.map +0 -1
- package/dist/agent-farm/tutorial/steps/first-spec.d.ts +0 -7
- package/dist/agent-farm/tutorial/steps/first-spec.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/steps/first-spec.js +0 -136
- package/dist/agent-farm/tutorial/steps/first-spec.js.map +0 -1
- package/dist/agent-farm/tutorial/steps/implementation.d.ts +0 -7
- package/dist/agent-farm/tutorial/steps/implementation.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/steps/implementation.js +0 -76
- package/dist/agent-farm/tutorial/steps/implementation.js.map +0 -1
- package/dist/agent-farm/tutorial/steps/index.d.ts +0 -10
- package/dist/agent-farm/tutorial/steps/index.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/steps/index.js +0 -10
- package/dist/agent-farm/tutorial/steps/index.js.map +0 -1
- package/dist/agent-farm/tutorial/steps/planning.d.ts +0 -7
- package/dist/agent-farm/tutorial/steps/planning.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/steps/planning.js +0 -143
- package/dist/agent-farm/tutorial/steps/planning.js.map +0 -1
- package/dist/agent-farm/tutorial/steps/review.d.ts +0 -7
- package/dist/agent-farm/tutorial/steps/review.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/steps/review.js +0 -78
- package/dist/agent-farm/tutorial/steps/review.js.map +0 -1
- package/dist/agent-farm/tutorial/steps/setup.d.ts +0 -7
- package/dist/agent-farm/tutorial/steps/setup.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/steps/setup.js +0 -126
- package/dist/agent-farm/tutorial/steps/setup.js.map +0 -1
- package/dist/agent-farm/tutorial/steps/welcome.d.ts +0 -7
- package/dist/agent-farm/tutorial/steps/welcome.d.ts.map +0 -1
- package/dist/agent-farm/tutorial/steps/welcome.js +0 -50
- package/dist/agent-farm/tutorial/steps/welcome.js.map +0 -1
- package/dist/agent-farm/utils/orphan-handler.d.ts +0 -27
- package/dist/agent-farm/utils/orphan-handler.d.ts.map +0 -1
- package/dist/agent-farm/utils/orphan-handler.js +0 -149
- package/dist/agent-farm/utils/orphan-handler.js.map +0 -1
- package/dist/agent-farm/utils/port-registry.d.ts +0 -58
- package/dist/agent-farm/utils/port-registry.d.ts.map +0 -1
- package/dist/agent-farm/utils/port-registry.js +0 -166
- package/dist/agent-farm/utils/port-registry.js.map +0 -1
- package/dist/agent-farm/utils/terminal-ports.d.ts +0 -18
- package/dist/agent-farm/utils/terminal-ports.d.ts.map +0 -1
- package/dist/agent-farm/utils/terminal-ports.js +0 -35
- package/dist/agent-farm/utils/terminal-ports.js.map +0 -1
- package/dist/commands/pcheck/cache.d.ts +0 -48
- package/dist/commands/pcheck/cache.d.ts.map +0 -1
- package/dist/commands/pcheck/cache.js +0 -170
- package/dist/commands/pcheck/cache.js.map +0 -1
- package/dist/commands/pcheck/evaluator.d.ts +0 -15
- package/dist/commands/pcheck/evaluator.d.ts.map +0 -1
- package/dist/commands/pcheck/evaluator.js +0 -246
- package/dist/commands/pcheck/evaluator.js.map +0 -1
- package/dist/commands/pcheck/index.d.ts +0 -12
- package/dist/commands/pcheck/index.d.ts.map +0 -1
- package/dist/commands/pcheck/index.js +0 -249
- package/dist/commands/pcheck/index.js.map +0 -1
- package/dist/commands/pcheck/parser.d.ts +0 -39
- package/dist/commands/pcheck/parser.d.ts.map +0 -1
- package/dist/commands/pcheck/parser.js +0 -155
- package/dist/commands/pcheck/parser.js.map +0 -1
- package/dist/commands/pcheck/types.d.ts +0 -82
- package/dist/commands/pcheck/types.d.ts.map +0 -1
- package/dist/commands/pcheck/types.js +0 -5
- package/dist/commands/pcheck/types.js.map +0 -1
- package/dist/commands/porch/claude.d.ts +0 -29
- package/dist/commands/porch/claude.d.ts.map +0 -1
- package/dist/commands/porch/claude.js +0 -79
- package/dist/commands/porch/claude.js.map +0 -1
- package/dist/commands/porch/consultation.d.ts +0 -56
- package/dist/commands/porch/consultation.d.ts.map +0 -1
- package/dist/commands/porch/consultation.js +0 -330
- package/dist/commands/porch/consultation.js.map +0 -1
- package/dist/commands/porch/notifications.d.ts +0 -99
- package/dist/commands/porch/notifications.d.ts.map +0 -1
- package/dist/commands/porch/notifications.js +0 -223
- package/dist/commands/porch/notifications.js.map +0 -1
- package/dist/commands/porch/plan-parser.d.ts +0 -38
- package/dist/commands/porch/plan-parser.d.ts.map +0 -1
- package/dist/commands/porch/plan-parser.js +0 -166
- package/dist/commands/porch/plan-parser.js.map +0 -1
- package/dist/commands/porch/protocol-loader.d.ts +0 -46
- package/dist/commands/porch/protocol-loader.d.ts.map +0 -1
- package/dist/commands/porch/protocol-loader.js +0 -262
- package/dist/commands/porch/protocol-loader.js.map +0 -1
- package/dist/commands/porch/repl.d.ts +0 -33
- package/dist/commands/porch/repl.d.ts.map +0 -1
- package/dist/commands/porch/repl.js +0 -206
- package/dist/commands/porch/repl.js.map +0 -1
- package/dist/commands/porch/run.d.ts +0 -15
- package/dist/commands/porch/run.d.ts.map +0 -1
- package/dist/commands/porch/run.js +0 -551
- package/dist/commands/porch/run.js.map +0 -1
- package/dist/commands/porch/signal-parser.d.ts +0 -102
- package/dist/commands/porch/signal-parser.d.ts.map +0 -1
- package/dist/commands/porch/signal-parser.js +0 -199
- package/dist/commands/porch/signal-parser.js.map +0 -1
- package/dist/commands/porch/signals.d.ts +0 -35
- package/dist/commands/porch/signals.d.ts.map +0 -1
- package/dist/commands/porch/signals.js +0 -76
- package/dist/commands/porch/signals.js.map +0 -1
- package/dist/commands/porch2/checks.d.ts +0 -29
- package/dist/commands/porch2/checks.d.ts.map +0 -1
- package/dist/commands/porch2/checks.js +0 -141
- package/dist/commands/porch2/checks.js.map +0 -1
- package/dist/commands/porch2/index.d.ts +0 -38
- package/dist/commands/porch2/index.d.ts.map +0 -1
- package/dist/commands/porch2/index.js +0 -483
- package/dist/commands/porch2/index.js.map +0 -1
- package/dist/commands/porch2/plan.d.ts +0 -70
- package/dist/commands/porch2/plan.d.ts.map +0 -1
- package/dist/commands/porch2/plan.js +0 -227
- package/dist/commands/porch2/plan.js.map +0 -1
- package/dist/commands/porch2/protocol.d.ts +0 -37
- package/dist/commands/porch2/protocol.d.ts.map +0 -1
- package/dist/commands/porch2/protocol.js +0 -183
- package/dist/commands/porch2/protocol.js.map +0 -1
- package/dist/commands/porch2/state.d.ts +0 -35
- package/dist/commands/porch2/state.d.ts.map +0 -1
- package/dist/commands/porch2/state.js +0 -124
- package/dist/commands/porch2/state.js.map +0 -1
- package/dist/commands/porch2/types.d.ts +0 -79
- package/dist/commands/porch2/types.d.ts.map +0 -1
- package/dist/commands/porch2/types.js +0 -8
- package/dist/commands/porch2/types.js.map +0 -1
- package/dist/commands/tower.d.ts +0 -16
- package/dist/commands/tower.d.ts.map +0 -1
- package/dist/commands/tower.js +0 -21
- package/dist/commands/tower.js.map +0 -1
- package/skeleton/config.json +0 -7
- package/skeleton/protocols/spider/prompts/defend.md +0 -215
- package/skeleton/protocols/spider/prompts/evaluate.md +0 -241
- package/skeleton/protocols/spider/prompts/implement.md +0 -149
- package/skeleton/protocols/spider/templates/review.md +0 -207
- /package/skeleton/protocols/{spider → spir}/templates/plan.md +0 -0
- /package/skeleton/protocols/{spider → spir}/templates/spec.md +0 -0
|
@@ -1,37 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Spawn command
|
|
2
|
+
* Spawn command — orchestrator module.
|
|
3
|
+
* Spec 0105: Tower Server Decomposition — Phase 7
|
|
3
4
|
*
|
|
4
5
|
* Modes:
|
|
5
6
|
* - spec: --project/-p Spawn for a spec file (existing behavior)
|
|
6
7
|
* - task: --task Spawn with an ad-hoc task description
|
|
7
8
|
* - protocol: --protocol Spawn to run a protocol (cleanup, experiment, etc.)
|
|
8
9
|
* - shell: --shell Bare Claude session (no prompt, no worktree)
|
|
10
|
+
*
|
|
11
|
+
* Role/prompt logic extracted to spawn-roles.ts.
|
|
12
|
+
* Worktree/git logic extracted to spawn-worktree.ts.
|
|
9
13
|
*/
|
|
10
14
|
import { resolve, basename } from 'node:path';
|
|
11
|
-
import { existsSync,
|
|
12
|
-
import { readdir } from 'node:fs/promises';
|
|
15
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
13
16
|
import { getConfig, ensureDirectories, getResolvedCommands } from '../utils/index.js';
|
|
14
17
|
import { logger, fatal } from '../utils/logger.js';
|
|
15
|
-
import { run
|
|
16
|
-
import {
|
|
18
|
+
import { run } from '../utils/shell.js';
|
|
19
|
+
import { upsertBuilder } from '../state.js';
|
|
17
20
|
import { loadRolePrompt } from '../utils/roles.js';
|
|
21
|
+
import { buildPromptFromTemplate, buildResumeNotice, loadProtocolRole, findSpecFile, validateProtocol, loadProtocol, resolveProtocol, resolveMode, } from './spawn-roles.js';
|
|
22
|
+
import { DEFAULT_TOWER_PORT, checkDependencies, createWorktree, initPorchInWorktree, checkBugfixCollisions, fetchGitHubIssue, executePreSpawnHooks, slugify, validateResumeWorktree, createPtySession, startBuilderSession, startShellSession, buildWorktreeLaunchScript, } from './spawn-worktree.js';
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// ID and Session Management
|
|
25
|
+
// =============================================================================
|
|
18
26
|
/**
|
|
19
27
|
* Generate a short 4-character base64-encoded ID
|
|
20
28
|
* Uses URL-safe base64 (a-z, A-Z, 0-9, -, _) for filesystem-safe IDs
|
|
21
29
|
*/
|
|
22
|
-
/**
|
|
23
|
-
* Get the project name from config (basename of projectRoot)
|
|
24
|
-
* Used to namespace tmux sessions and prevent cross-project collisions
|
|
25
|
-
*/
|
|
26
|
-
function getProjectName(config) {
|
|
27
|
-
return basename(config.projectRoot);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Get a namespaced tmux session name: builder-{project}-{id}
|
|
31
|
-
*/
|
|
32
|
-
function getSessionName(config, builderId) {
|
|
33
|
-
return `builder-${getProjectName(config)}-${builderId}`;
|
|
34
|
-
}
|
|
35
30
|
function generateShortId() {
|
|
36
31
|
// Generate random 24-bit number and base64 encode to 4 chars
|
|
37
32
|
const num = Math.floor(Math.random() * 0xFFFFFF);
|
|
@@ -43,22 +38,27 @@ function generateShortId() {
|
|
|
43
38
|
.substring(0, 4);
|
|
44
39
|
}
|
|
45
40
|
/**
|
|
46
|
-
* Validate spawn options - ensure exactly one mode is selected
|
|
41
|
+
* Validate spawn options - ensure exactly one input mode is selected
|
|
42
|
+
* Note: --protocol serves dual purpose:
|
|
43
|
+
* 1. As an input mode when used alone (e.g., `af spawn --protocol experiment`)
|
|
44
|
+
* 2. As a protocol override when combined with other input modes (e.g., `af spawn -p 0001 --protocol tick`)
|
|
47
45
|
*/
|
|
48
46
|
function validateSpawnOptions(options) {
|
|
49
|
-
|
|
47
|
+
// Count input modes (excluding --protocol which can be used as override)
|
|
48
|
+
const inputModes = [
|
|
50
49
|
options.project,
|
|
51
50
|
options.task,
|
|
52
|
-
options.protocol,
|
|
53
51
|
options.shell,
|
|
54
52
|
options.worktree,
|
|
55
53
|
options.issue,
|
|
56
54
|
].filter(Boolean);
|
|
57
|
-
|
|
55
|
+
// --protocol alone is a valid input mode
|
|
56
|
+
const protocolAlone = options.protocol && inputModes.length === 0;
|
|
57
|
+
if (inputModes.length === 0 && !protocolAlone) {
|
|
58
58
|
fatal('Must specify one of: --project (-p), --issue (-i), --task, --protocol, --shell, --worktree\n\nRun "af spawn --help" for examples.');
|
|
59
59
|
}
|
|
60
|
-
if (
|
|
61
|
-
fatal('Flags --project, --issue, --task, --
|
|
60
|
+
if (inputModes.length > 1) {
|
|
61
|
+
fatal('Flags --project, --issue, --task, --shell, --worktree are mutually exclusive');
|
|
62
62
|
}
|
|
63
63
|
if (options.files && !options.task) {
|
|
64
64
|
fatal('--files requires --task');
|
|
@@ -66,246 +66,37 @@ function validateSpawnOptions(options) {
|
|
|
66
66
|
if ((options.noComment || options.force) && !options.issue) {
|
|
67
67
|
fatal('--no-comment and --force require --issue');
|
|
68
68
|
}
|
|
69
|
+
// --protocol as override cannot be used with --shell or --worktree
|
|
70
|
+
if (options.protocol && inputModes.length > 0 && (options.shell || options.worktree)) {
|
|
71
|
+
fatal('--protocol cannot be used with --shell or --worktree (no protocol applies)');
|
|
72
|
+
}
|
|
73
|
+
// --use-protocol is now deprecated in favor of --protocol as universal override
|
|
74
|
+
// Keep for backwards compatibility but prefer --protocol
|
|
75
|
+
if (options.useProtocol && (options.shell || options.worktree)) {
|
|
76
|
+
fatal('--use-protocol cannot be used with --shell or --worktree (no protocol applies)');
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
79
|
/**
|
|
71
80
|
* Determine the spawn mode from options
|
|
81
|
+
* Note: --protocol can be used as both an input mode (alone) or an override (with other modes)
|
|
72
82
|
*/
|
|
73
83
|
function getSpawnMode(options) {
|
|
84
|
+
// Primary input modes take precedence over --protocol as override
|
|
74
85
|
if (options.project)
|
|
75
86
|
return 'spec';
|
|
76
87
|
if (options.issue)
|
|
77
88
|
return 'bugfix';
|
|
78
89
|
if (options.task)
|
|
79
90
|
return 'task';
|
|
80
|
-
if (options.protocol)
|
|
81
|
-
return 'protocol';
|
|
82
91
|
if (options.shell)
|
|
83
92
|
return 'shell';
|
|
84
93
|
if (options.worktree)
|
|
85
94
|
return 'worktree';
|
|
95
|
+
// --protocol alone is the protocol input mode
|
|
96
|
+
if (options.protocol)
|
|
97
|
+
return 'protocol';
|
|
86
98
|
throw new Error('No mode specified');
|
|
87
99
|
}
|
|
88
|
-
// loadRolePrompt imported from ../utils/roles.js
|
|
89
|
-
/**
|
|
90
|
-
* Load a protocol-specific role if it exists
|
|
91
|
-
*/
|
|
92
|
-
function loadProtocolRole(config, protocolName) {
|
|
93
|
-
const protocolRolePath = resolve(config.codevDir, 'protocols', protocolName, 'role.md');
|
|
94
|
-
if (existsSync(protocolRolePath)) {
|
|
95
|
-
return { content: readFileSync(protocolRolePath, 'utf-8'), source: 'protocol' };
|
|
96
|
-
}
|
|
97
|
-
// Fall back to builder role
|
|
98
|
-
return loadRolePrompt(config, 'builder');
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Find a spec file by project ID
|
|
102
|
-
*/
|
|
103
|
-
async function findSpecFile(codevDir, projectId) {
|
|
104
|
-
const specsDir = resolve(codevDir, 'specs');
|
|
105
|
-
if (!existsSync(specsDir)) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
const files = await readdir(specsDir);
|
|
109
|
-
// Try exact match first (e.g., "0001-feature.md")
|
|
110
|
-
for (const file of files) {
|
|
111
|
-
if (file.startsWith(projectId) && file.endsWith('.md')) {
|
|
112
|
-
return resolve(specsDir, file);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Try partial match (e.g., just "0001")
|
|
116
|
-
for (const file of files) {
|
|
117
|
-
if (file.startsWith(projectId + '-') && file.endsWith('.md')) {
|
|
118
|
-
return resolve(specsDir, file);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Validate that a protocol exists
|
|
125
|
-
*/
|
|
126
|
-
function validateProtocol(config, protocolName) {
|
|
127
|
-
const protocolDir = resolve(config.codevDir, 'protocols', protocolName);
|
|
128
|
-
const protocolFile = resolve(protocolDir, 'protocol.md');
|
|
129
|
-
if (!existsSync(protocolDir)) {
|
|
130
|
-
// List available protocols
|
|
131
|
-
const protocolsDir = resolve(config.codevDir, 'protocols');
|
|
132
|
-
let available = '';
|
|
133
|
-
if (existsSync(protocolsDir)) {
|
|
134
|
-
const dirs = readdirSync(protocolsDir, { withFileTypes: true })
|
|
135
|
-
.filter((d) => d.isDirectory())
|
|
136
|
-
.map((d) => d.name);
|
|
137
|
-
if (dirs.length > 0) {
|
|
138
|
-
available = `\n\nAvailable protocols: ${dirs.join(', ')}`;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
fatal(`Protocol not found: ${protocolName}${available}`);
|
|
142
|
-
}
|
|
143
|
-
if (!existsSync(protocolFile)) {
|
|
144
|
-
fatal(`Protocol ${protocolName} exists but has no protocol.md file`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Check for required dependencies
|
|
149
|
-
*/
|
|
150
|
-
async function checkDependencies() {
|
|
151
|
-
if (!(await commandExists('git'))) {
|
|
152
|
-
fatal('git not found');
|
|
153
|
-
}
|
|
154
|
-
if (!(await commandExists('ttyd'))) {
|
|
155
|
-
fatal('ttyd not found. Install with: brew install ttyd');
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Find an available port, avoiding ports already in use by other builders
|
|
160
|
-
*/
|
|
161
|
-
async function findFreePort(config) {
|
|
162
|
-
const state = loadState();
|
|
163
|
-
const usedPorts = new Set();
|
|
164
|
-
for (const b of state.builders || []) {
|
|
165
|
-
if (b.port)
|
|
166
|
-
usedPorts.add(b.port);
|
|
167
|
-
}
|
|
168
|
-
let port = config.builderPortRange[0];
|
|
169
|
-
while (usedPorts.has(port)) {
|
|
170
|
-
port++;
|
|
171
|
-
}
|
|
172
|
-
return findAvailablePort(port);
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Create git branch and worktree
|
|
176
|
-
*/
|
|
177
|
-
async function createWorktree(config, branchName, worktreePath) {
|
|
178
|
-
logger.info('Creating branch...');
|
|
179
|
-
try {
|
|
180
|
-
await run(`git branch ${branchName}`, { cwd: config.projectRoot });
|
|
181
|
-
}
|
|
182
|
-
catch (error) {
|
|
183
|
-
// Branch might already exist, that's OK
|
|
184
|
-
logger.debug(`Branch creation: ${error}`);
|
|
185
|
-
}
|
|
186
|
-
logger.info('Creating worktree...');
|
|
187
|
-
try {
|
|
188
|
-
await run(`git worktree add "${worktreePath}" ${branchName}`, { cwd: config.projectRoot });
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
fatal(`Failed to create worktree: ${error}`);
|
|
192
|
-
}
|
|
193
|
-
// Symlink .env from project root into worktree (if it exists)
|
|
194
|
-
const rootEnvPath = resolve(config.projectRoot, '.env');
|
|
195
|
-
const worktreeEnvPath = resolve(worktreePath, '.env');
|
|
196
|
-
if (existsSync(rootEnvPath) && !existsSync(worktreeEnvPath)) {
|
|
197
|
-
try {
|
|
198
|
-
symlinkSync(rootEnvPath, worktreeEnvPath);
|
|
199
|
-
logger.info('Linked .env from project root');
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
logger.debug(`Failed to symlink .env: ${error}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Start tmux session and ttyd for a builder
|
|
208
|
-
*/
|
|
209
|
-
async function startBuilderSession(config, builderId, worktreePath, baseCmd, prompt, roleContent, roleSource) {
|
|
210
|
-
const port = await findFreePort(config);
|
|
211
|
-
const sessionName = getSessionName(config, builderId);
|
|
212
|
-
logger.info('Creating tmux session...');
|
|
213
|
-
// Write initial prompt to a file for reference
|
|
214
|
-
const promptFile = resolve(worktreePath, '.builder-prompt.txt');
|
|
215
|
-
writeFileSync(promptFile, prompt);
|
|
216
|
-
// Build the start script with role if provided
|
|
217
|
-
const scriptPath = resolve(worktreePath, '.builder-start.sh');
|
|
218
|
-
let scriptContent;
|
|
219
|
-
if (roleContent) {
|
|
220
|
-
// Write role to a file and use $(cat) to avoid shell escaping issues
|
|
221
|
-
const roleFile = resolve(worktreePath, '.builder-role.md');
|
|
222
|
-
// Inject the actual dashboard port into the role prompt
|
|
223
|
-
const roleWithPort = roleContent.replace(/\{PORT\}/g, String(config.dashboardPort));
|
|
224
|
-
writeFileSync(roleFile, roleWithPort);
|
|
225
|
-
logger.info(`Loaded role (${roleSource})`);
|
|
226
|
-
scriptContent = `#!/bin/bash
|
|
227
|
-
cd "${worktreePath}"
|
|
228
|
-
while true; do
|
|
229
|
-
${baseCmd} --append-system-prompt "$(cat '${roleFile}')" "$(cat '${promptFile}')"
|
|
230
|
-
echo ""
|
|
231
|
-
echo "Claude exited. Restarting in 2 seconds... (Ctrl+C to quit)"
|
|
232
|
-
sleep 2
|
|
233
|
-
done
|
|
234
|
-
`;
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
scriptContent = `#!/bin/bash
|
|
238
|
-
cd "${worktreePath}"
|
|
239
|
-
while true; do
|
|
240
|
-
${baseCmd} "$(cat '${promptFile}')"
|
|
241
|
-
echo ""
|
|
242
|
-
echo "Claude exited. Restarting in 2 seconds... (Ctrl+C to quit)"
|
|
243
|
-
sleep 2
|
|
244
|
-
done
|
|
245
|
-
`;
|
|
246
|
-
}
|
|
247
|
-
writeFileSync(scriptPath, scriptContent);
|
|
248
|
-
chmodSync(scriptPath, '755');
|
|
249
|
-
// Create tmux session running the script
|
|
250
|
-
await run(`tmux new-session -d -s "${sessionName}" -x 200 -y 50 -c "${worktreePath}" "${scriptPath}"`);
|
|
251
|
-
await run(`tmux set-option -t "${sessionName}" status off`);
|
|
252
|
-
// Enable mouse scrolling in tmux
|
|
253
|
-
await run('tmux set -g mouse on');
|
|
254
|
-
await run('tmux set -g set-clipboard on');
|
|
255
|
-
await run('tmux set -g allow-passthrough on');
|
|
256
|
-
// Copy selection to clipboard when mouse is released (pbcopy for macOS)
|
|
257
|
-
await run('tmux bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"');
|
|
258
|
-
await run('tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"');
|
|
259
|
-
// Start ttyd connecting to the tmux session
|
|
260
|
-
logger.info('Starting builder terminal...');
|
|
261
|
-
const customIndexPath = resolve(config.templatesDir, 'ttyd-index.html');
|
|
262
|
-
const hasCustomIndex = existsSync(customIndexPath);
|
|
263
|
-
if (hasCustomIndex) {
|
|
264
|
-
logger.info('Using custom terminal with file click support');
|
|
265
|
-
}
|
|
266
|
-
const ttydProcess = spawnTtyd({
|
|
267
|
-
port,
|
|
268
|
-
sessionName,
|
|
269
|
-
cwd: worktreePath,
|
|
270
|
-
customIndexPath: hasCustomIndex ? customIndexPath : undefined,
|
|
271
|
-
});
|
|
272
|
-
if (!ttydProcess?.pid) {
|
|
273
|
-
fatal('Failed to start ttyd process for builder');
|
|
274
|
-
}
|
|
275
|
-
return { port, pid: ttydProcess.pid, sessionName };
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Start a shell session (no worktree, just tmux + ttyd)
|
|
279
|
-
*/
|
|
280
|
-
async function startShellSession(config, shellId, baseCmd) {
|
|
281
|
-
const port = await findFreePort(config);
|
|
282
|
-
const sessionName = `shell-${shellId}`;
|
|
283
|
-
logger.info('Creating tmux session...');
|
|
284
|
-
// Shell mode: just launch Claude with no prompt
|
|
285
|
-
await run(`tmux new-session -d -s "${sessionName}" -x 200 -y 50 -c "${config.projectRoot}" "${baseCmd}"`);
|
|
286
|
-
await run(`tmux set-option -t "${sessionName}" status off`);
|
|
287
|
-
// Enable mouse scrolling in tmux
|
|
288
|
-
await run('tmux set -g mouse on');
|
|
289
|
-
await run('tmux set -g set-clipboard on');
|
|
290
|
-
await run('tmux set -g allow-passthrough on');
|
|
291
|
-
// Copy selection to clipboard when mouse is released (pbcopy for macOS)
|
|
292
|
-
await run('tmux bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"');
|
|
293
|
-
await run('tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"');
|
|
294
|
-
// Start ttyd connecting to the tmux session
|
|
295
|
-
logger.info('Starting shell terminal...');
|
|
296
|
-
const customIndexPath = resolve(config.templatesDir, 'ttyd-index.html');
|
|
297
|
-
const hasCustomIndex = existsSync(customIndexPath);
|
|
298
|
-
const ttydProcess = spawnTtyd({
|
|
299
|
-
port,
|
|
300
|
-
sessionName,
|
|
301
|
-
cwd: config.projectRoot,
|
|
302
|
-
customIndexPath: hasCustomIndex ? customIndexPath : undefined,
|
|
303
|
-
});
|
|
304
|
-
if (!ttydProcess?.pid) {
|
|
305
|
-
fatal('Failed to start ttyd process for shell');
|
|
306
|
-
}
|
|
307
|
-
return { port, pid: ttydProcess.pid, sessionName };
|
|
308
|
-
}
|
|
309
100
|
// =============================================================================
|
|
310
101
|
// Mode-specific spawn implementations
|
|
311
102
|
// =============================================================================
|
|
@@ -326,56 +117,53 @@ async function spawnSpec(options, config) {
|
|
|
326
117
|
// Check for corresponding plan file
|
|
327
118
|
const planFile = resolve(config.codevDir, 'plans', `${specName}.md`);
|
|
328
119
|
const hasPlan = existsSync(planFile);
|
|
329
|
-
logger.header(
|
|
120
|
+
logger.header(`${options.resume ? 'Resuming' : 'Spawning'} Builder ${builderId} (spec)`);
|
|
330
121
|
logger.kv('Spec', specFile);
|
|
331
122
|
logger.kv('Branch', branchName);
|
|
332
123
|
logger.kv('Worktree', worktreePath);
|
|
333
124
|
await ensureDirectories(config);
|
|
334
125
|
await checkDependencies();
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
126
|
+
if (options.resume) {
|
|
127
|
+
validateResumeWorktree(worktreePath);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
await createWorktree(config, branchName, worktreePath);
|
|
131
|
+
}
|
|
132
|
+
const protocol = await resolveProtocol(options, config);
|
|
133
|
+
const protocolDef = loadProtocol(config, protocol);
|
|
134
|
+
const mode = resolveMode(options, protocolDef);
|
|
135
|
+
logger.kv('Protocol', protocol.toUpperCase());
|
|
136
|
+
logger.kv('Mode', mode.toUpperCase());
|
|
137
|
+
// Pre-initialize porch so the builder doesn't need to figure out project ID
|
|
138
|
+
if (!options.resume) {
|
|
139
|
+
const porchProjectName = specName.replace(new RegExp(`^${projectId}-`), '');
|
|
140
|
+
await initPorchInWorktree(worktreePath, protocol, projectId, porchProjectName);
|
|
141
|
+
}
|
|
342
142
|
const specRelPath = `codev/specs/${specName}.md`;
|
|
343
143
|
const planRelPath = `codev/plans/${specName}.md`;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
initialPrompt
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
const builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.
|
|
357
|
-
|
|
358
|
-
${initialPrompt}`;
|
|
359
|
-
// Load role
|
|
144
|
+
const templateContext = {
|
|
145
|
+
protocol_name: protocol.toUpperCase(), mode,
|
|
146
|
+
mode_soft: mode === 'soft', mode_strict: mode === 'strict',
|
|
147
|
+
project_id: projectId,
|
|
148
|
+
input_description: `the feature specified in ${specRelPath}`,
|
|
149
|
+
spec: { path: specRelPath, name: specName },
|
|
150
|
+
};
|
|
151
|
+
if (hasPlan)
|
|
152
|
+
templateContext.plan = { path: planRelPath, name: specName };
|
|
153
|
+
const initialPrompt = buildPromptFromTemplate(config, protocol, templateContext);
|
|
154
|
+
const resumeNotice = options.resume ? `\n${buildResumeNotice(projectId)}\n` : '';
|
|
155
|
+
const builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.\n${resumeNotice}\n${initialPrompt}`;
|
|
360
156
|
const role = options.noRole ? null : loadRolePrompt(config, 'builder');
|
|
361
157
|
const commands = getResolvedCommands();
|
|
362
|
-
const {
|
|
363
|
-
|
|
364
|
-
id: builderId,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
pid,
|
|
368
|
-
status: 'spawning',
|
|
369
|
-
phase: 'init',
|
|
370
|
-
worktree: worktreePath,
|
|
371
|
-
branch: branchName,
|
|
372
|
-
tmuxSession: sessionName,
|
|
373
|
-
type: 'spec',
|
|
374
|
-
};
|
|
375
|
-
upsertBuilder(builder);
|
|
158
|
+
const { terminalId } = await startBuilderSession(config, builderId, worktreePath, commands.builder, builderPrompt, role?.content ?? null, role?.source ?? null);
|
|
159
|
+
upsertBuilder({
|
|
160
|
+
id: builderId, name: specName, status: 'implementing', phase: 'init',
|
|
161
|
+
worktree: worktreePath, branch: branchName, type: 'spec', terminalId,
|
|
162
|
+
});
|
|
376
163
|
logger.blank();
|
|
377
164
|
logger.success(`Builder ${builderId} spawned!`);
|
|
378
|
-
logger.kv('
|
|
165
|
+
logger.kv('Mode', mode === 'strict' ? 'Strict (porch-driven)' : 'Soft (protocol-guided)');
|
|
166
|
+
logger.kv('Terminal', `ws://localhost:${DEFAULT_TOWER_PORT}/ws/terminal/${terminalId}`);
|
|
379
167
|
}
|
|
380
168
|
/**
|
|
381
169
|
* Spawn builder for an ad-hoc task
|
|
@@ -386,7 +174,7 @@ async function spawnTask(options, config) {
|
|
|
386
174
|
const builderId = `task-${shortId}`;
|
|
387
175
|
const branchName = `builder/task-${shortId}`;
|
|
388
176
|
const worktreePath = resolve(config.buildersDir, builderId);
|
|
389
|
-
logger.header(
|
|
177
|
+
logger.header(`${options.resume ? 'Resuming' : 'Spawning'} Builder ${builderId} (task)`);
|
|
390
178
|
logger.kv('Task', taskText.substring(0, 60) + (taskText.length > 60 ? '...' : ''));
|
|
391
179
|
logger.kv('Branch', branchName);
|
|
392
180
|
logger.kv('Worktree', worktreePath);
|
|
@@ -395,34 +183,46 @@ async function spawnTask(options, config) {
|
|
|
395
183
|
}
|
|
396
184
|
await ensureDirectories(config);
|
|
397
185
|
await checkDependencies();
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
186
|
+
if (options.resume) {
|
|
187
|
+
validateResumeWorktree(worktreePath);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
await createWorktree(config, branchName, worktreePath);
|
|
191
|
+
}
|
|
192
|
+
let taskDescription = taskText;
|
|
401
193
|
if (options.files && options.files.length > 0) {
|
|
402
|
-
|
|
194
|
+
taskDescription += `\n\nRelevant files to consider:\n${options.files.map(f => `- ${f}`).join('\n')}`;
|
|
195
|
+
}
|
|
196
|
+
const hasExplicitProtocol = options.protocol || options.useProtocol;
|
|
197
|
+
const resumeNotice = options.resume ? `\n${buildResumeNotice(builderId)}\n` : '';
|
|
198
|
+
let builderPrompt;
|
|
199
|
+
if (hasExplicitProtocol) {
|
|
200
|
+
const protocol = await resolveProtocol(options, config);
|
|
201
|
+
const protocolDef = loadProtocol(config, protocol);
|
|
202
|
+
const mode = resolveMode(options, protocolDef);
|
|
203
|
+
const templateContext = {
|
|
204
|
+
protocol_name: protocol.toUpperCase(), mode,
|
|
205
|
+
mode_soft: mode === 'soft', mode_strict: mode === 'strict',
|
|
206
|
+
project_id: builderId, input_description: 'an ad-hoc task', task_text: taskDescription,
|
|
207
|
+
};
|
|
208
|
+
const prompt = buildPromptFromTemplate(config, protocol, templateContext);
|
|
209
|
+
builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.\n${resumeNotice}\n${prompt}`;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.\n${resumeNotice}\n# Task\n\n${taskDescription}`;
|
|
403
213
|
}
|
|
404
|
-
const builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition. ${prompt}`;
|
|
405
|
-
// Load role
|
|
406
214
|
const role = options.noRole ? null : loadRolePrompt(config, 'builder');
|
|
407
215
|
const commands = getResolvedCommands();
|
|
408
|
-
const {
|
|
409
|
-
|
|
216
|
+
const { terminalId } = await startBuilderSession(config, builderId, worktreePath, commands.builder, builderPrompt, role?.content ?? null, role?.source ?? null);
|
|
217
|
+
upsertBuilder({
|
|
410
218
|
id: builderId,
|
|
411
219
|
name: `Task: ${taskText.substring(0, 30)}${taskText.length > 30 ? '...' : ''}`,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
phase: 'init',
|
|
416
|
-
worktree: worktreePath,
|
|
417
|
-
branch: branchName,
|
|
418
|
-
tmuxSession: sessionName,
|
|
419
|
-
type: 'task',
|
|
420
|
-
taskText,
|
|
421
|
-
};
|
|
422
|
-
upsertBuilder(builder);
|
|
220
|
+
status: 'implementing', phase: 'init',
|
|
221
|
+
worktree: worktreePath, branch: branchName, type: 'task', taskText, terminalId,
|
|
222
|
+
});
|
|
423
223
|
logger.blank();
|
|
424
224
|
logger.success(`Builder ${builderId} spawned!`);
|
|
425
|
-
logger.kv('Terminal', `
|
|
225
|
+
logger.kv('Terminal', `ws://localhost:${DEFAULT_TOWER_PORT}/ws/terminal/${terminalId}`);
|
|
426
226
|
}
|
|
427
227
|
/**
|
|
428
228
|
* Spawn builder to run a protocol
|
|
@@ -434,36 +234,41 @@ async function spawnProtocol(options, config) {
|
|
|
434
234
|
const builderId = `${protocolName}-${shortId}`;
|
|
435
235
|
const branchName = `builder/${protocolName}-${shortId}`;
|
|
436
236
|
const worktreePath = resolve(config.buildersDir, builderId);
|
|
437
|
-
logger.header(
|
|
237
|
+
logger.header(`${options.resume ? 'Resuming' : 'Spawning'} Builder ${builderId} (protocol)`);
|
|
438
238
|
logger.kv('Protocol', protocolName);
|
|
439
239
|
logger.kv('Branch', branchName);
|
|
440
240
|
logger.kv('Worktree', worktreePath);
|
|
441
241
|
await ensureDirectories(config);
|
|
442
242
|
await checkDependencies();
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
243
|
+
if (options.resume) {
|
|
244
|
+
validateResumeWorktree(worktreePath);
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
await createWorktree(config, branchName, worktreePath);
|
|
248
|
+
}
|
|
249
|
+
const protocolDef = loadProtocol(config, protocolName);
|
|
250
|
+
const mode = resolveMode(options, protocolDef);
|
|
251
|
+
logger.kv('Mode', mode.toUpperCase());
|
|
252
|
+
const templateContext = {
|
|
253
|
+
protocol_name: protocolName.toUpperCase(), mode,
|
|
254
|
+
mode_soft: mode === 'soft', mode_strict: mode === 'strict',
|
|
255
|
+
project_id: builderId,
|
|
256
|
+
input_description: `running the ${protocolName.toUpperCase()} protocol`,
|
|
257
|
+
};
|
|
258
|
+
const promptContent = buildPromptFromTemplate(config, protocolName, templateContext);
|
|
259
|
+
const resumeNotice = options.resume ? `\n${buildResumeNotice(builderId)}\n` : '';
|
|
260
|
+
const prompt = resumeNotice ? `${resumeNotice}\n${promptContent}` : promptContent;
|
|
447
261
|
const role = options.noRole ? null : loadProtocolRole(config, protocolName);
|
|
448
262
|
const commands = getResolvedCommands();
|
|
449
|
-
const {
|
|
450
|
-
|
|
451
|
-
id: builderId,
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
status: 'spawning',
|
|
456
|
-
phase: 'init',
|
|
457
|
-
worktree: worktreePath,
|
|
458
|
-
branch: branchName,
|
|
459
|
-
tmuxSession: sessionName,
|
|
460
|
-
type: 'protocol',
|
|
461
|
-
protocolName,
|
|
462
|
-
};
|
|
463
|
-
upsertBuilder(builder);
|
|
263
|
+
const { terminalId } = await startBuilderSession(config, builderId, worktreePath, commands.builder, prompt, role?.content ?? null, role?.source ?? null);
|
|
264
|
+
upsertBuilder({
|
|
265
|
+
id: builderId, name: `Protocol: ${protocolName}`,
|
|
266
|
+
status: 'implementing', phase: 'init',
|
|
267
|
+
worktree: worktreePath, branch: branchName, type: 'protocol', protocolName, terminalId,
|
|
268
|
+
});
|
|
464
269
|
logger.blank();
|
|
465
270
|
logger.success(`Builder ${builderId} spawned!`);
|
|
466
|
-
logger.kv('Terminal', `
|
|
271
|
+
logger.kv('Terminal', `ws://localhost:${DEFAULT_TOWER_PORT}/ws/terminal/${terminalId}`);
|
|
467
272
|
}
|
|
468
273
|
/**
|
|
469
274
|
* Spawn a bare shell session (no worktree, no prompt)
|
|
@@ -475,25 +280,15 @@ async function spawnShell(options, config) {
|
|
|
475
280
|
await ensureDirectories(config);
|
|
476
281
|
await checkDependencies();
|
|
477
282
|
const commands = getResolvedCommands();
|
|
478
|
-
const {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
port,
|
|
485
|
-
pid,
|
|
486
|
-
status: 'spawning',
|
|
487
|
-
phase: 'interactive',
|
|
488
|
-
worktree: '',
|
|
489
|
-
branch: '',
|
|
490
|
-
tmuxSession: sessionName,
|
|
491
|
-
type: 'shell',
|
|
492
|
-
};
|
|
493
|
-
upsertBuilder(builder);
|
|
283
|
+
const { terminalId } = await startShellSession(config, shortId, commands.builder);
|
|
284
|
+
upsertBuilder({
|
|
285
|
+
id: shellId, name: 'Shell session',
|
|
286
|
+
status: 'implementing', phase: 'interactive',
|
|
287
|
+
worktree: '', branch: '', type: 'shell', terminalId,
|
|
288
|
+
});
|
|
494
289
|
logger.blank();
|
|
495
290
|
logger.success(`Shell ${shellId} spawned!`);
|
|
496
|
-
logger.kv('Terminal', `
|
|
291
|
+
logger.kv('Terminal', `ws://localhost:${DEFAULT_TOWER_PORT}/ws/terminal/${terminalId}`);
|
|
497
292
|
}
|
|
498
293
|
/**
|
|
499
294
|
* Spawn a worktree session (has worktree/branch, but no initial prompt)
|
|
@@ -504,167 +299,42 @@ async function spawnWorktree(options, config) {
|
|
|
504
299
|
const builderId = `worktree-${shortId}`;
|
|
505
300
|
const branchName = `builder/worktree-${shortId}`;
|
|
506
301
|
const worktreePath = resolve(config.buildersDir, builderId);
|
|
507
|
-
logger.header(
|
|
302
|
+
logger.header(`${options.resume ? 'Resuming' : 'Spawning'} Worktree ${builderId}`);
|
|
508
303
|
logger.kv('Branch', branchName);
|
|
509
304
|
logger.kv('Worktree', worktreePath);
|
|
510
305
|
await ensureDirectories(config);
|
|
511
306
|
await checkDependencies();
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
const role = options.noRole ? null : loadRolePrompt(config, 'builder');
|
|
515
|
-
const commands = getResolvedCommands();
|
|
516
|
-
// Worktree mode: launch Claude with no prompt, but in the worktree directory
|
|
517
|
-
const port = await findFreePort(config);
|
|
518
|
-
const sessionName = getSessionName(config, builderId);
|
|
519
|
-
logger.info('Creating tmux session...');
|
|
520
|
-
// Build launch script (with role if provided) to avoid shell escaping issues
|
|
521
|
-
const scriptPath = resolve(worktreePath, '.builder-start.sh');
|
|
522
|
-
let scriptContent;
|
|
523
|
-
if (role) {
|
|
524
|
-
const roleFile = resolve(worktreePath, '.builder-role.md');
|
|
525
|
-
// Inject the actual dashboard port into the role prompt
|
|
526
|
-
const roleWithPort = role.content.replace(/\{PORT\}/g, String(config.dashboardPort));
|
|
527
|
-
writeFileSync(roleFile, roleWithPort);
|
|
528
|
-
logger.info(`Loaded role (${role.source})`);
|
|
529
|
-
scriptContent = `#!/bin/bash
|
|
530
|
-
cd "${worktreePath}"
|
|
531
|
-
while true; do
|
|
532
|
-
${commands.builder} --append-system-prompt "$(cat '${roleFile}')"
|
|
533
|
-
echo ""
|
|
534
|
-
echo "Claude exited. Restarting in 2 seconds... (Ctrl+C to quit)"
|
|
535
|
-
sleep 2
|
|
536
|
-
done
|
|
537
|
-
`;
|
|
307
|
+
if (options.resume) {
|
|
308
|
+
validateResumeWorktree(worktreePath);
|
|
538
309
|
}
|
|
539
310
|
else {
|
|
540
|
-
|
|
541
|
-
cd "${worktreePath}"
|
|
542
|
-
while true; do
|
|
543
|
-
${commands.builder}
|
|
544
|
-
echo ""
|
|
545
|
-
echo "Claude exited. Restarting in 2 seconds... (Ctrl+C to quit)"
|
|
546
|
-
sleep 2
|
|
547
|
-
done
|
|
548
|
-
`;
|
|
311
|
+
await createWorktree(config, branchName, worktreePath);
|
|
549
312
|
}
|
|
313
|
+
const role = options.noRole ? null : loadRolePrompt(config, 'builder');
|
|
314
|
+
const commands = getResolvedCommands();
|
|
315
|
+
logger.info('Creating terminal session...');
|
|
316
|
+
const scriptContent = buildWorktreeLaunchScript(worktreePath, commands.builder, role);
|
|
317
|
+
const scriptPath = resolve(worktreePath, '.builder-start.sh');
|
|
550
318
|
writeFileSync(scriptPath, scriptContent, { mode: 0o755 });
|
|
551
|
-
|
|
552
|
-
await
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
await run('tmux bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"');
|
|
560
|
-
await run('tmux bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel "pbcopy"');
|
|
561
|
-
// Start ttyd connecting to the tmux session
|
|
562
|
-
logger.info('Starting worktree terminal...');
|
|
563
|
-
const customIndexPath = resolve(config.codevDir, 'templates', 'ttyd-index.html');
|
|
564
|
-
const hasCustomIndex = existsSync(customIndexPath);
|
|
565
|
-
if (hasCustomIndex) {
|
|
566
|
-
logger.info('Using custom terminal with file click support');
|
|
567
|
-
}
|
|
568
|
-
const ttydProcess = spawnTtyd({
|
|
569
|
-
port,
|
|
570
|
-
sessionName,
|
|
571
|
-
cwd: worktreePath,
|
|
572
|
-
customIndexPath: hasCustomIndex ? customIndexPath : undefined,
|
|
319
|
+
logger.info('Creating PTY terminal session for worktree...');
|
|
320
|
+
const { terminalId: worktreeTerminalId } = await createPtySession(config, '/bin/bash', [scriptPath], worktreePath, { projectPath: config.projectRoot, type: 'builder', roleId: builderId });
|
|
321
|
+
logger.info(`Worktree terminal session created: ${worktreeTerminalId}`);
|
|
322
|
+
upsertBuilder({
|
|
323
|
+
id: builderId, name: 'Worktree session',
|
|
324
|
+
status: 'implementing', phase: 'interactive',
|
|
325
|
+
worktree: worktreePath, branch: branchName, type: 'worktree',
|
|
326
|
+
terminalId: worktreeTerminalId,
|
|
573
327
|
});
|
|
574
|
-
if (!ttydProcess?.pid) {
|
|
575
|
-
fatal('Failed to start ttyd process for worktree');
|
|
576
|
-
}
|
|
577
|
-
const builder = {
|
|
578
|
-
id: builderId,
|
|
579
|
-
name: 'Worktree session',
|
|
580
|
-
port,
|
|
581
|
-
pid: ttydProcess.pid,
|
|
582
|
-
status: 'spawning',
|
|
583
|
-
phase: 'interactive',
|
|
584
|
-
worktree: worktreePath,
|
|
585
|
-
branch: branchName,
|
|
586
|
-
tmuxSession: sessionName,
|
|
587
|
-
type: 'worktree',
|
|
588
|
-
};
|
|
589
|
-
upsertBuilder(builder);
|
|
590
328
|
logger.blank();
|
|
591
329
|
logger.success(`Worktree ${builderId} spawned!`);
|
|
592
|
-
logger.kv('Terminal', `
|
|
593
|
-
}
|
|
594
|
-
/**
|
|
595
|
-
* Generate a slug from an issue title (max 30 chars, lowercase, alphanumeric + hyphens)
|
|
596
|
-
*/
|
|
597
|
-
function slugify(title) {
|
|
598
|
-
return title
|
|
599
|
-
.toLowerCase()
|
|
600
|
-
.replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens
|
|
601
|
-
.replace(/-+/g, '-') // Collapse multiple hyphens
|
|
602
|
-
.replace(/^-|-$/g, '') // Trim leading/trailing hyphens
|
|
603
|
-
.slice(0, 30); // Max 30 chars
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Fetch a GitHub issue via gh CLI
|
|
607
|
-
*/
|
|
608
|
-
async function fetchGitHubIssue(issueNumber) {
|
|
609
|
-
try {
|
|
610
|
-
const result = await run(`gh issue view ${issueNumber} --json title,body,state,comments`);
|
|
611
|
-
return JSON.parse(result.stdout);
|
|
612
|
-
}
|
|
613
|
-
catch (error) {
|
|
614
|
-
fatal(`Failed to fetch issue #${issueNumber}. Ensure 'gh' CLI is installed and authenticated.`);
|
|
615
|
-
throw error; // TypeScript doesn't know fatal() never returns
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
/**
|
|
619
|
-
* Check for collision conditions before spawning bugfix
|
|
620
|
-
*/
|
|
621
|
-
async function checkBugfixCollisions(issueNumber, worktreePath, issue, force) {
|
|
622
|
-
// 1. Check if worktree already exists
|
|
623
|
-
if (existsSync(worktreePath)) {
|
|
624
|
-
fatal(`Worktree already exists at ${worktreePath}\nRun: af cleanup --issue ${issueNumber}`);
|
|
625
|
-
}
|
|
626
|
-
// 2. Check for recent "On it" comments (< 24h old)
|
|
627
|
-
const onItComments = issue.comments.filter((c) => c.body.toLowerCase().includes('on it'));
|
|
628
|
-
if (onItComments.length > 0) {
|
|
629
|
-
const lastComment = onItComments[onItComments.length - 1];
|
|
630
|
-
const age = Date.now() - new Date(lastComment.createdAt).getTime();
|
|
631
|
-
const hoursAgo = Math.round(age / (1000 * 60 * 60));
|
|
632
|
-
if (hoursAgo < 24) {
|
|
633
|
-
if (!force) {
|
|
634
|
-
fatal(`Issue #${issueNumber} has "On it" comment from ${hoursAgo}h ago (by @${lastComment.author.login}).\nSomeone may already be working on this. Use --force to override.`);
|
|
635
|
-
}
|
|
636
|
-
logger.warn(`Warning: "On it" comment from ${hoursAgo}h ago - proceeding with --force`);
|
|
637
|
-
}
|
|
638
|
-
else {
|
|
639
|
-
logger.warn(`Warning: Stale "On it" comment (${hoursAgo}h ago). Proceeding.`);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
// 3. Check for open PRs referencing this issue
|
|
643
|
-
try {
|
|
644
|
-
const prResult = await run(`gh pr list --search "in:body #${issueNumber}" --json number,title --limit 5`);
|
|
645
|
-
const openPRs = JSON.parse(prResult.stdout);
|
|
646
|
-
if (openPRs.length > 0) {
|
|
647
|
-
if (!force) {
|
|
648
|
-
const prList = openPRs.map((pr) => ` - PR #${pr.number}: ${pr.title}`).join('\n');
|
|
649
|
-
fatal(`Found ${openPRs.length} open PR(s) referencing issue #${issueNumber}:\n${prList}\nUse --force to proceed anyway.`);
|
|
650
|
-
}
|
|
651
|
-
logger.warn(`Warning: Found ${openPRs.length} open PR(s) referencing issue - proceeding with --force`);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
catch {
|
|
655
|
-
// Non-fatal: continue if PR check fails
|
|
656
|
-
}
|
|
657
|
-
// 4. Warn if issue is already closed
|
|
658
|
-
if (issue.state === 'CLOSED') {
|
|
659
|
-
logger.warn(`Warning: Issue #${issueNumber} is already closed`);
|
|
660
|
-
}
|
|
330
|
+
logger.kv('Terminal', `ws://localhost:${DEFAULT_TOWER_PORT}/ws/terminal/${worktreeTerminalId}`);
|
|
661
331
|
}
|
|
662
332
|
/**
|
|
663
333
|
* Spawn builder for a GitHub issue (bugfix mode)
|
|
664
334
|
*/
|
|
665
335
|
async function spawnBugfix(options, config) {
|
|
666
336
|
const issueNumber = options.issue;
|
|
667
|
-
logger.header(
|
|
337
|
+
logger.header(`${options.resume ? 'Resuming' : 'Spawning'} Bugfix Builder for Issue #${issueNumber}`);
|
|
668
338
|
// Fetch issue from GitHub
|
|
669
339
|
logger.info('Fetching issue from GitHub...');
|
|
670
340
|
const issue = await fetchGitHubIssue(issueNumber);
|
|
@@ -672,70 +342,72 @@ async function spawnBugfix(options, config) {
|
|
|
672
342
|
const builderId = `bugfix-${issueNumber}`;
|
|
673
343
|
const branchName = `builder/bugfix-${issueNumber}-${slug}`;
|
|
674
344
|
const worktreePath = resolve(config.buildersDir, builderId);
|
|
345
|
+
const protocol = await resolveProtocol(options, config);
|
|
346
|
+
const protocolDef = loadProtocol(config, protocol);
|
|
347
|
+
const mode = resolveMode(options, protocolDef);
|
|
675
348
|
logger.kv('Title', issue.title);
|
|
676
349
|
logger.kv('Branch', branchName);
|
|
677
350
|
logger.kv('Worktree', worktreePath);
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
351
|
+
logger.kv('Protocol', protocol.toUpperCase());
|
|
352
|
+
logger.kv('Mode', mode.toUpperCase());
|
|
353
|
+
// Execute pre-spawn hooks (skip in resume mode)
|
|
354
|
+
if (!options.resume) {
|
|
355
|
+
if (protocolDef?.hooks?.['pre-spawn']) {
|
|
356
|
+
await executePreSpawnHooks(protocolDef, {
|
|
357
|
+
issueNumber,
|
|
358
|
+
issue,
|
|
359
|
+
worktreePath,
|
|
360
|
+
force: options.force,
|
|
361
|
+
noComment: options.noComment,
|
|
362
|
+
});
|
|
688
363
|
}
|
|
689
|
-
|
|
690
|
-
|
|
364
|
+
else {
|
|
365
|
+
// Fallback: hardcoded behavior for backwards compatibility
|
|
366
|
+
await checkBugfixCollisions(issueNumber, worktreePath, issue, !!options.force);
|
|
367
|
+
if (!options.noComment) {
|
|
368
|
+
logger.info('Commenting on issue...');
|
|
369
|
+
try {
|
|
370
|
+
await run(`gh issue comment ${issueNumber} --body "On it! Working on a fix now."`);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
logger.warn('Warning: Failed to comment on issue (continuing anyway)');
|
|
374
|
+
}
|
|
375
|
+
}
|
|
691
376
|
}
|
|
692
377
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
If the fix is too complex (> 300 LOC or architectural changes), notify the Architect via:
|
|
714
|
-
af send architect "Issue #${issueNumber} is more complex than expected. [Reason]. Recommend escalating to SPIDER/TICK."
|
|
715
|
-
|
|
716
|
-
Start by reading the issue and reproducing the bug.`;
|
|
717
|
-
const builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.\n\n${prompt}`;
|
|
718
|
-
// Load role
|
|
378
|
+
await ensureDirectories(config);
|
|
379
|
+
await checkDependencies();
|
|
380
|
+
if (options.resume) {
|
|
381
|
+
validateResumeWorktree(worktreePath);
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
await createWorktree(config, branchName, worktreePath);
|
|
385
|
+
// Pre-initialize porch so the builder doesn't need to figure out project ID
|
|
386
|
+
await initPorchInWorktree(worktreePath, protocol, builderId, slug);
|
|
387
|
+
}
|
|
388
|
+
const templateContext = {
|
|
389
|
+
protocol_name: protocol.toUpperCase(), mode,
|
|
390
|
+
mode_soft: mode === 'soft', mode_strict: mode === 'strict',
|
|
391
|
+
project_id: builderId,
|
|
392
|
+
input_description: `a fix for GitHub Issue #${issueNumber}`,
|
|
393
|
+
issue: { number: issueNumber, title: issue.title, body: issue.body || '(No description provided)' },
|
|
394
|
+
};
|
|
395
|
+
const prompt = buildPromptFromTemplate(config, protocol, templateContext);
|
|
396
|
+
const resumeNotice = options.resume ? `\n${buildResumeNotice(builderId)}\n` : '';
|
|
397
|
+
const builderPrompt = `You are a Builder. Read codev/roles/builder.md for your full role definition.\n${resumeNotice}\n${prompt}`;
|
|
719
398
|
const role = options.noRole ? null : loadRolePrompt(config, 'builder');
|
|
720
399
|
const commands = getResolvedCommands();
|
|
721
|
-
const {
|
|
722
|
-
|
|
400
|
+
const { terminalId } = await startBuilderSession(config, builderId, worktreePath, commands.builder, builderPrompt, role?.content ?? null, role?.source ?? null);
|
|
401
|
+
upsertBuilder({
|
|
723
402
|
id: builderId,
|
|
724
403
|
name: `Bugfix #${issueNumber}: ${issue.title.substring(0, 40)}${issue.title.length > 40 ? '...' : ''}`,
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
phase: 'init',
|
|
729
|
-
worktree: worktreePath,
|
|
730
|
-
branch: branchName,
|
|
731
|
-
tmuxSession: sessionName,
|
|
732
|
-
type: 'bugfix',
|
|
733
|
-
issueNumber,
|
|
734
|
-
};
|
|
735
|
-
upsertBuilder(builder);
|
|
404
|
+
status: 'implementing', phase: 'init',
|
|
405
|
+
worktree: worktreePath, branch: branchName, type: 'bugfix', issueNumber, terminalId,
|
|
406
|
+
});
|
|
736
407
|
logger.blank();
|
|
737
408
|
logger.success(`Bugfix builder for issue #${issueNumber} spawned!`);
|
|
738
|
-
logger.kv('
|
|
409
|
+
logger.kv('Mode', mode === 'strict' ? 'Strict (porch-driven)' : 'Soft (protocol-guided)');
|
|
410
|
+
logger.kv('Terminal', `ws://localhost:${DEFAULT_TOWER_PORT}/ws/terminal/${terminalId}`);
|
|
739
411
|
}
|
|
740
412
|
// =============================================================================
|
|
741
413
|
// Main entry point
|
|
@@ -746,6 +418,25 @@ Start by reading the issue and reproducing the bug.`;
|
|
|
746
418
|
export async function spawn(options) {
|
|
747
419
|
validateSpawnOptions(options);
|
|
748
420
|
const config = getConfig();
|
|
421
|
+
// Refuse to spawn if the main worktree has uncommitted changes.
|
|
422
|
+
// Builders work in git worktrees branched from HEAD — uncommitted changes
|
|
423
|
+
// (specs, plans, codev updates) won't be visible to the builder.
|
|
424
|
+
// Skip this check in resume mode — the worktree already exists with its own branch.
|
|
425
|
+
if (!options.force && !options.resume) {
|
|
426
|
+
try {
|
|
427
|
+
const { stdout } = await run('git status --porcelain', { cwd: config.projectRoot });
|
|
428
|
+
if (stdout.trim().length > 0) {
|
|
429
|
+
fatal('Uncommitted changes detected in main worktree.\n\n' +
|
|
430
|
+
' Builders branch from HEAD, so uncommitted files (specs, plans,\n' +
|
|
431
|
+
' codev updates) will NOT be visible to the builder.\n\n' +
|
|
432
|
+
' Please commit or stash your changes first, then retry.\n' +
|
|
433
|
+
' Use --force to skip this check.');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// Non-fatal — if git status fails, allow spawn to continue
|
|
438
|
+
}
|
|
439
|
+
}
|
|
749
440
|
// Prune stale worktrees before spawning to prevent "can't find session" errors
|
|
750
441
|
// This catches orphaned worktrees from crashes, manual kills, or incomplete cleanups
|
|
751
442
|
try {
|
|
@@ -755,25 +446,14 @@ export async function spawn(options) {
|
|
|
755
446
|
// Non-fatal - continue with spawn even if prune fails
|
|
756
447
|
}
|
|
757
448
|
const mode = getSpawnMode(options);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
break;
|
|
768
|
-
case 'protocol':
|
|
769
|
-
await spawnProtocol(options, config);
|
|
770
|
-
break;
|
|
771
|
-
case 'shell':
|
|
772
|
-
await spawnShell(options, config);
|
|
773
|
-
break;
|
|
774
|
-
case 'worktree':
|
|
775
|
-
await spawnWorktree(options, config);
|
|
776
|
-
break;
|
|
777
|
-
}
|
|
449
|
+
const handlers = {
|
|
450
|
+
spec: () => spawnSpec(options, config),
|
|
451
|
+
bugfix: () => spawnBugfix(options, config),
|
|
452
|
+
task: () => spawnTask(options, config),
|
|
453
|
+
protocol: () => spawnProtocol(options, config),
|
|
454
|
+
shell: () => spawnShell(options, config),
|
|
455
|
+
worktree: () => spawnWorktree(options, config),
|
|
456
|
+
};
|
|
457
|
+
await handlers[mode]();
|
|
778
458
|
}
|
|
779
459
|
//# sourceMappingURL=spawn.js.map
|