From 63b76f844401bd04583227c5a99f8f23a43c5b03 Mon Sep 17 00:00:00 2001 From: Sibin Grasic Date: Mon, 1 Jun 2026 01:51:24 +0200 Subject: [PATCH 1/5] ci: add release build matrix and Linux packaging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the build half of the release pipeline (apps-k6w) plus native Linux packaging (apps-e6y): - Matrix workflow at .github/workflows/release.yml builds for five targets: x86_64/aarch64 musl Linux (via cargo-zigbuild), Intel and Apple Silicon macOS, and x86_64 Windows MSVC. Each target produces a tar.gz/zip containing the static binary plus README and both LICENSE files, uploaded as a workflow artifact. - For both Linux targets, additionally build a .deb (cargo-deb) and .rpm (cargo-generate-rpm) using the metadata blocks added to Cargo.toml, installing the binary to /usr/bin and docs to /usr/share/doc/transliterator/. - New smoke-test job installs and runs the amd64/x86_64 packages inside debian:stable-slim and fedora:latest containers, asserting that `echo "Београд" | transliterate lat` prints "Beograd"; the arm64/aarch64 packages are validated via dpkg/rpm metadata inspection (full install would need QEMU). - Triggers: workflow_call (for the future semantic-release orchestrator), workflow_dispatch, and pull_request (so a broken matrix can't land on master). - Adds README.md, LICENSE-MIT, and LICENSE-APACHE — required by both the archive contents and the Linux package assets. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/release.yml | 248 ++++++++++++++++++++++++++++++++++ Cargo.toml | 29 ++++ LICENSE-APACHE | 201 +++++++++++++++++++++++++++ LICENSE-MIT | 21 +++ README.md | 46 +++++++ 5 files changed, 545 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b3a519f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,248 @@ +name: release-build + +# Build matrix that produces one signed-off artifact per supported target. +# +# Triggered three ways: +# * workflow_call — invoked by the semantic-release orchestrator (apps-l5f). +# * workflow_dispatch — manual runs from the Actions tab. +# * pull_request — guards every PR so a broken matrix can't land on master. + +on: + workflow_call: + inputs: + ref: + description: Git ref (tag or SHA) to build. + required: false + type: string + workflow_dispatch: + inputs: + ref: + description: Git ref to build (defaults to the workflow's ref). + required: false + type: string + pull_request: + paths: + - 'Cargo.toml' + - 'Cargo.lock' + - 'src/**' + - '.github/workflows/release.yml' + +permissions: + contents: read + +concurrency: + group: release-build-${{ inputs.ref || github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build: + name: ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-musl + os: ubuntu-latest + archive: tar.gz + zigbuild: true + - target: aarch64-unknown-linux-musl + os: ubuntu-latest + archive: tar.gz + zigbuild: true + - target: x86_64-apple-darwin + os: macos-13 + archive: tar.gz + zigbuild: false + - target: aarch64-apple-darwin + os: macos-latest + archive: tar.gz + zigbuild: false + - target: x86_64-pc-windows-msvc + os: windows-latest + archive: zip + zigbuild: false + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install cargo-zigbuild + if: matrix.zigbuild + uses: taiki-e/install-action@v2 + with: + tool: cargo-zigbuild + + - name: Install Linux packaging tools + if: matrix.zigbuild + uses: taiki-e/install-action@v2 + with: + tool: cargo-deb,cargo-generate-rpm + + - name: Cache cargo build + uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.target }} + + - name: Build (cargo zigbuild) + if: matrix.zigbuild + run: cargo zigbuild --release --locked --target ${{ matrix.target }} + + - name: Build (cargo build) + if: ${{ !matrix.zigbuild }} + shell: bash + run: cargo build --release --locked --target ${{ matrix.target }} + + - name: Verify Linux binary is statically linked + if: matrix.zigbuild + run: | + set -euo pipefail + bin="target/${{ matrix.target }}/release/transliterate" + file "$bin" + # `file` works for both x86_64 and aarch64 (no exec required). + # `ldd` is also documented in the acceptance criteria; it only + # runs natively on x86_64 hosts, so guard it on the target name. + file "$bin" | grep -Eq 'statically linked|static-pie linked' + if [[ "${{ matrix.target }}" == x86_64-* ]]; then + ldd "$bin" 2>&1 | tee /tmp/ldd.out + grep -q 'not a dynamic executable\|statically linked' /tmp/ldd.out + fi + + - name: Stage release archive + id: stage + shell: bash + env: + TARGET: ${{ matrix.target }} + ARCHIVE_KIND: ${{ matrix.archive }} + run: | + set -euo pipefail + stage="transliterator-${TARGET}" + mkdir -p "dist/${stage}" + if [[ "$RUNNER_OS" == "Windows" ]]; then + bin_src="target/${TARGET}/release/transliterate.exe" + else + bin_src="target/${TARGET}/release/transliterate" + fi + cp "$bin_src" "dist/${stage}/" + cp README.md LICENSE-MIT LICENSE-APACHE "dist/${stage}/" + cd dist + case "$ARCHIVE_KIND" in + tar.gz) + tar -czf "${stage}.tar.gz" "${stage}" + archive_path="dist/${stage}.tar.gz" + archive_name="${stage}.tar.gz" + ;; + zip) + 7z a -bd "${stage}.zip" "${stage}" >/dev/null + archive_path="dist/${stage}.zip" + archive_name="${stage}.zip" + ;; + *) + echo "Unknown archive kind: ${ARCHIVE_KIND}" >&2 + exit 1 + ;; + esac + echo "archive_path=${archive_path}" >> "$GITHUB_OUTPUT" + echo "archive_name=${archive_name}" >> "$GITHUB_OUTPUT" + + - name: Upload tarball/zip artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.stage.outputs.archive_name }} + path: ${{ steps.stage.outputs.archive_path }} + if-no-files-found: error + retention-days: 7 + + - name: Build .deb + if: matrix.zigbuild + run: cargo deb --no-build --no-strip --target ${{ matrix.target }} + + - name: Build .rpm + if: matrix.zigbuild + run: cargo generate-rpm --target ${{ matrix.target }} + + - name: Stage Linux packages + if: matrix.zigbuild + shell: bash + run: | + set -euo pipefail + mkdir -p dist-pkg + # cargo-deb writes to target//debian/, cargo-generate-rpm to + # target//generate-rpm/. + cp target/${{ matrix.target }}/debian/*.deb dist-pkg/ + cp target/${{ matrix.target }}/generate-rpm/*.rpm dist-pkg/ + ls -la dist-pkg/ + + - name: Upload Linux package artifacts + if: matrix.zigbuild + uses: actions/upload-artifact@v4 + with: + name: linux-packages-${{ matrix.target }} + path: | + dist-pkg/*.deb + dist-pkg/*.rpm + if-no-files-found: error + retention-days: 7 + + smoke-test: + name: smoke-test (${{ matrix.distro }}) + needs: build + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - distro: debian:stable-slim + kind: deb + install: dpkg -i + - distro: fedora:latest + kind: rpm + install: rpm -i + steps: + - name: Download amd64/x86_64 Linux packages + uses: actions/download-artifact@v4 + with: + name: linux-packages-x86_64-unknown-linux-musl + path: pkgs + + - name: Install and run inside ${{ matrix.distro }} + run: | + set -euo pipefail + docker run --rm -v "$PWD/pkgs:/pkgs" ${{ matrix.distro }} bash -eu -c ' + ${{ matrix.install }} /pkgs/*.${{ matrix.kind }} + out=$(echo "Београд" | transliterate lat) + echo "got: $out" + test "$out" = "Beograd" + ' + + # arm64 install needs QEMU; metadata inspection is enough to prove the + # package is well-formed and architecture-tagged correctly. + - name: Download arm64/aarch64 Linux packages + uses: actions/download-artifact@v4 + with: + name: linux-packages-aarch64-unknown-linux-musl + path: arm-pkgs + + - name: Inspect aarch64 package metadata + run: | + set -euo pipefail + if [[ "${{ matrix.kind }}" == "deb" ]]; then + for f in arm-pkgs/*.deb; do + echo "== $f ==" + dpkg --info "$f" + dpkg --info "$f" | grep -E 'Architecture: arm64' + done + else + sudo apt-get update -qq && sudo apt-get install -y -qq rpm + for f in arm-pkgs/*.rpm; do + echo "== $f ==" + rpm -qpi "$f" + rpm -qp --qf '%{ARCH}\n' "$f" | grep -E '^aarch64$' + done + fi diff --git a/Cargo.toml b/Cargo.toml index c368413..819fe23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2024" description = "Transliterate Serbian Cyrillic and Latin scripts from files or stdin" license = "MIT OR Apache-2.0" +authors = ["Sibin Grasic "] +repository = "https://github.com/oblakstudio/transliterator-cli" +readme = "README.md" publish = false [[bin]] @@ -21,3 +24,29 @@ clap = { version = "4.5", features = ["derive"] } lto = "thin" codegen-units = 1 strip = "symbols" + +[package.metadata.deb] +maintainer = "Sibin Grasic " +section = "utils" +priority = "optional" +depends = "" +extended-description = """\ +A small Rust port of oblakstudio/transliterator that transliterates Serbian +text between Cyrillic, Latin (with diacritics), and diacritic-free ASCII +Latin. Reads from files or stdin, writes to stdout or `--out`.""" +assets = [ + ["target/release/transliterate", "usr/bin/", "755"], + ["README.md", "usr/share/doc/transliterator/README", "644"], + ["LICENSE-MIT", "usr/share/doc/transliterator/LICENSE-MIT", "644"], + ["LICENSE-APACHE", "usr/share/doc/transliterator/LICENSE-APACHE", "644"], +] + +[package.metadata.generate-rpm] +summary = "Transliterate Serbian Cyrillic and Latin scripts" +license = "MIT OR Apache-2.0" +assets = [ + { source = "target/release/transliterate", dest = "/usr/bin/transliterate", mode = "755" }, + { source = "README.md", dest = "/usr/share/doc/transliterator/README", mode = "644" }, + { source = "LICENSE-MIT", dest = "/usr/share/doc/transliterator/LICENSE-MIT", mode = "644" }, + { source = "LICENSE-APACHE", dest = "/usr/share/doc/transliterator/LICENSE-APACHE", mode = "644" }, +] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..a146f94 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for describing the origin of the Work and + reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Support. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or support. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Oblak Studio + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..e0301cf --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Oblak Studio + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4fb2ed --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# transliterator + +Transliterate Serbian Cyrillic ↔ Latin from files or stdin. + +A Rust port of [oblakstudio/transliterator](https://github.com/oblakstudio/transliterator), +shipped as a single static binary called `transliterate`. + +## Install + +Prebuilt binaries for Linux (musl, x86_64 + aarch64), macOS (Intel + Apple +Silicon) and Windows are published on the [releases page]. Linux users can +also install `.deb` or `.rpm` packages from the same release. + +[releases page]: https://github.com/oblakstudio/transliterator-cli/releases + +Or build from source: + +```sh +cargo install --path . +``` + +## Usage + +```sh +echo "Београд" | transliterate lat # → Beograd +echo "Београд" | transliterate cut-lat # → Beograd (diacritic-free) +echo "Đorđe" | transliterate cir # → Ђорђе +echo "Četiri" | transliterate cut-lat # → Cetiri + +transliterate lat input.txt --out output.txt +transliterate lat a.txt b.txt c.txt # files are concatenated in order +``` + +Subcommands: + +- `lat` — Cyrillic → Serbian Latin (with diacritics) +- `cut-lat` — Cyrillic *and* diacritic Latin → URL-safe Latin +- `cir` — Serbian Latin → Cyrillic + +With no files, input is read from stdin. With `--out PATH`, output is +written to a file instead of stdout. + +## License + +Dual-licensed under either [MIT](LICENSE-MIT) or +[Apache 2.0](LICENSE-APACHE), at your option. From 211efb335d1b656f6d781dfe387495cf3e2a82a3 Mon Sep 17 00:00:00 2001 From: Sibin Grasic Date: Mon, 1 Jun 2026 01:54:58 +0200 Subject: [PATCH 2/5] chore: add project files left out of repo Restore Rust ignores in .gitignore (bd init had overwritten them, which left target/ untracked), and add beads tracker config, Claude agent settings, AGENTS.md and CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) --- .beads/.gitignore | 73 ++++++++++++++++++++++++++++ .beads/README.md | 81 +++++++++++++++++++++++++++++++ .beads/config.yaml | 56 ++++++++++++++++++++++ .beads/hooks/post-checkout | 24 ++++++++++ .beads/hooks/post-merge | 24 ++++++++++ .beads/hooks/pre-commit | 24 ++++++++++ .beads/hooks/pre-push | 24 ++++++++++ .beads/hooks/prepare-commit-msg | 24 ++++++++++ .beads/metadata.json | 7 +++ .claude/settings.json | 26 ++++++++++ .gitignore | 5 ++ AGENTS.md | 84 +++++++++++++++++++++++++++++++++ CLAUDE.md | 69 +++++++++++++++++++++++++++ 13 files changed, 521 insertions(+) create mode 100644 .beads/.gitignore create mode 100644 .beads/README.md create mode 100644 .beads/config.yaml create mode 100755 .beads/hooks/post-checkout create mode 100755 .beads/hooks/post-merge create mode 100755 .beads/hooks/pre-commit create mode 100755 .beads/hooks/pre-push create mode 100755 .beads/hooks/prepare-commit-msg create mode 100644 .beads/metadata.json create mode 100644 .claude/settings.json create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 0000000..df4911d --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,73 @@ +# Dolt database (managed by Dolt, not git) +dolt/ +embeddeddolt/ + +# Runtime files +bd.sock +bd.sock.startlock +sync-state.json +last-touched +.exclusive-lock + +# Daemon runtime (lock, log, pid) +daemon.* + +# Interactions log (runtime, not versioned) +interactions.jsonl + +# Push state (runtime, per-machine) +push-state.json + +# Lock files (various runtime locks) +*.lock + +# Credential key (encryption key for federation peer auth — never commit) +.beads-credential-key + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +export-state/ +export-state.json + +# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned) +ephemeral.sqlite3 +ephemeral.sqlite3-journal +ephemeral.sqlite3-wal +ephemeral.sqlite3-shm + +# Dolt server management (auto-started by bd) +dolt-server.pid +dolt-server.log +dolt-server.lock +dolt-server.port +dolt-server.activity + +# Corrupt backup directories (created by bd doctor --fix recovery) +*.corrupt.backup/ + +# Backup data (auto-exported JSONL, local-only) +backup/ + +# Per-project environment file (Dolt connection config, GH#2520) +.env + +# Legacy files (from pre-Dolt versions) +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm +db.sqlite +bd.db +# NOTE: Do NOT add negation patterns here. +# They would override fork protection in .git/info/exclude. +# Config files (metadata.json, config.yaml) are tracked by git by default +# since no pattern above ignores them. diff --git a/.beads/README.md b/.beads/README.md new file mode 100644 index 0000000..dbfe363 --- /dev/null +++ b/.beads/README.md @@ -0,0 +1,81 @@ +# Beads - AI-Native Issue Tracking + +Welcome to Beads! This repository uses **Beads** for issue tracking - a modern, AI-native tool designed to live directly in your codebase alongside your code. + +## What is Beads? + +Beads is issue tracking that lives in your repo, making it perfect for AI coding agents and developers who want their issues close to their code. No web UI required - everything works through the CLI and integrates seamlessly with git. + +**Learn more:** [github.com/steveyegge/beads](https://github.com/steveyegge/beads) + +## Quick Start + +### Essential Commands + +```bash +# Create new issues +bd create "Add user authentication" + +# View all issues +bd list + +# View issue details +bd show + +# Update issue status +bd update --claim +bd update --status done + +# Sync with Dolt remote +bd dolt push +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in Dolt database with version control and branching +- **AI-friendly**: CLI-first design works perfectly with AI coding agents +- **Branch-aware**: Issues can follow your branch workflow +- **Always in sync**: Auto-syncs with your commits + +## Why Beads? + +✨ **AI-Native Design** +- Built specifically for AI-assisted development workflows +- CLI-first interface works seamlessly with AI coding agents +- No context switching to web UIs + +🚀 **Developer Focused** +- Issues live in your repo, right next to your code +- Works offline, syncs when you push +- Fast, lightweight, and stays out of your way + +🔧 **Git Integration** +- Automatic sync with git commits +- Branch-aware issue tracking +- Dolt-native three-way merge resolution + +## Get Started with Beads + +Try Beads in your own projects: + +```bash +# Install Beads +curl -sSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash + +# Initialize in your repo +bd init + +# Create your first issue +bd create "Try out Beads" +``` + +## Learn More + +- **Documentation**: [github.com/steveyegge/beads/docs](https://github.com/steveyegge/beads/tree/main/docs) +- **Quick Start Guide**: Run `bd quickstart` +- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples) + +--- + +*Beads: Issue tracking that moves at the speed of thought* ⚡ diff --git a/.beads/config.yaml b/.beads/config.yaml new file mode 100644 index 0000000..d0f6261 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,56 @@ +# Beads Configuration File +# This file configures default behavior for all bd commands in this repository +# All settings can also be set via environment variables (BD_* prefix) +# or overridden with command-line flags + +# Issue prefix for this repository (used by bd init) +# If not set, bd init will auto-detect from directory name +# Example: issue-prefix: "myproject" creates issues like "myproject-1", "myproject-2", etc. +# issue-prefix: "" + +# Use no-db mode: JSONL-only, no Dolt database +# When true, bd will use .beads/issues.jsonl as the source of truth +# no-db: false + +# Enable JSON output by default +# json: false + +# Feedback title formatting for mutating commands (create/update/close/dep/edit) +# 0 = hide titles, N > 0 = truncate to N characters +# output: +# title-length: 255 + +# Default actor for audit trails (overridden by BEADS_ACTOR or --actor) +# actor: "" + +# Export events (audit trail) to .beads/events.jsonl on each flush/sync +# When enabled, new events are appended incrementally using a high-water mark. +# Use 'bd export --events' to trigger manually regardless of this setting. +# events-export: false + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct database +# repos: +# primary: "." # Primary repo (where this database lives) +# additional: # Additional repos to hydrate from (read-only) +# - ~/beads-planning # Personal planning repo +# - ~/work-planning # Work planning repo + +# JSONL backup (periodic export for off-machine recovery) +# Auto-enabled when a git remote exists. Override explicitly: +# backup: +# enabled: false # Disable auto-backup entirely +# interval: 15m # Minimum time between auto-exports +# git-push: false # Disable git push (export locally only) +# git-repo: "" # Separate git repo for backups (default: project repo) + +# Integration settings (access with 'bd config get/set') +# These are stored in the database, not in this file: +# - jira.url +# - jira.project +# - linear.url +# - linear.api-key +# - github.org +# - github.repo + +export.auto: false \ No newline at end of file diff --git a/.beads/hooks/post-checkout b/.beads/hooks/post-checkout new file mode 100755 index 0000000..8740e4f --- /dev/null +++ b/.beads/hooks/post-checkout @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.2 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run post-checkout "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'post-checkout' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run post-checkout "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'post-checkout'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.2 --- diff --git a/.beads/hooks/post-merge b/.beads/hooks/post-merge new file mode 100755 index 0000000..79487b2 --- /dev/null +++ b/.beads/hooks/post-merge @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.2 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run post-merge "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'post-merge' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run post-merge "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'post-merge'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.2 --- diff --git a/.beads/hooks/pre-commit b/.beads/hooks/pre-commit new file mode 100755 index 0000000..bae3803 --- /dev/null +++ b/.beads/hooks/pre-commit @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.2 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run pre-commit "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'pre-commit' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run pre-commit "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'pre-commit'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.2 --- diff --git a/.beads/hooks/pre-push b/.beads/hooks/pre-push new file mode 100755 index 0000000..490f66e --- /dev/null +++ b/.beads/hooks/pre-push @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.2 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run pre-push "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'pre-push' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run pre-push "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'pre-push'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.2 --- diff --git a/.beads/hooks/prepare-commit-msg b/.beads/hooks/prepare-commit-msg new file mode 100755 index 0000000..e10a4fe --- /dev/null +++ b/.beads/hooks/prepare-commit-msg @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v1.0.2 --- +# This section is managed by beads. Do not remove these markers. +if command -v bd >/dev/null 2>&1; then + export BD_GIT_HOOK=1 + _bd_timeout=${BEADS_HOOK_TIMEOUT:-300} + if command -v timeout >/dev/null 2>&1; then + timeout "$_bd_timeout" bd hooks run prepare-commit-msg "$@" + _bd_exit=$? + if [ $_bd_exit -eq 124 ]; then + echo >&2 "beads: hook 'prepare-commit-msg' timed out after ${_bd_timeout}s — continuing without beads" + _bd_exit=0 + fi + else + bd hooks run prepare-commit-msg "$@" + _bd_exit=$? + fi + if [ $_bd_exit -eq 3 ]; then + echo >&2 "beads: database not initialized — skipping hook 'prepare-commit-msg'" + _bd_exit=0 + fi + if [ $_bd_exit -ne 0 ]; then exit $_bd_exit; fi +fi +# --- END BEADS INTEGRATION v1.0.2 --- diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 0000000..d45b854 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,7 @@ +{ + "database": "dolt", + "backend": "dolt", + "dolt_mode": "embedded", + "dolt_database": "apps", + "project_id": "66bca953-5d0b-497f-9d5a-bf5bf5a0db22" +} \ No newline at end of file diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..963a538 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,26 @@ +{ + "hooks": { + "PreCompact": [ + { + "hooks": [ + { + "command": "bd prime", + "type": "command" + } + ], + "matcher": "" + } + ], + "SessionStart": [ + { + "hooks": [ + { + "command": "bd prime", + "type": "command" + } + ], + "matcher": "" + } + ] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d559817..a61b9d8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ node_modules/ Cargo.lock.bak .DS_Store + +# Beads / Dolt files (added by bd init) +.dolt/ +*.db +.beads-credential-key diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9390d72 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,84 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. Run `bd prime` for full workflow context. + +## Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work atomically +bd close # Complete work +bd dolt push # Push beads data to remote +``` + +## Non-Interactive Shell Commands + +**ALWAYS use non-interactive flags** with file operations to avoid hanging on confirmation prompts. + +Shell commands like `cp`, `mv`, and `rm` may be aliased to include `-i` (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input. + +**Use these forms instead:** +```bash +# Force overwrite without prompting +cp -f source dest # NOT: cp source dest +mv -f source dest # NOT: mv source dest +rm -f file # NOT: rm file + +# For recursive operations +rm -rf directory # NOT: rm -r directory +cp -rf source dest # NOT: cp -r source dest +``` + +**Other commands that may prompt:** +- `scp` - use `-o BatchMode=yes` for non-interactive +- `ssh` - use `-o BatchMode=yes` to fail instead of prompting +- `apt-get` - use `-y` flag +- `brew` - use `HOMEBREW_NO_AUTO_UPDATE=1` env var + + +## Beads Issue Tracker + +This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands. + +### Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work +bd close # Complete work +``` + +### Rules + +- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists +- Run `bd prime` for detailed command reference and session close protocol +- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files + +## Session Completion + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd dolt push + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..50af487 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,69 @@ +# Project Instructions for AI Agents + +This file provides instructions and context for AI coding agents working on this project. + + +## Beads Issue Tracker + +This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands. + +### Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --claim # Claim work +bd close # Complete work +``` + +### Rules + +- Use `bd` for ALL task tracking — do NOT use TodoWrite, TaskCreate, or markdown TODO lists +- Run `bd prime` for detailed command reference and session close protocol +- Use `bd remember` for persistent knowledge — do NOT use MEMORY.md files + +## Session Completion + +**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds. + +**MANDATORY WORKFLOW:** + +1. **File issues for remaining work** - Create issues for anything that needs follow-up +2. **Run quality gates** (if code changed) - Tests, linters, builds +3. **Update issue status** - Close finished work, update in-progress items +4. **PUSH TO REMOTE** - This is MANDATORY: + ```bash + git pull --rebase + bd dolt push + git push + git status # MUST show "up to date with origin" + ``` +5. **Clean up** - Clear stashes, prune remote branches +6. **Verify** - All changes committed AND pushed +7. **Hand off** - Provide context for next session + +**CRITICAL RULES:** +- Work is NOT complete until `git push` succeeds +- NEVER stop before pushing - that leaves work stranded locally +- NEVER say "ready to push when you are" - YOU must push +- If push fails, resolve and retry until it succeeds + + + +## Build & Test + +_Add your build and test commands here_ + +```bash +# Example: +# npm install +# npm test +``` + +## Architecture Overview + +_Add a brief overview of your project architecture_ + +## Conventions & Patterns + +_Add your project-specific conventions here_ From e52c28cc7f81625c13e27c81e2577ad7d8822dca Mon Sep 17 00:00:00 2001 From: Sibin Grasic Date: Mon, 1 Jun 2026 02:09:12 +0200 Subject: [PATCH 3/5] ci: install Zig for cargo-zigbuild musl builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cargo-zigbuild shells out to the zig compiler, but taiki-e/install-action only installs the cargo-zigbuild wrapper — not zig itself. Both musl targets failed instantly at the build step because zig was missing from PATH. Add an mlugg/setup-zig step (pinned to 0.13.0) ahead of the build. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b3a519f..609fcb9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -73,6 +73,12 @@ jobs: with: targets: ${{ matrix.target }} + - name: Install Zig + if: matrix.zigbuild + uses: mlugg/setup-zig@v2 + with: + version: 0.13.0 + - name: Install cargo-zigbuild if: matrix.zigbuild uses: taiki-e/install-action@v2 From 649fafb564992e8eceddb893b6dd4b59d34f4d9b Mon Sep 17 00:00:00 2001 From: Sibin Grasic Date: Mon, 1 Jun 2026 02:12:03 +0200 Subject: [PATCH 4/5] ci: don't let ldd's exit code fail the static-link check ldd exits non-zero on a fully static binary (printing "not a dynamic executable"). Under set -euo pipefail the `ldd | tee` pipeline aborted the verify step before the grep assertion could run, even though the binary was correctly static. Swallow ldd's exit code and let the grep on its captured output be the real check. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 609fcb9..f83003e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -116,7 +116,10 @@ jobs: # runs natively on x86_64 hosts, so guard it on the target name. file "$bin" | grep -Eq 'statically linked|static-pie linked' if [[ "${{ matrix.target }}" == x86_64-* ]]; then - ldd "$bin" 2>&1 | tee /tmp/ldd.out + # `ldd` exits non-zero on a static binary ("not a dynamic + # executable"), which would abort the step under pipefail before + # the grep runs — so swallow its exit code and let grep assert. + ldd "$bin" 2>&1 | tee /tmp/ldd.out || true grep -q 'not a dynamic executable\|statically linked' /tmp/ldd.out fi From f385f9cb2fbafe70739f23576eac0bf1ab18f9ad Mon Sep 17 00:00:00 2001 From: Sibin Grasic Date: Mon, 1 Jun 2026 02:19:28 +0200 Subject: [PATCH 5/5] ci: cross-compile x86_64-apple-darwin on Apple Silicon MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The free Intel macOS runner (macos-13) has been retired, so the x86_64-apple-darwin job sat queued indefinitely waiting for a runner that no longer exists. Build that target on macos-latest (Apple Silicon) instead — the macOS SDK is universal and the toolchain step already installs the x86_64 rustup target, so `cargo build --target x86_64-apple-darwin` cross-compiles cleanly. The darwin jobs only build and archive (no execution), so producing an Intel binary on an ARM host is safe. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/release.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f83003e..2cc9070 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,12 @@ jobs: archive: tar.gz zigbuild: true - target: x86_64-apple-darwin - os: macos-13 + # macos-13 (the last free Intel runner) has been retired, so the + # Intel slice cross-compiles on an Apple Silicon runner instead — + # the macOS SDK is universal and the toolchain step installs the + # x86_64 target. These darwin jobs only build + archive, never run + # the binary, so cross-compilation is safe. + os: macos-latest archive: tar.gz zigbuild: false - target: aarch64-apple-darwin