dirsql 0.0.1

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,120 @@
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
+ 6. **Set up cron polling** (via CronCreate, every minute) when waiting for a PR to be merged. The cron prompt should handle the full post-merge cycle: pull, cleanup, monitor release, delete the cron job.
68
+
69
+ ### Post-Merge Cleanup
70
+
71
+ After a PR merges, the agent (or orchestrator) must:
72
+ 1. Pull main in the **root repo**: `git -C /home/duncan/work/code/projects/dirsql pull origin main`
73
+ 2. **Move CWD to root repo first** (CRITICAL -- never remove a worktree from inside it): `cd /home/duncan/work/code/projects/dirsql`
74
+ 3. Remove the worktree: `git worktree remove .worktrees/<name>`
75
+ 4. Delete the local branch: `git branch -d <branch-name>`
76
+ 5. **Verify the bead is addressed** by the merged PR, then close it: `bd close <id>`
77
+
78
+ ## Testing
79
+
80
+ ### Red/Green Development
81
+
82
+ Follow **red/green** (test-first) methodology:
83
+
84
+ 1. **Write the test first** -- it must capture the desired behavior
85
+ 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.
86
+ 3. **Make the minimal change to pass (GREEN)** -- only then write the implementation
87
+ 4. Refactor if needed, keeping tests green
88
+
89
+ ### TDD Order: Outside-In
90
+
91
+ Tests are written **before** implementation, starting from the outermost layer:
92
+
93
+ 1. **Integration test first** -- proves the feature works from the consumer's perspective
94
+ 2. **Unit tests** -- written as you implement each module
95
+
96
+ A feature is not done until integration tests pass and cover the new functionality.
97
+
98
+ ### When to Write What
99
+
100
+ **Does the commit change the public-facing API?**
101
+ - Yes -> **integration test required**, plus unit tests as you go
102
+ - No -> Check if adequate integration coverage already exists:
103
+ - Adequate -> unit tests only
104
+ - Gaps -> add the missing integration tests, plus unit tests
105
+
106
+ **Always write unit tests.** The question is whether you also need integration tests.
107
+
108
+ ### Test Locations
109
+
110
+ - **Unit tests**: Colocated with source
111
+ - Python: `foo.py` -> `foo_test.py` in same directory
112
+ - Rust: inline `#[cfg(test)]` module at bottom of each source file
113
+ - **Integration tests**: `tests/integration/` -- test the Python SDK layer, mock third-party deps (SQLite, LLM calls). Heavy use of pytest fixtures. Run in CI.
114
+ - **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.
115
+
116
+ ### E2E Test Policy
117
+
118
+ 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.
119
+
120
+ 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,306 @@
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
+ build:
28
+ needs: check-python
29
+ if: needs.check-python.outputs.has_python == 'true'
30
+ runs-on: ${{ matrix.os }}
31
+ strategy:
32
+ fail-fast: false
33
+ matrix:
34
+ include:
35
+ - os: ubuntu-latest
36
+ target: x86_64-unknown-linux-gnu
37
+ - os: ubuntu-latest
38
+ target: aarch64-unknown-linux-gnu
39
+ - os: macos-latest
40
+ target: x86_64-apple-darwin
41
+ - os: macos-latest
42
+ target: aarch64-apple-darwin
43
+ - os: windows-latest
44
+ target: x86_64-pc-windows-msvc
45
+ steps:
46
+ - uses: actions/checkout@v6
47
+ with:
48
+ fetch-depth: 0
49
+
50
+ - name: Set up QEMU
51
+ if: runner.os == 'Linux' && matrix.target == 'aarch64-unknown-linux-gnu'
52
+ uses: docker/setup-qemu-action@v3
53
+ with:
54
+ platforms: arm64
55
+
56
+ - uses: PyO3/maturin-action@v1
57
+ with:
58
+ target: ${{ matrix.target }}
59
+ args: --release --out dist
60
+ manylinux: auto
61
+ before-script-linux: |
62
+ # Install OpenSSL dev for cross-compilation
63
+ if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then
64
+ apt-get update && apt-get install -y gcc-aarch64-linux-gnu
65
+ fi
66
+
67
+ - uses: actions/upload-artifact@v4
68
+ with:
69
+ name: wheels-${{ matrix.target }}
70
+ path: dist
71
+
72
+ sdist:
73
+ needs: check-python
74
+ if: needs.check-python.outputs.has_python == 'true'
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - uses: actions/checkout@v6
78
+ with:
79
+ fetch-depth: 0
80
+
81
+ - uses: PyO3/maturin-action@v1
82
+ with:
83
+ command: sdist
84
+ args: --out dist
85
+
86
+ - uses: actions/upload-artifact@v4
87
+ with:
88
+ name: sdist
89
+ path: dist
90
+
91
+ tag:
92
+ runs-on: ubuntu-latest
93
+ permissions:
94
+ contents: write
95
+ outputs:
96
+ created: ${{ steps.tag.outputs.created }}
97
+ new_version: ${{ steps.version.outputs.new_version }}
98
+ steps:
99
+ - uses: actions/checkout@v6
100
+ with:
101
+ fetch-depth: 0
102
+
103
+ - name: Get current version
104
+ id: current
105
+ run: |
106
+ latest_tag=$(git tag --sort=-v:refname | grep -E '^v[0-9]' | head -n1)
107
+
108
+ if [ -z "$latest_tag" ]; then
109
+ echo "No tags found"
110
+ echo "version=0.0.0" >> $GITHUB_OUTPUT
111
+ echo "has_tags=false" >> $GITHUB_OUTPUT
112
+ else
113
+ echo "Latest tag: $latest_tag"
114
+ echo "version=${latest_tag#v}" >> $GITHUB_OUTPUT
115
+ echo "has_tags=true" >> $GITHUB_OUTPUT
116
+
117
+ commits_since_tag=$(git rev-list ${latest_tag}..HEAD --count)
118
+ echo "commits_since_tag=$commits_since_tag" >> $GITHUB_OUTPUT
119
+ fi
120
+
121
+ - name: Check for changes (patch only)
122
+ if: inputs.bump_type == 'patch' && steps.current.outputs.has_tags == 'true'
123
+ id: check_changes
124
+ run: |
125
+ if [ "${{ steps.current.outputs.commits_since_tag }}" -eq 0 ]; then
126
+ echo "No new commits since last tag, skipping release"
127
+ echo "should_release=false" >> $GITHUB_OUTPUT
128
+ else
129
+ echo "Found ${{ steps.current.outputs.commits_since_tag }} commits since last tag"
130
+ echo "should_release=true" >> $GITHUB_OUTPUT
131
+ fi
132
+
133
+ - name: Calculate new version
134
+ id: version
135
+ run: |
136
+ current="${{ steps.current.outputs.version }}"
137
+ IFS='.' read -r major minor patch <<< "$current"
138
+
139
+ if [ "${{ inputs.bump_type }}" == "minor" ]; then
140
+ new_version="${major}.$((minor + 1)).0"
141
+ else
142
+ new_version="${major}.${minor}.$((patch + 1))"
143
+ fi
144
+
145
+ echo "new_version=$new_version" >> $GITHUB_OUTPUT
146
+ echo "New version will be: $new_version"
147
+
148
+ - name: Check if tag exists
149
+ id: tag_check
150
+ run: |
151
+ if git ls-remote --tags origin | grep -q "refs/tags/v${{ steps.version.outputs.new_version }}$"; then
152
+ echo "Tag v${{ steps.version.outputs.new_version }} already exists on remote"
153
+ echo "exists=true" >> $GITHUB_OUTPUT
154
+ else
155
+ echo "exists=false" >> $GITHUB_OUTPUT
156
+ fi
157
+
158
+ - name: Determine if release should proceed
159
+ id: should_release
160
+ run: |
161
+ if [ "${{ steps.tag_check.outputs.exists }}" == "true" ]; then
162
+ echo "proceed=false" >> $GITHUB_OUTPUT
163
+ exit 0
164
+ fi
165
+
166
+ if [ "${{ inputs.bump_type }}" == "patch" ] && [ "${{ steps.current.outputs.has_tags }}" == "true" ]; then
167
+ if [ "${{ steps.check_changes.outputs.should_release }}" == "false" ]; then
168
+ echo "proceed=false" >> $GITHUB_OUTPUT
169
+ exit 0
170
+ fi
171
+ fi
172
+
173
+ echo "proceed=true" >> $GITHUB_OUTPUT
174
+
175
+ - name: Create and push tag
176
+ if: steps.should_release.outputs.proceed == 'true'
177
+ id: tag
178
+ run: |
179
+ git config user.name "github-actions[bot]"
180
+ git config user.email "github-actions[bot]@users.noreply.github.com"
181
+ git tag -a "v${{ steps.version.outputs.new_version }}" -m "Release v${{ steps.version.outputs.new_version }}"
182
+ git push origin "v${{ steps.version.outputs.new_version }}"
183
+ echo "created=true" >> $GITHUB_OUTPUT
184
+
185
+ publish-pypi:
186
+ needs: [tag, build, sdist]
187
+ if: always() && needs.tag.outputs.created == 'true' && needs.sdist.result == 'success'
188
+ runs-on: ubuntu-latest
189
+ permissions:
190
+ id-token: write
191
+ environment: release
192
+ steps:
193
+ - uses: actions/download-artifact@v4
194
+ with:
195
+ pattern: wheels-*
196
+ merge-multiple: true
197
+ path: dist
198
+
199
+ - uses: actions/download-artifact@v4
200
+ with:
201
+ name: sdist
202
+ path: dist
203
+
204
+ - name: Publish to PyPI
205
+ uses: pypa/gh-action-pypi-publish@release/v1
206
+
207
+ publish-crates:
208
+ needs: tag
209
+ if: needs.tag.outputs.created == 'true'
210
+ runs-on: ubuntu-latest
211
+ steps:
212
+ - uses: actions/checkout@v6
213
+ with:
214
+ fetch-depth: 0
215
+
216
+ - name: Install Rust
217
+ uses: dtolnay/rust-toolchain@stable
218
+
219
+ - name: Update Cargo.toml version
220
+ run: |
221
+ sed -i 's/^version = ".*"/version = "${{ needs.tag.outputs.new_version }}"/' Cargo.toml
222
+
223
+ - name: Publish to crates.io
224
+ env:
225
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
226
+ run: |
227
+ for attempt in 1 2 3; do
228
+ echo "Attempt $attempt of 3"
229
+ if cargo publish --allow-dirty; then
230
+ echo "Published successfully"
231
+ break
232
+ fi
233
+ if [ "$attempt" -lt 3 ]; then
234
+ echo "Publish failed, retrying in 15s..."
235
+ sleep 15
236
+ else
237
+ echo "All attempts failed"
238
+ exit 1
239
+ fi
240
+ done
241
+
242
+ publish-npm:
243
+ needs: tag
244
+ if: needs.tag.outputs.created == 'true'
245
+ runs-on: ubuntu-latest
246
+ permissions:
247
+ contents: read
248
+ id-token: write
249
+ steps:
250
+ - uses: actions/checkout@v6
251
+
252
+ - name: Check if package.json exists
253
+ id: check
254
+ run: |
255
+ if [ -f package.json ]; then
256
+ echo "exists=true" >> $GITHUB_OUTPUT
257
+ else
258
+ echo "exists=false" >> $GITHUB_OUTPUT
259
+ fi
260
+
261
+ - name: Setup Node.js
262
+ if: steps.check.outputs.exists == 'true'
263
+ uses: actions/setup-node@v4
264
+ with:
265
+ node-version: '20'
266
+ registry-url: https://registry.npmjs.org
267
+
268
+ - name: Publish to npm
269
+ if: steps.check.outputs.exists == 'true'
270
+ env:
271
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
272
+ run: npm publish --provenance --access public
273
+
274
+ github-release:
275
+ needs: [tag, publish-crates]
276
+ if: always() && needs.tag.outputs.created == 'true' && (needs.publish-crates.result == 'success' || needs.publish-crates.result == 'skipped')
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, github-release]
296
+ if: failure() && needs.tag.outputs.created == 'true'
297
+ runs-on: ubuntu-latest
298
+ permissions:
299
+ contents: write
300
+ steps:
301
+ - uses: actions/checkout@v6
302
+ with:
303
+ fetch-depth: 0
304
+
305
+ - name: Rollback tag on failure
306
+ 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