dirsql 0.0.16__tar.gz → 0.0.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dirsql-0.0.18/.claude/CLAUDE.md +3 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.gitignore +0 -1
- {dirsql-0.0.16 → dirsql-0.0.18}/PKG-INFO +1 -1
- {dirsql-0.0.16 → dirsql-0.0.18}/pyproject.toml +2 -2
- {dirsql-0.0.16 → dirsql-0.0.18}/python/dirsql/_async.py +26 -10
- {dirsql-0.0.16 → dirsql-0.0.18}/tests/integration/test_async_dirsql.py +60 -25
- dirsql-0.0.16/.claude/CLAUDE.md +0 -132
- {dirsql-0.0.16 → dirsql-0.0.18}/.github/workflows/minor-release.yml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.github/workflows/patch-release.yml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.github/workflows/pr-monitor.yml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.github/workflows/publish.yml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.github/workflows/python-lint.yml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.github/workflows/python-test.yml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.github/workflows/rust-test.yml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/.npmignore +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/Cargo.lock +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/Cargo.toml +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/LICENSE +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/SUMMARY.md +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/python/dirsql/__init__.py +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/src/db.rs +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/src/differ.rs +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/src/lib.rs +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/src/matcher.rs +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/src/scanner.rs +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/src/watcher.rs +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/tests/__init__.py +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/tests/conftest.py +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/tests/integration/__init__.py +0 -0
- {dirsql-0.0.16 → dirsql-0.0.18}/tests/integration/test_dirsql.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dirsql"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.18"
|
|
8
8
|
description = "Ephemeral SQL index over a local directory"
|
|
9
9
|
license = "MIT"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -24,7 +24,7 @@ dev = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[tool.maturin]
|
|
27
|
-
features = ["
|
|
27
|
+
features = ["extension-module"]
|
|
28
28
|
exclude = [
|
|
29
29
|
".github/",
|
|
30
30
|
".claude/",
|
|
@@ -33,7 +33,8 @@ class AsyncDirSQL:
|
|
|
33
33
|
"""Async wrapper around DirSQL.
|
|
34
34
|
|
|
35
35
|
Usage:
|
|
36
|
-
db =
|
|
36
|
+
db = AsyncDirSQL(root, tables=[...])
|
|
37
|
+
await db.ready()
|
|
37
38
|
results = await db.query("SELECT ...")
|
|
38
39
|
async for event in db.watch():
|
|
39
40
|
...
|
|
@@ -44,15 +45,30 @@ class AsyncDirSQL:
|
|
|
44
45
|
self._tables = tables
|
|
45
46
|
self._ignore = ignore
|
|
46
47
|
self._db = None
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
async def
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
self._ready_event = asyncio.Event()
|
|
49
|
+
self._init_error = None
|
|
50
|
+
self._task = asyncio.ensure_future(self._init_bg())
|
|
51
|
+
|
|
52
|
+
async def _init_bg(self):
|
|
53
|
+
"""Run the scan in the background."""
|
|
54
|
+
try:
|
|
55
|
+
self._db = await asyncio.to_thread(
|
|
56
|
+
DirSQL, self._root, tables=self._tables, ignore=self._ignore
|
|
57
|
+
)
|
|
58
|
+
except Exception as exc:
|
|
59
|
+
self._init_error = exc
|
|
60
|
+
finally:
|
|
61
|
+
self._ready_event.set()
|
|
62
|
+
|
|
63
|
+
async def ready(self):
|
|
64
|
+
"""Wait until the initial scan is complete.
|
|
65
|
+
|
|
66
|
+
Raises any exception that occurred during init.
|
|
67
|
+
Can be called multiple times safely.
|
|
68
|
+
"""
|
|
69
|
+
await self._ready_event.wait()
|
|
70
|
+
if self._init_error is not None:
|
|
71
|
+
raise self._init_error
|
|
56
72
|
|
|
57
73
|
async def query(self, sql):
|
|
58
74
|
"""Execute a SQL query asynchronously."""
|
|
@@ -12,9 +12,9 @@ from dirsql import AsyncDirSQL, Table
|
|
|
12
12
|
def describe_AsyncDirSQL():
|
|
13
13
|
def describe_init():
|
|
14
14
|
@pytest.mark.asyncio
|
|
15
|
-
async def
|
|
16
|
-
"""AsyncDirSQL
|
|
17
|
-
db =
|
|
15
|
+
async def it_creates_instance_synchronously(jsonl_dir):
|
|
16
|
+
"""AsyncDirSQL constructor is sync and returns immediately."""
|
|
17
|
+
db = AsyncDirSQL(
|
|
18
18
|
jsonl_dir,
|
|
19
19
|
tables=[
|
|
20
20
|
Table(
|
|
@@ -35,9 +35,9 @@ def describe_AsyncDirSQL():
|
|
|
35
35
|
assert db is not None
|
|
36
36
|
|
|
37
37
|
@pytest.mark.asyncio
|
|
38
|
-
async def
|
|
39
|
-
"""
|
|
40
|
-
db =
|
|
38
|
+
async def it_indexes_files_after_ready(jsonl_dir):
|
|
39
|
+
"""Data is available after awaiting ready()."""
|
|
40
|
+
db = AsyncDirSQL(
|
|
41
41
|
jsonl_dir,
|
|
42
42
|
tables=[
|
|
43
43
|
Table(
|
|
@@ -55,33 +55,61 @@ def describe_AsyncDirSQL():
|
|
|
55
55
|
),
|
|
56
56
|
],
|
|
57
57
|
)
|
|
58
|
+
await db.ready()
|
|
58
59
|
results = await db.query("SELECT * FROM comments")
|
|
59
60
|
assert len(results) == 3
|
|
60
61
|
|
|
61
62
|
@pytest.mark.asyncio
|
|
62
|
-
async def
|
|
63
|
-
"""Extract lambda errors during
|
|
63
|
+
async def it_raises_on_extract_error_during_ready(tmp_dir):
|
|
64
|
+
"""Extract lambda errors during ready() raise exceptions."""
|
|
64
65
|
os.makedirs(os.path.join(tmp_dir, "data"), exist_ok=True)
|
|
65
66
|
with open(os.path.join(tmp_dir, "data", "bad.json"), "w") as f:
|
|
66
67
|
f.write("not valid json")
|
|
67
68
|
|
|
69
|
+
db = AsyncDirSQL(
|
|
70
|
+
tmp_dir,
|
|
71
|
+
tables=[
|
|
72
|
+
Table(
|
|
73
|
+
ddl="CREATE TABLE items (name TEXT)",
|
|
74
|
+
glob="data/*.json",
|
|
75
|
+
extract=lambda path, content: [json.loads(content)],
|
|
76
|
+
),
|
|
77
|
+
],
|
|
78
|
+
)
|
|
68
79
|
with pytest.raises(Exception):
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
await db.ready()
|
|
81
|
+
|
|
82
|
+
@pytest.mark.asyncio
|
|
83
|
+
async def it_allows_multiple_ready_calls(jsonl_dir):
|
|
84
|
+
"""Calling ready() multiple times is safe and idempotent."""
|
|
85
|
+
db = AsyncDirSQL(
|
|
86
|
+
jsonl_dir,
|
|
87
|
+
tables=[
|
|
88
|
+
Table(
|
|
89
|
+
ddl="CREATE TABLE comments (id TEXT, body TEXT, author TEXT)",
|
|
90
|
+
glob="comments/**/index.jsonl",
|
|
91
|
+
extract=lambda path, content: [
|
|
92
|
+
{
|
|
93
|
+
"id": os.path.basename(os.path.dirname(path)),
|
|
94
|
+
"body": row["body"],
|
|
95
|
+
"author": row["author"],
|
|
96
|
+
}
|
|
97
|
+
for line in content.splitlines()
|
|
98
|
+
for row in [json.loads(line)]
|
|
99
|
+
],
|
|
100
|
+
),
|
|
101
|
+
],
|
|
102
|
+
)
|
|
103
|
+
await db.ready()
|
|
104
|
+
await db.ready()
|
|
105
|
+
results = await db.query("SELECT * FROM comments")
|
|
106
|
+
assert len(results) == 3
|
|
79
107
|
|
|
80
108
|
def describe_query():
|
|
81
109
|
@pytest.mark.asyncio
|
|
82
110
|
async def it_returns_results_as_list_of_dicts(jsonl_dir):
|
|
83
111
|
"""Async query returns list of dicts with column names."""
|
|
84
|
-
db =
|
|
112
|
+
db = AsyncDirSQL(
|
|
85
113
|
jsonl_dir,
|
|
86
114
|
tables=[
|
|
87
115
|
Table(
|
|
@@ -99,6 +127,7 @@ def describe_AsyncDirSQL():
|
|
|
99
127
|
),
|
|
100
128
|
],
|
|
101
129
|
)
|
|
130
|
+
await db.ready()
|
|
102
131
|
results = await db.query(
|
|
103
132
|
"SELECT author FROM comments WHERE body = 'first comment'"
|
|
104
133
|
)
|
|
@@ -108,7 +137,7 @@ def describe_AsyncDirSQL():
|
|
|
108
137
|
@pytest.mark.asyncio
|
|
109
138
|
async def it_raises_on_invalid_sql(jsonl_dir):
|
|
110
139
|
"""Invalid SQL raises an exception."""
|
|
111
|
-
db =
|
|
140
|
+
db = AsyncDirSQL(
|
|
112
141
|
jsonl_dir,
|
|
113
142
|
tables=[
|
|
114
143
|
Table(
|
|
@@ -126,6 +155,7 @@ def describe_AsyncDirSQL():
|
|
|
126
155
|
),
|
|
127
156
|
],
|
|
128
157
|
)
|
|
158
|
+
await db.ready()
|
|
129
159
|
with pytest.raises(Exception):
|
|
130
160
|
await db.query("NOT VALID SQL")
|
|
131
161
|
|
|
@@ -133,7 +163,7 @@ def describe_AsyncDirSQL():
|
|
|
133
163
|
@pytest.mark.asyncio
|
|
134
164
|
async def it_emits_insert_events_for_new_files(tmp_dir):
|
|
135
165
|
"""watch() yields insert events when a new file is created."""
|
|
136
|
-
db =
|
|
166
|
+
db = AsyncDirSQL(
|
|
137
167
|
tmp_dir,
|
|
138
168
|
tables=[
|
|
139
169
|
Table(
|
|
@@ -143,6 +173,7 @@ def describe_AsyncDirSQL():
|
|
|
143
173
|
),
|
|
144
174
|
],
|
|
145
175
|
)
|
|
176
|
+
await db.ready()
|
|
146
177
|
|
|
147
178
|
events = []
|
|
148
179
|
|
|
@@ -179,7 +210,7 @@ def describe_AsyncDirSQL():
|
|
|
179
210
|
with open(os.path.join(tmp_dir, "doomed.json"), "w") as f:
|
|
180
211
|
json.dump({"name": "doomed"}, f)
|
|
181
212
|
|
|
182
|
-
db =
|
|
213
|
+
db = AsyncDirSQL(
|
|
183
214
|
tmp_dir,
|
|
184
215
|
tables=[
|
|
185
216
|
Table(
|
|
@@ -189,6 +220,7 @@ def describe_AsyncDirSQL():
|
|
|
189
220
|
),
|
|
190
221
|
],
|
|
191
222
|
)
|
|
223
|
+
await db.ready()
|
|
192
224
|
|
|
193
225
|
# Confirm initial data
|
|
194
226
|
results = await db.query("SELECT * FROM items")
|
|
@@ -228,7 +260,7 @@ def describe_AsyncDirSQL():
|
|
|
228
260
|
with open(os.path.join(tmp_dir, "item.json"), "w") as f:
|
|
229
261
|
json.dump({"name": "draft"}, f)
|
|
230
262
|
|
|
231
|
-
db =
|
|
263
|
+
db = AsyncDirSQL(
|
|
232
264
|
tmp_dir,
|
|
233
265
|
tables=[
|
|
234
266
|
Table(
|
|
@@ -238,6 +270,7 @@ def describe_AsyncDirSQL():
|
|
|
238
270
|
),
|
|
239
271
|
],
|
|
240
272
|
)
|
|
273
|
+
await db.ready()
|
|
241
274
|
|
|
242
275
|
events = []
|
|
243
276
|
|
|
@@ -267,7 +300,7 @@ def describe_AsyncDirSQL():
|
|
|
267
300
|
@pytest.mark.asyncio
|
|
268
301
|
async def it_emits_error_events_for_bad_extract(tmp_dir):
|
|
269
302
|
"""watch() yields error events when extract lambda fails."""
|
|
270
|
-
db =
|
|
303
|
+
db = AsyncDirSQL(
|
|
271
304
|
tmp_dir,
|
|
272
305
|
tables=[
|
|
273
306
|
Table(
|
|
@@ -277,6 +310,7 @@ def describe_AsyncDirSQL():
|
|
|
277
310
|
),
|
|
278
311
|
],
|
|
279
312
|
)
|
|
313
|
+
await db.ready()
|
|
280
314
|
|
|
281
315
|
events = []
|
|
282
316
|
|
|
@@ -305,7 +339,7 @@ def describe_AsyncDirSQL():
|
|
|
305
339
|
@pytest.mark.asyncio
|
|
306
340
|
async def it_updates_db_on_file_changes(tmp_dir):
|
|
307
341
|
"""The database is kept in sync with file system changes."""
|
|
308
|
-
db =
|
|
342
|
+
db = AsyncDirSQL(
|
|
309
343
|
tmp_dir,
|
|
310
344
|
tables=[
|
|
311
345
|
Table(
|
|
@@ -315,6 +349,7 @@ def describe_AsyncDirSQL():
|
|
|
315
349
|
),
|
|
316
350
|
],
|
|
317
351
|
)
|
|
352
|
+
await db.ready()
|
|
318
353
|
|
|
319
354
|
# Initially empty
|
|
320
355
|
results = await db.query("SELECT * FROM items")
|
dirsql-0.0.16/.claude/CLAUDE.md
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# dirsql Development
|
|
2
|
-
|
|
3
|
-
## Cross-Language Parity
|
|
4
|
-
|
|
5
|
-
dirsql ships SDKs in Rust, Python, and TypeScript. Aim for **complete API parity across all three languages**: same concepts, same capabilities, same naming where possible. Exceptions are allowed for language-idiomatic patterns:
|
|
6
|
-
|
|
7
|
-
- **Python**: `await db.ready()` (method call, not awaitable property). snake_case. Async iterators for event streams.
|
|
8
|
-
- **TypeScript**: `await db.ready` (awaitable property is idiomatic). camelCase. AsyncIterables for event streams.
|
|
9
|
-
- **Rust**: Builder pattern or `db.ready().await`. snake_case. Stream trait for event streams.
|
|
10
|
-
|
|
11
|
-
When adding a feature to one SDK, create beads for the other two. Don't let them drift apart.
|
|
12
|
-
|
|
13
|
-
## Scratch Files
|
|
14
|
-
|
|
15
|
-
Write scratch/temporary files to `/tmp` instead of asking permission. Use unique filenames to avoid collisions with other sessions.
|
|
16
|
-
|
|
17
|
-
## Workflow
|
|
18
|
-
|
|
19
|
-
- Work in git worktrees under `.worktrees/` folder
|
|
20
|
-
- **NEVER commit directly to main** - always create a PR
|
|
21
|
-
- One PR per bead. Beads should be concise and small -- as small as possible while still being useful
|
|
22
|
-
- Use `bd` (Beads) for task tracking: `bd list`, `bd show <id>`, `bd ready`
|
|
23
|
-
- **Bead first**: When starting new work, the first step is always to create a bead (`bd create`). No implementation work begins without a bead.
|
|
24
|
-
|
|
25
|
-
### Git Worktrees
|
|
26
|
-
|
|
27
|
-
**ALL work happens in git worktrees.** Never edit files in the root repo directory. Never commit outside a worktree.
|
|
28
|
-
|
|
29
|
-
#### Creating a Worktree
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
git worktree add .worktrees/my-feature -b feat/my-feature
|
|
33
|
-
cd .worktrees/my-feature
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
#### Removing a Worktree
|
|
37
|
-
|
|
38
|
-
**DANGER: removing a worktree while your shell CWD is inside it permanently breaks the shell.** The ONLY safe procedure:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# Step 1: Move CWD to the root repo FIRST (not optional)
|
|
42
|
-
cd /home/duncan/work/code/projects/dirsql
|
|
43
|
-
|
|
44
|
-
# Step 2: Now remove the worktree
|
|
45
|
-
git worktree remove .worktrees/my-feature
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**Do NOT skip step 1. Do NOT substitute `git -C` for `cd`.**
|
|
49
|
-
|
|
50
|
-
### Beads Workflow
|
|
51
|
-
|
|
52
|
-
**Lifecycle:**
|
|
53
|
-
1. **Claim it FIRST**: `bd update <id> --claim` before any work
|
|
54
|
-
2. **Create worktree and branch**
|
|
55
|
-
3. **Link the PR**: `bd update <id> --external-ref "gh-<pr-number>"` after creating the PR
|
|
56
|
-
4. **Close**: `bd close <id>` immediately after the PR is merged
|
|
57
|
-
|
|
58
|
-
### Subagent Workflow
|
|
59
|
-
|
|
60
|
-
New work on beads should be done via subagents in isolated worktrees. Each subagent:
|
|
61
|
-
1. Claims the bead (`bd update <id> --claim`) before starting any work
|
|
62
|
-
2. Creates a worktree and branch for its bead
|
|
63
|
-
3. Does the implementation work (red/green TDD)
|
|
64
|
-
4. Pushes the branch and opens a PR
|
|
65
|
-
5. Monitors the PR and proactively resolves:
|
|
66
|
-
- CI failures
|
|
67
|
-
- GPG signing complaints
|
|
68
|
-
- Merge conflicts
|
|
69
|
-
6. Continues monitoring until the PR is in a mergeable state
|
|
70
|
-
|
|
71
|
-
### Orchestrator Responsibilities
|
|
72
|
-
|
|
73
|
-
The orchestrator (main Claude session) must proactively:
|
|
74
|
-
1. **Monitor all open PRs** -- don't wait for the user to report failures. Check CI status after agent completion and on an ongoing basis.
|
|
75
|
-
2. **Fix CI failures** on open PRs immediately, either directly or by dispatching a fix agent.
|
|
76
|
-
3. **Handle post-merge cleanup** as soon as a PR merges (pull main, remove worktree, delete branch, close bead).
|
|
77
|
-
4. **Keep the user informed** of PR status without being asked.
|
|
78
|
-
5. **Use foreground monitoring** when waiting on CI and there's no other work to do. Background monitoring causes the conversation to go silent -- use it only when there's genuinely parallel work to perform.
|
|
79
|
-
6. **Scripts to `/tmp`**: For polling/monitoring scripts (watching CI, waiting for merges), write the script to `/tmp` then run it via `bash /tmp/script.sh`. Do not use inline bash loops in tool calls.
|
|
80
|
-
|
|
81
|
-
### Post-Merge Cleanup
|
|
82
|
-
|
|
83
|
-
After a PR merges, the agent (or orchestrator) must:
|
|
84
|
-
1. Pull main in the **root repo**: `git -C /home/duncan/work/code/projects/dirsql pull origin main`
|
|
85
|
-
2. **Move CWD to root repo first** (CRITICAL -- never remove a worktree from inside it): `cd /home/duncan/work/code/projects/dirsql`
|
|
86
|
-
3. Remove the worktree: `git worktree remove .worktrees/<name>`
|
|
87
|
-
4. Delete the local branch: `git branch -d <branch-name>`
|
|
88
|
-
5. **Verify the bead is addressed** by the merged PR, then close it: `bd close <id>`
|
|
89
|
-
|
|
90
|
-
## Testing
|
|
91
|
-
|
|
92
|
-
### Red/Green Development
|
|
93
|
-
|
|
94
|
-
Follow **red/green** (test-first) methodology:
|
|
95
|
-
|
|
96
|
-
1. **Write the test first** -- it must capture the desired behavior
|
|
97
|
-
2. **Run it and confirm it fails (RED)** -- do NOT proceed until the test turns red reliably. A test that passes before implementation proves nothing.
|
|
98
|
-
3. **Make the minimal change to pass (GREEN)** -- only then write the implementation
|
|
99
|
-
4. Refactor if needed, keeping tests green
|
|
100
|
-
|
|
101
|
-
### TDD Order: Outside-In
|
|
102
|
-
|
|
103
|
-
Tests are written **before** implementation, starting from the outermost layer:
|
|
104
|
-
|
|
105
|
-
1. **Integration test first** -- proves the feature works from the consumer's perspective
|
|
106
|
-
2. **Unit tests** -- written as you implement each module
|
|
107
|
-
|
|
108
|
-
A feature is not done until integration tests pass and cover the new functionality.
|
|
109
|
-
|
|
110
|
-
### When to Write What
|
|
111
|
-
|
|
112
|
-
**Does the commit change the public-facing API?**
|
|
113
|
-
- Yes -> **integration test required**, plus unit tests as you go
|
|
114
|
-
- No -> Check if adequate integration coverage already exists:
|
|
115
|
-
- Adequate -> unit tests only
|
|
116
|
-
- Gaps -> add the missing integration tests, plus unit tests
|
|
117
|
-
|
|
118
|
-
**Always write unit tests.** The question is whether you also need integration tests.
|
|
119
|
-
|
|
120
|
-
### Test Locations
|
|
121
|
-
|
|
122
|
-
- **Unit tests**: Colocated with source
|
|
123
|
-
- Python: `foo.py` -> `foo_test.py` in same directory
|
|
124
|
-
- Rust: inline `#[cfg(test)]` module at bottom of each source file
|
|
125
|
-
- **Integration tests**: `tests/integration/` -- test the Python SDK layer, mock third-party deps (SQLite, LLM calls). Heavy use of pytest fixtures. Run in CI.
|
|
126
|
-
- **E2E tests**: `tests/e2e/` -- real filesystem, real SQLite, real LLM calls, no mocks. Heavy use of pytest fixtures. **NOT run in CI** (eventual LLM calls make them non-free). Run locally by Claude after significant code changes.
|
|
127
|
-
|
|
128
|
-
### E2E Test Policy
|
|
129
|
-
|
|
130
|
-
E2E tests are your primary feedback mechanism. Run them liberally after significant changes -- they catch issues that integration tests miss because integration tests mock out SQLite and (eventually) LLM calls. But do NOT add them to CI workflows. They are a local development tool.
|
|
131
|
-
|
|
132
|
-
See skillet or karat for examples of test organization, fixtures, and pytest-describe patterns.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|