@cluesmith/codev 2.0.0-rc.7 → 2.0.0-rc.70
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/bin/porch.js +6 -35
- package/dashboard/dist/assets/index-C7FtNK6Y.css +32 -0
- package/dashboard/dist/assets/index-CDAINZKT.js +131 -0
- package/dashboard/dist/assets/index-CDAINZKT.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 +173 -118
- 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 +60 -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.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +597 -281
- 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 -491
- 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 +334 -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 +246 -18
- 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/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-server.js +2650 -185
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/state.d.ts +6 -12
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +34 -49
- 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 +11 -54
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +49 -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 +245 -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 +41 -2
- 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 +17 -29
- package/dist/commands/porch/checks.d.ts.map +1 -1
- package/dist/commands/porch/checks.js +96 -144
- package/dist/commands/porch/checks.js.map +1 -1
- package/dist/commands/porch/index.d.ts +25 -43
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +463 -1116
- 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 +70 -0
- package/dist/commands/porch/plan.d.ts.map +1 -0
- package/dist/commands/porch/plan.js +190 -0
- package/dist/commands/porch/plan.js.map +1 -0
- package/dist/commands/porch/prompts.d.ts +19 -0
- package/dist/commands/porch/prompts.d.ts.map +1 -0
- package/dist/commands/porch/prompts.js +277 -0
- package/dist/commands/porch/prompts.js.map +1 -0
- package/dist/commands/porch/protocol.d.ts +59 -0
- package/dist/commands/porch/protocol.d.ts.map +1 -0
- package/dist/commands/porch/protocol.js +294 -0
- package/dist/commands/porch/protocol.js.map +1 -0
- package/dist/commands/porch/state.d.ts +36 -107
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +120 -699
- package/dist/commands/porch/state.js.map +1 -1
- package/dist/commands/porch/types.d.ts +99 -164
- package/dist/commands/porch/types.d.ts.map +1 -1
- package/dist/commands/porch/types.js +2 -1
- package/dist/commands/porch/types.js.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 +31 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/lib/scaffold.d.ts +37 -0
- package/dist/lib/scaffold.d.ts.map +1 -1
- package/dist/lib/scaffold.js +114 -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/shepherd-client.d.ts +58 -0
- package/dist/terminal/shepherd-client.d.ts.map +1 -0
- package/dist/terminal/shepherd-client.js +212 -0
- package/dist/terminal/shepherd-client.js.map +1 -0
- package/dist/terminal/shepherd-main.d.ts +19 -0
- package/dist/terminal/shepherd-main.d.ts.map +1 -0
- package/dist/terminal/shepherd-main.js +153 -0
- package/dist/terminal/shepherd-main.js.map +1 -0
- package/dist/terminal/shepherd-process.d.ts +75 -0
- package/dist/terminal/shepherd-process.d.ts.map +1 -0
- package/dist/terminal/shepherd-process.js +279 -0
- package/dist/terminal/shepherd-process.js.map +1 -0
- package/dist/terminal/shepherd-protocol.d.ts +115 -0
- package/dist/terminal/shepherd-protocol.d.ts.map +1 -0
- package/dist/terminal/shepherd-protocol.js +214 -0
- package/dist/terminal/shepherd-protocol.js.map +1 -0
- package/dist/terminal/shepherd-replay-buffer.d.ts +38 -0
- package/dist/terminal/shepherd-replay-buffer.d.ts.map +1 -0
- package/dist/terminal/shepherd-replay-buffer.js +94 -0
- package/dist/terminal/shepherd-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 +19 -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 +54 -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 +59 -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 +33 -61
- package/skeleton/protocols/spir/protocol.json +156 -0
- package/skeleton/protocols/{spider → spir}/protocol.md +35 -21
- package/skeleton/protocols/{spider → spir}/templates/plan.md +14 -0
- 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 +152 -315
- package/skeleton/roles/builder.md +110 -218
- 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 -43
- 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 -87
- package/templates/open.html +26 -0
- package/templates/tower.html +642 -36
- 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 -337
- 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 -1872
- 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/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 -253
- package/dist/commands/porch/protocol-loader.js.map +0 -1
- package/dist/commands/porch/signal-parser.d.ts +0 -88
- package/dist/commands/porch/signal-parser.d.ts.map +0 -1
- package/dist/commands/porch/signal-parser.js +0 -148
- package/dist/commands/porch/signal-parser.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/porch/protocols/bugfix.json +0 -85
- package/skeleton/porch/protocols/spider.json +0 -135
- package/skeleton/porch/protocols/tick.json +0 -76
- 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/protocol.json +0 -210
- package/skeleton/protocols/spider/templates/review.md +0 -207
- package/templates/dashboard/css/activity.css +0 -151
- package/templates/dashboard/js/activity.js +0 -112
- /package/skeleton/protocols/{spider → spir}/templates/spec.md +0 -0
|
@@ -1,1213 +1,560 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Porch - Protocol Orchestrator
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* context per iteration with state persisted to files.
|
|
4
|
+
* Claude calls porch as a tool; porch returns prescriptive instructions.
|
|
5
|
+
* All commands produce clear, actionable output.
|
|
7
6
|
*/
|
|
8
7
|
import * as fs from 'node:fs';
|
|
9
8
|
import * as path from 'node:path';
|
|
10
|
-
import * as readline from 'node:readline';
|
|
11
|
-
import { spawn } from 'node:child_process';
|
|
12
9
|
import chalk from 'chalk';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { runPhaseChecks, formatCheckResults } from './checks.js';
|
|
18
|
-
import { runConsultationLoop, formatConsultationResults, hasConsultation, } from './consultation.js';
|
|
19
|
-
import { loadProtocol as loadProtocolFromLoader, listProtocols as listProtocolsFromLoader, } from './protocol-loader.js';
|
|
20
|
-
import { createNotifier, } from './notifications.js';
|
|
10
|
+
import { globSync } from 'glob';
|
|
11
|
+
import { readState, writeState, createInitialState, findStatusPath, getProjectDir, getStatusPath, resolveProjectId, } from './state.js';
|
|
12
|
+
import { loadProtocol, getPhaseConfig, getNextPhase, getPhaseChecks, getPhaseGate, isPhased, isBuildVerify, getVerifyConfig, } from './protocol.js';
|
|
13
|
+
import { findPlanFile, extractPhasesFromFile, getCurrentPlanPhase, getPhaseContent, allPlanPhasesComplete, } from './plan.js';
|
|
14
|
+
import { runPhaseChecks, formatCheckResults, allChecksPassed, } from './checks.js';
|
|
21
15
|
// ============================================================================
|
|
22
|
-
//
|
|
16
|
+
// Output Helpers
|
|
23
17
|
// ============================================================================
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
28
|
-
export function listProtocols(projectRoot) {
|
|
29
|
-
const root = projectRoot || findProjectRoot();
|
|
30
|
-
return listProtocolsFromLoader(root);
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Load a protocol definition
|
|
34
|
-
* Delegates to protocol-loader.ts which properly converts steps→substates
|
|
35
|
-
*/
|
|
36
|
-
export function loadProtocol(name, projectRoot) {
|
|
37
|
-
const root = projectRoot || findProjectRoot();
|
|
38
|
-
const protocol = loadProtocolFromLoader(root, name);
|
|
39
|
-
if (!protocol) {
|
|
40
|
-
throw new Error(`Protocol not found: ${name}\nAvailable protocols: ${listProtocols(root).join(', ')}`);
|
|
41
|
-
}
|
|
42
|
-
return protocol;
|
|
18
|
+
function header(text) {
|
|
19
|
+
const line = '═'.repeat(50);
|
|
20
|
+
return `${line}\n ${text}\n${line}`;
|
|
43
21
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*/
|
|
47
|
-
function loadPrompt(protocol, phaseId, projectRoot) {
|
|
48
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
49
|
-
if (!phase?.prompt) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
// New structure: protocols/<protocol>/prompts/<prompt>.md
|
|
53
|
-
const promptPaths = [
|
|
54
|
-
path.join(projectRoot, 'codev', 'protocols', protocol.name, 'prompts', phase.prompt),
|
|
55
|
-
path.join(getSkeletonDir(), 'protocols', protocol.name, 'prompts', phase.prompt),
|
|
56
|
-
// Legacy paths
|
|
57
|
-
path.join(projectRoot, 'codev', 'porch', 'prompts', phase.prompt),
|
|
58
|
-
path.join(getSkeletonDir(), 'porch', 'prompts', phase.prompt),
|
|
59
|
-
];
|
|
60
|
-
for (const promptPath of promptPaths) {
|
|
61
|
-
if (fs.existsSync(promptPath)) {
|
|
62
|
-
return fs.readFileSync(promptPath, 'utf-8');
|
|
63
|
-
}
|
|
64
|
-
// Try with .md extension
|
|
65
|
-
if (fs.existsSync(`${promptPath}.md`)) {
|
|
66
|
-
return fs.readFileSync(`${promptPath}.md`, 'utf-8');
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
22
|
+
function section(title, content) {
|
|
23
|
+
return `\n${chalk.bold(title)}:\n${content}`;
|
|
70
24
|
}
|
|
71
25
|
// ============================================================================
|
|
72
|
-
//
|
|
73
|
-
// ============================================================================
|
|
74
|
-
/**
|
|
75
|
-
* Check if a phase is terminal
|
|
76
|
-
*/
|
|
77
|
-
function isTerminalPhase(protocol, phaseId) {
|
|
78
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
79
|
-
return phase?.terminal === true;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Find the phase that has a gate blocking after the given state
|
|
83
|
-
*/
|
|
84
|
-
function getGateForState(protocol, state) {
|
|
85
|
-
const [phaseId, substate] = state.split(':');
|
|
86
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
87
|
-
if (phase?.gate && phase.gate.after === substate) {
|
|
88
|
-
const gateId = `${phaseId}_approval`;
|
|
89
|
-
return { gateId, phase };
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Get next state after gate passes
|
|
95
|
-
*/
|
|
96
|
-
function getGateNextState(protocol, phaseId) {
|
|
97
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
98
|
-
return phase?.gate?.next || null;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Get signal-based next state
|
|
102
|
-
*/
|
|
103
|
-
function getSignalNextState(protocol, phaseId, signal) {
|
|
104
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
105
|
-
return phase?.signals?.[signal] || null;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Get the default next state for a phase (first substate or next phase)
|
|
109
|
-
*/
|
|
110
|
-
function getDefaultNextState(protocol, state) {
|
|
111
|
-
const [phaseId, substate] = state.split(':');
|
|
112
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
113
|
-
if (!phase)
|
|
114
|
-
return null;
|
|
115
|
-
// If phase has substates, move to next substate
|
|
116
|
-
if (phase.substates && substate) {
|
|
117
|
-
const currentIdx = phase.substates.indexOf(substate);
|
|
118
|
-
if (currentIdx >= 0 && currentIdx < phase.substates.length - 1) {
|
|
119
|
-
return `${phaseId}:${phase.substates[currentIdx + 1]}`;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// Move to next phase
|
|
123
|
-
const phaseIdx = protocol.phases.findIndex(p => p.id === phaseId);
|
|
124
|
-
if (phaseIdx >= 0 && phaseIdx < protocol.phases.length - 1) {
|
|
125
|
-
const nextPhase = protocol.phases[phaseIdx + 1];
|
|
126
|
-
if (nextPhase.substates && nextPhase.substates.length > 0) {
|
|
127
|
-
return `${nextPhase.id}:${nextPhase.substates[0]}`;
|
|
128
|
-
}
|
|
129
|
-
return nextPhase.id;
|
|
130
|
-
}
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
// ============================================================================
|
|
134
|
-
// Claude Invocation
|
|
26
|
+
// Commands
|
|
135
27
|
// ============================================================================
|
|
136
|
-
// Note: extractSignal is now imported from signal-parser.js
|
|
137
|
-
/**
|
|
138
|
-
* Rolling buffer for last N lines of output
|
|
139
|
-
*/
|
|
140
|
-
class OutputBuffer {
|
|
141
|
-
lines = [];
|
|
142
|
-
currentLine = '';
|
|
143
|
-
maxLines;
|
|
144
|
-
constructor(maxLines = 20) {
|
|
145
|
-
this.maxLines = maxLines;
|
|
146
|
-
}
|
|
147
|
-
append(data) {
|
|
148
|
-
const text = data.toString();
|
|
149
|
-
for (const char of text) {
|
|
150
|
-
if (char === '\n') {
|
|
151
|
-
this.lines.push(this.currentLine);
|
|
152
|
-
if (this.lines.length > this.maxLines) {
|
|
153
|
-
this.lines.shift();
|
|
154
|
-
}
|
|
155
|
-
this.currentLine = '';
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
this.currentLine += char;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
getContext() {
|
|
163
|
-
return this.currentLine
|
|
164
|
-
? [...this.lines, this.currentLine]
|
|
165
|
-
: [...this.lines];
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
28
|
/**
|
|
169
|
-
*
|
|
29
|
+
* porch status <id>
|
|
30
|
+
* Shows current state and prescriptive next steps.
|
|
170
31
|
*/
|
|
171
|
-
async function
|
|
172
|
-
const
|
|
173
|
-
if (!
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
if (options.noClaude) {
|
|
182
|
-
console.log(chalk.blue(`[porch] [NO_CLAUDE] Simulating phase: ${phaseId}`));
|
|
183
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
184
|
-
console.log(chalk.green(`[porch] Simulated completion of phase: ${phaseId}`));
|
|
185
|
-
return '';
|
|
186
|
-
}
|
|
187
|
-
console.log(chalk.cyan(`[phase] Invoking Claude for phase: ${phaseId}`));
|
|
188
|
-
console.log(chalk.gray(`[porch] Press ESC to cancel`));
|
|
32
|
+
export async function status(projectRoot, projectId) {
|
|
33
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
34
|
+
if (!statusPath) {
|
|
35
|
+
throw new Error(`Project ${projectId} not found.\nRun 'porch init' to create a new project.`);
|
|
36
|
+
}
|
|
37
|
+
const state = readState(statusPath);
|
|
38
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
39
|
+
const phaseConfig = getPhaseConfig(protocol, state.phase);
|
|
40
|
+
// Header
|
|
189
41
|
console.log('');
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
Execute the ${phaseId} phase for project ${state.id} - ${state.title}
|
|
205
|
-
|
|
206
|
-
## Phase Instructions
|
|
207
|
-
${promptContent}
|
|
208
|
-
|
|
209
|
-
## Important
|
|
210
|
-
- Project ID: ${state.id}
|
|
211
|
-
- Protocol: ${protocol.name}
|
|
212
|
-
- Follow the instructions above precisely
|
|
213
|
-
- Output <signal>...</signal> tags when you reach completion points
|
|
214
|
-
`;
|
|
215
|
-
return new Promise((resolve, reject) => {
|
|
216
|
-
const args = ['--print', '-p', fullPrompt, '--dangerously-skip-permissions'];
|
|
217
|
-
const proc = spawn('claude', args, {
|
|
218
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
219
|
-
timeout,
|
|
220
|
-
});
|
|
221
|
-
let output = '';
|
|
222
|
-
let stderr = '';
|
|
223
|
-
let cancelled = false;
|
|
224
|
-
const outputBuffer = new OutputBuffer(20);
|
|
225
|
-
// Set up escape key handler
|
|
226
|
-
const wasRawMode = process.stdin.isRaw;
|
|
227
|
-
if (process.stdin.isTTY) {
|
|
228
|
-
process.stdin.setRawMode(true);
|
|
229
|
-
process.stdin.resume();
|
|
230
|
-
}
|
|
231
|
-
const escapeHandler = (key) => {
|
|
232
|
-
// ESC key is ASCII 27, Ctrl+C is 3
|
|
233
|
-
if (key[0] === 27 || key[0] === 3) {
|
|
234
|
-
cancelled = true;
|
|
235
|
-
console.log('');
|
|
236
|
-
console.log(chalk.yellow('━'.repeat(50)));
|
|
237
|
-
console.log(chalk.yellow('[porch] Cancelling Claude execution...'));
|
|
238
|
-
console.log(chalk.yellow('━'.repeat(50)));
|
|
239
|
-
// Show last 20 lines of context
|
|
240
|
-
const context = outputBuffer.getContext();
|
|
241
|
-
if (context.length > 0) {
|
|
242
|
-
console.log(chalk.gray('\nLast output:'));
|
|
243
|
-
console.log(chalk.gray('─'.repeat(40)));
|
|
244
|
-
for (const line of context.slice(-10)) {
|
|
245
|
-
console.log(chalk.gray(' ' + line.slice(0, 70)));
|
|
246
|
-
}
|
|
247
|
-
console.log(chalk.gray('─'.repeat(40)));
|
|
248
|
-
}
|
|
249
|
-
proc.kill('SIGTERM');
|
|
250
|
-
// Give it a moment then force kill
|
|
251
|
-
setTimeout(() => {
|
|
252
|
-
if (!proc.killed) {
|
|
253
|
-
proc.kill('SIGKILL');
|
|
254
|
-
}
|
|
255
|
-
}, 2000);
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
if (process.stdin.isTTY) {
|
|
259
|
-
process.stdin.on('data', escapeHandler);
|
|
260
|
-
}
|
|
261
|
-
// Periodic status update timer
|
|
262
|
-
let lineCount = 0;
|
|
263
|
-
const statusInterval = setInterval(() => {
|
|
264
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
265
|
-
const mins = Math.floor(elapsed / 60);
|
|
266
|
-
const secs = elapsed % 60;
|
|
267
|
-
const timeStr = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
268
|
-
// Use stderr so it doesn't interfere with output parsing
|
|
269
|
-
process.stderr.write(chalk.gray(`\n[porch] Running... ${lineCount} lines | ${timeStr} elapsed | ESC to cancel\n`));
|
|
270
|
-
}, 30000); // Every 30 seconds
|
|
271
|
-
const cleanup = () => {
|
|
272
|
-
clearInterval(statusInterval);
|
|
273
|
-
if (process.stdin.isTTY) {
|
|
274
|
-
process.stdin.removeListener('data', escapeHandler);
|
|
275
|
-
process.stdin.setRawMode(wasRawMode ?? false);
|
|
276
|
-
process.stdin.pause();
|
|
42
|
+
console.log(header(`PROJECT: ${state.id} - ${state.title}`));
|
|
43
|
+
console.log(` PROTOCOL: ${state.protocol}`);
|
|
44
|
+
console.log(` PHASE: ${state.phase} (${phaseConfig?.name || 'unknown'})`);
|
|
45
|
+
// For phased protocols, show plan phase status
|
|
46
|
+
if (isPhased(protocol, state.phase) && state.plan_phases.length > 0) {
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(chalk.bold('PLAN PHASES:'));
|
|
49
|
+
console.log('');
|
|
50
|
+
// Status icons
|
|
51
|
+
const icon = (status) => {
|
|
52
|
+
switch (status) {
|
|
53
|
+
case 'complete': return chalk.green('✓');
|
|
54
|
+
case 'in_progress': return chalk.yellow('►');
|
|
55
|
+
default: return chalk.gray('○');
|
|
277
56
|
}
|
|
278
57
|
};
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (cancelled) {
|
|
299
|
-
console.log(chalk.yellow(`[porch] Cancelled after ${timeStr}`));
|
|
300
|
-
resolve(''); // Return empty on cancel, don't reject
|
|
58
|
+
// Show phases
|
|
59
|
+
for (const phase of state.plan_phases) {
|
|
60
|
+
const isCurrent = phase.status === 'in_progress';
|
|
61
|
+
const prefix = isCurrent ? chalk.cyan('→ ') : ' ';
|
|
62
|
+
const title = isCurrent ? chalk.bold(phase.title) : phase.title;
|
|
63
|
+
console.log(`${prefix}${icon(phase.status)} ${phase.id}: ${title}`);
|
|
64
|
+
}
|
|
65
|
+
const currentPlanPhase = getCurrentPlanPhase(state.plan_phases);
|
|
66
|
+
if (currentPlanPhase) {
|
|
67
|
+
console.log('');
|
|
68
|
+
console.log(chalk.bold(`CURRENT: ${currentPlanPhase.id} - ${currentPlanPhase.title}`));
|
|
69
|
+
// Show phase content from plan
|
|
70
|
+
const planPath = findPlanFile(projectRoot, state.id, state.title);
|
|
71
|
+
if (planPath) {
|
|
72
|
+
const content = fs.readFileSync(planPath, 'utf-8');
|
|
73
|
+
const phaseContent = getPhaseContent(content, currentPlanPhase.id);
|
|
74
|
+
if (phaseContent) {
|
|
75
|
+
console.log(section('FROM THE PLAN', phaseContent.slice(0, 500)));
|
|
76
|
+
}
|
|
301
77
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
78
|
+
// Find the next phase name for the warning
|
|
79
|
+
const currentIdx = state.plan_phases.findIndex(p => p.id === currentPlanPhase.id);
|
|
80
|
+
const nextPlanPhase = state.plan_phases[currentIdx + 1];
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(chalk.red.bold('╔══════════════════════════════════════════════════════════════╗'));
|
|
83
|
+
console.log(chalk.red.bold('║ 🛑 CRITICAL RULES ║'));
|
|
84
|
+
if (nextPlanPhase) {
|
|
85
|
+
console.log(chalk.red.bold(`║ 1. DO NOT start ${nextPlanPhase.id} until you run porch again!`.padEnd(63) + '║'));
|
|
305
86
|
}
|
|
306
87
|
else {
|
|
307
|
-
console.log('');
|
|
308
|
-
console.log(chalk.green(`[porch] Phase complete: ${lineCount} lines in ${timeStr}`));
|
|
309
|
-
resolve(output);
|
|
88
|
+
console.log(chalk.red.bold('║ 1. DO NOT start the next phase until you run porch again! ║'));
|
|
310
89
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
reject(err);
|
|
315
|
-
});
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
// ============================================================================
|
|
319
|
-
// Commands
|
|
320
|
-
// ============================================================================
|
|
321
|
-
/**
|
|
322
|
-
* Initialize a new project with a protocol
|
|
323
|
-
*/
|
|
324
|
-
export async function init(protocolName, projectId, projectName, options = {}) {
|
|
325
|
-
const projectRoot = findProjectRoot();
|
|
326
|
-
const protocol = loadProtocol(protocolName, projectRoot);
|
|
327
|
-
// Create project directory
|
|
328
|
-
const projectDir = getProjectDir(projectRoot, projectId, projectName);
|
|
329
|
-
fs.mkdirSync(projectDir, { recursive: true });
|
|
330
|
-
// Create initial state
|
|
331
|
-
const state = createInitialState(protocol, projectId, projectName, options.worktree);
|
|
332
|
-
const statusPath = path.join(projectDir, 'status.yaml');
|
|
333
|
-
await writeState(statusPath, state);
|
|
334
|
-
console.log(chalk.green(`[porch] Initialized project ${projectId} with protocol ${protocolName}`));
|
|
335
|
-
console.log(chalk.blue(`[porch] Project directory: ${projectDir}`));
|
|
336
|
-
console.log(chalk.blue(`[porch] Initial state: ${state.current_state}`));
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Check if a protocol phase is a "phased" phase (runs per plan-phase)
|
|
340
|
-
*/
|
|
341
|
-
function isPhasedPhase(protocol, phaseId) {
|
|
342
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
343
|
-
return phase?.phased === true;
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Get the IDE phases (implement, defend, evaluate) that run per plan-phase
|
|
347
|
-
*/
|
|
348
|
-
function getIDEPhases(protocol) {
|
|
349
|
-
return protocol.phases
|
|
350
|
-
.filter(p => p.phased === true)
|
|
351
|
-
.map(p => p.id);
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Parse the current plan-phase from state like "implement:phase_1"
|
|
355
|
-
*/
|
|
356
|
-
function parsePlanPhaseFromState(state) {
|
|
357
|
-
const parts = state.split(':');
|
|
358
|
-
const phaseId = parts[0];
|
|
359
|
-
// Check if second part is a plan phase (phase_N) or a substate
|
|
360
|
-
if (parts.length > 1) {
|
|
361
|
-
if (parts[1].startsWith('phase_')) {
|
|
362
|
-
return { phaseId, planPhaseId: parts[1], substate: parts[2] || null };
|
|
90
|
+
console.log(chalk.red.bold('║ 2. Run /compact before starting each new phase ║'));
|
|
91
|
+
console.log(chalk.red.bold('║ 3. After completing this phase, run: porch done ' + state.id.padEnd(12) + '║'));
|
|
92
|
+
console.log(chalk.red.bold('╚══════════════════════════════════════════════════════════════╝'));
|
|
363
93
|
}
|
|
364
|
-
return { phaseId, planPhaseId: null, substate: parts[1] };
|
|
365
94
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
*/
|
|
372
|
-
function getNextIDEState(protocol, currentState, planPhases, signal) {
|
|
373
|
-
const { phaseId, planPhaseId } = parsePlanPhaseFromState(currentState);
|
|
374
|
-
if (!planPhaseId)
|
|
375
|
-
return null;
|
|
376
|
-
const idePhases = getIDEPhases(protocol);
|
|
377
|
-
const currentIdeIndex = idePhases.indexOf(phaseId);
|
|
378
|
-
if (currentIdeIndex < 0)
|
|
379
|
-
return null;
|
|
380
|
-
// If not at the end of IDE phases, move to next IDE phase for same plan-phase
|
|
381
|
-
if (currentIdeIndex < idePhases.length - 1) {
|
|
382
|
-
return `${idePhases[currentIdeIndex + 1]}:${planPhaseId}`;
|
|
95
|
+
// Show checks status
|
|
96
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
97
|
+
if (Object.keys(checks).length > 0) {
|
|
98
|
+
const checkLines = Object.keys(checks).map(name => ` ○ ${name} (not yet run)`);
|
|
99
|
+
console.log(section('CRITERIA', checkLines.join('\n')));
|
|
383
100
|
}
|
|
384
|
-
//
|
|
385
|
-
const
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return `${idePhases[0]}:${nextPlanPhase.id}`; // Start implement for next phase
|
|
101
|
+
// Instructions
|
|
102
|
+
const gate = getPhaseGate(protocol, state.phase);
|
|
103
|
+
if (gate && state.gates[gate]?.status === 'pending' && state.gates[gate]?.requested_at) {
|
|
104
|
+
console.log(section('STATUS', chalk.yellow('WAITING FOR HUMAN APPROVAL')));
|
|
105
|
+
console.log(`\n Gate: ${gate}`);
|
|
106
|
+
console.log(' Do not proceed until gate is approved.');
|
|
107
|
+
console.log(`\n To approve: porch approve ${state.id} ${gate}`);
|
|
392
108
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (reviewPhase) {
|
|
396
|
-
return 'review';
|
|
109
|
+
else {
|
|
110
|
+
console.log(section('INSTRUCTIONS', getInstructions(state, protocol)));
|
|
397
111
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
// ============================================================================
|
|
401
|
-
// Interactive REPL
|
|
402
|
-
// ============================================================================
|
|
403
|
-
/**
|
|
404
|
-
* Create a readline interface for interactive input
|
|
405
|
-
*/
|
|
406
|
-
function createRepl() {
|
|
407
|
-
return readline.createInterface({
|
|
408
|
-
input: process.stdin,
|
|
409
|
-
output: process.stdout,
|
|
410
|
-
terminal: true,
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
/**
|
|
414
|
-
* Prompt user for input with a given message
|
|
415
|
-
*/
|
|
416
|
-
async function prompt(rl, message) {
|
|
417
|
-
return new Promise((resolve) => {
|
|
418
|
-
rl.question(message, (answer) => {
|
|
419
|
-
resolve(answer.trim().toLowerCase());
|
|
420
|
-
});
|
|
421
|
-
});
|
|
112
|
+
console.log(section('NEXT ACTION', getNextAction(state, protocol)));
|
|
113
|
+
console.log('');
|
|
422
114
|
}
|
|
423
115
|
/**
|
|
424
|
-
*
|
|
116
|
+
* porch check <id>
|
|
117
|
+
* Runs the phase checks and reports results.
|
|
425
118
|
*/
|
|
426
|
-
function
|
|
119
|
+
export async function check(projectRoot, projectId) {
|
|
120
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
121
|
+
if (!statusPath) {
|
|
122
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
123
|
+
}
|
|
124
|
+
const state = readState(statusPath);
|
|
125
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
126
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
127
|
+
if (Object.keys(checks).length === 0) {
|
|
128
|
+
console.log(chalk.dim('No checks defined for this phase.'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
|
|
427
132
|
console.log('');
|
|
428
|
-
console.log(chalk.
|
|
429
|
-
console.log(' ' + chalk.green('approve') + ' [gate] - Approve pending gate (or current if omitted)');
|
|
430
|
-
console.log(' ' + chalk.green('view') + ' - View the full artifact for current gate');
|
|
431
|
-
console.log(' ' + chalk.green('status') + ' - Show current project status');
|
|
432
|
-
console.log(' ' + chalk.green('continue') + ' - Continue to next iteration');
|
|
433
|
-
console.log(' ' + chalk.green('skip') + ' - Skip current phase (use with caution)');
|
|
434
|
-
console.log(' ' + chalk.green('help') + ' - Show this help');
|
|
435
|
-
console.log(' ' + chalk.green('quit') + ' - Exit porch');
|
|
133
|
+
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
436
134
|
console.log('');
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
* Display current status summary
|
|
440
|
-
*/
|
|
441
|
-
function displayStatus(state, protocol) {
|
|
135
|
+
const results = await runPhaseChecks(checks, projectRoot, checkEnv);
|
|
136
|
+
console.log(formatCheckResults(results));
|
|
442
137
|
console.log('');
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
.filter(([, g]) => g.status === 'pending' && g.requested_at)
|
|
451
|
-
.map(([id]) => id);
|
|
452
|
-
if (pendingGates.length > 0) {
|
|
453
|
-
console.log(chalk.yellow(`Pending gates: ${pendingGates.join(', ')}`));
|
|
138
|
+
if (allChecksPassed(results)) {
|
|
139
|
+
console.log(chalk.green('RESULT: ALL CHECKS PASSED'));
|
|
140
|
+
console.log(`\n Run: porch done ${state.id} (to advance)`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
console.log(chalk.red('RESULT: CHECKS FAILED'));
|
|
144
|
+
console.log(`\n Fix the failures and run: porch check ${state.id}`);
|
|
454
145
|
}
|
|
455
|
-
console.log(chalk.blue('─'.repeat(50)));
|
|
456
146
|
console.log('');
|
|
457
147
|
}
|
|
458
148
|
/**
|
|
459
|
-
*
|
|
149
|
+
* porch done <id>
|
|
150
|
+
* Advances to next phase if checks pass. Refuses if checks fail.
|
|
460
151
|
*/
|
|
461
|
-
function
|
|
462
|
-
const
|
|
463
|
-
if (
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
else if (gateId === 'review_approval') {
|
|
483
|
-
// Look for review file
|
|
484
|
-
const reviewPath = path.join(projectRoot, 'codev', 'reviews', `${projectDir}.md`);
|
|
485
|
-
if (fs.existsSync(reviewPath))
|
|
486
|
-
return reviewPath;
|
|
152
|
+
export async function done(projectRoot, projectId) {
|
|
153
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
154
|
+
if (!statusPath) {
|
|
155
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
156
|
+
}
|
|
157
|
+
let state = readState(statusPath);
|
|
158
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
159
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
160
|
+
// Run checks first
|
|
161
|
+
if (Object.keys(checks).length > 0) {
|
|
162
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
|
|
163
|
+
console.log('');
|
|
164
|
+
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
165
|
+
const results = await runPhaseChecks(checks, projectRoot, checkEnv);
|
|
166
|
+
console.log(formatCheckResults(results));
|
|
167
|
+
if (!allChecksPassed(results)) {
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(chalk.red('CHECKS FAILED. Cannot advance.'));
|
|
170
|
+
console.log(`\n Fix the failures and try again.`);
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
487
173
|
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
*/
|
|
493
|
-
function displayGateContext(gateId, state, projectRoot) {
|
|
494
|
-
const artifactPath = getGateArtifact(gateId, state, projectRoot);
|
|
495
|
-
console.log('');
|
|
496
|
-
console.log(chalk.cyan('─'.repeat(50)));
|
|
497
|
-
console.log(chalk.cyan(' CONTEXT FOR REVIEW'));
|
|
498
|
-
console.log(chalk.cyan('─'.repeat(50)));
|
|
499
|
-
if (artifactPath) {
|
|
500
|
-
console.log(chalk.white(` Artifact: ${artifactPath}`));
|
|
174
|
+
// For build_verify phases: mark build as complete for verification
|
|
175
|
+
if (isBuildVerify(protocol, state.phase) && !state.build_complete) {
|
|
176
|
+
state.build_complete = true;
|
|
177
|
+
writeState(statusPath, state);
|
|
501
178
|
console.log('');
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
179
|
+
console.log(chalk.green('BUILD COMPLETE. Ready for verification.'));
|
|
180
|
+
console.log(`\n Run: porch next ${state.id} (to get verification tasks)`);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// Enforce 3-way verification for build_verify phases
|
|
184
|
+
const verifyConfig = getVerifyConfig(protocol, state.phase);
|
|
185
|
+
if (verifyConfig) {
|
|
186
|
+
const projectDir = getProjectDir(projectRoot, state.id, state.title);
|
|
187
|
+
const phase = state.current_plan_phase || state.phase;
|
|
188
|
+
const missingModels = [];
|
|
189
|
+
for (const model of verifyConfig.models) {
|
|
190
|
+
// Look for any review file for this model+phase (any iteration)
|
|
191
|
+
const pattern = path.join(projectDir, `${state.id}-${phase}-iter*-${model}.txt`);
|
|
192
|
+
const matches = globSync(pattern);
|
|
193
|
+
if (matches.length === 0) {
|
|
194
|
+
missingModels.push(model);
|
|
513
195
|
}
|
|
514
|
-
console.log(chalk.gray(' └' + '─'.repeat(46) + '┘'));
|
|
515
|
-
console.log('');
|
|
516
|
-
console.log(chalk.white(` To view full document: cat ${artifactPath}`));
|
|
517
196
|
}
|
|
518
|
-
|
|
519
|
-
console.log(
|
|
197
|
+
if (missingModels.length > 0) {
|
|
198
|
+
console.log('');
|
|
199
|
+
console.log(chalk.red('VERIFICATION REQUIRED'));
|
|
200
|
+
console.log(`\n 3-way review not completed. Missing: ${missingModels.join(', ')}`);
|
|
201
|
+
console.log(`\n Run: porch next ${state.id} (to trigger verification)`);
|
|
202
|
+
process.exit(1);
|
|
520
203
|
}
|
|
521
204
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
205
|
+
// Check for gate
|
|
206
|
+
const gate = getPhaseGate(protocol, state.phase);
|
|
207
|
+
if (gate && state.gates[gate]?.status !== 'approved') {
|
|
208
|
+
console.log('');
|
|
209
|
+
console.log(chalk.yellow(`GATE REQUIRED: ${gate}`));
|
|
210
|
+
console.log(`\n Run: porch gate ${state.id}`);
|
|
211
|
+
console.log(' Wait for human approval before advancing.');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// For phased protocols: plan phase advancement requires 3-way review.
|
|
215
|
+
// The isBuildVerify block above already marked build_complete=true.
|
|
216
|
+
// Redirect to porch next for verification (3-way review + unanimous verdict).
|
|
217
|
+
if (isPhased(protocol, state.phase) && state.plan_phases.length > 0) {
|
|
218
|
+
const currentPlanPhase = getCurrentPlanPhase(state.plan_phases);
|
|
219
|
+
if (currentPlanPhase && !allPlanPhasesComplete(state.plan_phases)) {
|
|
220
|
+
console.log('');
|
|
221
|
+
console.log(chalk.green('BUILD COMPLETE. Ready for 3-way review.'));
|
|
222
|
+
console.log(`\n Run: porch next ${state.id} (to trigger verification)`);
|
|
223
|
+
return;
|
|
531
224
|
}
|
|
532
225
|
}
|
|
533
|
-
|
|
226
|
+
// Advance to next protocol phase
|
|
227
|
+
advanceProtocolPhase(state, protocol, statusPath);
|
|
534
228
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
`Run: porch init <protocol> ${projectId} <project-name>`);
|
|
545
|
-
}
|
|
546
|
-
// Read state and load protocol
|
|
547
|
-
const state = readState(statusFilePath);
|
|
548
|
-
if (!state) {
|
|
549
|
-
throw new Error(`Could not read state from: ${statusFilePath}`);
|
|
229
|
+
function advanceProtocolPhase(state, protocol, statusPath) {
|
|
230
|
+
const nextPhase = getNextPhase(protocol, state.phase);
|
|
231
|
+
if (!nextPhase) {
|
|
232
|
+
state.phase = 'complete';
|
|
233
|
+
writeState(statusPath, state);
|
|
234
|
+
console.log('');
|
|
235
|
+
console.log(chalk.green.bold('🎉 PROTOCOL COMPLETE'));
|
|
236
|
+
console.log(`\n Project ${state.id} has completed the ${state.protocol} protocol.`);
|
|
237
|
+
return;
|
|
550
238
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
console.log(chalk.blue(` Protocol: ${state.protocol}`));
|
|
563
|
-
console.log(chalk.blue(` State: ${state.current_state}`));
|
|
564
|
-
console.log(chalk.green('═'.repeat(50)));
|
|
565
|
-
console.log('');
|
|
566
|
-
console.log(chalk.gray('Type "help" for commands, "quit" to exit'));
|
|
567
|
-
console.log('');
|
|
568
|
-
let currentState = state;
|
|
569
|
-
// Extract plan phases if not already done and we're past planning
|
|
570
|
-
if (!currentState.plan_phases || currentState.plan_phases.length === 0) {
|
|
571
|
-
const planFile = findPlanFile(projectRoot, projectId, currentState.title);
|
|
572
|
-
if (planFile) {
|
|
573
|
-
try {
|
|
574
|
-
const planPhases = extractPhasesFromPlanFile(planFile);
|
|
575
|
-
currentState = setPlanPhases(currentState, planPhases);
|
|
576
|
-
await writeState(statusFilePath, currentState);
|
|
577
|
-
console.log(chalk.blue(`[porch] Extracted ${planPhases.length} phases from plan`));
|
|
578
|
-
for (const phase of planPhases) {
|
|
579
|
-
console.log(chalk.blue(` - ${phase.id}: ${phase.title}`));
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
catch (e) {
|
|
583
|
-
console.log(chalk.yellow(`[porch] Could not extract plan phases: ${e}`));
|
|
239
|
+
state.phase = nextPhase.id;
|
|
240
|
+
state.build_complete = false;
|
|
241
|
+
state.iteration = 1;
|
|
242
|
+
// If entering a phased phase (implement), extract plan phases
|
|
243
|
+
if (isPhased(protocol, nextPhase.id)) {
|
|
244
|
+
const planPath = findPlanFile(process.cwd(), state.id, state.title);
|
|
245
|
+
if (planPath) {
|
|
246
|
+
state.plan_phases = extractPhasesFromFile(planPath);
|
|
247
|
+
// extractPhasesFromFile already marks first phase as in_progress
|
|
248
|
+
if (state.plan_phases.length > 0) {
|
|
249
|
+
state.current_plan_phase = state.plan_phases[0].id;
|
|
584
250
|
}
|
|
585
251
|
}
|
|
586
252
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
//
|
|
597
|
-
|
|
598
|
-
if (
|
|
599
|
-
const
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
currentState = setPlanPhases(currentState, planPhases);
|
|
604
|
-
await writeState(statusFilePath, currentState);
|
|
605
|
-
console.log(chalk.blue(`[porch] Late discovery: Extracted ${planPhases.length} phases from plan`));
|
|
606
|
-
for (const phase of planPhases) {
|
|
607
|
-
console.log(chalk.blue(` - ${phase.id}: ${phase.title}`));
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
catch (e) {
|
|
611
|
-
console.log(chalk.yellow(`[porch] Could not extract plan phases: ${e}`));
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
else {
|
|
615
|
-
console.log(chalk.yellow(`[porch] Warning: Entering phased phase '${phaseId}' but no plan file found`));
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
// Check if terminal phase
|
|
619
|
-
if (isTerminalPhase(protocol, phaseId)) {
|
|
620
|
-
console.log(chalk.green('━'.repeat(40)));
|
|
621
|
-
console.log(chalk.green(`[porch] ${state.protocol} loop COMPLETE`));
|
|
622
|
-
console.log(chalk.green(`[porch] Project ${projectId} finished all phases`));
|
|
623
|
-
console.log(chalk.green('━'.repeat(40)));
|
|
624
|
-
rl.close();
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
// Check if there's a pending gate from a previous iteration (step already executed)
|
|
628
|
-
const pendingGateInfo = getGateForState(protocol, currentState.current_state);
|
|
629
|
-
if (pendingGateInfo) {
|
|
630
|
-
const { gateId } = pendingGateInfo;
|
|
631
|
-
// Check if gate is already approved
|
|
632
|
-
if (currentState.gates[gateId]?.status === 'passed') {
|
|
633
|
-
// Gate approved - proceed to next state
|
|
634
|
-
const nextState = getGateNextState(protocol, phaseId);
|
|
635
|
-
if (nextState) {
|
|
636
|
-
console.log(chalk.green(`[porch] Gate ${gateId} passed! Proceeding to ${nextState}`));
|
|
637
|
-
await notifier.gateApproved(gateId);
|
|
638
|
-
// Reset any consultation attempts for the gated state
|
|
639
|
-
currentState = resetConsultationAttempts(currentState, currentState.current_state);
|
|
640
|
-
// If entering a phased phase, start with first plan phase
|
|
641
|
-
if (isPhasedPhase(protocol, nextState.split(':')[0]) && currentState.plan_phases?.length) {
|
|
642
|
-
const firstPlanPhase = currentState.plan_phases[0];
|
|
643
|
-
currentState = updateState(currentState, `${nextState.split(':')[0]}:${firstPlanPhase.id}`);
|
|
644
|
-
currentState = updatePhaseStatus(currentState, firstPlanPhase.id, 'in_progress');
|
|
645
|
-
}
|
|
646
|
-
else {
|
|
647
|
-
currentState = updateState(currentState, nextState);
|
|
648
|
-
}
|
|
649
|
-
await writeState(statusFilePath, currentState);
|
|
650
|
-
continue; // Start next iteration with new state
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
else if (currentState.gates[gateId]?.requested_at) {
|
|
654
|
-
// Gate requested but not approved - prompt user interactively
|
|
655
|
-
console.log('');
|
|
656
|
-
console.log(chalk.yellow('═'.repeat(50)));
|
|
657
|
-
console.log(chalk.yellow(` GATE PENDING: ${gateId}`));
|
|
658
|
-
console.log(chalk.yellow('═'.repeat(50)));
|
|
659
|
-
console.log(chalk.cyan(` Phase: ${phaseId}`));
|
|
660
|
-
console.log(chalk.cyan(` State: ${currentState.current_state}`));
|
|
661
|
-
// Show context for the gate - what artifact was created
|
|
662
|
-
displayGateContext(gateId, currentState, projectRoot);
|
|
663
|
-
// Interactive gate approval loop
|
|
664
|
-
let gateHandled = false;
|
|
665
|
-
while (!gateHandled) {
|
|
666
|
-
const answer = await prompt(rl, chalk.yellow(`Approve ${gateId}? [y/n/help]: `));
|
|
667
|
-
switch (answer) {
|
|
668
|
-
case 'y':
|
|
669
|
-
case 'yes':
|
|
670
|
-
case 'approve':
|
|
671
|
-
currentState = approveGate(currentState, gateId);
|
|
672
|
-
await writeState(statusFilePath, currentState);
|
|
673
|
-
console.log(chalk.green(`✓ Gate ${gateId} approved`));
|
|
674
|
-
gateHandled = true;
|
|
675
|
-
break;
|
|
676
|
-
case 'n':
|
|
677
|
-
case 'no':
|
|
678
|
-
console.log(chalk.yellow('Gate not approved. Staying in current state.'));
|
|
679
|
-
console.log(chalk.gray('Type "quit" to exit or wait for changes.'));
|
|
680
|
-
break;
|
|
681
|
-
case 'status':
|
|
682
|
-
displayStatus(currentState, protocol);
|
|
683
|
-
break;
|
|
684
|
-
case 'view':
|
|
685
|
-
case 'cat':
|
|
686
|
-
case 'show':
|
|
687
|
-
// Show full artifact
|
|
688
|
-
const artifactPath = getGateArtifact(gateId, currentState, projectRoot);
|
|
689
|
-
if (artifactPath) {
|
|
690
|
-
console.log(chalk.cyan(`\n─── ${artifactPath} ───\n`));
|
|
691
|
-
try {
|
|
692
|
-
const content = fs.readFileSync(artifactPath, 'utf-8');
|
|
693
|
-
console.log(content);
|
|
694
|
-
console.log(chalk.cyan(`\n─── End of ${artifactPath} ───\n`));
|
|
695
|
-
}
|
|
696
|
-
catch (e) {
|
|
697
|
-
console.log(chalk.red(`Error reading file: ${e}`));
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
else {
|
|
701
|
-
console.log(chalk.yellow('No artifact file found for this gate.'));
|
|
702
|
-
}
|
|
703
|
-
break;
|
|
704
|
-
case 'help':
|
|
705
|
-
case '?':
|
|
706
|
-
showReplHelp();
|
|
707
|
-
break;
|
|
708
|
-
case 'quit':
|
|
709
|
-
case 'exit':
|
|
710
|
-
console.log(chalk.blue('Exiting porch...'));
|
|
711
|
-
rl.close();
|
|
712
|
-
return;
|
|
713
|
-
default:
|
|
714
|
-
console.log(chalk.gray('Type "y" to approve, "n" to decline, or "help" for commands'));
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
// If gate not yet requested, fall through to execute phase first
|
|
720
|
-
}
|
|
721
|
-
// Get the current phase definition
|
|
722
|
-
const phase = protocol.phases.find(p => p.id === phaseId);
|
|
723
|
-
// Show plan phase context if in a phased phase
|
|
724
|
-
if (planPhaseId && currentState.plan_phases) {
|
|
725
|
-
const planPhase = currentState.plan_phases.find(p => p.id === planPhaseId);
|
|
726
|
-
if (planPhase) {
|
|
727
|
-
console.log(chalk.cyan(`[phase] IDE Phase: ${phaseId} | Plan Phase: ${planPhase.title}`));
|
|
728
|
-
}
|
|
729
|
-
else {
|
|
730
|
-
console.log(chalk.cyan(`[phase] Phase: ${phaseId}`));
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
console.log(chalk.cyan(`[phase] Phase: ${phaseId}`));
|
|
735
|
-
}
|
|
736
|
-
// Notify phase start
|
|
737
|
-
await notifier.phaseStart(phaseId);
|
|
738
|
-
// Execute phase
|
|
739
|
-
const output = await invokeClaude(protocol, phaseId, currentState, statusFilePath, projectRoot, options);
|
|
740
|
-
const signal = extractSignal(output);
|
|
741
|
-
// Run phase checks (build/test) if defined
|
|
742
|
-
if (phase?.checks && !options.dryRun) {
|
|
743
|
-
console.log(chalk.blue(`[porch] Running checks for phase ${phaseId}...`));
|
|
744
|
-
const checkResult = await runPhaseChecks(phase, {
|
|
745
|
-
cwd: projectRoot,
|
|
746
|
-
dryRun: options.dryRun,
|
|
747
|
-
});
|
|
748
|
-
console.log(formatCheckResults(checkResult));
|
|
749
|
-
if (!checkResult.success) {
|
|
750
|
-
// Notify about check failure
|
|
751
|
-
const failedCheck = checkResult.checks.find(c => !c.success);
|
|
752
|
-
await notifier.checkFailed(phaseId, failedCheck?.name || 'build/test', failedCheck?.error || 'Check failed');
|
|
753
|
-
// If checks fail, handle based on check configuration
|
|
754
|
-
if (checkResult.returnTo) {
|
|
755
|
-
console.log(chalk.yellow(`[porch] Checks failed, returning to ${checkResult.returnTo}`));
|
|
756
|
-
if (planPhaseId) {
|
|
757
|
-
currentState = updateState(currentState, `${checkResult.returnTo}:${planPhaseId}`);
|
|
758
|
-
}
|
|
759
|
-
else {
|
|
760
|
-
currentState = updateState(currentState, checkResult.returnTo);
|
|
761
|
-
}
|
|
762
|
-
await writeState(statusFilePath, currentState);
|
|
763
|
-
continue;
|
|
764
|
-
}
|
|
765
|
-
// No returnTo means we should retry (already handled in checks)
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
// Run consultation if configured for this phase/substate
|
|
769
|
-
if (phase?.consultation && hasConsultation(phase) && !options.dryRun && !options.noClaude) {
|
|
770
|
-
const consultConfig = phase.consultation;
|
|
771
|
-
const currentSubstate = substate || parsePlanPhaseFromState(currentState.current_state).substate;
|
|
772
|
-
// Check if consultation is triggered by current substate
|
|
773
|
-
if (consultConfig.on === currentSubstate || consultConfig.on === phaseId) {
|
|
774
|
-
const maxRounds = consultConfig.max_rounds || 3;
|
|
775
|
-
const stateKey = currentState.current_state;
|
|
776
|
-
// Get attempt count from state (persisted across porch iterations)
|
|
777
|
-
const attemptCount = getConsultationAttempts(currentState, stateKey) + 1;
|
|
778
|
-
console.log(chalk.blue(`[porch] Consultation triggered for phase ${phaseId} (attempt ${attemptCount}/${maxRounds})`));
|
|
779
|
-
await notifier.consultationStart(phaseId, consultConfig.models || ['gemini', 'codex', 'claude']);
|
|
780
|
-
const consultResult = await runConsultationLoop(consultConfig, {
|
|
781
|
-
subcommand: consultConfig.type.includes('pr') ? 'pr' : consultConfig.type.includes('spec') ? 'spec' : 'plan',
|
|
782
|
-
identifier: projectId,
|
|
783
|
-
cwd: projectRoot,
|
|
784
|
-
timeout: protocol.config?.consultation_timeout,
|
|
785
|
-
dryRun: options.dryRun,
|
|
786
|
-
});
|
|
787
|
-
console.log(formatConsultationResults(consultResult));
|
|
788
|
-
await notifier.consultationComplete(phaseId, consultResult.feedback, consultResult.allApproved);
|
|
789
|
-
// If not all approved, track attempt and check for escalation
|
|
790
|
-
if (!consultResult.allApproved) {
|
|
791
|
-
// Increment attempt count in state (persists across iterations)
|
|
792
|
-
currentState = incrementConsultationAttempts(currentState, stateKey);
|
|
793
|
-
await writeState(statusFilePath, currentState);
|
|
794
|
-
// Check if we've reached max attempts
|
|
795
|
-
if (attemptCount >= maxRounds) {
|
|
796
|
-
// Create escalation gate - requires human intervention
|
|
797
|
-
const escalationGateId = `${phaseId}_consultation_escalation`;
|
|
798
|
-
// Check if escalation gate was already approved (human override)
|
|
799
|
-
if (currentState.gates[escalationGateId]?.status === 'passed') {
|
|
800
|
-
console.log(chalk.green(`[porch] Consultation escalation gate already approved, continuing`));
|
|
801
|
-
// Reset attempts and fall through to next state handling
|
|
802
|
-
currentState = resetConsultationAttempts(currentState, stateKey);
|
|
803
|
-
await writeState(statusFilePath, currentState);
|
|
804
|
-
}
|
|
805
|
-
else {
|
|
806
|
-
console.log(chalk.red(`[porch] Consultation failed after ${attemptCount} attempts - escalating to human`));
|
|
807
|
-
console.log(chalk.yellow(`[porch] To override and continue: porch approve ${projectId} ${escalationGateId}`));
|
|
808
|
-
// Request human gate if not already requested
|
|
809
|
-
if (!currentState.gates[escalationGateId]?.requested_at) {
|
|
810
|
-
currentState = requestGateApproval(currentState, escalationGateId);
|
|
811
|
-
await writeState(statusFilePath, currentState);
|
|
812
|
-
await notifier.gatePending(phaseId, escalationGateId);
|
|
813
|
-
}
|
|
814
|
-
// Prompt user to approve escalation gate
|
|
815
|
-
console.log('');
|
|
816
|
-
console.log(chalk.red('═'.repeat(50)));
|
|
817
|
-
console.log(chalk.red(` ESCALATION: ${escalationGateId}`));
|
|
818
|
-
console.log(chalk.red('═'.repeat(50)));
|
|
819
|
-
console.log(chalk.yellow(` Consultation failed after ${attemptCount} attempts`));
|
|
820
|
-
console.log('');
|
|
821
|
-
let escalationHandled = false;
|
|
822
|
-
while (!escalationHandled) {
|
|
823
|
-
const answer = await prompt(rl, chalk.red(`Override and continue? [y/n/help]: `));
|
|
824
|
-
switch (answer) {
|
|
825
|
-
case 'y':
|
|
826
|
-
case 'yes':
|
|
827
|
-
case 'approve':
|
|
828
|
-
case 'override':
|
|
829
|
-
currentState = approveGate(currentState, escalationGateId);
|
|
830
|
-
currentState = resetConsultationAttempts(currentState, stateKey);
|
|
831
|
-
await writeState(statusFilePath, currentState);
|
|
832
|
-
console.log(chalk.green(`✓ Escalation gate ${escalationGateId} approved`));
|
|
833
|
-
escalationHandled = true;
|
|
834
|
-
break;
|
|
835
|
-
case 'n':
|
|
836
|
-
case 'no':
|
|
837
|
-
console.log(chalk.yellow('Escalation not approved. Consultation loop will continue.'));
|
|
838
|
-
break;
|
|
839
|
-
case 'status':
|
|
840
|
-
displayStatus(currentState, protocol);
|
|
841
|
-
break;
|
|
842
|
-
case 'help':
|
|
843
|
-
case '?':
|
|
844
|
-
showReplHelp();
|
|
845
|
-
break;
|
|
846
|
-
case 'quit':
|
|
847
|
-
case 'exit':
|
|
848
|
-
console.log(chalk.blue('Exiting porch...'));
|
|
849
|
-
rl.close();
|
|
850
|
-
return;
|
|
851
|
-
default:
|
|
852
|
-
console.log(chalk.gray('Type "y" to override and continue, "n" to decline, or "help" for commands'));
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
continue;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
else {
|
|
859
|
-
console.log(chalk.yellow(`[porch] Consultation requested changes (attempt ${attemptCount}/${maxRounds}), continuing for revision`));
|
|
860
|
-
// Stay in same state for Claude to revise on next iteration
|
|
861
|
-
continue;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
else {
|
|
865
|
-
// All approved - reset attempt counter
|
|
866
|
-
currentState = resetConsultationAttempts(currentState, stateKey);
|
|
867
|
-
await writeState(statusFilePath, currentState);
|
|
868
|
-
}
|
|
869
|
-
// All approved (or escalation gate passed) - use consultation's next state if defined
|
|
870
|
-
if (consultConfig.next) {
|
|
871
|
-
if (planPhaseId) {
|
|
872
|
-
currentState = updateState(currentState, `${consultConfig.next}:${planPhaseId}`);
|
|
873
|
-
}
|
|
874
|
-
else {
|
|
875
|
-
currentState = updateState(currentState, consultConfig.next);
|
|
876
|
-
}
|
|
877
|
-
await writeState(statusFilePath, currentState);
|
|
878
|
-
continue;
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
// Check if current state has a gate that should trigger AFTER this step
|
|
883
|
-
// This is the gate trigger point - step has executed, now block before transition
|
|
884
|
-
const gateInfo = getGateForState(protocol, currentState.current_state);
|
|
885
|
-
if (gateInfo) {
|
|
886
|
-
const { gateId } = gateInfo;
|
|
887
|
-
// Request gate approval if not already requested
|
|
888
|
-
if (!currentState.gates[gateId]?.requested_at) {
|
|
889
|
-
currentState = requestGateApproval(currentState, gateId);
|
|
890
|
-
await writeState(statusFilePath, currentState);
|
|
891
|
-
console.log(chalk.yellow(`[porch] Step complete. Gate approval requested: ${gateId}`));
|
|
892
|
-
await notifier.gatePending(phaseId, gateId);
|
|
893
|
-
}
|
|
894
|
-
// Gate not yet approved - prompt user interactively
|
|
895
|
-
if (currentState.gates[gateId]?.status !== 'passed') {
|
|
896
|
-
console.log('');
|
|
897
|
-
console.log(chalk.yellow('═'.repeat(50)));
|
|
898
|
-
console.log(chalk.yellow(` GATE PENDING: ${gateId}`));
|
|
899
|
-
console.log(chalk.yellow('═'.repeat(50)));
|
|
900
|
-
let gateHandled = false;
|
|
901
|
-
while (!gateHandled) {
|
|
902
|
-
const answer = await prompt(rl, chalk.yellow(`Approve ${gateId}? [y/n/help]: `));
|
|
903
|
-
switch (answer) {
|
|
904
|
-
case 'y':
|
|
905
|
-
case 'yes':
|
|
906
|
-
case 'approve':
|
|
907
|
-
currentState = approveGate(currentState, gateId);
|
|
908
|
-
await writeState(statusFilePath, currentState);
|
|
909
|
-
console.log(chalk.green(`✓ Gate ${gateId} approved`));
|
|
910
|
-
gateHandled = true;
|
|
911
|
-
break;
|
|
912
|
-
case 'n':
|
|
913
|
-
case 'no':
|
|
914
|
-
console.log(chalk.yellow('Gate not approved. Staying in current state.'));
|
|
915
|
-
break;
|
|
916
|
-
case 'status':
|
|
917
|
-
displayStatus(currentState, protocol);
|
|
918
|
-
break;
|
|
919
|
-
case 'help':
|
|
920
|
-
case '?':
|
|
921
|
-
showReplHelp();
|
|
922
|
-
break;
|
|
923
|
-
case 'quit':
|
|
924
|
-
case 'exit':
|
|
925
|
-
console.log(chalk.blue('Exiting porch...'));
|
|
926
|
-
rl.close();
|
|
927
|
-
return;
|
|
928
|
-
default:
|
|
929
|
-
console.log(chalk.gray('Type "y" to approve, "n" to decline, or "help" for commands'));
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
continue;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
// Determine next state
|
|
936
|
-
let nextState = null;
|
|
937
|
-
if (signal) {
|
|
938
|
-
console.log(chalk.green(`[porch] Signal received: ${signal}`));
|
|
939
|
-
nextState = getSignalNextState(protocol, phaseId, signal);
|
|
940
|
-
}
|
|
941
|
-
if (!nextState) {
|
|
942
|
-
// Check if this is a phased phase (IDE loop)
|
|
943
|
-
if (isPhasedPhase(protocol, phaseId) && currentState.plan_phases?.length) {
|
|
944
|
-
nextState = getNextIDEState(protocol, currentState.current_state, currentState.plan_phases, signal || undefined);
|
|
945
|
-
// Mark current plan phase as complete if moving to next
|
|
946
|
-
if (nextState && planPhaseId) {
|
|
947
|
-
const { planPhaseId: nextPlanPhaseId } = parsePlanPhaseFromState(nextState);
|
|
948
|
-
if (nextPlanPhaseId !== planPhaseId) {
|
|
949
|
-
currentState = updatePhaseStatus(currentState, planPhaseId, 'complete');
|
|
950
|
-
if (nextPlanPhaseId) {
|
|
951
|
-
currentState = updatePhaseStatus(currentState, nextPlanPhaseId, 'in_progress');
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
else {
|
|
957
|
-
// Use default transition
|
|
958
|
-
nextState = getDefaultNextState(protocol, currentState.current_state);
|
|
253
|
+
writeState(statusPath, state);
|
|
254
|
+
console.log('');
|
|
255
|
+
console.log(chalk.green(`ADVANCING TO: ${nextPhase.id} - ${nextPhase.name}`));
|
|
256
|
+
// If we just entered implement phase, show phase 1 info and the critical warning
|
|
257
|
+
if (isPhased(protocol, nextPhase.id) && state.plan_phases.length > 0) {
|
|
258
|
+
const firstPhase = state.plan_phases[0];
|
|
259
|
+
const nextPlanPhase = state.plan_phases[1];
|
|
260
|
+
console.log('');
|
|
261
|
+
console.log(chalk.bold(`YOUR TASK: ${firstPhase.id} - "${firstPhase.title}"`));
|
|
262
|
+
// Show phase content from plan
|
|
263
|
+
const planPath = findPlanFile(process.cwd(), state.id, state.title);
|
|
264
|
+
if (planPath) {
|
|
265
|
+
const content = fs.readFileSync(planPath, 'utf-8');
|
|
266
|
+
const phaseContent = getPhaseContent(content, firstPhase.id);
|
|
267
|
+
if (phaseContent) {
|
|
268
|
+
console.log(section('FROM THE PLAN', phaseContent.slice(0, 800)));
|
|
959
269
|
}
|
|
960
270
|
}
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(chalk.red.bold('╔══════════════════════════════════════════════════════════════╗'));
|
|
273
|
+
console.log(chalk.red.bold('║ 🛑 CRITICAL RULES ║'));
|
|
274
|
+
if (nextPlanPhase) {
|
|
275
|
+
console.log(chalk.red.bold(`║ 1. DO NOT start ${nextPlanPhase.id} until you run porch again!`.padEnd(63) + '║'));
|
|
964
276
|
}
|
|
965
277
|
else {
|
|
966
|
-
console.log(chalk.
|
|
278
|
+
console.log(chalk.red.bold('║ 1. DO NOT start the next phase until you run porch again! ║'));
|
|
967
279
|
}
|
|
280
|
+
console.log(chalk.red.bold('║ 2. Run /compact before starting each new phase ║'));
|
|
281
|
+
console.log(chalk.red.bold('║ 3. When phase complete, run: porch done ' + state.id.padEnd(20) + '║'));
|
|
282
|
+
console.log(chalk.red.bold('╚══════════════════════════════════════════════════════════════╝'));
|
|
968
283
|
}
|
|
969
|
-
|
|
970
|
-
throw new Error(`Max iterations (${maxIterations}) reached!`);
|
|
284
|
+
console.log(`\n Run: porch status ${state.id}`);
|
|
971
285
|
}
|
|
972
286
|
/**
|
|
973
|
-
*
|
|
287
|
+
* porch gate <id>
|
|
288
|
+
* Requests human approval for current gate.
|
|
974
289
|
*/
|
|
975
|
-
export async function
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
290
|
+
export async function gate(projectRoot, projectId) {
|
|
291
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
292
|
+
if (!statusPath) {
|
|
293
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
294
|
+
}
|
|
295
|
+
const state = readState(statusPath);
|
|
296
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
297
|
+
const gateName = getPhaseGate(protocol, state.phase);
|
|
298
|
+
if (!gateName) {
|
|
299
|
+
console.log(chalk.dim('No gate required for this phase.'));
|
|
300
|
+
console.log(`\n Run: porch done ${state.id}`);
|
|
301
|
+
return;
|
|
980
302
|
}
|
|
981
|
-
|
|
982
|
-
if (!state) {
|
|
983
|
-
|
|
303
|
+
// Mark gate as requested
|
|
304
|
+
if (!state.gates[gateName]) {
|
|
305
|
+
state.gates[gateName] = { status: 'pending' };
|
|
984
306
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
}
|
|
989
|
-
/**
|
|
990
|
-
* Show project status
|
|
991
|
-
*/
|
|
992
|
-
export async function status(projectId) {
|
|
993
|
-
const projectRoot = findProjectRoot();
|
|
994
|
-
if (projectId) {
|
|
995
|
-
// Show specific project
|
|
996
|
-
const statusFilePath = findStatusFile(projectRoot, projectId);
|
|
997
|
-
if (!statusFilePath) {
|
|
998
|
-
throw new Error(`Status file not found for project: ${projectId}`);
|
|
999
|
-
}
|
|
1000
|
-
const state = readState(statusFilePath);
|
|
1001
|
-
if (!state) {
|
|
1002
|
-
throw new Error(`Could not read state from: ${statusFilePath}`);
|
|
1003
|
-
}
|
|
1004
|
-
console.log(chalk.blue(`[porch] Status for project ${projectId}:`));
|
|
1005
|
-
console.log('');
|
|
1006
|
-
console.log(` ID: ${state.id}`);
|
|
1007
|
-
console.log(` Title: ${state.title}`);
|
|
1008
|
-
console.log(` Protocol: ${state.protocol}`);
|
|
1009
|
-
console.log(` State: ${state.current_state}`);
|
|
1010
|
-
console.log(` Iteration: ${state.iteration}`);
|
|
1011
|
-
console.log(` Started: ${state.started_at}`);
|
|
1012
|
-
console.log(` Updated: ${state.last_updated}`);
|
|
1013
|
-
console.log('');
|
|
1014
|
-
if (Object.keys(state.gates).length > 0) {
|
|
1015
|
-
console.log(' Gates:');
|
|
1016
|
-
for (const [gateId, gateStatus] of Object.entries(state.gates)) {
|
|
1017
|
-
const icon = gateStatus.status === 'passed' ? '✓' : gateStatus.status === 'failed' ? '✗' : '⏳';
|
|
1018
|
-
console.log(` ${icon} ${gateId}: ${gateStatus.status}`);
|
|
1019
|
-
}
|
|
1020
|
-
console.log('');
|
|
1021
|
-
}
|
|
1022
|
-
if (state.plan_phases && state.plan_phases.length > 0) {
|
|
1023
|
-
console.log(' Plan Phases:');
|
|
1024
|
-
for (const phase of state.plan_phases) {
|
|
1025
|
-
const phaseStatus = state.phases[phase.id]?.status || 'pending';
|
|
1026
|
-
const icon = phaseStatus === 'complete' ? '✓' : phaseStatus === 'in_progress' ? '🔄' : '○';
|
|
1027
|
-
console.log(` ${icon} ${phase.id}: ${phase.title}`);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
307
|
+
if (!state.gates[gateName].requested_at) {
|
|
308
|
+
state.gates[gateName].requested_at = new Date().toISOString();
|
|
309
|
+
writeState(statusPath, state);
|
|
1030
310
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
for (const { id, path: statusPath } of projects) {
|
|
1041
|
-
const state = readState(statusPath);
|
|
1042
|
-
if (state) {
|
|
1043
|
-
const pendingGates = Object.entries(state.gates)
|
|
1044
|
-
.filter(([, g]) => g.status === 'pending' && g.requested_at)
|
|
1045
|
-
.map(([id]) => id);
|
|
1046
|
-
const gateStr = pendingGates.length > 0 ? chalk.yellow(` [${pendingGates.join(', ')}]`) : '';
|
|
1047
|
-
console.log(` ${id} ${state.title} - ${state.current_state}${gateStr}`);
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (executions.length > 0) {
|
|
311
|
+
console.log('');
|
|
312
|
+
console.log(chalk.bold(`GATE: ${gateName}`));
|
|
313
|
+
console.log('');
|
|
314
|
+
// Show relevant artifact and open it for review
|
|
315
|
+
const artifact = getArtifactForPhase(projectRoot, state);
|
|
316
|
+
if (artifact) {
|
|
317
|
+
const fullPath = path.join(projectRoot, artifact);
|
|
318
|
+
if (fs.existsSync(fullPath)) {
|
|
319
|
+
console.log(` Artifact: ${artifact}`);
|
|
1051
320
|
console.log('');
|
|
1052
|
-
console.log(chalk.
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
321
|
+
console.log(chalk.cyan(' Opening artifact for human review...'));
|
|
322
|
+
// Use af open to display in annotation viewer
|
|
323
|
+
const { spawn } = await import('node:child_process');
|
|
324
|
+
spawn('af', ['open', fullPath], {
|
|
325
|
+
stdio: 'inherit',
|
|
326
|
+
detached: true
|
|
327
|
+
}).unref();
|
|
1059
328
|
}
|
|
1060
329
|
}
|
|
330
|
+
console.log('');
|
|
331
|
+
console.log(chalk.yellow(' Human approval required. STOP and wait.'));
|
|
332
|
+
console.log(' Do not proceed until gate is approved.');
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(chalk.bold('STATUS: WAITING FOR HUMAN APPROVAL'));
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log(chalk.dim(` To approve: porch approve ${state.id} ${gateName}`));
|
|
337
|
+
console.log('');
|
|
1061
338
|
}
|
|
1062
339
|
/**
|
|
1063
|
-
*
|
|
340
|
+
* porch approve <id> <gate> --a-human-explicitly-approved-this
|
|
341
|
+
* Human approves a gate. Requires explicit flag to prevent automated approvals.
|
|
1064
342
|
*/
|
|
1065
|
-
export async function
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
343
|
+
export async function approve(projectRoot, projectId, gateName, hasHumanFlag) {
|
|
344
|
+
const statusPath = findStatusPath(projectRoot, projectId);
|
|
345
|
+
if (!statusPath) {
|
|
346
|
+
throw new Error(`Project ${projectId} not found.`);
|
|
347
|
+
}
|
|
348
|
+
const state = readState(statusPath);
|
|
349
|
+
if (!state.gates[gateName]) {
|
|
350
|
+
const knownGates = Object.keys(state.gates).join(', ');
|
|
351
|
+
throw new Error(`Unknown gate: ${gateName}\nKnown gates: ${knownGates || 'none'}`);
|
|
352
|
+
}
|
|
353
|
+
if (state.gates[gateName].status === 'approved') {
|
|
354
|
+
console.log(chalk.yellow(`Gate ${gateName} is already approved.`));
|
|
1070
355
|
return;
|
|
1071
356
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
357
|
+
// Require explicit human flag
|
|
358
|
+
if (!hasHumanFlag) {
|
|
359
|
+
console.log('');
|
|
360
|
+
console.log(chalk.red('ERROR: Human approval required.'));
|
|
361
|
+
console.log('');
|
|
362
|
+
console.log(' To approve, please run:');
|
|
363
|
+
console.log('');
|
|
364
|
+
console.log(chalk.cyan(` porch approve ${projectId} ${gateName} --a-human-explicitly-approved-this`));
|
|
365
|
+
console.log('');
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
// Run phase checks before approving
|
|
369
|
+
const protocol = loadProtocol(projectRoot, state.protocol);
|
|
370
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
371
|
+
if (Object.keys(checks).length > 0) {
|
|
372
|
+
const checkEnv = { PROJECT_ID: state.id, PROJECT_TITLE: state.title };
|
|
373
|
+
console.log('');
|
|
374
|
+
console.log(chalk.bold('RUNNING CHECKS...'));
|
|
375
|
+
const results = await runPhaseChecks(checks, projectRoot, checkEnv);
|
|
376
|
+
console.log(formatCheckResults(results));
|
|
377
|
+
if (!allChecksPassed(results)) {
|
|
378
|
+
console.log('');
|
|
379
|
+
console.log(chalk.red('CHECKS FAILED. Cannot approve gate.'));
|
|
380
|
+
console.log(`\n Fix the failures and try again.`);
|
|
381
|
+
process.exit(1);
|
|
1080
382
|
}
|
|
1081
383
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
const protocol = loadProtocol(protocolName, projectRoot);
|
|
1089
|
-
console.log(chalk.blue(`[porch] Protocol: ${protocolName}`));
|
|
384
|
+
state.gates[gateName].status = 'approved';
|
|
385
|
+
state.gates[gateName].approved_at = new Date().toISOString();
|
|
386
|
+
writeState(statusPath, state);
|
|
387
|
+
console.log('');
|
|
388
|
+
console.log(chalk.green(`Gate ${gateName} approved.`));
|
|
389
|
+
console.log(`\n Run: porch done ${state.id} (to advance)`);
|
|
1090
390
|
console.log('');
|
|
1091
|
-
console.log(JSON.stringify(protocol, null, 2));
|
|
1092
391
|
}
|
|
1093
392
|
/**
|
|
1094
|
-
*
|
|
393
|
+
* porch init <protocol> <id> <name>
|
|
394
|
+
* Initialize a new project.
|
|
395
|
+
*
|
|
396
|
+
* Idempotent: if status.yaml already exists, preserves it and reports
|
|
397
|
+
* current state. This supports `af spawn --resume` where the builder
|
|
398
|
+
* may re-run `porch init` after a session restart.
|
|
1095
399
|
*/
|
|
1096
|
-
export async function
|
|
1097
|
-
const
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
400
|
+
export async function init(projectRoot, protocolName, projectId, projectName) {
|
|
401
|
+
const protocol = loadProtocol(projectRoot, protocolName);
|
|
402
|
+
const statusPath = getStatusPath(projectRoot, projectId, projectName);
|
|
403
|
+
// If status.yaml already exists, preserve it (idempotent for resume)
|
|
404
|
+
if (fs.existsSync(statusPath)) {
|
|
405
|
+
const existingState = readState(statusPath);
|
|
406
|
+
console.log('');
|
|
407
|
+
console.log(chalk.yellow(`Project ${projectId}-${projectName} already exists. Preserving existing state.`));
|
|
408
|
+
console.log(` Protocol: ${existingState.protocol}`);
|
|
409
|
+
console.log(` Current phase: ${existingState.phase}`);
|
|
410
|
+
if (existingState.current_plan_phase) {
|
|
411
|
+
console.log(` Plan phase: ${existingState.current_plan_phase}`);
|
|
412
|
+
}
|
|
413
|
+
console.log(`\n Run: porch next ${projectId}`);
|
|
414
|
+
console.log('');
|
|
1101
415
|
return;
|
|
1102
416
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
console.log(
|
|
417
|
+
// Also check if a project with this ID exists under a different name
|
|
418
|
+
const existingPath = findStatusPath(projectRoot, projectId);
|
|
419
|
+
if (existingPath) {
|
|
420
|
+
const existingState = readState(existingPath);
|
|
421
|
+
console.log('');
|
|
422
|
+
console.log(chalk.yellow(`Project ${projectId} already exists (as ${existingState.id}-${existingState.title}). Preserving existing state.`));
|
|
423
|
+
console.log(` Protocol: ${existingState.protocol}`);
|
|
424
|
+
console.log(` Current phase: ${existingState.phase}`);
|
|
425
|
+
if (existingState.current_plan_phase) {
|
|
426
|
+
console.log(` Plan phase: ${existingState.current_plan_phase}`);
|
|
427
|
+
}
|
|
428
|
+
console.log(`\n Run: porch next ${projectId}`);
|
|
429
|
+
console.log('');
|
|
430
|
+
return;
|
|
1108
431
|
}
|
|
432
|
+
const state = createInitialState(protocol, projectId, projectName, projectRoot);
|
|
433
|
+
writeState(statusPath, state);
|
|
434
|
+
console.log('');
|
|
435
|
+
console.log(chalk.green(`Project initialized: ${projectId}-${projectName}`));
|
|
436
|
+
console.log(` Protocol: ${protocolName}`);
|
|
437
|
+
console.log(` Starting phase: ${state.phase}`);
|
|
438
|
+
console.log(`\n Run: porch status ${projectId}`);
|
|
439
|
+
console.log('');
|
|
1109
440
|
}
|
|
1110
441
|
// ============================================================================
|
|
1111
|
-
//
|
|
442
|
+
// Helpers
|
|
1112
443
|
// ============================================================================
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
*/
|
|
1121
|
-
function autoDetectProject() {
|
|
1122
|
-
const cwd = process.cwd();
|
|
1123
|
-
// Method 1: Check path pattern for builder worktree
|
|
1124
|
-
// Pattern: .builders/<id> or .builders/<id>-<name>
|
|
1125
|
-
const buildersMatch = cwd.match(/[/\\]\.builders[/\\](\d+)(?:-[^/\\]*)?(?:[/\\]|$)/);
|
|
1126
|
-
if (buildersMatch) {
|
|
1127
|
-
return buildersMatch[1];
|
|
444
|
+
function getInstructions(state, protocol) {
|
|
445
|
+
const phase = state.phase;
|
|
446
|
+
if (isPhased(protocol, phase) && state.plan_phases.length > 0) {
|
|
447
|
+
const current = getCurrentPlanPhase(state.plan_phases);
|
|
448
|
+
if (current) {
|
|
449
|
+
return ` You are implementing ${current.id}: "${current.title}".\n\n Complete the work, then run: porch check ${state.id}`;
|
|
450
|
+
}
|
|
1128
451
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
452
|
+
const phaseConfig = getPhaseConfig(protocol, phase);
|
|
453
|
+
return ` You are in the ${phaseConfig?.name || phase} phase.\n\n When complete, run: porch done ${state.id}`;
|
|
454
|
+
}
|
|
455
|
+
function getNextAction(state, protocol) {
|
|
456
|
+
const checks = getPhaseChecks(protocol, state.phase);
|
|
457
|
+
const gate = getPhaseGate(protocol, state.phase);
|
|
458
|
+
if (gate && state.gates[gate]?.status === 'pending' && state.gates[gate]?.requested_at) {
|
|
459
|
+
return chalk.yellow('Wait for human to approve the gate.');
|
|
1133
460
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
if (content) {
|
|
1139
|
-
return content;
|
|
461
|
+
if (isPhased(protocol, state.phase)) {
|
|
462
|
+
const current = getCurrentPlanPhase(state.plan_phases);
|
|
463
|
+
if (current) {
|
|
464
|
+
return `Implement ${current.title} as specified in the plan.`;
|
|
1140
465
|
}
|
|
1141
466
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
const projectRoot = findProjectRoot();
|
|
1145
|
-
const projects = findProjects(projectRoot);
|
|
1146
|
-
if (projects.length === 1) {
|
|
1147
|
-
return projects[0].id;
|
|
1148
|
-
}
|
|
467
|
+
if (Object.keys(checks).length > 0) {
|
|
468
|
+
return `Complete the phase work, then run: porch check ${state.id}`;
|
|
1149
469
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
470
|
+
return `Complete the phase work, then run: porch done ${state.id}`;
|
|
471
|
+
}
|
|
472
|
+
function getArtifactForPhase(projectRoot, state) {
|
|
473
|
+
switch (state.phase) {
|
|
474
|
+
case 'specify':
|
|
475
|
+
return `codev/specs/${state.id}-${state.title}.md`;
|
|
476
|
+
case 'plan':
|
|
477
|
+
return `codev/plans/${state.id}-${state.title}.md`;
|
|
478
|
+
case 'review':
|
|
479
|
+
return `codev/reviews/${state.id}-${state.title}.md`;
|
|
480
|
+
default:
|
|
481
|
+
return null;
|
|
1152
482
|
}
|
|
1153
|
-
return null;
|
|
1154
483
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}
|
|
1167
|
-
projectId = detected;
|
|
1168
|
-
console.log(chalk.blue(`[porch] Auto-detected project: ${projectId}`));
|
|
1169
|
-
}
|
|
1170
|
-
await run(projectId, { dryRun, noClaude, pollInterval });
|
|
1171
|
-
break;
|
|
1172
|
-
}
|
|
1173
|
-
case 'init': {
|
|
1174
|
-
if (args.length < 3) {
|
|
1175
|
-
throw new Error('Usage: porch init <protocol> <project-id> <project-name>');
|
|
1176
|
-
}
|
|
1177
|
-
await init(args[0], args[1], args[2], { description, worktree });
|
|
1178
|
-
break;
|
|
1179
|
-
}
|
|
1180
|
-
case 'approve': {
|
|
1181
|
-
if (args.length < 2) {
|
|
1182
|
-
throw new Error('Usage: porch approve <project-id> <gate-id>');
|
|
1183
|
-
}
|
|
1184
|
-
await approve(args[0], args[1]);
|
|
1185
|
-
break;
|
|
1186
|
-
}
|
|
1187
|
-
case 'status': {
|
|
1188
|
-
await status(args[0]);
|
|
1189
|
-
break;
|
|
1190
|
-
}
|
|
1191
|
-
case 'pending': {
|
|
1192
|
-
await pending();
|
|
1193
|
-
break;
|
|
484
|
+
// ============================================================================
|
|
485
|
+
// CLI
|
|
486
|
+
// ============================================================================
|
|
487
|
+
export async function cli(args) {
|
|
488
|
+
const [command, ...rest] = args;
|
|
489
|
+
const projectRoot = process.cwd();
|
|
490
|
+
// Auto-detect project ID for commands that need it
|
|
491
|
+
function getProjectId(provided) {
|
|
492
|
+
const { id, source } = resolveProjectId(provided, process.cwd(), projectRoot);
|
|
493
|
+
if (source === 'cwd') {
|
|
494
|
+
console.log(chalk.dim(`[auto-detected project from worktree: ${id}]`));
|
|
1194
495
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
await list();
|
|
1198
|
-
break;
|
|
496
|
+
else if (source === 'filesystem') {
|
|
497
|
+
console.log(chalk.dim(`[auto-detected project: ${id}]`));
|
|
1199
498
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
499
|
+
return id;
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
switch (command) {
|
|
503
|
+
case 'next': {
|
|
504
|
+
const { next: porchNext } = await import('./next.js');
|
|
505
|
+
const result = await porchNext(projectRoot, getProjectId(rest[0]));
|
|
506
|
+
console.log(JSON.stringify(result, null, 2));
|
|
507
|
+
break;
|
|
1204
508
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
509
|
+
case 'run':
|
|
510
|
+
console.error("Error: 'porch run' has been removed. Use 'porch next <id>' instead.");
|
|
511
|
+
console.error("See: porch --help");
|
|
512
|
+
process.exit(1);
|
|
513
|
+
break;
|
|
514
|
+
case 'status':
|
|
515
|
+
await status(projectRoot, getProjectId(rest[0]));
|
|
516
|
+
break;
|
|
517
|
+
case 'check':
|
|
518
|
+
await check(projectRoot, getProjectId(rest[0]));
|
|
519
|
+
break;
|
|
520
|
+
case 'done':
|
|
521
|
+
await done(projectRoot, getProjectId(rest[0]));
|
|
522
|
+
break;
|
|
523
|
+
case 'gate':
|
|
524
|
+
await gate(projectRoot, getProjectId(rest[0]));
|
|
525
|
+
break;
|
|
526
|
+
case 'approve':
|
|
527
|
+
if (!rest[0] || !rest[1])
|
|
528
|
+
throw new Error('Usage: porch approve <id> <gate> --a-human-explicitly-approved-this');
|
|
529
|
+
const hasHumanFlag = rest.includes('--a-human-explicitly-approved-this');
|
|
530
|
+
await approve(projectRoot, rest[0], rest[1], hasHumanFlag);
|
|
531
|
+
break;
|
|
532
|
+
case 'init':
|
|
533
|
+
if (!rest[0] || !rest[1] || !rest[2]) {
|
|
534
|
+
throw new Error('Usage: porch init <protocol> <id> <name>');
|
|
535
|
+
}
|
|
536
|
+
await init(projectRoot, rest[0], rest[1], rest[2]);
|
|
537
|
+
break;
|
|
538
|
+
default:
|
|
539
|
+
console.log('porch - Protocol Orchestrator');
|
|
540
|
+
console.log('');
|
|
541
|
+
console.log('Commands:');
|
|
542
|
+
console.log(' next [id] Emit next tasks as JSON (planner mode)');
|
|
543
|
+
console.log(' status [id] Show current state and instructions');
|
|
544
|
+
console.log(' check [id] Run checks for current phase');
|
|
545
|
+
console.log(' done [id] Signal build complete (validates checks, advances)');
|
|
546
|
+
console.log(' gate [id] Request human approval');
|
|
547
|
+
console.log(' approve <id> <gate> --a-human-explicitly-approved-this');
|
|
548
|
+
console.log(' init <protocol> <id> <name> Initialize a new project');
|
|
549
|
+
console.log('');
|
|
550
|
+
console.log('Project ID is auto-detected from worktree path or when exactly one project exists.');
|
|
551
|
+
console.log('');
|
|
552
|
+
process.exit(command && command !== '--help' && command !== '-h' ? 1 : 0);
|
|
1207
553
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
557
|
+
process.exit(1);
|
|
1211
558
|
}
|
|
1212
559
|
}
|
|
1213
560
|
//# sourceMappingURL=index.js.map
|