@stoneforge/smithy 0.1.0
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/LICENSE +13 -0
- package/README.md +114 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +7 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/orchestrator-api.d.ts +153 -0
- package/dist/api/orchestrator-api.d.ts.map +1 -0
- package/dist/api/orchestrator-api.js +374 -0
- package/dist/api/orchestrator-api.js.map +1 -0
- package/dist/bin/sf.d.ts +3 -0
- package/dist/bin/sf.d.ts.map +1 -0
- package/dist/bin/sf.js +10 -0
- package/dist/bin/sf.js.map +1 -0
- package/dist/cli/commands/agent.d.ts +20 -0
- package/dist/cli/commands/agent.d.ts.map +1 -0
- package/dist/cli/commands/agent.js +861 -0
- package/dist/cli/commands/agent.js.map +1 -0
- package/dist/cli/commands/daemon.d.ts +14 -0
- package/dist/cli/commands/daemon.d.ts.map +1 -0
- package/dist/cli/commands/daemon.js +272 -0
- package/dist/cli/commands/daemon.js.map +1 -0
- package/dist/cli/commands/dispatch.d.ts +9 -0
- package/dist/cli/commands/dispatch.d.ts.map +1 -0
- package/dist/cli/commands/dispatch.js +128 -0
- package/dist/cli/commands/dispatch.js.map +1 -0
- package/dist/cli/commands/merge.d.ts +11 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +246 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/pool.d.ts +21 -0
- package/dist/cli/commands/pool.d.ts.map +1 -0
- package/dist/cli/commands/pool.js +762 -0
- package/dist/cli/commands/pool.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +54 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +57 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/task.d.ts +36 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +889 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/test-orchestration.d.ts +32 -0
- package/dist/cli/commands/test-orchestration.d.ts.map +1 -0
- package/dist/cli/commands/test-orchestration.js +392 -0
- package/dist/cli/commands/test-orchestration.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/plugin.d.ts +23 -0
- package/dist/cli/plugin.d.ts.map +1 -0
- package/dist/cli/plugin.js +36 -0
- package/dist/cli/plugin.js.map +1 -0
- package/dist/git/index.d.ts +10 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +12 -0
- package/dist/git/index.js.map +1 -0
- package/dist/git/merge.d.ts +79 -0
- package/dist/git/merge.d.ts.map +1 -0
- package/dist/git/merge.js +254 -0
- package/dist/git/merge.js.map +1 -0
- package/dist/git/worktree-manager.d.ts +299 -0
- package/dist/git/worktree-manager.d.ts.map +1 -0
- package/dist/git/worktree-manager.js +744 -0
- package/dist/git/worktree-manager.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/director.md +272 -0
- package/dist/prompts/index.d.ts +100 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +294 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/message-triage.md +50 -0
- package/dist/prompts/persistent-worker.md +240 -0
- package/dist/prompts/steward-base.md +64 -0
- package/dist/prompts/steward-docs.md +118 -0
- package/dist/prompts/steward-health.md +39 -0
- package/dist/prompts/steward-merge.md +168 -0
- package/dist/prompts/steward-ops.md +28 -0
- package/dist/prompts/steward-reminder.md +26 -0
- package/dist/prompts/worker.md +282 -0
- package/dist/providers/claude/headless.d.ts +18 -0
- package/dist/providers/claude/headless.d.ts.map +1 -0
- package/dist/providers/claude/headless.js +307 -0
- package/dist/providers/claude/headless.js.map +1 -0
- package/dist/providers/claude/index.d.ts +24 -0
- package/dist/providers/claude/index.d.ts.map +1 -0
- package/dist/providers/claude/index.js +80 -0
- package/dist/providers/claude/index.js.map +1 -0
- package/dist/providers/claude/interactive.d.ts +21 -0
- package/dist/providers/claude/interactive.d.ts.map +1 -0
- package/dist/providers/claude/interactive.js +142 -0
- package/dist/providers/claude/interactive.js.map +1 -0
- package/dist/providers/codex/event-mapper.d.ts +91 -0
- package/dist/providers/codex/event-mapper.d.ts.map +1 -0
- package/dist/providers/codex/event-mapper.js +299 -0
- package/dist/providers/codex/event-mapper.js.map +1 -0
- package/dist/providers/codex/headless.d.ts +20 -0
- package/dist/providers/codex/headless.d.ts.map +1 -0
- package/dist/providers/codex/headless.js +174 -0
- package/dist/providers/codex/headless.js.map +1 -0
- package/dist/providers/codex/index.d.ts +30 -0
- package/dist/providers/codex/index.d.ts.map +1 -0
- package/dist/providers/codex/index.js +55 -0
- package/dist/providers/codex/index.js.map +1 -0
- package/dist/providers/codex/interactive.d.ts +21 -0
- package/dist/providers/codex/interactive.d.ts.map +1 -0
- package/dist/providers/codex/interactive.js +141 -0
- package/dist/providers/codex/interactive.js.map +1 -0
- package/dist/providers/codex/jsonrpc-client.d.ts +52 -0
- package/dist/providers/codex/jsonrpc-client.d.ts.map +1 -0
- package/dist/providers/codex/jsonrpc-client.js +141 -0
- package/dist/providers/codex/jsonrpc-client.js.map +1 -0
- package/dist/providers/codex/server-manager.d.ts +100 -0
- package/dist/providers/codex/server-manager.d.ts.map +1 -0
- package/dist/providers/codex/server-manager.js +153 -0
- package/dist/providers/codex/server-manager.js.map +1 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +19 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/opencode/async-queue.d.ts +21 -0
- package/dist/providers/opencode/async-queue.d.ts.map +1 -0
- package/dist/providers/opencode/async-queue.js +51 -0
- package/dist/providers/opencode/async-queue.js.map +1 -0
- package/dist/providers/opencode/event-mapper.d.ts +132 -0
- package/dist/providers/opencode/event-mapper.d.ts.map +1 -0
- package/dist/providers/opencode/event-mapper.js +204 -0
- package/dist/providers/opencode/event-mapper.js.map +1 -0
- package/dist/providers/opencode/headless.d.ts +25 -0
- package/dist/providers/opencode/headless.d.ts.map +1 -0
- package/dist/providers/opencode/headless.js +190 -0
- package/dist/providers/opencode/headless.js.map +1 -0
- package/dist/providers/opencode/index.d.ts +33 -0
- package/dist/providers/opencode/index.d.ts.map +1 -0
- package/dist/providers/opencode/index.js +42 -0
- package/dist/providers/opencode/index.js.map +1 -0
- package/dist/providers/opencode/interactive.d.ts +21 -0
- package/dist/providers/opencode/interactive.d.ts.map +1 -0
- package/dist/providers/opencode/interactive.js +135 -0
- package/dist/providers/opencode/interactive.js.map +1 -0
- package/dist/providers/opencode/server-manager.d.ts +145 -0
- package/dist/providers/opencode/server-manager.d.ts.map +1 -0
- package/dist/providers/opencode/server-manager.js +163 -0
- package/dist/providers/opencode/server-manager.js.map +1 -0
- package/dist/providers/registry.d.ts +38 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +82 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +144 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +25 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/runtime/event-utils.d.ts +8 -0
- package/dist/runtime/event-utils.d.ts.map +1 -0
- package/dist/runtime/event-utils.js +23 -0
- package/dist/runtime/event-utils.js.map +1 -0
- package/dist/runtime/handoff.d.ts +195 -0
- package/dist/runtime/handoff.d.ts.map +1 -0
- package/dist/runtime/handoff.js +332 -0
- package/dist/runtime/handoff.js.map +1 -0
- package/dist/runtime/index.d.ts +17 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +60 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/message-mapper.d.ts +99 -0
- package/dist/runtime/message-mapper.d.ts.map +1 -0
- package/dist/runtime/message-mapper.js +202 -0
- package/dist/runtime/message-mapper.js.map +1 -0
- package/dist/runtime/predecessor-query.d.ts +212 -0
- package/dist/runtime/predecessor-query.d.ts.map +1 -0
- package/dist/runtime/predecessor-query.js +283 -0
- package/dist/runtime/predecessor-query.js.map +1 -0
- package/dist/runtime/session-manager.d.ts +466 -0
- package/dist/runtime/session-manager.d.ts.map +1 -0
- package/dist/runtime/session-manager.js +986 -0
- package/dist/runtime/session-manager.js.map +1 -0
- package/dist/runtime/spawner.d.ts +407 -0
- package/dist/runtime/spawner.d.ts.map +1 -0
- package/dist/runtime/spawner.js +781 -0
- package/dist/runtime/spawner.js.map +1 -0
- package/dist/server/config.d.ts +22 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +59 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/daemon-state.d.ts +50 -0
- package/dist/server/daemon-state.d.ts.map +1 -0
- package/dist/server/daemon-state.js +100 -0
- package/dist/server/daemon-state.js.map +1 -0
- package/dist/server/events-websocket.d.ts +32 -0
- package/dist/server/events-websocket.d.ts.map +1 -0
- package/dist/server/events-websocket.js +96 -0
- package/dist/server/events-websocket.js.map +1 -0
- package/dist/server/formatters.d.ts +94 -0
- package/dist/server/formatters.d.ts.map +1 -0
- package/dist/server/formatters.js +142 -0
- package/dist/server/formatters.js.map +1 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +153 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/lsp-websocket.d.ts +33 -0
- package/dist/server/lsp-websocket.d.ts.map +1 -0
- package/dist/server/lsp-websocket.js +161 -0
- package/dist/server/lsp-websocket.js.map +1 -0
- package/dist/server/routes/agents.d.ts +9 -0
- package/dist/server/routes/agents.d.ts.map +1 -0
- package/dist/server/routes/agents.js +369 -0
- package/dist/server/routes/agents.js.map +1 -0
- package/dist/server/routes/daemon.d.ts +13 -0
- package/dist/server/routes/daemon.d.ts.map +1 -0
- package/dist/server/routes/daemon.js +187 -0
- package/dist/server/routes/daemon.js.map +1 -0
- package/dist/server/routes/events.d.ts +23 -0
- package/dist/server/routes/events.d.ts.map +1 -0
- package/dist/server/routes/events.js +282 -0
- package/dist/server/routes/events.js.map +1 -0
- package/dist/server/routes/extensions.d.ts +9 -0
- package/dist/server/routes/extensions.d.ts.map +1 -0
- package/dist/server/routes/extensions.js +202 -0
- package/dist/server/routes/extensions.js.map +1 -0
- package/dist/server/routes/health.d.ts +7 -0
- package/dist/server/routes/health.d.ts.map +1 -0
- package/dist/server/routes/health.js +33 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/index.d.ts +21 -0
- package/dist/server/routes/index.d.ts.map +1 -0
- package/dist/server/routes/index.js +21 -0
- package/dist/server/routes/index.js.map +1 -0
- package/dist/server/routes/lsp.d.ts +9 -0
- package/dist/server/routes/lsp.d.ts.map +1 -0
- package/dist/server/routes/lsp.js +50 -0
- package/dist/server/routes/lsp.js.map +1 -0
- package/dist/server/routes/plugins.d.ts +9 -0
- package/dist/server/routes/plugins.d.ts.map +1 -0
- package/dist/server/routes/plugins.js +109 -0
- package/dist/server/routes/plugins.js.map +1 -0
- package/dist/server/routes/pools.d.ts +9 -0
- package/dist/server/routes/pools.d.ts.map +1 -0
- package/dist/server/routes/pools.js +189 -0
- package/dist/server/routes/pools.js.map +1 -0
- package/dist/server/routes/scheduler.d.ts +9 -0
- package/dist/server/routes/scheduler.d.ts.map +1 -0
- package/dist/server/routes/scheduler.js +162 -0
- package/dist/server/routes/scheduler.js.map +1 -0
- package/dist/server/routes/sessions.d.ts +27 -0
- package/dist/server/routes/sessions.d.ts.map +1 -0
- package/dist/server/routes/sessions.js +773 -0
- package/dist/server/routes/sessions.js.map +1 -0
- package/dist/server/routes/tasks.d.ts +9 -0
- package/dist/server/routes/tasks.d.ts.map +1 -0
- package/dist/server/routes/tasks.js +954 -0
- package/dist/server/routes/tasks.js.map +1 -0
- package/dist/server/routes/upload.d.ts +8 -0
- package/dist/server/routes/upload.d.ts.map +1 -0
- package/dist/server/routes/upload.js +40 -0
- package/dist/server/routes/upload.js.map +1 -0
- package/dist/server/routes/workflows.d.ts +9 -0
- package/dist/server/routes/workflows.d.ts.map +1 -0
- package/dist/server/routes/workflows.js +532 -0
- package/dist/server/routes/workflows.js.map +1 -0
- package/dist/server/routes/workspace-files.d.ts +12 -0
- package/dist/server/routes/workspace-files.d.ts.map +1 -0
- package/dist/server/routes/workspace-files.js +520 -0
- package/dist/server/routes/workspace-files.js.map +1 -0
- package/dist/server/routes/worktrees.d.ts +9 -0
- package/dist/server/routes/worktrees.d.ts.map +1 -0
- package/dist/server/routes/worktrees.js +94 -0
- package/dist/server/routes/worktrees.js.map +1 -0
- package/dist/server/server.d.ts +14 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +258 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/services/lsp-manager.d.ts +93 -0
- package/dist/server/services/lsp-manager.d.ts.map +1 -0
- package/dist/server/services/lsp-manager.js +291 -0
- package/dist/server/services/lsp-manager.js.map +1 -0
- package/dist/server/services/session-messages.d.ts +61 -0
- package/dist/server/services/session-messages.d.ts.map +1 -0
- package/dist/server/services/session-messages.js +101 -0
- package/dist/server/services/session-messages.js.map +1 -0
- package/dist/server/services.d.ts +35 -0
- package/dist/server/services.d.ts.map +1 -0
- package/dist/server/services.js +159 -0
- package/dist/server/services.js.map +1 -0
- package/dist/server/static.d.ts +18 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +71 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/types.d.ts +20 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +7 -0
- package/dist/server/types.js.map +1 -0
- package/dist/server/websocket.d.ts +16 -0
- package/dist/server/websocket.d.ts.map +1 -0
- package/dist/server/websocket.js +143 -0
- package/dist/server/websocket.js.map +1 -0
- package/dist/services/agent-pool-service.d.ts +181 -0
- package/dist/services/agent-pool-service.d.ts.map +1 -0
- package/dist/services/agent-pool-service.js +590 -0
- package/dist/services/agent-pool-service.js.map +1 -0
- package/dist/services/agent-registry.d.ts +185 -0
- package/dist/services/agent-registry.d.ts.map +1 -0
- package/dist/services/agent-registry.js +432 -0
- package/dist/services/agent-registry.js.map +1 -0
- package/dist/services/dispatch-daemon.d.ts +429 -0
- package/dist/services/dispatch-daemon.d.ts.map +1 -0
- package/dist/services/dispatch-daemon.js +1833 -0
- package/dist/services/dispatch-daemon.js.map +1 -0
- package/dist/services/dispatch-service.d.ts +148 -0
- package/dist/services/dispatch-service.d.ts.map +1 -0
- package/dist/services/dispatch-service.js +170 -0
- package/dist/services/dispatch-service.js.map +1 -0
- package/dist/services/docs-steward-service.d.ts +199 -0
- package/dist/services/docs-steward-service.d.ts.map +1 -0
- package/dist/services/docs-steward-service.js +599 -0
- package/dist/services/docs-steward-service.js.map +1 -0
- package/dist/services/health-steward-service.d.ts +446 -0
- package/dist/services/health-steward-service.d.ts.map +1 -0
- package/dist/services/health-steward-service.js +866 -0
- package/dist/services/health-steward-service.js.map +1 -0
- package/dist/services/index.d.ts +26 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +111 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/merge-request-provider.d.ts +59 -0
- package/dist/services/merge-request-provider.d.ts.map +1 -0
- package/dist/services/merge-request-provider.js +89 -0
- package/dist/services/merge-request-provider.js.map +1 -0
- package/dist/services/merge-steward-service.d.ts +268 -0
- package/dist/services/merge-steward-service.d.ts.map +1 -0
- package/dist/services/merge-steward-service.js +568 -0
- package/dist/services/merge-steward-service.js.map +1 -0
- package/dist/services/plugin-executor.d.ts +247 -0
- package/dist/services/plugin-executor.d.ts.map +1 -0
- package/dist/services/plugin-executor.js +451 -0
- package/dist/services/plugin-executor.js.map +1 -0
- package/dist/services/role-definition-service.d.ts +117 -0
- package/dist/services/role-definition-service.d.ts.map +1 -0
- package/dist/services/role-definition-service.js +289 -0
- package/dist/services/role-definition-service.js.map +1 -0
- package/dist/services/steward-scheduler.d.ts +336 -0
- package/dist/services/steward-scheduler.d.ts.map +1 -0
- package/dist/services/steward-scheduler.js +732 -0
- package/dist/services/steward-scheduler.js.map +1 -0
- package/dist/services/task-assignment-service.d.ts +291 -0
- package/dist/services/task-assignment-service.d.ts.map +1 -0
- package/dist/services/task-assignment-service.js +454 -0
- package/dist/services/task-assignment-service.js.map +1 -0
- package/dist/services/worker-task-service.d.ts +202 -0
- package/dist/services/worker-task-service.d.ts.map +1 -0
- package/dist/services/worker-task-service.js +228 -0
- package/dist/services/worker-task-service.js.map +1 -0
- package/dist/testing/index.d.ts +13 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +17 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/orchestration-tests.d.ts +62 -0
- package/dist/testing/orchestration-tests.d.ts.map +1 -0
- package/dist/testing/orchestration-tests.js +1115 -0
- package/dist/testing/orchestration-tests.js.map +1 -0
- package/dist/testing/test-context.d.ts +171 -0
- package/dist/testing/test-context.d.ts.map +1 -0
- package/dist/testing/test-context.js +665 -0
- package/dist/testing/test-context.js.map +1 -0
- package/dist/testing/test-prompts.d.ts +46 -0
- package/dist/testing/test-prompts.d.ts.map +1 -0
- package/dist/testing/test-prompts.js +140 -0
- package/dist/testing/test-prompts.js.map +1 -0
- package/dist/testing/test-utils.d.ts +200 -0
- package/dist/testing/test-utils.d.ts.map +1 -0
- package/dist/testing/test-utils.js +378 -0
- package/dist/testing/test-utils.js.map +1 -0
- package/dist/types/agent-pool.d.ts +215 -0
- package/dist/types/agent-pool.d.ts.map +1 -0
- package/dist/types/agent-pool.js +143 -0
- package/dist/types/agent-pool.js.map +1 -0
- package/dist/types/agent.d.ts +265 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +127 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +40 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/message-types.d.ts +294 -0
- package/dist/types/message-types.d.ts.map +1 -0
- package/dist/types/message-types.js +354 -0
- package/dist/types/message-types.js.map +1 -0
- package/dist/types/role-definition.d.ts +272 -0
- package/dist/types/role-definition.d.ts.map +1 -0
- package/dist/types/role-definition.js +144 -0
- package/dist/types/role-definition.js.map +1 -0
- package/dist/types/task-meta.d.ts +248 -0
- package/dist/types/task-meta.d.ts.map +1 -0
- package/dist/types/task-meta.js +213 -0
- package/dist/types/task-meta.js.map +1 -0
- package/package.json +120 -0
- package/web/assets/abap-BrgZPUOV.js +6 -0
- package/web/assets/apex-DyP6w7ZV.js +6 -0
- package/web/assets/azcli-BaLxmfj-.js +6 -0
- package/web/assets/bat-CFOPXBzS.js +6 -0
- package/web/assets/bicep-BfEKNvv3.js +7 -0
- package/web/assets/cameligo-BFG1Mk7z.js +6 -0
- package/web/assets/clojure-DTECt2xU.js +6 -0
- package/web/assets/codicon-DCmgc-ay.ttf +0 -0
- package/web/assets/coffee-CDGzqUPQ.js +6 -0
- package/web/assets/cpp-CLLBncYj.js +6 -0
- package/web/assets/csharp-dUCx_-0o.js +6 -0
- package/web/assets/csp-5Rap-vPy.js +6 -0
- package/web/assets/css-D3h14YRZ.js +8 -0
- package/web/assets/cssMode-DMo-5YLA.js +9 -0
- package/web/assets/cypher-DrQuvNYM.js +6 -0
- package/web/assets/dart-CFKIUWau.js +6 -0
- package/web/assets/dockerfile-Zznr-cwX.js +6 -0
- package/web/assets/ecl-Ce3n6wWz.js +6 -0
- package/web/assets/elixir-deUWdS0T.js +6 -0
- package/web/assets/flow9-i9-g7ZhI.js +6 -0
- package/web/assets/freemarker2-D4qgkQzN.js +8 -0
- package/web/assets/fsharp-CzKuDChf.js +6 -0
- package/web/assets/go-Cphgjts3.js +6 -0
- package/web/assets/graphql-Cg7bfA9N.js +6 -0
- package/web/assets/handlebars-CXFvNjQC.js +6 -0
- package/web/assets/hcl-0cvrggvQ.js +6 -0
- package/web/assets/html-oyuB_D-B.js +6 -0
- package/web/assets/htmlMode-iWuZ24-r.js +9 -0
- package/web/assets/index-DqP-_E4F.css +32 -0
- package/web/assets/index-R1cylSgw.js +1665 -0
- package/web/assets/ini-Drc7WvVn.js +6 -0
- package/web/assets/java-B_fMsGYe.js +6 -0
- package/web/assets/javascript-CRIkN2Pg.js +6 -0
- package/web/assets/jsonMode-DVDkDgex.js +15 -0
- package/web/assets/julia-Bqgm2twL.js +6 -0
- package/web/assets/kotlin-BSkB5QuD.js +6 -0
- package/web/assets/less-BsTHnhdd.js +7 -0
- package/web/assets/lexon-YWi4-JPR.js +6 -0
- package/web/assets/liquid-CSfldbB5.js +6 -0
- package/web/assets/lua-nf6ki56Z.js +6 -0
- package/web/assets/m3-Cpb6xl2v.js +6 -0
- package/web/assets/markdown-DSZPf7rp.js +6 -0
- package/web/assets/mdx-Dd58iymR.js +6 -0
- package/web/assets/mips-B_c3zf-v.js +6 -0
- package/web/assets/monaco-editor-B4lwqA13.js +751 -0
- package/web/assets/monaco-editor-CQpyCxOA.css +1 -0
- package/web/assets/msdax-rUNN04Wq.js +6 -0
- package/web/assets/mysql-DDwshQtU.js +6 -0
- package/web/assets/objective-c-B5zXfXm9.js +6 -0
- package/web/assets/pascal-CXOwvkN_.js +6 -0
- package/web/assets/pascaligo-Bc-ZgV77.js +6 -0
- package/web/assets/perl-CwNk8-XU.js +6 -0
- package/web/assets/pgsql-tGk8EFnU.js +6 -0
- package/web/assets/php-CpIb_Oan.js +6 -0
- package/web/assets/pla-B03wrqEc.js +6 -0
- package/web/assets/postiats-BKlk5iyT.js +6 -0
- package/web/assets/powerquery-Bhzvs7bI.js +6 -0
- package/web/assets/powershell-Dd3NCNK9.js +6 -0
- package/web/assets/protobuf-COyEY5Pt.js +7 -0
- package/web/assets/pug-BaJupSGV.js +6 -0
- package/web/assets/python-XWrMqdhO.js +6 -0
- package/web/assets/qsharp-DXyYeYxl.js +6 -0
- package/web/assets/r-CdQndTaG.js +6 -0
- package/web/assets/razor-DPlhCpIs.js +6 -0
- package/web/assets/redis-CVwtpugi.js +6 -0
- package/web/assets/redshift-25W9uPmb.js +6 -0
- package/web/assets/restructuredtext-DfzH4Xui.js +6 -0
- package/web/assets/router-vendor-DHlGizSU.js +41 -0
- package/web/assets/ruby-Cp1zYvxS.js +6 -0
- package/web/assets/rust-D5C2fndG.js +6 -0
- package/web/assets/sb-CDntyWJ8.js +6 -0
- package/web/assets/scala-BoFRg7Ot.js +6 -0
- package/web/assets/scheme-Bio4gycK.js +6 -0
- package/web/assets/scss-4Ik7cdeQ.js +8 -0
- package/web/assets/shell-CX-rkNHf.js +6 -0
- package/web/assets/solidity-Tw7wswEv.js +6 -0
- package/web/assets/sophia-C5WLch3f.js +6 -0
- package/web/assets/sparql-DHaeiCBh.js +6 -0
- package/web/assets/sql-CCSDG5nI.js +6 -0
- package/web/assets/st-pnP8ivHi.js +6 -0
- package/web/assets/swift-DwJ7jVG9.js +8 -0
- package/web/assets/systemverilog-B9Xyijhd.js +6 -0
- package/web/assets/tcl-DnHyzjbg.js +6 -0
- package/web/assets/tsMode-BbA1Jbf3.js +16 -0
- package/web/assets/twig-CPajHgWi.js +6 -0
- package/web/assets/typescript-DcLHYzvH.js +6 -0
- package/web/assets/typespec-D-MeaMDU.js +6 -0
- package/web/assets/ui-vendor-BSco96uv.js +51 -0
- package/web/assets/utils-vendor-DaJ2Dubl.js +911 -0
- package/web/assets/vb-DgyLZaXg.js +6 -0
- package/web/assets/wgsl-DYQUnd45.js +303 -0
- package/web/assets/xml-xKYS3dO6.js +6 -0
- package/web/assets/yaml-CNmlXqzH.js +6 -0
- package/web/favicon.ico +0 -0
- package/web/index.html +22 -0
- package/web/logo.png +0 -0
|
@@ -0,0 +1,744 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worktree Manager
|
|
3
|
+
*
|
|
4
|
+
* This module provides git worktree management for the orchestration system.
|
|
5
|
+
* Each worker agent gets a dedicated git worktree for true parallel development.
|
|
6
|
+
*
|
|
7
|
+
* Key features:
|
|
8
|
+
* - Verify git repo exists before operations
|
|
9
|
+
* - Create isolated worktrees for workers
|
|
10
|
+
* - Branch naming: agent/{worker-name}/{task-id}-{slug}
|
|
11
|
+
* - Worktree path: .stoneforge/.worktrees/{worker-name}-{task-slug}/
|
|
12
|
+
* - Clean up worktrees after merge
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { execFile } from 'node:child_process';
|
|
17
|
+
import { promisify } from 'node:util';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
import { createTimestamp } from '@stoneforge/core';
|
|
21
|
+
import { generateBranchName, generateWorktreePath, createSlugFromTitle, } from '../types/task-meta.js';
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
23
|
+
/** Default timeout for git operations (30 seconds) */
|
|
24
|
+
const GIT_OPERATION_TIMEOUT_MS = 30_000;
|
|
25
|
+
/**
|
|
26
|
+
* All valid worktree states
|
|
27
|
+
*/
|
|
28
|
+
export const WorktreeStates = [
|
|
29
|
+
'creating',
|
|
30
|
+
'active',
|
|
31
|
+
'suspended',
|
|
32
|
+
'merging',
|
|
33
|
+
'cleaning',
|
|
34
|
+
'archived',
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Valid state transitions for worktrees
|
|
38
|
+
*/
|
|
39
|
+
export const WorktreeStateTransitions = {
|
|
40
|
+
creating: ['active', 'cleaning'],
|
|
41
|
+
active: ['suspended', 'merging', 'cleaning'],
|
|
42
|
+
suspended: ['active', 'cleaning'],
|
|
43
|
+
merging: ['archived', 'cleaning', 'active'],
|
|
44
|
+
cleaning: ['archived'],
|
|
45
|
+
archived: [],
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Error thrown when git repository is not found
|
|
49
|
+
*/
|
|
50
|
+
export class GitRepositoryNotFoundError extends Error {
|
|
51
|
+
constructor(path) {
|
|
52
|
+
super(`Git repository not found at ${path}.\n\n` +
|
|
53
|
+
`The orchestrator requires an existing git repository.\n` +
|
|
54
|
+
`Please initialize a git repository first:\n\n` +
|
|
55
|
+
` cd ${path}\n` +
|
|
56
|
+
` git init\n` +
|
|
57
|
+
` git add .\n` +
|
|
58
|
+
` git commit -m "Initial commit"\n`);
|
|
59
|
+
this.name = 'GitRepositoryNotFoundError';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Error thrown when a worktree operation fails
|
|
64
|
+
*/
|
|
65
|
+
export class WorktreeError extends Error {
|
|
66
|
+
code;
|
|
67
|
+
details;
|
|
68
|
+
constructor(message, code, details) {
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = 'WorktreeError';
|
|
71
|
+
this.code = code;
|
|
72
|
+
this.details = details;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// WorktreeManager Implementation
|
|
77
|
+
// ============================================================================
|
|
78
|
+
/**
|
|
79
|
+
* Implementation of the WorktreeManager.
|
|
80
|
+
*/
|
|
81
|
+
export class WorktreeManagerImpl {
|
|
82
|
+
config;
|
|
83
|
+
initialized = false;
|
|
84
|
+
defaultBranch;
|
|
85
|
+
worktreeStates = new Map();
|
|
86
|
+
realWorkspaceRoot;
|
|
87
|
+
constructor(config) {
|
|
88
|
+
this.config = {
|
|
89
|
+
workspaceRoot: path.resolve(config.workspaceRoot),
|
|
90
|
+
worktreeDir: config.worktreeDir ?? '.stoneforge/.worktrees',
|
|
91
|
+
defaultBaseBranch: config.defaultBaseBranch ?? '',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// ----------------------------------------
|
|
95
|
+
// Initialization
|
|
96
|
+
// ----------------------------------------
|
|
97
|
+
async initWorkspace() {
|
|
98
|
+
const gitDir = path.join(this.config.workspaceRoot, '.git');
|
|
99
|
+
// Check if .git exists (could be a file for worktrees or a directory)
|
|
100
|
+
if (!fs.existsSync(gitDir)) {
|
|
101
|
+
throw new GitRepositoryNotFoundError(this.config.workspaceRoot);
|
|
102
|
+
}
|
|
103
|
+
// Verify it's actually a git repository by running a git command
|
|
104
|
+
try {
|
|
105
|
+
await this.execGit(['rev-parse', '--git-dir']);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw new GitRepositoryNotFoundError(this.config.workspaceRoot);
|
|
109
|
+
}
|
|
110
|
+
// Create worktree directory if it doesn't exist
|
|
111
|
+
const worktreeDir = path.join(this.config.workspaceRoot, this.config.worktreeDir);
|
|
112
|
+
if (!fs.existsSync(worktreeDir)) {
|
|
113
|
+
fs.mkdirSync(worktreeDir, { recursive: true });
|
|
114
|
+
}
|
|
115
|
+
// Add worktree directory to .gitignore if not already there
|
|
116
|
+
await this.ensureGitignore();
|
|
117
|
+
// Cache the default branch
|
|
118
|
+
this.defaultBranch = await this.detectDefaultBranch();
|
|
119
|
+
// Prune stale worktree entries (directories removed but git still tracks them)
|
|
120
|
+
await this.execGit(['worktree', 'prune']);
|
|
121
|
+
// Resolve real path (handles symlinks like /tmp -> /private/tmp on macOS)
|
|
122
|
+
try {
|
|
123
|
+
this.realWorkspaceRoot = fs.realpathSync(this.config.workspaceRoot);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
this.realWorkspaceRoot = this.config.workspaceRoot;
|
|
127
|
+
}
|
|
128
|
+
this.initialized = true;
|
|
129
|
+
}
|
|
130
|
+
isInitialized() {
|
|
131
|
+
return this.initialized;
|
|
132
|
+
}
|
|
133
|
+
getWorkspaceRoot() {
|
|
134
|
+
return this.config.workspaceRoot;
|
|
135
|
+
}
|
|
136
|
+
// ----------------------------------------
|
|
137
|
+
// Worktree Operations
|
|
138
|
+
// ----------------------------------------
|
|
139
|
+
async createWorktree(options) {
|
|
140
|
+
this.ensureInitialized();
|
|
141
|
+
// Generate paths and branch name
|
|
142
|
+
const slug = options.taskTitle ? createSlugFromTitle(options.taskTitle) : undefined;
|
|
143
|
+
const branch = options.customBranch ?? generateBranchName(options.agentName, options.taskId, slug);
|
|
144
|
+
const relativePath = options.customPath ?? generateWorktreePath(options.agentName, slug);
|
|
145
|
+
const fullPath = path.join(this.config.workspaceRoot, relativePath);
|
|
146
|
+
// Check if worktree already exists
|
|
147
|
+
if (fs.existsSync(fullPath)) {
|
|
148
|
+
throw new WorktreeError(`Worktree already exists at ${relativePath}`, 'WORKTREE_EXISTS');
|
|
149
|
+
}
|
|
150
|
+
// Track state
|
|
151
|
+
this.worktreeStates.set(relativePath, 'creating');
|
|
152
|
+
let branchCreated = false;
|
|
153
|
+
const baseBranch = options.baseBranch ?? await this.getDefaultBranch();
|
|
154
|
+
// Fetch latest remote state so origin/<baseBranch> is up to date.
|
|
155
|
+
// Without this, worktrees branch from the (possibly stale) local ref
|
|
156
|
+
// and may be missing recently merged dependencies/code.
|
|
157
|
+
try {
|
|
158
|
+
await this.execGit(['fetch', 'origin', baseBranch]);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Non-fatal: remote may not exist or be unreachable
|
|
162
|
+
}
|
|
163
|
+
// Use origin/<baseBranch> as the start point for new worktrees so they
|
|
164
|
+
// always include the latest merged code, even when the local branch ref
|
|
165
|
+
// has fallen behind. Falls back to the local branch if the remote ref
|
|
166
|
+
// doesn't exist (e.g. no remote configured).
|
|
167
|
+
let startPoint = baseBranch;
|
|
168
|
+
try {
|
|
169
|
+
await this.execGit(['rev-parse', '--verify', `origin/${baseBranch}`]);
|
|
170
|
+
startPoint = `origin/${baseBranch}`;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// origin/<baseBranch> doesn't exist — use local branch
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
// Check if branch exists
|
|
177
|
+
const branchExists = await this.branchExists(branch);
|
|
178
|
+
if (branchExists) {
|
|
179
|
+
// Branch exists, create worktree checking out existing branch
|
|
180
|
+
await this.execGit(['worktree', 'add', fullPath, branch]);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// Create new branch from the latest remote base
|
|
184
|
+
await this.execGit(['worktree', 'add', '-b', branch, fullPath, startPoint]);
|
|
185
|
+
branchCreated = true;
|
|
186
|
+
// Set up tracking if requested
|
|
187
|
+
if (options.trackRemote !== false) {
|
|
188
|
+
try {
|
|
189
|
+
// Try to set upstream (may fail if remote doesn't exist)
|
|
190
|
+
await this.execGit(['branch', '--set-upstream-to', `origin/${baseBranch}`, branch]);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// Ignore - remote may not exist
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Install dependencies if requested
|
|
198
|
+
if (options.installDependencies) {
|
|
199
|
+
await this.installDependencies(fullPath);
|
|
200
|
+
}
|
|
201
|
+
// Update state to active
|
|
202
|
+
this.worktreeStates.set(relativePath, 'active');
|
|
203
|
+
// Get worktree info
|
|
204
|
+
const worktree = await this.getWorktree(relativePath);
|
|
205
|
+
if (!worktree) {
|
|
206
|
+
throw new WorktreeError('Failed to get worktree info after creation', 'WORKTREE_INFO_FAILED');
|
|
207
|
+
}
|
|
208
|
+
// Attach additional metadata
|
|
209
|
+
const enrichedWorktree = {
|
|
210
|
+
...worktree,
|
|
211
|
+
agentName: options.agentName,
|
|
212
|
+
taskId: options.taskId,
|
|
213
|
+
createdAt: createTimestamp(),
|
|
214
|
+
state: 'active',
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
worktree: enrichedWorktree,
|
|
218
|
+
branch,
|
|
219
|
+
path: fullPath,
|
|
220
|
+
branchCreated,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
// Clean up on failure
|
|
225
|
+
this.worktreeStates.delete(relativePath);
|
|
226
|
+
if (fs.existsSync(fullPath)) {
|
|
227
|
+
try {
|
|
228
|
+
await this.execGit(['worktree', 'remove', '--force', fullPath]);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Ignore cleanup errors
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Create a read-only (detached HEAD) worktree for triage sessions.
|
|
239
|
+
* Uses `git worktree add --detach` so no new branch is created.
|
|
240
|
+
*/
|
|
241
|
+
async createReadOnlyWorktree(options) {
|
|
242
|
+
this.ensureInitialized();
|
|
243
|
+
const relativePath = `.stoneforge/.worktrees/${options.agentName}-${options.purpose}`;
|
|
244
|
+
const fullPath = path.join(this.config.workspaceRoot, relativePath);
|
|
245
|
+
if (fs.existsSync(fullPath)) {
|
|
246
|
+
throw new WorktreeError(`Worktree already exists at ${relativePath}`, 'WORKTREE_EXISTS');
|
|
247
|
+
}
|
|
248
|
+
this.worktreeStates.set(relativePath, 'creating');
|
|
249
|
+
try {
|
|
250
|
+
const baseBranch = await this.getDefaultBranch();
|
|
251
|
+
// Fetch latest remote state so read-only worktrees see the latest code
|
|
252
|
+
try {
|
|
253
|
+
await this.execGit(['fetch', 'origin', baseBranch]);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
// Non-fatal: remote may not exist
|
|
257
|
+
}
|
|
258
|
+
// Use origin/<baseBranch> if available for latest code
|
|
259
|
+
let startPoint = baseBranch;
|
|
260
|
+
try {
|
|
261
|
+
await this.execGit(['rev-parse', '--verify', `origin/${baseBranch}`]);
|
|
262
|
+
startPoint = `origin/${baseBranch}`;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// origin/<baseBranch> doesn't exist — use local branch
|
|
266
|
+
}
|
|
267
|
+
// Prune stale worktree entries before adding, in case git's list is stale
|
|
268
|
+
await this.execGit(['worktree', 'prune']);
|
|
269
|
+
await this.execGit(['worktree', 'add', '--detach', fullPath, startPoint]);
|
|
270
|
+
this.worktreeStates.set(relativePath, 'active');
|
|
271
|
+
const worktree = await this.getWorktree(relativePath);
|
|
272
|
+
if (!worktree) {
|
|
273
|
+
throw new WorktreeError('Failed to get worktree info after creation', 'WORKTREE_INFO_FAILED');
|
|
274
|
+
}
|
|
275
|
+
const enrichedWorktree = {
|
|
276
|
+
...worktree,
|
|
277
|
+
agentName: options.agentName,
|
|
278
|
+
createdAt: createTimestamp(),
|
|
279
|
+
state: 'active',
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
worktree: enrichedWorktree,
|
|
283
|
+
branch: `(detached-${options.purpose})`,
|
|
284
|
+
path: fullPath,
|
|
285
|
+
branchCreated: false,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
this.worktreeStates.delete(relativePath);
|
|
290
|
+
if (fs.existsSync(fullPath)) {
|
|
291
|
+
try {
|
|
292
|
+
await this.execGit(['worktree', 'remove', '--force', fullPath]);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// Ignore cleanup errors
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async removeWorktree(worktreePath, options) {
|
|
302
|
+
this.ensureInitialized();
|
|
303
|
+
const fullPath = this.resolvePath(worktreePath);
|
|
304
|
+
const relativePath = this.getRelativePath(fullPath);
|
|
305
|
+
// Get worktree info to find the branch
|
|
306
|
+
const worktree = await this.getWorktree(worktreePath);
|
|
307
|
+
if (!worktree) {
|
|
308
|
+
throw new WorktreeError(`Worktree not found: ${worktreePath}`, 'WORKTREE_NOT_FOUND');
|
|
309
|
+
}
|
|
310
|
+
if (worktree.isMain) {
|
|
311
|
+
throw new WorktreeError('Cannot remove the main worktree', 'CANNOT_REMOVE_MAIN');
|
|
312
|
+
}
|
|
313
|
+
// Update state
|
|
314
|
+
this.worktreeStates.set(relativePath, 'cleaning');
|
|
315
|
+
try {
|
|
316
|
+
// Remove the worktree
|
|
317
|
+
const removeArgs = ['worktree', 'remove'];
|
|
318
|
+
if (options?.force) {
|
|
319
|
+
removeArgs.push('--force');
|
|
320
|
+
}
|
|
321
|
+
removeArgs.push(fullPath);
|
|
322
|
+
await this.execGit(removeArgs);
|
|
323
|
+
// Delete branch if requested
|
|
324
|
+
if (options?.deleteBranch && worktree.branch) {
|
|
325
|
+
// Delete remote branch first if requested
|
|
326
|
+
if (options.deleteRemoteBranch) {
|
|
327
|
+
try {
|
|
328
|
+
await this.execGit(['push', 'origin', '--delete', worktree.branch]);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
// Remote branch might not exist or push access denied
|
|
332
|
+
// Log warning but continue with local branch deletion
|
|
333
|
+
console.warn(`[worktree-manager] Failed to delete remote branch origin/${worktree.branch}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Delete local branch
|
|
337
|
+
const deleteArgs = ['branch'];
|
|
338
|
+
if (options.forceBranchDelete) {
|
|
339
|
+
deleteArgs.push('-D');
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
deleteArgs.push('-d');
|
|
343
|
+
}
|
|
344
|
+
deleteArgs.push(worktree.branch);
|
|
345
|
+
try {
|
|
346
|
+
await this.execGit(deleteArgs);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
// Branch might not exist or might not be fully merged
|
|
350
|
+
// Only throw if not forcing
|
|
351
|
+
if (!options.forceBranchDelete) {
|
|
352
|
+
throw new WorktreeError(`Failed to delete branch ${worktree.branch}. Use forceBranchDelete to force deletion.`, 'BRANCH_DELETE_FAILED', error.message);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// Update state to archived
|
|
357
|
+
this.worktreeStates.set(relativePath, 'archived');
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
if (error instanceof WorktreeError) {
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
throw new WorktreeError(`Failed to remove worktree: ${worktreePath}`, 'REMOVE_FAILED', error.message);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
async suspendWorktree(worktreePath) {
|
|
367
|
+
this.ensureInitialized();
|
|
368
|
+
const worktree = await this.getWorktree(worktreePath);
|
|
369
|
+
if (!worktree) {
|
|
370
|
+
throw new WorktreeError(`Worktree not found: ${worktreePath}`, 'WORKTREE_NOT_FOUND');
|
|
371
|
+
}
|
|
372
|
+
const relativePath = this.getRelativePath(this.resolvePath(worktreePath));
|
|
373
|
+
const currentState = this.worktreeStates.get(relativePath) ?? 'active';
|
|
374
|
+
if (!WorktreeStateTransitions[currentState].includes('suspended')) {
|
|
375
|
+
throw new WorktreeError(`Cannot suspend worktree in state: ${currentState}`, 'INVALID_STATE_TRANSITION');
|
|
376
|
+
}
|
|
377
|
+
this.worktreeStates.set(relativePath, 'suspended');
|
|
378
|
+
}
|
|
379
|
+
async resumeWorktree(worktreePath) {
|
|
380
|
+
this.ensureInitialized();
|
|
381
|
+
const worktree = await this.getWorktree(worktreePath);
|
|
382
|
+
if (!worktree) {
|
|
383
|
+
throw new WorktreeError(`Worktree not found: ${worktreePath}`, 'WORKTREE_NOT_FOUND');
|
|
384
|
+
}
|
|
385
|
+
const relativePath = this.getRelativePath(this.resolvePath(worktreePath));
|
|
386
|
+
const currentState = this.worktreeStates.get(relativePath) ?? 'active';
|
|
387
|
+
if (currentState !== 'suspended') {
|
|
388
|
+
throw new WorktreeError(`Cannot resume worktree in state: ${currentState}`, 'INVALID_STATE_TRANSITION');
|
|
389
|
+
}
|
|
390
|
+
this.worktreeStates.set(relativePath, 'active');
|
|
391
|
+
}
|
|
392
|
+
// ----------------------------------------
|
|
393
|
+
// Worktree Queries
|
|
394
|
+
// ----------------------------------------
|
|
395
|
+
async listWorktrees(includeMain = false) {
|
|
396
|
+
this.ensureInitialized();
|
|
397
|
+
try {
|
|
398
|
+
const { stdout } = await this.execGit(['worktree', 'list', '--porcelain']);
|
|
399
|
+
const worktrees = this.parseWorktreeList(stdout);
|
|
400
|
+
if (includeMain) {
|
|
401
|
+
return worktrees;
|
|
402
|
+
}
|
|
403
|
+
return worktrees.filter((w) => !w.isMain);
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
throw new WorktreeError('Failed to list worktrees', 'LIST_FAILED', error.message);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async getWorktree(worktreePath) {
|
|
410
|
+
this.ensureInitialized();
|
|
411
|
+
const fullPath = this.resolvePath(worktreePath);
|
|
412
|
+
// Resolve real path for comparison (handles symlinks like /tmp -> /private/tmp)
|
|
413
|
+
let realFullPath;
|
|
414
|
+
try {
|
|
415
|
+
realFullPath = fs.realpathSync(fullPath);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
realFullPath = fullPath;
|
|
419
|
+
}
|
|
420
|
+
const worktrees = await this.listWorktrees(true);
|
|
421
|
+
return worktrees.find((w) => {
|
|
422
|
+
// Compare real paths to handle symlinks
|
|
423
|
+
try {
|
|
424
|
+
const realWPath = fs.realpathSync(w.path);
|
|
425
|
+
return realWPath === realFullPath;
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
return w.path === fullPath;
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
getWorktreePath(agentName, taskTitle) {
|
|
433
|
+
const slug = taskTitle ? createSlugFromTitle(taskTitle) : undefined;
|
|
434
|
+
const relativePath = generateWorktreePath(agentName, slug);
|
|
435
|
+
return path.join(this.config.workspaceRoot, relativePath);
|
|
436
|
+
}
|
|
437
|
+
async getWorktreesForAgent(agentName) {
|
|
438
|
+
this.ensureInitialized();
|
|
439
|
+
const worktrees = await this.listWorktrees();
|
|
440
|
+
const safeName = agentName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
441
|
+
return worktrees.filter((w) => {
|
|
442
|
+
// Check if the relative path starts with the agent's prefix
|
|
443
|
+
// Match either exact name (.stoneforge/.worktrees/bob) or name with slug (.stoneforge/.worktrees/bob-feature)
|
|
444
|
+
// Escape special regex characters in the worktreeDir path (like .)
|
|
445
|
+
const escapedDir = this.config.worktreeDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
446
|
+
const pathPattern = new RegExp(`^${escapedDir}/${safeName}($|-)`);
|
|
447
|
+
return pathPattern.test(w.relativePath);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
async worktreeExists(worktreePath) {
|
|
451
|
+
const worktree = await this.getWorktree(worktreePath);
|
|
452
|
+
return worktree !== undefined;
|
|
453
|
+
}
|
|
454
|
+
// ----------------------------------------
|
|
455
|
+
// Branch Operations
|
|
456
|
+
// ----------------------------------------
|
|
457
|
+
async getCurrentBranch() {
|
|
458
|
+
this.ensureInitialized();
|
|
459
|
+
try {
|
|
460
|
+
const { stdout } = await this.execGit(['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
461
|
+
return stdout.trim();
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
throw new WorktreeError('Failed to get current branch', 'BRANCH_QUERY_FAILED', error.message);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async getDefaultBranch() {
|
|
468
|
+
if (this.defaultBranch) {
|
|
469
|
+
return this.defaultBranch;
|
|
470
|
+
}
|
|
471
|
+
this.defaultBranch = await this.detectDefaultBranch();
|
|
472
|
+
return this.defaultBranch;
|
|
473
|
+
}
|
|
474
|
+
async branchExists(branchName) {
|
|
475
|
+
try {
|
|
476
|
+
await this.execGit(['rev-parse', '--verify', `refs/heads/${branchName}`]);
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
catch {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
// ----------------------------------------
|
|
484
|
+
// Private Helpers
|
|
485
|
+
// ----------------------------------------
|
|
486
|
+
ensureInitialized() {
|
|
487
|
+
if (!this.initialized) {
|
|
488
|
+
throw new WorktreeError('WorktreeManager not initialized. Call initWorkspace() first.', 'NOT_INITIALIZED');
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async execGit(args) {
|
|
492
|
+
return execFileAsync('git', args, {
|
|
493
|
+
cwd: this.config.workspaceRoot,
|
|
494
|
+
encoding: 'utf8',
|
|
495
|
+
timeout: GIT_OPERATION_TIMEOUT_MS,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Installs dependencies in a worktree directory.
|
|
500
|
+
* Detects the package manager (pnpm, npm, yarn, bun) and runs install.
|
|
501
|
+
* Uses a longer timeout since dependency installation can take a while.
|
|
502
|
+
*
|
|
503
|
+
* Tries a strict install first (--frozen-lockfile / ci) and falls back to
|
|
504
|
+
* a plain install when the lockfile is out of sync with package.json
|
|
505
|
+
* (common when the base branch has uncommitted lockfile changes).
|
|
506
|
+
*/
|
|
507
|
+
async installDependencies(worktreePath) {
|
|
508
|
+
// Check if package.json exists
|
|
509
|
+
const packageJsonPath = path.join(worktreePath, 'package.json');
|
|
510
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
511
|
+
// No package.json, nothing to install
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
// Detect package manager by lockfile presence
|
|
515
|
+
const hasLockfile = (name) => fs.existsSync(path.join(worktreePath, name));
|
|
516
|
+
let command;
|
|
517
|
+
let strictArgs;
|
|
518
|
+
if (hasLockfile('pnpm-lock.yaml')) {
|
|
519
|
+
command = 'pnpm';
|
|
520
|
+
strictArgs = ['install', '--frozen-lockfile'];
|
|
521
|
+
}
|
|
522
|
+
else if (hasLockfile('bun.lockb') || hasLockfile('bun.lock')) {
|
|
523
|
+
command = 'bun';
|
|
524
|
+
strictArgs = ['install', '--frozen-lockfile'];
|
|
525
|
+
}
|
|
526
|
+
else if (hasLockfile('yarn.lock')) {
|
|
527
|
+
command = 'yarn';
|
|
528
|
+
strictArgs = ['install', '--frozen-lockfile'];
|
|
529
|
+
}
|
|
530
|
+
else if (hasLockfile('package-lock.json')) {
|
|
531
|
+
command = 'npm';
|
|
532
|
+
strictArgs = ['ci'];
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
// Default to pnpm if no lockfile detected (monorepo case)
|
|
536
|
+
command = 'pnpm';
|
|
537
|
+
strictArgs = ['install'];
|
|
538
|
+
}
|
|
539
|
+
// Use a longer timeout for dependency installation (5 minutes)
|
|
540
|
+
const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
541
|
+
const execOptions = {
|
|
542
|
+
cwd: worktreePath,
|
|
543
|
+
encoding: 'utf8',
|
|
544
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
545
|
+
};
|
|
546
|
+
try {
|
|
547
|
+
await execFileAsync(command, strictArgs, execOptions);
|
|
548
|
+
}
|
|
549
|
+
catch {
|
|
550
|
+
// Strict install failed — the lockfile may be out of sync with
|
|
551
|
+
// package.json (e.g. uncommitted lockfile changes on the base branch).
|
|
552
|
+
// Fall back to a plain install so the worktree is still usable.
|
|
553
|
+
try {
|
|
554
|
+
await execFileAsync(command, ['install'], execOptions);
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
const execError = error;
|
|
558
|
+
const details = [
|
|
559
|
+
execError.message,
|
|
560
|
+
execError.stderr ? `stderr: ${execError.stderr}` : '',
|
|
561
|
+
execError.stdout ? `stdout: ${execError.stdout}` : '',
|
|
562
|
+
].filter(Boolean).join('\n');
|
|
563
|
+
throw new WorktreeError(`Failed to install dependencies in worktree: ${worktreePath}`, 'DEPENDENCY_INSTALL_FAILED', details);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
resolvePath(worktreePath) {
|
|
568
|
+
if (path.isAbsolute(worktreePath)) {
|
|
569
|
+
return worktreePath;
|
|
570
|
+
}
|
|
571
|
+
return path.join(this.config.workspaceRoot, worktreePath);
|
|
572
|
+
}
|
|
573
|
+
getRelativePath(fullPath) {
|
|
574
|
+
// Use the real workspace root for relative path calculation to handle symlinks
|
|
575
|
+
const baseRoot = this.realWorkspaceRoot ?? this.config.workspaceRoot;
|
|
576
|
+
// Also resolve the full path to handle symlinks in both paths
|
|
577
|
+
let realFullPath = fullPath;
|
|
578
|
+
try {
|
|
579
|
+
realFullPath = fs.realpathSync(fullPath);
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
// Path might not exist yet during creation, use as-is
|
|
583
|
+
}
|
|
584
|
+
return path.relative(baseRoot, realFullPath);
|
|
585
|
+
}
|
|
586
|
+
parseWorktreeList(output) {
|
|
587
|
+
const worktrees = [];
|
|
588
|
+
const lines = output.trim().split('\n');
|
|
589
|
+
let current = {};
|
|
590
|
+
for (const line of lines) {
|
|
591
|
+
if (line.startsWith('worktree ')) {
|
|
592
|
+
// Start of new worktree entry
|
|
593
|
+
if (current.path) {
|
|
594
|
+
worktrees.push(this.finalizeWorktreeInfo(current));
|
|
595
|
+
}
|
|
596
|
+
current = {
|
|
597
|
+
path: line.substring(9),
|
|
598
|
+
isMain: false,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
else if (line.startsWith('HEAD ')) {
|
|
602
|
+
current.head = line.substring(5);
|
|
603
|
+
}
|
|
604
|
+
else if (line.startsWith('branch ')) {
|
|
605
|
+
// Remove refs/heads/ prefix
|
|
606
|
+
const branch = line.substring(7);
|
|
607
|
+
current.branch = branch.replace(/^refs\/heads\//, '');
|
|
608
|
+
}
|
|
609
|
+
else if (line === 'bare') {
|
|
610
|
+
current.isMain = true;
|
|
611
|
+
}
|
|
612
|
+
else if (line === '') {
|
|
613
|
+
// End of entry
|
|
614
|
+
if (current.path) {
|
|
615
|
+
worktrees.push(this.finalizeWorktreeInfo(current));
|
|
616
|
+
current = {};
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
// Handle last entry
|
|
621
|
+
if (current.path) {
|
|
622
|
+
worktrees.push(this.finalizeWorktreeInfo(current));
|
|
623
|
+
}
|
|
624
|
+
// The first worktree is always the main one
|
|
625
|
+
if (worktrees.length > 0) {
|
|
626
|
+
worktrees[0].isMain = true;
|
|
627
|
+
}
|
|
628
|
+
return worktrees;
|
|
629
|
+
}
|
|
630
|
+
finalizeWorktreeInfo(partial) {
|
|
631
|
+
const fullPath = partial.path;
|
|
632
|
+
const relativePath = this.getRelativePath(fullPath);
|
|
633
|
+
const state = this.worktreeStates.get(relativePath) ?? 'active';
|
|
634
|
+
// Parse agent name and task ID from path if it matches our pattern
|
|
635
|
+
let agentName;
|
|
636
|
+
let taskId;
|
|
637
|
+
// Escape special regex characters in the worktreeDir path and match agent name
|
|
638
|
+
const escapedDir = this.config.worktreeDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
639
|
+
const match = relativePath.match(new RegExp(`^${escapedDir}/([^-]+)-`));
|
|
640
|
+
if (match) {
|
|
641
|
+
agentName = match[1];
|
|
642
|
+
// Task ID might be in the branch name
|
|
643
|
+
const branchMatch = partial.branch?.match(/^agent\/[^/]+\/([^-]+)/);
|
|
644
|
+
if (branchMatch) {
|
|
645
|
+
taskId = branchMatch[1];
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return {
|
|
649
|
+
path: fullPath,
|
|
650
|
+
relativePath,
|
|
651
|
+
branch: partial.branch ?? 'HEAD',
|
|
652
|
+
head: partial.head ?? '',
|
|
653
|
+
isMain: partial.isMain ?? false,
|
|
654
|
+
state,
|
|
655
|
+
agentName,
|
|
656
|
+
taskId,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
async detectDefaultBranch() {
|
|
660
|
+
// Check config first
|
|
661
|
+
if (this.config.defaultBaseBranch) {
|
|
662
|
+
return this.config.defaultBaseBranch;
|
|
663
|
+
}
|
|
664
|
+
// Try to detect from remote
|
|
665
|
+
try {
|
|
666
|
+
const { stdout } = await this.execGit(['remote', 'show', 'origin']);
|
|
667
|
+
const match = stdout.match(/HEAD branch: (.+)/);
|
|
668
|
+
if (match) {
|
|
669
|
+
return match[1].trim();
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
// No remote or error, continue
|
|
674
|
+
}
|
|
675
|
+
// Try common default branch names
|
|
676
|
+
for (const branch of ['main', 'master', 'develop']) {
|
|
677
|
+
if (await this.branchExists(branch)) {
|
|
678
|
+
return branch;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
// Fall back to current branch
|
|
682
|
+
return this.getCurrentBranch();
|
|
683
|
+
}
|
|
684
|
+
async ensureGitignore() {
|
|
685
|
+
const gitignorePath = path.join(this.config.workspaceRoot, '.gitignore');
|
|
686
|
+
const worktreeDirPattern = `/${this.config.worktreeDir}/`;
|
|
687
|
+
let content = '';
|
|
688
|
+
if (fs.existsSync(gitignorePath)) {
|
|
689
|
+
content = fs.readFileSync(gitignorePath, 'utf8');
|
|
690
|
+
}
|
|
691
|
+
if (!content.includes(worktreeDirPattern)) {
|
|
692
|
+
const newContent = content.endsWith('\n')
|
|
693
|
+
? content + worktreeDirPattern + '\n'
|
|
694
|
+
: content + '\n' + worktreeDirPattern + '\n';
|
|
695
|
+
fs.writeFileSync(gitignorePath, newContent, 'utf8');
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// ============================================================================
|
|
700
|
+
// Factory Function
|
|
701
|
+
// ============================================================================
|
|
702
|
+
/**
|
|
703
|
+
* Creates a WorktreeManager instance
|
|
704
|
+
*/
|
|
705
|
+
export function createWorktreeManager(config) {
|
|
706
|
+
return new WorktreeManagerImpl(config);
|
|
707
|
+
}
|
|
708
|
+
// ============================================================================
|
|
709
|
+
// Utility Functions
|
|
710
|
+
// ============================================================================
|
|
711
|
+
/**
|
|
712
|
+
* Type guard to check if a value is a valid WorktreeState
|
|
713
|
+
*/
|
|
714
|
+
export function isWorktreeState(value) {
|
|
715
|
+
return typeof value === 'string' && WorktreeStates.includes(value);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Checks if a state transition is valid
|
|
719
|
+
*/
|
|
720
|
+
export function isValidStateTransition(from, to) {
|
|
721
|
+
return WorktreeStateTransitions[from].includes(to);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Gets human-readable description of a worktree state
|
|
725
|
+
*/
|
|
726
|
+
export function getWorktreeStateDescription(state) {
|
|
727
|
+
switch (state) {
|
|
728
|
+
case 'creating':
|
|
729
|
+
return 'Being created';
|
|
730
|
+
case 'active':
|
|
731
|
+
return 'Active and in use';
|
|
732
|
+
case 'suspended':
|
|
733
|
+
return 'Suspended (can be resumed)';
|
|
734
|
+
case 'merging':
|
|
735
|
+
return 'Branch being merged';
|
|
736
|
+
case 'cleaning':
|
|
737
|
+
return 'Being cleaned up';
|
|
738
|
+
case 'archived':
|
|
739
|
+
return 'Archived (removed)';
|
|
740
|
+
default:
|
|
741
|
+
return 'Unknown';
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
//# sourceMappingURL=worktree-manager.js.map
|