huskies: merge 858
This commit is contained in:
@@ -1,12 +1,19 @@
|
|||||||
//! Agent subsystem — types, configuration, and orchestration for coding agents.
|
//! Agent subsystem — types, configuration, and orchestration for coding agents.
|
||||||
|
/// Acceptance-gate checks (clippy, tests, doc coverage).
|
||||||
pub mod gates;
|
pub mod gates;
|
||||||
|
/// Agent start/stop and pipeline advancement on completion.
|
||||||
pub mod lifecycle;
|
pub mod lifecycle;
|
||||||
|
/// Constructs the system prompt sent to coding agents.
|
||||||
pub mod local_prompt;
|
pub mod local_prompt;
|
||||||
|
/// Merge-conflict resolution helpers.
|
||||||
pub mod merge;
|
pub mod merge;
|
||||||
pub(crate) mod pool;
|
pub(crate) mod pool;
|
||||||
pub(crate) mod pty;
|
pub(crate) mod pty;
|
||||||
|
/// Runtime backends (Claude Code, OpenAI, Gemini) that execute agent sessions.
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
/// Persistent session-ID storage for agent resume support.
|
||||||
pub mod session_store;
|
pub mod session_store;
|
||||||
|
/// Token-usage tracking and budget estimation.
|
||||||
pub mod token_usage;
|
pub mod token_usage;
|
||||||
|
|
||||||
use crate::config::AgentConfig;
|
use crate::config::AgentConfig;
|
||||||
@@ -74,6 +81,7 @@ pub enum AgentEvent {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
/// Lifecycle state of an agent session.
|
||||||
pub enum AgentStatus {
|
pub enum AgentStatus {
|
||||||
Pending,
|
Pending,
|
||||||
Running,
|
Running,
|
||||||
@@ -212,6 +220,7 @@ impl TokenUsage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Clone)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
|
/// Snapshot of a running or completed agent, exposed via the HTTP API.
|
||||||
pub struct AgentInfo {
|
pub struct AgentInfo {
|
||||||
pub story_id: String,
|
pub story_id: String,
|
||||||
pub agent_name: String,
|
pub agent_name: String,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ pub struct AgentPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AgentPool {
|
impl AgentPool {
|
||||||
|
/// Create a new agent pool bound to the given HTTP port and event channel.
|
||||||
pub fn new(port: u16, watcher_tx: broadcast::Sender<WatcherEvent>) -> Self {
|
pub fn new(port: u16, watcher_tx: broadcast::Sender<WatcherEvent>) -> Self {
|
||||||
let pool = Self {
|
let pool = Self {
|
||||||
agents: Arc::new(Mutex::new(HashMap::new())),
|
agents: Arc::new(Mutex::new(HashMap::new())),
|
||||||
@@ -98,6 +99,7 @@ impl AgentPool {
|
|||||||
pool
|
pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the HTTP port this pool's agents connect to.
|
||||||
pub fn port(&self) -> u16 {
|
pub fn port(&self) -> u16 {
|
||||||
self.port
|
self.port
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub struct ClaudeCodeRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClaudeCodeRuntime {
|
impl ClaudeCodeRuntime {
|
||||||
|
/// Create a new Claude Code runtime with shared child-killer registry and event channel.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
child_killers: Arc<Mutex<HashMap<String, Box<dyn ChildKiller + Send + Sync>>>>,
|
child_killers: Arc<Mutex<HashMap<String, Box<dyn ChildKiller + Send + Sync>>>>,
|
||||||
watcher_tx: broadcast::Sender<WatcherEvent>,
|
watcher_tx: broadcast::Sender<WatcherEvent>,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ pub struct GeminiRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GeminiRuntime {
|
impl GeminiRuntime {
|
||||||
|
/// Create a new Gemini runtime instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cancelled: Arc::new(AtomicBool::new(false)),
|
cancelled: Arc::new(AtomicBool::new(false)),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub struct OpenAiRuntime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OpenAiRuntime {
|
impl OpenAiRuntime {
|
||||||
|
/// Create a new OpenAI runtime instance.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cancelled: Arc::new(AtomicBool::new(false)),
|
cancelled: Arc::new(AtomicBool::new(false)),
|
||||||
|
|||||||
@@ -4,13 +4,18 @@
|
|||||||
//! sending and editing messages, allowing the bot logic (commands, htop,
|
//! sending and editing messages, allowing the bot logic (commands, htop,
|
||||||
//! notifications) to work against any chat platform — Matrix, WhatsApp, etc.
|
//! notifications) to work against any chat platform — Matrix, WhatsApp, etc.
|
||||||
|
|
||||||
|
/// Bot command registry and dispatch — parses and routes incoming chat messages.
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
/// Chat history utilities — loading and serialising conversation history.
|
||||||
pub mod history;
|
pub mod history;
|
||||||
pub(crate) mod lookup;
|
pub(crate) mod lookup;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test_helpers;
|
pub(crate) mod test_helpers;
|
||||||
|
/// Rate-limit retry timers — stores and fires scheduled retry reminders.
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
|
/// Platform transports — pluggable backends (Matrix, Slack, WhatsApp, Discord).
|
||||||
pub mod transport;
|
pub mod transport;
|
||||||
|
/// Chat utility functions — shared helpers for message formatting and bot logic.
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pub struct DiscordTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiscordTransport {
|
impl DiscordTransport {
|
||||||
|
/// Creates a new `DiscordTransport` authenticated with the given bot token.
|
||||||
pub fn new(bot_token: String) -> Self {
|
pub fn new(bot_token: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bot_token,
|
bot_token,
|
||||||
|
|||||||
@@ -7,10 +7,15 @@
|
|||||||
//! receives `MESSAGE_CREATE` events, and dispatches commands.
|
//! receives `MESSAGE_CREATE` events, and dispatches commands.
|
||||||
//! - [`DiscordContext`] — shared context for the bot.
|
//! - [`DiscordContext`] — shared context for the bot.
|
||||||
|
|
||||||
|
/// Discord bot command handlers — parses and dispatches bot commands from Discord messages.
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
/// Discord message formatter — converts markdown to Discord-compatible markup.
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
/// Discord Gateway WebSocket — connects to the Discord Gateway and handles MESSAGE_CREATE events.
|
||||||
pub mod gateway;
|
pub mod gateway;
|
||||||
|
/// Discord conversation history — loads prior chat history for context.
|
||||||
pub mod history;
|
pub mod history;
|
||||||
|
/// DiscordTransport — `ChatTransport` implementation for the Discord Bot API.
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
|
||||||
pub use commands::DiscordContext;
|
pub use commands::DiscordContext;
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
//! Matrix bot — sub-modules for the Matrix chat bot implementation.
|
//! Matrix bot — sub-modules for the Matrix chat bot implementation.
|
||||||
|
/// Bot context — shared state passed to Matrix bot command handlers.
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
/// Matrix message formatter — converts markdown to Matrix HTML.
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
/// Conversation history — loads and saves per-room chat history.
|
||||||
pub mod history;
|
pub mod history;
|
||||||
|
/// Mention detection — identifies messages that mention the bot user.
|
||||||
pub mod mentions;
|
pub mod mentions;
|
||||||
|
/// Message handlers — processes incoming Matrix room messages.
|
||||||
pub mod messages;
|
pub mod messages;
|
||||||
|
/// Bot run loop — the main async task that drives the Matrix sync loop.
|
||||||
pub mod run;
|
pub mod run;
|
||||||
|
/// Device verification — handles Matrix cross-signing and emoji verification flows.
|
||||||
pub mod verification;
|
pub mod verification;
|
||||||
|
|
||||||
// Re-export all public types so existing import paths continue to work.
|
// Re-export all public types so existing import paths continue to work.
|
||||||
|
|||||||
@@ -15,16 +15,25 @@
|
|||||||
//! Multi-room support: configure `room_ids = ["!room1:…", "!room2:…"]` in
|
//! Multi-room support: configure `room_ids = ["!room1:…", "!room2:…"]` in
|
||||||
//! `bot.toml`. Each room maintains its own independent conversation history.
|
//! `bot.toml`. Each room maintains its own independent conversation history.
|
||||||
|
|
||||||
|
/// Auto-assign handler — listens for pipeline events and assigns stories to free agents.
|
||||||
pub mod assign;
|
pub mod assign;
|
||||||
mod bot;
|
mod bot;
|
||||||
|
/// Matrix bot command handlers — parses and routes bot commands from Matrix messages.
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub(crate) mod config;
|
pub(crate) mod config;
|
||||||
|
/// Story deletion command — handles `!delete` bot commands to remove work items.
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
|
/// htop-style agent monitor command — renders a live process table in Matrix.
|
||||||
pub mod htop;
|
pub mod htop;
|
||||||
|
/// Rebuild command — triggers a server rebuild/restart via a bot command.
|
||||||
pub mod rebuild;
|
pub mod rebuild;
|
||||||
|
/// Reset command — handles `!reset` bot commands to restart the server state.
|
||||||
pub mod reset;
|
pub mod reset;
|
||||||
|
/// rmtree command — handles `!rmtree` bot commands to remove worktrees.
|
||||||
pub mod rmtree;
|
pub mod rmtree;
|
||||||
|
/// Start command — handles `!start` bot commands to launch agents on stories.
|
||||||
pub mod start;
|
pub mod start;
|
||||||
|
/// Matrix `ChatTransport` implementation wrapping the Matrix SDK client.
|
||||||
pub mod transport_impl;
|
pub mod transport_impl;
|
||||||
|
|
||||||
pub use bot::{ConversationEntry, ConversationRole, RoomConversation};
|
pub use bot::{ConversationEntry, ConversationRole, RoomConversation};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub struct MatrixTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MatrixTransport {
|
impl MatrixTransport {
|
||||||
|
/// Creates a new `MatrixTransport` wrapping the given Matrix SDK `Client`.
|
||||||
pub fn new(client: Client) -> Self {
|
pub fn new(client: Client) -> Self {
|
||||||
Self { client }
|
Self { client }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
//! Chat transports — pluggable backends (Matrix, Slack, WhatsApp, Discord) for bot messaging.
|
//! Chat transports — pluggable backends (Matrix, Slack, WhatsApp, Discord) for bot messaging.
|
||||||
|
/// Discord bot transport — sends and receives messages via the Discord REST/Gateway APIs.
|
||||||
pub mod discord;
|
pub mod discord;
|
||||||
|
/// Matrix bot transport — sends messages via the Matrix SDK and runs the sync loop.
|
||||||
pub mod matrix;
|
pub mod matrix;
|
||||||
|
/// Slack bot transport — sends messages via the Slack Web API and handles webhook events.
|
||||||
pub mod slack;
|
pub mod slack;
|
||||||
|
/// WhatsApp transport — sends messages via Meta Cloud API and Twilio API.
|
||||||
pub mod whatsapp;
|
pub mod whatsapp;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub struct SlackTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SlackTransport {
|
impl SlackTransport {
|
||||||
|
/// Creates a new `SlackTransport` authenticated with the given bot token.
|
||||||
pub fn new(bot_token: String) -> Self {
|
pub fn new(bot_token: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bot_token,
|
bot_token,
|
||||||
|
|||||||
@@ -6,10 +6,15 @@
|
|||||||
//! - [`webhook_receive`] — Poem handler for the Slack Events API webhook
|
//! - [`webhook_receive`] — Poem handler for the Slack Events API webhook
|
||||||
//! (POST incoming events including URL verification challenge).
|
//! (POST incoming events including URL verification challenge).
|
||||||
|
|
||||||
|
/// Slack bot command handlers — parses and dispatches bot commands from Slack messages.
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
/// Slack message formatter — converts markdown to Slack mrkdwn syntax.
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
/// Slack conversation history — loads prior chat history for context.
|
||||||
pub mod history;
|
pub mod history;
|
||||||
|
/// SlackTransport — `ChatTransport` implementation for the Slack Bot API.
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
|
/// Slack request signature verification — validates HMAC-SHA256 signatures on incoming webhooks.
|
||||||
pub mod verify;
|
pub mod verify;
|
||||||
|
|
||||||
pub use commands::SlackWebhookContext;
|
pub use commands::SlackWebhookContext;
|
||||||
@@ -38,6 +43,7 @@ pub struct SlackEventEnvelope {
|
|||||||
pub event: Option<SlackEvent>,
|
pub event: Option<SlackEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single Slack event payload (message, reaction, etc.) nested inside an event callback.
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct SlackEvent {
|
pub struct SlackEvent {
|
||||||
pub r#type: Option<String>,
|
pub r#type: Option<String>,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ pub struct WhatsAppTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WhatsAppTransport {
|
impl WhatsAppTransport {
|
||||||
|
/// Creates a new `WhatsAppTransport` authenticated with the given Meta Cloud API credentials.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
phone_number_id: String,
|
phone_number_id: String,
|
||||||
access_token: String,
|
access_token: String,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub struct TwilioWhatsAppTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TwilioWhatsAppTransport {
|
impl TwilioWhatsAppTransport {
|
||||||
|
/// Creates a new `TwilioWhatsAppTransport` authenticated with the given Twilio credentials.
|
||||||
pub fn new(account_sid: String, auth_token: String, from_number: String) -> Self {
|
pub fn new(account_sid: String, auth_token: String, from_number: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
account_sid,
|
account_sid,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use serde::Deserialize;
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Top-level project configuration loaded from `.huskies/project.toml`.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct ProjectConfig {
|
pub struct ProjectConfig {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -195,6 +196,7 @@ fn default_max_mesh_peers() -> usize {
|
|||||||
3
|
3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for a project component (name, path, setup/teardown commands).
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct ComponentConfig {
|
pub struct ComponentConfig {
|
||||||
@@ -207,6 +209,7 @@ pub struct ComponentConfig {
|
|||||||
pub teardown: Vec<String>,
|
pub teardown: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configuration for a single agent definition from `[[agent]]` in `project.toml`.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct AgentConfig {
|
pub struct AgentConfig {
|
||||||
#[serde(default = "default_agent_name")]
|
#[serde(default = "default_agent_name")]
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ pub(crate) use state::{ALL_OPS, VECTOR_CLOCK};
|
|||||||
|
|
||||||
/// Hex-encode a byte slice (no external dep needed).
|
/// Hex-encode a byte slice (no external dep needed).
|
||||||
pub(crate) mod hex {
|
pub(crate) mod hex {
|
||||||
|
/// Encode `bytes` as a lowercase hexadecimal string.
|
||||||
pub fn encode(bytes: &[u8]) -> String {
|
pub fn encode(bytes: &[u8]) -> String {
|
||||||
bytes.iter().map(|b| format!("{b:02x}")).collect()
|
bytes.iter().map(|b| format!("{b:02x}")).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ pub struct GatewayConfigCrdt {
|
|||||||
pub active_project: LwwRegisterCrdt<String>,
|
pub active_project: LwwRegisterCrdt<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Top-level CRDT document holding all replicated pipeline state (items, nodes, jobs, etc.).
|
||||||
#[add_crdt_fields]
|
#[add_crdt_fields]
|
||||||
#[derive(Clone, CrdtNode, Debug)]
|
#[derive(Clone, CrdtNode, Debug)]
|
||||||
pub struct PipelineDoc {
|
pub struct PipelineDoc {
|
||||||
@@ -57,6 +58,7 @@ pub struct PipelineDoc {
|
|||||||
pub gateway_config: GatewayConfigCrdt,
|
pub gateway_config: GatewayConfigCrdt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CRDT sub-document representing a single pipeline work item with LWW fields for stage, agent, etc.
|
||||||
#[add_crdt_fields]
|
#[add_crdt_fields]
|
||||||
#[derive(Clone, CrdtNode, Debug)]
|
#[derive(Clone, CrdtNode, Debug)]
|
||||||
pub struct PipelineItemCrdt {
|
pub struct PipelineItemCrdt {
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ fn map_svc_error(err: svc::Error) -> poem::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for agent management (start, stop, list, inspect).
|
||||||
pub struct AgentsApi {
|
pub struct AgentsApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,13 @@ enum AnthropicTags {
|
|||||||
Anthropic,
|
Anthropic,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for Anthropic API key and model operations.
|
||||||
pub struct AnthropicApi {
|
pub struct AnthropicApi {
|
||||||
ctx: Arc<AppContext>,
|
ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AnthropicApi {
|
impl AnthropicApi {
|
||||||
|
/// Create a new `AnthropicApi` bound to the given application context.
|
||||||
pub fn new(ctx: Arc<AppContext>) -> Self {
|
pub fn new(ctx: Arc<AppContext>) -> Self {
|
||||||
Self { ctx }
|
Self { ctx }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ struct BotCommandResponse {
|
|||||||
response: String,
|
response: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for bot slash-command execution.
|
||||||
pub struct BotCommandApi {
|
pub struct BotCommandApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct BotConfigPayload {
|
|||||||
pub slack_channel_ids: Option<Vec<String>>,
|
pub slack_channel_ids: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for reading and writing bot configuration.
|
||||||
pub struct BotConfigApi {
|
pub struct BotConfigApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ enum ChatTags {
|
|||||||
Chat,
|
Chat,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for the LLM-powered chat interface.
|
||||||
pub struct ChatApi {
|
pub struct ChatApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ pub struct PermissionForward {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
/// Shared application state threaded through all HTTP handlers via Poem's `Data` extractor.
|
||||||
pub struct AppContext {
|
pub struct AppContext {
|
||||||
pub state: Arc<SessionState>,
|
pub state: Arc<SessionState>,
|
||||||
pub store: Arc<JsonFileStore>,
|
pub store: Arc<JsonFileStore>,
|
||||||
@@ -78,6 +79,7 @@ pub struct AppContext {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl AppContext {
|
impl AppContext {
|
||||||
|
/// Build a minimal `AppContext` for unit tests with an in-memory store.
|
||||||
pub fn new_test(project_root: std::path::PathBuf) -> Self {
|
pub fn new_test(project_root: std::path::PathBuf) -> Self {
|
||||||
use crate::agents::AgentPool;
|
use crate::agents::AgentPool;
|
||||||
let state = SessionState::default();
|
let state = SessionState::default();
|
||||||
@@ -119,12 +121,15 @@ impl AppContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Alias for `poem::Result<T>` used by OpenAPI handler return types.
|
||||||
pub type OpenApiResult<T> = poem::Result<T>;
|
pub type OpenApiResult<T> = poem::Result<T>;
|
||||||
|
|
||||||
|
/// Return a 400 Bad Request error with the given message.
|
||||||
pub fn bad_request(message: String) -> poem::Error {
|
pub fn bad_request(message: String) -> poem::Error {
|
||||||
poem::Error::from_string(message, StatusCode::BAD_REQUEST)
|
poem::Error::from_string(message, StatusCode::BAD_REQUEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a 404 Not Found error with the given message.
|
||||||
pub fn not_found(message: String) -> poem::Error {
|
pub fn not_found(message: String) -> poem::Error {
|
||||||
poem::Error::from_string(message, StatusCode::NOT_FOUND)
|
poem::Error::from_string(message, StatusCode::NOT_FOUND)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ struct ExecShellPayload {
|
|||||||
pub args: Vec<String>,
|
pub args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for filesystem I/O operations (read, write, list, search).
|
||||||
pub struct IoApi {
|
pub struct IoApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,14 +9,23 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// MCP tools for agent start, stop, wait, list, and inspect.
|
||||||
pub mod agent_tools;
|
pub mod agent_tools;
|
||||||
|
/// MCP tools for server logs, CRDT dump, version, and story movement.
|
||||||
pub mod diagnostics;
|
pub mod diagnostics;
|
||||||
|
/// MCP tools for git operations scoped to agent worktrees.
|
||||||
pub mod git_tools;
|
pub mod git_tools;
|
||||||
|
/// MCP tools for merge status and merge-to-master operations.
|
||||||
pub mod merge_tools;
|
pub mod merge_tools;
|
||||||
|
/// MCP tools for QA request, approve, and reject workflows.
|
||||||
pub mod qa_tools;
|
pub mod qa_tools;
|
||||||
|
/// MCP tools for running shell commands and test suites.
|
||||||
pub mod shell_tools;
|
pub mod shell_tools;
|
||||||
|
/// MCP tools for pipeline status, story todos, and triage dump.
|
||||||
pub mod status_tools;
|
pub mod status_tools;
|
||||||
|
/// MCP tools for creating, updating, and managing stories and bugs.
|
||||||
pub mod story_tools;
|
pub mod story_tools;
|
||||||
|
/// MCP tools for the project setup wizard.
|
||||||
pub mod wizard_tools;
|
pub mod wizard_tools;
|
||||||
|
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
|
|||||||
@@ -1,26 +1,46 @@
|
|||||||
//! HTTP server — module declarations for all REST, MCP, WebSocket, and SSE endpoints.
|
//! HTTP server — module declarations for all REST, MCP, WebSocket, and SSE endpoints.
|
||||||
|
/// Agent management HTTP endpoints.
|
||||||
pub mod agents;
|
pub mod agents;
|
||||||
|
/// Server-sent event stream for real-time agent output.
|
||||||
pub mod agents_sse;
|
pub mod agents_sse;
|
||||||
|
/// Anthropic API key management endpoints.
|
||||||
pub mod anthropic;
|
pub mod anthropic;
|
||||||
|
/// Static asset serving (embedded frontend files).
|
||||||
pub mod assets;
|
pub mod assets;
|
||||||
|
/// Bot slash-command HTTP endpoint.
|
||||||
pub mod bot_command;
|
pub mod bot_command;
|
||||||
|
/// Bot configuration read/write endpoints.
|
||||||
pub mod bot_config;
|
pub mod bot_config;
|
||||||
|
/// Chat session HTTP endpoints.
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
|
/// Shared application context threaded through handlers.
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
/// Server-sent event stream for pipeline/watcher events.
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
/// Node identity endpoint (public key, node ID).
|
||||||
pub mod identity;
|
pub mod identity;
|
||||||
|
/// Filesystem I/O HTTP endpoints (read, write, list, search).
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Model Context Protocol (MCP) HTTP endpoint and tool modules.
|
||||||
pub mod mcp;
|
pub mod mcp;
|
||||||
|
/// LLM model selection and listing endpoints.
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
/// OAuth 2.0 PKCE flow endpoints for Anthropic authentication.
|
||||||
pub mod oauth;
|
pub mod oauth;
|
||||||
|
/// Project settings HTTP endpoints.
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test_helpers;
|
pub(crate) mod test_helpers;
|
||||||
|
/// Workflow helpers for story/bug file operations.
|
||||||
pub mod workflow;
|
pub mod workflow;
|
||||||
|
|
||||||
|
/// Gateway-mode HTTP endpoints for multi-project proxy.
|
||||||
pub mod gateway;
|
pub mod gateway;
|
||||||
|
/// Project open/close/list HTTP endpoints.
|
||||||
pub mod project;
|
pub mod project;
|
||||||
|
/// Setup wizard HTTP endpoints.
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
/// WebSocket handler for real-time frontend communication.
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|
||||||
use agents::AgentsApi;
|
use agents::AgentsApi;
|
||||||
@@ -44,26 +64,31 @@ use crate::chat::transport::whatsapp::WhatsAppWebhookContext;
|
|||||||
|
|
||||||
const DEFAULT_PORT: u16 = 3001;
|
const DEFAULT_PORT: u16 = 3001;
|
||||||
|
|
||||||
|
/// Parse an optional port string, falling back to the default (3001).
|
||||||
pub fn parse_port(value: Option<String>) -> u16 {
|
pub fn parse_port(value: Option<String>) -> u16 {
|
||||||
value
|
value
|
||||||
.and_then(|v| v.parse::<u16>().ok())
|
.and_then(|v| v.parse::<u16>().ok())
|
||||||
.unwrap_or(DEFAULT_PORT)
|
.unwrap_or(DEFAULT_PORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read the server port from the `HUSKIES_PORT` env var, or use the default.
|
||||||
pub fn resolve_port() -> u16 {
|
pub fn resolve_port() -> u16 {
|
||||||
parse_port(std::env::var("HUSKIES_PORT").ok())
|
parse_port(std::env::var("HUSKIES_PORT").ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a `.huskies_port` file so other processes can discover the port.
|
||||||
pub fn write_port_file(dir: &Path, port: u16) -> Option<PathBuf> {
|
pub fn write_port_file(dir: &Path, port: u16) -> Option<PathBuf> {
|
||||||
let path = dir.join(".huskies_port");
|
let path = dir.join(".huskies_port");
|
||||||
std::fs::write(&path, port.to_string()).ok()?;
|
std::fs::write(&path, port.to_string()).ok()?;
|
||||||
Some(path)
|
Some(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete the `.huskies_port` file on shutdown.
|
||||||
pub fn remove_port_file(path: &Path) {
|
pub fn remove_port_file(path: &Path) {
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assemble the full Poem route tree (API, WebSocket, MCP, OAuth, assets).
|
||||||
pub fn build_routes(
|
pub fn build_routes(
|
||||||
ctx: AppContext,
|
ctx: AppContext,
|
||||||
whatsapp_ctx: Option<Arc<WhatsAppWebhookContext>>,
|
whatsapp_ctx: Option<Arc<WhatsAppWebhookContext>>,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ struct ModelPayload {
|
|||||||
model: String,
|
model: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for LLM model selection and listing.
|
||||||
pub struct ModelApi {
|
pub struct ModelApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ fn map_project_error(e: ProjectError) -> poem::Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for project open, close, and listing operations.
|
||||||
pub struct ProjectApi {
|
pub struct ProjectApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ struct OpenFileResponse {
|
|||||||
success: bool,
|
success: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for user preferences and editor configuration.
|
||||||
pub struct SettingsApi {
|
pub struct SettingsApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ fn parse_step(step_str: &str) -> Result<WizardStep, poem::Error> {
|
|||||||
.map_err(|_| not_found(format!("Unknown wizard step: {step_str}")))
|
.map_err(|_| not_found(format!("Unknown wizard step: {step_str}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OpenAPI endpoint group for the multi-step project setup wizard.
|
||||||
pub struct WizardApi {
|
pub struct WizardApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use super::super::{
|
|||||||
slugify_name, story_stage, write_story_content,
|
slugify_name, story_stage, write_story_content,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Write a new story file to the CRDT content store and return the generated story ID.
|
||||||
pub fn create_story_file(
|
pub fn create_story_file(
|
||||||
root: &std::path::Path,
|
root: &std::path::Path,
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use super::super::{
|
|||||||
slugify_name, story_stage, write_story_content,
|
slugify_name, story_stage, write_story_content,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Toggle an acceptance criterion checkbox (`- [ ]` → `- [x]`) by its 0-based index among unchecked items.
|
||||||
pub fn check_criterion_in_file(
|
pub fn check_criterion_in_file(
|
||||||
project_root: &Path,
|
project_root: &Path,
|
||||||
story_id: &str,
|
story_id: &str,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ pub async fn write_file(path: String, content: String, state: &SessionState) ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug, poem_openapi::Object)]
|
#[derive(Serialize, Debug, poem_openapi::Object)]
|
||||||
|
/// A directory listing entry with its name and kind (file or directory).
|
||||||
pub struct FileEntry {
|
pub struct FileEntry {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub kind: String,
|
pub kind: String,
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
//! Filesystem I/O — module declarations and re-exports for file operations.
|
//! Filesystem I/O — module declarations and re-exports for file operations.
|
||||||
|
/// File read/write/list operations.
|
||||||
pub mod files;
|
pub mod files;
|
||||||
|
/// Path resolution and project-root discovery.
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
|
/// User model-preference storage.
|
||||||
pub mod preferences;
|
pub mod preferences;
|
||||||
|
/// Project open/close/list lifecycle.
|
||||||
pub mod project;
|
pub mod project;
|
||||||
|
/// `.huskies/` directory scaffolding for new projects.
|
||||||
pub mod scaffold;
|
pub mod scaffold;
|
||||||
|
|
||||||
pub use files::{
|
pub use files::{
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub fn find_story_kit_root(start: &Path) -> Option<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the current user's home directory as a string.
|
||||||
pub fn get_home_directory() -> Result<String, String> {
|
pub fn get_home_directory() -> Result<String, String> {
|
||||||
let home = homedir::my_home()
|
let home = homedir::my_home()
|
||||||
.map_err(|e| format!("Failed to resolve home directory: {e}"))?
|
.map_err(|e| format!("Failed to resolve home directory: {e}"))?
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use serde_json::json;
|
|||||||
|
|
||||||
const KEY_SELECTED_MODEL: &str = "selected_model";
|
const KEY_SELECTED_MODEL: &str = "selected_model";
|
||||||
|
|
||||||
|
/// Read the user's selected LLM model name from the store.
|
||||||
pub fn get_model_preference(store: &dyn StoreOps) -> Result<Option<String>, String> {
|
pub fn get_model_preference(store: &dyn StoreOps) -> Result<Option<String>, String> {
|
||||||
if let Some(model) = store
|
if let Some(model) = store
|
||||||
.get(KEY_SELECTED_MODEL)
|
.get(KEY_SELECTED_MODEL)
|
||||||
@@ -15,6 +16,7 @@ pub fn get_model_preference(store: &dyn StoreOps) -> Result<Option<String>, Stri
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Persist the user's selected LLM model name to the store.
|
||||||
pub fn set_model_preference(model: String, store: &dyn StoreOps) -> Result<(), String> {
|
pub fn set_model_preference(model: String, store: &dyn StoreOps) -> Result<(), String> {
|
||||||
store.set(KEY_SELECTED_MODEL, json!(model));
|
store.set(KEY_SELECTED_MODEL, json!(model));
|
||||||
store.save()?;
|
store.save()?;
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ pub async fn open_project(
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close the active project by clearing the in-memory root and stored path.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn close_project(state: &SessionState, store: &dyn StoreOps) -> Result<(), String> {
|
pub fn close_project(state: &SessionState, store: &dyn StoreOps) -> Result<(), String> {
|
||||||
{
|
{
|
||||||
@@ -99,6 +100,7 @@ pub fn close_project(state: &SessionState, store: &dyn StoreOps) -> Result<(), S
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the active project path, restoring it from the store if needed.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_current_project(
|
pub fn get_current_project(
|
||||||
state: &SessionState,
|
state: &SessionState,
|
||||||
@@ -133,6 +135,7 @@ pub fn get_current_project(
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List all previously-opened project paths from the store.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn get_known_projects(store: &dyn StoreOps) -> Result<Vec<String>, String> {
|
pub fn get_known_projects(store: &dyn StoreOps) -> Result<Vec<String>, String> {
|
||||||
let projects = store
|
let projects = store
|
||||||
@@ -146,6 +149,7 @@ pub fn get_known_projects(store: &dyn StoreOps) -> Result<Vec<String>, String> {
|
|||||||
Ok(projects)
|
Ok(projects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a project path from the known-projects list in the store.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn forget_known_project(path: String, store: &dyn StoreOps) -> Result<(), String> {
|
pub fn forget_known_project(path: String, store: &dyn StoreOps) -> Result<(), String> {
|
||||||
let mut known_projects = get_known_projects(store)?;
|
let mut known_projects = get_known_projects(store)?;
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
//! I/O subsystem — filesystem, shell, search, onboarding, and story metadata operations.
|
//! I/O subsystem — filesystem, shell, search, onboarding, and story metadata operations.
|
||||||
|
/// Filesystem helpers — file/directory read, write, list, path resolution, and project scaffolding.
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
|
/// Onboarding — guides new projects through spec setup and initial configuration.
|
||||||
pub mod onboarding;
|
pub mod onboarding;
|
||||||
|
/// Code search — full-text search across project files using the `ignore` crate.
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
/// Shell command execution — runs sandboxed commands in the project directory.
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
|
/// Story metadata — parses and serialises front-matter from story files.
|
||||||
pub mod story_metadata;
|
pub mod story_metadata;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test_helpers;
|
pub(crate) mod test_helpers;
|
||||||
|
/// Filesystem watcher — detects config changes and pipeline file events for hot-reload.
|
||||||
pub mod watcher;
|
pub mod watcher;
|
||||||
|
/// Project wizard — multi-step state machine for guided project initialisation.
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use std::fs;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Serialize, Debug, poem_openapi::Object)]
|
#[derive(Serialize, Debug, poem_openapi::Object)]
|
||||||
|
/// A single file that matched a text search, with its match count.
|
||||||
pub struct SearchResult {
|
pub struct SearchResult {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub matches: usize,
|
pub matches: usize,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use serde::Serialize;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// Output captured from a shell command: stdout, stderr, and exit code.
|
||||||
#[derive(Serialize, Debug, poem_openapi::Object)]
|
#[derive(Serialize, Debug, poem_openapi::Object)]
|
||||||
pub struct CommandOutput {
|
pub struct CommandOutput {
|
||||||
pub stdout: String,
|
pub stdout: String,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ impl QaMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the lowercase string representation of this QA mode.
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Server => "server",
|
Self::Server => "server",
|
||||||
@@ -39,6 +40,7 @@ impl std::fmt::Display for QaMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parsed YAML front-matter fields from a story markdown file.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct StoryMetadata {
|
pub struct StoryMetadata {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
@@ -71,6 +73,7 @@ pub struct StoryMetadata {
|
|||||||
pub mergemaster_attempted: Option<bool>,
|
pub mergemaster_attempted: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Errors that can occur when parsing story front-matter metadata.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum StoryMetaError {
|
pub enum StoryMetaError {
|
||||||
MissingFrontMatter,
|
MissingFrontMatter,
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
//! LLM subsystem — chat orchestration, prompts, OAuth, and provider integrations.
|
//! LLM subsystem — chat orchestration, prompts, OAuth, and provider integrations.
|
||||||
|
/// Chat session orchestration — manages multi-turn LLM conversations with streaming.
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
|
/// OAuth credential flow for LLM API access (e.g. Anthropic OAuth PKCE).
|
||||||
pub mod oauth;
|
pub mod oauth;
|
||||||
|
/// System prompt templates for agent and onboarding sessions.
|
||||||
pub mod prompts;
|
pub mod prompts;
|
||||||
|
/// LLM provider implementations (Anthropic, Claude Code, Ollama).
|
||||||
pub mod providers;
|
pub mod providers;
|
||||||
|
/// Core LLM data types: `Message`, `Role`, `ToolCall`, and `ModelProvider`.
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
//! System prompts — static prompt templates for the LLM chat and onboarding flows.
|
//! System prompts — static prompt templates for the LLM chat and onboarding flows.
|
||||||
|
|
||||||
|
/// The default system prompt injected at the start of every agent chat session.
|
||||||
pub const SYSTEM_PROMPT: &str = r#"You are an AI Agent with direct access to the user's filesystem and development environment.
|
pub const SYSTEM_PROMPT: &str = r#"You are an AI Agent with direct access to the user's filesystem and development environment.
|
||||||
|
|
||||||
CRITICAL INSTRUCTIONS:
|
CRITICAL INSTRUCTIONS:
|
||||||
@@ -91,6 +93,7 @@ REMEMBER:
|
|||||||
Remember: You are an autonomous agent that can both explain concepts and take action. Choose appropriately based on the user's request.
|
Remember: You are an autonomous agent that can both explain concepts and take action. Choose appropriately based on the user's request.
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
/// System prompt override used when a project is newly scaffolded and needs onboarding.
|
||||||
pub const ONBOARDING_PROMPT: &str = r#"ONBOARDING MODE ACTIVE — This is a newly scaffolded project. The spec files still contain placeholder content and must be replaced with real project information before any stories can be written.
|
pub const ONBOARDING_PROMPT: &str = r#"ONBOARDING MODE ACTIVE — This is a newly scaffolded project. The spec files still contain placeholder content and must be replaced with real project information before any stories can be written.
|
||||||
|
|
||||||
Guide the user through each step below. Ask ONE category of questions at a time — do not overwhelm the user with everything at once.
|
Guide the user through each step below. Ask ONE category of questions at a time — do not overwhelm the user with everything at once.
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ mod parse;
|
|||||||
|
|
||||||
use events::{handle_stream_event, process_json_event};
|
use events::{handle_stream_event, process_json_event};
|
||||||
|
|
||||||
|
/// Orchestrates Claude Code CLI sessions via a PTY for streaming agent chat.
|
||||||
pub struct ClaudeCodeProvider;
|
pub struct ClaudeCodeProvider;
|
||||||
|
|
||||||
impl ClaudeCodeProvider {
|
impl ClaudeCodeProvider {
|
||||||
|
/// Creates a new `ClaudeCodeProvider`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
//! LLM providers — module declarations for Anthropic, Claude Code, and Ollama backends.
|
//! LLM providers — module declarations for Anthropic, Claude Code, and Ollama backends.
|
||||||
|
/// Anthropic API provider — drives chat completions via the Anthropic Messages API.
|
||||||
pub mod anthropic;
|
pub mod anthropic;
|
||||||
|
/// Claude Code CLI provider — runs `claude` in a PTY and parses structured NDJSON output.
|
||||||
pub mod claude_code;
|
pub mod claude_code;
|
||||||
|
/// Ollama provider — streaming completion client for locally-hosted Ollama models.
|
||||||
pub mod ollama;
|
pub mod ollama;
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ use futures::StreamExt;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
/// Ollama HTTP/streaming client that connects to a local Ollama server.
|
||||||
pub struct OllamaProvider {
|
pub struct OllamaProvider {
|
||||||
base_url: String,
|
base_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OllamaProvider {
|
impl OllamaProvider {
|
||||||
|
/// Creates a new `OllamaProvider` pointing at the given Ollama server base URL.
|
||||||
pub fn new(base_url: String) -> Self {
|
pub fn new(base_url: String) -> Self {
|
||||||
Self { base_url }
|
Self { base_url }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use async_trait::async_trait;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// The role of a message participant in an LLM conversation.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum Role {
|
pub enum Role {
|
||||||
@@ -12,6 +13,7 @@ pub enum Role {
|
|||||||
Tool,
|
Tool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single message in an LLM conversation, including optional tool call attachments.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
@@ -24,6 +26,7 @@ pub struct Message {
|
|||||||
pub tool_call_id: Option<String>,
|
pub tool_call_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A tool invocation requested by the LLM, containing the call ID and function details.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ToolCall {
|
pub struct ToolCall {
|
||||||
pub id: Option<String>,
|
pub id: Option<String>,
|
||||||
@@ -32,12 +35,14 @@ pub struct ToolCall {
|
|||||||
pub kind: String,
|
pub kind: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The function name and JSON-encoded arguments for a tool call.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct FunctionCall {
|
pub struct FunctionCall {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub arguments: String,
|
pub arguments: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A tool definition passed to the LLM describing an available function and its schema.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ToolDefinition {
|
pub struct ToolDefinition {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
@@ -45,6 +50,7 @@ pub struct ToolDefinition {
|
|||||||
pub function: ToolFunctionDefinition,
|
pub function: ToolFunctionDefinition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The name, description, and JSON schema for a single tool function.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct ToolFunctionDefinition {
|
pub struct ToolFunctionDefinition {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -52,6 +58,7 @@ pub struct ToolFunctionDefinition {
|
|||||||
pub parameters: serde_json::Value,
|
pub parameters: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The response from an LLM completion request, containing text and/or tool calls.
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct CompletionResponse {
|
pub struct CompletionResponse {
|
||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
@@ -61,6 +68,7 @@ pub struct CompletionResponse {
|
|||||||
pub session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trait for LLM backends; implementations drive chat completions with optional tool use.
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub trait ModelProvider: Send + Sync {
|
pub trait ModelProvider: Send + Sync {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ pub enum LogLevel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LogLevel {
|
impl LogLevel {
|
||||||
|
/// Return the uppercase string label for this level (`"ERROR"`, `"WARN"`, or `"INFO"`).
|
||||||
pub fn as_str(&self) -> &'static str {
|
pub fn as_str(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
LogLevel::Error => "ERROR",
|
LogLevel::Error => "ERROR",
|
||||||
@@ -75,6 +76,7 @@ impl LogEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bounded in-memory ring buffer holding recent log entries and a broadcast channel for live streaming.
|
||||||
pub struct LogBuffer {
|
pub struct LogBuffer {
|
||||||
entries: Mutex<VecDeque<LogEntry>>,
|
entries: Mutex<VecDeque<LogEntry>>,
|
||||||
log_file: Mutex<Option<PathBuf>>,
|
log_file: Mutex<Option<PathBuf>>,
|
||||||
|
|||||||
@@ -9,22 +9,32 @@ mod agent_mode;
|
|||||||
mod agents;
|
mod agents;
|
||||||
mod chat;
|
mod chat;
|
||||||
mod config;
|
mod config;
|
||||||
|
/// CRDT snapshot — serialisation and restore of the full pipeline CRDT state.
|
||||||
pub mod crdt_snapshot;
|
pub mod crdt_snapshot;
|
||||||
|
/// CRDT state — in-memory pipeline state machine backed by a distributed CRDT.
|
||||||
pub mod crdt_state;
|
pub mod crdt_state;
|
||||||
|
/// CRDT sync — WebSocket-based peer synchronisation for distributed nodes.
|
||||||
pub mod crdt_sync;
|
pub mod crdt_sync;
|
||||||
|
/// CRDT wire format — on-wire message types for the crdt-sync protocol.
|
||||||
pub mod crdt_wire;
|
pub mod crdt_wire;
|
||||||
mod db;
|
mod db;
|
||||||
|
/// Gateway mode — multi-project reverse proxy that fronts multiple project containers.
|
||||||
pub mod gateway;
|
pub mod gateway;
|
||||||
mod gateway_relay;
|
mod gateway_relay;
|
||||||
mod http;
|
mod http;
|
||||||
mod io;
|
mod io;
|
||||||
mod llm;
|
mod llm;
|
||||||
|
/// Log buffer — in-memory ring buffer for recent server-side log lines.
|
||||||
pub mod log_buffer;
|
pub mod log_buffer;
|
||||||
|
/// Mesh — peer discovery and multi-hop CRDT replication over WebSocket.
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
|
/// Node identity — Ed25519 keypair generation and stable node ID management.
|
||||||
pub mod node_identity;
|
pub mod node_identity;
|
||||||
pub(crate) mod pipeline_state;
|
pub(crate) mod pipeline_state;
|
||||||
|
/// Rebuild — process restart and shutdown coordination.
|
||||||
pub mod rebuild;
|
pub mod rebuild;
|
||||||
mod service;
|
mod service;
|
||||||
|
/// Services — shared service bundle injected into HTTP handlers and bot tasks.
|
||||||
pub mod services;
|
pub mod services;
|
||||||
mod startup;
|
mod startup;
|
||||||
mod state;
|
mod state;
|
||||||
|
|||||||
@@ -21,21 +21,25 @@ pub trait TransitionSubscriber: Send + Sync {
|
|||||||
fn on_transition(&self, fired: &TransitionFired);
|
fn on_transition(&self, fired: &TransitionFired);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collects [`TransitionSubscriber`]s and dispatches [`TransitionFired`] events to each.
|
||||||
pub struct EventBus {
|
pub struct EventBus {
|
||||||
subscribers: Vec<Box<dyn TransitionSubscriber>>,
|
subscribers: Vec<Box<dyn TransitionSubscriber>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventBus {
|
impl EventBus {
|
||||||
|
/// Create an empty event bus with no subscribers.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
subscribers: Vec::new(),
|
subscribers: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a subscriber to receive all future transition events.
|
||||||
pub fn subscribe<S: TransitionSubscriber + 'static>(&mut self, subscriber: S) {
|
pub fn subscribe<S: TransitionSubscriber + 'static>(&mut self, subscriber: S) {
|
||||||
self.subscribers.push(Box::new(subscriber));
|
self.subscribers.push(Box::new(subscriber));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fire a transition event, calling every registered subscriber in order.
|
||||||
pub fn fire(&self, event: TransitionFired) {
|
pub fn fire(&self, event: TransitionFired) {
|
||||||
for sub in &self.subscribers {
|
for sub in &self.subscribers {
|
||||||
sub.on_transition(&event);
|
sub.on_transition(&event);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use super::{event_label, stage_dir_name, stage_label};
|
|||||||
// These are ready to wire into the event bus but not yet connected to the
|
// These are ready to wire into the event bus but not yet connected to the
|
||||||
// actual subsystems. Suppress dead_code until consumers are migrated.
|
// actual subsystems. Suppress dead_code until consumers are migrated.
|
||||||
|
|
||||||
|
/// Subscriber that logs pipeline transitions to the Matrix bot channel.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct MatrixBotSubscriber;
|
pub struct MatrixBotSubscriber;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -27,6 +28,7 @@ impl TransitionSubscriber for MatrixBotSubscriber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subscriber that re-renders the filesystem `work/` directory on stage transitions.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct FileRendererSubscriber;
|
pub struct FileRendererSubscriber;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -43,6 +45,7 @@ impl TransitionSubscriber for FileRendererSubscriber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subscriber that writes stage updates to the pipeline-items data store.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct PipelineItemsSubscriber;
|
pub struct PipelineItemsSubscriber;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -59,6 +62,7 @@ impl TransitionSubscriber for PipelineItemsSubscriber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subscriber that promotes eligible backlog items when a story completes or is archived.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct AutoAssignSubscriber;
|
pub struct AutoAssignSubscriber;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -77,6 +81,7 @@ impl TransitionSubscriber for AutoAssignSubscriber {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Subscriber that broadcasts stage transitions to all connected WebSocket clients.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct WebUiBroadcastSubscriber;
|
pub struct WebUiBroadcastSubscriber;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ pub enum PipelineEvent {
|
|||||||
|
|
||||||
// ── Per-node execution events ───────────────────────────────────────────────
|
// ── Per-node execution events ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Events that drive per-node [`ExecutionState`] transitions (agent lifecycle).
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum ExecutionEvent {
|
pub enum ExecutionEvent {
|
||||||
SpawnRequested { agent: AgentName },
|
SpawnRequested { agent: AgentName },
|
||||||
|
|||||||
@@ -7,18 +7,23 @@ use std::num::NonZeroU32;
|
|||||||
|
|
||||||
// ── Newtypes ────────────────────────────────────────────────────────────────
|
// ── Newtypes ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Unique identifier for a pipeline work item (story, bug, spike, or refactor).
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct StoryId(pub String);
|
pub struct StoryId(pub String);
|
||||||
|
|
||||||
|
/// Git branch name associated with a work item.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct BranchName(pub String);
|
pub struct BranchName(pub String);
|
||||||
|
|
||||||
|
/// A Git commit SHA (typically the merge commit written when a story lands on master).
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct GitSha(pub String);
|
pub struct GitSha(pub String);
|
||||||
|
|
||||||
|
/// The name of the agent process handling a work item on a given node.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct AgentName(pub String);
|
pub struct AgentName(pub String);
|
||||||
|
|
||||||
|
/// Ed25519 public key (32 bytes) that uniquely identifies a huskies node in the mesh.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct NodePubkey(pub [u8; 32]);
|
pub struct NodePubkey(pub [u8; 32]);
|
||||||
|
|
||||||
@@ -191,6 +196,7 @@ pub struct PipelineItem {
|
|||||||
|
|
||||||
// ── Transition errors ───────────────────────────────────────────────────────
|
// ── Transition errors ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Error returned when a pipeline event is not valid for the current stage.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum TransitionError {
|
pub enum TransitionError {
|
||||||
InvalidTransition { from_stage: String, event: String },
|
InvalidTransition { from_stage: String, event: String },
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub struct BotShutdownNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BotShutdownNotifier {
|
impl BotShutdownNotifier {
|
||||||
|
/// Create a notifier that will send shutdown/startup messages via `transport` to the given `channels`.
|
||||||
pub fn new(transport: Arc<dyn ChatTransport>, channels: Vec<String>, bot_name: String) -> Self {
|
pub fn new(transport: Arc<dyn ChatTransport>, channels: Vec<String>, bot_name: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
transport,
|
transport,
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
//!
|
//!
|
||||||
//! Conventions: `docs/architecture/service-modules.md`
|
//! Conventions: `docs/architecture/service-modules.md`
|
||||||
mod io;
|
mod io;
|
||||||
|
/// Agent selection heuristics — pick the best agent for a story.
|
||||||
pub mod selection;
|
pub mod selection;
|
||||||
|
/// Token usage tracking and budget enforcement.
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
|
||||||
use crate::agents::AgentInfo;
|
use crate::agents::AgentInfo;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
//! - `io.rs` — all side-effectful calls (transport handlers, stores, agent pool)
|
//! - `io.rs` — all side-effectful calls (transport handlers, stores, agent pool)
|
||||||
|
|
||||||
pub(super) mod io;
|
pub(super) mod io;
|
||||||
|
/// Pure argument parsing for bot slash commands.
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
|
|
||||||
use crate::agents::AgentPool;
|
use crate::agents::AgentPool;
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
//! All sub-modules here are pure (no I/O, no side effects). Any helper that
|
//! All sub-modules here are pure (no I/O, no side effects). Any helper that
|
||||||
//! duplicates logic across two or more service modules belongs here; anything
|
//! duplicates logic across two or more service modules belongs here; anything
|
||||||
//! used by only one service stays in that service.
|
//! used by only one service stays in that service.
|
||||||
|
/// Story/bug/spike ID extraction and formatting helpers.
|
||||||
pub mod item_id;
|
pub mod item_id;
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
//! - `io.rs` — the ONLY place that performs side effects (filesystem reads/writes)
|
//! - `io.rs` — the ONLY place that performs side effects (filesystem reads/writes)
|
||||||
//! - `permission.rs` — pure permission-rule generation and wildcard checks
|
//! - `permission.rs` — pure permission-rule generation and wildcard checks
|
||||||
|
|
||||||
|
/// Side-effectful diagnostics I/O — log reads, CRDT dumps, filesystem writes.
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Pure permission-rule generation and wildcard matching.
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Conventions: `docs/architecture/service-modules.md`
|
//! Conventions: `docs/architecture/service-modules.md`
|
||||||
|
|
||||||
|
/// Bounded in-memory event ring buffer for SSE streaming.
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
pub(super) mod io;
|
pub(super) mod io;
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,12 @@
|
|||||||
//! - `aggregation.rs` — pure cross-project pipeline formatting
|
//! - `aggregation.rs` — pure cross-project pipeline formatting
|
||||||
//! - `polling.rs` — pure notification event formatting
|
//! - `polling.rs` — pure notification event formatting
|
||||||
|
|
||||||
|
/// Cross-project pipeline status aggregation and formatting.
|
||||||
pub mod aggregation;
|
pub mod aggregation;
|
||||||
|
/// Gateway configuration types and TOML parsing.
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub(crate) mod io;
|
pub(crate) mod io;
|
||||||
|
/// Notification event polling for gateway-level broadcasts.
|
||||||
pub mod polling;
|
pub mod polling;
|
||||||
|
|
||||||
pub use aggregation::format_aggregate_status_compact;
|
pub use aggregation::format_aggregate_status_compact;
|
||||||
|
|||||||
@@ -7,8 +7,11 @@
|
|||||||
//! - `path_guard.rs` — pure path-prefix safety checks
|
//! - `path_guard.rs` — pure path-prefix safety checks
|
||||||
//! - `porcelain.rs` — pure git porcelain output parsers
|
//! - `porcelain.rs` — pure git porcelain output parsers
|
||||||
|
|
||||||
|
/// Side-effectful git command execution (add, commit, diff, log, status).
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Pure worktree path-prefix safety checks.
|
||||||
pub mod path_guard;
|
pub mod path_guard;
|
||||||
|
/// Pure git porcelain output parsers.
|
||||||
pub mod porcelain;
|
pub mod porcelain;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
//! - `io.rs` — the ONLY place that performs side effects
|
//! - `io.rs` — the ONLY place that performs side effects
|
||||||
//! - `status.rs` — pure merge-status message formatting
|
//! - `status.rs` — pure merge-status message formatting
|
||||||
|
|
||||||
|
/// Side-effectful merge I/O — rebase, cherry-pick, and branch operations.
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Pure merge-status message formatting.
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -5,25 +5,47 @@
|
|||||||
//! - `mod.rs` orchestrates and owns the typed `Error` type
|
//! - `mod.rs` orchestrates and owns the typed `Error` type
|
||||||
//! - `io.rs` is the only file that performs side effects
|
//! - `io.rs` is the only file that performs side effects
|
||||||
//! - Topic-named pure files contain branching logic with no I/O
|
//! - Topic-named pure files contain branching logic with no I/O
|
||||||
|
/// Agent management — start, stop, inspect, and list agents.
|
||||||
pub mod agents;
|
pub mod agents;
|
||||||
|
/// Anthropic API key storage and model listing.
|
||||||
pub mod anthropic;
|
pub mod anthropic;
|
||||||
|
/// Bot command dispatch — parses and executes slash commands.
|
||||||
pub mod bot_command;
|
pub mod bot_command;
|
||||||
|
/// Shared pure helpers used across service modules.
|
||||||
pub mod common;
|
pub mod common;
|
||||||
|
/// Diagnostics — server logs, CRDT dump, and permission management.
|
||||||
pub mod diagnostics;
|
pub mod diagnostics;
|
||||||
|
/// Pipeline event buffer for SSE streaming.
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
/// File I/O — path validation, read, write, and listing.
|
||||||
pub mod file_io;
|
pub mod file_io;
|
||||||
|
/// Gateway — multi-project proxy domain logic.
|
||||||
pub mod gateway;
|
pub mod gateway;
|
||||||
|
/// Git operations — worktree-scoped git commands.
|
||||||
pub mod git_ops;
|
pub mod git_ops;
|
||||||
|
/// Merge — rebase agent work onto master and validate.
|
||||||
pub mod merge;
|
pub mod merge;
|
||||||
|
/// Notifications — fan-out pipeline events to chat transports.
|
||||||
pub mod notifications;
|
pub mod notifications;
|
||||||
|
/// OAuth 2.0 PKCE flow for Anthropic authentication.
|
||||||
pub mod oauth;
|
pub mod oauth;
|
||||||
|
/// Pipeline status aggregation helpers.
|
||||||
pub mod pipeline;
|
pub mod pipeline;
|
||||||
|
/// Project open/close/list domain logic.
|
||||||
pub mod project;
|
pub mod project;
|
||||||
|
/// QA — request, approve, and reject code reviews.
|
||||||
pub mod qa;
|
pub mod qa;
|
||||||
|
/// Project settings read/write and validation.
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
|
/// Shell command safety, sandboxing, and output helpers.
|
||||||
pub mod shell;
|
pub mod shell;
|
||||||
|
/// Status broadcaster — unified event fan-out to all consumers.
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
/// Story CRUD — create, update, move, and manage work items.
|
||||||
pub mod story;
|
pub mod story;
|
||||||
|
/// Timer — deferred agent start via one-shot timers.
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
|
/// Wizard — multi-step project setup domain logic.
|
||||||
pub mod wizard;
|
pub mod wizard;
|
||||||
|
/// WebSocket — real-time pipeline updates and permission prompts.
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|||||||
@@ -7,8 +7,10 @@
|
|||||||
//! - `pkce.rs` — pure PKCE helpers: generation, challenge, encoding
|
//! - `pkce.rs` — pure PKCE helpers: generation, challenge, encoding
|
||||||
//! - `flow.rs` — pure flow types and token-expiry decision logic
|
//! - `flow.rs` — pure flow types and token-expiry decision logic
|
||||||
|
|
||||||
|
/// Pure OAuth flow types and token-expiry decision logic.
|
||||||
pub mod flow;
|
pub mod flow;
|
||||||
pub(super) mod io;
|
pub(super) mod io;
|
||||||
|
/// Pure PKCE helpers — code verifier generation, challenge derivation, base64url encoding.
|
||||||
pub mod pkce;
|
pub mod pkce;
|
||||||
|
|
||||||
pub use flow::AccountInfo;
|
pub use flow::AccountInfo;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
//! Conventions: `docs/architecture/service-modules.md`
|
//! Conventions: `docs/architecture/service-modules.md`
|
||||||
|
|
||||||
pub(super) mod io;
|
pub(super) mod io;
|
||||||
|
/// Pure project selection and path-matching logic.
|
||||||
pub mod selection;
|
pub mod selection;
|
||||||
|
|
||||||
use crate::state::SessionState;
|
use crate::state::SessionState;
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
//! - `io.rs` — the ONLY place that performs side effects (git, TCP, process)
|
//! - `io.rs` — the ONLY place that performs side effects (git, TCP, process)
|
||||||
//! - `lifecycle.rs` — pure QA routing decisions (spike vs. normal story)
|
//! - `lifecycle.rs` — pure QA routing decisions (spike vs. normal story)
|
||||||
|
|
||||||
|
/// Side-effectful QA I/O — git operations, port scanning, branch merging.
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Pure QA routing decisions (spike vs. normal story).
|
||||||
pub mod lifecycle;
|
pub mod lifecycle;
|
||||||
|
|
||||||
pub use io::{find_free_port, merge_spike_branch_to_master};
|
pub use io::{find_free_port, merge_spike_branch_to_master};
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
//! - `validate.rs` — pure validation: [`validate_project_settings`]
|
//! - `validate.rs` — pure validation: [`validate_project_settings`]
|
||||||
|
|
||||||
pub(super) mod io;
|
pub(super) mod io;
|
||||||
|
/// Pure types: `ProjectSettings`, TOML serialization, config merging.
|
||||||
pub mod project;
|
pub mod project;
|
||||||
|
/// Pure project-settings validation rules.
|
||||||
pub mod validate;
|
pub mod validate;
|
||||||
|
|
||||||
pub use project::{ProjectSettings, merge_settings_into_toml, settings_from_config};
|
pub use project::{ProjectSettings, merge_settings_into_toml, settings_from_config};
|
||||||
|
|||||||
@@ -6,7 +6,9 @@
|
|||||||
//! - `io.rs` — the ONLY place that performs side effects (filesystem checks)
|
//! - `io.rs` — the ONLY place that performs side effects (filesystem checks)
|
||||||
//! - `path_guard.rs` — pure command-safety checks and output utilities
|
//! - `path_guard.rs` — pure command-safety checks and output utilities
|
||||||
|
|
||||||
|
/// Side-effectful shell I/O — filesystem permission checks.
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Pure command-safety checks, blocked-binary lists, and output truncation.
|
||||||
pub mod path_guard;
|
pub mod path_guard;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -23,7 +23,9 @@
|
|||||||
//! - Story 643 (Web UI): calls `subscribe()` once at startup.
|
//! - Story 643 (Web UI): calls `subscribe()` once at startup.
|
||||||
//! - Story 644 (chat transports): calls `subscribe()` once per transport.
|
//! - Story 644 (chat transports): calls `subscribe()` once per transport.
|
||||||
|
|
||||||
|
/// Bounded ring buffer for recent status events.
|
||||||
pub mod buffer;
|
pub mod buffer;
|
||||||
|
/// Pure status-event to human-readable string formatting.
|
||||||
pub mod format;
|
pub mod format;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|||||||
@@ -31,10 +31,15 @@
|
|||||||
//! - `pipeline_items` row — updated on stage transitions and item creation/deletion
|
//! - `pipeline_items` row — updated on stage transitions and item creation/deletion
|
||||||
//! - `content_store` entry — updated on story content changes, deleted on purge/delete
|
//! - `content_store` entry — updated on story content changes, deleted on purge/delete
|
||||||
|
|
||||||
|
/// Pure criterion parsing, checkbox toggling, and validation.
|
||||||
pub mod criteria;
|
pub mod criteria;
|
||||||
|
/// Pure front-matter field validation (stage names, agent assignments).
|
||||||
pub mod front_matter;
|
pub mod front_matter;
|
||||||
|
/// Side-effectful story file I/O — read, write, move, and delete.
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
/// Pure story-ID helpers and lifecycle state transitions.
|
||||||
pub mod lifecycle;
|
pub mod lifecycle;
|
||||||
|
/// Pure story content validation rules.
|
||||||
pub mod validation;
|
pub mod validation;
|
||||||
|
|
||||||
pub use criteria::parse_test_cases;
|
pub use criteria::parse_test_cases;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
//! step classification — all unit-testable without tempdirs or an async runtime
|
//! step classification — all unit-testable without tempdirs or an async runtime
|
||||||
|
|
||||||
pub(crate) mod io;
|
pub(crate) mod io;
|
||||||
|
/// Pure wizard state-machine helpers — bare-project detection, step classification.
|
||||||
pub mod state_machine;
|
pub mod state_machine;
|
||||||
|
|
||||||
use crate::io::wizard::{StepStatus, WizardState, WizardStep, format_wizard_state};
|
use crate::io::wizard::{StepStatus, WizardState, WizardStep, format_wizard_state};
|
||||||
|
|||||||
@@ -4,8 +4,11 @@
|
|||||||
//! Conversions from domain events to WsResponse live here too.
|
//! Conversions from domain events to WsResponse live here too.
|
||||||
//! All logic is pure data transformation; I/O lives in `io.rs`.
|
//! All logic is pure data transformation; I/O lives in `io.rs`.
|
||||||
|
|
||||||
|
/// Conversions from domain events to `WsResponse` frames.
|
||||||
pub mod convert;
|
pub mod convert;
|
||||||
|
/// Client-to-server `WsRequest` message definitions.
|
||||||
pub mod request;
|
pub mod request;
|
||||||
|
/// Server-to-client `WsResponse` message definitions.
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
||||||
pub use convert::{needs_pipeline_refresh, wizard_steps_to_info};
|
pub use convert::{needs_pipeline_refresh, wizard_steps_to_info};
|
||||||
|
|||||||
@@ -9,9 +9,12 @@
|
|||||||
//! - `dispatch.rs` — pure request routing and permission resolution
|
//! - `dispatch.rs` — pure request routing and permission resolution
|
||||||
//! - `error.rs` — typed error enum
|
//! - `error.rs` — typed error enum
|
||||||
|
|
||||||
|
/// Pure request routing and permission resolution.
|
||||||
pub mod dispatch;
|
pub mod dispatch;
|
||||||
|
/// Typed WebSocket error enum.
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub(super) mod io;
|
pub(super) mod io;
|
||||||
|
/// Pure WebSocket message types and domain-event conversions.
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
|
||||||
pub use dispatch::{
|
pub use dispatch::{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
/// Global server session state: the open project root and a cancellation signal.
|
||||||
pub struct SessionState {
|
pub struct SessionState {
|
||||||
pub project_root: Mutex<Option<PathBuf>>,
|
pub project_root: Mutex<Option<PathBuf>>,
|
||||||
pub cancel_tx: watch::Sender<bool>,
|
pub cancel_tx: watch::Sender<bool>,
|
||||||
@@ -21,6 +22,7 @@ impl Default for SessionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionState {
|
impl SessionState {
|
||||||
|
/// Return the currently open project root, or an error if no project is open.
|
||||||
pub fn get_project_root(&self) -> Result<PathBuf, String> {
|
pub fn get_project_root(&self) -> Result<PathBuf, String> {
|
||||||
let root_guard = self.project_root.lock().map_err(|e| e.to_string())?;
|
let root_guard = self.project_root.lock().map_err(|e| e.to_string())?;
|
||||||
let root = root_guard.as_ref().ok_or_else(|| {
|
let root = root_guard.as_ref().ok_or_else(|| {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use std::fs;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
/// Trait for a simple key-value store that can get, set, delete, and persist entries.
|
||||||
pub trait StoreOps: Send + Sync {
|
pub trait StoreOps: Send + Sync {
|
||||||
fn get(&self, key: &str) -> Option<Value>;
|
fn get(&self, key: &str) -> Option<Value>;
|
||||||
fn set(&self, key: &str, value: Value);
|
fn set(&self, key: &str, value: Value);
|
||||||
@@ -12,12 +13,14 @@ pub trait StoreOps: Send + Sync {
|
|||||||
fn save(&self) -> Result<(), String>;
|
fn save(&self) -> Result<(), String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A JSON-backed file store that persists key-value data to a single file on disk.
|
||||||
pub struct JsonFileStore {
|
pub struct JsonFileStore {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
data: Mutex<HashMap<String, Value>>,
|
data: Mutex<HashMap<String, Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JsonFileStore {
|
impl JsonFileStore {
|
||||||
|
/// Create a new store backed by `path`, loading existing data if the file is present.
|
||||||
pub fn new(path: PathBuf) -> Result<Self, String> {
|
pub fn new(path: PathBuf) -> Result<Self, String> {
|
||||||
let data = if path.exists() {
|
let data = if path.exists() {
|
||||||
let content =
|
let content =
|
||||||
@@ -38,10 +41,12 @@ impl JsonFileStore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience constructor accepting any path type; delegates to [`Self::new`].
|
||||||
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, String> {
|
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, String> {
|
||||||
Self::new(path.as_ref().to_path_buf())
|
Self::new(path.as_ref().to_path_buf())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the path to the backing JSON file.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn path(&self) -> &Path {
|
pub fn path(&self) -> &Path {
|
||||||
&self.path
|
&self.path
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Whether an individual test case passed or failed.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum TestStatus {
|
pub enum TestStatus {
|
||||||
@@ -10,6 +11,7 @@ pub enum TestStatus {
|
|||||||
Fail,
|
Fail,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The name, status, and optional details for a single test case execution.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct TestCaseResult {
|
pub struct TestCaseResult {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -22,6 +24,7 @@ struct TestRunSummary {
|
|||||||
failed: usize,
|
failed: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The outcome of evaluating whether a story is ready for acceptance.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AcceptanceDecision {
|
pub struct AcceptanceDecision {
|
||||||
pub can_accept: bool,
|
pub can_accept: bool,
|
||||||
@@ -29,12 +32,14 @@ pub struct AcceptanceDecision {
|
|||||||
pub warning: Option<String>,
|
pub warning: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collected unit and integration test results recorded for a single story.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
pub struct StoryTestResults {
|
pub struct StoryTestResults {
|
||||||
pub unit: Vec<TestCaseResult>,
|
pub unit: Vec<TestCaseResult>,
|
||||||
pub integration: Vec<TestCaseResult>,
|
pub integration: Vec<TestCaseResult>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Server-wide in-memory workflow state: test results and coverage reports keyed by story ID.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct WorkflowState {
|
pub struct WorkflowState {
|
||||||
pub results: HashMap<String, StoryTestResults>,
|
pub results: HashMap<String, StoryTestResults>,
|
||||||
@@ -42,6 +47,7 @@ pub struct WorkflowState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl WorkflowState {
|
impl WorkflowState {
|
||||||
|
/// Record unit and integration test results for a story, rejecting batches with more than one failure.
|
||||||
pub fn record_test_results_validated(
|
pub fn record_test_results_validated(
|
||||||
&mut self,
|
&mut self,
|
||||||
story_id: String,
|
story_id: String,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub use remove::remove_worktree_by_story_id;
|
|||||||
pub use sweep::sweep_orphaned_worktrees;
|
pub use sweep::sweep_orphaned_worktrees;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// Details about a newly created worktree: path, branch, and base branch.
|
||||||
pub struct WorktreeInfo {
|
pub struct WorktreeInfo {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
pub branch: String,
|
pub branch: String,
|
||||||
@@ -19,6 +20,7 @@ pub struct WorktreeInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
/// A discovered worktree on disk: its story ID and filesystem path.
|
||||||
pub struct WorktreeListEntry {
|
pub struct WorktreeListEntry {
|
||||||
pub story_id: String,
|
pub story_id: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|||||||
Reference in New Issue
Block a user