@@ -240,12 +240,46 @@ export const listTmuxPanes = (
240240 }
241241 } )
242242
243+ // CHANGE: shared session attach logic extracted to avoid code duplication
244+ // WHY: attachTmux and attachTmuxFromProject share identical session management;
245+ // duplicate code triggers vibecode-linter DUPLICATE detection
246+ // PURITY: SHELL
247+ // EFFECT: Effect<void, CommandFailedError | PlatformError, CommandExecutor>
248+ // INVARIANT: tmux session name is deterministic; old layout is recreated
249+ // COMPLEXITY: O(1)
250+ type TmuxSessionParams = {
251+ readonly session : string
252+ readonly repoDisplayName : string
253+ readonly statusRight : string
254+ readonly sshCommand : string
255+ readonly containerName : string
256+ }
257+
258+ const attachOrRecreateSession = (
259+ params : TmuxSessionParams
260+ ) : Effect . Effect < void , CommandFailedError | PlatformError , CommandExecutor . CommandExecutor > =>
261+ Effect . gen ( function * ( _ ) {
262+ const hasSessionCode = yield * _ ( runTmuxExitCode ( [ "has-session" , "-t" , params . session ] ) )
263+
264+ if ( hasSessionCode === 0 ) {
265+ const existingLayout = yield * _ ( readLayoutVersion ( params . session ) )
266+ if ( existingLayout === layoutVersion ) {
267+ yield * _ ( runTmux ( [ "attach" , "-t" , params . session ] ) )
268+ return
269+ }
270+ yield * _ ( Effect . logWarning ( `tmux session ${ params . session } uses an old layout; recreating.` ) )
271+ yield * _ ( runTmux ( [ "kill-session" , "-t" , params . session ] ) )
272+ }
273+
274+ yield * _ ( createLayout ( params . session ) )
275+ yield * _ ( configureSession ( params . session , params . repoDisplayName , params . statusRight ) )
276+ yield * _ ( setupPanes ( params . session , params . sshCommand , params . containerName ) )
277+ yield * _ ( runTmux ( [ "attach" , "-t" , params . session ] ) )
278+ } )
279+
243280// CHANGE: attach a tmux workspace for a docker-git project
244281// WHY: provide multi-pane terminal layout for sandbox work
245282// QUOTE(ТЗ): "окей Давай подключим tmux"
246- // REF: user-request-2026-02-02-tmux
247- // SOURCE: n/a
248- // FORMAT THEOREM: forall p: attach(p) -> tmux(p)
249283// PURITY: SHELL
250284// EFFECT: Effect<void, CommandFailedError | DockerCommandError | ConfigNotFoundError | ConfigDecodeError | FileExistsError | PortProbeError | PlatformError, CommandExecutor | FileSystem | Path>
251285// INVARIANT: tmux session name is deterministic from repo url
@@ -270,25 +304,14 @@ export const attachTmux = (
270304 const sshCommand = buildSshCommand ( template , sshKey )
271305 const repoDisplayName = formatRepoDisplayName ( template . repoUrl )
272306 const refLabel = formatRepoRefLabel ( template . repoRef )
273- const statusRight =
274- `SSH: ${ template . sshUser } @localhost:${ template . sshPort } | Repo: ${ repoDisplayName } | Ref: ${ refLabel } | Status: Running`
275- const session = `dg-${ deriveRepoSlug ( template . repoUrl ) } `
276- const hasSessionCode = yield * _ ( runTmuxExitCode ( [ "has-session" , "-t" , session ] ) )
277-
278- if ( hasSessionCode === 0 ) {
279- const existingLayout = yield * _ ( readLayoutVersion ( session ) )
280- if ( existingLayout === layoutVersion ) {
281- yield * _ ( runTmux ( [ "attach" , "-t" , session ] ) )
282- return
283- }
284- yield * _ ( Effect . logWarning ( `tmux session ${ session } uses an old layout; recreating.` ) )
285- yield * _ ( runTmux ( [ "kill-session" , "-t" , session ] ) )
286- }
287-
288- yield * _ ( createLayout ( session ) )
289- yield * _ ( configureSession ( session , repoDisplayName , statusRight ) )
290- yield * _ ( setupPanes ( session , sshCommand , template . containerName ) )
291- yield * _ ( runTmux ( [ "attach" , "-t" , session ] ) )
307+ yield * _ ( attachOrRecreateSession ( {
308+ session : `dg-${ deriveRepoSlug ( template . repoUrl ) } ` ,
309+ repoDisplayName,
310+ statusRight :
311+ `SSH: ${ template . sshUser } @localhost:${ template . sshPort } | Repo: ${ repoDisplayName } | Ref: ${ refLabel } | Status: Running` ,
312+ sshCommand,
313+ containerName : template . containerName
314+ } ) )
292315 } )
293316
294317// CHANGE: attach tmux from API project details without local filesystem access
@@ -317,23 +340,12 @@ export const attachTmuxFromProject = (
317340 Effect . gen ( function * ( _ ) {
318341 const repoDisplayName = formatRepoDisplayName ( project . repoUrl )
319342 const refLabel = formatRepoRefLabel ( project . repoRef )
320- const statusRight =
321- `SSH: ${ project . sshUser } @localhost:${ project . sshPort } | Repo: ${ repoDisplayName } | Ref: ${ refLabel } | Status: Running`
322- const session = `dg-${ deriveRepoSlug ( project . repoUrl ) } `
323- const hasSessionCode = yield * _ ( runTmuxExitCode ( [ "has-session" , "-t" , session ] ) )
324-
325- if ( hasSessionCode === 0 ) {
326- const existingLayout = yield * _ ( readLayoutVersion ( session ) )
327- if ( existingLayout === layoutVersion ) {
328- yield * _ ( runTmux ( [ "attach" , "-t" , session ] ) )
329- return
330- }
331- yield * _ ( Effect . logWarning ( `tmux session ${ session } uses an old layout; recreating.` ) )
332- yield * _ ( runTmux ( [ "kill-session" , "-t" , session ] ) )
333- }
334-
335- yield * _ ( createLayout ( session ) )
336- yield * _ ( configureSession ( session , repoDisplayName , statusRight ) )
337- yield * _ ( setupPanes ( session , project . sshCommand , project . containerName ) )
338- yield * _ ( runTmux ( [ "attach" , "-t" , session ] ) )
343+ yield * _ ( attachOrRecreateSession ( {
344+ session : `dg-${ deriveRepoSlug ( project . repoUrl ) } ` ,
345+ repoDisplayName,
346+ statusRight :
347+ `SSH: ${ project . sshUser } @localhost:${ project . sshPort } | Repo: ${ repoDisplayName } | Ref: ${ refLabel } | Status: Running` ,
348+ sshCommand : project . sshCommand ,
349+ containerName : project . containerName
350+ } ) )
339351 } )
0 commit comments