diff --git a/packages/cli/src/commands/handoff.test.ts b/packages/cli/src/commands/handoff.test.ts index 490a3fab..fe7e4561 100644 --- a/packages/cli/src/commands/handoff.test.ts +++ b/packages/cli/src/commands/handoff.test.ts @@ -196,15 +196,21 @@ describe("basou handoff generate", () => { expect(joinCalls(out)).toContain("sessions: 1"); }); - it("case 3: aggregates multiple sessions and unions related_files", async () => { + it("case 3: 直近の変更ファイル shows only the most recent session's related_files", async () => { const repo = await setupInitedRepo(); + // Older session — its files must NOT appear once a newer session supersedes it. await placeSession(repo, { id: SES("X02"), relatedFiles: ["src/a.ts"] }); - await placeSession(repo, { id: SES("X03"), relatedFiles: ["src/b.ts"] }); + // More recent session (later started_at) — this one wins. + await placeSession(repo, { + id: SES("X03"), + relatedFiles: ["src/b.ts"], + startedAt: "2026-05-08T12:00:00+09:00", + }); const out = captureStdout(); await doRunHandoffGenerate({}, { cwd: repo, nowProvider: () => FIXED_DATE }); expect(joinCalls(out)).toContain("sessions: 2"); const body = await readFile(basouPaths(repo).files.handoff, "utf8"); - expect(body).toContain("- src/a.ts"); + expect(body).not.toContain("- src/a.ts"); expect(body).toContain("- src/b.ts"); }); diff --git a/packages/core/src/handoff/handoff-renderer.test.ts b/packages/core/src/handoff/handoff-renderer.test.ts index e745ff67..ba40f2a7 100644 --- a/packages/core/src/handoff/handoff-renderer.test.ts +++ b/packages/core/src/handoff/handoff-renderer.test.ts @@ -250,12 +250,15 @@ describe("handoff-renderer", () => { expect(result.body).toContain(`from ${SHORT(id)}..${SHORT(id)}`); }); - it("case 3: aggregates related_files across sessions and dedups", async () => { + it("case 3: 直近の変更ファイル shows only the most recent session's related_files", async () => { const paths = await setupPaths(); + // Older session — its unique files must NOT appear once a newer session + // supersedes it. await placeSession(paths, { id: SES("X02"), relatedFiles: ["src/a.ts", "src/b.ts"], }); + // More recent session (later started_at) — this one wins. await placeSession(paths, { id: SES("X03"), relatedFiles: ["src/b.ts", "src/c.ts"], @@ -263,15 +266,13 @@ describe("handoff-renderer", () => { }); const result = await renderHandoff({ paths, nowIso: FIXED_NOW_ISO }); expect(result.sessionCount).toBe(2); - // dedup + sort asc, scoped to the 直近の変更ファイル section. The next-to- - // read section deliberately reuses `displayedFiles.slice(0, 3)`, so a - // global count would over-report by design. const recentSection = sliceSection(result.body, "## 直近の変更ファイル", "##"); - const idxA = recentSection.indexOf("- src/a.ts"); + // src/a.ts is only in the older session, so it is excluded; the newer + // session's files appear, sorted asc. + expect(recentSection).not.toContain("- src/a.ts"); const idxB = recentSection.indexOf("- src/b.ts"); const idxC = recentSection.indexOf("- src/c.ts"); - expect(idxA).toBeGreaterThanOrEqual(0); - expect(idxB).toBeGreaterThan(idxA); + expect(idxB).toBeGreaterThanOrEqual(0); expect(idxC).toBeGreaterThan(idxB); expect(recentSection.split("- src/b.ts\n").length - 1).toBe(1); }); diff --git a/packages/core/src/handoff/handoff-renderer.ts b/packages/core/src/handoff/handoff-renderer.ts index 3295e8f9..6d98d546 100644 --- a/packages/core/src/handoff/handoff-renderer.ts +++ b/packages/core/src/handoff/handoff-renderer.ts @@ -75,7 +75,7 @@ type TaskStatusChangedRecord = { * the spec's `handoff.md` sections in order: * * 1. `現在の状態`: latest live session (status not archived, source not import). - * 2. `直近の変更ファイル`: union of `related_files` across sessions, dedup + + * 2. `直近の変更ファイル`: the most recent session's `related_files`, dedup + * sorted asc + truncated to `relatedFilesLimit` (default 20). * 3. `直近の判断`: latest `decision_recorded` event (chronological). * 4. `未決事項`: pending-approval count + suspect-session count. @@ -202,16 +202,14 @@ export async function renderHandoff(input: HandoffRendererInput): Promise Date.parse(b.session.session.started_at) - Date.parse(a.session.session.started_at), )[0]; - // 「直近の変更ファイル」 collects related_files from live sessions only. - // Imported sessions are historical / cross-workspace and would otherwise - // pollute the "what changed recently" view with paths that don't belong - // to the current workspace's live work. - const allFiles = new Set(); - for (const e of entries) { - if (e.session.session.source.kind === "import") continue; - for (const f of e.session.session.related_files) allFiles.add(f); - } - const sortedFiles = [...allFiles].sort(); + // 「直近の変更ファイル」 shows the files touched by the single most recent + // session — the same session surfaced as 最終 session above — so the section + // reflects the latest activity rather than the whole history. Unioning every + // session's related_files turned this into a whole-history dump once + // transcript imports became the primary source, since each import carries a + // full day of file changes. + const latestFiles = latestSession?.session.session.related_files ?? []; + const sortedFiles = [...new Set(latestFiles)].sort(); const displayedFiles = sortedFiles.slice(0, limit); const overflow = Math.max(0, sortedFiles.length - limit);