wip(929): stage 5 — drop FS-based dep checks and qa-mode parser from io/story_metadata
Migrate the last three callers of the FS-scanning dependency helpers to the CRDT-direct equivalents and delete the dead helpers: - agents/pool/auto_assign/story_checks.rs: has_unmet_dependencies and check_archived_dependencies now wrap check_unmet_deps_crdt / check_archived_deps_crdt directly. Tests rewritten to seed the CRDT. - http/mcp/story_tools/story/update.rs: bug-503 archived-dep warning now reads from CRDT instead of scanning 6_archived. - agents/pool/pipeline/advance/helpers.rs: resolve_qa_mode_from_store is CRDT-only (the FS fallback for content-store-empty stories is gone). - io/story_metadata/parser.rs: resolve_qa_mode_from_content removed. - io/story_metadata/deps.rs: check_unmet_deps and dep_is_done deleted, along with the unused check_unmet_deps_from_list helper. - io/story_metadata/mod.rs: re-exports trimmed accordingly. check_archived_deps_from_list survives because story-creation still calls it before the CRDT entry exists (used from story_tools/story/create.rs). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,29 +9,6 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Return `true` if a story with the given numeric ID exists in `5_done` or `6_archived`.
|
||||
fn dep_is_done(project_root: &Path, dep_number: u32) -> bool {
|
||||
let prefix = format!("{dep_number}_");
|
||||
let exact = dep_number.to_string();
|
||||
for stage in &["5_done", "6_archived"] {
|
||||
let dir = project_root.join(".huskies").join("work").join(stage);
|
||||
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
|
||||
}
|
||||
|
||||
/// 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}_");
|
||||
@@ -56,19 +33,6 @@ fn dep_is_archived(project_root: &Path, dep_number: u32) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Given an explicit list of dep numbers, return those that have NOT reached
|
||||
/// `5_done` or `6_archived`.
|
||||
///
|
||||
/// Used by callers that have the dep list in memory (e.g. story update at
|
||||
/// promotion time) and want a filesystem fact rather than an in-memory CRDT
|
||||
/// state which may be stale during transitions.
|
||||
pub fn check_unmet_deps_from_list(project_root: &Path, deps: &[u32]) -> Vec<u32> {
|
||||
deps.iter()
|
||||
.copied()
|
||||
.filter(|&dep| !dep_is_done(project_root, dep))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// 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
|
||||
@@ -81,80 +45,10 @@ pub fn check_archived_deps_from_list(project_root: &Path, deps: &[u32]) -> Vec<u
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Filesystem-backed unmet-dep check for a story file in `<stage_dir>/`.
|
||||
///
|
||||
/// Reads the story's `depends_on` list from its YAML front matter and returns
|
||||
/// the numeric deps still pending (not yet in `5_done` or `6_archived`). This
|
||||
/// is the legacy API used by the auto-assigner when the CRDT layer is not yet
|
||||
/// initialised; CRDT-aware callers should prefer `check_unmet_deps_crdt`.
|
||||
pub fn check_unmet_deps(project_root: &Path, stage_dir: &str, story_id: &str) -> Vec<u32> {
|
||||
let path = project_root
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
let contents = match fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
let deps = match crate::db::yaml_legacy::parse_front_matter(&contents)
|
||||
.ok()
|
||||
.and_then(|m| m.depends_on)
|
||||
{
|
||||
Some(d) => d,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
check_unmet_deps_from_list(project_root, &deps)
|
||||
}
|
||||
|
||||
/// Filesystem-backed archived-dep check for a story file in `<stage_dir>/`.
|
||||
///
|
||||
/// Reads the story's `depends_on` list from its YAML front matter and returns
|
||||
/// the numeric deps that satisfied via `6_archived` rather than `5_done`.
|
||||
pub fn check_archived_deps(project_root: &Path, stage_dir: &str, story_id: &str) -> Vec<u32> {
|
||||
let path = project_root
|
||||
.join(".huskies")
|
||||
.join("work")
|
||||
.join(stage_dir)
|
||||
.join(format!("{story_id}.md"));
|
||||
let contents = match fs::read_to_string(&path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
let deps = match crate::db::yaml_legacy::parse_front_matter(&contents)
|
||||
.ok()
|
||||
.and_then(|m| m.depends_on)
|
||||
{
|
||||
Some(d) => d,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
check_archived_deps_from_list(project_root, &deps)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn dep_is_done_finds_story_in_archived() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let archived = tmp.path().join(".huskies/work/6_archived");
|
||||
std::fs::create_dir_all(&archived).unwrap();
|
||||
std::fs::write(archived.join("100_story_old.md"), "# old\n").unwrap();
|
||||
assert!(dep_is_done(tmp.path(), 100));
|
||||
assert!(!dep_is_done(tmp.path(), 101));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_unmet_deps_from_list_returns_unmet_numbers() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let done = tmp.path().join(".huskies/work/5_done");
|
||||
std::fs::create_dir_all(&done).unwrap();
|
||||
std::fs::write(done.join("477_story_dep.md"), "# dep\n").unwrap();
|
||||
let unmet = check_unmet_deps_from_list(tmp.path(), &[477, 478]);
|
||||
assert_eq!(unmet, vec![478]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_archived_deps_from_list_returns_archived_ids() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
@@ -10,8 +10,6 @@ mod deps;
|
||||
mod parser;
|
||||
mod types;
|
||||
|
||||
pub use deps::{check_archived_deps, check_archived_deps_from_list, check_unmet_deps};
|
||||
pub use parser::{
|
||||
is_story_frozen_in_store, parse_unchecked_todos, resolve_qa_mode, resolve_qa_mode_from_content,
|
||||
};
|
||||
pub use deps::check_archived_deps_from_list;
|
||||
pub use parser::{is_story_frozen_in_store, parse_unchecked_todos, resolve_qa_mode};
|
||||
pub use types::QaMode;
|
||||
|
||||
@@ -29,16 +29,6 @@ pub fn resolve_qa_mode(story_id: &str, default: QaMode) -> QaMode {
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
/// Resolve the effective QA mode by parsing legacy YAML front matter from a
|
||||
/// markdown body. Used during one-time fallbacks when the CRDT register isn't
|
||||
/// set; new code should always read `qa_mode` from the CRDT.
|
||||
pub fn resolve_qa_mode_from_content(_story_id: &str, content: &str, default: QaMode) -> QaMode {
|
||||
crate::db::yaml_legacy::parse_front_matter(content)
|
||||
.ok()
|
||||
.and_then(|m| m.qa)
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
/// Return `true` if the story is in the `Frozen` pipeline stage.
|
||||
///
|
||||
/// Checks the typed CRDT stage via `read_typed`. Used by the pipeline advance
|
||||
|
||||
Reference in New Issue
Block a user