@@ -1063,6 +1063,66 @@ describe("LifecycleService", () => {
10631063 expect ( archiveState . entries [ 0 ] ?. path ) . toBe ( join ( repoRoot , "__worktrees" , "feature-archive" ) ) ;
10641064 } ) ;
10651065
1066+ it ( "updates and clears a worktree label in metadata and runtime state" , async ( ) => {
1067+ const repoRoot = await initRepo ( ) ;
1068+ const runtime = new ProjectRuntime ( ) ;
1069+ const tmux = new FakeTmuxGateway ( ) ;
1070+ const lifecycle = makeLifecycleService ( repoRoot , tmux , runtime ) ;
1071+
1072+ const worktreePath = join ( repoRoot , "__worktrees" , "feature-label" ) ;
1073+ await lifecycle . createWorktree ( { branch : "feature-label" } ) ;
1074+ const gitDir = new BunGitGateway ( ) . resolveWorktreeGitDir ( worktreePath ) ;
1075+ const paths = getWorktreeStoragePaths ( gitDir ) ;
1076+ await Bun . write ( paths . runtimeEnvPath , "runtime-marker\n" ) ;
1077+ await Bun . write ( paths . controlEnvPath , "control-marker\n" ) ;
1078+ const allocatedPorts = { ...( await readWorktreeMeta ( gitDir ) ) ?. allocatedPorts } ;
1079+ const labeled = await lifecycle . setWorktreeLabel ( "feature-label" , " Search ranking " ) ;
1080+
1081+ expect ( labeled ) . toEqual ( { label : "Search ranking" } ) ;
1082+ expect ( ( await readWorktreeMeta ( gitDir ) ) ?. label ) . toBe ( "Search ranking" ) ;
1083+ expect ( runtime . getWorktreeByBranch ( "feature-label" ) ?. label ) . toBe ( "Search ranking" ) ;
1084+ expect ( await Bun . file ( paths . runtimeEnvPath ) . text ( ) ) . toBe ( "runtime-marker\n" ) ;
1085+ expect ( await Bun . file ( paths . controlEnvPath ) . text ( ) ) . toBe ( "control-marker\n" ) ;
1086+ expect ( ( await readWorktreeMeta ( gitDir ) ) ?. allocatedPorts ) . toEqual ( allocatedPorts ) ;
1087+
1088+ const cleared = await lifecycle . setWorktreeLabel ( "feature-label" , "" ) ;
1089+
1090+ expect ( cleared ) . toEqual ( { label : null } ) ;
1091+ expect ( ( await readWorktreeMeta ( gitDir ) ) ?. label ) . toBeUndefined ( ) ;
1092+ expect ( runtime . getWorktreeByBranch ( "feature-label" ) ?. label ) . toBeNull ( ) ;
1093+ expect ( await Bun . file ( paths . runtimeEnvPath ) . text ( ) ) . toBe ( "runtime-marker\n" ) ;
1094+ expect ( await Bun . file ( paths . controlEnvPath ) . text ( ) ) . toBe ( "control-marker\n" ) ;
1095+ expect ( ( await readWorktreeMeta ( gitDir ) ) ?. allocatedPorts ) . toEqual ( allocatedPorts ) ;
1096+ } ) ;
1097+
1098+ it ( "rejects labeling unmanaged worktrees without creating metadata" , async ( ) => {
1099+ const repoRoot = await initRepo ( ) ;
1100+ const runtime = new ProjectRuntime ( ) ;
1101+ const tmux = new FakeTmuxGateway ( ) ;
1102+ const lifecycle = makeLifecycleService ( repoRoot , tmux , runtime ) ;
1103+ const git = new BunGitGateway ( ) ;
1104+ const worktreePath = join ( repoRoot , "__worktrees" , "feature-unmanaged-label" ) ;
1105+
1106+ git . createWorktree ( {
1107+ repoRoot,
1108+ worktreePath,
1109+ branch : "feature-unmanaged-label" ,
1110+ mode : "new" ,
1111+ baseBranch : "main" ,
1112+ } ) ;
1113+ const gitDir = git . resolveWorktreeGitDir ( worktreePath ) ;
1114+ const paths = getWorktreeStoragePaths ( gitDir ) ;
1115+
1116+ await expect ( lifecycle . setWorktreeLabel ( "feature-unmanaged-label" , "Search ranking" ) )
1117+ . rejects . toMatchObject ( {
1118+ status : 409 ,
1119+ message : "Worktree feature-unmanaged-label has no managed metadata to label" ,
1120+ } ) ;
1121+ expect ( await Bun . file ( paths . metaPath ) . exists ( ) ) . toBe ( false ) ;
1122+ expect ( await Bun . file ( paths . runtimeEnvPath ) . exists ( ) ) . toBe ( false ) ;
1123+ expect ( await Bun . file ( paths . controlEnvPath ) . exists ( ) ) . toBe ( false ) ;
1124+ } ) ;
1125+
10661126 it ( "creates a managed docker worktree through the container runtime path" , async ( ) => {
10671127 const repoRoot = await initRepo ( ) ;
10681128 const runtime = new ProjectRuntime ( ) ;
0 commit comments