wip(929): stage 9 — drop FS-archived-deps scan; story_tools/story/create.rs reads CRDT
io/watcher and io/watcher/sweep were already CRDT-only — the watcher only
watches .huskies/{project,agents}.toml, work-item events come from CRDT
subscribe — so the remaining FS shadow reader was the bug-503 archived-dep
warning in story_tools/story/create.rs (via check_archived_deps_from_list,
which scanned .huskies/work/6_archived/). Migrate that call to the
CRDT-direct `dep_is_archived_crdt`. Drop the now-unused helper and the
four dead imports in bug/spike/refactor/criteria.rs that referenced it.
io/story_metadata/deps.rs is reduced to a module-level comment pointing
callers at the crdt_state helpers; nothing in io/ now scans the FS shadow
tree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ use crate::http::workflow::{
|
|||||||
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
||||||
update_story_in_file, validate_story_dirs,
|
update_story_in_file, validate_story_dirs,
|
||||||
};
|
};
|
||||||
use crate::io::story_metadata::{check_archived_deps_from_list, parse_unchecked_todos};
|
use crate::io::story_metadata::parse_unchecked_todos;
|
||||||
use crate::service::story::parse_test_cases;
|
use crate::service::story::parse_test_cases;
|
||||||
use crate::slog_warn;
|
use crate::slog_warn;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::http::workflow::{
|
|||||||
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
||||||
update_story_in_file, validate_story_dirs,
|
update_story_in_file, validate_story_dirs,
|
||||||
};
|
};
|
||||||
use crate::io::story_metadata::{check_archived_deps_from_list, parse_unchecked_todos};
|
use crate::io::story_metadata::parse_unchecked_todos;
|
||||||
use crate::service::story::parse_test_cases;
|
use crate::service::story::parse_test_cases;
|
||||||
use crate::slog_warn;
|
use crate::slog_warn;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::http::workflow::{
|
|||||||
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
||||||
update_story_in_file, validate_story_dirs,
|
update_story_in_file, validate_story_dirs,
|
||||||
};
|
};
|
||||||
use crate::io::story_metadata::{check_archived_deps_from_list, parse_unchecked_todos};
|
use crate::io::story_metadata::parse_unchecked_todos;
|
||||||
use crate::service::story::parse_test_cases;
|
use crate::service::story::parse_test_cases;
|
||||||
use crate::slog_warn;
|
use crate::slog_warn;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use crate::http::workflow::{
|
|||||||
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
list_refactor_files, load_pipeline_state, load_upcoming_stories, remove_criterion_from_file,
|
||||||
update_story_in_file, validate_story_dirs,
|
update_story_in_file, validate_story_dirs,
|
||||||
};
|
};
|
||||||
use crate::io::story_metadata::{check_archived_deps_from_list, parse_unchecked_todos};
|
use crate::io::story_metadata::parse_unchecked_todos;
|
||||||
use crate::service::story::parse_test_cases;
|
use crate::service::story::parse_test_cases;
|
||||||
use crate::slog_warn;
|
use crate::slog_warn;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
use crate::http::context::AppContext;
|
use crate::http::context::AppContext;
|
||||||
use crate::http::workflow::create_story_file;
|
use crate::http::workflow::create_story_file;
|
||||||
use crate::io::story_metadata::check_archived_deps_from_list;
|
|
||||||
use crate::slog_warn;
|
use crate::slog_warn;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
@@ -51,9 +50,15 @@ pub(crate) fn tool_create_story(args: &Value, ctx: &AppContext) -> Result<String
|
|||||||
// Bug 503: warn at creation time if any depends_on points at an already-archived story.
|
// Bug 503: warn at creation time if any depends_on points at an already-archived story.
|
||||||
// Archived = satisfied semantics: the dep will resolve immediately on the next promotion
|
// Archived = satisfied semantics: the dep will resolve immediately on the next promotion
|
||||||
// tick, which is surprising if the archived story was abandoned rather than cleanly done.
|
// tick, which is surprising if the archived story was abandoned rather than cleanly done.
|
||||||
let archived_deps = depends_on
|
// Story 929: dep archived-status now comes from the CRDT, not a FS scan of 6_archived/.
|
||||||
|
let archived_deps: Vec<u32> = depends_on
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(|deps| check_archived_deps_from_list(&root, deps))
|
.map(|deps| {
|
||||||
|
deps.iter()
|
||||||
|
.copied()
|
||||||
|
.filter(|&d| crate::crdt_state::dep_is_archived_crdt(d))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
if !archived_deps.is_empty() {
|
if !archived_deps.is_empty() {
|
||||||
slog_warn!(
|
slog_warn!(
|
||||||
|
|||||||
@@ -1,78 +1,6 @@
|
|||||||
//! Dependency resolution helpers — filesystem-backed lookups that don't
|
//! Dependency resolution helpers.
|
||||||
//! require any in-memory CRDT state.
|
|
||||||
//!
|
//!
|
||||||
//! The CRDT-backed equivalents (`check_unmet_deps_crdt`,
|
//! Story 929: all dep-status checks now go through the CRDT-backed
|
||||||
//! `check_archived_deps_crdt`) live in `crate::crdt_state::read`; callers
|
//! equivalents in `crate::crdt_state::read` (`check_unmet_deps_crdt`,
|
||||||
//! that already have a CRDT entry should prefer those. This module exists
|
//! `check_archived_deps_crdt`, `dep_is_archived_crdt`). This module no
|
||||||
//! for the story-creation path, where dependency IDs are known in memory
|
//! longer hosts a filesystem-backed scan of `6_archived/`.
|
||||||
//! before any CRDT entry has been written.
|
|
||||||
use std::fs;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
/// Return `true` if a story with the given numeric ID exists specifically in `6_archived`.
|
|
||||||
fn dep_is_archived(project_root: &Path, dep_number: u32) -> bool {
|
|
||||||
let prefix = format!("{dep_number}_");
|
|
||||||
let exact = dep_number.to_string();
|
|
||||||
let dir = project_root
|
|
||||||
.join(".huskies")
|
|
||||||
.join("work")
|
|
||||||
.join("6_archived");
|
|
||||||
if let Ok(entries) = fs::read_dir(&dir) {
|
|
||||||
for entry in entries.flatten() {
|
|
||||||
let path = entry.path();
|
|
||||||
if path.extension().and_then(|e| e.to_str()) != Some("md") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let Some(stem) = path.file_stem().and_then(|s| s.to_str())
|
|
||||||
&& (stem == exact || stem.starts_with(&prefix))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given an explicit list of dep numbers, return those already in `6_archived`.
|
|
||||||
///
|
|
||||||
/// Used at story-creation time when the dep list is known in memory (before
|
|
||||||
/// the story file has been written), so the caller does not need to parse
|
|
||||||
/// the story.
|
|
||||||
pub fn check_archived_deps_from_list(project_root: &Path, deps: &[u32]) -> Vec<u32> {
|
|
||||||
deps.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|&dep| dep_is_archived(project_root, dep))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn check_archived_deps_from_list_returns_archived_ids() {
|
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
|
||||||
let done = tmp.path().join(".huskies/work/5_done");
|
|
||||||
let archived = tmp.path().join(".huskies/work/6_archived");
|
|
||||||
std::fs::create_dir_all(&done).unwrap();
|
|
||||||
std::fs::create_dir_all(&archived).unwrap();
|
|
||||||
std::fs::write(done.join("10_story_done.md"), "# done\n").unwrap();
|
|
||||||
std::fs::write(archived.join("20_story_old.md"), "# old\n").unwrap();
|
|
||||||
let result = check_archived_deps_from_list(tmp.path(), &[10, 20, 30]);
|
|
||||||
assert_eq!(result, vec![20]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn dep_is_archived_distinguishes_done_from_archived() {
|
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
|
||||||
let done = tmp.path().join(".huskies/work/5_done");
|
|
||||||
let archived = tmp.path().join(".huskies/work/6_archived");
|
|
||||||
std::fs::create_dir_all(&done).unwrap();
|
|
||||||
std::fs::create_dir_all(&archived).unwrap();
|
|
||||||
std::fs::write(done.join("10_story_done.md"), "# done\n").unwrap();
|
|
||||||
std::fs::write(archived.join("20_story_old.md"), "# old\n").unwrap();
|
|
||||||
assert!(!dep_is_archived(tmp.path(), 10));
|
|
||||||
assert!(dep_is_archived(tmp.path(), 20));
|
|
||||||
assert!(!dep_is_archived(tmp.path(), 99));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,5 @@ mod deps;
|
|||||||
mod parser;
|
mod parser;
|
||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
pub use deps::check_archived_deps_from_list;
|
|
||||||
pub use parser::{is_story_frozen_in_store, parse_unchecked_todos, resolve_qa_mode};
|
pub use parser::{is_story_frozen_in_store, parse_unchecked_todos, resolve_qa_mode};
|
||||||
pub use types::QaMode;
|
pub use types::QaMode;
|
||||||
|
|||||||
Reference in New Issue
Block a user