dirsql 0.0.13__tar.gz → 0.0.15__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.
Files changed (32) hide show
  1. {dirsql-0.0.13 → dirsql-0.0.15}/.claude/CLAUDE.md +8 -5
  2. dirsql-0.0.15/.github/workflows/minor-release.yml +117 -0
  3. dirsql-0.0.15/.github/workflows/patch-release.yml +155 -0
  4. {dirsql-0.0.13 → dirsql-0.0.15}/.github/workflows/publish.yml +16 -6
  5. {dirsql-0.0.13 → dirsql-0.0.15}/PKG-INFO +1 -1
  6. {dirsql-0.0.13 → dirsql-0.0.15}/pyproject.toml +3 -1
  7. dirsql-0.0.15/python/dirsql/__init__.py +6 -0
  8. dirsql-0.0.15/python/dirsql/_async.py +63 -0
  9. dirsql-0.0.15/src/lib.rs +645 -0
  10. dirsql-0.0.15/tests/integration/test_async_dirsql.py +346 -0
  11. dirsql-0.0.13/.github/workflows/minor-release.yml +0 -48
  12. dirsql-0.0.13/.github/workflows/patch-release.yml +0 -80
  13. dirsql-0.0.13/src/lib.rs +0 -291
  14. {dirsql-0.0.13 → dirsql-0.0.15}/.github/workflows/pr-monitor.yml +0 -0
  15. {dirsql-0.0.13 → dirsql-0.0.15}/.github/workflows/python-lint.yml +0 -0
  16. {dirsql-0.0.13 → dirsql-0.0.15}/.github/workflows/python-test.yml +0 -0
  17. {dirsql-0.0.13 → dirsql-0.0.15}/.github/workflows/rust-test.yml +0 -0
  18. {dirsql-0.0.13 → dirsql-0.0.15}/.gitignore +0 -0
  19. {dirsql-0.0.13 → dirsql-0.0.15}/.npmignore +0 -0
  20. {dirsql-0.0.13 → dirsql-0.0.15}/Cargo.lock +0 -0
  21. {dirsql-0.0.13 → dirsql-0.0.15}/Cargo.toml +0 -0
  22. {dirsql-0.0.13 → dirsql-0.0.15}/LICENSE +0 -0
  23. {dirsql-0.0.13 → dirsql-0.0.15}/SUMMARY.md +0 -0
  24. {dirsql-0.0.13 → dirsql-0.0.15}/src/db.rs +0 -0
  25. {dirsql-0.0.13 → dirsql-0.0.15}/src/differ.rs +0 -0
  26. {dirsql-0.0.13 → dirsql-0.0.15}/src/matcher.rs +0 -0
  27. {dirsql-0.0.13 → dirsql-0.0.15}/src/scanner.rs +0 -0
  28. {dirsql-0.0.13 → dirsql-0.0.15}/src/watcher.rs +0 -0
  29. {dirsql-0.0.13 → dirsql-0.0.15}/tests/__init__.py +0 -0
  30. {dirsql-0.0.13 → dirsql-0.0.15}/tests/conftest.py +0 -0
  31. {dirsql-0.0.13 → dirsql-0.0.15}/tests/integration/__init__.py +0 -0
  32. {dirsql-0.0.13 → dirsql-0.0.15}/tests/integration/test_dirsql.py +0 -0
@@ -10,6 +10,7 @@ Write scratch/temporary files to `/tmp` instead of asking permission. Use unique
10
10
  - **NEVER commit directly to main** - always create a PR
11
11
  - One PR per bead. Beads should be concise and small -- as small as possible while still being useful
12
12
  - Use `bd` (Beads) for task tracking: `bd list`, `bd show <id>`, `bd ready`
13
+ - **Bead first**: When starting new work, the first step is always to create a bead (`bd create`). No implementation work begins without a bead.
13
14
 
14
15
  ### Git Worktrees
15
16
 
@@ -47,14 +48,15 @@ git worktree remove .worktrees/my-feature
47
48
  ### Subagent Workflow
48
49
 
49
50
  New work on beads should be done via subagents in isolated worktrees. Each subagent:
50
- 1. Creates a worktree and branch for its bead
51
- 2. Does the implementation work (red/green TDD)
52
- 3. Pushes the branch and opens a PR
53
- 4. Monitors the PR and proactively resolves:
51
+ 1. Claims the bead (`bd update <id> --claim`) before starting any work
52
+ 2. Creates a worktree and branch for its bead
53
+ 3. Does the implementation work (red/green TDD)
54
+ 4. Pushes the branch and opens a PR
55
+ 5. Monitors the PR and proactively resolves:
54
56
  - CI failures
55
57
  - GPG signing complaints
56
58
  - Merge conflicts
57
- 5. Continues monitoring until the PR is in a mergeable state
59
+ 6. Continues monitoring until the PR is in a mergeable state
58
60
 
59
61
  ### Orchestrator Responsibilities
60
62
 
@@ -64,6 +66,7 @@ The orchestrator (main Claude session) must proactively:
64
66
  3. **Handle post-merge cleanup** as soon as a PR merges (pull main, remove worktree, delete branch, close bead).
65
67
  4. **Keep the user informed** of PR status without being asked.
66
68
  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.
69
+ 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.
67
70
 
68
71
  ### Post-Merge Cleanup
69
72
 
@@ -0,0 +1,117 @@
1
+ name: Minor Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ permissions:
7
+ contents: write
8
+ id-token: write
9
+
10
+ jobs:
11
+ detect-changes:
12
+ runs-on: ubuntu-latest
13
+ outputs:
14
+ rust_changed: ${{ steps.changes.outputs.rust_changed }}
15
+ python_changed: ${{ steps.changes.outputs.python_changed }}
16
+ js_changed: ${{ steps.changes.outputs.js_changed }}
17
+ docs_changed: ${{ steps.changes.outputs.docs_changed }}
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Detect changed file types since last tag
24
+ id: changes
25
+ run: |
26
+ latest_tag=$(git tag --sort=-v:refname | grep -E '^v[0-9]' | head -n1)
27
+
28
+ if [ -z "$latest_tag" ]; then
29
+ # No tags yet -- treat everything as changed
30
+ echo "rust_changed=true" >> "$GITHUB_OUTPUT"
31
+ echo "python_changed=true" >> "$GITHUB_OUTPUT"
32
+ echo "js_changed=true" >> "$GITHUB_OUTPUT"
33
+ echo "docs_changed=true" >> "$GITHUB_OUTPUT"
34
+ exit 0
35
+ fi
36
+
37
+ changed_files=$(git diff --name-only "${latest_tag}..HEAD")
38
+
39
+ if [ -z "$changed_files" ]; then
40
+ echo "rust_changed=false" >> "$GITHUB_OUTPUT"
41
+ echo "python_changed=false" >> "$GITHUB_OUTPUT"
42
+ echo "js_changed=false" >> "$GITHUB_OUTPUT"
43
+ echo "docs_changed=false" >> "$GITHUB_OUTPUT"
44
+ exit 0
45
+ fi
46
+
47
+ rust_changed=false
48
+ python_changed=false
49
+ js_changed=false
50
+ docs_changed=false
51
+
52
+ while IFS= read -r file; do
53
+ case "$file" in
54
+ *.rs|Cargo.toml|Cargo.lock) rust_changed=true ;;
55
+ *.py|pyproject.toml|uv.lock) python_changed=true ;;
56
+ package.json|*.ts|*.js) js_changed=true ;;
57
+ README.md|*.md) docs_changed=true ;;
58
+ esac
59
+ done <<< "$changed_files"
60
+
61
+ echo "rust_changed=$rust_changed" >> "$GITHUB_OUTPUT"
62
+ echo "python_changed=$python_changed" >> "$GITHUB_OUTPUT"
63
+ echo "js_changed=$js_changed" >> "$GITHUB_OUTPUT"
64
+ echo "docs_changed=$docs_changed" >> "$GITHUB_OUTPUT"
65
+
66
+ echo "Rust: $rust_changed, Python: $python_changed, JS: $js_changed, Docs: $docs_changed"
67
+
68
+ release:
69
+ needs: detect-changes
70
+ uses: ./.github/workflows/publish.yml
71
+ with:
72
+ bump_type: minor
73
+ publish_pypi: ${{ needs.detect-changes.outputs.rust_changed == 'true' || needs.detect-changes.outputs.python_changed == 'true' || needs.detect-changes.outputs.docs_changed == 'true' }}
74
+ publish_crates: ${{ needs.detect-changes.outputs.rust_changed == 'true' || needs.detect-changes.outputs.docs_changed == 'true' }}
75
+ secrets: inherit
76
+ permissions:
77
+ contents: write
78
+ id-token: write
79
+
80
+ # npm publish inlined here because npm OIDC doesn't support reusable workflows
81
+ publish-npm:
82
+ needs: [detect-changes, release]
83
+ if: >-
84
+ always() &&
85
+ needs.release.outputs.tag_created == 'true' &&
86
+ (needs.detect-changes.outputs.rust_changed == 'true' ||
87
+ needs.detect-changes.outputs.js_changed == 'true' ||
88
+ needs.detect-changes.outputs.docs_changed == 'true')
89
+ runs-on: ubuntu-latest
90
+ permissions:
91
+ contents: read
92
+ id-token: write
93
+ steps:
94
+ - uses: actions/checkout@v6
95
+
96
+ - name: Check if package.json exists
97
+ id: check_npm
98
+ run: |
99
+ if [ -f package.json ]; then
100
+ echo "exists=true" >> $GITHUB_OUTPUT
101
+ else
102
+ echo "exists=false" >> $GITHUB_OUTPUT
103
+ fi
104
+
105
+ - name: Setup Node.js
106
+ if: steps.check_npm.outputs.exists == 'true'
107
+ uses: actions/setup-node@v4
108
+ with:
109
+ node-version: '24.x'
110
+
111
+ - name: Update package.json version
112
+ if: steps.check_npm.outputs.exists == 'true'
113
+ run: npm version "${{ needs.release.outputs.new_version }}" --no-git-tag-version
114
+
115
+ - name: Publish to npm
116
+ if: steps.check_npm.outputs.exists == 'true'
117
+ run: npm publish --provenance --access public
@@ -0,0 +1,155 @@
1
+ name: Patch Release
2
+
3
+ on:
4
+ schedule:
5
+ # Run at 2:00 AM UTC every day
6
+ - cron: '0 2 * * *'
7
+ push:
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: write
13
+ id-token: write
14
+
15
+ jobs:
16
+ check:
17
+ runs-on: ubuntu-latest
18
+ outputs:
19
+ should_release: ${{ steps.decide.outputs.should_release }}
20
+ steps:
21
+ - id: decide
22
+ env:
23
+ EVENT: ${{ github.event_name }}
24
+ STRATEGY: ${{ vars.RELEASE_STRATEGY }}
25
+ COMMIT_MSG: ${{ github.event.head_commit.message }}
26
+ run: |
27
+ if [ "$EVENT" = "workflow_dispatch" ]; then
28
+ echo "should_release=true" >> "$GITHUB_OUTPUT"
29
+ elif [ "$EVENT" = "schedule" ] && [ "$STRATEGY" != "immediate" ]; then
30
+ echo "should_release=true" >> "$GITHUB_OUTPUT"
31
+ elif [ "$EVENT" = "push" ] && [ "$STRATEGY" = "immediate" ]; then
32
+ case "$COMMIT_MSG" in
33
+ *'[no-release]'*) echo "should_release=false" >> "$GITHUB_OUTPUT" ;;
34
+ *) echo "should_release=true" >> "$GITHUB_OUTPUT" ;;
35
+ esac
36
+ else
37
+ echo "should_release=false" >> "$GITHUB_OUTPUT"
38
+ fi
39
+
40
+ detect-changes:
41
+ needs: check
42
+ if: needs.check.outputs.should_release == 'true'
43
+ runs-on: ubuntu-latest
44
+ outputs:
45
+ rust_changed: ${{ steps.changes.outputs.rust_changed }}
46
+ python_changed: ${{ steps.changes.outputs.python_changed }}
47
+ js_changed: ${{ steps.changes.outputs.js_changed }}
48
+ docs_changed: ${{ steps.changes.outputs.docs_changed }}
49
+ steps:
50
+ - uses: actions/checkout@v6
51
+ with:
52
+ fetch-depth: 0
53
+
54
+ - name: Detect changed file types since last tag
55
+ id: changes
56
+ run: |
57
+ latest_tag=$(git tag --sort=-v:refname | grep -E '^v[0-9]' | head -n1)
58
+
59
+ if [ -z "$latest_tag" ]; then
60
+ # No tags yet -- treat everything as changed
61
+ echo "rust_changed=true" >> "$GITHUB_OUTPUT"
62
+ echo "python_changed=true" >> "$GITHUB_OUTPUT"
63
+ echo "js_changed=true" >> "$GITHUB_OUTPUT"
64
+ echo "docs_changed=true" >> "$GITHUB_OUTPUT"
65
+ exit 0
66
+ fi
67
+
68
+ changed_files=$(git diff --name-only "${latest_tag}..HEAD")
69
+
70
+ if [ -z "$changed_files" ]; then
71
+ echo "rust_changed=false" >> "$GITHUB_OUTPUT"
72
+ echo "python_changed=false" >> "$GITHUB_OUTPUT"
73
+ echo "js_changed=false" >> "$GITHUB_OUTPUT"
74
+ echo "docs_changed=false" >> "$GITHUB_OUTPUT"
75
+ exit 0
76
+ fi
77
+
78
+ rust_changed=false
79
+ python_changed=false
80
+ js_changed=false
81
+ docs_changed=false
82
+
83
+ while IFS= read -r file; do
84
+ case "$file" in
85
+ *.rs|Cargo.toml|Cargo.lock) rust_changed=true ;;
86
+ *.py|pyproject.toml|uv.lock) python_changed=true ;;
87
+ package.json|*.ts|*.js) js_changed=true ;;
88
+ README.md|*.md) docs_changed=true ;;
89
+ esac
90
+ done <<< "$changed_files"
91
+
92
+ echo "rust_changed=$rust_changed" >> "$GITHUB_OUTPUT"
93
+ echo "python_changed=$python_changed" >> "$GITHUB_OUTPUT"
94
+ echo "js_changed=$js_changed" >> "$GITHUB_OUTPUT"
95
+ echo "docs_changed=$docs_changed" >> "$GITHUB_OUTPUT"
96
+
97
+ echo "Rust: $rust_changed, Python: $python_changed, JS: $js_changed, Docs: $docs_changed"
98
+
99
+ release:
100
+ needs: [check, detect-changes]
101
+ if: >-
102
+ needs.check.outputs.should_release == 'true' &&
103
+ (needs.detect-changes.outputs.rust_changed == 'true' ||
104
+ needs.detect-changes.outputs.python_changed == 'true' ||
105
+ needs.detect-changes.outputs.docs_changed == 'true')
106
+ uses: ./.github/workflows/publish.yml
107
+ with:
108
+ bump_type: patch
109
+ publish_pypi: ${{ needs.detect-changes.outputs.rust_changed == 'true' || needs.detect-changes.outputs.python_changed == 'true' || needs.detect-changes.outputs.docs_changed == 'true' }}
110
+ publish_crates: ${{ needs.detect-changes.outputs.rust_changed == 'true' || needs.detect-changes.outputs.docs_changed == 'true' }}
111
+ secrets: inherit
112
+ permissions:
113
+ contents: write
114
+ id-token: write
115
+
116
+ # npm publish inlined here because npm OIDC doesn't support reusable workflows
117
+ # (it validates the caller workflow name, not the called workflow)
118
+ publish-npm:
119
+ needs: [check, detect-changes, release]
120
+ if: >-
121
+ always() &&
122
+ needs.check.outputs.should_release == 'true' &&
123
+ needs.release.outputs.tag_created == 'true' &&
124
+ (needs.detect-changes.outputs.rust_changed == 'true' ||
125
+ needs.detect-changes.outputs.js_changed == 'true' ||
126
+ needs.detect-changes.outputs.docs_changed == 'true')
127
+ runs-on: ubuntu-latest
128
+ permissions:
129
+ contents: read
130
+ id-token: write
131
+ steps:
132
+ - uses: actions/checkout@v6
133
+
134
+ - name: Check if package.json exists
135
+ id: check_npm
136
+ run: |
137
+ if [ -f package.json ]; then
138
+ echo "exists=true" >> $GITHUB_OUTPUT
139
+ else
140
+ echo "exists=false" >> $GITHUB_OUTPUT
141
+ fi
142
+
143
+ - name: Setup Node.js
144
+ if: steps.check_npm.outputs.exists == 'true'
145
+ uses: actions/setup-node@v4
146
+ with:
147
+ node-version: '24.x'
148
+
149
+ - name: Update package.json version
150
+ if: steps.check_npm.outputs.exists == 'true'
151
+ run: npm version "${{ needs.release.outputs.new_version }}" --no-git-tag-version
152
+
153
+ - name: Publish to npm
154
+ if: steps.check_npm.outputs.exists == 'true'
155
+ run: npm publish --provenance --access public
@@ -7,6 +7,16 @@ on:
7
7
  description: 'Version bump type: patch or minor'
8
8
  required: true
9
9
  type: string
10
+ publish_pypi:
11
+ description: 'Whether to publish to PyPI'
12
+ required: false
13
+ type: boolean
14
+ default: true
15
+ publish_crates:
16
+ description: 'Whether to publish to crates.io'
17
+ required: false
18
+ type: boolean
19
+ default: true
10
20
  outputs:
11
21
  tag_created:
12
22
  description: 'Whether a tag was created'
@@ -127,7 +137,7 @@ jobs:
127
137
 
128
138
  build:
129
139
  needs: [check-python, tag]
130
- if: needs.check-python.outputs.has_python == 'true' && needs.tag.outputs.created == 'true'
140
+ if: needs.check-python.outputs.has_python == 'true' && needs.tag.outputs.created == 'true' && inputs.publish_pypi
131
141
  runs-on: ${{ matrix.os }}
132
142
  strategy:
133
143
  fail-fast: false
@@ -164,7 +174,7 @@ jobs:
164
174
 
165
175
  sdist:
166
176
  needs: [check-python, tag]
167
- if: needs.check-python.outputs.has_python == 'true' && needs.tag.outputs.created == 'true'
177
+ if: needs.check-python.outputs.has_python == 'true' && needs.tag.outputs.created == 'true' && inputs.publish_pypi
168
178
  runs-on: ubuntu-latest
169
179
  steps:
170
180
  - uses: actions/checkout@v6
@@ -187,7 +197,7 @@ jobs:
187
197
 
188
198
  publish-pypi:
189
199
  needs: [tag, build, sdist]
190
- if: always() && needs.tag.outputs.created == 'true' && needs.sdist.result == 'success'
200
+ if: always() && needs.tag.outputs.created == 'true' && needs.sdist.result == 'success' && inputs.publish_pypi
191
201
  runs-on: ubuntu-latest
192
202
  permissions:
193
203
  id-token: write
@@ -211,7 +221,7 @@ jobs:
211
221
 
212
222
  publish-crates:
213
223
  needs: tag
214
- if: needs.tag.outputs.created == 'true'
224
+ if: needs.tag.outputs.created == 'true' && inputs.publish_crates
215
225
  runs-on: ubuntu-latest
216
226
  steps:
217
227
  - uses: actions/checkout@v6
@@ -269,8 +279,8 @@ jobs:
269
279
  if: |
270
280
  always() &&
271
281
  needs.tag.outputs.created == 'true' &&
272
- needs.publish-pypi.result != 'success' &&
273
- needs.publish-crates.result != 'success'
282
+ (needs.publish-pypi.result == 'failure' ||
283
+ needs.publish-crates.result == 'failure')
274
284
  runs-on: ubuntu-latest
275
285
  permissions:
276
286
  contents: write
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.0.13
3
+ Version: 0.0.15
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "dirsql"
7
- version = "0.0.13"
7
+ version = "0.0.15"
8
8
  description = "Ephemeral SQL index over a local directory"
9
9
  license = "MIT"
10
10
  requires-python = ">=3.12"
@@ -36,3 +36,5 @@ exclude = [
36
36
  "AGENTS.md",
37
37
  "justfile",
38
38
  ]
39
+ python-source = "python"
40
+ module-name = "dirsql._dirsql"
@@ -0,0 +1,6 @@
1
+ """dirsql - Ephemeral SQL index over a local directory."""
2
+
3
+ from dirsql._dirsql import DirSQL, Table, RowEvent, __version__
4
+ from dirsql._async import AsyncDirSQL
5
+
6
+ __all__ = ["DirSQL", "Table", "RowEvent", "AsyncDirSQL", "__version__"]
@@ -0,0 +1,63 @@
1
+ """Async wrapper for DirSQL."""
2
+
3
+ import asyncio
4
+
5
+ from dirsql._dirsql import DirSQL
6
+
7
+
8
+ class _WatchStream:
9
+ """Async iterator that polls for file events."""
10
+
11
+ def __init__(self, db):
12
+ self._db = db
13
+ self._started = False
14
+ self._buffer = []
15
+
16
+ def __aiter__(self):
17
+ return self
18
+
19
+ async def __anext__(self):
20
+ if not self._started:
21
+ await asyncio.to_thread(self._db._start_watcher)
22
+ self._started = True
23
+
24
+ while True:
25
+ if self._buffer:
26
+ return self._buffer.pop(0)
27
+ events = await asyncio.to_thread(self._db._poll_events, 200)
28
+ if events:
29
+ self._buffer.extend(events)
30
+
31
+
32
+ class AsyncDirSQL:
33
+ """Async wrapper around DirSQL.
34
+
35
+ Usage:
36
+ db = await AsyncDirSQL(root, tables=[...])
37
+ results = await db.query("SELECT ...")
38
+ async for event in db.watch():
39
+ ...
40
+ """
41
+
42
+ def __init__(self, root, *, tables, ignore=None):
43
+ self._root = root
44
+ self._tables = tables
45
+ self._ignore = ignore
46
+ self._db = None
47
+
48
+ def __await__(self):
49
+ return self._init().__await__()
50
+
51
+ async def _init(self):
52
+ self._db = await asyncio.to_thread(
53
+ DirSQL, self._root, tables=self._tables, ignore=self._ignore
54
+ )
55
+ return self
56
+
57
+ async def query(self, sql):
58
+ """Execute a SQL query asynchronously."""
59
+ return await asyncio.to_thread(self._db.query, sql)
60
+
61
+ def watch(self):
62
+ """Start watching for file changes. Returns an async iterable of RowEvent."""
63
+ return _WatchStream(self._db)