@cluesmith/codev 2.0.0-rc.6 → 2.0.0-rc.61
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-CXloFYpB.css +32 -0
- package/dashboard/dist/assets/index-Ca2fjOJf.js +131 -0
- package/dashboard/dist/assets/index-Ca2fjOJf.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 +94 -65
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +13 -6
- 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 +202 -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 +30 -3
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/consult.js +1 -1
- 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 +34 -70
- package/dist/agent-farm/commands/open.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +55 -17
- 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 +61 -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 +503 -226
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/start.d.ts +3 -0
- package/dist/agent-farm/commands/start.d.ts.map +1 -1
- package/dist/agent-farm/commands/start.js +58 -265
- 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 +61 -3
- 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 +116 -12
- package/dist/agent-farm/commands/stop.js.map +1 -1
- 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.map +1 -1
- package/dist/agent-farm/db/index.js +124 -0
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/schema.d.ts +2 -2
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +26 -5
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +3 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js +3 -0
- 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/tower-client.d.ts +157 -0
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -0
- package/dist/agent-farm/lib/tower-client.js +223 -0
- package/dist/agent-farm/lib/tower-client.js.map +1 -0
- package/dist/agent-farm/servers/tower-server.js +2340 -109
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/state.d.ts +4 -10
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +30 -31
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +48 -1
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.d.ts.map +1 -1
- package/dist/agent-farm/utils/config.js +13 -14
- 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 -16
- package/dist/agent-farm/utils/deps.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/port-registry.d.ts +0 -1
- package/dist/agent-farm/utils/port-registry.d.ts.map +1 -1
- package/dist/agent-farm/utils/port-registry.js +1 -1
- package/dist/agent-farm/utils/port-registry.js.map +1 -1
- package/dist/agent-farm/utils/server-utils.d.ts +4 -4
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +4 -15
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- 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/agent-farm/utils/terminal-ports.d.ts +1 -1
- package/dist/agent-farm/utils/terminal-ports.js +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -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 +2 -0
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +103 -6
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +0 -15
- 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 +21 -43
- package/dist/commands/porch/index.d.ts.map +1 -1
- package/dist/commands/porch/index.js +418 -1123
- 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 +481 -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 +255 -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 +23 -112
- package/dist/commands/porch/state.d.ts.map +1 -1
- package/dist/commands/porch/state.js +81 -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 +60 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -0
- package/dist/terminal/pty-manager.js +334 -0
- package/dist/terminal/pty-manager.js.map +1 -0
- package/dist/terminal/pty-session.d.ts +79 -0
- package/dist/terminal/pty-session.d.ts.map +1 -0
- package/dist/terminal/pty-session.js +215 -0
- package/dist/terminal/pty-session.js.map +1 -0
- package/dist/terminal/ring-buffer.d.ts +27 -0
- package/dist/terminal/ring-buffer.d.ts.map +1 -0
- package/dist/terminal/ring-buffer.js +74 -0
- package/dist/terminal/ring-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 +18 -5
- package/skeleton/.claude/skills/af/SKILL.md +74 -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 +3 -29
- package/skeleton/builders.md +1 -1
- package/skeleton/consult-types/impl-review.md +9 -0
- 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 +61 -0
- package/skeleton/protocols/bugfix/protocol.json +19 -2
- 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 +14 -8
- 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 +7 -25
- package/skeleton/protocols/{spider → spir}/prompts/specify.md +33 -61
- package/skeleton/protocols/spir/protocol.json +152 -0
- package/skeleton/protocols/{spider → spir}/protocol.md +35 -21
- package/skeleton/protocols/{spider → spir}/templates/plan.md +14 -0
- package/skeleton/protocols/{spider → spir}/templates/review.md +1 -1
- 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 +25 -43
- 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 +109 -218
- 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 +542 -27
- package/dist/agent-farm/commands/kickoff.d.ts +0 -19
- package/dist/agent-farm/commands/kickoff.d.ts.map +0 -1
- package/dist/agent-farm/commands/kickoff.js +0 -331
- 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/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/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,774 +1,156 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Porch State Management
|
|
3
3
|
*
|
|
4
|
-
* Handles project state persistence with
|
|
5
|
-
*
|
|
6
|
-
* - Atomic writes (tmp file + fsync + rename)
|
|
7
|
-
* - File locking (flock advisory locking)
|
|
8
|
-
* - Crash recovery
|
|
4
|
+
* Handles project state persistence with atomic writes.
|
|
5
|
+
* Fails loudly on any error - no guessing.
|
|
9
6
|
*/
|
|
10
7
|
import * as fs from 'node:fs';
|
|
11
8
|
import * as path from 'node:path';
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
// Constants
|
|
15
|
-
// ============================================================================
|
|
16
|
-
/** Directory for SPIDER project state (relative to project root) */
|
|
9
|
+
import * as yaml from 'js-yaml';
|
|
10
|
+
/** Directory for project state (relative to project root) */
|
|
17
11
|
export const PROJECTS_DIR = 'codev/projects';
|
|
18
|
-
/** Directory for TICK/BUGFIX execution state (relative to project root) */
|
|
19
|
-
export const EXECUTIONS_DIR = 'codev/executions';
|
|
20
|
-
/** Lock timeout in milliseconds */
|
|
21
|
-
const LOCK_TIMEOUT_MS = 5000;
|
|
22
|
-
/** Lock retry interval in milliseconds */
|
|
23
|
-
const LOCK_RETRY_MS = 100;
|
|
24
12
|
// ============================================================================
|
|
25
13
|
// Path Utilities
|
|
26
14
|
// ============================================================================
|
|
27
15
|
/**
|
|
28
|
-
* Get the
|
|
29
|
-
*/
|
|
30
|
-
export function getProjectStatusPath(projectRoot, projectId, name) {
|
|
31
|
-
const projectDir = name
|
|
32
|
-
? path.join(projectRoot, PROJECTS_DIR, `${projectId}-${name}`)
|
|
33
|
-
: path.join(projectRoot, PROJECTS_DIR, projectId);
|
|
34
|
-
return path.join(projectDir, 'status.yaml');
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Get the status file path for a TICK/BUGFIX execution
|
|
38
|
-
*/
|
|
39
|
-
export function getExecutionStatusPath(projectRoot, protocol, id, name) {
|
|
40
|
-
const dirName = name ? `${protocol}_${id}_${name}` : `${protocol}_${id}`;
|
|
41
|
-
return path.join(projectRoot, EXECUTIONS_DIR, dirName, 'status.yaml');
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Get the project directory for a SPIDER project
|
|
16
|
+
* Get the project directory path
|
|
45
17
|
*/
|
|
46
18
|
export function getProjectDir(projectRoot, projectId, name) {
|
|
47
|
-
return name
|
|
48
|
-
? path.join(projectRoot, PROJECTS_DIR, `${projectId}-${name}`)
|
|
49
|
-
: path.join(projectRoot, PROJECTS_DIR, projectId);
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Get the worktree path for a protocol execution
|
|
53
|
-
*/
|
|
54
|
-
export function getWorktreePath(projectRoot, protocol, id, name) {
|
|
55
|
-
const dirName = name ? `${protocol}_${id}_${name}` : `${protocol}_${id}`;
|
|
56
|
-
return path.join(projectRoot, 'worktrees', dirName);
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Acquire an advisory lock on a file
|
|
60
|
-
* Creates a .lock file to indicate lock ownership
|
|
61
|
-
*/
|
|
62
|
-
export async function acquireLock(filePath) {
|
|
63
|
-
const lockFile = `${filePath}.lock`;
|
|
64
|
-
const startTime = Date.now();
|
|
65
|
-
while (Date.now() - startTime < LOCK_TIMEOUT_MS) {
|
|
66
|
-
try {
|
|
67
|
-
// Try to create lock file exclusively
|
|
68
|
-
const fd = openSync(lockFile, 'wx');
|
|
69
|
-
// Write our PID for debugging
|
|
70
|
-
writeFileSync(lockFile, `${process.pid}\n`);
|
|
71
|
-
return { fd, lockFile };
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
if (err.code === 'EEXIST') {
|
|
75
|
-
// Lock file exists, check if stale
|
|
76
|
-
try {
|
|
77
|
-
const stat = fs.statSync(lockFile);
|
|
78
|
-
// If lock is older than 60 seconds, consider it stale
|
|
79
|
-
if (Date.now() - stat.mtimeMs > 60000) {
|
|
80
|
-
unlinkSync(lockFile);
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
// Lock file disappeared, retry
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
// Wait and retry
|
|
89
|
-
await new Promise(r => setTimeout(r, LOCK_RETRY_MS));
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
throw err;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
throw new Error(`Failed to acquire lock on ${filePath} after ${LOCK_TIMEOUT_MS}ms`);
|
|
19
|
+
return path.join(projectRoot, PROJECTS_DIR, `${projectId}-${name}`);
|
|
97
20
|
}
|
|
98
21
|
/**
|
|
99
|
-
*
|
|
22
|
+
* Get the status.yaml path for a project
|
|
100
23
|
*/
|
|
101
|
-
export function
|
|
102
|
-
|
|
103
|
-
closeSync(lock.fd);
|
|
104
|
-
unlinkSync(lock.lockFile);
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// Ignore errors during cleanup
|
|
108
|
-
}
|
|
24
|
+
export function getStatusPath(projectRoot, projectId, name) {
|
|
25
|
+
return path.join(getProjectDir(projectRoot, projectId, name), 'status.yaml');
|
|
109
26
|
}
|
|
110
27
|
// ============================================================================
|
|
111
|
-
//
|
|
28
|
+
// State Operations
|
|
112
29
|
// ============================================================================
|
|
113
30
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
31
|
+
* Read project state from status.yaml
|
|
32
|
+
* Fails loudly if file is missing or corrupted.
|
|
116
33
|
*/
|
|
117
|
-
export function
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
lines.push(`id: "${state.id}"`);
|
|
121
|
-
lines.push(`title: "${state.title}"`);
|
|
122
|
-
lines.push(`protocol: "${state.protocol}"`);
|
|
123
|
-
lines.push(`state: "${state.current_state}"`);
|
|
124
|
-
if (state.worktree) {
|
|
125
|
-
lines.push(`worktree: "${state.worktree}"`);
|
|
126
|
-
}
|
|
127
|
-
lines.push('');
|
|
128
|
-
// Gates
|
|
129
|
-
lines.push('gates:');
|
|
130
|
-
if (state.gates && Object.keys(state.gates).length > 0) {
|
|
131
|
-
for (const [gateId, gateStatus] of Object.entries(state.gates)) {
|
|
132
|
-
const status = gateStatus.status || 'pending';
|
|
133
|
-
const requestedAt = gateStatus.requested_at ? `, requested_at: "${gateStatus.requested_at}"` : '';
|
|
134
|
-
const approvedAt = gateStatus.approved_at ? `, approved_at: "${gateStatus.approved_at}"` : '';
|
|
135
|
-
lines.push(` ${gateId}: { status: ${status}${requestedAt}${approvedAt} }`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
lines.push(' # No gates defined');
|
|
140
|
-
}
|
|
141
|
-
lines.push('');
|
|
142
|
-
// Phases (for phased implementation)
|
|
143
|
-
lines.push('phases:');
|
|
144
|
-
if (state.phases && Object.keys(state.phases).length > 0) {
|
|
145
|
-
for (const [phaseId, phaseStatus] of Object.entries(state.phases)) {
|
|
146
|
-
if (typeof phaseStatus === 'object' && phaseStatus !== null) {
|
|
147
|
-
const ps = phaseStatus;
|
|
148
|
-
const status = ps.status || 'pending';
|
|
149
|
-
const title = ps.title ? `, title: "${ps.title}"` : '';
|
|
150
|
-
lines.push(` ${phaseId}: { status: ${status}${title} }`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
lines.push(' # No phases extracted yet');
|
|
156
|
-
}
|
|
157
|
-
lines.push('');
|
|
158
|
-
// Plan phases (extracted from plan.md)
|
|
159
|
-
if (state.plan_phases && state.plan_phases.length > 0) {
|
|
160
|
-
lines.push('plan_phases:');
|
|
161
|
-
for (const phase of state.plan_phases) {
|
|
162
|
-
lines.push(` - id: "${phase.id}"`);
|
|
163
|
-
lines.push(` title: "${phase.title}"`);
|
|
164
|
-
if (phase.description) {
|
|
165
|
-
lines.push(` description: "${phase.description.replace(/"/g, '\\"')}"`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
lines.push('');
|
|
34
|
+
export function readState(statusPath) {
|
|
35
|
+
if (!fs.existsSync(statusPath)) {
|
|
36
|
+
throw new Error(`Project not found: ${statusPath}\nRun 'porch init' to create a new project.`);
|
|
169
37
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
lines.push('');
|
|
177
|
-
}
|
|
178
|
-
// Metadata
|
|
179
|
-
lines.push(`iteration: ${state.iteration || 0}`);
|
|
180
|
-
lines.push(`started_at: "${state.started_at || new Date().toISOString()}"`);
|
|
181
|
-
lines.push(`last_updated: "${new Date().toISOString()}"`);
|
|
182
|
-
lines.push('');
|
|
183
|
-
// Log
|
|
184
|
-
lines.push('log:');
|
|
185
|
-
if (state.log && state.log.length > 0) {
|
|
186
|
-
for (const entry of state.log) {
|
|
187
|
-
if (typeof entry === 'string') {
|
|
188
|
-
lines.push(` - "${entry}"`);
|
|
189
|
-
}
|
|
190
|
-
else if (typeof entry === 'object' && entry !== null) {
|
|
191
|
-
const logEntry = entry;
|
|
192
|
-
const ts = logEntry.ts || new Date().toISOString();
|
|
193
|
-
const event = logEntry.event || 'unknown';
|
|
194
|
-
let entryLine = ` - ts: "${ts}"`;
|
|
195
|
-
lines.push(entryLine);
|
|
196
|
-
lines.push(` event: "${event}"`);
|
|
197
|
-
if (logEntry.from)
|
|
198
|
-
lines.push(` from: "${logEntry.from}"`);
|
|
199
|
-
if (logEntry.to)
|
|
200
|
-
lines.push(` to: "${logEntry.to}"`);
|
|
201
|
-
if (logEntry.signal)
|
|
202
|
-
lines.push(` signal: "${logEntry.signal}"`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return lines.join('\n') + '\n';
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* Parse YAML status file into ProjectState
|
|
210
|
-
*/
|
|
211
|
-
export function parseState(content) {
|
|
212
|
-
const state = {
|
|
213
|
-
gates: {},
|
|
214
|
-
phases: {},
|
|
215
|
-
log: [],
|
|
216
|
-
consultation_attempts: {},
|
|
217
|
-
};
|
|
218
|
-
const lines = content.split('\n');
|
|
219
|
-
let currentSection = '';
|
|
220
|
-
let currentArrayItem = null;
|
|
221
|
-
for (const line of lines) {
|
|
222
|
-
// Skip comments and empty lines
|
|
223
|
-
if (line.trim().startsWith('#') || line.trim() === '') {
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
// Reset section if we hit a non-indented line (top-level field)
|
|
227
|
-
if (currentSection && !line.startsWith(' ') && !line.startsWith('\t')) {
|
|
228
|
-
// If we have a pending array item, push it before leaving the section
|
|
229
|
-
if (currentArrayItem) {
|
|
230
|
-
if (currentSection === 'plan_phases') {
|
|
231
|
-
state.plan_phases.push(currentArrayItem);
|
|
232
|
-
}
|
|
233
|
-
else if (currentSection === 'log') {
|
|
234
|
-
state.log.push(currentArrayItem);
|
|
235
|
-
}
|
|
236
|
-
currentArrayItem = null;
|
|
237
|
-
}
|
|
238
|
-
currentSection = '';
|
|
239
|
-
}
|
|
240
|
-
// Detect section headers
|
|
241
|
-
if (line.match(/^gates:\s*$/)) {
|
|
242
|
-
currentSection = 'gates';
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
if (line.match(/^phases:\s*$/)) {
|
|
246
|
-
currentSection = 'phases';
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
if (line.match(/^plan_phases:\s*$/)) {
|
|
250
|
-
currentSection = 'plan_phases';
|
|
251
|
-
state.plan_phases = [];
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
if (line.match(/^log:\s*$/)) {
|
|
255
|
-
currentSection = 'log';
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (line.match(/^consultation_attempts:\s*$/)) {
|
|
259
|
-
currentSection = 'consultation_attempts';
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
// Parse based on section
|
|
263
|
-
if (currentSection === 'gates') {
|
|
264
|
-
// Parse: gate_id: { status: pending, requested_at: "..." }
|
|
265
|
-
const match = line.match(/^\s+(\w+):\s*\{\s*status:\s*(\w+)(?:,\s*requested_at:\s*"([^"]*)")?(?:,\s*approved_at:\s*"([^"]*)")?\s*\}/);
|
|
266
|
-
if (match) {
|
|
267
|
-
const [, gateId, status, requestedAt, approvedAt] = match;
|
|
268
|
-
state.gates[gateId] = {
|
|
269
|
-
status: status,
|
|
270
|
-
...(requestedAt && { requested_at: requestedAt }),
|
|
271
|
-
...(approvedAt && { approved_at: approvedAt }),
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
if (currentSection === 'phases') {
|
|
277
|
-
// Parse: phase_id: { status: pending, title: "..." }
|
|
278
|
-
const match = line.match(/^\s+(\w+):\s*\{\s*status:\s*(\w+)(?:,\s*title:\s*"([^"]*)")?\s*\}/);
|
|
279
|
-
if (match) {
|
|
280
|
-
const [, phaseId, status, title] = match;
|
|
281
|
-
state.phases[phaseId] = {
|
|
282
|
-
status: status,
|
|
283
|
-
...(title && { title }),
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
if (currentSection === 'plan_phases') {
|
|
289
|
-
// Parse array items
|
|
290
|
-
if (line.match(/^\s+-\s+id:/)) {
|
|
291
|
-
if (currentArrayItem) {
|
|
292
|
-
state.plan_phases.push(currentArrayItem);
|
|
293
|
-
}
|
|
294
|
-
currentArrayItem = {};
|
|
295
|
-
const idMatch = line.match(/id:\s*"([^"]*)"/);
|
|
296
|
-
if (idMatch)
|
|
297
|
-
currentArrayItem.id = idMatch[1];
|
|
298
|
-
}
|
|
299
|
-
else if (line.match(/^\s+title:/)) {
|
|
300
|
-
const titleMatch = line.match(/title:\s*"([^"]*)"/);
|
|
301
|
-
if (titleMatch && currentArrayItem)
|
|
302
|
-
currentArrayItem.title = titleMatch[1];
|
|
303
|
-
}
|
|
304
|
-
else if (line.match(/^\s+description:/)) {
|
|
305
|
-
const descMatch = line.match(/description:\s*"([^"]*)"/);
|
|
306
|
-
if (descMatch && currentArrayItem)
|
|
307
|
-
currentArrayItem.description = descMatch[1];
|
|
308
|
-
}
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
if (currentSection === 'consultation_attempts') {
|
|
312
|
-
// Parse: "state:key": count
|
|
313
|
-
const match = line.match(/^\s+"([^"]+)":\s*(\d+)/);
|
|
314
|
-
if (match) {
|
|
315
|
-
const [, stateKey, count] = match;
|
|
316
|
-
state.consultation_attempts[stateKey] = parseInt(count, 10);
|
|
317
|
-
}
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
if (currentSection === 'log') {
|
|
321
|
-
// Parse log entries - simplified
|
|
322
|
-
if (line.match(/^\s+-\s+ts:/)) {
|
|
323
|
-
if (currentArrayItem) {
|
|
324
|
-
state.log.push(currentArrayItem);
|
|
325
|
-
}
|
|
326
|
-
currentArrayItem = {};
|
|
327
|
-
const tsMatch = line.match(/ts:\s*"([^"]*)"/);
|
|
328
|
-
if (tsMatch)
|
|
329
|
-
currentArrayItem.ts = tsMatch[1];
|
|
330
|
-
}
|
|
331
|
-
else if (line.match(/^\s+event:/)) {
|
|
332
|
-
const eventMatch = line.match(/event:\s*"([^"]*)"/);
|
|
333
|
-
if (eventMatch && currentArrayItem)
|
|
334
|
-
currentArrayItem.event = eventMatch[1];
|
|
335
|
-
}
|
|
336
|
-
else if (line.match(/^\s+from:/)) {
|
|
337
|
-
const fromMatch = line.match(/from:\s*"([^"]*)"/);
|
|
338
|
-
if (fromMatch && currentArrayItem)
|
|
339
|
-
currentArrayItem.from = fromMatch[1];
|
|
340
|
-
}
|
|
341
|
-
else if (line.match(/^\s+to:/)) {
|
|
342
|
-
const toMatch = line.match(/to:\s*"([^"]*)"/);
|
|
343
|
-
if (toMatch && currentArrayItem)
|
|
344
|
-
currentArrayItem.to = toMatch[1];
|
|
345
|
-
}
|
|
346
|
-
else if (line.match(/^\s+signal:/)) {
|
|
347
|
-
const signalMatch = line.match(/signal:\s*"([^"]*)"/);
|
|
348
|
-
if (signalMatch && currentArrayItem)
|
|
349
|
-
currentArrayItem.signal = signalMatch[1];
|
|
350
|
-
}
|
|
351
|
-
else if (line.match(/^\s+-\s*"[^"]*"/)) {
|
|
352
|
-
// Simple string log entry
|
|
353
|
-
const strMatch = line.match(/^\s+-\s*"([^"]*)"/);
|
|
354
|
-
if (strMatch)
|
|
355
|
-
state.log.push(strMatch[1]);
|
|
356
|
-
}
|
|
357
|
-
continue;
|
|
38
|
+
try {
|
|
39
|
+
const content = fs.readFileSync(statusPath, 'utf-8');
|
|
40
|
+
const state = yaml.load(content);
|
|
41
|
+
// Basic validation
|
|
42
|
+
if (!state || typeof state !== 'object') {
|
|
43
|
+
throw new Error('Invalid state file: not an object');
|
|
358
44
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (kvMatch) {
|
|
362
|
-
const [, key, value] = kvMatch;
|
|
363
|
-
switch (key) {
|
|
364
|
-
case 'id':
|
|
365
|
-
state.id = value;
|
|
366
|
-
break;
|
|
367
|
-
case 'title':
|
|
368
|
-
state.title = value;
|
|
369
|
-
break;
|
|
370
|
-
case 'protocol':
|
|
371
|
-
state.protocol = value;
|
|
372
|
-
break;
|
|
373
|
-
case 'state':
|
|
374
|
-
state.current_state = value;
|
|
375
|
-
break;
|
|
376
|
-
case 'worktree':
|
|
377
|
-
state.worktree = value;
|
|
378
|
-
break;
|
|
379
|
-
case 'iteration':
|
|
380
|
-
state.iteration = parseInt(value, 10);
|
|
381
|
-
break;
|
|
382
|
-
case 'started_at':
|
|
383
|
-
state.started_at = value;
|
|
384
|
-
break;
|
|
385
|
-
case 'last_updated':
|
|
386
|
-
state.last_updated = value;
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
45
|
+
if (!state.id || !state.protocol || !state.phase) {
|
|
46
|
+
throw new Error('Invalid state file: missing required fields (id, protocol, phase)');
|
|
389
47
|
}
|
|
48
|
+
return state;
|
|
390
49
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
state.plan_phases.push(currentArrayItem);
|
|
395
|
-
}
|
|
396
|
-
else if (currentSection === 'log') {
|
|
397
|
-
state.log.push(currentArrayItem);
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err instanceof yaml.YAMLException) {
|
|
52
|
+
throw new Error(`Invalid state file: YAML parse error\n${err.message}`);
|
|
398
53
|
}
|
|
54
|
+
throw err;
|
|
399
55
|
}
|
|
400
|
-
return state;
|
|
401
56
|
}
|
|
402
|
-
// ============================================================================
|
|
403
|
-
// State Operations
|
|
404
|
-
// ============================================================================
|
|
405
57
|
/**
|
|
406
|
-
*
|
|
58
|
+
* Write project state atomically (tmp file + rename)
|
|
407
59
|
*/
|
|
408
|
-
export function
|
|
409
|
-
|
|
410
|
-
const tmpPath = `${
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
if (!fs.existsSync(statusFilePath)) {
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
428
|
-
const content = readFileSync(statusFilePath, 'utf-8');
|
|
429
|
-
return parseState(content);
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Write project state atomically
|
|
433
|
-
* Uses tmp file + fsync + rename for crash safety
|
|
434
|
-
*/
|
|
435
|
-
export async function writeState(statusFilePath, state) {
|
|
436
|
-
const lock = await acquireLock(statusFilePath);
|
|
437
|
-
try {
|
|
438
|
-
const content = serializeState(state);
|
|
439
|
-
const tmpPath = `${statusFilePath}.tmp`;
|
|
440
|
-
const dir = path.dirname(statusFilePath);
|
|
441
|
-
// Ensure directory exists
|
|
442
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
443
|
-
// Write to temp file
|
|
444
|
-
const fd = openSync(tmpPath, 'w');
|
|
445
|
-
writeFileSync(fd, content);
|
|
446
|
-
fsyncSync(fd);
|
|
447
|
-
closeSync(fd);
|
|
448
|
-
// Atomic rename
|
|
449
|
-
renameSync(tmpPath, statusFilePath);
|
|
450
|
-
}
|
|
451
|
-
finally {
|
|
452
|
-
releaseLock(lock);
|
|
453
|
-
}
|
|
60
|
+
export function writeState(statusPath, state) {
|
|
61
|
+
const dir = path.dirname(statusPath);
|
|
62
|
+
const tmpPath = `${statusPath}.tmp`;
|
|
63
|
+
// Ensure directory exists
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
+
// Update timestamp
|
|
66
|
+
state.updated_at = new Date().toISOString();
|
|
67
|
+
// Write to temp file then rename (atomic)
|
|
68
|
+
const content = yaml.dump(state, {
|
|
69
|
+
indent: 2,
|
|
70
|
+
lineWidth: 120,
|
|
71
|
+
noRefs: true,
|
|
72
|
+
});
|
|
73
|
+
fs.writeFileSync(tmpPath, content, 'utf-8');
|
|
74
|
+
fs.renameSync(tmpPath, statusPath);
|
|
454
75
|
}
|
|
455
76
|
/**
|
|
456
|
-
* Create initial project
|
|
77
|
+
* Create initial state for a new project.
|
|
78
|
+
*
|
|
79
|
+
* Always starts at the first protocol phase. If artifacts (spec, plan)
|
|
80
|
+
* already exist with approval metadata (YAML frontmatter), the run loop
|
|
81
|
+
* will detect this and skip those phases automatically.
|
|
457
82
|
*/
|
|
458
|
-
export function createInitialState(protocol, projectId, title,
|
|
83
|
+
export function createInitialState(protocol, projectId, title, _projectRoot) {
|
|
459
84
|
const now = new Date().toISOString();
|
|
460
|
-
//
|
|
85
|
+
// Initialize gates from protocol
|
|
461
86
|
const gates = {};
|
|
462
87
|
for (const phase of protocol.phases) {
|
|
463
88
|
if (phase.gate) {
|
|
464
|
-
|
|
465
|
-
gates[gateId] = { status: 'pending' };
|
|
89
|
+
gates[phase.gate] = { status: 'pending' };
|
|
466
90
|
}
|
|
467
91
|
}
|
|
92
|
+
const initialPhase = protocol.phases[0]?.id || 'specify';
|
|
468
93
|
return {
|
|
469
94
|
id: projectId,
|
|
470
95
|
title,
|
|
471
96
|
protocol: protocol.name,
|
|
472
|
-
|
|
473
|
-
worktree: worktreePath,
|
|
474
|
-
gates,
|
|
475
|
-
phases: {},
|
|
97
|
+
phase: initialPhase,
|
|
476
98
|
plan_phases: [],
|
|
477
|
-
|
|
99
|
+
current_plan_phase: null,
|
|
100
|
+
gates,
|
|
101
|
+
iteration: 1,
|
|
102
|
+
build_complete: false,
|
|
103
|
+
history: [],
|
|
478
104
|
started_at: now,
|
|
479
|
-
|
|
480
|
-
log: [{
|
|
481
|
-
ts: now,
|
|
482
|
-
event: 'state_change',
|
|
483
|
-
from: null,
|
|
484
|
-
to: protocol.initial || `${protocol.phases[0]?.id}:draft`,
|
|
485
|
-
}],
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Update state with a new current state
|
|
490
|
-
*/
|
|
491
|
-
export function updateState(state, newState, options = {}) {
|
|
492
|
-
const now = new Date().toISOString();
|
|
493
|
-
const logEntry = {
|
|
494
|
-
ts: now,
|
|
495
|
-
event: 'state_change',
|
|
496
|
-
from: state.current_state,
|
|
497
|
-
to: newState,
|
|
498
|
-
};
|
|
499
|
-
if (options.signal) {
|
|
500
|
-
logEntry.signal = options.signal;
|
|
501
|
-
}
|
|
502
|
-
return {
|
|
503
|
-
...state,
|
|
504
|
-
current_state: newState,
|
|
505
|
-
iteration: state.iteration + 1,
|
|
506
|
-
last_updated: now,
|
|
507
|
-
log: [...state.log, logEntry],
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Approve a gate in state
|
|
512
|
-
*/
|
|
513
|
-
export function approveGate(state, gateId) {
|
|
514
|
-
const now = new Date().toISOString();
|
|
515
|
-
return {
|
|
516
|
-
...state,
|
|
517
|
-
gates: {
|
|
518
|
-
...state.gates,
|
|
519
|
-
[gateId]: {
|
|
520
|
-
...state.gates[gateId],
|
|
521
|
-
status: 'passed',
|
|
522
|
-
approved_at: now,
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
last_updated: now,
|
|
526
|
-
log: [...state.log, {
|
|
527
|
-
ts: now,
|
|
528
|
-
event: 'gate_approved',
|
|
529
|
-
gate: gateId,
|
|
530
|
-
}],
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
/**
|
|
534
|
-
* Request a gate approval (mark as pending with timestamp)
|
|
535
|
-
*/
|
|
536
|
-
export function requestGateApproval(state, gateId) {
|
|
537
|
-
const now = new Date().toISOString();
|
|
538
|
-
return {
|
|
539
|
-
...state,
|
|
540
|
-
gates: {
|
|
541
|
-
...state.gates,
|
|
542
|
-
[gateId]: {
|
|
543
|
-
status: 'pending',
|
|
544
|
-
requested_at: now,
|
|
545
|
-
},
|
|
546
|
-
},
|
|
547
|
-
last_updated: now,
|
|
548
|
-
log: [...state.log, {
|
|
549
|
-
ts: now,
|
|
550
|
-
event: 'gate_requested',
|
|
551
|
-
gate: gateId,
|
|
552
|
-
}],
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* Update phase status
|
|
557
|
-
*/
|
|
558
|
-
export function updatePhaseStatus(state, phaseId, status) {
|
|
559
|
-
const now = new Date().toISOString();
|
|
560
|
-
return {
|
|
561
|
-
...state,
|
|
562
|
-
phases: {
|
|
563
|
-
...state.phases,
|
|
564
|
-
[phaseId]: {
|
|
565
|
-
...state.phases[phaseId],
|
|
566
|
-
status,
|
|
567
|
-
},
|
|
568
|
-
},
|
|
569
|
-
last_updated: now,
|
|
570
|
-
log: [...state.log, {
|
|
571
|
-
ts: now,
|
|
572
|
-
event: 'phase_status_change',
|
|
573
|
-
phase: phaseId,
|
|
574
|
-
status,
|
|
575
|
-
}],
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
/**
|
|
579
|
-
* Set plan phases extracted from plan.md
|
|
580
|
-
*/
|
|
581
|
-
export function setPlanPhases(state, phases) {
|
|
582
|
-
const now = new Date().toISOString();
|
|
583
|
-
// Initialize phase status for each extracted phase
|
|
584
|
-
const phaseStatus = {};
|
|
585
|
-
for (const phase of phases) {
|
|
586
|
-
phaseStatus[phase.id] = { status: 'pending', title: phase.title };
|
|
587
|
-
}
|
|
588
|
-
return {
|
|
589
|
-
...state,
|
|
590
|
-
plan_phases: phases,
|
|
591
|
-
phases: phaseStatus,
|
|
592
|
-
last_updated: now,
|
|
593
|
-
log: [...state.log, {
|
|
594
|
-
ts: now,
|
|
595
|
-
event: 'plan_phases_extracted',
|
|
596
|
-
count: phases.length,
|
|
597
|
-
}],
|
|
105
|
+
updated_at: now,
|
|
598
106
|
};
|
|
599
107
|
}
|
|
600
108
|
// ============================================================================
|
|
601
109
|
// Discovery
|
|
602
110
|
// ============================================================================
|
|
603
111
|
/**
|
|
604
|
-
* Find
|
|
112
|
+
* Find status.yaml by project ID (searches for NNNN-* directories)
|
|
605
113
|
*/
|
|
606
|
-
export function
|
|
114
|
+
export function findStatusPath(projectRoot, projectId) {
|
|
607
115
|
const projectsDir = path.join(projectRoot, PROJECTS_DIR);
|
|
608
116
|
if (!fs.existsSync(projectsDir)) {
|
|
609
|
-
return
|
|
117
|
+
return null;
|
|
610
118
|
}
|
|
611
119
|
const entries = fs.readdirSync(projectsDir, { withFileTypes: true });
|
|
612
|
-
const projects = [];
|
|
613
120
|
for (const entry of entries) {
|
|
614
|
-
if (entry.isDirectory()) {
|
|
121
|
+
if (entry.isDirectory() && entry.name.startsWith(`${projectId}-`)) {
|
|
615
122
|
const statusPath = path.join(projectsDir, entry.name, 'status.yaml');
|
|
616
123
|
if (fs.existsSync(statusPath)) {
|
|
617
|
-
|
|
618
|
-
const idMatch = entry.name.match(/^(\d+)/);
|
|
619
|
-
if (idMatch) {
|
|
620
|
-
projects.push({
|
|
621
|
-
id: idMatch[1],
|
|
622
|
-
path: statusPath,
|
|
623
|
-
});
|
|
624
|
-
}
|
|
124
|
+
return statusPath;
|
|
625
125
|
}
|
|
626
126
|
}
|
|
627
127
|
}
|
|
628
|
-
return
|
|
128
|
+
return null;
|
|
629
129
|
}
|
|
630
130
|
/**
|
|
631
|
-
*
|
|
131
|
+
* Auto-detect project ID when only one project exists.
|
|
132
|
+
* Returns null if zero or multiple projects found.
|
|
632
133
|
*/
|
|
633
|
-
export function
|
|
634
|
-
const
|
|
635
|
-
if (!fs.existsSync(
|
|
636
|
-
return
|
|
134
|
+
export function detectProjectId(projectRoot) {
|
|
135
|
+
const projectsDir = path.join(projectRoot, PROJECTS_DIR);
|
|
136
|
+
if (!fs.existsSync(projectsDir)) {
|
|
137
|
+
return null;
|
|
637
138
|
}
|
|
638
|
-
const entries = fs.readdirSync(
|
|
639
|
-
const
|
|
139
|
+
const entries = fs.readdirSync(projectsDir, { withFileTypes: true });
|
|
140
|
+
const projects = [];
|
|
640
141
|
for (const entry of entries) {
|
|
641
142
|
if (entry.isDirectory()) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const
|
|
646
|
-
if (match) {
|
|
647
|
-
executions.push({
|
|
648
|
-
protocol: match[1],
|
|
649
|
-
id: match[2],
|
|
650
|
-
path: statusPath,
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
return executions;
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Find status file for a project by ID
|
|
660
|
-
*/
|
|
661
|
-
export function findStatusFile(projectRoot, projectId) {
|
|
662
|
-
// Check projects directory first
|
|
663
|
-
const projectsDir = path.join(projectRoot, PROJECTS_DIR);
|
|
664
|
-
if (fs.existsSync(projectsDir)) {
|
|
665
|
-
const entries = fs.readdirSync(projectsDir);
|
|
666
|
-
for (const entry of entries) {
|
|
667
|
-
if (entry.startsWith(projectId)) {
|
|
668
|
-
const statusPath = path.join(projectsDir, entry, 'status.yaml');
|
|
669
|
-
if (fs.existsSync(statusPath)) {
|
|
670
|
-
return statusPath;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
// Check executions directory
|
|
676
|
-
const executionsDir = path.join(projectRoot, EXECUTIONS_DIR);
|
|
677
|
-
if (fs.existsSync(executionsDir)) {
|
|
678
|
-
const entries = fs.readdirSync(executionsDir);
|
|
679
|
-
for (const entry of entries) {
|
|
680
|
-
if (entry.includes(`_${projectId}`)) {
|
|
681
|
-
const statusPath = path.join(executionsDir, entry, 'status.yaml');
|
|
143
|
+
// Extract project ID from directory name (e.g., "0076-skip-close" -> "0076")
|
|
144
|
+
const match = entry.name.match(/^(\d{4})-/);
|
|
145
|
+
if (match) {
|
|
146
|
+
const statusPath = path.join(projectsDir, entry.name, 'status.yaml');
|
|
682
147
|
if (fs.existsSync(statusPath)) {
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return null;
|
|
689
|
-
}
|
|
690
|
-
// ============================================================================
|
|
691
|
-
// Consultation Attempt Tracking
|
|
692
|
-
// ============================================================================
|
|
693
|
-
/**
|
|
694
|
-
* Get the number of consultation attempts for a given state
|
|
695
|
-
*/
|
|
696
|
-
export function getConsultationAttempts(state, stateKey) {
|
|
697
|
-
return state.consultation_attempts?.[stateKey] ?? 0;
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Increment consultation attempts for a given state
|
|
701
|
-
*/
|
|
702
|
-
export function incrementConsultationAttempts(state, stateKey) {
|
|
703
|
-
const now = new Date().toISOString();
|
|
704
|
-
const currentAttempts = getConsultationAttempts(state, stateKey);
|
|
705
|
-
return {
|
|
706
|
-
...state,
|
|
707
|
-
consultation_attempts: {
|
|
708
|
-
...state.consultation_attempts,
|
|
709
|
-
[stateKey]: currentAttempts + 1,
|
|
710
|
-
},
|
|
711
|
-
last_updated: now,
|
|
712
|
-
log: [...state.log, {
|
|
713
|
-
ts: now,
|
|
714
|
-
event: 'consultation_attempt',
|
|
715
|
-
phase: stateKey,
|
|
716
|
-
count: currentAttempts + 1,
|
|
717
|
-
}],
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
/**
|
|
721
|
-
* Reset consultation attempts for a given state (e.g., after gate approval)
|
|
722
|
-
*/
|
|
723
|
-
export function resetConsultationAttempts(state, stateKey) {
|
|
724
|
-
const newAttempts = { ...state.consultation_attempts };
|
|
725
|
-
delete newAttempts[stateKey];
|
|
726
|
-
return {
|
|
727
|
-
...state,
|
|
728
|
-
consultation_attempts: newAttempts,
|
|
729
|
-
last_updated: new Date().toISOString(),
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
// ============================================================================
|
|
733
|
-
// Discovery
|
|
734
|
-
// ============================================================================
|
|
735
|
-
/**
|
|
736
|
-
* Find all status files with pending gates
|
|
737
|
-
*/
|
|
738
|
-
export function findPendingGates(projectRoot) {
|
|
739
|
-
const pending = [];
|
|
740
|
-
// Check projects
|
|
741
|
-
for (const { id, path: statusPath } of findProjects(projectRoot)) {
|
|
742
|
-
const state = readState(statusPath);
|
|
743
|
-
if (state && state.gates) {
|
|
744
|
-
for (const [gateId, gateStatus] of Object.entries(state.gates)) {
|
|
745
|
-
if (gateStatus.status === 'pending' && gateStatus.requested_at) {
|
|
746
|
-
pending.push({
|
|
747
|
-
projectId: id,
|
|
748
|
-
gateId,
|
|
749
|
-
requestedAt: gateStatus.requested_at,
|
|
750
|
-
statusPath,
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
// Check executions
|
|
757
|
-
for (const { id, path: statusPath } of findExecutions(projectRoot)) {
|
|
758
|
-
const state = readState(statusPath);
|
|
759
|
-
if (state && state.gates) {
|
|
760
|
-
for (const [gateId, gateStatus] of Object.entries(state.gates)) {
|
|
761
|
-
if (gateStatus.status === 'pending' && gateStatus.requested_at) {
|
|
762
|
-
pending.push({
|
|
763
|
-
projectId: id,
|
|
764
|
-
gateId,
|
|
765
|
-
requestedAt: gateStatus.requested_at,
|
|
766
|
-
statusPath,
|
|
767
|
-
});
|
|
148
|
+
projects.push(match[1]);
|
|
768
149
|
}
|
|
769
150
|
}
|
|
770
151
|
}
|
|
771
152
|
}
|
|
772
|
-
return
|
|
153
|
+
// Only return if exactly one project
|
|
154
|
+
return projects.length === 1 ? projects[0] : null;
|
|
773
155
|
}
|
|
774
156
|
//# sourceMappingURL=state.js.map
|