Overview
Magpie automatically classifies changed files as docs-only or code to determine whether to run CI checks (lint + test). This optimization prevents unnecessary test runs when only documentation, images, or config files are modified.
Classification Rules
Docs-Only Extensions
Files with these extensions are considered docs-only:
const DOCS_ONLY_EXTENSIONS : & [ & str ] = & [
".md" , ".txt" , ".rst" , ".adoc" , // Documentation
".png" , ".jpg" , ".jpeg" , ".gif" , ".svg" , ".ico" , // Images
".json" , // Config-only JSON (e.g., .vscode settings)
".yml" , ".yaml" , // CI config changes (arguably need CI, but typically safe)
];
Docs-Only Paths
Files with these exact names (case-insensitive) are always considered docs-only:
const DOCS_ONLY_PATHS : & [ & str ] = & [
"LICENSE" ,
"CHANGELOG" ,
"CHANGES" ,
"AUTHORS" ,
"CONTRIBUTORS" ,
];
Classification Logic
The needs_ci() function checks every changed file:
pub fn needs_ci ( changed_files : & [ String ]) -> bool {
if changed_files . is_empty () {
return false ; // nothing changed, nothing to test
}
for file in changed_files {
let lower = file . to_lowercase ();
let basename = lower . rsplit ( '/' ) . next () . unwrap_or ( & lower );
// Check well-known docs-only filenames
if DOCS_ONLY_PATHS . iter () . any ( | p | basename . eq_ignore_ascii_case ( p )) {
continue ;
}
// Check extension
let is_docs = DOCS_ONLY_EXTENSIONS . iter () . any ( | ext | lower . ends_with ( ext ));
if ! is_docs {
return true ; // at least one code file → run CI
}
}
false // all files are docs-only
}
The classification is conservative : if any file doesn’t match the docs-only rules, CI runs. Only when all changed files are docs-only is CI skipped.
Pipeline Integration
Diff Classification
After the agent completes, the pipeline checks which files changed:
let changed = git . changed_files () . await . unwrap_or_default ();
let run_ci = if changed . is_empty () {
info! ( "no files changed — skipping CI" );
false
} else {
let needs = needs_ci ( & changed );
if needs {
info! (
file_count = changed . len (),
"code changes detected → running CI"
);
} else {
info! (
file_count = changed . len (),
files = ? changed ,
"docs-only changes → skipping CI"
);
}
needs
};
CI Bypass
When run_ci is false, the pipeline:
Skips lint + test execution
Marks ci_passed = true (considered a pass)
Commits and opens a PR without running checks
if run_ci {
// Run lint + test with retry loop...
} else {
// Docs-only: skip CI, mark as passed
ci_passed = true ;
info! ( "CI skipped — docs-only change" );
}
Examples
Docs-Only Changes (CI Skipped)
README update
License + images
Changelog
Changed files: README.md
docs/getting-started.md
Result: CI skipped ✅Log output: docs-only changes → skipping CI
CI skipped — docs-only change
Changed files: LICENSE
assets/logo.png
assets/screenshot.jpg
Result: CI skipped ✅Changed files: Result: CI skipped ✅
Code Changes (CI Runs)
Mixed changes
Config files
Test files
Changed files: README.md
src/main.rs ← code file
Result: CI runs ❌Reason: At least one code file changed. Changed files: Cargo.toml
rust-toolchain.toml
Result: CI runs ❌Reason: .toml is NOT in DOCS_ONLY_EXTENSIONS. Changed files: tests/integration_test.rs
Result: CI runs ❌Reason: .rs is a code extension.
Edge Cases
YAML/JSON Files
CI config files (.yml, .yaml, .json) are classified as docs-only:
.github/workflows/ci.yml → docs-only ✅
package.json → docs-only ✅
tsconfig.json → docs-only ✅
This is a pragmatic tradeoff. CI config changes should be tested, but in practice:
Most CI tweaks are low-risk (comment changes, version bumps)
Running tests for CI config changes creates chicken-and-egg problems
The config itself is tested when the PR CI runs
If you need strict validation, remove .yml/.yaml/.json from DOCS_ONLY_EXTENSIONS.
No Changes
If the agent makes no file changes (e.g., task was already complete), CI is skipped:
if changed . is_empty () {
info! ( "no files changed — skipping CI" );
false
}
Empty Repo
If git.changed_files() fails (e.g., repo is not a git repository), changed is empty and CI is skipped.
Time Savings
Typical CI execution times:
Rust (cargo test) 2-5 minutes Incremental builds + test suite
Node.js (npm test) 1-3 minutes Jest/Vitest suite
Python (pytest) 30s-2 minutes Test discovery + execution
Docs-only classification saves:
2-5 minutes per docs-only PR
Reduced sandbox usage (Daytona billing)
Faster PR turnaround for documentation updates
PR Labels
The pipeline does not currently add a docs-only label to PRs when CI is skipped. The PR body includes a “CI: passed” badge regardless:
let body = format! (
"## Summary \n\n {commit_msg} \n\n ## Changed files \n\n {} \n\n ## Context \n\n > {task} \n\n **CI:** {}" ,
file_list ,
if ci_passed { "passed" } else { "FAILED" },
);
To distinguish skipped CI from actual test runs, check the pipeline logs: grep -i "CI skipped" ~/.magpie/logs/pipeline.log
Customization
Adding Extensions
To classify additional file types as docs-only, edit DOCS_ONLY_EXTENSIONS:
const DOCS_ONLY_EXTENSIONS : & [ & str ] = & [
".md" , ".txt" , ".rst" , ".adoc" ,
".png" , ".jpg" , ".jpeg" , ".gif" , ".svg" , ".ico" ,
".json" , ".yml" , ".yaml" ,
".pdf" , ".docx" , // Add custom extensions here
];
Removing Extensions
To force CI for YAML/JSON files, remove them from the list:
const DOCS_ONLY_EXTENSIONS : & [ & str ] = & [
".md" , ".txt" , ".rst" , ".adoc" ,
".png" , ".jpg" , ".jpeg" , ".gif" , ".svg" , ".ico" ,
// Removed: ".json", ".yml", ".yaml"
];
Path-Based Rules
To skip CI for specific directories (e.g., docs/), add path prefix checks:
if file . starts_with ( "docs/" ) || file . starts_with ( "examples/" ) {
continue ; // skip this file
}
Testing
cargo test -p magpie-core -- needs_ci
Add test cases for your custom rules:
#[test]
fn test_needs_ci_pdf_is_docs_only () {
let files = vec! [ "guide.pdf" . to_string ()];
assert! ( ! needs_ci ( & files )); // should skip CI
}
#[test]
fn test_needs_ci_mixed_with_toml () {
let files = vec! [ "README.md" . to_string (), "Cargo.toml" . to_string ()];
assert! ( needs_ci ( & files )); // .toml is code
}
Pipeline Flow See where CI classification fits in the pipeline
CI Loop Understand the retry behavior when CI runs