@@ -673,22 +673,19 @@ export class TrackerService {
673673 }
674674
675675 // Ensures the item's content files exist inside the worktree at
676- // <wt>/tracker/items/<slug>/. New items don't have any files yet; this is where
677- // the user-typed description (stashed on `index.sessions[slug].description` by
678- // createItem) gets drained into notes.md so the body lands in the worktree, not
679- // the project root. Pre-existing legacy main-project layouts are migrated and
680- // then the source dir is deleted to clean up project-root pollution from the
681- // old behaviour. No requirements.md stub is created — that file only appears
682- // once the requirements stage produces real content. Commits to the worktree
683- // branch so the seed survives a future worktree obliteration.
684- ensureItemFiles ( mainProjectPath : string , slug : string , worktreePath : string , _item ?: TrackerItem ) : void {
676+ // <wt>/tracker/items/<slug>/. New items have no files yet; this is where the
677+ // user-typed description (stashed on `index.sessions[slug].description` by
678+ // createItem) drains into notes.md so the body lands in the worktree, not the
679+ // project root. Pre-existing legacy main-project layouts are migrated and the
680+ // source dir is removed to clean up project-root pollution from the old
681+ // behaviour. Commits to the worktree branch so the seed survives a future
682+ // worktree obliteration.
683+ ensureItemFiles ( mainProjectPath : string , slug : string , worktreePath : string ) : void {
685684 const destDir = path . join ( worktreePath , 'tracker' , 'items' , slug ) ;
686685 ensureDirectory ( destDir ) ;
687686
688687 let wroteAnything = false ;
689688
690- // 1) Migrate from legacy locations into the worktree, then delete the source dir
691- // so the main project tree gets cleaned up. Highest-priority sources first.
692689 const legacySources = [
693690 path . join ( worktreePath , 'tracker' , slug ) ,
694691 ...this . findLegacyMainProjectDirs ( mainProjectPath , slug ) ,
@@ -701,52 +698,38 @@ export class TrackerService {
701698 fs . copyFileSync ( path . join ( src , file ) , destFile ) ;
702699 wroteAnything = true ;
703700 }
704- // The legacy worktree-internal source (`<wt>/tracker/<slug>/`) sits inside the
705- // worktree itself; deleting it is the same act as moving its contents up. The
706- // main-project legacy sources are real project-root pollution that we want gone.
707701 fs . rmSync ( src , { recursive : true , force : true } ) ;
708702 }
709703
710- // 2) Drain a user-typed description from the index into notes.md. createItem
711- // stashes the body here when no worktree exists; we only get to write it once.
712- const notesPath = path . join ( destDir , 'notes.md' ) ;
713- if ( ! fs . existsSync ( notesPath ) ) {
714- const description = this . readSessionDescription ( mainProjectPath , slug ) ;
715- if ( description ) {
716- fs . writeFileSync ( notesPath , `${ description } \n` ) ;
717- wroteAnything = true ;
704+ // Drain (and clear) any stashed description from the index in a single
705+ // read-modify-write. We always clear it once a worktree exists — even if
706+ // notes.md already came from legacy migration, the description has served
707+ // its purpose and shouldn't linger.
708+ if ( this . hasTracker ( mainProjectPath ) ) {
709+ const index = this . readIndex ( mainProjectPath ) ;
710+ const entry = index . sessions ?. [ slug ] ;
711+ if ( entry ?. description !== undefined ) {
712+ const notesPath = path . join ( destDir , 'notes.md' ) ;
713+ if ( ! fs . existsSync ( notesPath ) ) {
714+ fs . writeFileSync ( notesPath , `${ entry . description } \n` ) ;
715+ wroteAnything = true ;
716+ }
717+ const { description : _ , ...rest } = entry ;
718+ const sessions = { ...( index . sessions ?? { } ) } ;
719+ sessions [ slug ] = rest ;
720+ index . sessions = sessions ;
721+ writeJSONAtomic ( this . getIndexPath ( mainProjectPath ) , index ) ;
718722 }
719723 }
720- // Always clear the field once we've reached the worktree — even if notes.md
721- // already existed (e.g. legacy migration produced one), the description has
722- // served its purpose and shouldn't linger on the index.
723- this . clearSessionDescription ( mainProjectPath , slug ) ;
724724
725- // 3) Commit the seeded files only if we actually wrote something. Skipping the
725+ // Commit the seeded files only if we actually wrote something. Skipping the
726726 // empty-commit attempt avoids a couple of git fork+exec calls on every reattach.
727727 if ( ! wroteAnything ) return ;
728728 const relativeDestDir = path . relative ( worktreePath , destDir ) ;
729729 runCommandQuick ( [ 'git' , '-C' , worktreePath , 'add' , relativeDestDir ] ) ;
730730 runCommandQuick ( [ 'git' , '-C' , worktreePath , 'commit' , '-m' , `tracker: seed item files for ${ slug } ` ] ) ;
731731 }
732732
733- private readSessionDescription ( projectPath : string , slug : string ) : string | undefined {
734- if ( ! this . hasTracker ( projectPath ) ) return undefined ;
735- return this . readIndex ( projectPath ) . sessions ?. [ slug ] ?. description ;
736- }
737-
738- private clearSessionDescription ( projectPath : string , slug : string ) : void {
739- if ( ! this . hasTracker ( projectPath ) ) return ;
740- const index = this . readIndex ( projectPath ) ;
741- const entry = index . sessions ?. [ slug ] ;
742- if ( ! entry || entry . description === undefined ) return ;
743- const { description : _ , ...rest } = entry ;
744- const sessions = { ...( index . sessions ?? { } ) } ;
745- sessions [ slug ] = rest ;
746- index . sessions = sessions ;
747- writeJSONAtomic ( this . getIndexPath ( projectPath ) , index ) ;
748- }
749-
750733 private findLegacyMainProjectDirs ( projectPath : string , slug : string ) : string [ ] {
751734 const out : string [ ] = [ ] ;
752735 // Pre-refactor: items lived in `tracker/{backlog,implementation}/<slug>/`.
0 commit comments