Skip to content

Commit 8caa841

Browse files
agent-era-aiclaude
andcommitted
refactor(tracker): inline description drain, drop unused param
Fuse readSessionDescription + clearSessionDescription into one read-modify- write inside ensureItemFiles. Each cold-path call now reads the index at most once and only writes when a description was actually stashed; old shape did two reads (one always wasted) and one wasted write per attach. Drop the now-unused TrackerItem parameter from ensureItemFiles; readItem already reads the title from index.sessions[slug].title. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c92ad96 commit 8caa841

2 files changed

Lines changed: 28 additions & 45 deletions

File tree

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ function AppContent() {
253253
let worktree = worktrees.find(wt => wt.project === project.name && wt.feature === item.slug) || null;
254254
if (!worktree) worktree = await recreateImplementWorktree(project.name, item.slug);
255255
if (!worktree) return null;
256-
tracker.ensureItemFiles(project.path, item.slug, worktree.path, item);
256+
tracker.ensureItemFiles(project.path, item.slug, worktree.path);
257257
return worktree;
258258
};
259259

src/services/TrackerService.ts

Lines changed: 27 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)