dirsql 0.0.5__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.
@@ -0,0 +1,119 @@
1
+ # dirsql Development
2
+
3
+ ## Scratch Files
4
+
5
+ Write scratch/temporary files to `/tmp` instead of asking permission. Use unique filenames to avoid collisions with other sessions.
6
+
7
+ ## Workflow
8
+
9
+ - Work in git worktrees under `.worktrees/` folder
10
+ - **NEVER commit directly to main** - always create a PR
11
+ - One PR per bead. Beads should be concise and small -- as small as possible while still being useful
12
+ - Use `bd` (Beads) for task tracking: `bd list`, `bd show <id>`, `bd ready`
13
+
14
+ ### Git Worktrees
15
+
16
+ **ALL work happens in git worktrees.** Never edit files in the root repo directory. Never commit outside a worktree.
17
+
18
+ #### Creating a Worktree
19
+
20
+ ```bash
21
+ git worktree add .worktrees/my-feature -b feat/my-feature
22
+ cd .worktrees/my-feature
23
+ ```
24
+
25
+ #### Removing a Worktree
26
+
27
+ **DANGER: removing a worktree while your shell CWD is inside it permanently breaks the shell.** The ONLY safe procedure:
28
+
29
+ ```bash
30
+ # Step 1: Move CWD to the root repo FIRST (not optional)
31
+ cd /home/duncan/work/code/projects/dirsql
32
+
33
+ # Step 2: Now remove the worktree
34
+ git worktree remove .worktrees/my-feature
35
+ ```
36
+
37
+ **Do NOT skip step 1. Do NOT substitute `git -C` for `cd`.**
38
+
39
+ ### Beads Workflow
40
+
41
+ **Lifecycle:**
42
+ 1. **Claim it FIRST**: `bd update <id> --claim` before any work
43
+ 2. **Create worktree and branch**
44
+ 3. **Link the PR**: `bd update <id> --external-ref "gh-<pr-number>"` after creating the PR
45
+ 4. **Close**: `bd close <id>` immediately after the PR is merged
46
+
47
+ ### Subagent Workflow
48
+
49
+ 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:
54
+ - CI failures
55
+ - GPG signing complaints
56
+ - Merge conflicts
57
+ 5. Continues monitoring until the PR is in a mergeable state
58
+
59
+ ### Orchestrator Responsibilities
60
+
61
+ The orchestrator (main Claude session) must proactively:
62
+ 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.
63
+ 2. **Fix CI failures** on open PRs immediately, either directly or by dispatching a fix agent.
64
+ 3. **Handle post-merge cleanup** as soon as a PR merges (pull main, remove worktree, delete branch, close bead).
65
+ 4. **Keep the user informed** of PR status without being asked.
66
+ 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.
67
+
68
+ ### Post-Merge Cleanup
69
+
70
+ After a PR merges, the agent (or orchestrator) must:
71
+ 1. Pull main in the **root repo**: `git -C /home/duncan/work/code/projects/dirsql pull origin main`
72
+ 2. **Move CWD to root repo first** (CRITICAL -- never remove a worktree from inside it): `cd /home/duncan/work/code/projects/dirsql`
73
+ 3. Remove the worktree: `git worktree remove .worktrees/<name>`
74
+ 4. Delete the local branch: `git branch -d <branch-name>`
75
+ 5. **Verify the bead is addressed** by the merged PR, then close it: `bd close <id>`
76
+
77
+ ## Testing
78
+
79
+ ### Red/Green Development
80
+
81
+ Follow **red/green** (test-first) methodology:
82
+
83
+ 1. **Write the test first** -- it must capture the desired behavior
84
+ 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.
85
+ 3. **Make the minimal change to pass (GREEN)** -- only then write the implementation
86
+ 4. Refactor if needed, keeping tests green
87
+
88
+ ### TDD Order: Outside-In
89
+
90
+ Tests are written **before** implementation, starting from the outermost layer:
91
+
92
+ 1. **Integration test first** -- proves the feature works from the consumer's perspective
93
+ 2. **Unit tests** -- written as you implement each module
94
+
95
+ A feature is not done until integration tests pass and cover the new functionality.
96
+
97
+ ### When to Write What
98
+
99
+ **Does the commit change the public-facing API?**
100
+ - Yes -> **integration test required**, plus unit tests as you go
101
+ - No -> Check if adequate integration coverage already exists:
102
+ - Adequate -> unit tests only
103
+ - Gaps -> add the missing integration tests, plus unit tests
104
+
105
+ **Always write unit tests.** The question is whether you also need integration tests.
106
+
107
+ ### Test Locations
108
+
109
+ - **Unit tests**: Colocated with source
110
+ - Python: `foo.py` -> `foo_test.py` in same directory
111
+ - Rust: inline `#[cfg(test)]` module at bottom of each source file
112
+ - **Integration tests**: `tests/integration/` -- test the Python SDK layer, mock third-party deps (SQLite, LLM calls). Heavy use of pytest fixtures. Run in CI.
113
+ - **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.
114
+
115
+ ### E2E Test Policy
116
+
117
+ 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.
118
+
119
+ See skillet or karat for examples of test organization, fixtures, and pytest-describe patterns.
@@ -0,0 +1,14 @@
1
+ name: Minor Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ jobs:
7
+ release:
8
+ uses: ./.github/workflows/publish.yml
9
+ with:
10
+ bump_type: minor
11
+ secrets: inherit
12
+ permissions:
13
+ contents: write
14
+ id-token: write
@@ -0,0 +1,45 @@
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
+ jobs:
12
+ check:
13
+ runs-on: ubuntu-latest
14
+ outputs:
15
+ should_release: ${{ steps.decide.outputs.should_release }}
16
+ steps:
17
+ - id: decide
18
+ env:
19
+ EVENT: ${{ github.event_name }}
20
+ STRATEGY: ${{ vars.RELEASE_STRATEGY }}
21
+ COMMIT_MSG: ${{ github.event.head_commit.message }}
22
+ run: |
23
+ if [ "$EVENT" = "workflow_dispatch" ]; then
24
+ echo "should_release=true" >> "$GITHUB_OUTPUT"
25
+ elif [ "$EVENT" = "schedule" ] && [ "$STRATEGY" != "immediate" ]; then
26
+ echo "should_release=true" >> "$GITHUB_OUTPUT"
27
+ elif [ "$EVENT" = "push" ] && [ "$STRATEGY" = "immediate" ]; then
28
+ case "$COMMIT_MSG" in
29
+ *'[no-release]'*) echo "should_release=false" >> "$GITHUB_OUTPUT" ;;
30
+ *) echo "should_release=true" >> "$GITHUB_OUTPUT" ;;
31
+ esac
32
+ else
33
+ echo "should_release=false" >> "$GITHUB_OUTPUT"
34
+ fi
35
+
36
+ release:
37
+ needs: check
38
+ if: needs.check.outputs.should_release == 'true'
39
+ uses: ./.github/workflows/publish.yml
40
+ with:
41
+ bump_type: patch
42
+ secrets: inherit
43
+ permissions:
44
+ contents: write
45
+ id-token: write
@@ -0,0 +1,16 @@
1
+ name: PR Monitor
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ permissions:
7
+ checks: read
8
+
9
+ jobs:
10
+ monitor:
11
+ name: 'CI Gate'
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: clankerbot/pr-monitor@v1
15
+ with:
16
+ job-name: 'CI Gate'
@@ -0,0 +1,311 @@
1
+ name: Publish Release
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ bump_type:
7
+ description: 'Version bump type: patch or minor'
8
+ required: true
9
+ type: string
10
+
11
+ jobs:
12
+ check-python:
13
+ runs-on: ubuntu-latest
14
+ outputs:
15
+ has_python: ${{ steps.check.outputs.has_python }}
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ - name: Check for pyproject.toml
19
+ id: check
20
+ run: |
21
+ if [ -f pyproject.toml ]; then
22
+ echo "has_python=true" >> $GITHUB_OUTPUT
23
+ else
24
+ echo "has_python=false" >> $GITHUB_OUTPUT
25
+ fi
26
+
27
+ tag:
28
+ runs-on: ubuntu-latest
29
+ permissions:
30
+ contents: write
31
+ outputs:
32
+ created: ${{ steps.tag.outputs.created }}
33
+ new_version: ${{ steps.version.outputs.new_version }}
34
+ steps:
35
+ - uses: actions/checkout@v6
36
+ with:
37
+ fetch-depth: 0
38
+
39
+ - name: Get current version
40
+ id: current
41
+ run: |
42
+ latest_tag=$(git tag --sort=-v:refname | grep -E '^v[0-9]' | head -n1)
43
+
44
+ if [ -z "$latest_tag" ]; then
45
+ echo "No tags found"
46
+ echo "version=0.0.0" >> $GITHUB_OUTPUT
47
+ echo "has_tags=false" >> $GITHUB_OUTPUT
48
+ else
49
+ echo "Latest tag: $latest_tag"
50
+ echo "version=${latest_tag#v}" >> $GITHUB_OUTPUT
51
+ echo "has_tags=true" >> $GITHUB_OUTPUT
52
+
53
+ commits_since_tag=$(git rev-list ${latest_tag}..HEAD --count)
54
+ echo "commits_since_tag=$commits_since_tag" >> $GITHUB_OUTPUT
55
+ fi
56
+
57
+ - name: Check for changes (patch only)
58
+ if: inputs.bump_type == 'patch' && steps.current.outputs.has_tags == 'true'
59
+ id: check_changes
60
+ run: |
61
+ if [ "${{ steps.current.outputs.commits_since_tag }}" -eq 0 ]; then
62
+ echo "No new commits since last tag, skipping release"
63
+ echo "should_release=false" >> $GITHUB_OUTPUT
64
+ else
65
+ echo "Found ${{ steps.current.outputs.commits_since_tag }} commits since last tag"
66
+ echo "should_release=true" >> $GITHUB_OUTPUT
67
+ fi
68
+
69
+ - name: Calculate new version
70
+ id: version
71
+ run: |
72
+ current="${{ steps.current.outputs.version }}"
73
+ IFS='.' read -r major minor patch <<< "$current"
74
+
75
+ if [ "${{ inputs.bump_type }}" == "minor" ]; then
76
+ new_version="${major}.$((minor + 1)).0"
77
+ else
78
+ new_version="${major}.${minor}.$((patch + 1))"
79
+ fi
80
+
81
+ echo "new_version=$new_version" >> $GITHUB_OUTPUT
82
+ echo "New version will be: $new_version"
83
+
84
+ - name: Check if tag exists
85
+ id: tag_check
86
+ run: |
87
+ if git ls-remote --tags origin | grep -q "refs/tags/v${{ steps.version.outputs.new_version }}$"; then
88
+ echo "Tag v${{ steps.version.outputs.new_version }} already exists on remote"
89
+ echo "exists=true" >> $GITHUB_OUTPUT
90
+ else
91
+ echo "exists=false" >> $GITHUB_OUTPUT
92
+ fi
93
+
94
+ - name: Determine if release should proceed
95
+ id: should_release
96
+ run: |
97
+ if [ "${{ steps.tag_check.outputs.exists }}" == "true" ]; then
98
+ echo "proceed=false" >> $GITHUB_OUTPUT
99
+ exit 0
100
+ fi
101
+
102
+ if [ "${{ inputs.bump_type }}" == "patch" ] && [ "${{ steps.current.outputs.has_tags }}" == "true" ]; then
103
+ if [ "${{ steps.check_changes.outputs.should_release }}" == "false" ]; then
104
+ echo "proceed=false" >> $GITHUB_OUTPUT
105
+ exit 0
106
+ fi
107
+ fi
108
+
109
+ echo "proceed=true" >> $GITHUB_OUTPUT
110
+
111
+ - name: Create and push tag
112
+ if: steps.should_release.outputs.proceed == 'true'
113
+ id: tag
114
+ run: |
115
+ git config user.name "github-actions[bot]"
116
+ git config user.email "github-actions[bot]@users.noreply.github.com"
117
+ git tag -a "v${{ steps.version.outputs.new_version }}" -m "Release v${{ steps.version.outputs.new_version }}"
118
+ git push origin "v${{ steps.version.outputs.new_version }}"
119
+ echo "created=true" >> $GITHUB_OUTPUT
120
+
121
+ build:
122
+ needs: [check-python, tag]
123
+ if: needs.check-python.outputs.has_python == 'true' && needs.tag.outputs.created == 'true'
124
+ runs-on: ${{ matrix.os }}
125
+ strategy:
126
+ fail-fast: false
127
+ matrix:
128
+ include:
129
+ - os: ubuntu-latest
130
+ target: x86_64-unknown-linux-gnu
131
+ - os: macos-latest
132
+ target: x86_64-apple-darwin
133
+ - os: macos-latest
134
+ target: aarch64-apple-darwin
135
+ - os: windows-latest
136
+ target: x86_64-pc-windows-msvc
137
+ steps:
138
+ - uses: actions/checkout@v6
139
+ with:
140
+ fetch-depth: 0
141
+
142
+ - name: Update pyproject.toml version
143
+ shell: bash
144
+ run: |
145
+ sed -i.bak 's/^version = ".*"/version = "${{ needs.tag.outputs.new_version }}"/' pyproject.toml
146
+
147
+ - uses: PyO3/maturin-action@v1
148
+ with:
149
+ target: ${{ matrix.target }}
150
+ args: --release --out dist
151
+ manylinux: auto
152
+
153
+ - uses: actions/upload-artifact@v4
154
+ with:
155
+ name: wheels-${{ matrix.target }}
156
+ path: dist
157
+
158
+ sdist:
159
+ needs: [check-python, tag]
160
+ if: needs.check-python.outputs.has_python == 'true' && needs.tag.outputs.created == 'true'
161
+ runs-on: ubuntu-latest
162
+ steps:
163
+ - uses: actions/checkout@v6
164
+ with:
165
+ fetch-depth: 0
166
+
167
+ - name: Update pyproject.toml version
168
+ run: |
169
+ sed -i 's/^version = ".*"/version = "${{ needs.tag.outputs.new_version }}"/' pyproject.toml
170
+
171
+ - uses: PyO3/maturin-action@v1
172
+ with:
173
+ command: sdist
174
+ args: --out dist
175
+
176
+ - uses: actions/upload-artifact@v4
177
+ with:
178
+ name: sdist
179
+ path: dist
180
+
181
+ publish-pypi:
182
+ needs: [tag, build, sdist]
183
+ if: always() && needs.tag.outputs.created == 'true' && needs.sdist.result == 'success'
184
+ runs-on: ubuntu-latest
185
+ permissions:
186
+ id-token: write
187
+ environment: release
188
+ steps:
189
+ - uses: actions/download-artifact@v4
190
+ with:
191
+ pattern: wheels-*
192
+ merge-multiple: true
193
+ path: dist
194
+
195
+ - uses: actions/download-artifact@v4
196
+ with:
197
+ name: sdist
198
+ path: dist
199
+
200
+ - name: Publish to PyPI
201
+ uses: pypa/gh-action-pypi-publish@release/v1
202
+ with:
203
+ attestations: false
204
+
205
+ publish-crates:
206
+ needs: tag
207
+ if: needs.tag.outputs.created == 'true'
208
+ runs-on: ubuntu-latest
209
+ steps:
210
+ - uses: actions/checkout@v6
211
+ with:
212
+ fetch-depth: 0
213
+
214
+ - name: Install Rust
215
+ uses: dtolnay/rust-toolchain@stable
216
+
217
+ - name: Update Cargo.toml version
218
+ run: |
219
+ sed -i 's/^version = ".*"/version = "${{ needs.tag.outputs.new_version }}"/' Cargo.toml
220
+
221
+ - name: Publish to crates.io
222
+ env:
223
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
224
+ run: |
225
+ for attempt in 1 2 3; do
226
+ echo "Attempt $attempt of 3"
227
+ if cargo publish --allow-dirty; then
228
+ echo "Published successfully"
229
+ break
230
+ fi
231
+ if [ "$attempt" -lt 3 ]; then
232
+ echo "Publish failed, retrying in 15s..."
233
+ sleep 15
234
+ else
235
+ echo "All attempts failed"
236
+ exit 1
237
+ fi
238
+ done
239
+
240
+ publish-npm:
241
+ needs: tag
242
+ if: needs.tag.outputs.created == 'true'
243
+ runs-on: ubuntu-latest
244
+ permissions:
245
+ contents: read
246
+ id-token: write
247
+ steps:
248
+ - uses: actions/checkout@v6
249
+
250
+ - name: Check if package.json exists
251
+ id: check
252
+ run: |
253
+ if [ -f package.json ]; then
254
+ echo "exists=true" >> $GITHUB_OUTPUT
255
+ else
256
+ echo "exists=false" >> $GITHUB_OUTPUT
257
+ fi
258
+
259
+ - name: Setup Node.js
260
+ if: steps.check.outputs.exists == 'true'
261
+ uses: actions/setup-node@v4
262
+ with:
263
+ node-version: '20'
264
+ registry-url: https://registry.npmjs.org
265
+
266
+ - name: Update package.json version
267
+ if: steps.check.outputs.exists == 'true'
268
+ run: npm version "${{ needs.tag.outputs.new_version }}" --no-git-tag-version
269
+
270
+ - name: Publish to npm
271
+ if: steps.check.outputs.exists == 'true'
272
+ run: npm publish --provenance --access public
273
+
274
+ github-release:
275
+ needs: [tag, publish-pypi, publish-crates, publish-npm]
276
+ if: always() && needs.tag.outputs.created == 'true'
277
+ runs-on: ubuntu-latest
278
+ permissions:
279
+ contents: write
280
+ steps:
281
+ - uses: actions/checkout@v6
282
+ with:
283
+ fetch-depth: 0
284
+
285
+ - name: Create GitHub Release
286
+ env:
287
+ GH_TOKEN: ${{ github.token }}
288
+ run: |
289
+ gh release create "v${{ needs.tag.outputs.new_version }}" \
290
+ --repo ${{ github.repository }} \
291
+ --title "v${{ needs.tag.outputs.new_version }}" \
292
+ --generate-notes
293
+
294
+ rollback:
295
+ needs: [tag, publish-pypi, publish-crates, publish-npm]
296
+ if: |
297
+ always() &&
298
+ needs.tag.outputs.created == 'true' &&
299
+ needs.publish-pypi.result != 'success' &&
300
+ needs.publish-crates.result != 'success' &&
301
+ needs.publish-npm.result != 'success'
302
+ runs-on: ubuntu-latest
303
+ permissions:
304
+ contents: write
305
+ steps:
306
+ - uses: actions/checkout@v6
307
+ with:
308
+ fetch-depth: 0
309
+
310
+ - name: Rollback tag on failure
311
+ run: git push --delete origin "v${{ needs.tag.outputs.new_version }}"
@@ -0,0 +1,35 @@
1
+ name: Python Lint
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - '**.py'
8
+ - 'pyproject.toml'
9
+ - 'uv.lock'
10
+ pull_request:
11
+ paths:
12
+ - '**.py'
13
+ - 'pyproject.toml'
14
+ - 'uv.lock'
15
+
16
+ jobs:
17
+ lint:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v6
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v7
24
+
25
+ - name: Install just
26
+ uses: extractions/setup-just@v2
27
+
28
+ - name: Install dependencies
29
+ run: uv sync --extra dev
30
+
31
+ - name: Lint
32
+ run: uv run just lint
33
+
34
+ - name: Format check
35
+ run: uv run just format-check
@@ -0,0 +1,45 @@
1
+ name: Python Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - '**.py'
8
+ - 'pyproject.toml'
9
+ - 'uv.lock'
10
+ - 'tests/**'
11
+ pull_request:
12
+ paths:
13
+ - '**.py'
14
+ - 'pyproject.toml'
15
+ - 'uv.lock'
16
+ - 'tests/**'
17
+
18
+ jobs:
19
+ test:
20
+ runs-on: ubuntu-latest
21
+ strategy:
22
+ matrix:
23
+ python-version: ["3.12", "3.13"]
24
+ steps:
25
+ - uses: actions/checkout@v6
26
+
27
+ - name: Install Rust
28
+ uses: dtolnay/rust-toolchain@stable
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@v7
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+
35
+ - name: Install just
36
+ uses: extractions/setup-just@v2
37
+
38
+ - name: Install dependencies
39
+ run: uv sync --extra dev
40
+
41
+ - name: Build Rust extension
42
+ run: uv run maturin develop
43
+
44
+ - name: Run tests
45
+ run: uv run just test-ci
@@ -0,0 +1,41 @@
1
+ name: Rust Test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - '**.rs'
8
+ - 'Cargo.toml'
9
+ - 'Cargo.lock'
10
+ pull_request:
11
+ paths:
12
+ - '**.rs'
13
+ - 'Cargo.toml'
14
+ - 'Cargo.lock'
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v6
21
+
22
+ - name: Install Rust
23
+ uses: dtolnay/rust-toolchain@stable
24
+
25
+ - name: Cache cargo
26
+ uses: actions/cache@v4
27
+ with:
28
+ path: |
29
+ ~/.cargo/registry
30
+ ~/.cargo/git
31
+ target
32
+ key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
33
+
34
+ - name: Run tests
35
+ run: cargo test
36
+
37
+ - name: Clippy
38
+ run: cargo clippy -- -D warnings
39
+
40
+ - name: Format check
41
+ run: cargo fmt -- --check
@@ -0,0 +1,8 @@
1
+ /target
2
+ .beads
3
+ .claude/settings.local.json
4
+ AGENTS.md
5
+
6
+ # Dolt database files (added by bd init)
7
+ .dolt/
8
+ *.db