feat(domain,app,infra): injection « État du projet » + outils MCP workstate (LS4)
Clôt le cold-start de coordination du programme live-state : - domain : WorkStatus::parse/label, commands ReadWorkState/SetWorkState (request status/intent/progress/lastDelegation), erreur UnknownWorkStatus + validate. - application : section bornée « # État du projet » injectée au lancement (port LiveStateLeanProvider, InjectedLiveRow, LIVE_STATE_INJECT_MAX, resolve_live_state) ; LiveStateReadProvider + read_workstate/set_workstate câblés au dispatch. - infrastructure : 2 ToolDef idea_workstate_read / idea_workstate_set (mapping, tool_returns_reply), compteur d'outils MCP 12→14. - app-tauri : providers câblés, garde du nombre d'outils 12→14. - tests (QA, verts) : domain/orchestrator, application lifecycle + orchestrator_service, infrastructure mcp_server, app-tauri state. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -48,6 +48,39 @@ pub enum WorkStatus {
|
||||
Done,
|
||||
}
|
||||
|
||||
impl WorkStatus {
|
||||
/// Parses a status label (case-insensitive) into a [`WorkStatus`].
|
||||
///
|
||||
/// Accepts exactly the five canonical labels (`idle`, `working`, `blocked`,
|
||||
/// `waiting`, `done`), ignoring surrounding whitespace and ASCII case. Returns
|
||||
/// `None` for anything else, so the caller can raise a typed error rather than
|
||||
/// guess a default.
|
||||
#[must_use]
|
||||
pub fn parse(raw: &str) -> Option<Self> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
"idle" => Some(Self::Idle),
|
||||
"working" => Some(Self::Working),
|
||||
"blocked" => Some(Self::Blocked),
|
||||
"waiting" => Some(Self::Waiting),
|
||||
"done" => Some(Self::Done),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The canonical, human-readable label of this status (the inverse of
|
||||
/// [`WorkStatus::parse`]): `idle`, `working`, `blocked`, `waiting` or `done`.
|
||||
#[must_use]
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Idle => "idle",
|
||||
Self::Working => "working",
|
||||
Self::Blocked => "blocked",
|
||||
Self::Waiting => "waiting",
|
||||
Self::Done => "done",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single agent's live-state row. One per [`AgentId`] in a [`LiveState`].
|
||||
///
|
||||
/// Construct via [`LiveEntry::new`], which enforces the field invariants
|
||||
@ -180,6 +213,30 @@ mod tests {
|
||||
TicketId::from_uuid(uuid::Uuid::from_u128(n))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn work_status_parse_is_case_insensitive_and_rejects_unknown() {
|
||||
assert_eq!(WorkStatus::parse("idle"), Some(WorkStatus::Idle));
|
||||
assert_eq!(WorkStatus::parse(" Working "), Some(WorkStatus::Working));
|
||||
assert_eq!(WorkStatus::parse("BLOCKED"), Some(WorkStatus::Blocked));
|
||||
assert_eq!(WorkStatus::parse("waiting"), Some(WorkStatus::Waiting));
|
||||
assert_eq!(WorkStatus::parse("Done"), Some(WorkStatus::Done));
|
||||
assert_eq!(WorkStatus::parse("busy"), None);
|
||||
assert_eq!(WorkStatus::parse(""), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn work_status_label_round_trips_through_parse() {
|
||||
for status in [
|
||||
WorkStatus::Idle,
|
||||
WorkStatus::Working,
|
||||
WorkStatus::Blocked,
|
||||
WorkStatus::Waiting,
|
||||
WorkStatus::Done,
|
||||
] {
|
||||
assert_eq!(WorkStatus::parse(status.label()), Some(status));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_truncates_intent_and_progress_at_soft_bound() {
|
||||
let long = "x".repeat(FIELD_PREVIEW_MAX_CHARS + 50);
|
||||
|
||||
Reference in New Issue
Block a user