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