"""05 §5.8 / U-035: path-based classification of whether a self-target change touches "core engine" territory -- `.claude/agents/*.md` identity files, Graph Engine scheduler/executor source (`src/*.py`), or 01-M's own schema (`src/schema.py`, itself a `src/*.py` file so already covered). Deliberately NOT the Validator's existing 3-perspective `judge_root_cause` (root_cause_trace.py) -- that produces planning|coder|skill_defect from content-level judgment about WHY a bug happened; this produces core_engine from a purely mechanical WHERE-did-the-change-land rule, independent of whether anything went wrong. The two are orthogonal and only combined at the call site (scheduler.py), never merged into one function. Scope note: `frontend/` is deliberately excluded (05 §5.8 names the identity files + engine source + schema, not the console UI -- a frontend/app.js bug is much lower blast radius and doesn't need blue-green). `supervisor/` IS included -- Deploy Supervisor modifying its own isolation/rollback logic is at least as self-referentially risky as the engine it's protecting. """ import posixpath _CORE_ENGINE_PREFIXES = ("src/", "supervisor/", ".claude/agents/") def classify_core_engine_change(changed_files): """changed_files: iterable of repo-relative paths (posix-style, as produced by `git diff --name-only` / `diff --git a/X b/Y` headers). Returns True if ANY path falls under a core-engine prefix.""" for path in changed_files: normalized = posixpath.normpath(path.replace("\\", "/")) if normalized.startswith(_CORE_ENGINE_PREFIXES) or any( normalized == prefix.rstrip("/") for prefix in _CORE_ENGINE_PREFIXES ): return True return False def parse_changed_files_from_diff(diff_text): """Extracts repo-relative changed file paths from unified `git diff` output (`diff --git a/X b/Y` header lines) -- avoids requiring a second git subprocess call when the diff text is already on hand (e.g. as the code_diff artifact's content).""" changed = [] for line in (diff_text or "").splitlines(): if line.startswith("diff --git a/"): rest = line[len("diff --git a/"):] # " b/" -- split on the last " b/" occurrence # since a path itself could theoretically contain " b/". marker = rest.rfind(" b/") if marker != -1: changed.append(rest[:marker]) return changed