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/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2cc9070 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,262 @@ +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 + # 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 + 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 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 + 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` 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 + + - 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/.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_ 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.