feat(sprints): modèle de sprints — domaine, use-cases, persistance et surfaces (backend)

Ticket #10 — introduction du modèle de sprints côté backend.

- Domaine : nouvel agrégat Sprint (sprint.rs), IDs, événements et invariants ;
  rattachement des issues à un sprint (issue.rs) et ports associés.
- Application : use-cases sprints (application/src/sprints) + erreurs dédiées.
- Infrastructure : store de sprints (infrastructure/src/sprints.rs), adaptation
  du store d'issues et exposition MCP via orchestrator/mcp/tickets.rs.
- app-tauri : commandes, state et events pour piloter les sprints depuis l'UI.

Tests domaine/application/infra/app-tauri verts (sprint_usecases, sprint_store,
issue_store, mcp_server).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 10:57:33 +02:00
parent 9740149ebb
commit 48b652ff91
21 changed files with 2896 additions and 45 deletions

View File

@ -32,7 +32,7 @@ use crate::background_task::{
BackgroundTask, BackgroundTaskKind, BackgroundTaskResult, BackgroundTaskWakePolicy,
};
use crate::events::DomainEvent;
use crate::ids::{AgentId, NodeId, ProjectId, ScheduleId, SessionId, TaskId};
use crate::ids::{AgentId, NodeId, ProjectId, ScheduleId, SessionId, SprintId, TaskId};
use crate::issue::{
Issue, IssueCarnet, IssueIndexEntry, IssueListFilter, IssueNumber, IssueRef, IssueVersion,
};
@ -43,6 +43,7 @@ use crate::profile::{AgentProfile, EmbedderProfile};
use crate::project::{Project, ProjectPath};
use crate::remote::RemoteKind;
use crate::skill::{Skill, SkillScope};
use crate::sprint::{Sprint, SprintIndexEntry, SprintVersion};
use crate::template::AgentTemplate;
use crate::terminal::PtySize;
@ -596,6 +597,28 @@ pub enum IssueStoreError {
#[error("issue store failed: {0}")]
Store(String),
}
/// Errors from the sprint store.
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum SprintStoreError {
/// The requested sprint does not exist.
#[error("sprint not found")]
NotFound,
/// Optimistic concurrency conflict.
#[error("sprint version conflict: expected {expected}, actual {actual}")]
VersionConflict {
/// Expected version.
expected: SprintVersion,
/// Actual persisted version.
actual: SprintVersion,
},
/// The sprint payload is invalid.
#[error("sprint invalid: {0}")]
Invalid(String),
/// Store I/O or serialization failed.
#[error("sprint store failed: {0}")]
Store(String),
}
// ---------------------------------------------------------------------------
// Git port support types
// ---------------------------------------------------------------------------
@ -1507,6 +1530,50 @@ pub trait IssueStore: Send + Sync {
) -> Result<IssueCarnet, IssueStoreError>;
}
/// Persistence port for project-scoped sprints.
#[async_trait]
pub trait SprintStore: Send + Sync {
/// Creates a new sprint.
///
/// # Errors
/// [`SprintStoreError`] when the sprint already exists or cannot be stored.
async fn create(&self, root: &ProjectPath, sprint: &Sprint) -> Result<(), SprintStoreError>;
/// Reads a sprint by stable id.
///
/// # Errors
/// [`SprintStoreError::NotFound`] when absent.
async fn get(
&self,
root: &ProjectPath,
sprint_id: SprintId,
) -> Result<Sprint, SprintStoreError>;
/// Lists sprint index entries.
///
/// # Errors
/// [`SprintStoreError`] on persistence failure.
async fn list(&self, root: &ProjectPath) -> Result<Vec<SprintIndexEntry>, SprintStoreError>;
/// Replaces a sprint after checking its expected version.
///
/// # Errors
/// [`SprintStoreError::VersionConflict`] on optimistic-concurrency conflict.
async fn update(
&self,
root: &ProjectPath,
sprint: &Sprint,
expected_version: SprintVersion,
) -> Result<(), SprintStoreError>;
/// Deletes a sprint by id.
///
/// # Errors
/// [`SprintStoreError::NotFound`] when absent.
async fn delete(&self, root: &ProjectPath, sprint_id: SprintId)
-> Result<(), SprintStoreError>;
}
/// Git operations for a project. Named `GitPort` to avoid clashing with the
/// [`crate::git::GitRepository`] *entity* (state image).
#[async_trait]