diff --git a/.beads/.gitignore b/.beads/.gitignore new file mode 100644 index 0000000..d27a1db --- /dev/null +++ b/.beads/.gitignore @@ -0,0 +1,44 @@ +# SQLite databases +*.db +*.db?* +*.db-journal +*.db-wal +*.db-shm + +# Daemon runtime files +daemon.lock +daemon.log +daemon.pid +bd.sock +sync-state.json +last-touched + +# Local version tracking (prevents upgrade notification spam after git ops) +.local_version + +# Legacy database files +db.sqlite +bd.db + +# Worktree redirect file (contains relative path to main repo's .beads/) +# Must not be committed as paths would be wrong in other clones +redirect + +# Merge artifacts (temporary files from 3-way merge) +beads.base.jsonl +beads.base.meta.json +beads.left.jsonl +beads.left.meta.json +beads.right.jsonl +beads.right.meta.json + +# Sync state (local-only, per-machine) +# These files are machine-specific and should not be shared across clones +.sync.lock +sync_base.jsonl + +# NOTE: Do NOT add negation patterns (e.g., !issues.jsonl) here. +# They would override fork protection in .git/info/exclude, allowing +# contributors to accidentally commit upstream issue databases. +# The JSONL files (issues.jsonl, interactions.jsonl) and config files +# 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..50f281f --- /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 --status in_progress +bd update --status done + +# Sync with git remote +bd sync +``` + +### Working with Issues + +Issues in Beads are: +- **Git-native**: Stored in `.beads/issues.jsonl` and synced like code +- **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 +- Intelligent JSONL 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..1de3590 --- /dev/null +++ b/.beads/config.yaml @@ -0,0 +1,62 @@ +# 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: load from JSONL, no SQLite, write back after each command +# When true, bd will use .beads/issues.jsonl as the source of truth +# instead of SQLite database +# no-db: false + +# Disable daemon for RPC communication (forces direct database access) +# no-daemon: false + +# Disable auto-flush of database to JSONL after mutations +# no-auto-flush: false + +# Disable auto-import from JSONL when it's newer than database +# no-auto-import: false + +# Enable JSON output by default +# json: false + +# Default actor for audit trails (overridden by BD_ACTOR or --actor) +# actor: "" + +# Path to database (overridden by BEADS_DB or --db) +# db: "" + +# Auto-start daemon if not running (can also use BEADS_AUTO_START_DAEMON) +# auto-start-daemon: true + +# Debounce interval for auto-flush (can also use BEADS_FLUSH_DEBOUNCE) +# flush-debounce: "5s" + +# Git branch for beads commits (bd sync will commit to this branch) +# IMPORTANT: Set this for team projects so all clones use the same sync branch. +# This setting persists across clones (unlike database config which is gitignored). +# Can also use BEADS_SYNC_BRANCH env var for local override. +# If not set, bd sync will require you to run 'bd config set sync.branch '. +sync-branch: "beads-sync" + +# Multi-repo configuration (experimental - bd-307) +# Allows hydrating from multiple repositories and routing writes to the correct JSONL +# 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 + +# 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 \ No newline at end of file diff --git a/.beads/interactions.jsonl b/.beads/interactions.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 0000000..e69de29 diff --git a/.beads/metadata.json b/.beads/metadata.json new file mode 100644 index 0000000..c787975 --- /dev/null +++ b/.beads/metadata.json @@ -0,0 +1,4 @@ +{ + "database": "beads.db", + "jsonl_export": "issues.jsonl" +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..807d598 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ + +# Use bd merge for beads JSONL files +.beads/issues.jsonl merge=beads diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..296820f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,24 @@ +# Agent Instructions + +This project uses **bd** (beads) for issue tracking. + +Run `bd prime` for workflow context, or install hooks (`bd hooks install`) for auto-injection. + +## Quick Reference + +- `bd ready` - Find unblocked work +- `bd create "Title" --type task --priority 2` - Create issue +- `bd update --status in_progress` # Claim work +- `bd close ` - Complete work +- `bd sync` - Sync with git (run at session end) + +## Landing the Plane (Session Completion) + +**When ending a work session**, you MUST complete ALL steps below. + +**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. **Hand off** - Provide context for next session diff --git a/bot/src/training_common.rs b/bot/src/training_common.rs index 8c85021..3754086 100644 --- a/bot/src/training_common.rs +++ b/bot/src/training_common.rs @@ -15,7 +15,8 @@ pub const ACTION_SPACE_SIZE: usize = 514; pub enum TrictracAction { /// Lancer les dés Roll, - /// Continuer après avoir gagné un trou + /// Faire un nouveau 'relevé' (repositionnement des dames à l'état de départ) après avoir gagné un trou, + /// au lieu de continuer dans la position courante Go, /// Effectuer un mouvement de pions Move { diff --git a/doc/ai/history/beads.md b/doc/ai/history/beads.md new file mode 100644 index 0000000..dad9923 --- /dev/null +++ b/doc/ai/history/beads.md @@ -0,0 +1,24 @@ +```sh +❯ bd init + Repository ID: d5459d4d + Clone ID: 73ab432945c43882 + ✓ Created AGENTS.md with landing-the-plane instructions + +✓ bd initialized successfully! + + Database: .beads/beads.db + Issue prefix: trictrac + Issues will be named: trictrac- (e.g., trictrac-a3f2dd) + +Run bd quickstart to get started. + +⚠ Setup incomplete. Some issues were detected: + • Git Hooks: Missing 1 recommended hook(s) + • Sync Divergence: 1 sync divergence issue(s) detected + • Claude Integration: Not configured + • Git Working Tree: Uncommitted changes present + • Version Tracking: Version tracking not initialized + • Sync Branch Config: sync-branch not configured + +Run bd doctor --fix to see details and fix these issues. +``` diff --git a/bot/pyproject.toml b/store/pyproject.toml similarity index 100% rename from bot/pyproject.toml rename to store/pyproject.toml diff --git a/store/src/pyengine.rs b/store/src/pyengine.rs index af2b650..b436baa 100644 --- a/store/src/pyengine.rs +++ b/store/src/pyengine.rs @@ -3,7 +3,7 @@ use pyo3::prelude::*; use pyo3::types::PyDict; use crate::board::CheckerMove; -use crate::dice::Dice; +use crate::dice::{Dice, DiceRoller}; use crate::game::{GameEvent, GameState, Stage, TurnStage}; use crate::game_rules_moves::MoveRules; use crate::game_rules_points::PointsRules; @@ -24,7 +24,7 @@ impl TricTrac { // Initialiser 2 joueurs game_state.init_player("player1"); - game_state.init_player("bot"); + game_state.init_player("player2"); // Commencer la partie avec le joueur 1 game_state.consume(&GameEvent::BeginGame { goes_first: 1 }); @@ -36,50 +36,81 @@ impl TricTrac { } } + /// Obtenir l'état du jeu sous forme de dictionnaire + fn get_state_dict<'py>(&self, py: Python<'py>) -> PyResult> { + let dict = PyDict::new(py); + dict.set_item("stage", format!("{:?}", self.game_state.stage))?; + dict.set_item("turn_stage", format!("{:?}", self.game_state.turn_stage))?; + dict.set_item("active_player_id", self.game_state.active_player_id)?; + + // Board + let board_list = self.game_state.board.to_vec(); // returns Vec + dict.set_item("board", board_list)?; + + // Dice + dict.set_item("dice", (self.game_state.dice.values.0, self.game_state.dice.values.1))?; + + // Players + let players_dict = PyDict::new(py); + for (id, player) in &self.game_state.players { + let p_dict = PyDict::new(py); + p_dict.set_item("color", format!("{:?}", player.color))?; + p_dict.set_item("holes", player.holes)?; + p_dict.set_item("points", player.points)?; + p_dict.set_item("can_bredouille", player.can_bredouille)?; + p_dict.set_item("dice_roll_count", player.dice_roll_count)?; + players_dict.set_item(id, p_dict)?; + } + dict.set_item("players", players_dict)?; + + Ok(dict) + } + + /// Lance les dés ou utilise la séquence prédéfinie + fn roll_dice(&mut self) -> PyResult<(u8, u8)> { + let player_id = self.game_state.active_player_id; + + if self.game_state.turn_stage != TurnStage::RollDice { + return Err(pyo3::exceptions::PyRuntimeError::new_err("Not in RollDice stage")); + } + + self.game_state.consume(&GameEvent::Roll { player_id }); + + let dice = if self.current_dice_index < self.dice_roll_sequence.len() { + let vals = self.dice_roll_sequence[self.current_dice_index]; + self.current_dice_index += 1; + Dice { values: vals } + } else { + DiceRoller::default().roll() + }; + + self.game_state.consume(&GameEvent::RollResult { player_id, dice }); + + Ok(dice.values) + } + + /// Applique un mouvement (deux déplacements de dames) + fn apply_move(&mut self, from1: usize, to1: usize, from2: usize, to2: usize) -> PyResult<()> { + let player_id = self.game_state.active_player_id; + + let m1 = CheckerMove::new(from1, to1).map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; + let m2 = CheckerMove::new(from2, to2).map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; + + let moves = (m1, m2); + + if !self.game_state.validate(&GameEvent::Move { player_id, moves }) { + return Err(pyo3::exceptions::PyValueError::new_err("Invalid move")); + } + + self.game_state.consume(&GameEvent::Move { player_id, moves }); + Ok(()) + } + /// Obtenir l'état du jeu sous forme de chaîne de caractères compacte fn get_state_id(&self) -> String { self.game_state.to_string_id() } - /// Obtenir l'état du jeu sous forme de dictionnaire pour faciliter l'entrainement - fn get_state_dict(&self) -> PyResult> { - Python::with_gil(|py| { - let state_dict = PyDict::new(py); - - // Informations essentielles sur l'état du jeu - state_dict.set_item("active_player", self.game_state.active_player_id)?; - state_dict.set_item("stage", format!("{:?}", self.game_state.stage))?; - state_dict.set_item("turn_stage", format!("{:?}", self.game_state.turn_stage))?; - - // Dés - let (dice1, dice2) = self.game_state.dice.values; - state_dict.set_item("dice", (dice1, dice2))?; - - // Points des joueurs - if let Some(white_player) = self.game_state.get_white_player() { - state_dict.set_item("white_points", white_player.points)?; - state_dict.set_item("white_holes", white_player.holes)?; - } - - if let Some(black_player) = self.game_state.get_black_player() { - state_dict.set_item("black_points", black_player.points)?; - state_dict.set_item("black_holes", black_player.holes)?; - } - - // Positions des pièces - let white_positions = self.get_checker_positions(Color::White); - let black_positions = self.get_checker_positions(Color::Black); - - state_dict.set_item("white_positions", white_positions)?; - state_dict.set_item("black_positions", black_positions)?; - - // État compact pour la comparaison d'états - state_dict.set_item("state_id", self.game_state.to_string_id())?; - - Ok(state_dict.into()) - }) - } - /// Renvoie les positions des pièces pour un joueur spécifique fn get_checker_positions(&self, color: Color) -> Vec<(usize, i8)> { self.game_state.board.get_color_fields(color) @@ -115,144 +146,6 @@ impl TricTrac { .collect() } - /// Jouer un coup ((from1, to1), (from2, to2)) - fn play_move(&mut self, moves: ((usize, usize), (usize, usize))) -> bool { - let ((from1, to1), (from2, to2)) = moves; - - // Vérifier que c'est au tour du joueur de jouer - if self.game_state.turn_stage != TurnStage::Move - && self.game_state.turn_stage != TurnStage::HoldOrGoChoice - { - return false; - } - - let move1 = CheckerMove::new(from1, to1).unwrap_or_default(); - let move2 = CheckerMove::new(from2, to2).unwrap_or_default(); - - let event = GameEvent::Move { - player_id: self.game_state.active_player_id, - moves: (move1, move2), - }; - - // Vérifier si le mouvement est valide - if !self.game_state.validate(&event) { - return false; - } - - // Exécuter le mouvement - self.game_state.consume(&event); - - // Si l'autre joueur doit lancer les dés maintenant, simuler ce lancement - if self.game_state.turn_stage == TurnStage::RollDice { - self.roll_dice(); - } - - true - } - - /// Lancer les dés (soit aléatoirement, soit en utilisant une séquence prédéfinie) - fn roll_dice(&mut self) -> (u8, u8) { - // Vérifier que c'est au bon moment pour lancer les dés - if self.game_state.turn_stage != TurnStage::RollDice - && self.game_state.turn_stage != TurnStage::RollWaiting - { - return self.game_state.dice.values; - } - - // Simuler un lancer de dés - let dice_values = if !self.dice_roll_sequence.is_empty() - && self.current_dice_index < self.dice_roll_sequence.len() - { - // Utiliser la séquence prédéfinie - let dice = self.dice_roll_sequence[self.current_dice_index]; - self.current_dice_index += 1; - dice - } else { - // Générer aléatoirement - ( - (1 + (rand::random::() % 6)), - (1 + (rand::random::() % 6)), - ) - }; - - // Envoyer les événements appropriés - let roll_event = GameEvent::Roll { - player_id: self.game_state.active_player_id, - }; - - if self.game_state.validate(&roll_event) { - self.game_state.consume(&roll_event); - } - - let roll_result_event = GameEvent::RollResult { - player_id: self.game_state.active_player_id, - dice: Dice { - values: dice_values, - }, - }; - - if self.game_state.validate(&roll_result_event) { - self.game_state.consume(&roll_result_event); - } - - dice_values - } - - /// Marquer des points - fn mark_points(&mut self, points: u8) -> bool { - // Vérifier que c'est au bon moment pour marquer des points - if self.game_state.turn_stage != TurnStage::MarkPoints - && self.game_state.turn_stage != TurnStage::MarkAdvPoints - { - return false; - } - - let event = GameEvent::Mark { - player_id: self.game_state.active_player_id, - points, - }; - - // Vérifier si l'événement est valide - if !self.game_state.validate(&event) { - return false; - } - - // Exécuter l'événement - self.game_state.consume(&event); - - // Si l'autre joueur doit lancer les dés maintenant, simuler ce lancement - if self.game_state.turn_stage == TurnStage::RollDice { - self.roll_dice(); - } - - true - } - - /// Choisir de "continuer" (Go) après avoir gagné un trou - fn choose_go(&mut self) -> bool { - // Vérifier que c'est au bon moment pour choisir de continuer - if self.game_state.turn_stage != TurnStage::HoldOrGoChoice { - return false; - } - - let event = GameEvent::Go { - player_id: self.game_state.active_player_id, - }; - - // Vérifier si l'événement est valide - if !self.game_state.validate(&event) { - return false; - } - - // Exécuter l'événement - self.game_state.consume(&event); - - // Simuler le lancer de dés pour le prochain tour - self.roll_dice(); - - true - } - /// Calcule les points maximaux que le joueur actif peut obtenir avec les dés actuels fn calculate_points(&self) -> u8 { let active_player = self @@ -280,7 +173,7 @@ impl TricTrac { // Initialiser 2 joueurs self.game_state.init_player("player1"); - self.game_state.init_player("bot"); + self.game_state.init_player("player2"); // Commencer la partie avec le joueur 1 self.game_state