Skip to main content

SmartNotes File Manager Capstone

If you're new to programming

This is a timed challenge with no step-by-step instructions. You drive the entire TDG cycle: design the class, write the tests, prompt AI, verify, and debug. The <details> blocks have hints if you get stuck. You can also reference the functions from Lessons 1-4 as building blocks.

If you've coded before

25 minutes. Design a FileManager class that wraps all three formats (Markdown, JSON, CSV) behind a unified interface. Write tests first, generate the implementation, verify the full stack. The mastery gate is not just "tests pass" but "the class handles errors gracefully and the code is reviewable."

Emma sets the timer. "You have built individual read and write functions for three formats. Now unify them. One class. One interface. All three formats." She closes her laptop. "Twenty-five minutes."


The Problem

SmartNotes needs a FileManager class that handles all file I/O through a single interface. Instead of calling six different functions, you call methods on one object:

fm = FileManager(data_dir=Path("smartnotes_data"))

# Save in any format
fm.save_json(notes)
fm.save_csv(notes)
fm.save_markdown(notes)

# Load from any format
notes = fm.load_json()
notes = fm.load_csv()

# Pipeline
fm.export_tagged("python", format="markdown")

Your deliverables:

FilePurpose
file_manager.pyFileManager class stub with types and docstrings
test_file_manager.py10+ tests covering all methods and error cases
tdg_journal.mdDebugging journal documenting your TDG cycle

Start the timer.


Step 1: Specify (5 minutes)

Open file_manager.py. Design the class.

Questions to answer before you type:

  1. Constructor: What does FileManager need to know? At minimum, a base directory where files are stored.
  2. Methods: What operations does it support? At least: save_json, load_json, save_csv, load_csv, save_markdown, export_tagged.
  3. Return types: Load methods return list[Note]. Save methods return None or Path. Export methods return int (count).
  4. Error handling: What happens when loading a missing file? Return empty list or raise?

Write the class stub. Every method body is .... Every method has a typed signature and a docstring.

Hint: class structure
from pathlib import Path
from dataclasses import dataclass, field


@dataclass
class Note:
title: str
body: str
word_count: int
author: str = "Anonymous"
is_draft: bool = True
tags: list[str] = field(default_factory=list)


class FileManager:
"""Manages SmartNotes file I/O across JSON, CSV, and Markdown formats.

All files are stored relative to the data_dir provided at construction.
- JSON: data_dir/notebook.json
- CSV: data_dir/notebook.csv
- Markdown: data_dir/markdown/ (one file per note)
"""

def __init__(self, data_dir: Path) -> None:
...

def save_json(self, notes: list[Note]) -> None:
"""Save notes to notebook.json in the data directory."""
...

def load_json(self) -> list[Note]:
"""Load notes from notebook.json. Returns empty list if file missing."""
...

def save_csv(self, notes: list[Note]) -> None:
"""Save notes to notebook.csv in the data directory."""
...

def load_csv(self) -> list[Note]:
"""Load notes from notebook.csv. Returns empty list if file missing."""
...

def save_markdown(self, notes: list[Note]) -> int:
"""Export each note as a Markdown file. Returns count of files written."""
...

def export_tagged(self, tag: str, format: str = "markdown") -> int:
"""Load from JSON, filter by tag, export in given format. Returns count."""
...

Run uv run pyright file_manager.py. It should pass with zero errors. Fix any type issues before writing tests.


Step 2: Test (7 minutes)

Open test_file_manager.py. Write at least 10 tests.

Think about what each method should do, including edge cases:

  • Does save_json create the directory if it does not exist?
  • Does load_json return an empty list when the file is missing?
  • Does the JSON round trip preserve all fields (title, body, word_count, author, is_draft, tags)?
  • Does save_csv and load_csv handle tags correctly (list to comma-separated string and back)?
  • Does save_markdown create one file per note?
  • Does export_tagged filter correctly?
  • Does export_tagged return 0 for a tag that no note has?
Hint: test setup with tmp_path

Use pytest's tmp_path fixture so each test gets a fresh temporary directory:

import pytest
from pathlib import Path
from file_manager import FileManager, Note


@pytest.fixture
def sample_notes() -> list[Note]:
return [
Note("Python Tips", "Learn basics", 2, "James", True, ["python", "beginner"]),
Note("Debugging", "Fix errors", 2, "James", False, ["python", "debug"]),
Note("Cooking", "Boil water", 2, "Emma", False, ["cooking"]),
]


@pytest.fixture
def fm(tmp_path: Path) -> FileManager:
return FileManager(data_dir=tmp_path)


def test_save_and_load_json(fm: FileManager, sample_notes: list[Note]) -> None:
fm.save_json(sample_notes)
loaded = fm.load_json()
assert len(loaded) == 3
assert loaded[0].title == "Python Tips"


def test_load_json_missing_file(fm: FileManager) -> None:
loaded = fm.load_json()
assert loaded == []

Run uv run pytest test_file_manager.py -v. Every test should FAIL (RED). If any passes, your stub is not returning ... or your test is not calling the right method.


Step 3: Generate (3 minutes)

Open Claude Code and prompt:

Implement all methods in FileManager in file_manager.py
so that every test in test_file_manager.py passes.
Do not modify the test file.

The stub has the types and docstrings. The tests define correctness. The AI has everything it needs.


Step 4: Verify (3 minutes)

Run the verification stack:

uv run ruff check file_manager.py
uv run pyright file_manager.py
uv run pytest test_file_manager.py -v
OutcomeWhat to do
All GREENMove to Step 6
Some REDMove to Step 5
Pyright errorsRe-prompt: "Fix the type errors. Keep the same logic."

Step 5: Debug (5 minutes)

If tests failed, apply the debugging loop. You can type /debug in Claude Code to walk through each step:

  1. Read the failure message. Which method failed? What was expected vs. actual?
  2. Classify the bug. Is it a type error, logic error, or I/O error?
  3. Fix. Re-prompt with the failure output or fix manually.
  4. Verify again. Repeat until GREEN.
Hint: common FileManager failures
  • CSV tags not round-tripping: The save method converts ["python", "debug"] to "python, debug", but the load method does not split it back. Check the note_from_csv_row conversion.
  • Markdown count wrong: save_markdown might not count correctly if mkdir fails silently.
  • export_tagged returning wrong count: The method might load from the wrong file or not filter at all.
  • Directory not created: save_json might fail if data_dir does not exist. Check mkdir(parents=True, exist_ok=True).

Document each failure in tdg_journal.md:

# TDG Cycle Journal: FileManager

## Iteration 1
- Prompt: "Implement all methods in FileManager..."
- Result: 7/12 tests passed
- Failures:
- test_csv_round_trip: tags not split back to list
- test_markdown_count: returned None instead of int
- ...
- Fix: Re-prompted with failure output

## Iteration 2
- Result: 12/12 tests passed

Step 6: Read (2 minutes)

All tests pass. Apply PRIMM: predict before you verify.

Pick an input NOT in your test suite. For example: a note with an empty title. What does save_markdown do with a filename of ".md"? Or a note with tags containing commas: tags=["rock, paper"]. Does the CSV format break?

Check the generated code for hardcoded paths. Does it always use self.data_dir or does it hardcode Path("smartnotes_data")?


Try With AI

Opening Claude Code

If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.

Prompt 1: Review the FileManager Design

Here is my FileManager class:

[paste file_manager.py]

Review the class design. Is there a better way to
organize the methods? Should save/load use an enum
for format instead of separate methods? What are
the tradeoffs?

What you're learning: Class design involves tradeoffs between simplicity (separate methods) and flexibility (format parameter). The AI presents both approaches and you evaluate which fits your use case.

Prompt 2: What If the Disk Is Full?

My FileManager writes files to disk. What happens if
the disk is full? Which error does Python raise? Show
me how to catch it and return a meaningful error message
from each save method.

What you're learning: Production code handles infrastructure failures. Disk full, permission denied, and network errors are real scenarios. You are moving beyond "does the logic work" to "does it survive real conditions."

Prompt 3: Rate My TDG Cycle

I just completed a TDG cycle on a FileManager class.
Here is what I did:

1. Wrote class stub with 7 methods from a problem statement
2. Wrote [N] tests covering save, load, round trip, errors
3. Prompted AI to implement
4. [describe: passed first try / needed N re-prompts]
5. Reviewed with PRIMM: tested [describe input]
6. Checked for hardcoded values: [found / not found]

Rate my cycle. What should I improve next time?

What you're learning: You are evaluating your own process. The AI rates your TDG workflow, not the code. Building this self-assessment habit makes you faster with every cycle.


James looks at his FileManager class. Seven methods, twelve tests, two TDG iterations. JSON, CSV, and Markdown behind one interface. He runs a quick demo:

fm = FileManager(data_dir=Path("smartnotes_data"))
fm.save_json(notes)
fm.save_csv(notes)
fm.save_markdown(notes)
loaded = fm.load_json()
print(f"Saved and loaded {len(loaded)} notes across 3 formats")

Three formats, one object, one line per operation.

"At the warehouse," he says, "we had a receiving desk. Every incoming shipment went to the same desk regardless of whether it arrived by truck, courier, or hand delivery. The desk handled everything: scan the barcode, check the manifest, route to the right shelf. This class is the receiving desk for data."

Emma glances at the test output. All green. "What changed between this capstone and the search capstone in Chapter 57?"

James thinks. "Scale. The search capstone was one function with one purpose. This is a class with seven methods that share state through self.data_dir. The tests are more complex because methods interact: save_json and load_json must agree on the file format. The TDG cycle is the same, but the specification is bigger."

"Bigger specification, same discipline," Emma says. "Stub, test, generate, verify, debug. It scales."

She looks at his project folder. file_manager.py, test_file_manager.py, the Note class, the search functions from earlier chapters. Everything in one directory.

"How many Python files do you have now?"

James counts. "Eight. Maybe nine."

"And they are all in one folder. One flat directory with no organization. Every import goes to the same place. Every function lives next to every other function."

"That works, does it not?"

"For nine files. Not for ninety. Not for a project someone else needs to navigate. Chapter 63 fixes this: modules and packages. You split your code the same way you split your data."