Skip to content

Commit fc80efb

Browse files
docs(automaton): document fixer, recipe, and path behavior
1 parent 54a78a2 commit fc80efb

3 files changed

Lines changed: 56 additions & 22 deletions

File tree

‎robot-repo-automaton/src/fixer.rs‎

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,23 @@ const BINARY_EXTENSIONS: &[&str] = &[
7272
];
7373

7474
impl Fixer {
75-
/// Create a new fixer for a repository
75+
/// Create a fixer rooted at `repo_path`.
76+
///
77+
/// When `dry_run` is true, policy and boundary checks still run, but eligible
78+
/// fixes are only reported and no files or commits are changed.
7679
pub fn new(repo_path: PathBuf, dry_run: bool) -> Self {
7780
// Keep one root representation for ignore rules and index paths.
7881
// Invalid roots are retained so apply's boundary check reports the error.
7982
let repo_path = repo_path.canonicalize().unwrap_or(repo_path);
8083
Fixer { repo_path, dry_run }
8184
}
8285

83-
/// Apply a fix for a detected issue
86+
/// Apply a fix after checking the exclusion registry and repository boundary.
87+
///
88+
/// Policy rejections and action-level non-applicability are represented in
89+
/// [`FixResult`]; failures that prevent the operation itself may be returned
90+
/// as errors. In dry-run mode, eligible fixes report their intended changes
91+
/// without writing them.
8492
pub fn apply(&self, issue: &DetectedIssue, fix: &Fix) -> Result<FixResult> {
8593
// EXCLUSION REGISTRY GUARD: refuse the write if the target repo,
8694
// origin, or target path is on the estate-wide denylist. In dry-run
@@ -379,10 +387,10 @@ impl Fixer {
379387
})
380388
}
381389

382-
/// Modify a file with safety checks and rollback support
390+
/// Modify a text file without exposing partially written content.
383391
///
384-
/// Reads the modification specification from the fix, applies it to the file,
385-
/// and rolls back if the modification produces invalid content.
392+
/// Binary files are rejected. For supported structured formats, the complete
393+
/// result is parsed before the original file is atomically replaced.
386394
fn apply_modify(
387395
&self,
388396
target_path: &Path,
@@ -502,13 +510,12 @@ impl Fixer {
502510
})
503511
}
504512

505-
/// Create a file with template expansion
513+
/// Create a file from explicit fallback content or a built-in template.
506514
///
507-
/// Supports template variables:
508-
/// - `gitbot-fleet` - Repository name
509-
/// - `hyperpolymath` - Repository owner
510-
/// - `{{LICENSE}}` - License identifier
511-
/// - `{{YEAR}}` - Current year
515+
/// Existing or ignored targets are not written, and publication uses
516+
/// no-clobber semantics. Template expansion replaces the literal
517+
/// `gitbot-fleet` name and the `{{LICENSE}}`, `{{YEAR}}`, `{{AUTHOR}}` and
518+
/// `{{EMAIL}}` placeholders.
512519
fn apply_create(
513520
&self,
514521
target_path: &Path,
@@ -604,7 +611,9 @@ impl Fixer {
604611
})
605612
}
606613

607-
/// Disable a workflow (rename to .disabled)
614+
/// Disable a file by renaming it with a `yml.disabled` extension.
615+
///
616+
/// The rename does not replace an existing destination.
608617
fn apply_disable(
609618
&self,
610619
target_path: &Path,
@@ -671,7 +680,9 @@ impl Fixer {
671680
false
672681
}
673682

674-
/// Get template content for a file creation
683+
/// Return explicit fallback content or the built-in template for `target`.
684+
///
685+
/// Targets without either source return an empty string.
675686
fn get_template_content(&self, target: &str, fix: &Fix) -> String {
676687
// If the fix has explicit content in the fallback field, use it
677688
if let Some(ref fallback) = fix.fallback {
@@ -687,7 +698,7 @@ impl Fixer {
687698
}
688699
}
689700

690-
/// Expand template variables in content
701+
/// Expand the repository-specific and fixed metadata placeholders in a template.
691702
fn expand_template(&self, content: &str) -> String {
692703
let repo_name = self
693704
.repo_path
@@ -705,7 +716,11 @@ impl Fixer {
705716
.replace("{{EMAIL}}", "j.d.a.jewell@open.ac.uk")
706717
}
707718

708-
/// Commit changes to the repository
719+
/// Stage the listed paths and commit the resulting repository index.
720+
///
721+
/// Deleted paths are removed from the index, while paths outside the canonical
722+
/// repository root are ignored. Dry-run mode performs the exclusion check but
723+
/// neither stages nor commits changes.
709724
pub fn commit(&self, message: &str, files: &[PathBuf]) -> Result<()> {
710725
// EXCLUSION REGISTRY GUARD: a commit is a write action even though
711726
// apply() has already checked each file individually, because some
@@ -761,7 +776,11 @@ impl Fixer {
761776
Ok(())
762777
}
763778

764-
/// Apply multiple fixes and commit
779+
/// Apply each issue/fix pair and commit files reported as modified.
780+
///
781+
/// No commit is created when nothing changes or dry-run mode is enabled. The
782+
/// separate `issues` slice is retained for caller compatibility and is not
783+
/// consulted; each tuple in `fixes` supplies its associated issue.
765784
pub fn apply_and_commit(
766785
&self,
767786
_issues: &[DetectedIssue],
@@ -889,12 +908,14 @@ fn resolve_from_existing_ancestor(path: &Path) -> Result<PathBuf> {
889908
}
890909
}
891910

911+
/// Return whether the character at `index` follows an odd run of backslashes.
892912
fn is_escaped(value: &str, index: usize) -> bool {
893913
value[..index].bytes().rev()
894914
.take_while(|byte| *byte == b'\\')
895915
.count() % 2 == 1
896916
}
897917

918+
/// Replace each escaped colon with a literal colon, preserving other backslashes.
898919
fn unescape_colons(value: &str) -> String {
899920
let mut output = String::with_capacity(value.len());
900921
let mut characters = value.chars().peekable();

‎robot-repo-automaton/src/hypatia.rs‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,11 @@ impl CicdHyperAClient {
237237
self.load_local_ruleset(ruleset_id)
238238
}
239239

240-
/// Load rules from local verisimdb-data recipes directory.
240+
/// Load a local ruleset from the first configured recipes directory that exists.
241+
///
242+
/// Data-root environment variables take precedence over `REPOS_BASE`, the
243+
/// default estate directory and legacy locations. Built-in RSR rules are used
244+
/// when that selected directory yields no valid recipes.
241245
fn load_local_ruleset(&self, ruleset_id: &str) -> crate::Result<Ruleset> {
242246
let mut recipes_dirs = Vec::new();
243247
for key in ["HYPATIA_DATA", "VERISIMDB_DATA"] {
@@ -259,6 +263,10 @@ impl CicdHyperAClient {
259263
self.load_recipes_from(ruleset_id, &recipes_dirs)
260264
}
261265

266+
/// Build a ruleset from JSON recipes in the first existing candidate directory.
267+
///
268+
/// Unreadable entries and malformed or incomplete recipes are skipped. If no
269+
/// rules remain, the built-in RSR rules are returned instead.
262270
fn load_recipes_from(&self, ruleset_id: &str, recipes_dirs: &[PathBuf]) -> crate::Result<Ruleset> {
263271
let recipes_dir = recipes_dirs.iter().find(|d| d.is_dir());
264272

@@ -615,7 +623,10 @@ impl CicdHyperAClient {
615623
}
616624
}
617625

618-
/// Convert a verisimdb-data recipe JSON to a Rule.
626+
/// Convert a verisimdb-data recipe into a rule.
627+
///
628+
/// A string `id` and either `file_glob` or `pattern` are required; recipes
629+
/// missing those fields are ignored.
619630
fn recipe_to_rule(recipe: &serde_json::Value) -> Option<Rule> {
620631
let id = recipe.get("id")?.as_str()?.to_string();
621632
let name = recipe.get("name").and_then(|v| v.as_str()).unwrap_or(&id).to_string();

‎robot-repo-automaton/src/main.rs‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -744,9 +744,9 @@ fn cmd_catalog(path: &Path, severity_filter: Option<&str>) -> anyhow::Result<()>
744744

745745
/// Base directory holding local repo checkouts.
746746
///
747-
/// Override with `REPOS_BASE`; otherwise defaults to the canonical estate tree.
748-
/// Legacy checkout locations can also be selected through `REPOS_BASE`; the
749-
/// former literal `/var$REPOS_DIR` path did not expand a shell variable in Rust.
747+
/// Uses a non-empty `REPOS_BASE` value when set; otherwise returns
748+
/// `$HOME/developer/hyper-repos`, relative to the current directory when no home
749+
/// directory is available.
750750
fn repos_base() -> PathBuf {
751751
if let Ok(base) = std::env::var("REPOS_BASE") {
752752
if !base.is_empty() {
@@ -761,7 +761,9 @@ fn repos_base() -> PathBuf {
761761

762762
/// Resolve a repo argument to a local path.
763763
///
764-
/// Accepts either a local path or a GitHub owner/name format.
764+
/// Existing paths are returned directly. Other values are resolved relative to
765+
/// [`repos_base`], including nested `owner/name` paths, and an error lists the
766+
/// attempted location when neither exists.
765767
fn resolve_repo_path(repo: &str) -> anyhow::Result<PathBuf> {
766768
let path = PathBuf::from(repo);
767769
if path.exists() {

0 commit comments

Comments
 (0)