Skip to content
This repository was archived by the owner on Jan 19, 2026. It is now read-only.

Commit 56aae63

Browse files
committed
refactor: export plans to plans/ folder when multiple files
- Single plan file exports to cwd directly - Multiple plan files (2+) export to plans/ folder - Plans folder only created when needed - Updated tests to verify new folder behavior
1 parent 6f8abd8 commit 56aae63

File tree

4 files changed

+138
-19
lines changed

4 files changed

+138
-19
lines changed

scripts/export_project_plans.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,35 @@ def main() -> int:
5959
print("No slugs found in any transcript files", file=sys.stderr)
6060
return 0
6161

62-
# 3. Copy plan files to project root
63-
plans_dir = Path.home() / ".claude" / "plans"
64-
copied = 0
62+
# 3. Collect valid plan files
63+
plans_source_dir = Path.home() / ".claude" / "plans"
64+
valid_files: list[tuple[str, Path]] = []
6565

6666
for slug in sorted(all_slugs):
67-
source_file = plans_dir / f"{slug}.md"
68-
dest_file = Path.cwd() / f"plan-{slug}.md"
67+
source_file = plans_source_dir / f"{slug}.md"
6968

7069
if not source_file.exists():
7170
print(
7271
f"Plan file not found for slug '{slug}': {source_file}", file=sys.stderr
7372
)
7473
continue
7574

75+
valid_files.append((slug, source_file))
76+
77+
# 4. Copy plan files (use plans/ folder only if more than one file)
78+
copied = 0
79+
use_plans_folder = len(valid_files) > 1
80+
plans_dest_dir = Path.cwd() / "plans"
81+
82+
if use_plans_folder and not plans_dest_dir.exists():
83+
plans_dest_dir.mkdir(parents=True)
84+
85+
for slug, source_file in valid_files:
86+
if use_plans_folder:
87+
dest_file = plans_dest_dir / f"plan-{slug}.md"
88+
else:
89+
dest_file = Path.cwd() / f"plan-{slug}.md"
90+
7691
try:
7792
shutil.copy2(source_file, dest_file)
7893
print(f"Copied: {dest_file}")

scripts/export_project_plans_with_timestamp.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,35 @@ def main() -> int:
5252
print("No slugs found in any transcript files", file=sys.stderr)
5353
return 0
5454

55-
# 3. Copy plan files to project root with timestamp prefix
56-
plans_dir = Path.home() / ".claude" / "plans"
57-
copied = 0
55+
# 3. Collect valid plan files
56+
plans_source_dir = Path.home() / ".claude" / "plans"
57+
valid_files: list[tuple[str, Path]] = []
5858

5959
for slug in sorted(all_slugs):
60-
source_file = plans_dir / f"{slug}.md"
60+
source_file = plans_source_dir / f"{slug}.md"
6161

6262
if not source_file.exists():
6363
print(
6464
f"Plan file not found for slug '{slug}': {source_file}", file=sys.stderr
6565
)
6666
continue
6767

68+
valid_files.append((slug, source_file))
69+
70+
# 4. Copy plan files (use plans/ folder only if more than one file)
71+
copied = 0
72+
use_plans_folder = len(valid_files) > 1
73+
plans_dest_dir = Path.cwd() / "plans"
74+
75+
if use_plans_folder and not plans_dest_dir.exists():
76+
plans_dest_dir.mkdir(parents=True)
77+
78+
for slug, source_file in valid_files:
6879
timestamp = get_file_timestamp(source_file)
69-
dest_file = Path.cwd() / f"{timestamp}-plan-{slug}.md"
80+
if use_plans_folder:
81+
dest_file = plans_dest_dir / f"{timestamp}-plan-{slug}.md"
82+
else:
83+
dest_file = Path.cwd() / f"{timestamp}-plan-{slug}.md"
7084

7185
try:
7286
shutil.copy2(source_file, dest_file)

tests/test_export_project_plans.py

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,40 @@ def test_invalid_transcript_directory(self) -> None:
6363

6464
self.assertEqual(result, 1)
6565

66-
def test_exports_multiple_plan_files_skipping_agent_transcripts(self) -> None:
66+
def test_single_plan_file_exported_to_cwd_not_plans_folder(self) -> None:
67+
"""When only one plan file exists, it should go to cwd, not plans/."""
68+
project_dir = self.tmpdir / "project"
69+
project_dir.mkdir()
70+
71+
home_dir = self.tmpdir / "home"
72+
plans_dir = home_dir / ".claude" / "plans"
73+
plans_dir.mkdir(parents=True)
74+
75+
transcript_dir = self.tmpdir / "transcripts"
76+
transcript_dir.mkdir()
77+
78+
(transcript_dir / "a.jsonl").write_text(
79+
json.dumps({"slug": "only"}), encoding="utf-8"
80+
)
81+
(plans_dir / "only.md").write_text("single plan", encoding="utf-8")
82+
83+
with mock.patch.dict(
84+
os.environ, {"TRANSCRIPT_DIR": str(transcript_dir)}, clear=True
85+
):
86+
with mock.patch("pathlib.Path.home", return_value=home_dir):
87+
with mock.patch("pathlib.Path.cwd", return_value=project_dir):
88+
result = export_project_plans.main()
89+
90+
self.assertEqual(result, 0)
91+
# Single file should be in cwd, not plans/ folder
92+
self.assertTrue((project_dir / "plan-only.md").exists())
93+
self.assertFalse((project_dir / "plans").exists())
94+
self.assertEqual(
95+
(project_dir / "plan-only.md").read_text(encoding="utf-8"), "single plan"
96+
)
97+
98+
def test_multiple_plan_files_exported_to_plans_folder(self) -> None:
99+
"""When 2+ plan files exist, they should go to plans/ folder."""
67100
project_dir = self.tmpdir / "project"
68101
project_dir.mkdir()
69102

@@ -100,13 +133,18 @@ def test_exports_multiple_plan_files_skipping_agent_transcripts(self) -> None:
100133
result = export_project_plans.main()
101134

102135
self.assertEqual(result, 0)
103-
exported_one = project_dir / "plan-one.md"
104-
exported_two = project_dir / "plan-two.md"
136+
# Multiple files should be in plans/ folder
137+
self.assertTrue((project_dir / "plans").is_dir())
138+
exported_one = project_dir / "plans" / "plan-one.md"
139+
exported_two = project_dir / "plans" / "plan-two.md"
105140

106141
self.assertTrue(exported_one.exists())
107142
self.assertTrue(exported_two.exists())
108143
self.assertEqual(exported_one.read_text(encoding="utf-8"), "plan one")
109144
self.assertEqual(exported_two.read_text(encoding="utf-8"), "plan two")
145+
# Should NOT be in project root
146+
self.assertFalse((project_dir / "plan-one.md").exists())
147+
self.assertFalse((project_dir / "plan-two.md").exists())
110148

111149
def test_no_slugs_in_any_file_returns_zero(self) -> None:
112150
project_dir = self.tmpdir / "project"
@@ -186,10 +224,10 @@ def mock_copy2(src, dst):
186224

187225
# Should return 0 (logs error but continues)
188226
self.assertEqual(result, 0)
189-
# Succeed file should be copied
190-
self.assertTrue((project_dir / "plan-succeed.md").exists())
227+
# Succeed file should be copied (in plans/ folder since 2 valid files)
228+
self.assertTrue((project_dir / "plans" / "plan-succeed.md").exists())
191229
# Fail file should NOT be copied
192-
self.assertFalse((project_dir / "plan-fail.md").exists())
230+
self.assertFalse((project_dir / "plans" / "plan-fail.md").exists())
193231

194232

195233
if __name__ == "__main__":

tests/test_export_project_plans_with_timestamp.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ def test_invalid_transcript_directory(self) -> None:
4343
result = export_project_plans_with_timestamp.main()
4444
self.assertEqual(result, 1)
4545

46-
def test_exports_with_correct_timestamp_prefix(self) -> None:
46+
def test_single_file_exports_to_cwd_not_plans_folder(self) -> None:
47+
"""When only one plan file exists, it should go to cwd, not plans/."""
4748
project_dir = self.tmpdir / "project"
4849
project_dir.mkdir()
4950

@@ -72,10 +73,60 @@ def test_exports_with_correct_timestamp_prefix(self) -> None:
7273
result = export_project_plans_with_timestamp.main()
7374

7475
self.assertEqual(result, 0)
76+
# Single file should be in cwd, not plans/ folder
7577
exported = project_dir / f"{expected_prefix}-plan-one.md"
7678
self.assertTrue(exported.exists())
79+
self.assertFalse((project_dir / "plans").exists())
7780
self.assertEqual(exported.read_text(encoding="utf-8"), "plan one")
7881

82+
def test_multiple_files_export_to_plans_folder(self) -> None:
83+
"""When 2+ plan files exist, they should go to plans/ folder."""
84+
project_dir = self.tmpdir / "project"
85+
project_dir.mkdir()
86+
87+
home_dir = self.tmpdir / "home"
88+
plans_dir = home_dir / ".claude" / "plans"
89+
plans_dir.mkdir(parents=True)
90+
91+
transcript_dir = self.tmpdir / "transcripts"
92+
transcript_dir.mkdir()
93+
(transcript_dir / "a.jsonl").write_text(
94+
json.dumps({"slug": "one"}) + "\n" + json.dumps({"slug": "two"}),
95+
encoding="utf-8",
96+
)
97+
98+
plan_one = plans_dir / "one.md"
99+
plan_two = plans_dir / "two.md"
100+
plan_one.write_text("plan one", encoding="utf-8")
101+
plan_two.write_text("plan two", encoding="utf-8")
102+
103+
ts_one = 1735689600
104+
ts_two = 1735689700
105+
os.utime(plan_one, (ts_one, ts_one))
106+
os.utime(plan_two, (ts_two, ts_two))
107+
prefix_one = datetime.fromtimestamp(ts_one).strftime("%Y%m%d-%H:%M:%S")
108+
prefix_two = datetime.fromtimestamp(ts_two).strftime("%Y%m%d-%H:%M:%S")
109+
110+
with mock.patch.dict(
111+
os.environ, {"TRANSCRIPT_DIR": str(transcript_dir)}, clear=True
112+
):
113+
with mock.patch("pathlib.Path.home", return_value=home_dir):
114+
with mock.patch("pathlib.Path.cwd", return_value=project_dir):
115+
result = export_project_plans_with_timestamp.main()
116+
117+
self.assertEqual(result, 0)
118+
# Multiple files should be in plans/ folder
119+
self.assertTrue((project_dir / "plans").is_dir())
120+
exported_one = project_dir / "plans" / f"{prefix_one}-plan-one.md"
121+
exported_two = project_dir / "plans" / f"{prefix_two}-plan-two.md"
122+
self.assertTrue(exported_one.exists())
123+
self.assertTrue(exported_two.exists())
124+
self.assertEqual(exported_one.read_text(encoding="utf-8"), "plan one")
125+
self.assertEqual(exported_two.read_text(encoding="utf-8"), "plan two")
126+
# Should NOT be in project root
127+
self.assertFalse(any(project_dir.glob("*-plan-one.md")))
128+
self.assertFalse(any(project_dir.glob("*-plan-two.md")))
129+
79130
def test_skips_agent_transcripts(self) -> None:
80131
project_dir = self.tmpdir / "project"
81132
project_dir.mkdir()
@@ -166,8 +217,9 @@ def mock_copy2(src, dst):
166217
result = export_project_plans_with_timestamp.main()
167218

168219
self.assertEqual(result, 0)
169-
self.assertFalse(any(project_dir.glob("*-plan-fail.md")))
170-
self.assertTrue(any(project_dir.glob("*-plan-succeed.md")))
220+
# Files should be in plans/ folder since 2 valid files
221+
self.assertFalse(any((project_dir / "plans").glob("*-plan-fail.md")))
222+
self.assertTrue(any((project_dir / "plans").glob("*-plan-succeed.md")))
171223

172224

173225
if __name__ == "__main__":

0 commit comments

Comments
 (0)