Skip to content

Commit 04c71b7

Browse files
NikitasT2003claude
andcommitted
fix(setup): don't re-copy examples on setup re-runs
User report: `example-source.md` was being copied back into the workspace every time `thesis setup` ran. That meant a user who deleted the example on purpose had it restored uninvited. Root cause: Step 5 (Copy examples) treated every invocation as a first run. Non-interactive / quickstart modes auto-copied; the interactive prompt defaulted to Yes; the `_copy_examples` helper silently skipped existing files (so the re-copy only created files the user had since deleted, which is exactly the surprise they reported). Changes - setup() now captures `first_run = not .env.exists()` at entry. - Step 5 branching: * `--skip-examples` → always skip * `--with-examples` → always copy (new flag; explicit opt-in for re-runs) * `--overwrite-examples` → now implies copy too, so the flag isn't a silent no-op on re-runs * non-interactive / quickstart on a re-run → skip with a clear message naming the escape hatch * interactive on a re-run → prompt defaults to No and the wording says why ("you've run setup before; default is No so deleted examples don't come back") * first run everywhere → behaves as before (copy by default) Tests (1 new, 1 rewritten; 271 total passing) - test_second_run_skips_examples_by_default: locks in that a re-run without flags neither copies nor re-creates deleted examples, and the output names `--with-examples`. - test_second_run_with_examples_flag_copies_and_reports_already_present: explicit opt-in path still works. - Regression smoke verified manually: deleted research/raw/example-source.md, re-ran `thesis setup`, file stayed gone. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8579eff commit 04c71b7

2 files changed

Lines changed: 46 additions & 7 deletions

File tree

src/thesis_agent/cli.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ def setup(
223223
False, "--overwrite-examples",
224224
help="Replace example files in the workspace even if they already exist.",
225225
),
226+
with_examples: bool = typer.Option(
227+
False, "--with-examples",
228+
help="Copy examples even on a re-run of setup (by default examples are only offered on first run).",
229+
),
226230
quickstart: bool = typer.Option(
227231
False, "--quickstart", help="Accept sensible defaults for every prompt.",
228232
),
@@ -244,6 +248,10 @@ def setup(
244248
)
245249

246250
p = paths()
251+
# `.env` existing is our signal that setup has run here before — we don't
252+
# re-offer examples on re-runs so users who deleted them don't have them
253+
# come back uninvited. `--with-examples` forces the copy on a re-run.
254+
first_run = not p.env_file.exists()
247255

248256
try:
249257
# Step 1: Provider ---------------------------------------------------
@@ -348,14 +356,33 @@ def setup(
348356
_step(5, "Copy examples (optional)")
349357
if skip_examples:
350358
copy_ex = False
359+
elif with_examples or overwrite_examples:
360+
# Explicit opt-in — copy regardless of first-run status.
361+
# `--overwrite-examples` implies opt-in; otherwise it would be a
362+
# no-op on re-runs (which would be surprising).
363+
copy_ex = True
351364
elif non_interactive or quickstart:
352-
copy_ex = not skip_examples
365+
# Headless: only auto-copy on first run. Once the user has run
366+
# setup before (i.e. .env already exists), we assume they either
367+
# already accepted examples the first time OR chose to delete them.
368+
copy_ex = first_run
369+
if not copy_ex:
370+
console.print(
371+
" [dim]re-run detected — skipping examples. "
372+
"Pass [cyan]--with-examples[/] to copy them anyway.[/]"
373+
)
353374
else:
375+
prompt = (
376+
"Copy example sources + sample style essay? (recommended for first run)"
377+
if first_run
378+
else "Copy example sources again? (you've run setup before; "
379+
"default is No so deleted examples don't come back)"
380+
)
354381
try:
355382
copy_ex = _ask(
356383
questionary.confirm,
357-
"Copy example sources + sample style essay? (recommended for first run)",
358-
default=True,
384+
prompt,
385+
default=first_run,
359386
)
360387
except _Cancelled:
361388
copy_ex = False

tests/test_copy_examples.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,27 @@ def test_first_run_says_copied_N(self, ws: Path):
9797
# Should name at least one new file
9898
assert "new" in result.stdout
9999

100-
def test_second_run_says_already_present(self, ws: Path):
100+
def test_second_run_skips_examples_by_default(self, ws: Path):
101+
"""On re-run, we must NOT re-copy examples (user may have deleted
102+
them deliberately). The wizard should tell them what happened and
103+
how to force a copy."""
101104
self._run_setup()
102105
result = self._run_setup()
103106
assert result.exit_code == 0
104-
# Must NOT mislead with "copied 0"
107+
# Must not mislead with "copied 0"
108+
assert "copied 0" not in result.stdout
109+
# Must announce the skip + the escape hatch
110+
assert "re-run detected" in result.stdout or "skipping examples" in result.stdout
111+
assert "--with-examples" in result.stdout
112+
113+
def test_second_run_with_examples_flag_copies_and_reports_already_present(self, ws: Path):
114+
"""If user explicitly passes --with-examples on a re-run, we attempt
115+
the copy and report honestly (already-present since they're already there)."""
116+
self._run_setup()
117+
result = self._run_setup(["--with-examples"])
118+
assert result.exit_code == 0
105119
assert "copied 0" not in result.stdout
106-
# Must explain the real state
107120
assert "already in your workspace" in result.stdout
108-
# Must point to the escape hatch
109121
assert "--overwrite-examples" in result.stdout
110122

111123
def test_overwrite_flag_replaces_and_reports(self, ws: Path):

0 commit comments

Comments
 (0)