From a29a46f38e07072eff1e9883dddc3988341571c0 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sat, 14 Mar 2026 12:41:20 -0700 Subject: [PATCH 1/6] bd init: initialize beads issue tracking --- .beads/.gitignore | 49 ++++++++++ .beads/README.md | 81 +++++++++++++++++ .beads/config.yaml | 54 ++++++++++++ .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/interactions.jsonl | 0 .beads/metadata.json | 7 ++ .gitignore | 4 + AGENTS.md | 152 ++++++++++++++++++++++++++++++++ 12 files changed, 467 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/interactions.jsonl create mode 100644 .beads/metadata.json create mode 100644 AGENTS.md diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 00000000..830ae107 --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,49 @@ +# Dolt database (managed by Dolt, not git) +dolt/ +dolt-access.lock + +# Runtime files +bd.sock +bd.sock.startlock +sync-state.json +last-touched + +# 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/ + +# 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 + +# Backup data (auto-exported JSONL, local-only) +backup/ + +# 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 00000000..dbfe3631 --- /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 00000000..e831a6be --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,54 @@ +# 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 BD_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 diff --git a/.beads/hooks/post-checkout b/.beads/hooks/post-checkout new file mode 100755 index 00000000..c1fa905b --- /dev/null +++ b/.beads/hooks/post-checkout @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.60.0 --- +# 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:-30} + 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 v0.60.0 --- diff --git a/.beads/hooks/post-merge b/.beads/hooks/post-merge new file mode 100755 index 00000000..dfbb5865 --- /dev/null +++ b/.beads/hooks/post-merge @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.60.0 --- +# 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:-30} + 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 v0.60.0 --- diff --git a/.beads/hooks/pre-commit b/.beads/hooks/pre-commit new file mode 100755 index 00000000..c644d1f5 --- /dev/null +++ b/.beads/hooks/pre-commit @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.60.0 --- +# 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:-30} + 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 v0.60.0 --- diff --git a/.beads/hooks/pre-push b/.beads/hooks/pre-push new file mode 100755 index 00000000..46df32c9 --- /dev/null +++ b/.beads/hooks/pre-push @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.60.0 --- +# 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:-30} + 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 v0.60.0 --- diff --git a/.beads/hooks/prepare-commit-msg b/.beads/hooks/prepare-commit-msg new file mode 100755 index 00000000..25de9efe --- /dev/null +++ b/.beads/hooks/prepare-commit-msg @@ -0,0 +1,24 @@ +#!/usr/bin/env sh +# --- BEGIN BEADS INTEGRATION v0.60.0 --- +# 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:-30} + 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 v0.60.0 --- diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl new file mode 100644 index 00000000..e69de29b diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 00000000..8e59c308 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,7 @@ +{ + "database": "dolt", + "backend": "dolt", + "dolt_mode": "server", + "dolt_database": "hgvs", + "project_id": "ab72a248-33b9-4c7f-9e8c-cacf11bf1c3b" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index efcdea1c..3cce6980 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,7 @@ var/ venv.bak/ venv/ wheels/ + +# Dolt database files (added by bd init) +.dolt/ +*.db diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..e3125d9a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,152 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. + +## Quick Reference + +```bash +bd ready # Find available work +bd show # View issue details +bd update --status in_progress # Claim work +bd close # Complete work +bd sync # Sync with git +``` + +## Landing the Plane (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 sync + 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 + + +## Issue Tracking with bd (beads) + +**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods. + +### Why bd? + +- Dependency-aware: Track blockers and relationships between issues +- Git-friendly: Dolt-powered version control with native sync +- Agent-optimized: JSON output, ready work detection, discovered-from links +- Prevents duplicate tracking systems and confusion + +### Quick Start + +**Check for ready work:** + +```bash +bd ready --json +``` + +**Create new issues:** + +```bash +bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json +bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json +``` + +**Claim and update:** + +```bash +bd update --claim --json +bd update bd-42 --priority 1 --json +``` + +**Complete work:** + +```bash +bd close bd-42 --reason "Completed" --json +``` + +### Issue Types + +- `bug` - Something broken +- `feature` - New functionality +- `task` - Work item (tests, docs, refactoring) +- `epic` - Large feature with subtasks +- `chore` - Maintenance (dependencies, tooling) + +### Priorities + +- `0` - Critical (security, data loss, broken builds) +- `1` - High (major features, important bugs) +- `2` - Medium (default, nice-to-have) +- `3` - Low (polish, optimization) +- `4` - Backlog (future ideas) + +### Workflow for AI Agents + +1. **Check ready work**: `bd ready` shows unblocked issues +2. **Claim your task atomically**: `bd update --claim` +3. **Work on it**: Implement, test, document +4. **Discover new work?** Create linked issue: + - `bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:` +5. **Complete**: `bd close --reason "Done"` + +### Auto-Sync + +bd automatically syncs via Dolt: + +- Each write auto-commits to Dolt history +- Use `bd dolt push`/`bd dolt pull` for remote sync +- No manual export/import needed! + +### Important Rules + +- ✅ Use bd for ALL task tracking +- ✅ Always use `--json` flag for programmatic use +- ✅ Link discovered work with `discovered-from` dependencies +- ✅ Check `bd ready` before asking "what should I work on?" +- ❌ Do NOT create markdown TODO lists +- ❌ Do NOT use external issue trackers +- ❌ Do NOT duplicate tracking systems + +For more details, see README.md and docs/QUICKSTART.md. + +## Landing the Plane (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 + + From 680d3089c111f3f569c0ed473b64294eefdcaa0c Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sat, 14 Mar 2026 16:06:46 -0700 Subject: [PATCH 2/6] Fix g_to_c mapping when CIGAR I-segment falls inside variant interval When a genomic delins spans a position where the CIGAR alignment has an I-segment (genomic bases absent from the transcript), both variant endpoints can land in normal = segments so pos_c.uncertain remains False. The previous code path would strand-flip the edit without adjusting for the missing transcript base, producing an incorrect transcript edit. Fix: add _variant_has_internal_gap() and _gap_segments_within_pos_g() helpers that detect I/D segments strictly inside the variant interval, and route these cases through _get_altered_tx_sequence() which correctly filters genomic-only bases before building the transcript edit. Also refactors _i_segment_offsets_in_pos_g into the new unified _gap_segments_within_pos_g. Tests: update expected value for the adjacent-I-segment case (c.527_532delinsTGTGA, which is the correct result since the I-segment at g.119027727 is outside the variant g.119027721-119027726), and add a new test for the true internal-gap case (g.119027726_119027728delinsTT -> c.526_527delinsAA). Closes: hgvs-cl9, hgvs-hqa, hgvs-83z, hgvs-wma, hgvs-o26 Co-Authored-By: Claude Sonnet 4.6 --- src/hgvs/variantmapper.py | 184 +++++++++++++++++++++++++++++- tests/data/cache-py3.hdp | Bin 2276411 -> 2317043 bytes tests/test_hgvs_assemblymapper.py | 48 ++++++++ 3 files changed, 226 insertions(+), 6 deletions(-) diff --git a/src/hgvs/variantmapper.py b/src/hgvs/variantmapper.py index 297bf6f3..9a33588b 100644 --- a/src/hgvs/variantmapper.py +++ b/src/hgvs/variantmapper.py @@ -179,9 +179,12 @@ def g_to_n(self, var_g, tx_ac, alt_aln_method=hgvs.global_config.mapping.alt_aln else: # variant at alignment gap pos_g = mapper.n_to_g(pos_n) - edit_n = hgvs.edit.NARefAlt( - ref="", alt=self._get_altered_sequence(mapper.strand, pos_g, var_g) - ) + if self._variant_spans_i_segment(mapper, var_g): + edit_n, pos_n = self._get_altered_tx_sequence(mapper.strand, mapper, pos_g, var_g, pos_n) + else: + edit_n = hgvs.edit.NARefAlt( + ref="", alt=self._get_altered_sequence(mapper.strand, pos_g, var_g) + ) pos_n.uncertain = var_g.posedit.pos.uncertain var_n = hgvs.sequencevariant.SequenceVariant( ac=tx_ac, type="n", posedit=hgvs.posedit.PosEdit(pos_n, edit_n) @@ -281,12 +284,22 @@ def g_to_c(self, var_g, tx_ac, alt_aln_method=hgvs.global_config.mapping.alt_aln pos_c.start.base += 1 pos_c.end.base -= 1 edit_c.ref = "" + elif self._variant_has_internal_gap(mapper, var_g): + # Gap segment lies inside the variant interval; both endpoints are in = regions + # so pos_c.uncertain is False, but the transcript length differs from genomic length. + # Recalculate the transcript edit from sequences to absorb the gap. + edit_c, pos_c = self._get_altered_tx_sequence( + mapper.strand, mapper, var_g.posedit.pos, var_g, pos_c + ) else: # variant at alignment gap pos_g = mapper.c_to_g(pos_c) - edit_c = hgvs.edit.NARefAlt( - ref="", alt=self._get_altered_sequence(mapper.strand, pos_g, var_g) - ) + if self._variant_spans_i_segment(mapper, var_g): + edit_c, pos_c = self._get_altered_tx_sequence(mapper.strand, mapper, pos_g, var_g, pos_c) + else: + edit_c = hgvs.edit.NARefAlt( + ref="", alt=self._get_altered_sequence(mapper.strand, pos_g, var_g) + ) pos_c.uncertain = var_g.posedit.pos.uncertain var_c = hgvs.sequencevariant.SequenceVariant( ac=tx_ac, type="c", posedit=hgvs.posedit.PosEdit(pos_c, edit_c) @@ -639,6 +652,165 @@ def _get_altered_sequence(self, strand, interval, var): seq = reverse_complement(seq) return seq + def _variant_spans_i_segment(self, mapper, var_g): + """Return True if var_g straddles a normal-to-I-segment boundary in the CIGAR. + + I-segments are genomic positions with no transcript equivalent. The fix is needed + only when the variant spans *across* an I-segment boundary (one endpoint in a matching + "=" region, the other landing in an I-segment). When the entire variant lies within an + I-segment, the existing uncertain-path logic handles it correctly. + """ + gc_offset = mapper.gc_offset + ref_pos = mapper.cigarmapper.ref_pos + cigar_op = mapper.cigarmapper.cigar_op + # Use interbase convention: start is 0-based (base-1), end is open (base-gc_offset, no -1) + start_offset = var_g.posedit.pos.start.base - 1 - gc_offset + end_offset = var_g.posedit.pos.end.base - gc_offset + + # Determine the CIGAR op at each endpoint + start_op = end_op = None + for i, op in enumerate(cigar_op): + if start_op is None and start_offset < ref_pos[i + 1]: + start_op = op + if end_op is None and end_offset < ref_pos[i + 1]: + end_op = op + if start_op is not None and end_op is not None: + break + + # Apply fix only when at least one endpoint is in "I" and at least one is not + ops = {start_op, end_op} + return "I" in ops and ops != {"I"} + + def _gap_segments_within_pos_g(self, mapper, pos_g): + """Return gap segments (I and D CIGAR ops) that fall within genomic interval pos_g. + + I-segments: genomic bases with no transcript equivalent. + D-segments: transcript-only positions at an interbase genomic location. + + Returns: + dict with keys: + "I": set of 0-based offsets (relative to pos_g.start.base) for I-segment bases + "D": list of (interbase_offset, tgt_start, tgt_end) for each D-segment + where interbase_offset is 0-based relative to pos_g.start.base, + tgt_start/tgt_end are 0-based transcript positions of the D-segment bases + """ + i_offsets = set() + d_segments = [] + cigar_ops = mapper.cigarmapper.cigar_op + ref_pos = mapper.cigarmapper.ref_pos + tgt_pos = mapper.cigarmapper.tgt_pos + gc_offset = mapper.gc_offset + + for i, op in enumerate(cigar_ops): + if op == "I": + # I advances genome/ref only — absolute genomic positions (1-based, inclusive) + abs_start = ref_pos[i] + gc_offset + 1 + abs_end = ref_pos[i + 1] + gc_offset # inclusive + overlap_start = max(abs_start, pos_g.start.base) + overlap_end = min(abs_end, pos_g.end.base) + for p in range(overlap_start, overlap_end + 1): + i_offsets.add(p - pos_g.start.base) + elif op == "D": + # D advances transcript/tgt only — genomic interbase position is ref_pos[i] (same as ref_pos[i+1]) + # The D-segment sits at interbase position gc_offset + ref_pos[i] + # It is "inside" pos_g if its genomic interbase position falls strictly between start and end + genomic_interbase = ref_pos[i] + gc_offset # 0-based interbase genomic position + # pos_g uses 1-based inclusive; interbase is between pos_g.start.base-1 and pos_g.start.base + # A D-segment is inside pos_g if: pos_g.start.base <= genomic_interbase+1 <= pos_g.end.base + # i.e., it sits between two bases both within pos_g + if pos_g.start.base <= genomic_interbase < pos_g.end.base: + interbase_offset = genomic_interbase - (pos_g.start.base - 1) # offset relative to pos_g start + d_segments.append((interbase_offset, tgt_pos[i], tgt_pos[i + 1])) + + return {"I": i_offsets, "D": d_segments} + + def _variant_has_internal_gap(self, mapper, var_g): + """Return True if any I or D CIGAR segment falls strictly inside var_g's interval. + + This is distinct from _variant_spans_i_segment which checks endpoint CIGAR ops. + Internal gaps occur when both endpoints are in normal (=) segments but a gap + segment lies between them. + """ + gaps = self._gap_segments_within_pos_g(mapper, var_g.posedit.pos) + return bool(gaps["I"] or gaps["D"]) + + def _get_altered_tx_sequence(self, strand, mapper, pos_g, var_g, tx_pos): + """Compute transcript-level edit for variants at alignment gaps (uncertain path). + + Correctly handles I-segment positions (genomic bases with no transcript equivalent) + by filtering them out when building the transcript reference and alt sequences. + + Returns (NARefAlt, adjusted_tx_pos). + """ + # Fetch genomic reference for pos_g + seq = list(self.hdp.get_seq(var_g.ac, pos_g.start.base - 1, pos_g.end.base)) + + # Find gap segment positions (0-based offsets into seq) + gaps = self._gap_segments_within_pos_g(mapper, pos_g) + i_offsets = gaps["I"] + + # Variant boundaries within seq (0-based) + var_start = var_g.posedit.pos.start.base - pos_g.start.base + var_end = var_g.posedit.pos.end.base - pos_g.start.base + 1 + edit = var_g.posedit.edit + + # Build transcript ref = all genomic bases excluding I-segment positions + tx_ref_str = "".join(seq[j] for j in range(len(seq)) if j not in i_offsets) + + # Build transcript alt by applying variant to prefix/suffix (filtered of I-bases) + if edit.type == "ins": + # Insertion between two positions: prefix includes var_start base + prefix_tx = [seq[j] for j in range(var_start + 1) if j not in i_offsets] + suffix_tx = [seq[j] for j in range(var_start + 1, len(seq)) if j not in i_offsets] + tx_alt_str = "".join(prefix_tx) + (edit.alt or "") + "".join(suffix_tx) + elif edit.type == "sub": + prefix_tx = [seq[j] for j in range(var_start) if j not in i_offsets] + suffix_tx = [seq[j] for j in range(var_start + 1, len(seq)) if j not in i_offsets] + tx_alt_str = "".join(prefix_tx) + (edit.alt or "") + "".join(suffix_tx) + elif edit.type == "del": + prefix_tx = [seq[j] for j in range(var_start) if j not in i_offsets] + suffix_tx = [seq[j] for j in range(var_end, len(seq)) if j not in i_offsets] + tx_alt_str = "".join(prefix_tx) + "".join(suffix_tx) + elif edit.type in ("delins", "dup", "inv", "identity"): + prefix_tx = [seq[j] for j in range(var_start) if j not in i_offsets] + suffix_tx = [seq[j] for j in range(var_end, len(seq)) if j not in i_offsets] + if edit.type == "delins": + ins_seq = edit.alt or "" + elif edit.type == "dup": + ins_seq = "".join(seq[var_start:var_end]) * 2 + elif edit.type == "inv": + ins_seq = reverse_complement("".join(seq[var_start:var_end])) + else: # identity + ins_seq = "".join(seq[var_start:var_end]) + tx_alt_str = "".join(prefix_tx) + ins_seq + "".join(suffix_tx) + else: + msg = f"_get_altered_tx_sequence: unsupported edit type {edit.type!r}" + raise HGVSUnsupportedOperationError(msg) + + # Apply strand: convert from genomic order to transcript order + if strand == -1: + tx_ref_str = reverse_complement(tx_ref_str) + tx_alt_str = reverse_complement(tx_alt_str) + + # Trim common prefix and suffix to find the minimal edit + n_prefix = 0 + while n_prefix < len(tx_ref_str) and n_prefix < len(tx_alt_str) and tx_ref_str[n_prefix] == tx_alt_str[n_prefix]: + n_prefix += 1 + + n_suffix = 0 + max_suffix = min(len(tx_ref_str) - n_prefix, len(tx_alt_str) - n_prefix) + while n_suffix < max_suffix and tx_ref_str[-(n_suffix + 1)] == tx_alt_str[-(n_suffix + 1)]: + n_suffix += 1 + + ref_trimmed = tx_ref_str[n_prefix: len(tx_ref_str) - n_suffix if n_suffix else None] + alt_trimmed = tx_alt_str[n_prefix: len(tx_alt_str) - n_suffix if n_suffix else None] + + adjusted_pos = copy.deepcopy(tx_pos) + adjusted_pos.start.base += n_prefix + adjusted_pos.end.base -= n_suffix + + return hgvs.edit.NARefAlt(ref=ref_trimmed, alt=alt_trimmed), adjusted_pos + def _update_gene_symbol(self, var, symbol): if not symbol: symbol = self.hdp.get_tx_identity_info(var.ac).get("hgnc", None) diff --git a/tests/data/cache-py3.hdp b/tests/data/cache-py3.hdp index 9d10cbb8a44fd90d8db6b44b7855e05e598a5c68..d3463ea8941595fe48f7e4aedcdeea1db8e37198 100644 GIT binary patch delta 31287 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 delta 92 zcmV~$w++HT002PVCTEkg!2_^`cR_NEYl^Fo*ak$w2uLhLL&@FuaSN~CHCzOdSXE73 sLsLszM^{hZz))gjY+`C=ZeeLpE5ch|ErxIV*mgE diff --git a/tests/test_hgvs_assemblymapper.py b/tests/test_hgvs_assemblymapper.py index 202aa534..b330043c 100644 --- a/tests/test_hgvs_assemblymapper.py +++ b/tests/test_hgvs_assemblymapper.py @@ -156,6 +156,54 @@ def test_projection_at_alignment_discrepancy(self): self.assertEqual(str(var_g), hgvs_g) + # g_to_c next to 3' UTR + hgvs_g = "NC_000002.11:g.182402910_182402911insTTACT" + hgvs_c = "NM_001030311.2:c.1677_*1insAGTAA" + var_g = self.hp.parse_hgvs_variant(hgvs_g) + var_c = self.am37.g_to_c(var_g, "NM_001030311.2") + assert str(var_c) == hgvs_c + + # g_to_c for a variant adjacent to a CIGAR I-segment (genome has extra base at + # position 119027727 with no transcript equivalent). The variant covers only "=" + # territory (119027721-119027726), so pos_c.uncertain=False and the strand-flip + # path applies directly. The 6-base delins on the minus-strand gene maps to a + # 6-base delins on the transcript after reverse-complementing the alt. + hgvs_g = "NC_000011.10:g.119027721_119027726delinsTCACA" + hgvs_c = "NM_001164277.1:c.527_532delinsTGTGA" + + var_g = self.hp.parse_hgvs_variant(hgvs_g) + var_c = self.am.g_to_c(var_g, "NM_001164277.1") + + assert str(var_g) == hgvs_g + assert str(var_c) == hgvs_c + + # g_to_c for a variant with a CIGAR I-segment strictly inside the interval. + # g.119027726 maps to c.527 (=), g.119027727 is the I-segment (no transcript + # equivalent), g.119027728 maps to c.526 (=). Both endpoints are in = segments + # so pos_c.uncertain=False, but the genomic span (3 bases) is wider than the + # transcript span (2 bases). The fix routes this through sequence reconstruction + # so the I-segment base is excluded from the transcript edit. + hgvs_g2 = "NC_000011.10:g.119027726_119027728delinsTT" + hgvs_c2 = "NM_001164277.1:c.526_527delinsAA" + + var_g2 = self.hp.parse_hgvs_variant(hgvs_g2) + var_c2 = self.am.g_to_c(var_g2, "NM_001164277.1") + + assert str(var_g2) == hgvs_g2 + assert str(var_c2) == hgvs_c2 + + # deletion spanning a 'D' (transcript deletion) in the CIGAR alignment + # NM_015120.4 exon 1 CIGAR: 146=3D289= (genome has 3 extra bases not in transcript) + # g.73385901_73385903del spans into the 3D region → maps to c.34_36del + hgvs_g = "NC_000002.12:g.73385901_73385903del" + hgvs_c = "NM_015120.4:c.34_36del" + + var_g = self.hp.parse_hgvs_variant(hgvs_g) + var_c = self.am.g_to_c(var_g, "NM_015120.4") + + assert str(var_c) == hgvs_c + + def test_c_to_p_with_stop_gain(self): # issue-474 hgvs_c = "NM_080877.2:c.1733_1735delinsTTT" From bd1b94d96db8d52023df9b929605b6f5cc41a586 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 15 Mar 2026 15:39:43 -0700 Subject: [PATCH 3/6] #819 fixes double gap issue. variant mapper now checks for internal or spanning alignment gaps. --- src/hgvs/variantmapper.py | 61 ++++++++++++++++++++++++++++-- tests/data/cache-py3.hdp | Bin 2317043 -> 2317233 bytes tests/test_hgvs_assemblymapper.py | 2 +- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/src/hgvs/variantmapper.py b/src/hgvs/variantmapper.py index 9a33588b..6f708bd6 100644 --- a/src/hgvs/variantmapper.py +++ b/src/hgvs/variantmapper.py @@ -176,6 +176,15 @@ def g_to_n(self, var_g, tx_ac, alt_aln_method=hgvs.global_config.mapping.alt_aln pos_n.start.base += 1 pos_n.end.base -= 1 edit_n.ref = "" + elif self._variant_has_internal_gap(mapper, var_g): + edit_n, pos_n = self._get_altered_tx_sequence( + mapper.strand, mapper, var_g.posedit.pos, var_g, pos_n + ) + elif self._variant_spans_i_segment(mapper, var_g): + expanded_pos_g = self._expand_pos_g_for_adjacent_gap(mapper, var_g) + edit_n, pos_n = self._get_altered_tx_sequence( + mapper.strand, mapper, expanded_pos_g, var_g, pos_n + ) else: # variant at alignment gap pos_g = mapper.n_to_g(pos_n) @@ -291,6 +300,13 @@ def g_to_c(self, var_g, tx_ac, alt_aln_method=hgvs.global_config.mapping.alt_aln edit_c, pos_c = self._get_altered_tx_sequence( mapper.strand, mapper, var_g.posedit.pos, var_g, pos_c ) + elif self._variant_spans_i_segment(mapper, var_g): + # I-segment is immediately adjacent to the variant interval (not internal, not uncertain). + # The alt allele may cancel with the adjacent I-segment yielding a simpler tx edit. + expanded_pos_g = self._expand_pos_g_for_adjacent_gap(mapper, var_g) + edit_c, pos_c = self._get_altered_tx_sequence( + mapper.strand, mapper, expanded_pos_g, var_g, pos_c + ) else: # variant at alignment gap pos_g = mapper.c_to_g(pos_c) @@ -663,9 +679,12 @@ def _variant_spans_i_segment(self, mapper, var_g): gc_offset = mapper.gc_offset ref_pos = mapper.cigarmapper.ref_pos cigar_op = mapper.cigarmapper.cigar_op - # Use interbase convention: start is 0-based (base-1), end is open (base-gc_offset, no -1) - start_offset = var_g.posedit.pos.start.base - 1 - gc_offset - end_offset = var_g.posedit.pos.end.base - gc_offset + # Guard: partially uncertain variants may have Interval positions without .base + try: + start_offset = var_g.posedit.pos.start.base - 1 - gc_offset + end_offset = var_g.posedit.pos.end.base - gc_offset + except AttributeError: + return False # Determine the CIGAR op at each endpoint start_op = end_op = None @@ -681,6 +700,37 @@ def _variant_spans_i_segment(self, mapper, var_g): ops = {start_op, end_op} return "I" in ops and ops != {"I"} + def _expand_pos_g_for_adjacent_gap(self, mapper, var_g): + """Return a copy of var_g.posedit.pos expanded to include any adjacent I-segment. + + When the variant's interbase end sits at the start of an I-segment (or its + interbase start sits at the end of an I-segment), extend pos_g so that + _get_altered_tx_sequence sees the full I-segment in seq and i_offsets. + """ + gc_offset = mapper.gc_offset + ref_pos = mapper.cigarmapper.ref_pos + cigar_op = mapper.cigarmapper.cigar_op + + pos_g = copy.deepcopy(var_g.posedit.pos) + + # Check right side: interbase end = var_g.posedit.pos.end.base - gc_offset + end_offset = var_g.posedit.pos.end.base - gc_offset + for i, op in enumerate(cigar_op): + if op == "I" and ref_pos[i] == end_offset: + # I-segment starts exactly at the interbase end of the variant + pos_g.end.base = ref_pos[i + 1] + gc_offset + break + + # Check left side: interbase start = var_g.posedit.pos.start.base - 1 - gc_offset + start_offset = var_g.posedit.pos.start.base - 1 - gc_offset + for i, op in enumerate(cigar_op): + if op == "I" and ref_pos[i + 1] == start_offset: + # I-segment ends exactly at the interbase start of the variant + pos_g.start.base = ref_pos[i] + gc_offset + 1 + break + + return pos_g + def _gap_segments_within_pos_g(self, mapper, pos_g): """Return gap segments (I and D CIGAR ops) that fall within genomic interval pos_g. @@ -773,7 +823,10 @@ def _get_altered_tx_sequence(self, strand, mapper, pos_g, var_g, tx_pos): tx_alt_str = "".join(prefix_tx) + "".join(suffix_tx) elif edit.type in ("delins", "dup", "inv", "identity"): prefix_tx = [seq[j] for j in range(var_start) if j not in i_offsets] - suffix_tx = [seq[j] for j in range(var_end, len(seq)) if j not in i_offsets] + # Include adjacent I-seg at exactly var_end in the alt suffix: this base is in the + # genomic reference but absent from the transcript, so it is included in tx_alt to + # allow the gap cancellation arithmetic to produce the correct minimal edit. + suffix_tx = [seq[j] for j in range(var_end, len(seq)) if j not in i_offsets or j == var_end] if edit.type == "delins": ins_seq = edit.alt or "" elif edit.type == "dup": diff --git a/tests/data/cache-py3.hdp b/tests/data/cache-py3.hdp index d3463ea8941595fe48f7e4aedcdeea1db8e37198..97a8ab6dcfefb491e2abe59e8efb35e2b75a4bb9 100644 GIT binary patch delta 211 zcmeyowU=?@_Fl$@7RDB)7UmX~7Sq57TFfL7Wo#17R45&Ey|^?Y$jq1Krp?~U-|0x05|0{5!S3I zMxem1C}s2M{!z+eoE#Ps>|XppV)}(tB?*omc4uctAe`R!MoCiBmxI+;mNjd}S_X!! snLr}7BdJH!JtV|E#5u&-IRu2A!5px1HeY5|AOai2-oq5)KBZI-0ABSz82|tP delta 112 zcmWN;F&2Sf0KjoyQYsNDr4+v1>I4{>JAmDg+3+8=^ Date: Sun, 15 Mar 2026 15:45:20 -0700 Subject: [PATCH 4/6] pydoc improvement --- tests/test_hgvs_assemblymapper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_hgvs_assemblymapper.py b/tests/test_hgvs_assemblymapper.py index 5cc459ed..5930e678 100644 --- a/tests/test_hgvs_assemblymapper.py +++ b/tests/test_hgvs_assemblymapper.py @@ -192,9 +192,9 @@ def test_projection_at_alignment_discrepancy(self): assert str(var_g2) == hgvs_g2 assert str(var_c2) == hgvs_c2 - # deletion spanning a 'D' (transcript deletion) in the CIGAR alignment - # NM_015120.4 exon 1 CIGAR: 146=3D289= (genome has 3 extra bases not in transcript) - # g.73385901_73385903del spans into the 3D region → maps to c.34_36del + # deletion spanning a 'D' segment in the CIGAR alignment + # NM_015120.4 exon 1 CIGAR: 146=3D289= (transcript has 3 extra bases not in genome) + # g.73385901_73385903del spans across the 3D boundary → maps to c.34_36del hgvs_g = "NC_000002.12:g.73385901_73385903del" hgvs_c = "NM_015120.4:c.34_36del" From 0d88950b325493b8740b9bbe2db09e463c57e48f Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 15 Mar 2026 20:45:33 -0700 Subject: [PATCH 5/6] more checks and documentation --- src/hgvs/variantmapper.py | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/hgvs/variantmapper.py b/src/hgvs/variantmapper.py index 6f708bd6..decfd717 100644 --- a/src/hgvs/variantmapper.py +++ b/src/hgvs/variantmapper.py @@ -696,6 +696,10 @@ def _variant_spans_i_segment(self, mapper, var_g): if start_op is not None and end_op is not None: break + # If either endpoint falls outside the alignment, we can't determine the op + if start_op is None or end_op is None: + return False + # Apply fix only when at least one endpoint is in "I" and at least one is not ops = {start_op, end_op} return "I" in ops and ops != {"I"} @@ -798,6 +802,11 @@ def _get_altered_tx_sequence(self, strand, mapper, pos_g, var_g, tx_pos): # Find gap segment positions (0-based offsets into seq) gaps = self._gap_segments_within_pos_g(mapper, pos_g) i_offsets = gaps["I"] + # NOTE: gaps["D"] (transcript-only bases with no genomic counterpart) are detected but + # not yet used for sequence reconstruction. Variants spanning D-segments are routed here + # via _variant_has_internal_gap, and the deletion/delins mapping happens to be correct + # because D-segment bases are not present in the fetched genomic seq. Full D-segment + # splicing (inserting transcript-only bases into tx_ref_str) is a future enhancement. # Variant boundaries within seq (0-based) var_start = var_g.posedit.pos.start.base - pos_g.start.base @@ -823,18 +832,24 @@ def _get_altered_tx_sequence(self, strand, mapper, pos_g, var_g, tx_pos): tx_alt_str = "".join(prefix_tx) + "".join(suffix_tx) elif edit.type in ("delins", "dup", "inv", "identity"): prefix_tx = [seq[j] for j in range(var_start) if j not in i_offsets] - # Include adjacent I-seg at exactly var_end in the alt suffix: this base is in the - # genomic reference but absent from the transcript, so it is included in tx_alt to - # allow the gap cancellation arithmetic to produce the correct minimal edit. - suffix_tx = [seq[j] for j in range(var_end, len(seq)) if j not in i_offsets or j == var_end] if edit.type == "delins": + # For delins, include the adjacent I-seg base at exactly var_end in the suffix. + # This base is in the genomic reference but absent from the transcript; including + # it allows the prefix/suffix trimming step to cancel the double gap and produce + # the correct minimal transcript edit (e.g. 6-base delins → single SNV). + suffix_tx = [seq[j] for j in range(var_end, len(seq)) if j not in i_offsets or j == var_end] ins_seq = edit.alt or "" - elif edit.type == "dup": - ins_seq = "".join(seq[var_start:var_end]) * 2 - elif edit.type == "inv": - ins_seq = reverse_complement("".join(seq[var_start:var_end])) - else: # identity - ins_seq = "".join(seq[var_start:var_end]) + else: + # For dup/inv/identity the duplicated/inverted/copied sequence must be + # transcript-only bases — exclude any I-segment positions from the variant range. + suffix_tx = [seq[j] for j in range(var_end, len(seq)) if j not in i_offsets] + clean_variant_seq = "".join(seq[j] for j in range(var_start, var_end) if j not in i_offsets) + if edit.type == "dup": + ins_seq = clean_variant_seq * 2 + elif edit.type == "inv": + ins_seq = reverse_complement(clean_variant_seq) + else: # identity + ins_seq = clean_variant_seq tx_alt_str = "".join(prefix_tx) + ins_seq + "".join(suffix_tx) else: msg = f"_get_altered_tx_sequence: unsupported edit type {edit.type!r}" From e9e1a97eb1bea81331d321367a930ce0f48b0277 Mon Sep 17 00:00:00 2001 From: Andreas Prlic Date: Sun, 15 Mar 2026 22:42:38 -0700 Subject: [PATCH 6/6] removing accidentally commited files --- .beads/.gitignore | 49 ---------- .beads/README.md | 81 ----------------- .beads/config.yaml | 54 ------------ .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/interactions.jsonl | 0 .beads/metadata.json | 7 -- AGENTS.md | 152 -------------------------------- 11 files changed, 463 deletions(-) delete mode 100644 .beads/.gitignore delete mode 100644 .beads/README.md delete mode 100644 .beads/config.yaml delete mode 100755 .beads/hooks/post-checkout delete mode 100755 .beads/hooks/post-merge delete mode 100755 .beads/hooks/pre-commit delete mode 100755 .beads/hooks/pre-push delete mode 100755 .beads/hooks/prepare-commit-msg delete mode 100644 .beads/interactions.jsonl delete mode 100644 .beads/metadata.json delete mode 100644 AGENTS.md diff --git a/.beads/.gitignore b/.beads/.gitignore deleted file mode 100644 index 830ae107..00000000 --- a/.beads/.gitignore +++ /dev/null @@ -1,49 +0,0 @@ -# Dolt database (managed by Dolt, not git) -dolt/ -dolt-access.lock - -# Runtime files -bd.sock -bd.sock.startlock -sync-state.json -last-touched - -# 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/ - -# 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 - -# Backup data (auto-exported JSONL, local-only) -backup/ - -# 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 deleted file mode 100644 index dbfe3631..00000000 --- a/.beads/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# 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 deleted file mode 100644 index e831a6be..00000000 --- a/.beads/config.yaml +++ /dev/null @@ -1,54 +0,0 @@ -# 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 BD_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 diff --git a/.beads/hooks/post-checkout b/.beads/hooks/post-checkout deleted file mode 100755 index c1fa905b..00000000 --- a/.beads/hooks/post-checkout +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.60.0 --- -# 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:-30} - 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 v0.60.0 --- diff --git a/.beads/hooks/post-merge b/.beads/hooks/post-merge deleted file mode 100755 index dfbb5865..00000000 --- a/.beads/hooks/post-merge +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.60.0 --- -# 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:-30} - 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 v0.60.0 --- diff --git a/.beads/hooks/pre-commit b/.beads/hooks/pre-commit deleted file mode 100755 index c644d1f5..00000000 --- a/.beads/hooks/pre-commit +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.60.0 --- -# 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:-30} - 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 v0.60.0 --- diff --git a/.beads/hooks/pre-push b/.beads/hooks/pre-push deleted file mode 100755 index 46df32c9..00000000 --- a/.beads/hooks/pre-push +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.60.0 --- -# 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:-30} - 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 v0.60.0 --- diff --git a/.beads/hooks/prepare-commit-msg b/.beads/hooks/prepare-commit-msg deleted file mode 100755 index 25de9efe..00000000 --- a/.beads/hooks/prepare-commit-msg +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env sh -# --- BEGIN BEADS INTEGRATION v0.60.0 --- -# 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:-30} - 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 v0.60.0 --- diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl deleted file mode 100644 index e69de29b..00000000 diff --git a/.beads/metadata.json b/.beads/metadata.json deleted file mode 100644 index 8e59c308..00000000 --- a/.beads/metadata.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "database": "dolt", - "backend": "dolt", - "dolt_mode": "server", - "dolt_database": "hgvs", - "project_id": "ab72a248-33b9-4c7f-9e8c-cacf11bf1c3b" -} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index e3125d9a..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,152 +0,0 @@ -# Agent Instructions - -This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started. - -## Quick Reference - -```bash -bd ready # Find available work -bd show # View issue details -bd update --status in_progress # Claim work -bd close # Complete work -bd sync # Sync with git -``` - -## Landing the Plane (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 sync - 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 - - -## Issue Tracking with bd (beads) - -**IMPORTANT**: This project uses **bd (beads)** for ALL issue tracking. Do NOT use markdown TODOs, task lists, or other tracking methods. - -### Why bd? - -- Dependency-aware: Track blockers and relationships between issues -- Git-friendly: Dolt-powered version control with native sync -- Agent-optimized: JSON output, ready work detection, discovered-from links -- Prevents duplicate tracking systems and confusion - -### Quick Start - -**Check for ready work:** - -```bash -bd ready --json -``` - -**Create new issues:** - -```bash -bd create "Issue title" --description="Detailed context" -t bug|feature|task -p 0-4 --json -bd create "Issue title" --description="What this issue is about" -p 1 --deps discovered-from:bd-123 --json -``` - -**Claim and update:** - -```bash -bd update --claim --json -bd update bd-42 --priority 1 --json -``` - -**Complete work:** - -```bash -bd close bd-42 --reason "Completed" --json -``` - -### Issue Types - -- `bug` - Something broken -- `feature` - New functionality -- `task` - Work item (tests, docs, refactoring) -- `epic` - Large feature with subtasks -- `chore` - Maintenance (dependencies, tooling) - -### Priorities - -- `0` - Critical (security, data loss, broken builds) -- `1` - High (major features, important bugs) -- `2` - Medium (default, nice-to-have) -- `3` - Low (polish, optimization) -- `4` - Backlog (future ideas) - -### Workflow for AI Agents - -1. **Check ready work**: `bd ready` shows unblocked issues -2. **Claim your task atomically**: `bd update --claim` -3. **Work on it**: Implement, test, document -4. **Discover new work?** Create linked issue: - - `bd create "Found bug" --description="Details about what was found" -p 1 --deps discovered-from:` -5. **Complete**: `bd close --reason "Done"` - -### Auto-Sync - -bd automatically syncs via Dolt: - -- Each write auto-commits to Dolt history -- Use `bd dolt push`/`bd dolt pull` for remote sync -- No manual export/import needed! - -### Important Rules - -- ✅ Use bd for ALL task tracking -- ✅ Always use `--json` flag for programmatic use -- ✅ Link discovered work with `discovered-from` dependencies -- ✅ Check `bd ready` before asking "what should I work on?" -- ❌ Do NOT create markdown TODO lists -- ❌ Do NOT use external issue trackers -- ❌ Do NOT duplicate tracking systems - -For more details, see README.md and docs/QUICKSTART.md. - -## Landing the Plane (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 - -