@agimon-ai/doompi-git 0.0.1-alpha.8 → 0.0.1-alpha.9
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/dist/_virtual/_rolldown/runtime.cjs +23 -1
- package/dist/api-contracts.cjs +7 -1
- package/dist/api-contracts.d.cts +1 -1
- package/dist/api-contracts.d.mts +1 -1
- package/dist/api-contracts.mjs +2 -1
- package/dist/extensions/pi.cjs +38 -1
- package/dist/extensions/pi.cjs.map +1 -1
- package/dist/extensions/pi.d.cts +5 -4
- package/dist/extensions/pi.d.cts.map +1 -1
- package/dist/extensions/pi.d.mts +5 -4
- package/dist/extensions/pi.d.mts.map +1 -1
- package/dist/extensions/pi.mjs +33 -1
- package/dist/extensions/pi.mjs.map +1 -1
- package/dist/extensions/server.cjs +72 -1
- package/dist/extensions/server.cjs.map +1 -1
- package/dist/extensions/server.d.cts +3 -3
- package/dist/extensions/server.d.cts.map +1 -1
- package/dist/extensions/server.d.mts +3 -3
- package/dist/extensions/server.d.mts.map +1 -1
- package/dist/extensions/server.mjs +67 -1
- package/dist/extensions/server.mjs.map +1 -1
- package/dist/extensions/web.mjs +485 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +5 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +3 -1
- package/dist/src/constants/git.cjs +8 -0
- package/dist/src/constants/git.cjs.map +1 -0
- package/dist/src/constants/git.mjs +7 -0
- package/dist/src/constants/git.mjs.map +1 -0
- package/dist/src/extensions/(backend)/api/_lib/route.server.cjs +93 -0
- package/dist/src/extensions/(backend)/api/_lib/route.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/api/_lib/route.server.mjs +92 -0
- package/dist/src/extensions/(backend)/api/_lib/route.server.mjs.map +1 -0
- package/dist/src/extensions/(backend)/api/route.server.cjs +7 -0
- package/dist/src/extensions/(backend)/api/route.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/api/route.server.mjs +8 -0
- package/dist/src/extensions/(backend)/api/route.server.mjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/_lib/git-worktrees.server.cjs +195 -0
- package/dist/src/extensions/(backend)/channel/_lib/git-worktrees.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/_lib/git-worktrees.server.mjs +194 -0
- package/dist/src/extensions/(backend)/channel/_lib/git-worktrees.server.mjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/git-worktrees.server.cjs +7 -0
- package/dist/src/extensions/(backend)/channel/git-worktrees.server.cjs.map +1 -0
- package/dist/src/extensions/(backend)/channel/git-worktrees.server.mjs +8 -0
- package/dist/src/extensions/(backend)/channel/git-worktrees.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.server.cjs +71 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.server.mjs +70 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/root.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/_lib/git.cli.cjs +17 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/_lib/git.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/_lib/git.cli.mjs +16 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/_lib/git.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/git.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/git.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/git.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/git.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompi-use-git.cli.cjs +15 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompi-use-git.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompi-use-git.cli.mjs +15 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompi-use-git.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGit.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGit.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGit.server.mjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGit.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGitReadme.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGitReadme.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGitReadme.server.mjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiGitReadme.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiUseGit.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiUseGit.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiUseGit.server.mjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/_lib/doompiUseGit.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-git.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-git.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-git.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-git.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGit.server.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGit.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGit.server.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGit.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGitReadme.server.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGitReadme.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGitReadme.server.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiGitReadme.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiUseGit.server.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiUseGit.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiUseGit.server.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompiUseGit.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/_lib/runWorktree.cli.cjs +38 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/_lib/runWorktree.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/_lib/runWorktree.cli.mjs +36 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/_lib/runWorktree.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.cli.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.cli.mjs +8 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/tool/runWorktree.server.mjs.map +1 -0
- package/dist/src/schemas/apiContracts.cjs +138 -0
- package/dist/src/schemas/apiContracts.cjs.map +1 -0
- package/dist/{schemas → src/schemas}/apiContracts.d.cts.map +1 -1
- package/dist/{schemas → src/schemas}/apiContracts.d.mts.map +1 -1
- package/dist/src/schemas/apiContracts.mjs +135 -0
- package/dist/src/schemas/apiContracts.mjs.map +1 -0
- package/dist/src/schemas/runWorktreeTool.cjs +152 -0
- package/dist/src/schemas/runWorktreeTool.cjs.map +1 -0
- package/dist/src/schemas/runWorktreeTool.mjs +150 -0
- package/dist/src/schemas/runWorktreeTool.mjs.map +1 -0
- package/dist/src/services/atomicJson/index.cjs +42 -0
- package/dist/src/services/atomicJson/index.cjs.map +1 -0
- package/dist/src/services/atomicJson/index.mjs +38 -0
- package/dist/src/services/atomicJson/index.mjs.map +1 -0
- package/dist/src/services/compositionMirror/index.cjs +119 -0
- package/dist/src/services/compositionMirror/index.cjs.map +1 -0
- package/dist/src/services/compositionMirror/index.mjs +116 -0
- package/dist/src/services/compositionMirror/index.mjs.map +1 -0
- package/dist/src/services/errors/index.cjs +29 -0
- package/dist/src/services/errors/index.cjs.map +1 -0
- package/dist/src/services/errors/index.mjs +27 -0
- package/dist/src/services/errors/index.mjs.map +1 -0
- package/dist/src/services/extensionService/index.cjs +13 -0
- package/dist/src/services/extensionService/index.cjs.map +1 -0
- package/dist/src/services/extensionService/index.d.cts.map +1 -0
- package/dist/src/services/extensionService/index.d.mts.map +1 -0
- package/dist/src/services/extensionService/index.mjs +13 -0
- package/dist/src/services/extensionService/index.mjs.map +1 -0
- package/dist/src/services/gitCli/index.cjs +144 -0
- package/dist/src/services/gitCli/index.cjs.map +1 -0
- package/dist/src/services/gitCli/index.mjs +142 -0
- package/dist/src/services/gitCli/index.mjs.map +1 -0
- package/dist/src/services/gitDependencies/index.cjs +14 -0
- package/dist/src/services/gitDependencies/index.cjs.map +1 -0
- package/dist/src/services/gitDependencies/index.d.cts.map +1 -0
- package/dist/src/services/gitDependencies/index.d.mts.map +1 -0
- package/dist/src/services/gitDependencies/index.mjs +14 -0
- package/dist/src/services/gitDependencies/index.mjs.map +1 -0
- package/dist/src/services/paths/index.cjs +35 -0
- package/dist/src/services/paths/index.cjs.map +1 -0
- package/dist/src/services/paths/index.mjs +30 -0
- package/dist/src/services/paths/index.mjs.map +1 -0
- package/dist/src/services/repositoryIdentity/index.cjs +71 -0
- package/dist/src/services/repositoryIdentity/index.cjs.map +1 -0
- package/dist/src/services/repositoryIdentity/index.mjs +65 -0
- package/dist/src/services/repositoryIdentity/index.mjs.map +1 -0
- package/dist/src/services/runWorktree/index.cjs +145 -0
- package/dist/src/services/runWorktree/index.cjs.map +1 -0
- package/dist/src/services/runWorktree/index.mjs +142 -0
- package/dist/src/services/runWorktree/index.mjs.map +1 -0
- package/dist/src/services/worktreeEvents/index.cjs +64 -0
- package/dist/src/services/worktreeEvents/index.cjs.map +1 -0
- package/dist/src/services/worktreeEvents/index.d.cts.map +1 -0
- package/dist/src/services/worktreeEvents/index.d.mts.map +1 -0
- package/dist/src/services/worktreeEvents/index.mjs +60 -0
- package/dist/src/services/worktreeEvents/index.mjs.map +1 -0
- package/dist/src/services/worktreeNaming/index.cjs +99 -0
- package/dist/src/services/worktreeNaming/index.cjs.map +1 -0
- package/dist/src/services/worktreeNaming/index.d.cts.map +1 -0
- package/dist/src/services/worktreeNaming/index.d.mts.map +1 -0
- package/dist/src/services/worktreeNaming/index.mjs +94 -0
- package/dist/src/services/worktreeNaming/index.mjs.map +1 -0
- package/dist/src/services/worktreeOperations/index.cjs +263 -0
- package/dist/src/services/worktreeOperations/index.cjs.map +1 -0
- package/dist/src/services/worktreeOperations/index.d.cts.map +1 -0
- package/dist/src/services/worktreeOperations/index.d.mts.map +1 -0
- package/dist/src/services/worktreeOperations/index.mjs +261 -0
- package/dist/src/services/worktreeOperations/index.mjs.map +1 -0
- package/dist/src/services/worktreeRegistry/index.cjs +38 -0
- package/dist/src/services/worktreeRegistry/index.cjs.map +1 -0
- package/dist/src/services/worktreeRegistry/index.mjs +38 -0
- package/dist/src/services/worktreeRegistry/index.mjs.map +1 -0
- package/dist/src/types/extension.d.cts.map +1 -0
- package/dist/src/types/extension.d.mts.map +1 -0
- package/dist/src/types/webWorktrees.cjs +20 -0
- package/dist/src/types/webWorktrees.cjs.map +1 -0
- package/dist/src/types/webWorktrees.mjs +20 -0
- package/dist/src/types/webWorktrees.mjs.map +1 -0
- package/dist/src/types/worktreeRegistry.d.cts.map +1 -0
- package/dist/src/types/worktreeRegistry.d.mts.map +1 -0
- package/package.json +24 -19
- package/dist/constants/git.cjs +0 -2
- package/dist/constants/git.cjs.map +0 -1
- package/dist/constants/git.mjs +0 -2
- package/dist/constants/git.mjs.map +0 -1
- package/dist/controllers/gitCommand.cjs +0 -2
- package/dist/controllers/gitCommand.cjs.map +0 -1
- package/dist/controllers/gitCommand.mjs +0 -2
- package/dist/controllers/gitCommand.mjs.map +0 -1
- package/dist/controllers/gitSession.cjs +0 -2
- package/dist/controllers/gitSession.cjs.map +0 -1
- package/dist/controllers/gitSession.mjs +0 -2
- package/dist/controllers/gitSession.mjs.map +0 -1
- package/dist/controllers/hubApi.cjs +0 -2
- package/dist/controllers/hubApi.cjs.map +0 -1
- package/dist/controllers/hubApi.mjs +0 -2
- package/dist/controllers/hubApi.mjs.map +0 -1
- package/dist/controllers/worktreesChannel.cjs +0 -2
- package/dist/controllers/worktreesChannel.cjs.map +0 -1
- package/dist/controllers/worktreesChannel.mjs +0 -2
- package/dist/controllers/worktreesChannel.mjs.map +0 -1
- package/dist/schemas/apiContracts.cjs +0 -2
- package/dist/schemas/apiContracts.cjs.map +0 -1
- package/dist/schemas/apiContracts.mjs +0 -2
- package/dist/schemas/apiContracts.mjs.map +0 -1
- package/dist/schemas/runWorktreeTool.cjs +0 -2
- package/dist/schemas/runWorktreeTool.cjs.map +0 -1
- package/dist/schemas/runWorktreeTool.mjs +0 -2
- package/dist/schemas/runWorktreeTool.mjs.map +0 -1
- package/dist/services/atomicJson/index.cjs +0 -2
- package/dist/services/atomicJson/index.cjs.map +0 -1
- package/dist/services/atomicJson/index.mjs +0 -2
- package/dist/services/atomicJson/index.mjs.map +0 -1
- package/dist/services/compositionMirror/index.cjs +0 -2
- package/dist/services/compositionMirror/index.cjs.map +0 -1
- package/dist/services/compositionMirror/index.mjs +0 -2
- package/dist/services/compositionMirror/index.mjs.map +0 -1
- package/dist/services/errors/index.cjs +0 -2
- package/dist/services/errors/index.cjs.map +0 -1
- package/dist/services/errors/index.mjs +0 -2
- package/dist/services/errors/index.mjs.map +0 -1
- package/dist/services/extensionService/index.cjs +0 -2
- package/dist/services/extensionService/index.cjs.map +0 -1
- package/dist/services/extensionService/index.d.cts.map +0 -1
- package/dist/services/extensionService/index.d.mts.map +0 -1
- package/dist/services/extensionService/index.mjs +0 -2
- package/dist/services/extensionService/index.mjs.map +0 -1
- package/dist/services/gitCli/index.cjs +0 -4
- package/dist/services/gitCli/index.cjs.map +0 -1
- package/dist/services/gitCli/index.mjs +0 -4
- package/dist/services/gitCli/index.mjs.map +0 -1
- package/dist/services/gitDependencies/index.cjs +0 -2
- package/dist/services/gitDependencies/index.cjs.map +0 -1
- package/dist/services/gitDependencies/index.d.cts.map +0 -1
- package/dist/services/gitDependencies/index.d.mts.map +0 -1
- package/dist/services/gitDependencies/index.mjs +0 -2
- package/dist/services/gitDependencies/index.mjs.map +0 -1
- package/dist/services/paths/index.cjs +0 -2
- package/dist/services/paths/index.cjs.map +0 -1
- package/dist/services/paths/index.mjs +0 -2
- package/dist/services/paths/index.mjs.map +0 -1
- package/dist/services/repositoryIdentity/index.cjs +0 -2
- package/dist/services/repositoryIdentity/index.cjs.map +0 -1
- package/dist/services/repositoryIdentity/index.mjs +0 -2
- package/dist/services/repositoryIdentity/index.mjs.map +0 -1
- package/dist/services/runWorktree/index.cjs +0 -15
- package/dist/services/runWorktree/index.cjs.map +0 -1
- package/dist/services/runWorktree/index.mjs +0 -15
- package/dist/services/runWorktree/index.mjs.map +0 -1
- package/dist/services/worktreeEvents/index.cjs +0 -2
- package/dist/services/worktreeEvents/index.cjs.map +0 -1
- package/dist/services/worktreeEvents/index.d.cts.map +0 -1
- package/dist/services/worktreeEvents/index.d.mts.map +0 -1
- package/dist/services/worktreeEvents/index.mjs +0 -2
- package/dist/services/worktreeEvents/index.mjs.map +0 -1
- package/dist/services/worktreeNaming/index.cjs +0 -2
- package/dist/services/worktreeNaming/index.cjs.map +0 -1
- package/dist/services/worktreeNaming/index.d.cts.map +0 -1
- package/dist/services/worktreeNaming/index.d.mts.map +0 -1
- package/dist/services/worktreeNaming/index.mjs +0 -2
- package/dist/services/worktreeNaming/index.mjs.map +0 -1
- package/dist/services/worktreeOperations/index.cjs +0 -2
- package/dist/services/worktreeOperations/index.cjs.map +0 -1
- package/dist/services/worktreeOperations/index.d.cts.map +0 -1
- package/dist/services/worktreeOperations/index.d.mts.map +0 -1
- package/dist/services/worktreeOperations/index.mjs +0 -2
- package/dist/services/worktreeOperations/index.mjs.map +0 -1
- package/dist/services/worktreeRegistry/index.cjs +0 -2
- package/dist/services/worktreeRegistry/index.cjs.map +0 -1
- package/dist/services/worktreeRegistry/index.mjs +0 -2
- package/dist/services/worktreeRegistry/index.mjs.map +0 -1
- package/dist/tools/runWorktree.cjs +0 -2
- package/dist/tools/runWorktree.cjs.map +0 -1
- package/dist/tools/runWorktree.mjs +0 -2
- package/dist/tools/runWorktree.mjs.map +0 -1
- package/dist/types/extension.d.cts.map +0 -1
- package/dist/types/extension.d.mts.map +0 -1
- package/dist/types/webWorktrees.cjs +0 -2
- package/dist/types/webWorktrees.cjs.map +0 -1
- package/dist/types/webWorktrees.mjs +0 -2
- package/dist/types/webWorktrees.mjs.map +0 -1
- package/dist/types/worktreeRegistry.d.cts.map +0 -1
- package/dist/types/worktreeRegistry.d.mts.map +0 -1
- package/src/extensions/web.ts +0 -33
- package/src/types/webWorktrees.ts +0 -55
- package/src/types/worktreeRegistry.ts +0 -78
- package/src/web/components/RunWorktreeToolMessage.tsx +0 -45
- package/src/web/components/WorktreesActivitySection.tsx +0 -80
- package/src/web/components/WorktreesPanel.tsx +0 -175
- package/src/web/components/toolRenderers.ts +0 -8
- package/src/web/stores/worktreesActivityStore.ts +0 -125
- package/src/web/tsconfig.json +0 -22
- /package/dist/{schemas → src/schemas}/apiContracts.d.cts +0 -0
- /package/dist/{schemas → src/schemas}/apiContracts.d.mts +0 -0
- /package/dist/{services → src/services}/extensionService/index.d.cts +0 -0
- /package/dist/{services → src/services}/extensionService/index.d.mts +0 -0
- /package/dist/{services → src/services}/gitDependencies/index.d.cts +0 -0
- /package/dist/{services → src/services}/gitDependencies/index.d.mts +0 -0
- /package/dist/{services → src/services}/worktreeEvents/index.d.cts +0 -0
- /package/dist/{services → src/services}/worktreeEvents/index.d.mts +0 -0
- /package/dist/{services → src/services}/worktreeNaming/index.d.cts +0 -0
- /package/dist/{services → src/services}/worktreeNaming/index.d.mts +0 -0
- /package/dist/{services → src/services}/worktreeOperations/index.d.cts +0 -0
- /package/dist/{services → src/services}/worktreeOperations/index.d.mts +0 -0
- /package/dist/{types → src/types}/extension.d.cts +0 -0
- /package/dist/{types → src/types}/extension.d.mts +0 -0
- /package/dist/{types → src/types}/worktreeRegistry.d.cts +0 -0
- /package/dist/{types → src/types}/worktreeRegistry.d.mts +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
const require_runtime = require("../../../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_index = require("../compositionMirror/index.cjs");
|
|
3
|
+
const require_index$1 = require("../errors/index.cjs");
|
|
4
|
+
const require_index$2 = require("../repositoryIdentity/index.cjs");
|
|
5
|
+
const require_index$3 = require("../paths/index.cjs");
|
|
6
|
+
const require_index$4 = require("../worktreeEvents/index.cjs");
|
|
7
|
+
const require_index$5 = require("../worktreeNaming/index.cjs");
|
|
8
|
+
const require_index$6 = require("../worktreeRegistry/index.cjs");
|
|
9
|
+
let node_fs = require("node:fs");
|
|
10
|
+
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
11
|
+
let node_crypto = require("node:crypto");
|
|
12
|
+
//#region src/services/worktreeOperations/index.ts
|
|
13
|
+
function createWorktreeOperations(deps) {
|
|
14
|
+
const { git } = deps;
|
|
15
|
+
const sessionService = deps.sessionService;
|
|
16
|
+
const mirror = deps.mirror ?? require_index.mirrorComposition;
|
|
17
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
18
|
+
const requireSessionService = () => {
|
|
19
|
+
if (sessionService === void 0) throw new require_index$1.HubUnavailableError("The cockpit session service is unavailable. Start the cockpit and try again.");
|
|
20
|
+
return sessionService;
|
|
21
|
+
};
|
|
22
|
+
const requireMessageInbox = () => {
|
|
23
|
+
if (deps.messageInbox === void 0) throw new require_index$1.HubUnavailableError("The Git message service is unavailable. Start the cockpit and try again.");
|
|
24
|
+
return deps.messageInbox;
|
|
25
|
+
};
|
|
26
|
+
const probe = {
|
|
27
|
+
alive: (record) => requireSessionService().isLive(record.sessionId),
|
|
28
|
+
exists: (path) => node_fs.default.existsSync(path)
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Everything an action needs about the repository it is acting on.
|
|
32
|
+
*
|
|
33
|
+
* Resolved per call rather than cached: a session's cwd is stable, but the
|
|
34
|
+
* worktree registry is shared and another process may have written it since
|
|
35
|
+
* the last call.
|
|
36
|
+
*/
|
|
37
|
+
const resolve = async (context) => {
|
|
38
|
+
const root = await git.repositoryRoot(context.cwd);
|
|
39
|
+
if (root === void 0) throw new require_index$1.DoomGitExpectedError("not_a_repository", `${context.cwd} is not inside a git repository.`, false, "Run this from a repository checkout.");
|
|
40
|
+
const store = require_index$6.createWorktreeRegistry(require_index$3.registryFile(root, deps.homeDir));
|
|
41
|
+
return {
|
|
42
|
+
root,
|
|
43
|
+
store,
|
|
44
|
+
records: store.list()
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
const require$1 = (records, id) => {
|
|
48
|
+
const record = records.find((entry) => entry.id === id);
|
|
49
|
+
if (record === void 0) throw new require_index$1.DoomGitExpectedError("worktree_not_found", `No worktree with id ${id}.`, false, "Call list to see the current ids.");
|
|
50
|
+
return record;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Refuses to destroy or merge a worktree another live session is using.
|
|
54
|
+
*
|
|
55
|
+
* `parentSessionId` records who asked for the worktree. Two sessions in one
|
|
56
|
+
* checkout share this registry, so without the check either could close the
|
|
57
|
+
* other's work in progress by id.
|
|
58
|
+
*
|
|
59
|
+
* Once that session is gone there is no owner left to protect, and refusing
|
|
60
|
+
* would strand the worktree: nothing else could ever close it.
|
|
61
|
+
*/
|
|
62
|
+
const requireOwner = (context, record, action) => {
|
|
63
|
+
if (record.parentSessionId === context.sessionId) return;
|
|
64
|
+
if (!requireSessionService().isLive(record.parentSessionId)) return;
|
|
65
|
+
throw new require_index$1.DoomGitExpectedError("worktree_not_owned", `Worktree ${record.id} belongs to another session.`, false, `Ask the session that created it to ${action} it, or close that session first.`);
|
|
66
|
+
};
|
|
67
|
+
const peerFor = (context, record) => {
|
|
68
|
+
if (context.sessionId === record.parentSessionId) return {
|
|
69
|
+
sessionId: record.sessionId,
|
|
70
|
+
party: "parent"
|
|
71
|
+
};
|
|
72
|
+
if (context.sessionId === record.sessionId) return {
|
|
73
|
+
sessionId: record.parentSessionId,
|
|
74
|
+
party: "child"
|
|
75
|
+
};
|
|
76
|
+
throw new require_index$1.DoomGitExpectedError("worktree_not_owned", `Worktree ${record.id} belongs to another session.`, false, "Use a worktree owned by this session.");
|
|
77
|
+
};
|
|
78
|
+
const requirePeerLive = (peerSessionId) => {
|
|
79
|
+
if (!requireSessionService().isLive(peerSessionId)) throw new require_index$1.DoomGitExpectedError("worktree_peer_unavailable", "The other worktree session is no longer live.", false, "Start a new worktree session before sending a message.");
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
async spawn(context, request, options) {
|
|
83
|
+
const { root, store, records } = await resolve(context);
|
|
84
|
+
const refusal = require_index$5.refuseSpawn({
|
|
85
|
+
branch: request.branch,
|
|
86
|
+
existing: records
|
|
87
|
+
});
|
|
88
|
+
if (refusal !== void 0) throw new require_index$1.DoomGitExpectedError("worktree_exists", refusal, false, "Pick another branch name, or close it.");
|
|
89
|
+
const baseRef = request.baseRef ?? await git.currentBranch(root) ?? "HEAD";
|
|
90
|
+
const id = require_index$2.shortId((0, node_crypto.randomUUID)());
|
|
91
|
+
const path = require_index$5.worktreeDirectory({
|
|
92
|
+
worktreesRoot: require_index$3.worktreesRoot(deps.homeDir),
|
|
93
|
+
repositoryLabel: require_index$2.repositoryLabel(root),
|
|
94
|
+
repositoryId: require_index$2.repositoryId(root),
|
|
95
|
+
branch: request.branch,
|
|
96
|
+
shortId: id
|
|
97
|
+
});
|
|
98
|
+
const cancelled = () => options?.signal?.aborted === true;
|
|
99
|
+
const rollback = async () => {
|
|
100
|
+
await git.removeWorktree({
|
|
101
|
+
repositoryRoot: root,
|
|
102
|
+
path,
|
|
103
|
+
force: true
|
|
104
|
+
}).catch(() => void 0);
|
|
105
|
+
return await git.deleteBranch({
|
|
106
|
+
repositoryRoot: root,
|
|
107
|
+
branch: request.branch
|
|
108
|
+
}).catch(() => false) ? "" : ` The branch ${request.branch} has work on it and was kept.`;
|
|
109
|
+
};
|
|
110
|
+
options?.onProgress?.(`creating branch ${request.branch}\u2026`);
|
|
111
|
+
await git.addWorktree({
|
|
112
|
+
repositoryRoot: root,
|
|
113
|
+
path,
|
|
114
|
+
branch: request.branch,
|
|
115
|
+
baseRef
|
|
116
|
+
});
|
|
117
|
+
if (cancelled()) {
|
|
118
|
+
const kept = await rollback();
|
|
119
|
+
throw new require_index$1.DoomGitExpectedError("spawn_cancelled", `Cancelled before the session started.${kept}`, false, "The worktree was removed; run it again when you are ready.");
|
|
120
|
+
}
|
|
121
|
+
options?.onProgress?.("mirroring build output…");
|
|
122
|
+
mirror(root, path);
|
|
123
|
+
options?.onProgress?.("starting session…");
|
|
124
|
+
let sessionId;
|
|
125
|
+
try {
|
|
126
|
+
sessionId = (await requireSessionService().create({
|
|
127
|
+
cwd: path,
|
|
128
|
+
name: request.name ?? request.branch,
|
|
129
|
+
parentSessionId: context.sessionId,
|
|
130
|
+
sessionProvenance: "worktree",
|
|
131
|
+
...options?.signal === void 0 ? {} : { signal: options.signal }
|
|
132
|
+
})).sessionId;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const kept = await rollback();
|
|
135
|
+
if (cancelled()) throw new require_index$1.DoomGitExpectedError("spawn_cancelled", `Cancelled while the session was starting.${kept}`, false, "The worktree was removed; run it again when you are ready.");
|
|
136
|
+
if (error instanceof require_index$1.HubUnavailableError) throw new require_index$1.DoomGitExpectedError("hub_unavailable", `${error.message}${kept}`, true, "Start the cockpit and try again.");
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
const record = {
|
|
140
|
+
version: 1,
|
|
141
|
+
id,
|
|
142
|
+
branch: request.branch,
|
|
143
|
+
baseRef,
|
|
144
|
+
path,
|
|
145
|
+
repositoryRoot: root,
|
|
146
|
+
sessionId,
|
|
147
|
+
parentSessionId: context.sessionId,
|
|
148
|
+
status: "running",
|
|
149
|
+
createdAt: now().toISOString()
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
store.replace([...records, record]);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
await requireSessionService().close(sessionId).catch(() => void 0);
|
|
155
|
+
const kept = await rollback();
|
|
156
|
+
throw new require_index$1.DoomGitExpectedError("registry_write_failed", `The worktree was created but the registry could not be written (${error.message}).${kept}`, true, "Check the registry file for permissions or free space, then try again.");
|
|
157
|
+
}
|
|
158
|
+
return record;
|
|
159
|
+
},
|
|
160
|
+
async close(context, id, force) {
|
|
161
|
+
const { root, store, records } = await resolve(context);
|
|
162
|
+
const record = require$1(records, id);
|
|
163
|
+
requireOwner(context, record, "close");
|
|
164
|
+
const dirtyFiles = node_fs.default.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];
|
|
165
|
+
const refusal = require_index$5.refuseClose({
|
|
166
|
+
record,
|
|
167
|
+
dirtyFiles,
|
|
168
|
+
force
|
|
169
|
+
});
|
|
170
|
+
if (refusal !== void 0) throw new require_index$1.DoomGitExpectedError("worktree_dirty", refusal, false, "Commit the work, or pass force: true.");
|
|
171
|
+
await requireSessionService().close(record.sessionId);
|
|
172
|
+
await git.removeWorktree({
|
|
173
|
+
repositoryRoot: root,
|
|
174
|
+
path: record.path,
|
|
175
|
+
force
|
|
176
|
+
});
|
|
177
|
+
store.replace(records.filter((entry) => entry.id !== id));
|
|
178
|
+
return record;
|
|
179
|
+
},
|
|
180
|
+
async list(context) {
|
|
181
|
+
const { store, records } = await resolve(context);
|
|
182
|
+
const outcome = require_index$5.reconcile(records, probe);
|
|
183
|
+
if (outcome.changed.length > 0) store.replace(outcome.records);
|
|
184
|
+
return outcome.records;
|
|
185
|
+
},
|
|
186
|
+
async status(context, id) {
|
|
187
|
+
const { records } = await resolve(context);
|
|
188
|
+
const record = require$1(records, id);
|
|
189
|
+
return {
|
|
190
|
+
record,
|
|
191
|
+
dirtyFiles: node_fs.default.existsSync(record.path) ? await git.dirtyFiles(record.path) : []
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
async merge(context, id, message) {
|
|
195
|
+
const { root, records } = await resolve(context);
|
|
196
|
+
const record = require$1(records, id);
|
|
197
|
+
requireOwner(context, record, "merge");
|
|
198
|
+
const parentDirty = await git.dirtyFiles(root);
|
|
199
|
+
if (parentDirty.length > 0) throw new require_index$1.DoomGitExpectedError("worktree_dirty", `The parent checkout has ${String(parentDirty.length)} uncommitted file(s): ${parentDirty.slice(0, 10).join(", ")}.`, false, "Commit or stash the parent checkout first.");
|
|
200
|
+
await git.mergeBranch({
|
|
201
|
+
repositoryRoot: root,
|
|
202
|
+
branch: record.branch,
|
|
203
|
+
...message === void 0 ? {} : { message }
|
|
204
|
+
});
|
|
205
|
+
return record;
|
|
206
|
+
},
|
|
207
|
+
/** Sends only between the parent session and its own worktree session. */
|
|
208
|
+
async send(context, id, message) {
|
|
209
|
+
if (Buffer.byteLength(message, "utf8") > 65536) throw new require_index$1.DoomGitExpectedError("message_too_large", `A message may not exceed ${String(require_index$4.MAX_WORKTREE_MESSAGE_BYTES)} bytes.`, false, "Send a shorter message.");
|
|
210
|
+
const { records } = await resolve(context);
|
|
211
|
+
const record = require$1(records, id);
|
|
212
|
+
const peer = peerFor(context, record);
|
|
213
|
+
requirePeerLive(peer.sessionId);
|
|
214
|
+
requireMessageInbox().send(peer.sessionId, {
|
|
215
|
+
version: 1,
|
|
216
|
+
worktreeId: record.id,
|
|
217
|
+
fromSessionId: context.sessionId,
|
|
218
|
+
from: peer.party,
|
|
219
|
+
text: message,
|
|
220
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
221
|
+
});
|
|
222
|
+
},
|
|
223
|
+
async messages(context, id) {
|
|
224
|
+
const { records } = await resolve(context);
|
|
225
|
+
const record = require$1(records, id);
|
|
226
|
+
peerFor(context, record);
|
|
227
|
+
return requireMessageInbox().receive(record.id);
|
|
228
|
+
},
|
|
229
|
+
async prune(context, dryRun) {
|
|
230
|
+
const { root, store, records } = await resolve(context);
|
|
231
|
+
const reconciled = require_index$5.reconcile(records, probe);
|
|
232
|
+
const gitPaths = await git.listWorktreePaths(root);
|
|
233
|
+
const dirtyByPath = /* @__PURE__ */ new Map();
|
|
234
|
+
for (const record of reconciled.records) if (record.status === "orphaned" && node_fs.default.existsSync(record.path)) dirtyByPath.set(record.path, await git.dirtyFiles(record.path));
|
|
235
|
+
const plan = require_index$5.planPrune({
|
|
236
|
+
records: reconciled.records,
|
|
237
|
+
gitPaths,
|
|
238
|
+
worktreesRoot: require_index$3.worktreesRoot(deps.homeDir),
|
|
239
|
+
dirtyByPath
|
|
240
|
+
});
|
|
241
|
+
if (dryRun) {
|
|
242
|
+
store.replace(reconciled.records);
|
|
243
|
+
return plan;
|
|
244
|
+
}
|
|
245
|
+
for (const record of plan.remove) await git.removeWorktree({
|
|
246
|
+
repositoryRoot: root,
|
|
247
|
+
path: record.path,
|
|
248
|
+
force: false
|
|
249
|
+
});
|
|
250
|
+
for (const record of [...plan.remove, ...plan.forget]) if (!await git.deleteBranch({
|
|
251
|
+
repositoryRoot: root,
|
|
252
|
+
branch: record.branch
|
|
253
|
+
}).catch(() => false)) plan.keptBranches.push(record.branch);
|
|
254
|
+
const dropped = new Set([...plan.remove, ...plan.forget].map((record) => record.id));
|
|
255
|
+
store.replace(reconciled.records.filter((record) => !dropped.has(record.id)));
|
|
256
|
+
return plan;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
exports.createWorktreeOperations = createWorktreeOperations;
|
|
262
|
+
|
|
263
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["mirrorComposition","HubUnavailableError","fs","DoomGitExpectedError","createWorktreeRegistry","registryFile","require","refuseSpawn","shortId","randomUUID","worktreeDirectory","worktreesRoot","repositoryLabel","repositoryId","refuseClose","reconcile","MAX_WORKTREE_MESSAGE_BYTES","planPrune"],"sources":["../../../../src/services/worktreeOperations/index.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\n\nimport type { DoomHubSessionService } from '@agimon-ai/doompi-core/hub-channel';\n\nimport { WORKTREE_RECORD_VERSION } from '../../types/worktreeRegistry';\nimport type { WorktreeGit, WorktreeRecord } from '../../types/worktreeRegistry';\nimport { mirrorComposition } from '../compositionMirror';\nimport { DoomGitExpectedError, HubUnavailableError } from '../errors';\nimport { registryFile, worktreesRoot } from '../paths';\nimport { repositoryId, repositoryLabel, shortId } from '../repositoryIdentity';\nimport { MAX_WORKTREE_MESSAGE_BYTES, type WorktreeMessageInbox, type WorktreeMessageParty } from '../worktreeEvents';\nimport { planPrune, reconcile, refuseClose, refuseSpawn, worktreeDirectory } from '../worktreeNaming';\nimport { createWorktreeRegistry } from '../worktreeRegistry';\n\nexport interface WorktreeContext {\n /** Where the calling session is working, used to find the repository. */\n cwd: string;\n /** The calling session, recorded as the new session's parent. */\n sessionId: string;\n}\n\nexport interface SpawnWorktreeRequest {\n branch: string;\n baseRef?: string;\n name?: string;\n}\n\nexport interface SpawnOptions {\n /** Aborted when the caller gives up, so a spawn nobody is waiting for stops. */\n signal?: AbortSignal;\n /** Phase labels, for a caller that can show them while the work runs. */\n onProgress?: (label: string) => void;\n}\n\nexport interface WorktreeOperations {\n spawn(context: WorktreeContext, request: SpawnWorktreeRequest, options?: SpawnOptions): Promise<WorktreeRecord>;\n close(context: WorktreeContext, id: string, force: boolean): Promise<WorktreeRecord>;\n list(context: WorktreeContext): Promise<WorktreeRecord[]>;\n status(context: WorktreeContext, id: string): Promise<{ record: WorktreeRecord; dirtyFiles: string[] }>;\n merge(context: WorktreeContext, id: string, message?: string): Promise<WorktreeRecord>;\n prune(context: WorktreeContext, dryRun: boolean): Promise<ReturnType<typeof planPrune>>;\n send(context: WorktreeContext, id: string, message: string): Promise<void>;\n messages(context: WorktreeContext, id: string): Promise<import('../worktreeEvents').WorktreeMessage[]>;\n}\n\nexport interface WorktreeOperationsDeps {\n git: WorktreeGit;\n /** The canonical hub-owned session lifecycle. */\n sessionService?: DoomHubSessionService;\n /** Session-local inbox for parent/worktree messages. */\n messageInbox?: WorktreeMessageInbox;\n /** Injected so a test never copies a real dependency tree. */\n mirror?: typeof mirrorComposition;\n homeDir?: string;\n /** Injected so a record's createdAt is reproducible in a test. */\n now?: () => Date;\n}\n\nexport function createWorktreeOperations(deps: WorktreeOperationsDeps): WorktreeOperations {\n const { git } = deps;\n const sessionService = deps.sessionService;\n const mirror = deps.mirror ?? mirrorComposition;\n const now = deps.now ?? (() => new Date());\n const requireSessionService = (): DoomHubSessionService => {\n if (sessionService === undefined)\n throw new HubUnavailableError('The cockpit session service is unavailable. Start the cockpit and try again.');\n return sessionService;\n };\n const requireMessageInbox = (): WorktreeMessageInbox => {\n if (deps.messageInbox === undefined)\n throw new HubUnavailableError('The Git message service is unavailable. Start the cockpit and try again.');\n return deps.messageInbox;\n };\n const probe = {\n alive: (record: WorktreeRecord) => requireSessionService().isLive(record.sessionId),\n exists: (path: string) => fs.existsSync(path),\n };\n\n /**\n * Everything an action needs about the repository it is acting on.\n *\n * Resolved per call rather than cached: a session's cwd is stable, but the\n * worktree registry is shared and another process may have written it since\n * the last call.\n */\n const resolve = async (context: WorktreeContext) => {\n const root = await git.repositoryRoot(context.cwd);\n if (root === undefined) {\n throw new DoomGitExpectedError(\n 'not_a_repository',\n `${context.cwd} is not inside a git repository.`,\n false,\n 'Run this from a repository checkout.',\n );\n }\n const store = createWorktreeRegistry(registryFile(root, deps.homeDir));\n return { root, store, records: store.list() };\n };\n\n const require = (records: readonly WorktreeRecord[], id: string): WorktreeRecord => {\n const record = records.find((entry) => entry.id === id);\n if (record === undefined) {\n throw new DoomGitExpectedError(\n 'worktree_not_found',\n `No worktree with id ${id}.`,\n false,\n 'Call list to see the current ids.',\n );\n }\n return record;\n };\n\n /**\n * Refuses to destroy or merge a worktree another live session is using.\n *\n * `parentSessionId` records who asked for the worktree. Two sessions in one\n * checkout share this registry, so without the check either could close the\n * other's work in progress by id.\n *\n * Once that session is gone there is no owner left to protect, and refusing\n * would strand the worktree: nothing else could ever close it.\n */\n const requireOwner = (context: WorktreeContext, record: WorktreeRecord, action: string): void => {\n if (record.parentSessionId === context.sessionId) return;\n if (!requireSessionService().isLive(record.parentSessionId)) return;\n throw new DoomGitExpectedError(\n 'worktree_not_owned',\n `Worktree ${record.id} belongs to another session.`,\n false,\n `Ask the session that created it to ${action} it, or close that session first.`,\n );\n };\n\n const peerFor = (\n context: WorktreeContext,\n record: WorktreeRecord,\n ): { sessionId: string; party: WorktreeMessageParty } => {\n if (context.sessionId === record.parentSessionId) return { sessionId: record.sessionId, party: 'parent' };\n if (context.sessionId === record.sessionId) return { sessionId: record.parentSessionId, party: 'child' };\n throw new DoomGitExpectedError(\n 'worktree_not_owned',\n `Worktree ${record.id} belongs to another session.`,\n false,\n 'Use a worktree owned by this session.',\n );\n };\n\n const requirePeerLive = (peerSessionId: string): void => {\n if (!requireSessionService().isLive(peerSessionId)) {\n throw new DoomGitExpectedError(\n 'worktree_peer_unavailable',\n 'The other worktree session is no longer live.',\n false,\n 'Start a new worktree session before sending a message.',\n );\n }\n };\n\n return {\n async spawn(context, request, options) {\n const { root, store, records } = await resolve(context);\n const refusal = refuseSpawn({ branch: request.branch, existing: records });\n if (refusal !== undefined) {\n throw new DoomGitExpectedError('worktree_exists', refusal, false, 'Pick another branch name, or close it.');\n }\n const baseRef = request.baseRef ?? (await git.currentBranch(root)) ?? 'HEAD';\n const id = shortId(randomUUID());\n const path = worktreeDirectory({\n worktreesRoot: worktreesRoot(deps.homeDir),\n repositoryLabel: repositoryLabel(root),\n repositoryId: repositoryId(root),\n branch: request.branch,\n shortId: id,\n });\n\n // The worktree and the branch are made by one command, so they are undone\n // by one too. `worktree remove` leaves the branch behind, and a branch\n // nobody asked for is what a failed spawn used to leave on the floor.\n const cancelled = (): boolean => options?.signal?.aborted === true;\n const rollback = async (): Promise<string> => {\n await git.removeWorktree({ repositoryRoot: root, path, force: true }).catch(() => undefined);\n const deleted = await git.deleteBranch({ repositoryRoot: root, branch: request.branch }).catch(() => false);\n return deleted ? '' : ` The branch ${request.branch} has work on it and was kept.`;\n };\n\n options?.onProgress?.(`creating branch ${request.branch}\\u2026`);\n await git.addWorktree({ repositoryRoot: root, path, branch: request.branch, baseRef });\n\n // Checked here rather than only inside the session call: this is the\n // point where a checkout exists that nothing has recorded yet, which is\n // exactly the state an interrupted spawn used to leave behind.\n if (cancelled()) {\n const kept = await rollback();\n throw new DoomGitExpectedError(\n 'spawn_cancelled',\n `Cancelled before the session started.${kept}`,\n false,\n 'The worktree was removed; run it again when you are ready.',\n );\n }\n\n // Before the session, because the session composes the extensions this\n // repository's config names and those live in build output git does not\n // track. A worktree without it starts and dies on the first package it\n // cannot resolve. Nothing here can fail the spawn: a repository with no\n // build output to mirror simply has none.\n options?.onProgress?.('mirroring build output\\u2026');\n mirror(root, path);\n\n // The worktree exists from here on, so every later failure has to leave\n // it removed or recorded. An unrecorded directory on disk is the one\n // outcome with no way back: nothing would ever list it again.\n options?.onProgress?.('starting session\\u2026');\n let sessionId: string;\n try {\n const session = await requireSessionService().create({\n cwd: path,\n name: request.name ?? request.branch,\n parentSessionId: context.sessionId,\n sessionProvenance: 'worktree',\n ...(options?.signal === undefined ? {} : { signal: options.signal }),\n });\n sessionId = session.sessionId;\n } catch (error) {\n const kept = await rollback();\n if (cancelled()) {\n throw new DoomGitExpectedError(\n 'spawn_cancelled',\n `Cancelled while the session was starting.${kept}`,\n false,\n 'The worktree was removed; run it again when you are ready.',\n );\n }\n if (error instanceof HubUnavailableError) {\n throw new DoomGitExpectedError(\n 'hub_unavailable',\n `${error.message}${kept}`,\n true,\n 'Start the cockpit and try again.',\n );\n }\n throw error;\n }\n\n const record: WorktreeRecord = {\n version: WORKTREE_RECORD_VERSION,\n id,\n branch: request.branch,\n baseRef,\n path,\n repositoryRoot: root,\n sessionId,\n parentSessionId: context.sessionId,\n status: 'running',\n createdAt: now().toISOString(),\n };\n try {\n store.replace([...records, record]);\n } catch (error) {\n // A live session with no record is the worst outcome available here:\n // it runs, and no id anything can name points at it. The session goes\n // back too, rather than being left for someone to find by hand.\n await requireSessionService()\n .close(sessionId)\n .catch(() => undefined);\n const kept = await rollback();\n throw new DoomGitExpectedError(\n 'registry_write_failed',\n `The worktree was created but the registry could not be written (${(error as Error).message}).${kept}`,\n true,\n 'Check the registry file for permissions or free space, then try again.',\n );\n }\n return record;\n },\n\n async close(context, id, force) {\n const { root, store, records } = await resolve(context);\n const record = require(records, id);\n requireOwner(context, record, 'close');\n const dirtyFiles = fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];\n const refusal = refuseClose({ record, dirtyFiles, force });\n if (refusal !== undefined) {\n throw new DoomGitExpectedError('worktree_dirty', refusal, false, 'Commit the work, or pass force: true.');\n }\n // The session is stopped before the directory goes. Removing a checkout\n // while a process still holds files open in it is how a half-removed\n // worktree and a stale git administrative entry are made.\n await requireSessionService().close(record.sessionId);\n await git.removeWorktree({ repositoryRoot: root, path: record.path, force });\n store.replace(records.filter((entry) => entry.id !== id));\n return record;\n },\n\n async list(context) {\n const { store, records } = await resolve(context);\n const outcome = reconcile(records, probe);\n if (outcome.changed.length > 0) store.replace(outcome.records);\n return outcome.records;\n },\n\n async status(context, id) {\n const { records } = await resolve(context);\n const record = require(records, id);\n const dirtyFiles = fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];\n return { record, dirtyFiles };\n },\n\n async merge(context, id, message) {\n const { root, records } = await resolve(context);\n const record = require(records, id);\n requireOwner(context, record, 'merge');\n // The merge lands in the parent, so the parent is what must be clean. A\n // dirty parent would mix the person's in-flight edits into a merge commit\n // they did not intend to make.\n const parentDirty = await git.dirtyFiles(root);\n if (parentDirty.length > 0) {\n throw new DoomGitExpectedError(\n 'worktree_dirty',\n `The parent checkout has ${String(parentDirty.length)} uncommitted file(s): ${parentDirty.slice(0, 10).join(', ')}.`,\n false,\n 'Commit or stash the parent checkout first.',\n );\n }\n await git.mergeBranch({\n repositoryRoot: root,\n branch: record.branch,\n ...(message === undefined ? {} : { message }),\n });\n return record;\n },\n\n /** Sends only between the parent session and its own worktree session. */\n async send(context, id, message) {\n if (Buffer.byteLength(message, 'utf8') > MAX_WORKTREE_MESSAGE_BYTES) {\n throw new DoomGitExpectedError(\n 'message_too_large',\n `A message may not exceed ${String(MAX_WORKTREE_MESSAGE_BYTES)} bytes.`,\n false,\n 'Send a shorter message.',\n );\n }\n const { records } = await resolve(context);\n const record = require(records, id);\n const peer = peerFor(context, record);\n requirePeerLive(peer.sessionId);\n requireMessageInbox().send(peer.sessionId, {\n version: 1,\n worktreeId: record.id,\n fromSessionId: context.sessionId,\n from: peer.party,\n text: message,\n sentAt: new Date().toISOString(),\n });\n },\n\n async messages(context, id) {\n const { records } = await resolve(context);\n const record = require(records, id);\n peerFor(context, record);\n return requireMessageInbox().receive(record.id);\n },\n\n async prune(context, dryRun) {\n const { root, store, records } = await resolve(context);\n const reconciled = reconcile(records, probe);\n const gitPaths = await git.listWorktreePaths(root);\n const dirtyByPath = new Map<string, readonly string[]>();\n for (const record of reconciled.records) {\n if (record.status === 'orphaned' && fs.existsSync(record.path)) {\n dirtyByPath.set(record.path, await git.dirtyFiles(record.path));\n }\n }\n const plan = planPrune({\n records: reconciled.records,\n gitPaths,\n worktreesRoot: worktreesRoot(deps.homeDir),\n dirtyByPath,\n });\n if (dryRun) {\n store.replace(reconciled.records);\n return plan;\n }\n for (const record of plan.remove) {\n await git.removeWorktree({ repositoryRoot: root, path: record.path, force: false });\n }\n // The checkout goes, so the branch it was made for goes with it. Safe\n // delete only: git keeps anything holding commits, and the plan says\n // which ones it kept rather than leaving the reader to notice later.\n for (const record of [...plan.remove, ...plan.forget]) {\n const deleted = await git.deleteBranch({ repositoryRoot: root, branch: record.branch }).catch(() => false);\n if (!deleted) plan.keptBranches.push(record.branch);\n }\n const dropped = new Set([...plan.remove, ...plan.forget].map((record) => record.id));\n store.replace(reconciled.records.filter((record) => !dropped.has(record.id)));\n return plan;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/worktreeOperations/index.ts"],"mappings":";;;;;iBAeiB;;EAEf;;EAEA;;iBAGe;EACf;EACA;EACA;;iBAGe;;EAEf,SAAS;;EAET,cAAc;;iBAGC;EACf,MAAM,SAAS,iBAAiB,SAAS,sBAAsB,UAAU,eAAe,QAAQ;EAChG,MAAM,SAAS,iBAAiB,YAAY,iBAAiB,QAAQ;EACrE,KAAK,SAAS,kBAAkB,QAAQ;EACxC,OAAO,SAAS,iBAAiB,aAAa;IAAU,QAAQ;IAAgB;;EAChF,MAAM,SAAS,iBAAiB,YAAY,mBAAmB,QAAQ;EACvE,MAAM,SAAS,iBAAiB,kBAAkB,QAAQ,kBAAkB;EAC5E,KAAK,SAAS,iBAAiB,YAAY,kBAAkB;EAC7D,SAAS,SAAS,iBAAiB,aAAa,QAA9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/worktreeOperations/index.ts"],"mappings":";;;;;iBAeiB;;EAEf;;EAEA;;iBAGe;EACf;EACA;EACA;;iBAGe;;EAEf,SAAS;;EAET,cAAc;;iBAGC;EACf,MAAM,SAAS,iBAAiB,SAAS,sBAAsB,UAAU,eAAe,QAAQ;EAChG,MAAM,SAAS,iBAAiB,YAAY,iBAAiB,QAAQ;EACrE,KAAK,SAAS,kBAAkB,QAAQ;EACxC,OAAO,SAAS,iBAAiB,aAAa;IAAU,QAAQ;IAAgB;;EAChF,MAAM,SAAS,iBAAiB,YAAY,mBAAmB,QAAQ;EACvE,MAAM,SAAS,iBAAiB,kBAAkB,QAAQ,kBAAkB;EAC5E,KAAK,SAAS,iBAAiB,YAAY,kBAAkB;EAC7D,SAAS,SAAS,iBAAiB,aAAa,QAA9B"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { mirrorComposition } from "../compositionMirror/index.mjs";
|
|
2
|
+
import { DoomGitExpectedError, HubUnavailableError } from "../errors/index.mjs";
|
|
3
|
+
import { repositoryId, repositoryLabel, shortId } from "../repositoryIdentity/index.mjs";
|
|
4
|
+
import { registryFile, worktreesRoot } from "../paths/index.mjs";
|
|
5
|
+
import { MAX_WORKTREE_MESSAGE_BYTES } from "../worktreeEvents/index.mjs";
|
|
6
|
+
import { planPrune, reconcile, refuseClose, refuseSpawn, worktreeDirectory } from "../worktreeNaming/index.mjs";
|
|
7
|
+
import { createWorktreeRegistry } from "../worktreeRegistry/index.mjs";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
//#region src/services/worktreeOperations/index.ts
|
|
11
|
+
function createWorktreeOperations(deps) {
|
|
12
|
+
const { git } = deps;
|
|
13
|
+
const sessionService = deps.sessionService;
|
|
14
|
+
const mirror = deps.mirror ?? mirrorComposition;
|
|
15
|
+
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
16
|
+
const requireSessionService = () => {
|
|
17
|
+
if (sessionService === void 0) throw new HubUnavailableError("The cockpit session service is unavailable. Start the cockpit and try again.");
|
|
18
|
+
return sessionService;
|
|
19
|
+
};
|
|
20
|
+
const requireMessageInbox = () => {
|
|
21
|
+
if (deps.messageInbox === void 0) throw new HubUnavailableError("The Git message service is unavailable. Start the cockpit and try again.");
|
|
22
|
+
return deps.messageInbox;
|
|
23
|
+
};
|
|
24
|
+
const probe = {
|
|
25
|
+
alive: (record) => requireSessionService().isLive(record.sessionId),
|
|
26
|
+
exists: (path) => fs.existsSync(path)
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Everything an action needs about the repository it is acting on.
|
|
30
|
+
*
|
|
31
|
+
* Resolved per call rather than cached: a session's cwd is stable, but the
|
|
32
|
+
* worktree registry is shared and another process may have written it since
|
|
33
|
+
* the last call.
|
|
34
|
+
*/
|
|
35
|
+
const resolve = async (context) => {
|
|
36
|
+
const root = await git.repositoryRoot(context.cwd);
|
|
37
|
+
if (root === void 0) throw new DoomGitExpectedError("not_a_repository", `${context.cwd} is not inside a git repository.`, false, "Run this from a repository checkout.");
|
|
38
|
+
const store = createWorktreeRegistry(registryFile(root, deps.homeDir));
|
|
39
|
+
return {
|
|
40
|
+
root,
|
|
41
|
+
store,
|
|
42
|
+
records: store.list()
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const require = (records, id) => {
|
|
46
|
+
const record = records.find((entry) => entry.id === id);
|
|
47
|
+
if (record === void 0) throw new DoomGitExpectedError("worktree_not_found", `No worktree with id ${id}.`, false, "Call list to see the current ids.");
|
|
48
|
+
return record;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Refuses to destroy or merge a worktree another live session is using.
|
|
52
|
+
*
|
|
53
|
+
* `parentSessionId` records who asked for the worktree. Two sessions in one
|
|
54
|
+
* checkout share this registry, so without the check either could close the
|
|
55
|
+
* other's work in progress by id.
|
|
56
|
+
*
|
|
57
|
+
* Once that session is gone there is no owner left to protect, and refusing
|
|
58
|
+
* would strand the worktree: nothing else could ever close it.
|
|
59
|
+
*/
|
|
60
|
+
const requireOwner = (context, record, action) => {
|
|
61
|
+
if (record.parentSessionId === context.sessionId) return;
|
|
62
|
+
if (!requireSessionService().isLive(record.parentSessionId)) return;
|
|
63
|
+
throw new DoomGitExpectedError("worktree_not_owned", `Worktree ${record.id} belongs to another session.`, false, `Ask the session that created it to ${action} it, or close that session first.`);
|
|
64
|
+
};
|
|
65
|
+
const peerFor = (context, record) => {
|
|
66
|
+
if (context.sessionId === record.parentSessionId) return {
|
|
67
|
+
sessionId: record.sessionId,
|
|
68
|
+
party: "parent"
|
|
69
|
+
};
|
|
70
|
+
if (context.sessionId === record.sessionId) return {
|
|
71
|
+
sessionId: record.parentSessionId,
|
|
72
|
+
party: "child"
|
|
73
|
+
};
|
|
74
|
+
throw new DoomGitExpectedError("worktree_not_owned", `Worktree ${record.id} belongs to another session.`, false, "Use a worktree owned by this session.");
|
|
75
|
+
};
|
|
76
|
+
const requirePeerLive = (peerSessionId) => {
|
|
77
|
+
if (!requireSessionService().isLive(peerSessionId)) throw new DoomGitExpectedError("worktree_peer_unavailable", "The other worktree session is no longer live.", false, "Start a new worktree session before sending a message.");
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
async spawn(context, request, options) {
|
|
81
|
+
const { root, store, records } = await resolve(context);
|
|
82
|
+
const refusal = refuseSpawn({
|
|
83
|
+
branch: request.branch,
|
|
84
|
+
existing: records
|
|
85
|
+
});
|
|
86
|
+
if (refusal !== void 0) throw new DoomGitExpectedError("worktree_exists", refusal, false, "Pick another branch name, or close it.");
|
|
87
|
+
const baseRef = request.baseRef ?? await git.currentBranch(root) ?? "HEAD";
|
|
88
|
+
const id = shortId(randomUUID());
|
|
89
|
+
const path = worktreeDirectory({
|
|
90
|
+
worktreesRoot: worktreesRoot(deps.homeDir),
|
|
91
|
+
repositoryLabel: repositoryLabel(root),
|
|
92
|
+
repositoryId: repositoryId(root),
|
|
93
|
+
branch: request.branch,
|
|
94
|
+
shortId: id
|
|
95
|
+
});
|
|
96
|
+
const cancelled = () => options?.signal?.aborted === true;
|
|
97
|
+
const rollback = async () => {
|
|
98
|
+
await git.removeWorktree({
|
|
99
|
+
repositoryRoot: root,
|
|
100
|
+
path,
|
|
101
|
+
force: true
|
|
102
|
+
}).catch(() => void 0);
|
|
103
|
+
return await git.deleteBranch({
|
|
104
|
+
repositoryRoot: root,
|
|
105
|
+
branch: request.branch
|
|
106
|
+
}).catch(() => false) ? "" : ` The branch ${request.branch} has work on it and was kept.`;
|
|
107
|
+
};
|
|
108
|
+
options?.onProgress?.(`creating branch ${request.branch}\u2026`);
|
|
109
|
+
await git.addWorktree({
|
|
110
|
+
repositoryRoot: root,
|
|
111
|
+
path,
|
|
112
|
+
branch: request.branch,
|
|
113
|
+
baseRef
|
|
114
|
+
});
|
|
115
|
+
if (cancelled()) {
|
|
116
|
+
const kept = await rollback();
|
|
117
|
+
throw new DoomGitExpectedError("spawn_cancelled", `Cancelled before the session started.${kept}`, false, "The worktree was removed; run it again when you are ready.");
|
|
118
|
+
}
|
|
119
|
+
options?.onProgress?.("mirroring build output…");
|
|
120
|
+
mirror(root, path);
|
|
121
|
+
options?.onProgress?.("starting session…");
|
|
122
|
+
let sessionId;
|
|
123
|
+
try {
|
|
124
|
+
sessionId = (await requireSessionService().create({
|
|
125
|
+
cwd: path,
|
|
126
|
+
name: request.name ?? request.branch,
|
|
127
|
+
parentSessionId: context.sessionId,
|
|
128
|
+
sessionProvenance: "worktree",
|
|
129
|
+
...options?.signal === void 0 ? {} : { signal: options.signal }
|
|
130
|
+
})).sessionId;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
const kept = await rollback();
|
|
133
|
+
if (cancelled()) throw new DoomGitExpectedError("spawn_cancelled", `Cancelled while the session was starting.${kept}`, false, "The worktree was removed; run it again when you are ready.");
|
|
134
|
+
if (error instanceof HubUnavailableError) throw new DoomGitExpectedError("hub_unavailable", `${error.message}${kept}`, true, "Start the cockpit and try again.");
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
const record = {
|
|
138
|
+
version: 1,
|
|
139
|
+
id,
|
|
140
|
+
branch: request.branch,
|
|
141
|
+
baseRef,
|
|
142
|
+
path,
|
|
143
|
+
repositoryRoot: root,
|
|
144
|
+
sessionId,
|
|
145
|
+
parentSessionId: context.sessionId,
|
|
146
|
+
status: "running",
|
|
147
|
+
createdAt: now().toISOString()
|
|
148
|
+
};
|
|
149
|
+
try {
|
|
150
|
+
store.replace([...records, record]);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
await requireSessionService().close(sessionId).catch(() => void 0);
|
|
153
|
+
const kept = await rollback();
|
|
154
|
+
throw new DoomGitExpectedError("registry_write_failed", `The worktree was created but the registry could not be written (${error.message}).${kept}`, true, "Check the registry file for permissions or free space, then try again.");
|
|
155
|
+
}
|
|
156
|
+
return record;
|
|
157
|
+
},
|
|
158
|
+
async close(context, id, force) {
|
|
159
|
+
const { root, store, records } = await resolve(context);
|
|
160
|
+
const record = require(records, id);
|
|
161
|
+
requireOwner(context, record, "close");
|
|
162
|
+
const dirtyFiles = fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];
|
|
163
|
+
const refusal = refuseClose({
|
|
164
|
+
record,
|
|
165
|
+
dirtyFiles,
|
|
166
|
+
force
|
|
167
|
+
});
|
|
168
|
+
if (refusal !== void 0) throw new DoomGitExpectedError("worktree_dirty", refusal, false, "Commit the work, or pass force: true.");
|
|
169
|
+
await requireSessionService().close(record.sessionId);
|
|
170
|
+
await git.removeWorktree({
|
|
171
|
+
repositoryRoot: root,
|
|
172
|
+
path: record.path,
|
|
173
|
+
force
|
|
174
|
+
});
|
|
175
|
+
store.replace(records.filter((entry) => entry.id !== id));
|
|
176
|
+
return record;
|
|
177
|
+
},
|
|
178
|
+
async list(context) {
|
|
179
|
+
const { store, records } = await resolve(context);
|
|
180
|
+
const outcome = reconcile(records, probe);
|
|
181
|
+
if (outcome.changed.length > 0) store.replace(outcome.records);
|
|
182
|
+
return outcome.records;
|
|
183
|
+
},
|
|
184
|
+
async status(context, id) {
|
|
185
|
+
const { records } = await resolve(context);
|
|
186
|
+
const record = require(records, id);
|
|
187
|
+
return {
|
|
188
|
+
record,
|
|
189
|
+
dirtyFiles: fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : []
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
async merge(context, id, message) {
|
|
193
|
+
const { root, records } = await resolve(context);
|
|
194
|
+
const record = require(records, id);
|
|
195
|
+
requireOwner(context, record, "merge");
|
|
196
|
+
const parentDirty = await git.dirtyFiles(root);
|
|
197
|
+
if (parentDirty.length > 0) throw new DoomGitExpectedError("worktree_dirty", `The parent checkout has ${String(parentDirty.length)} uncommitted file(s): ${parentDirty.slice(0, 10).join(", ")}.`, false, "Commit or stash the parent checkout first.");
|
|
198
|
+
await git.mergeBranch({
|
|
199
|
+
repositoryRoot: root,
|
|
200
|
+
branch: record.branch,
|
|
201
|
+
...message === void 0 ? {} : { message }
|
|
202
|
+
});
|
|
203
|
+
return record;
|
|
204
|
+
},
|
|
205
|
+
/** Sends only between the parent session and its own worktree session. */
|
|
206
|
+
async send(context, id, message) {
|
|
207
|
+
if (Buffer.byteLength(message, "utf8") > 65536) throw new DoomGitExpectedError("message_too_large", `A message may not exceed ${String(MAX_WORKTREE_MESSAGE_BYTES)} bytes.`, false, "Send a shorter message.");
|
|
208
|
+
const { records } = await resolve(context);
|
|
209
|
+
const record = require(records, id);
|
|
210
|
+
const peer = peerFor(context, record);
|
|
211
|
+
requirePeerLive(peer.sessionId);
|
|
212
|
+
requireMessageInbox().send(peer.sessionId, {
|
|
213
|
+
version: 1,
|
|
214
|
+
worktreeId: record.id,
|
|
215
|
+
fromSessionId: context.sessionId,
|
|
216
|
+
from: peer.party,
|
|
217
|
+
text: message,
|
|
218
|
+
sentAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
async messages(context, id) {
|
|
222
|
+
const { records } = await resolve(context);
|
|
223
|
+
const record = require(records, id);
|
|
224
|
+
peerFor(context, record);
|
|
225
|
+
return requireMessageInbox().receive(record.id);
|
|
226
|
+
},
|
|
227
|
+
async prune(context, dryRun) {
|
|
228
|
+
const { root, store, records } = await resolve(context);
|
|
229
|
+
const reconciled = reconcile(records, probe);
|
|
230
|
+
const gitPaths = await git.listWorktreePaths(root);
|
|
231
|
+
const dirtyByPath = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const record of reconciled.records) if (record.status === "orphaned" && fs.existsSync(record.path)) dirtyByPath.set(record.path, await git.dirtyFiles(record.path));
|
|
233
|
+
const plan = planPrune({
|
|
234
|
+
records: reconciled.records,
|
|
235
|
+
gitPaths,
|
|
236
|
+
worktreesRoot: worktreesRoot(deps.homeDir),
|
|
237
|
+
dirtyByPath
|
|
238
|
+
});
|
|
239
|
+
if (dryRun) {
|
|
240
|
+
store.replace(reconciled.records);
|
|
241
|
+
return plan;
|
|
242
|
+
}
|
|
243
|
+
for (const record of plan.remove) await git.removeWorktree({
|
|
244
|
+
repositoryRoot: root,
|
|
245
|
+
path: record.path,
|
|
246
|
+
force: false
|
|
247
|
+
});
|
|
248
|
+
for (const record of [...plan.remove, ...plan.forget]) if (!await git.deleteBranch({
|
|
249
|
+
repositoryRoot: root,
|
|
250
|
+
branch: record.branch
|
|
251
|
+
}).catch(() => false)) plan.keptBranches.push(record.branch);
|
|
252
|
+
const dropped = new Set([...plan.remove, ...plan.forget].map((record) => record.id));
|
|
253
|
+
store.replace(reconciled.records.filter((record) => !dropped.has(record.id)));
|
|
254
|
+
return plan;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
//#endregion
|
|
259
|
+
export { createWorktreeOperations };
|
|
260
|
+
|
|
261
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/worktreeOperations/index.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\n\nimport type { DoomHubSessionService } from '@agimon-ai/doompi-core/hub-channel';\n\nimport { WORKTREE_RECORD_VERSION } from '../../types/worktreeRegistry';\nimport type { WorktreeGit, WorktreeRecord } from '../../types/worktreeRegistry';\nimport { mirrorComposition } from '../compositionMirror';\nimport { DoomGitExpectedError, HubUnavailableError } from '../errors';\nimport { registryFile, worktreesRoot } from '../paths';\nimport { repositoryId, repositoryLabel, shortId } from '../repositoryIdentity';\nimport { MAX_WORKTREE_MESSAGE_BYTES, type WorktreeMessageInbox, type WorktreeMessageParty } from '../worktreeEvents';\nimport { planPrune, reconcile, refuseClose, refuseSpawn, worktreeDirectory } from '../worktreeNaming';\nimport { createWorktreeRegistry } from '../worktreeRegistry';\n\nexport interface WorktreeContext {\n /** Where the calling session is working, used to find the repository. */\n cwd: string;\n /** The calling session, recorded as the new session's parent. */\n sessionId: string;\n}\n\nexport interface SpawnWorktreeRequest {\n branch: string;\n baseRef?: string;\n name?: string;\n}\n\nexport interface SpawnOptions {\n /** Aborted when the caller gives up, so a spawn nobody is waiting for stops. */\n signal?: AbortSignal;\n /** Phase labels, for a caller that can show them while the work runs. */\n onProgress?: (label: string) => void;\n}\n\nexport interface WorktreeOperations {\n spawn(context: WorktreeContext, request: SpawnWorktreeRequest, options?: SpawnOptions): Promise<WorktreeRecord>;\n close(context: WorktreeContext, id: string, force: boolean): Promise<WorktreeRecord>;\n list(context: WorktreeContext): Promise<WorktreeRecord[]>;\n status(context: WorktreeContext, id: string): Promise<{ record: WorktreeRecord; dirtyFiles: string[] }>;\n merge(context: WorktreeContext, id: string, message?: string): Promise<WorktreeRecord>;\n prune(context: WorktreeContext, dryRun: boolean): Promise<ReturnType<typeof planPrune>>;\n send(context: WorktreeContext, id: string, message: string): Promise<void>;\n messages(context: WorktreeContext, id: string): Promise<import('../worktreeEvents').WorktreeMessage[]>;\n}\n\nexport interface WorktreeOperationsDeps {\n git: WorktreeGit;\n /** The canonical hub-owned session lifecycle. */\n sessionService?: DoomHubSessionService;\n /** Session-local inbox for parent/worktree messages. */\n messageInbox?: WorktreeMessageInbox;\n /** Injected so a test never copies a real dependency tree. */\n mirror?: typeof mirrorComposition;\n homeDir?: string;\n /** Injected so a record's createdAt is reproducible in a test. */\n now?: () => Date;\n}\n\nexport function createWorktreeOperations(deps: WorktreeOperationsDeps): WorktreeOperations {\n const { git } = deps;\n const sessionService = deps.sessionService;\n const mirror = deps.mirror ?? mirrorComposition;\n const now = deps.now ?? (() => new Date());\n const requireSessionService = (): DoomHubSessionService => {\n if (sessionService === undefined)\n throw new HubUnavailableError('The cockpit session service is unavailable. Start the cockpit and try again.');\n return sessionService;\n };\n const requireMessageInbox = (): WorktreeMessageInbox => {\n if (deps.messageInbox === undefined)\n throw new HubUnavailableError('The Git message service is unavailable. Start the cockpit and try again.');\n return deps.messageInbox;\n };\n const probe = {\n alive: (record: WorktreeRecord) => requireSessionService().isLive(record.sessionId),\n exists: (path: string) => fs.existsSync(path),\n };\n\n /**\n * Everything an action needs about the repository it is acting on.\n *\n * Resolved per call rather than cached: a session's cwd is stable, but the\n * worktree registry is shared and another process may have written it since\n * the last call.\n */\n const resolve = async (context: WorktreeContext) => {\n const root = await git.repositoryRoot(context.cwd);\n if (root === undefined) {\n throw new DoomGitExpectedError(\n 'not_a_repository',\n `${context.cwd} is not inside a git repository.`,\n false,\n 'Run this from a repository checkout.',\n );\n }\n const store = createWorktreeRegistry(registryFile(root, deps.homeDir));\n return { root, store, records: store.list() };\n };\n\n const require = (records: readonly WorktreeRecord[], id: string): WorktreeRecord => {\n const record = records.find((entry) => entry.id === id);\n if (record === undefined) {\n throw new DoomGitExpectedError(\n 'worktree_not_found',\n `No worktree with id ${id}.`,\n false,\n 'Call list to see the current ids.',\n );\n }\n return record;\n };\n\n /**\n * Refuses to destroy or merge a worktree another live session is using.\n *\n * `parentSessionId` records who asked for the worktree. Two sessions in one\n * checkout share this registry, so without the check either could close the\n * other's work in progress by id.\n *\n * Once that session is gone there is no owner left to protect, and refusing\n * would strand the worktree: nothing else could ever close it.\n */\n const requireOwner = (context: WorktreeContext, record: WorktreeRecord, action: string): void => {\n if (record.parentSessionId === context.sessionId) return;\n if (!requireSessionService().isLive(record.parentSessionId)) return;\n throw new DoomGitExpectedError(\n 'worktree_not_owned',\n `Worktree ${record.id} belongs to another session.`,\n false,\n `Ask the session that created it to ${action} it, or close that session first.`,\n );\n };\n\n const peerFor = (\n context: WorktreeContext,\n record: WorktreeRecord,\n ): { sessionId: string; party: WorktreeMessageParty } => {\n if (context.sessionId === record.parentSessionId) return { sessionId: record.sessionId, party: 'parent' };\n if (context.sessionId === record.sessionId) return { sessionId: record.parentSessionId, party: 'child' };\n throw new DoomGitExpectedError(\n 'worktree_not_owned',\n `Worktree ${record.id} belongs to another session.`,\n false,\n 'Use a worktree owned by this session.',\n );\n };\n\n const requirePeerLive = (peerSessionId: string): void => {\n if (!requireSessionService().isLive(peerSessionId)) {\n throw new DoomGitExpectedError(\n 'worktree_peer_unavailable',\n 'The other worktree session is no longer live.',\n false,\n 'Start a new worktree session before sending a message.',\n );\n }\n };\n\n return {\n async spawn(context, request, options) {\n const { root, store, records } = await resolve(context);\n const refusal = refuseSpawn({ branch: request.branch, existing: records });\n if (refusal !== undefined) {\n throw new DoomGitExpectedError('worktree_exists', refusal, false, 'Pick another branch name, or close it.');\n }\n const baseRef = request.baseRef ?? (await git.currentBranch(root)) ?? 'HEAD';\n const id = shortId(randomUUID());\n const path = worktreeDirectory({\n worktreesRoot: worktreesRoot(deps.homeDir),\n repositoryLabel: repositoryLabel(root),\n repositoryId: repositoryId(root),\n branch: request.branch,\n shortId: id,\n });\n\n // The worktree and the branch are made by one command, so they are undone\n // by one too. `worktree remove` leaves the branch behind, and a branch\n // nobody asked for is what a failed spawn used to leave on the floor.\n const cancelled = (): boolean => options?.signal?.aborted === true;\n const rollback = async (): Promise<string> => {\n await git.removeWorktree({ repositoryRoot: root, path, force: true }).catch(() => undefined);\n const deleted = await git.deleteBranch({ repositoryRoot: root, branch: request.branch }).catch(() => false);\n return deleted ? '' : ` The branch ${request.branch} has work on it and was kept.`;\n };\n\n options?.onProgress?.(`creating branch ${request.branch}\\u2026`);\n await git.addWorktree({ repositoryRoot: root, path, branch: request.branch, baseRef });\n\n // Checked here rather than only inside the session call: this is the\n // point where a checkout exists that nothing has recorded yet, which is\n // exactly the state an interrupted spawn used to leave behind.\n if (cancelled()) {\n const kept = await rollback();\n throw new DoomGitExpectedError(\n 'spawn_cancelled',\n `Cancelled before the session started.${kept}`,\n false,\n 'The worktree was removed; run it again when you are ready.',\n );\n }\n\n // Before the session, because the session composes the extensions this\n // repository's config names and those live in build output git does not\n // track. A worktree without it starts and dies on the first package it\n // cannot resolve. Nothing here can fail the spawn: a repository with no\n // build output to mirror simply has none.\n options?.onProgress?.('mirroring build output\\u2026');\n mirror(root, path);\n\n // The worktree exists from here on, so every later failure has to leave\n // it removed or recorded. An unrecorded directory on disk is the one\n // outcome with no way back: nothing would ever list it again.\n options?.onProgress?.('starting session\\u2026');\n let sessionId: string;\n try {\n const session = await requireSessionService().create({\n cwd: path,\n name: request.name ?? request.branch,\n parentSessionId: context.sessionId,\n sessionProvenance: 'worktree',\n ...(options?.signal === undefined ? {} : { signal: options.signal }),\n });\n sessionId = session.sessionId;\n } catch (error) {\n const kept = await rollback();\n if (cancelled()) {\n throw new DoomGitExpectedError(\n 'spawn_cancelled',\n `Cancelled while the session was starting.${kept}`,\n false,\n 'The worktree was removed; run it again when you are ready.',\n );\n }\n if (error instanceof HubUnavailableError) {\n throw new DoomGitExpectedError(\n 'hub_unavailable',\n `${error.message}${kept}`,\n true,\n 'Start the cockpit and try again.',\n );\n }\n throw error;\n }\n\n const record: WorktreeRecord = {\n version: WORKTREE_RECORD_VERSION,\n id,\n branch: request.branch,\n baseRef,\n path,\n repositoryRoot: root,\n sessionId,\n parentSessionId: context.sessionId,\n status: 'running',\n createdAt: now().toISOString(),\n };\n try {\n store.replace([...records, record]);\n } catch (error) {\n // A live session with no record is the worst outcome available here:\n // it runs, and no id anything can name points at it. The session goes\n // back too, rather than being left for someone to find by hand.\n await requireSessionService()\n .close(sessionId)\n .catch(() => undefined);\n const kept = await rollback();\n throw new DoomGitExpectedError(\n 'registry_write_failed',\n `The worktree was created but the registry could not be written (${(error as Error).message}).${kept}`,\n true,\n 'Check the registry file for permissions or free space, then try again.',\n );\n }\n return record;\n },\n\n async close(context, id, force) {\n const { root, store, records } = await resolve(context);\n const record = require(records, id);\n requireOwner(context, record, 'close');\n const dirtyFiles = fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];\n const refusal = refuseClose({ record, dirtyFiles, force });\n if (refusal !== undefined) {\n throw new DoomGitExpectedError('worktree_dirty', refusal, false, 'Commit the work, or pass force: true.');\n }\n // The session is stopped before the directory goes. Removing a checkout\n // while a process still holds files open in it is how a half-removed\n // worktree and a stale git administrative entry are made.\n await requireSessionService().close(record.sessionId);\n await git.removeWorktree({ repositoryRoot: root, path: record.path, force });\n store.replace(records.filter((entry) => entry.id !== id));\n return record;\n },\n\n async list(context) {\n const { store, records } = await resolve(context);\n const outcome = reconcile(records, probe);\n if (outcome.changed.length > 0) store.replace(outcome.records);\n return outcome.records;\n },\n\n async status(context, id) {\n const { records } = await resolve(context);\n const record = require(records, id);\n const dirtyFiles = fs.existsSync(record.path) ? await git.dirtyFiles(record.path) : [];\n return { record, dirtyFiles };\n },\n\n async merge(context, id, message) {\n const { root, records } = await resolve(context);\n const record = require(records, id);\n requireOwner(context, record, 'merge');\n // The merge lands in the parent, so the parent is what must be clean. A\n // dirty parent would mix the person's in-flight edits into a merge commit\n // they did not intend to make.\n const parentDirty = await git.dirtyFiles(root);\n if (parentDirty.length > 0) {\n throw new DoomGitExpectedError(\n 'worktree_dirty',\n `The parent checkout has ${String(parentDirty.length)} uncommitted file(s): ${parentDirty.slice(0, 10).join(', ')}.`,\n false,\n 'Commit or stash the parent checkout first.',\n );\n }\n await git.mergeBranch({\n repositoryRoot: root,\n branch: record.branch,\n ...(message === undefined ? {} : { message }),\n });\n return record;\n },\n\n /** Sends only between the parent session and its own worktree session. */\n async send(context, id, message) {\n if (Buffer.byteLength(message, 'utf8') > MAX_WORKTREE_MESSAGE_BYTES) {\n throw new DoomGitExpectedError(\n 'message_too_large',\n `A message may not exceed ${String(MAX_WORKTREE_MESSAGE_BYTES)} bytes.`,\n false,\n 'Send a shorter message.',\n );\n }\n const { records } = await resolve(context);\n const record = require(records, id);\n const peer = peerFor(context, record);\n requirePeerLive(peer.sessionId);\n requireMessageInbox().send(peer.sessionId, {\n version: 1,\n worktreeId: record.id,\n fromSessionId: context.sessionId,\n from: peer.party,\n text: message,\n sentAt: new Date().toISOString(),\n });\n },\n\n async messages(context, id) {\n const { records } = await resolve(context);\n const record = require(records, id);\n peerFor(context, record);\n return requireMessageInbox().receive(record.id);\n },\n\n async prune(context, dryRun) {\n const { root, store, records } = await resolve(context);\n const reconciled = reconcile(records, probe);\n const gitPaths = await git.listWorktreePaths(root);\n const dirtyByPath = new Map<string, readonly string[]>();\n for (const record of reconciled.records) {\n if (record.status === 'orphaned' && fs.existsSync(record.path)) {\n dirtyByPath.set(record.path, await git.dirtyFiles(record.path));\n }\n }\n const plan = planPrune({\n records: reconciled.records,\n gitPaths,\n worktreesRoot: worktreesRoot(deps.homeDir),\n dirtyByPath,\n });\n if (dryRun) {\n store.replace(reconciled.records);\n return plan;\n }\n for (const record of plan.remove) {\n await git.removeWorktree({ repositoryRoot: root, path: record.path, force: false });\n }\n // The checkout goes, so the branch it was made for goes with it. Safe\n // delete only: git keeps anything holding commits, and the plan says\n // which ones it kept rather than leaving the reader to notice later.\n for (const record of [...plan.remove, ...plan.forget]) {\n const deleted = await git.deleteBranch({ repositoryRoot: root, branch: record.branch }).catch(() => false);\n if (!deleted) plan.keptBranches.push(record.branch);\n }\n const dropped = new Set([...plan.remove, ...plan.forget].map((record) => record.id));\n store.replace(reconciled.records.filter((record) => !dropped.has(record.id)));\n return plan;\n },\n };\n}\n"],"mappings":"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"}
|