huskies: rename project from storkit to huskies
Rename all references from storkit to huskies across the codebase: - .storkit/ directory → .huskies/ - Binary name, Cargo package name, Docker image references - Server code, frontend code, config files, scripts - Fix script/test to build frontend before cargo clippy/test so merge worktrees have frontend/dist available for RustEmbed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,7 +18,7 @@ const SKIP_DIRS: &[&str] = &[
|
||||
];
|
||||
|
||||
/// Path components that indicate a worktree path that should be skipped.
|
||||
const SKIP_PATH_COMPONENTS: &[&str] = &[".storkit/worktrees"];
|
||||
const SKIP_PATH_COMPONENTS: &[&str] = &[".huskies/worktrees"];
|
||||
|
||||
/// Known-huge or machine-generated files that are excluded from the loc count
|
||||
/// even when they have a recognised source extension (e.g. `.json`, `.yaml`).
|
||||
@@ -88,10 +88,10 @@ fn loc_top_n(project_root: &std::path::Path, top_n: usize) -> String {
|
||||
if SKIP_DIRS.iter().any(|s| *s == name.as_ref()) {
|
||||
return false;
|
||||
}
|
||||
// Skip .storkit/worktrees — use relative path so the check
|
||||
// Skip .huskies/worktrees — use relative path so the check
|
||||
// doesn't exclude the project root itself when running
|
||||
// from inside a worktree (where the absolute path contains
|
||||
// ".storkit/worktrees").
|
||||
// ".huskies/worktrees").
|
||||
let rel = e
|
||||
.path()
|
||||
.strip_prefix(project_root)
|
||||
@@ -332,7 +332,7 @@ mod tests {
|
||||
let ctx = make_ctx(&agents, &ambient_rooms, repo_root, "");
|
||||
let output = handle_loc(&ctx).unwrap();
|
||||
assert!(
|
||||
!output.contains(".storkit/worktrees"),
|
||||
!output.contains(".huskies/worktrees"),
|
||||
"output must not include paths inside worktrees: {output}"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ pub(super) fn handle_move(ctx: &CommandContext) -> Option<String> {
|
||||
'outer: for stage_dir in SEARCH_DIRS {
|
||||
let dir = ctx
|
||||
.project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir);
|
||||
if !dir.exists() {
|
||||
@@ -242,7 +242,7 @@ mod tests {
|
||||
// Verify the file was actually moved.
|
||||
let new_path = tmp
|
||||
.path()
|
||||
.join(".storkit/work/2_current/42_story_some_feature.md");
|
||||
.join(".huskies/work/2_current/42_story_some_feature.md");
|
||||
assert!(new_path.exists(), "story file should be in 2_current/");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::CommandContext;
|
||||
|
||||
/// Show implementation summary for a story identified by its number.
|
||||
///
|
||||
/// Finds the `storkit: merge {story_id}` commit on master, displays the
|
||||
/// Finds the `huskies: merge {story_id}` commit on master, displays the
|
||||
/// git diff --stat (files changed with line counts), and extracts key
|
||||
/// function/struct/type names added or modified in the implementation.
|
||||
/// Returns a friendly message when no merge commit is found.
|
||||
@@ -82,11 +82,11 @@ pub(super) fn handle_overview(ctx: &CommandContext) -> Option<String> {
|
||||
/// Find the merge commit hash for a story by its numeric ID.
|
||||
///
|
||||
/// Searches git log for a commit whose subject matches
|
||||
/// `storkit: merge {num}_*` or the legacy `story-kit: merge {num}_*`.
|
||||
/// `huskies: merge {num}_*` or the legacy `story-kit: merge {num}_*`.
|
||||
fn find_story_merge_commit(root: &std::path::Path, num_str: &str) -> Option<String> {
|
||||
use std::process::Command;
|
||||
// Match both the current prefix and the legacy one from before the rename.
|
||||
let grep_pattern = format!("(storkit|story-kit): merge {num_str}_");
|
||||
let grep_pattern = format!("(huskies|storkit|story-kit): merge {num_str}_");
|
||||
let output = Command::new("git")
|
||||
.args([
|
||||
"log",
|
||||
@@ -116,7 +116,7 @@ fn find_story_name(root: &std::path::Path, num_str: &str) -> Option<String> {
|
||||
"6_archived",
|
||||
];
|
||||
for stage in &stages {
|
||||
let dir = root.join(".storkit").join("work").join(stage);
|
||||
let dir = root.join(".huskies").join("work").join(stage);
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ fn wizard_status_reply(ctx: &CommandContext) -> String {
|
||||
match WizardState::load(ctx.project_root) {
|
||||
Some(state) => format_wizard_state(&state),
|
||||
None => {
|
||||
"No setup wizard active. Run `storkit init` in the project root to begin.".to_string()
|
||||
"No setup wizard active. Run `huskies init` in the project root to begin.".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -215,13 +215,13 @@ mod tests {
|
||||
let rooms = Arc::new(Mutex::new(HashSet::new()));
|
||||
let ctx = make_ctx("", dir.path(), &agents, &rooms);
|
||||
let result = handle_setup(&ctx).unwrap();
|
||||
assert!(result.contains("storkit init"));
|
||||
assert!(result.contains("huskies init"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn setup_with_wizard_shows_status() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
|
||||
WizardState::init_if_missing(dir.path());
|
||||
let agents = Arc::new(crate::agents::AgentPool::new_test(4001));
|
||||
let rooms = Arc::new(Mutex::new(HashSet::new()));
|
||||
@@ -233,7 +233,7 @@ mod tests {
|
||||
#[test]
|
||||
fn setup_skip_advances_wizard() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
|
||||
WizardState::init_if_missing(dir.path());
|
||||
let agents = Arc::new(crate::agents::AgentPool::new_test(4002));
|
||||
let rooms = Arc::new(Mutex::new(HashSet::new()));
|
||||
@@ -247,7 +247,7 @@ mod tests {
|
||||
#[test]
|
||||
fn setup_confirm_advances_wizard() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
|
||||
WizardState::init_if_missing(dir.path());
|
||||
let agents = Arc::new(crate::agents::AgentPool::new_test(4003));
|
||||
let rooms = Arc::new(Mutex::new(HashSet::new()));
|
||||
@@ -261,7 +261,7 @@ mod tests {
|
||||
#[test]
|
||||
fn setup_retry_resets_step() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
|
||||
WizardState::init_if_missing(dir.path());
|
||||
// Stage some content first.
|
||||
{
|
||||
@@ -299,7 +299,7 @@ mod tests {
|
||||
#[test]
|
||||
fn setup_generate_marks_generating_and_returns_hint() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
|
||||
WizardState::init_if_missing(dir.path());
|
||||
let agents = Arc::new(crate::agents::AgentPool::new_test(4006));
|
||||
let rooms = Arc::new(Mutex::new(HashSet::new()));
|
||||
@@ -317,7 +317,7 @@ mod tests {
|
||||
fn setup_generate_bare_project_asks_user() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
// Bare project — only scaffolding files
|
||||
std::fs::create_dir_all(dir.path().join(".storkit")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies")).unwrap();
|
||||
WizardState::init_if_missing(dir.path());
|
||||
let agents = Arc::new(crate::agents::AgentPool::new_test(4007));
|
||||
let rooms = Arc::new(Mutex::new(HashSet::new()));
|
||||
|
||||
@@ -34,7 +34,7 @@ pub(super) fn handle_show(ctx: &CommandContext) -> Option<String> {
|
||||
for stage in &stages {
|
||||
let dir = ctx
|
||||
.project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage);
|
||||
if !dir.exists() {
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(super) fn story_short_label(stem: &str, name: Option<&str>) -> String {
|
||||
/// Returns `true` when the story has `blocked: true` set (retry limit reached).
|
||||
fn read_story_blocked(project_root: &std::path::Path, stage_dir: &str, stem: &str) -> bool {
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{stem}.md"));
|
||||
@@ -93,7 +93,7 @@ fn read_stage_items(
|
||||
stage_dir: &str,
|
||||
) -> Vec<(String, Option<String>)> {
|
||||
let dir = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir);
|
||||
if !dir.exists() {
|
||||
@@ -346,7 +346,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
// Write a story file with a front-matter name
|
||||
@@ -375,7 +375,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("293_story_register_all_bot_commands.md");
|
||||
@@ -413,7 +413,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("293_story_register_all_bot_commands.md");
|
||||
@@ -436,7 +436,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("293_story_register_all_bot_commands.md");
|
||||
@@ -480,7 +480,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("42_story_idle.md");
|
||||
@@ -502,7 +502,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("42_story_blocked.md");
|
||||
@@ -524,7 +524,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("42_story_idle.md");
|
||||
@@ -578,7 +578,7 @@ mod tests {
|
||||
fn read_story_blocked_returns_true_when_blocked() {
|
||||
use tempfile::TempDir;
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
std::fs::write(
|
||||
stage_dir.join("42_story_foo.md"),
|
||||
@@ -592,7 +592,7 @@ mod tests {
|
||||
fn read_story_blocked_returns_false_when_not_blocked() {
|
||||
use tempfile::TempDir;
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
std::fs::write(
|
||||
stage_dir.join("42_story_foo.md"),
|
||||
@@ -610,7 +610,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("42_story_idle.md");
|
||||
@@ -632,7 +632,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit/work/2_current");
|
||||
let stage_dir = tmp.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
|
||||
let story_path = stage_dir.join("42_story_blocked.md");
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(super) fn handle_triage(ctx: &CommandContext) -> Option<String> {
|
||||
|
||||
let current_dir = ctx
|
||||
.project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join("2_current");
|
||||
|
||||
@@ -179,7 +179,7 @@ fn build_triage_dump(
|
||||
// ---- Agent log tail ----
|
||||
let log_dir = ctx
|
||||
.project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("logs")
|
||||
.join(story_id);
|
||||
match latest_log_file(&log_dir) {
|
||||
|
||||
@@ -45,7 +45,7 @@ pub(crate) fn unblock_by_number(project_root: &Path, story_number: &str) -> Stri
|
||||
let mut found: Option<(std::path::PathBuf, String)> = None;
|
||||
|
||||
'outer: for stage_dir in SEARCH_DIRS {
|
||||
let dir = project_root.join(".storkit").join("work").join(stage_dir);
|
||||
let dir = project_root.join(".huskies").join("work").join(stage_dir);
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
@@ -252,7 +252,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let contents = std::fs::read_to_string(
|
||||
tmp.path().join(".storkit/work/2_current/7_story_stuck.md"),
|
||||
tmp.path().join(".huskies/work/2_current/7_story_stuck.md"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
|
||||
@@ -77,7 +77,7 @@ fn find_last_release_tag(root: &std::path::Path) -> Option<String> {
|
||||
if tag.is_empty() { None } else { Some(tag) }
|
||||
}
|
||||
|
||||
/// Return the subjects of all `storkit: merge …` commits reachable from HEAD
|
||||
/// Return the subjects of all `huskies: merge …` commits reachable from HEAD
|
||||
/// but not from `since_tag` (or all commits when `since_tag` is `None`).
|
||||
fn list_merge_commits_since(
|
||||
root: &std::path::Path,
|
||||
@@ -97,7 +97,7 @@ fn list_merge_commits_since(
|
||||
"--format=%s",
|
||||
"--extended-regexp",
|
||||
"--grep",
|
||||
"(storkit|story-kit): merge [0-9]+_",
|
||||
"(huskies|storkit|story-kit): merge [0-9]+_",
|
||||
])
|
||||
.current_dir(root)
|
||||
.output()
|
||||
@@ -115,14 +115,14 @@ fn list_merge_commits_since(
|
||||
}
|
||||
|
||||
/// Parse a story number and slug from a merge commit subject like
|
||||
/// `storkit: merge 386_story_unreleased_command`.
|
||||
/// `huskies: merge 386_story_unreleased_command`.
|
||||
///
|
||||
/// Returns `(story_number, slug_remainder)` or `None` if the subject doesn't
|
||||
/// match the expected pattern.
|
||||
fn parse_story_from_subject(subject: &str) -> Option<(u64, String)> {
|
||||
// Match "storkit: merge NNN_rest" or "story-kit: merge NNN_rest"
|
||||
// Match "huskies: merge NNN_rest" or "story-kit: merge NNN_rest"
|
||||
let rest = subject
|
||||
.strip_prefix("storkit: merge ")
|
||||
.strip_prefix("huskies: merge ")
|
||||
.or_else(|| subject.strip_prefix("story-kit: merge "))?;
|
||||
|
||||
let (num_str, slug) = rest.split_once('_')?;
|
||||
@@ -159,7 +159,7 @@ fn find_story_name(root: &std::path::Path, num_str: &str) -> Option<String> {
|
||||
"6_archived",
|
||||
];
|
||||
for stage in STAGES {
|
||||
let dir = root.join(".storkit").join("work").join(stage);
|
||||
let dir = root.join(".huskies").join("work").join(stage);
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
@@ -271,8 +271,8 @@ mod tests {
|
||||
// -- parse_story_from_subject ------------------------------------------
|
||||
|
||||
#[test]
|
||||
fn parse_story_storkit_prefix() {
|
||||
let result = parse_story_from_subject("storkit: merge 386_story_unreleased_command");
|
||||
fn parse_story_huskies_prefix() {
|
||||
let result = parse_story_from_subject("huskies: merge 386_story_unreleased_command");
|
||||
assert_eq!(result, Some((386, "story_unreleased_command".to_string())));
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_story_no_underscore_after_number() {
|
||||
let result = parse_story_from_subject("storkit: merge 123");
|
||||
let result = parse_story_from_subject("huskies: merge 123");
|
||||
assert_eq!(result, None);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ use std::path::Path;
|
||||
|
||||
/// Write a work-item file into the standard pipeline directory structure.
|
||||
///
|
||||
/// Creates `.storkit/work/{stage}/{filename}` under `root`, creating any
|
||||
/// Creates `.huskies/work/{stage}/{filename}` under `root`, creating any
|
||||
/// missing parent directories.
|
||||
pub(crate) fn write_story_file(root: &Path, stage: &str, filename: &str, content: &str) {
|
||||
let dir = root.join(".storkit/work").join(stage);
|
||||
let dir = root.join(".huskies/work").join(stage);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join(filename), content).unwrap();
|
||||
}
|
||||
|
||||
+12
-12
@@ -1,6 +1,6 @@
|
||||
//! Deferred agent start via one-shot timers.
|
||||
//!
|
||||
//! Provides [`TimerStore`] for persisting timers to `.storkit/timers.json`,
|
||||
//! Provides [`TimerStore`] for persisting timers to `.huskies/timers.json`,
|
||||
//! a 30-second tick loop ([`spawn_timer_tick_loop`]) that fires due timers,
|
||||
//! and command parsing / handling for the `timer` bot command.
|
||||
|
||||
@@ -398,7 +398,7 @@ pub async fn handle_timer_command(
|
||||
|
||||
// The story must be in backlog or current. When the timer fires,
|
||||
// backlog stories are moved to current automatically.
|
||||
let work_dir = project_root.join(".storkit").join("work");
|
||||
let work_dir = project_root.join(".huskies").join("work");
|
||||
let in_backlog = work_dir.join("1_backlog").join(format!("{story_id}.md")).exists();
|
||||
let in_current = work_dir.join("2_current").join(format!("{story_id}.md")).exists();
|
||||
if !in_backlog && !in_current {
|
||||
@@ -555,7 +555,7 @@ fn resolve_story_id(number_or_id: &str, project_root: &Path) -> Option<String> {
|
||||
}
|
||||
|
||||
for stage in STAGES {
|
||||
let dir = project_root.join(".storkit").join("work").join(stage);
|
||||
let dir = project_root.join(".huskies").join("work").join(stage);
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
@@ -931,8 +931,8 @@ mod tests {
|
||||
async fn handle_schedule_story_not_in_backlog_or_current() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
// Set up directory structure with no story in backlog or current
|
||||
std::fs::create_dir_all(dir.path().join(".storkit/work/1_backlog")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".storkit/work/2_current")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies/work/1_backlog")).unwrap();
|
||||
std::fs::create_dir_all(dir.path().join(".huskies/work/2_current")).unwrap();
|
||||
let store = TimerStore::load(dir.path().join("timers.json"));
|
||||
let result = handle_timer_command(
|
||||
TimerCommand::Schedule {
|
||||
@@ -952,7 +952,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn handle_schedule_accepts_backlog_story() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let backlog_dir = dir.path().join(".storkit/work/1_backlog");
|
||||
let backlog_dir = dir.path().join(".huskies/work/1_backlog");
|
||||
std::fs::create_dir_all(&backlog_dir).unwrap();
|
||||
std::fs::write(
|
||||
backlog_dir.join("421_story_foo.md"),
|
||||
@@ -978,7 +978,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn handle_schedule_success() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let current_dir = dir.path().join(".storkit/work/2_current");
|
||||
let current_dir = dir.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(¤t_dir).unwrap();
|
||||
std::fs::write(current_dir.join("421_story_foo.md"), "---\nname: Foo\n---").unwrap();
|
||||
let store = TimerStore::load(dir.path().join("timers.json"));
|
||||
@@ -1001,7 +1001,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn handle_schedule_invalid_time() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let current_dir = dir.path().join(".storkit/work/2_current");
|
||||
let current_dir = dir.path().join(".huskies/work/2_current");
|
||||
std::fs::create_dir_all(¤t_dir).unwrap();
|
||||
std::fs::write(current_dir.join("421_story_foo.md"), "---\nname: Foo\n---").unwrap();
|
||||
let store = TimerStore::load(dir.path().join("timers.json"));
|
||||
@@ -1058,8 +1058,8 @@ mod tests {
|
||||
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(backlog.join("421_story_foo.md"), "---\nname: Foo\n---\n").unwrap();
|
||||
@@ -1103,8 +1103,8 @@ mod tests {
|
||||
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
let backlog = root.join(".storkit/work/1_backlog");
|
||||
let current = root.join(".storkit/work/2_current");
|
||||
let backlog = root.join(".huskies/work/1_backlog");
|
||||
let current = root.join(".huskies/work/2_current");
|
||||
fs::create_dir_all(&backlog).unwrap();
|
||||
fs::create_dir_all(¤t).unwrap();
|
||||
fs::write(
|
||||
|
||||
@@ -104,7 +104,7 @@ pub async fn handle_assign(
|
||||
// Find the story file across all pipeline stages.
|
||||
let mut found: Option<(std::path::PathBuf, String)> = None;
|
||||
'outer: for stage in STAGES {
|
||||
let dir = project_root.join(".storkit").join("work").join(stage);
|
||||
let dir = project_root.join(".huskies").join("work").join(stage);
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
@@ -356,7 +356,7 @@ mod tests {
|
||||
async fn handle_assign_returns_not_found_for_unknown_number() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
for stage in STAGES {
|
||||
std::fs::create_dir_all(tmp.path().join(".storkit/work").join(stage)).unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join(".huskies/work").join(stage)).unwrap();
|
||||
}
|
||||
let agents = std::sync::Arc::new(AgentPool::new_test(3000));
|
||||
let response = handle_assign("Timmy", "999", "opus", tmp.path(), &agents).await;
|
||||
@@ -394,7 +394,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let contents = std::fs::read_to_string(
|
||||
tmp.path().join(".storkit/work/1_backlog/42_story_test.md"),
|
||||
tmp.path().join(".huskies/work/1_backlog/42_story_test.md"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
@@ -426,7 +426,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let contents = std::fs::read_to_string(
|
||||
tmp.path().join(".storkit/work/1_backlog/7_story_small.md"),
|
||||
tmp.path().join(".huskies/work/1_backlog/7_story_small.md"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
@@ -449,7 +449,7 @@ mod tests {
|
||||
handle_assign("Timmy", "5", "opus", tmp.path(), &agents).await;
|
||||
|
||||
let contents = std::fs::read_to_string(
|
||||
tmp.path().join(".storkit/work/1_backlog/5_story_existing.md"),
|
||||
tmp.path().join(".huskies/work/1_backlog/5_story_existing.md"),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
|
||||
@@ -51,7 +51,7 @@ pub(super) struct PersistedHistory {
|
||||
}
|
||||
|
||||
/// Path to the persisted conversation history file relative to project root.
|
||||
pub(super) const HISTORY_FILE: &str = ".storkit/matrix_history.json";
|
||||
pub(super) const HISTORY_FILE: &str = ".huskies/matrix_history.json";
|
||||
|
||||
/// Load conversation history from disk, returning an empty map on any error.
|
||||
pub fn load_history(project_root: &std::path::Path) -> HashMap<OwnedRoomId, RoomConversation> {
|
||||
@@ -197,7 +197,7 @@ mod tests {
|
||||
#[test]
|
||||
fn save_and_load_history_round_trip() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let story_kit_dir = dir.path().join(".storkit");
|
||||
let story_kit_dir = dir.path().join(".huskies");
|
||||
std::fs::create_dir_all(&story_kit_dir).unwrap();
|
||||
|
||||
let room_id: OwnedRoomId = "!persist:example.com".parse().unwrap();
|
||||
@@ -238,7 +238,7 @@ mod tests {
|
||||
#[test]
|
||||
fn load_history_returns_empty_on_corrupt_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let story_kit_dir = dir.path().join(".storkit");
|
||||
let story_kit_dir = dir.path().join(".huskies");
|
||||
std::fs::create_dir_all(&story_kit_dir).unwrap();
|
||||
std::fs::write(dir.path().join(HISTORY_FILE), "not valid json").unwrap();
|
||||
let loaded = load_history(dir.path());
|
||||
|
||||
@@ -27,7 +27,7 @@ pub async fn run_bot(
|
||||
agents: Arc<AgentPool>,
|
||||
shutdown_rx: watch::Receiver<Option<crate::rebuild::ShutdownReason>>,
|
||||
) -> Result<(), String> {
|
||||
let store_path = project_root.join(".storkit").join("matrix_store");
|
||||
let store_path = project_root.join(".huskies").join("matrix_store");
|
||||
let client = Client::builder()
|
||||
.homeserver_url(config.homeserver.as_deref().unwrap_or_default())
|
||||
.sqlite_store(&store_path, None)
|
||||
@@ -36,7 +36,7 @@ pub async fn run_bot(
|
||||
.map_err(|e| format!("Failed to build Matrix client: {e}"))?;
|
||||
|
||||
// Persist device ID so E2EE crypto state survives restarts.
|
||||
let device_id_path = project_root.join(".storkit").join("matrix_device_id");
|
||||
let device_id_path = project_root.join(".huskies").join("matrix_device_id");
|
||||
let saved_device_id: Option<String> = std::fs::read_to_string(&device_id_path)
|
||||
.ok()
|
||||
.map(|s| s.trim().to_string())
|
||||
@@ -48,7 +48,7 @@ pub async fn run_bot(
|
||||
config.username.as_deref().unwrap_or_default(),
|
||||
config.password.as_deref().unwrap_or_default(),
|
||||
)
|
||||
.initial_device_display_name("Storkit Bot");
|
||||
.initial_device_display_name("Huskies Bot");
|
||||
|
||||
if let Some(ref device_id) = saved_device_id {
|
||||
login_builder = login_builder.device_id(device_id);
|
||||
@@ -218,7 +218,7 @@ pub async fn run_bot(
|
||||
let announce_bot_name = bot_name.clone();
|
||||
|
||||
let timer_store = Arc::new(crate::chat::timer::TimerStore::load(
|
||||
project_root.join(".storkit").join("timers.json"),
|
||||
project_root.join(".huskies").join("timers.json"),
|
||||
));
|
||||
crate::chat::timer::spawn_timer_tick_loop(
|
||||
Arc::clone(&timer_store),
|
||||
|
||||
@@ -9,7 +9,7 @@ fn default_permission_timeout_secs() -> u64 {
|
||||
120
|
||||
}
|
||||
|
||||
/// Configuration for the Matrix bot, read from `.storkit/bot.toml`.
|
||||
/// Configuration for the Matrix bot, read from `.huskies/bot.toml`.
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct BotConfig {
|
||||
/// Matrix homeserver URL, e.g. `https://matrix.example.com`
|
||||
@@ -145,12 +145,12 @@ fn default_whatsapp_provider() -> String {
|
||||
}
|
||||
|
||||
impl BotConfig {
|
||||
/// Load bot configuration from `.storkit/bot.toml`.
|
||||
/// Load bot configuration from `.huskies/bot.toml`.
|
||||
///
|
||||
/// Returns `None` if the file does not exist, fails to parse, has
|
||||
/// `enabled = false`, or specifies no room IDs.
|
||||
pub fn load(project_root: &Path) -> Option<Self> {
|
||||
let path = project_root.join(".storkit").join("bot.toml");
|
||||
let path = project_root.join(".huskies").join("bot.toml");
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
@@ -285,7 +285,7 @@ impl BotConfig {
|
||||
/// array, and writes the result back. Errors are logged but not propagated
|
||||
/// so a persistence failure never interrupts the bot's message handling.
|
||||
pub fn save_ambient_rooms(project_root: &Path, room_ids: &[String]) {
|
||||
let path = project_root.join(".storkit").join("bot.toml");
|
||||
let path = project_root.join(".huskies").join("bot.toml");
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
@@ -334,7 +334,7 @@ mod tests {
|
||||
#[test]
|
||||
fn load_returns_none_when_disabled() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -354,7 +354,7 @@ enabled = false
|
||||
#[test]
|
||||
fn load_returns_config_when_enabled_with_room_ids() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -382,7 +382,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_merges_deprecated_room_id_into_room_ids() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
// Old-style single room_id key — should still work.
|
||||
fs::write(
|
||||
@@ -403,7 +403,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_returns_none_when_no_room_ids() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -422,7 +422,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_returns_none_when_toml_invalid() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(sk.join("bot.toml"), "not valid toml {{{").unwrap();
|
||||
let result = BotConfig::load(tmp.path());
|
||||
@@ -432,7 +432,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_respects_optional_model() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -453,7 +453,7 @@ model = "claude-sonnet-4-6"
|
||||
#[test]
|
||||
fn load_uses_default_history_size() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -473,7 +473,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_respects_custom_history_size() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -494,7 +494,7 @@ history_size = 50
|
||||
#[test]
|
||||
fn load_reads_display_name() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -515,7 +515,7 @@ display_name = "Timmy"
|
||||
#[test]
|
||||
fn load_display_name_defaults_to_none_when_absent() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -535,7 +535,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_uses_default_permission_timeout() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -555,7 +555,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_respects_custom_permission_timeout() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -579,7 +579,7 @@ permission_timeout_secs = 60
|
||||
// must parse successfully — the field is simply ignored now that
|
||||
// verification is always enforced unconditionally.
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -604,7 +604,7 @@ require_verified_devices = true
|
||||
#[test]
|
||||
fn load_reads_ambient_rooms() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -625,7 +625,7 @@ ambient_rooms = ["!abc:example.com"]
|
||||
#[test]
|
||||
fn load_ambient_rooms_defaults_to_empty_when_absent() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -645,7 +645,7 @@ enabled = true
|
||||
#[test]
|
||||
fn save_ambient_rooms_persists_to_bot_toml() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -667,7 +667,7 @@ enabled = true
|
||||
#[test]
|
||||
fn save_ambient_rooms_clears_when_empty() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -690,7 +690,7 @@ ambient_rooms = ["!abc:example.com"]
|
||||
#[test]
|
||||
fn load_transport_defaults_to_matrix() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -710,7 +710,7 @@ enabled = true
|
||||
#[test]
|
||||
fn load_transport_reads_custom_value() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -746,7 +746,7 @@ whatsapp_verify_token = "my-verify"
|
||||
#[test]
|
||||
fn load_whatsapp_returns_none_when_missing_phone_number_id() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -767,7 +767,7 @@ whatsapp_verify_token = "my-verify"
|
||||
#[test]
|
||||
fn load_whatsapp_returns_none_when_missing_access_token() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -788,7 +788,7 @@ whatsapp_verify_token = "my-verify"
|
||||
#[test]
|
||||
fn load_whatsapp_returns_none_when_missing_verify_token() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -811,7 +811,7 @@ whatsapp_access_token = "EAAtoken"
|
||||
#[test]
|
||||
fn load_twilio_whatsapp_reads_config() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -842,7 +842,7 @@ twilio_whatsapp_number = "+14155551234"
|
||||
#[test]
|
||||
fn load_whatsapp_provider_defaults_to_meta() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -865,7 +865,7 @@ whatsapp_verify_token = "my-verify"
|
||||
#[test]
|
||||
fn load_twilio_returns_none_when_missing_account_sid() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -887,7 +887,7 @@ twilio_whatsapp_number = "+14155551234"
|
||||
#[test]
|
||||
fn load_twilio_returns_none_when_missing_auth_token() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -909,7 +909,7 @@ twilio_whatsapp_number = "+14155551234"
|
||||
#[test]
|
||||
fn load_twilio_returns_none_when_missing_whatsapp_number() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -933,7 +933,7 @@ twilio_auth_token = "authtest"
|
||||
#[test]
|
||||
fn load_slack_transport_reads_config() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -959,7 +959,7 @@ slack_channel_ids = ["C01ABCDEF"]
|
||||
#[test]
|
||||
fn load_slack_returns_none_when_missing_bot_token() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -980,7 +980,7 @@ slack_channel_ids = ["C01ABCDEF"]
|
||||
#[test]
|
||||
fn load_slack_returns_none_when_missing_signing_secret() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
@@ -1001,7 +1001,7 @@ slack_channel_ids = ["C01ABCDEF"]
|
||||
#[test]
|
||||
fn load_slack_returns_none_when_missing_channel_ids() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
fs::create_dir_all(&sk).unwrap();
|
||||
fs::write(
|
||||
sk.join("bot.toml"),
|
||||
|
||||
@@ -72,7 +72,7 @@ pub async fn handle_delete(
|
||||
// Find the story file across all pipeline stages.
|
||||
let mut found: Option<(std::path::PathBuf, &str, String)> = None; // (path, stage, story_id)
|
||||
'outer: for stage in STAGES {
|
||||
let dir = project_root.join(".storkit").join("work").join(stage);
|
||||
let dir = project_root.join(".huskies").join("work").join(stage);
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
@@ -148,8 +148,8 @@ pub async fn handle_delete(
|
||||
}
|
||||
|
||||
// Commit the deletion to git.
|
||||
let commit_msg = format!("storkit: delete {story_id}");
|
||||
let work_rel = std::path::PathBuf::from(".storkit").join("work");
|
||||
let commit_msg = format!("huskies: delete {story_id}");
|
||||
let work_rel = std::path::PathBuf::from(".huskies").join("work");
|
||||
let _ = std::process::Command::new("git")
|
||||
.args(["add", "-A"])
|
||||
.arg(&work_rel)
|
||||
@@ -288,7 +288,7 @@ mod tests {
|
||||
"5_done",
|
||||
"6_archived",
|
||||
] {
|
||||
std::fs::create_dir_all(project_root.join(".storkit").join("work").join(stage))
|
||||
std::fs::create_dir_all(project_root.join(".huskies").join("work").join(stage))
|
||||
.unwrap();
|
||||
}
|
||||
let agents = std::sync::Arc::new(crate::agents::AgentPool::new_test(3000));
|
||||
@@ -321,7 +321,7 @@ mod tests {
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let backlog_dir = project_root.join(".storkit").join("work").join("1_backlog");
|
||||
let backlog_dir = project_root.join(".huskies").join("work").join("1_backlog");
|
||||
std::fs::create_dir_all(&backlog_dir).unwrap();
|
||||
let story_path = backlog_dir.join("42_story_some_feature.md");
|
||||
std::fs::write(&story_path, "---\nname: Some Feature\n---\n\n# Story 42\n").unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Matrix bot integration for Story Kit.
|
||||
//!
|
||||
//! When a `.storkit/bot.toml` file is present with `enabled = true`, the
|
||||
//! When a `.huskies/bot.toml` file is present with `enabled = true`, the
|
||||
//! server spawns a Matrix bot that:
|
||||
//!
|
||||
//! 1. Connects to the configured homeserver and joins the configured room.
|
||||
@@ -41,7 +41,7 @@ use tokio::sync::{Mutex as TokioMutex, broadcast, mpsc, watch};
|
||||
|
||||
/// Attempt to start the Matrix bot.
|
||||
///
|
||||
/// Reads the bot configuration from `.storkit/bot.toml`. If the file is
|
||||
/// Reads the bot configuration from `.huskies/bot.toml`. If the file is
|
||||
/// absent or `enabled = false`, this function returns immediately without
|
||||
/// spawning anything — the server continues normally.
|
||||
///
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn extract_story_number(item_id: &str) -> Option<&str> {
|
||||
/// Returns `None` if the file doesn't exist or has no parseable name.
|
||||
pub fn read_story_name(project_root: &Path, stage: &str, item_id: &str) -> Option<String> {
|
||||
let path = project_root
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage)
|
||||
.join(format!("{item_id}.md"));
|
||||
@@ -507,7 +507,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn rate_limit_warning_sends_notification_with_agent_and_story() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit").join("work").join("2_current");
|
||||
let stage_dir = tmp.path().join(".huskies").join("work").join("2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
std::fs::write(
|
||||
stage_dir.join("365_story_rate_limit.md"),
|
||||
@@ -613,7 +613,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn stage_notification_uses_dynamic_room_ids() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit").join("work").join("3_qa");
|
||||
let stage_dir = tmp.path().join(".huskies").join("work").join("3_qa");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
std::fs::write(
|
||||
stage_dir.join("10_story_foo.md"),
|
||||
@@ -642,7 +642,7 @@ mod tests {
|
||||
stage: "3_qa".to_string(),
|
||||
item_id: "10_story_foo".to_string(),
|
||||
action: "qa".to_string(),
|
||||
commit_msg: "storkit: qa 10_story_foo".to_string(),
|
||||
commit_msg: "huskies: qa 10_story_foo".to_string(),
|
||||
from_stage: None,
|
||||
}).unwrap();
|
||||
|
||||
@@ -677,7 +677,7 @@ mod tests {
|
||||
stage: "3_qa".to_string(),
|
||||
item_id: "10_story_foo".to_string(),
|
||||
action: "qa".to_string(),
|
||||
commit_msg: "storkit: qa 10_story_foo".to_string(),
|
||||
commit_msg: "huskies: qa 10_story_foo".to_string(),
|
||||
from_stage: None,
|
||||
}).unwrap();
|
||||
|
||||
@@ -746,7 +746,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let stage_dir = tmp
|
||||
.path()
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join("2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
@@ -772,7 +772,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let stage_dir = tmp
|
||||
.path()
|
||||
.join(".storkit")
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join("2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
@@ -858,7 +858,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn story_blocked_sends_notification_with_reason() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let stage_dir = tmp.path().join(".storkit").join("work").join("2_current");
|
||||
let stage_dir = tmp.path().join(".huskies").join("work").join("2_current");
|
||||
std::fs::create_dir_all(&stage_dir).unwrap();
|
||||
std::fs::write(
|
||||
stage_dir.join("425_story_blocking_test.md"),
|
||||
@@ -1033,7 +1033,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn rate_limit_warning_suppressed_when_config_false() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk_dir = tmp.path().join(".storkit");
|
||||
let sk_dir = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk_dir).unwrap();
|
||||
std::fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1066,7 +1066,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn rate_limit_hard_block_always_sent_when_config_false() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk_dir = tmp.path().join(".storkit");
|
||||
let sk_dir = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk_dir).unwrap();
|
||||
std::fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1101,7 +1101,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn story_blocked_always_sent_when_config_false() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk_dir = tmp.path().join(".storkit");
|
||||
let sk_dir = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk_dir).unwrap();
|
||||
std::fs::write(
|
||||
sk_dir.join("project.toml"),
|
||||
@@ -1135,7 +1135,7 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn rate_limit_warning_suppressed_after_hot_reload() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk_dir = tmp.path().join(".storkit");
|
||||
let sk_dir = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk_dir).unwrap();
|
||||
// Start with notifications enabled.
|
||||
std::fs::write(
|
||||
|
||||
@@ -51,7 +51,7 @@ pub fn extract_rmtree_command(
|
||||
|
||||
/// Handle an rmtree command asynchronously.
|
||||
///
|
||||
/// Finds the worktree for `story_number` under `.storkit/worktrees/`, stops any
|
||||
/// Finds the worktree for `story_number` under `.huskies/worktrees/`, stops any
|
||||
/// running agent, and removes the worktree directory and its feature branch.
|
||||
/// Returns a markdown-formatted response string.
|
||||
pub async fn handle_rmtree(
|
||||
@@ -201,7 +201,7 @@ mod tests {
|
||||
async fn handle_rmtree_returns_not_found_for_unknown_number() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project_root = tmp.path();
|
||||
std::fs::create_dir_all(project_root.join(".storkit").join("worktrees")).unwrap();
|
||||
std::fs::create_dir_all(project_root.join(".huskies").join("worktrees")).unwrap();
|
||||
let agents = std::sync::Arc::new(crate::agents::AgentPool::new_test(3000));
|
||||
let response = handle_rmtree("Timmy", "999", project_root, &agents).await;
|
||||
assert!(
|
||||
|
||||
@@ -91,7 +91,7 @@ pub async fn handle_start(
|
||||
// Find the story file across all pipeline stages.
|
||||
let mut found: Option<(std::path::PathBuf, String)> = None; // (path, story_id)
|
||||
'outer: for stage in STAGES {
|
||||
let dir = project_root.join(".storkit").join("work").join(stage);
|
||||
let dir = project_root.join(".huskies").join("work").join(stage);
|
||||
if !dir.exists() {
|
||||
continue;
|
||||
}
|
||||
@@ -274,7 +274,7 @@ mod tests {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project_root = tmp.path();
|
||||
for stage in &["1_backlog", "2_current", "3_qa", "4_merge", "5_done", "6_archived"] {
|
||||
std::fs::create_dir_all(project_root.join(".storkit").join("work").join(stage))
|
||||
std::fs::create_dir_all(project_root.join(".huskies").join("work").join(stage))
|
||||
.unwrap();
|
||||
}
|
||||
let agents = std::sync::Arc::new(crate::agents::AgentPool::new_test(3000));
|
||||
@@ -292,7 +292,7 @@ mod tests {
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let project_root = tmp.path();
|
||||
let sk = project_root.join(".storkit");
|
||||
let sk = project_root.join(".huskies");
|
||||
let backlog = sk.join("work/1_backlog");
|
||||
std::fs::create_dir_all(&backlog).unwrap();
|
||||
std::fs::write(
|
||||
|
||||
@@ -22,9 +22,9 @@ use super::format::markdown_to_slack;
|
||||
/// Payload sent by Slack for slash commands (application/x-www-form-urlencoded).
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SlackSlashCommandPayload {
|
||||
/// The slash command that was invoked (e.g. "/storkit-status").
|
||||
/// The slash command that was invoked (e.g. "/huskies-status").
|
||||
pub command: String,
|
||||
/// Any text typed after the command (e.g. "42" for "/storkit-show 42").
|
||||
/// Any text typed after the command (e.g. "42" for "/huskies-show 42").
|
||||
#[serde(default)]
|
||||
pub text: String,
|
||||
/// The user who invoked the command.
|
||||
@@ -44,12 +44,12 @@ pub(super) struct SlashCommandResponse {
|
||||
|
||||
/// Map a Slack slash command name to the corresponding bot command keyword.
|
||||
///
|
||||
/// Supported: `/storkit-status`, `/storkit-cost`, `/storkit-show`,
|
||||
/// `/storkit-git`, `/storkit-htop`.
|
||||
/// Supported: `/huskies-status`, `/huskies-cost`, `/huskies-show`,
|
||||
/// `/huskies-git`, `/huskies-htop`.
|
||||
pub(super) fn slash_command_to_bot_keyword(command: &str) -> Option<&'static str> {
|
||||
// Strip leading "/" and the "storkit-" prefix.
|
||||
// Strip leading "/" and the "huskies-" prefix.
|
||||
let name = command.strip_prefix('/').unwrap_or(command);
|
||||
let keyword = name.strip_prefix("storkit-")?;
|
||||
let keyword = name.strip_prefix("huskies-")?;
|
||||
match keyword {
|
||||
"status" => Some("status"),
|
||||
"cost" => Some("cost"),
|
||||
@@ -539,9 +539,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_slash_command_payload() {
|
||||
let body = "command=%2Fstorkit-status&text=&user_id=U123&channel_id=C456";
|
||||
let body = "command=%2Fhuskies-status&text=&user_id=U123&channel_id=C456";
|
||||
let payload: SlackSlashCommandPayload = serde_urlencoded::from_str(body).unwrap();
|
||||
assert_eq!(payload.command, "/storkit-status");
|
||||
assert_eq!(payload.command, "/huskies-status");
|
||||
assert_eq!(payload.text, "");
|
||||
assert_eq!(payload.user_id, "U123");
|
||||
assert_eq!(payload.channel_id, "C456");
|
||||
@@ -549,9 +549,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn parse_slash_command_payload_with_text() {
|
||||
let body = "command=%2Fstorkit-show&text=42&user_id=U123&channel_id=C456";
|
||||
let body = "command=%2Fhuskies-show&text=42&user_id=U123&channel_id=C456";
|
||||
let payload: SlackSlashCommandPayload = serde_urlencoded::from_str(body).unwrap();
|
||||
assert_eq!(payload.command, "/storkit-show");
|
||||
assert_eq!(payload.command, "/huskies-show");
|
||||
assert_eq!(payload.text, "42");
|
||||
}
|
||||
|
||||
@@ -559,36 +559,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn slash_command_maps_status() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/storkit-status"), Some("status"));
|
||||
assert_eq!(slash_command_to_bot_keyword("/huskies-status"), Some("status"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_maps_cost() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/storkit-cost"), Some("cost"));
|
||||
assert_eq!(slash_command_to_bot_keyword("/huskies-cost"), Some("cost"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_maps_show() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/storkit-show"), Some("show"));
|
||||
assert_eq!(slash_command_to_bot_keyword("/huskies-show"), Some("show"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_maps_git() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/storkit-git"), Some("git"));
|
||||
assert_eq!(slash_command_to_bot_keyword("/huskies-git"), Some("git"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_maps_htop() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/storkit-htop"), Some("htop"));
|
||||
assert_eq!(slash_command_to_bot_keyword("/huskies-htop"), Some("htop"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_unknown_returns_none() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/storkit-unknown"), None);
|
||||
assert_eq!(slash_command_to_bot_keyword("/huskies-unknown"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slash_command_non_storkit_returns_none() {
|
||||
fn slash_command_non_huskies_returns_none() {
|
||||
assert_eq!(slash_command_to_bot_keyword("/other-command"), None);
|
||||
}
|
||||
|
||||
@@ -628,8 +628,8 @@ mod tests {
|
||||
let room_id = "C01ABCDEF".to_string();
|
||||
|
||||
// Simulate what slash_command_receive does: build a synthetic message.
|
||||
let bot_name = "Storkit";
|
||||
let keyword = slash_command_to_bot_keyword("/storkit-status").unwrap();
|
||||
let bot_name = "Huskies";
|
||||
let keyword = slash_command_to_bot_keyword("/huskies-status").unwrap();
|
||||
let synthetic = format!("{bot_name} {keyword}");
|
||||
|
||||
let dispatch = CommandDispatch {
|
||||
@@ -654,9 +654,9 @@ mod tests {
|
||||
let ambient_rooms = test_ambient_rooms();
|
||||
let room_id = "C01ABCDEF".to_string();
|
||||
|
||||
let bot_name = "Storkit";
|
||||
let keyword = slash_command_to_bot_keyword("/storkit-show").unwrap();
|
||||
// Simulate /storkit-show with text "999"
|
||||
let bot_name = "Huskies";
|
||||
let keyword = slash_command_to_bot_keyword("/huskies-show").unwrap();
|
||||
// Simulate /huskies-show with text "999"
|
||||
let synthetic = format!("{bot_name} {keyword} 999");
|
||||
|
||||
let dispatch = CommandDispatch {
|
||||
@@ -679,11 +679,11 @@ mod tests {
|
||||
#[test]
|
||||
fn rebuild_command_extracted_from_slack_message() {
|
||||
let result = crate::chat::transport::matrix::rebuild::extract_rebuild_command(
|
||||
"Storkit rebuild",
|
||||
"Storkit",
|
||||
"Huskies rebuild",
|
||||
"Huskies",
|
||||
"slack-bot",
|
||||
);
|
||||
assert!(result.is_some(), "'Storkit rebuild' should be recognised");
|
||||
assert!(result.is_some(), "'Huskies rebuild' should be recognised");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -691,7 +691,7 @@ mod tests {
|
||||
// Slack slash-command synthetic messages may not include a bot mention.
|
||||
let result = crate::chat::transport::matrix::rebuild::extract_rebuild_command(
|
||||
"rebuild",
|
||||
"Storkit",
|
||||
"Huskies",
|
||||
"slack-bot",
|
||||
);
|
||||
assert!(result.is_some(), "plain 'rebuild' should be recognised");
|
||||
@@ -700,8 +700,8 @@ mod tests {
|
||||
#[test]
|
||||
fn non_rebuild_slack_message_not_extracted() {
|
||||
let result = crate::chat::transport::matrix::rebuild::extract_rebuild_command(
|
||||
"Storkit status",
|
||||
"Storkit",
|
||||
"Huskies status",
|
||||
"Huskies",
|
||||
"slack-bot",
|
||||
);
|
||||
assert!(result.is_none(), "'status' should not be recognised as rebuild");
|
||||
@@ -712,18 +712,18 @@ mod tests {
|
||||
#[test]
|
||||
fn reset_command_extracted_from_slack_message() {
|
||||
let result = crate::chat::transport::matrix::reset::extract_reset_command(
|
||||
"Storkit reset",
|
||||
"Storkit",
|
||||
"Huskies reset",
|
||||
"Huskies",
|
||||
"slack-bot",
|
||||
);
|
||||
assert!(result.is_some(), "'Storkit reset' should be recognised");
|
||||
assert!(result.is_some(), "'Huskies reset' should be recognised");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_command_extracted_plain_no_mention() {
|
||||
let result = crate::chat::transport::matrix::reset::extract_reset_command(
|
||||
"reset",
|
||||
"Storkit",
|
||||
"Huskies",
|
||||
"slack-bot",
|
||||
);
|
||||
assert!(result.is_some(), "plain 'reset' should be recognised");
|
||||
@@ -750,7 +750,7 @@ mod tests {
|
||||
}));
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ struct PersistedSlackHistory {
|
||||
}
|
||||
|
||||
/// Path to the persisted Slack conversation history file.
|
||||
const SLACK_HISTORY_FILE: &str = ".storkit/slack_history.json";
|
||||
const SLACK_HISTORY_FILE: &str = ".huskies/slack_history.json";
|
||||
|
||||
/// Load Slack conversation history from disk.
|
||||
pub fn load_slack_history(project_root: &std::path::Path) -> HashMap<String, RoomConversation> {
|
||||
@@ -66,7 +66,7 @@ mod tests {
|
||||
#[test]
|
||||
fn save_and_load_slack_history_round_trips() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
|
||||
let mut history = HashMap::new();
|
||||
@@ -110,7 +110,7 @@ mod tests {
|
||||
#[test]
|
||||
fn load_slack_history_returns_empty_on_invalid_json() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(sk.join("slack_history.json"), "not json {{{").unwrap();
|
||||
let history = load_slack_history(tmp.path());
|
||||
|
||||
@@ -635,7 +635,7 @@ mod tests {
|
||||
}));
|
||||
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ struct PersistedWhatsAppHistory {
|
||||
}
|
||||
|
||||
/// Path to the persisted WhatsApp conversation history file.
|
||||
const WHATSAPP_HISTORY_FILE: &str = ".storkit/whatsapp_history.json";
|
||||
const WHATSAPP_HISTORY_FILE: &str = ".huskies/whatsapp_history.json";
|
||||
|
||||
/// Load WhatsApp conversation history from disk.
|
||||
pub fn load_whatsapp_history(project_root: &std::path::Path) -> HashMap<String, RoomConversation> {
|
||||
@@ -162,7 +162,7 @@ mod tests {
|
||||
#[test]
|
||||
fn save_and_load_whatsapp_history_round_trips() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
|
||||
let mut history = HashMap::new();
|
||||
@@ -206,7 +206,7 @@ mod tests {
|
||||
#[test]
|
||||
fn load_whatsapp_history_returns_empty_on_invalid_json() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
std::fs::write(sk.join("whatsapp_history.json"), "not json {{{").unwrap();
|
||||
let history = load_whatsapp_history(tmp.path());
|
||||
@@ -216,7 +216,7 @@ mod tests {
|
||||
#[test]
|
||||
fn save_whatsapp_history_preserves_multiple_senders() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let sk = tmp.path().join(".storkit");
|
||||
let sk = tmp.path().join(".huskies");
|
||||
std::fs::create_dir_all(&sk).unwrap();
|
||||
|
||||
let mut history = HashMap::new();
|
||||
|
||||
@@ -63,11 +63,11 @@ pub fn strip_bot_mention<'a>(message: &'a str, bot_name: &str, bot_user_id: &str
|
||||
if let Some(close_paren) = url_content.find(')') {
|
||||
let url = &url_content[..close_paren]; // "https://matrix.to/#/@user:server"
|
||||
let matrix_prefix = "https://matrix.to/#/";
|
||||
if let Some(mentioned_id) = url.strip_prefix(matrix_prefix) {
|
||||
if mentioned_id.eq_ignore_ascii_case(bot_user_id) {
|
||||
let rest = &url_content[close_paren + 1..];
|
||||
return strip_mention_separator(rest);
|
||||
}
|
||||
if let Some(mentioned_id) = url.strip_prefix(matrix_prefix)
|
||||
&& mentioned_id.eq_ignore_ascii_case(bot_user_id)
|
||||
{
|
||||
let rest = &url_content[close_paren + 1..];
|
||||
return strip_mention_separator(rest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user