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:
@ -16,7 +16,7 @@ use tokio::io::AsyncWriteExt;
|
||||
use domain::{
|
||||
AgentIssueRef, Issue, IssueActor, IssueCarnet, IssueId, IssueIndexEntry, IssueLink,
|
||||
IssueListFilter, IssueNumber, IssueNumberAllocator, IssuePriority, IssueRef, IssueStatus,
|
||||
IssueStore, IssueStoreError, IssueVersion, MarkdownDoc, ProjectPath,
|
||||
IssueStore, IssueStoreError, IssueVersion, MarkdownDoc, ProjectPath, SprintId,
|
||||
};
|
||||
|
||||
const IDEAI_DIR: &str = ".ideai";
|
||||
@ -211,6 +211,12 @@ fn filter_matches(row: &IssueIndexEntry, filter: &IssueListFilter) -> bool {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if filter
|
||||
.sprint
|
||||
.is_some_and(|sprint| row.sprint != Some(sprint))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Some(text) = filter
|
||||
.text
|
||||
.as_ref()
|
||||
@ -423,6 +429,7 @@ number: {}\n\
|
||||
title: {}\n\
|
||||
status: {}\n\
|
||||
priority: {}\n\
|
||||
sprint: {}\n\
|
||||
links: {}\n\
|
||||
agentRefs: {}\n\
|
||||
createdBy: {}\n\
|
||||
@ -436,6 +443,7 @@ version: {}\n\
|
||||
json_string(&issue.title),
|
||||
json_value(&issue.status),
|
||||
json_value(&issue.priority),
|
||||
json_value(&issue.sprint),
|
||||
json_value(&issue.links),
|
||||
json_value(&issue.agent_refs),
|
||||
json_value(&issue.created_by),
|
||||
@ -475,6 +483,7 @@ fn parse_issue_doc(text: &str) -> Result<Issue, IssueStoreError> {
|
||||
let title: String = parse_json_field(&map, "title")?;
|
||||
let status: IssueStatus = parse_json_field(&map, "status")?;
|
||||
let priority: IssuePriority = parse_json_field(&map, "priority")?;
|
||||
let sprint: Option<SprintId> = parse_optional_json_field(&map, "sprint")?.unwrap_or(None);
|
||||
let links: Vec<IssueLink> = parse_json_field(&map, "links")?;
|
||||
let agent_refs: Vec<AgentIssueRef> = parse_json_field(&map, "agentRefs")?;
|
||||
let created_by: IssueActor = parse_json_field(&map, "createdBy")?;
|
||||
@ -490,6 +499,7 @@ fn parse_issue_doc(text: &str) -> Result<Issue, IssueStoreError> {
|
||||
description: MarkdownDoc::new(body),
|
||||
status,
|
||||
priority,
|
||||
sprint,
|
||||
carnet: MarkdownDoc::default(),
|
||||
links,
|
||||
agent_refs,
|
||||
@ -567,6 +577,18 @@ fn parse_json_field<T: serde::de::DeserializeOwned>(
|
||||
serde_json::from_str(value).map_err(|err| IssueStoreError::Invalid(err.to_string()))
|
||||
}
|
||||
|
||||
fn parse_optional_json_field<T: serde::de::DeserializeOwned>(
|
||||
map: &BTreeMap<String, String>,
|
||||
key: &str,
|
||||
) -> Result<Option<T>, IssueStoreError> {
|
||||
let Some(value) = map.get(key) else {
|
||||
return Ok(None);
|
||||
};
|
||||
serde_json::from_str(value)
|
||||
.map(Some)
|
||||
.map_err(|err| IssueStoreError::Invalid(err.to_string()))
|
||||
}
|
||||
|
||||
fn json_value<T: Serialize>(value: &T) -> String {
|
||||
serde_json::to_string(value).expect("serializing issue frontmatter cannot fail")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user