Skip to content

Commit dfa103b

Browse files
committed
build: validate and commit monorepo workspace migration
This commit finalizes the transition to a uv-based workspace, updates the pre-commit configuration, resolves test hangs related to the Gemini API, and passes all layer, type, and lint checks across the new sub-projects.
1 parent 9d62a78 commit dfa103b

131 files changed

Lines changed: 7719 additions & 852 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
applyTo: "projects/llm-patch/**"
3+
description: "Engine boundary rules: no imports from use-cases; preserve public API; preserve test baseline 216 passed / 3 skipped."
4+
---
5+
6+
# Engine Boundary Rules
7+
8+
These rules apply to every file under `projects/llm-patch/`. They
9+
implement the engine layer of the architecture in
10+
[SPEC.md §1](../../SPEC.md#1-architectural-layering) and
11+
[ADR-0002](../../docs/adr/0002-layered-architecture.md).
12+
13+
## Hard Rules
14+
15+
- **Do not import** from `llm_patch_wiki_agent` or any other use-case
16+
package. Dependency direction is one-way (use-cases → engine).
17+
- **Do not import** from `llm_patch_shared` until that dependency is
18+
explicitly added via ADR. Today the engine has no shared-utils dep.
19+
- **Do not edit** existing ABCs in `core/interfaces.py` to add a new
20+
capability. Add a new ABC or a new Strategy implementation instead
21+
(Open/Closed).
22+
- **Preserve the public API**: anything currently re-exported from
23+
`src/llm_patch/__init__.py` stays re-exported (additions OK, removals
24+
require a major bump + ADR + deprecation).
25+
- **Preserve the test baseline**: `216 passed, 3 skipped`. Any PR that
26+
changes the count must justify the change in its description.
27+
28+
## When Adding a New Source / Generator / Storage / Provider / Runtime
29+
30+
1. Implement the corresponding ABC from `core/interfaces.py` in a new
31+
module under the appropriate subpackage (`sources/`, `generators/`,
32+
`storage/`, `attach/`, `runtime/`).
33+
2. Add a unit test in `tests/unit/`.
34+
3. If the new class is part of the public API, re-export it from
35+
`src/llm_patch/__init__.py` and add to `__all__`.
36+
4. Update `docs/ARCHITECTURE.md` if the addition expands the architecture
37+
story.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
applyTo: "**/*.py"
3+
description: "Always-on Python style rules for the llm-patch monorepo (SOLID, OOP, type hints, OOD). Aligned with SPEC.md."
4+
---
5+
6+
# Python Style — Always On
7+
8+
These rules apply to every `.py` file in the workspace. They derive
9+
from [SPEC.md](../../SPEC.md). Where this file and `SPEC.md` disagree,
10+
`SPEC.md` wins.
11+
12+
## Type Hints
13+
14+
- All public functions, methods, and class attributes have full type
15+
annotations. Use `from __future__ import annotations` at the top of
16+
modules that need forward references.
17+
- Prefer `collections.abc` over `typing` for ABCs (e.g.,
18+
`Iterable`, `Mapping`). Use `|` union syntax (Python 3.11+).
19+
- No `Any` in public signatures unless documented and justified.
20+
21+
## OOP / OOD
22+
23+
- Use `abc.ABC` for new interfaces. Prefix interface names with `I`
24+
(e.g., `IWeightGenerator`).
25+
- Prefer **composition** over inheritance. Inherit only to satisfy an
26+
ABC or to share invariants.
27+
- Constructors take dependencies as parameters (DI). Don't reach out
28+
to module-level singletons.
29+
- Data classes for value objects: `@dataclass(frozen=True, slots=True)`
30+
unless the field requires Pydantic validation; in that case use
31+
`pydantic.BaseModel`.
32+
33+
## Module Hygiene
34+
35+
- **No module-level side effects** — no I/O, no network, no sleep, no
36+
registration of singletons at import time.
37+
- Every package has an `__init__.py` with an explicit `__all__` listing
38+
the public symbols (see [ADR-0003](../../docs/adr/0003-public-api-policy.md)).
39+
- New packages ship a `py.typed` marker.
40+
41+
## Errors
42+
43+
- Derive new exception types from `llm_patch_shared.errors.LlmPatchError`
44+
(or one of its subclasses). Never raise bare `Exception`.
45+
- Catch the narrowest exception possible. Don't swallow exceptions
46+
silently — log or re-raise.
47+
48+
## Naming
49+
50+
- Modules and packages: `snake_case`.
51+
- Classes: `PascalCase`. Interfaces: `IPascalCase`.
52+
- Constants: `UPPER_SNAKE_CASE`.
53+
- Test files: `test_<unit-under-test>.py`.
54+
55+
## Imports
56+
57+
- One import per line where the import system allows.
58+
- Group order: stdlib, third-party, first-party (handled by ruff isort).
59+
- **No** imports from another project's internal modules — see
60+
[ADR-0002](../../docs/adr/0002-layered-architecture.md) and
61+
[ADR-0003](../../docs/adr/0003-public-api-policy.md).
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
applyTo: "**/tests/**/*.py"
3+
description: "Testing conventions for the llm-patch monorepo: AAA, one behavior per test, no network, mock at the boundary, TDD for new public API."
4+
---
5+
6+
# Tests — Conventions
7+
8+
These rules apply to every test file under any `tests/` directory.
9+
10+
## Structure
11+
12+
- **Arrange / Act / Assert** — separate the three phases visibly with
13+
blank lines.
14+
- **One behavior per test.** A test name should read like a sentence:
15+
`test_<subject>_<expected_behavior>_when_<condition>`.
16+
- Place fast tests under `tests/unit/`, slow / I/O-dependent tests
17+
under `tests/integration/` and mark them `@pytest.mark.integration`.
18+
19+
## Boundaries
20+
21+
- **No network** in unit tests. Use `httpx.MockTransport` or stubs.
22+
- **No real model loads** in unit tests. Mock `IModelProvider` /
23+
`IAdapterLoader`.
24+
- File I/O only via the `tmp_path` fixture. Never write under the repo.
25+
26+
## Mocking
27+
28+
- Mock at the **boundary** of the unit under test, not in the middle.
29+
If you find yourself mocking an internal collaborator, the test is
30+
probably exercising the wrong unit.
31+
- Prefer hand-rolled fakes for ABCs (`class FakeRepo(IAdapterRepository)`)
32+
over `MagicMock` — they document the contract and break loudly when
33+
the contract changes.
34+
35+
## Public API Tests
36+
37+
- Any new public symbol must have at least one test that imports it
38+
from the **top-level package** (`from llm_patch import Foo`,
39+
`from llm_patch_wiki_agent import Bar`). This locks the public path
40+
per [ADR-0003](../../docs/adr/0003-public-api-policy.md).
41+
42+
## Baselines
43+
44+
- The engine baseline is `216 passed, 3 skipped`. A change that alters
45+
the count must justify it in the PR description.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
applyTo: "projects/wiki-agent/**"
3+
description: "Use-case boundary rules for wiki-agent: import only from llm_patch's public top-level API; no engine internals."
4+
---
5+
6+
# Use-Case Boundary Rules — wiki-agent
7+
8+
These rules apply to every file under `projects/wiki-agent/`. They
9+
implement the use-case layer of the architecture in
10+
[SPEC.md §1](../../SPEC.md#1-architectural-layering),
11+
[ADR-0002](../../docs/adr/0002-layered-architecture.md), and
12+
[ADR-0003](../../docs/adr/0003-public-api-policy.md).
13+
14+
## Hard Rules
15+
16+
- **Import only from the top-level engine package**:
17+
```python
18+
# OK
19+
from llm_patch import CompilePipeline, WikiKnowledgeSource, IAgentRuntime
20+
# NOT OK
21+
from llm_patch.pipelines.compile import CompilePipeline
22+
from llm_patch.core.interfaces import IAgentRuntime
23+
from llm_patch.wiki.manager import WikiManager # internal — use llm_patch.WikiManager
24+
```
25+
- **Compose, don't subclass** engine classes. The engine ABCs exist so
26+
you can plug in a new implementation; subclassing a concrete engine
27+
class couples you to its internals.
28+
- **Stay thin**: CLI commands delegate to a single `WikiAgent` method.
29+
Keep business logic in `WikiAgent`, not in `cli.py`.
30+
- **Add a test** for every new public symbol, importing it from
31+
`llm_patch_wiki_agent` (the top-level), not from a submodule.
32+
33+
## Allowed Runtime Dependencies
34+
35+
- `llm-patch` (workspace).
36+
- `llm-patch-shared` (workspace).
37+
- `click>=8.0`.
38+
- Anything else requires an ADR.

.github/pull_request_template.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<!--
2+
Thanks for contributing! Please confirm the items below before requesting review.
3+
See SPEC.md and the per-project AGENTS.md for the full rules.
4+
-->
5+
6+
## What & Why
7+
8+
<!-- One-paragraph summary of the change and the motivation. -->
9+
10+
## Affected Project(s)
11+
12+
- [ ] `llm-patch` (engine)
13+
- [ ] `llm-patch-shared`
14+
- [ ] `llm-patch-wiki-agent`
15+
- [ ] tooling / docs / CI only
16+
17+
## Checklist
18+
19+
- [ ] Read the relevant `AGENTS.md` (root + per-project).
20+
- [ ] Tests added/updated; **engine baseline `216 passed, 3 skipped` preserved** (or change justified below).
21+
- [ ] `make check` passes locally (`lint + typecheck + check-layering + test`).
22+
- [ ] Public API change? → updated `__init__.py` `__all__`, `CHANGELOG.md`, and bumped version per SemVer.
23+
- [ ] Architectural / dependency-direction change? → ADR added under `docs/adr/`.
24+
- [ ] CHANGELOG.md `[Unreleased]` updated for every affected project.
25+
26+
## Baseline Justification (if test count changed)
27+
28+
<!-- Required if engine test count != 216 passed / 3 skipped. -->

.github/workflows/ci.yml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
concurrency:
9+
group: ${{ github.workflow }}-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
quality:
14+
name: lint + typecheck + layering
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: astral-sh/setup-uv@v3
19+
with:
20+
enable-cache: true
21+
- name: Set up Python
22+
run: uv python install 3.11
23+
- name: Sync workspace
24+
run: uv sync
25+
- name: Ruff lint
26+
run: uv run ruff check .
27+
- name: Ruff format check
28+
run: uv run ruff format --check .
29+
- name: Layering check
30+
run: uv run python tools/check_layering.py
31+
- name: Mypy (engine)
32+
working-directory: projects/llm-patch
33+
run: uv run mypy src
34+
- name: Mypy (shared-utils)
35+
working-directory: projects/shared-utils
36+
run: uv run mypy src
37+
- name: Mypy (wiki-agent)
38+
working-directory: projects/wiki-agent
39+
run: uv run mypy src
40+
41+
test:
42+
name: tests (${{ matrix.project }} on py${{ matrix.python }})
43+
runs-on: ubuntu-latest
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
python: ["3.11", "3.12"]
48+
project:
49+
- llm-patch
50+
- llm-patch-shared
51+
- llm-patch-wiki-agent
52+
steps:
53+
- uses: actions/checkout@v4
54+
- uses: astral-sh/setup-uv@v3
55+
with:
56+
enable-cache: true
57+
- name: Set up Python ${{ matrix.python }}
58+
run: uv python install ${{ matrix.python }}
59+
- name: Sync workspace
60+
run: uv sync
61+
- name: Run tests
62+
run: |
63+
if [ "${{ matrix.project }}" = "llm-patch" ]; then
64+
cd projects/llm-patch
65+
uv run pytest -q
66+
elif [ "${{ matrix.project }}" = "llm-patch-shared" ]; then
67+
cd projects/shared-utils
68+
uv run pytest -q
69+
else
70+
cd projects/wiki-agent
71+
uv run pytest -q
72+
fi
73+
- name: Check engine coverage
74+
if: matrix.project == 'llm-patch' && matrix.python == '3.11'
75+
run: |
76+
cd projects/llm-patch
77+
uv run pytest --cov=llm_patch --cov-branch --cov-report=xml:coverage.xml -q
78+
cd ../..
79+
uv run python tools/check_coverage.py projects/llm-patch/coverage.xml
80+
81+
changelog:
82+
name: changelog enforcement
83+
if: github.event_name == 'pull_request'
84+
runs-on: ubuntu-latest
85+
steps:
86+
- uses: actions/checkout@v4
87+
with:
88+
fetch-depth: 0
89+
- name: Compute changed files
90+
id: diff
91+
run: |
92+
git diff --name-only origin/${{ github.base_ref }}...HEAD > .changed
93+
cat .changed
94+
- uses: astral-sh/setup-uv@v3
95+
- name: Set up Python
96+
run: uv python install 3.11
97+
- name: Run changelog check
98+
run: cat .changed | uv run python tools/check_changelog.py

.github/workflows/release.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Release
2+
3+
# Tag format: <project>-vX.Y.Z e.g. llm-patch-v0.2.0, llm-patch-wiki-agent-v0.1.0
4+
on:
5+
push:
6+
tags:
7+
- "llm-patch-v*"
8+
- "llm-patch-shared-v*"
9+
- "llm-patch-wiki-agent-v*"
10+
11+
jobs:
12+
publish:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
id-token: write # PyPI Trusted Publishing
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: astral-sh/setup-uv@v3
19+
with:
20+
enable-cache: true
21+
- name: Set up Python
22+
run: uv python install 3.11
23+
- name: Resolve project from tag
24+
id: tag
25+
run: |
26+
tag="${GITHUB_REF_NAME}"
27+
# Strip trailing -vX.Y.Z to get the project distribution name.
28+
project="${tag%-v*}"
29+
echo "project=$project" >> "$GITHUB_OUTPUT"
30+
echo "Resolved project: $project"
31+
- name: Sync workspace
32+
run: uv sync
33+
- name: Run tests for the project
34+
run: uv run --package "${{ steps.tag.outputs.project }}" pytest -q
35+
- name: Build distribution
36+
run: uv build --package "${{ steps.tag.outputs.project }}"
37+
- name: Publish to PyPI
38+
run: uv publish

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ wiki/
9292
raw/
9393
.obsidian/
9494

95+
# ─────────────────────────────────────────────────────────────
96+
# Per-project data / artifacts (created by tools/scaffold_project.py)
97+
# ─────────────────────────────────────────────────────────────
98+
projects/*/data/
99+
!projects/*/data/.gitkeep
100+
projects/*/artifacts/
101+
!projects/*/artifacts/.gitkeep
102+
95103
# ─────────────────────────────────────────────────────────────
96104
# Misc
97105
# ─────────────────────────────────────────────────────────────

0 commit comments

Comments
 (0)