Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
29 changes: 20 additions & 9 deletions .claude/commands/new-pattern.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
---
description: Scaffold a new pattern unit under patterns/<group>/<slug>
description: Scaffold a new pattern module under patterns/<group>/<slug>
argument-hint: <group>/<slug> "Pattern Name"
---

Scaffold a new pattern unit for $ARGUMENTS.
Scaffold a new pattern module for $ARGUMENTS.

1. Validate the group is one of: principle, python, creational, structural, behavioral, modern.
Refuse anything else.
2. Create `patterns/<group>/<slug>/` with the exact template from CLAUDE.md:
README.md (frontmatter with `id: <group>/<slug>`, all schema keys present,
`verdict:` left as `use-with-care` with a `TODO` caveat), empty-but-importable
`__init__.py`, and stub `naive.py`, `pythonic.py`, `real_world.py` each with a
typed `main() -> None` and script guard, plus `tests/test_<slug>.py` with one
failing `test_todo` marked `xfail(reason="unit not yet written")`.
3. Run `make check` and report the result. Do not write the actual pattern content —
2. Create `patterns/<group>/<slug>/` with the exact module template from CLAUDE.md:
- `README.md` — frontmatter with `id: <group>/<slug>`, all schema keys present,
`verdict:` left as `use-with-care` with a `TODO` caveat; a ~10-line front door
mapping the folders.
- `__init__.py` and `pattern/__init__.py` holding ONLY as-alias re-export
lines (`from .pattern.<slug> import X as X` / `from .<slug> import X as X`
— no docstrings, no `__all__`); `pattern/<slug>.py` with a typed stub.
- `docs/fundamentals.md`, `docs/implementation.md`, `docs/examples.md` — each a
heading plus a `TODO` line naming what belongs there (classic-form contrast in
fundamentals; never use the word "naive").
- `examples/demo/main.py` with a typed `main() -> None` + script guard
that imports from `...pattern`. Create NO other `__init__.py` — empty ones
are banned (PEP 420 namespace packages); the loader rejects them.
- `tests/test_<slug>.py` with one failing `test_todo` marked
`xfail(reason="unit not yet written")`.
3. Run `make check` and report the result. Note: the catalog loader will fail the
unit until the docs files, a runnable example importing `pattern/`, and real
tests exist — that is the point. Do not write the actual pattern content —
scaffolding only.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ uv.lock

# oh-my-claudecode runtime state
.omc/

# local working files (plans, research briefs)
.cache/

# agent worktrees
.claude/worktrees/
70 changes: 48 additions & 22 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,61 @@
# Agent instructions — python-design-patterns

Companion catalog to [python-patterns.guide](https://python-patterns.guide/): every design
pattern as runnable, tested, typed Python — plus an MCP server (`src/design_patterns_mcp/`)
that serves the catalog to agents.
pattern as a self-contained, tested, typed Python module — plus an MCP server
(`src/design_patterns/mcp/`) that serves the catalog to agents.

## Layout

- `patterns/<group>/<slug>/` — one directory per pattern ("unit"). Groups:
- `patterns/<group>/<slug>/` — one module per pattern ("unit"). Groups:
`principle`, `python`, `creational`, `structural`, `behavioral`, `modern`.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects).
- `src/design_patterns_mcp/` — FastMCP server (tools, resources, prompts, sandbox).
- Legacy flat dirs (`behavioral/`, `combos/`, `creational/`, `structural/`) are
pre-migration code: excluded from lint, deleted as units absorb them. Do not add to them.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects,
strict structure validation in CI).
- `src/design_patterns/mcp/` — MCP server on the mcp 2.x SDK (`MCPServer`, not
FastMCP): tools, resources, prompts, sandbox.

## Pattern unit template

Every unit has exactly this shape (scaffold one with `/new-pattern`):

```
patterns/<group>/<slug>/
├── README.md # YAML frontmatter + prose
├── __init__.py
├── naive.py # the literal 1994/Java-style translation
├── pythonic.py # what you actually write in Python
├── real_world.py # the pattern as it appears in the stdlib
└── tests/test_<slug>.py
├── README.md # YAML frontmatter + ~10-line front door: problem, verdict, folder map
├── __init__.py # public API — re-exports from pattern/
├── pattern/ # the pattern as importable, typed library code
│ ├── __init__.py
│ └── <named>.py # named for what it provides (chain.py, decorators.py, …)
├── docs/
│ ├── fundamentals.md # intent, participants, mechanism, when/when-not,
│ │ # the classic (GoF) form as an annotated listing — never call it "naive"
│ ├── implementation.md# introducing it into a real system: smell, steps, idioms, pitfalls
│ └── examples.md # cited EXTERNAL usages: stdlib, OSS, articles
├── examples/ # runnable mini-projects that import pattern/
│ └── <project>/ # realistic domain, no Foo/Bar; main.py + modules
└── tests/ # isolated: test_<named>.py + test_<project>.py
```

- Each `.py` variant is import-safe (no side effects at import) and has a
`main() -> None` demo runnable as a script (`if __name__ == "__main__": main()`).
- Tests import the variants and assert behavior — never just "it runs".
No other `__init__.py` exist — namespace packages (PEP 420) carry the rest;
the loader rejects empty ones.

- Everything import-safe (no side effects at import); mini-projects run via
`uv run python -m patterns.<group>.<slug>.examples.<project>.main`.
- Tests assert behavior, never just "it runs"; load-bearing claims get the
mutation treatment (mutate the code, prove the suite fails, revert).
- Examples must genuinely build on `pattern/` — the loader and a catalog test
enforce the structure and the import; reviewers reject token imports.
- Full type hints; `mypy --strict` must pass.

## House rules

- Name-keyed registries refuse silent duplicates (`ValueError` unless
`replace=True`); ordered collections append freely.
- `ParamSpec` only where a wrapper callable is returned; identity typing otherwise.
- Never `None` as a cache sentinel; immutability guards recurse into containers.
- No empty `__init__.py` — delete them (PEP 420 namespace packages). One exists
only when load-bearing: the unit's public-API re-exports
(`from .pattern.x import Y as Y` — the as-alias form) or an import-time
effect the unit teaches. Never `__all__`.

## Frontmatter schema (the MCP server indexes this — keep it valid)

```yaml
Expand All @@ -47,14 +71,15 @@ stdlib_sightings: [functools.wraps, contextlib.contextmanager]
```

Verdicts: `pythonic` = use it as shown; `use-with-care` = valid but has sharp edges
(caveats say which); `prefer-alternative` = the naive form exists for study, the
pythonic file shows what to write instead (e.g. Singleton → module global,
Visitor → singledispatch). See `docs/verdicts.md`.
(caveats say which); `prefer-alternative` = the classic form exists for study in
docs/fundamentals.md, `pattern/` exports the alternative to write instead
(e.g. Singleton → module global, Visitor → singledispatch).

## Workflow

- Branches: `main ← staging ← feat/<slug>`. PRs target `staging`. Never push to `main`.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov) green.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov,
readme-table drift) green.
- Commit style: `<type>: <summary>` (`feat`, `fix`, `chore`, `docs`, `refactor`).
- Toolchain is uv only — no pip/poetry. `make install` to set up.

Expand All @@ -63,5 +88,6 @@ Visitor → singledispatch). See `docs/verdicts.md`.
- Lead with the problem, not the pattern name's history.
- Say plainly when Python makes the pattern unnecessary — that honesty is the
point of the repo. Cite the guide chapter when one exists.
- naive.py mirrors the GoF book faithfully, even when un-Pythonic (that's its job);
pythonic.py is idiomatic; real_world.py points at real stdlib usage.
- The classic form in fundamentals.md mirrors the GoF book faithfully, even when
un-Pythonic (that's its job); `pattern/` is idiomatic; examples.md points at
real external usage.
70 changes: 48 additions & 22 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,61 @@
# python-design-patterns

Companion catalog to [python-patterns.guide](https://python-patterns.guide/): every design
pattern as runnable, tested, typed Python — plus an MCP server (`src/design_patterns_mcp/`)
that serves the catalog to agents.
pattern as a self-contained, tested, typed Python module — plus an MCP server
(`src/design_patterns/mcp/`) that serves the catalog to agents.

## Layout

- `patterns/<group>/<slug>/` — one directory per pattern ("unit"). Groups:
- `patterns/<group>/<slug>/` — one module per pattern ("unit"). Groups:
`principle`, `python`, `creational`, `structural`, `behavioral`, `modern`.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects).
- `src/design_patterns_mcp/` — FastMCP server (tools, resources, prompts, sandbox).
- Legacy flat dirs (`behavioral/`, `combos/`, `creational/`, `structural/`) are
pre-migration code: excluded from lint, deleted as units absorb them. Do not add to them.
- `src/design_patterns/` — catalog loader (frontmatter → typed `Pattern` objects,
strict structure validation in CI).
- `src/design_patterns/mcp/` — MCP server on the mcp 2.x SDK (`MCPServer`, not
FastMCP): tools, resources, prompts, sandbox.

## Pattern unit template

Every unit has exactly this shape (scaffold one with `/new-pattern`):

```
patterns/<group>/<slug>/
├── README.md # YAML frontmatter + prose
├── __init__.py
├── naive.py # the literal 1994/Java-style translation
├── pythonic.py # what you actually write in Python
├── real_world.py # the pattern as it appears in the stdlib
└── tests/test_<slug>.py
├── README.md # YAML frontmatter + ~10-line front door: problem, verdict, folder map
├── __init__.py # public API — re-exports from pattern/
├── pattern/ # the pattern as importable, typed library code
│ ├── __init__.py
│ └── <named>.py # named for what it provides (chain.py, decorators.py, …)
├── docs/
│ ├── fundamentals.md # intent, participants, mechanism, when/when-not,
│ │ # the classic (GoF) form as an annotated listing — never call it "naive"
│ ├── implementation.md# introducing it into a real system: smell, steps, idioms, pitfalls
│ └── examples.md # cited EXTERNAL usages: stdlib, OSS, articles
├── examples/ # runnable mini-projects that import pattern/
│ └── <project>/ # realistic domain, no Foo/Bar; main.py + modules
└── tests/ # isolated: test_<named>.py + test_<project>.py
```

- Each `.py` variant is import-safe (no side effects at import) and has a
`main() -> None` demo runnable as a script (`if __name__ == "__main__": main()`).
- Tests import the variants and assert behavior — never just "it runs".
No other `__init__.py` exist — namespace packages (PEP 420) carry the rest;
the loader rejects empty ones.

- Everything import-safe (no side effects at import); mini-projects run via
`uv run python -m patterns.<group>.<slug>.examples.<project>.main`.
- Tests assert behavior, never just "it runs"; load-bearing claims get the
mutation treatment (mutate the code, prove the suite fails, revert).
- Examples must genuinely build on `pattern/` — the loader and a catalog test
enforce the structure and the import; reviewers reject token imports.
- Full type hints; `mypy --strict` must pass.

## House rules

- Name-keyed registries refuse silent duplicates (`ValueError` unless
`replace=True`); ordered collections append freely.
- `ParamSpec` only where a wrapper callable is returned; identity typing otherwise.
- Never `None` as a cache sentinel; immutability guards recurse into containers.
- No empty `__init__.py` — delete them (PEP 420 namespace packages). One exists
only when load-bearing: the unit's public-API re-exports
(`from .pattern.x import Y as Y` — the as-alias form) or an import-time
effect the unit teaches. Never `__all__`.

## Frontmatter schema (the MCP server indexes this — keep it valid)

```yaml
Expand All @@ -47,14 +71,15 @@ stdlib_sightings: [functools.wraps, contextlib.contextmanager]
```

Verdicts: `pythonic` = use it as shown; `use-with-care` = valid but has sharp edges
(caveats say which); `prefer-alternative` = the naive form exists for study, the
pythonic file shows what to write instead (e.g. Singleton → module global,
Visitor → singledispatch). See `docs/verdicts.md`.
(caveats say which); `prefer-alternative` = the classic form exists for study in
docs/fundamentals.md, `pattern/` exports the alternative to write instead
(e.g. Singleton → module global, Visitor → singledispatch).

## Workflow

- Branches: `main ← staging ← feat/<slug>`. PRs target `staging`. Never push to `main`.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov) green.
- Gate before any PR: `make check` (ruff lint+format, mypy --strict, pytest+cov,
readme-table drift) green.
- Commit style: `<type>: <summary>` (`feat`, `fix`, `chore`, `docs`, `refactor`).
- Toolchain is uv only — no pip/poetry. `make install` to set up.

Expand All @@ -63,5 +88,6 @@ Visitor → singledispatch). See `docs/verdicts.md`.
- Lead with the problem, not the pattern name's history.
- Say plainly when Python makes the pattern unnecessary — that honesty is the
point of the repo. Cite the guide chapter when one exists.
- naive.py mirrors the GoF book faithfully, even when un-Pythonic (that's its job);
pythonic.py is idiomatic; real_world.py points at real stdlib usage.
- The classic form in fundamentals.md mirrors the GoF book faithfully, even when
un-Pythonic (that's its job); `pattern/` is idiomatic; examples.md points at
real external usage.
32 changes: 32 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Contributing

## Workflow

Branches flow `main ← staging ← feat/<slug>`. PRs target `staging`; `main`
takes only reviewed milestone merges. CI (3.11/3.12/3.13) must pass.

## Adding a pattern unit

1. Scaffold: `/new-pattern <group>/<slug> "Name"` (Claude Code) or copy an
existing unit's shape (the template is in [CLAUDE.md](CLAUDE.md)).
2. Fill the frontmatter — every key; `id` must equal `<group>/<slug>`; pick the
verdict (`pythonic` | `use-with-care` | `prefer-alternative`, defined in
[CLAUDE.md](CLAUDE.md)). The catalog loader validates this in CI and fails loudly.
3. Build the module: `pattern/` (the importable code), the three `docs/` files,
at least one `examples/<project>/` mini-project that genuinely imports
`pattern/` (entry point `main.py`, never `__main__.py`), and behavioral
tests for both.
4. `make check` — ruff, mypy --strict, pytest must all pass. The loader rejects
a unit missing any part of the template, and a catalog test rejects an
example that never imports its own pattern package.
5. `make readme` — regenerate the catalog table (CI rejects a stale one).

## Quality bar

- Full type hints; import-safe modules (no side effects at import).
- Tests assert behavior, not "it runs" — and load-bearing claims get the
mutation treatment (mutate the code, prove the suite fails, revert). Reviews
are severity-ordered; machines own style, humans argue design.
- Mini-projects use realistic domains, no Foo/Bar.
- Prose: one page, problem-first, no UML, no history lessons. The classic
(GoF) form lives in each unit's `docs/fundamentals.md` as an annotated listing.
Loading
Loading