Skip to content

Commit preflight

commit_preflight

GitPreflightTool — show working tree status for agent decision-making.

GitPreflightTool

Bases: AXMTool

Report working tree changes so the agent can plan commits.

Registered as git_preflight via axm.tools entry point.

Source code in packages/axm-git/src/axm_git/tools/commit_preflight.py
Python
class GitPreflightTool(AXMTool):
    """Report working tree changes so the agent can plan commits.

    Registered as ``git_preflight`` via axm.tools entry point.
    """

    @property
    def name(self) -> str:
        """Tool name used for MCP registration."""
        return "git_preflight"

    def execute(
        self,
        *,
        path: str = ".",
        diff_lines: int = 200,
        **kwargs: object,
    ) -> ToolResult:
        """Show current working tree status and diff summary.

        Args:
            path: Project root (required).
            diff_lines: Max diff lines to include (default 200, 0 to
                disable).

        Returns:
            ToolResult with file list, statuses, diff stats, and diff content.
        """
        resolved = Path(path).resolve()
        max_diff_lines = diff_lines

        try:
            git_root = find_git_root(resolved)

            if git_root is not None:
                # Scope to subdirectory when inside a workspace
                rel = resolved.relative_to(git_root.resolve())
                pathspec = ["--", str(rel)] if str(rel) != "." else []
                cwd = git_root
            else:
                # Not a repo (or find_git_root failed) — fall through with
                # resolved so run_git triggers not_a_repo_error naturally.
                pathspec = []
                cwd = resolved

            # git status --porcelain [-- rel_path]
            status = run_git(["status", "--porcelain", *pathspec], cwd)
            if status.returncode != 0:
                return not_a_repo_error(status.stderr, resolved)

            files = []
            for line in status.stdout.splitlines():
                if len(line) < _MIN_STATUS_LINE_LEN:
                    continue
                code = line[:2].strip()
                filepath = line[3:]
                files.append({"path": filepath, "status": code})

            # git diff --stat [-- rel_path] (only when dirty)
            diff_stat_out = ""
            if files:
                diff_stat = run_git(["diff", "--stat", *pathspec], cwd)
                diff_stat_out = diff_stat.stdout.strip()

            # git diff -U2 [-- rel_path] (reduced context, truncated to max)
            diff_content = ""
            diff_truncated = False
            if max_diff_lines > 0:
                diff_result = run_git(["diff", "-U2", *pathspec], cwd)
                lines = diff_result.stdout.splitlines()
                if len(lines) > max_diff_lines:
                    diff_content = "\n".join(lines[:max_diff_lines])
                    diff_truncated = True
                else:
                    diff_content = diff_result.stdout.strip()
        except subprocess.TimeoutExpired as exc:
            return timeout_error_result(exc)

        text = render_text(
            files=files,
            diff_stat=diff_stat_out,
            diff=diff_content,
            diff_truncated=diff_truncated,
            max_diff_lines=max_diff_lines,
        )

        return ToolResult(
            success=True,
            data={
                "files": files,
                "file_count": len(files),
                "diff_stat": diff_stat_out,
                "diff": diff_content,
                "diff_truncated": diff_truncated,
                "clean": len(files) == 0,
            },
            text=text,
        )
name property

Tool name used for MCP registration.

execute(*, path='.', diff_lines=200, **kwargs)

Show current working tree status and diff summary.

Parameters:

Name Type Description Default
path str

Project root (required).

'.'
diff_lines int

Max diff lines to include (default 200, 0 to disable).

200

Returns:

Type Description
ToolResult

ToolResult with file list, statuses, diff stats, and diff content.

Source code in packages/axm-git/src/axm_git/tools/commit_preflight.py
Python
def execute(
    self,
    *,
    path: str = ".",
    diff_lines: int = 200,
    **kwargs: object,
) -> ToolResult:
    """Show current working tree status and diff summary.

    Args:
        path: Project root (required).
        diff_lines: Max diff lines to include (default 200, 0 to
            disable).

    Returns:
        ToolResult with file list, statuses, diff stats, and diff content.
    """
    resolved = Path(path).resolve()
    max_diff_lines = diff_lines

    try:
        git_root = find_git_root(resolved)

        if git_root is not None:
            # Scope to subdirectory when inside a workspace
            rel = resolved.relative_to(git_root.resolve())
            pathspec = ["--", str(rel)] if str(rel) != "." else []
            cwd = git_root
        else:
            # Not a repo (or find_git_root failed) — fall through with
            # resolved so run_git triggers not_a_repo_error naturally.
            pathspec = []
            cwd = resolved

        # git status --porcelain [-- rel_path]
        status = run_git(["status", "--porcelain", *pathspec], cwd)
        if status.returncode != 0:
            return not_a_repo_error(status.stderr, resolved)

        files = []
        for line in status.stdout.splitlines():
            if len(line) < _MIN_STATUS_LINE_LEN:
                continue
            code = line[:2].strip()
            filepath = line[3:]
            files.append({"path": filepath, "status": code})

        # git diff --stat [-- rel_path] (only when dirty)
        diff_stat_out = ""
        if files:
            diff_stat = run_git(["diff", "--stat", *pathspec], cwd)
            diff_stat_out = diff_stat.stdout.strip()

        # git diff -U2 [-- rel_path] (reduced context, truncated to max)
        diff_content = ""
        diff_truncated = False
        if max_diff_lines > 0:
            diff_result = run_git(["diff", "-U2", *pathspec], cwd)
            lines = diff_result.stdout.splitlines()
            if len(lines) > max_diff_lines:
                diff_content = "\n".join(lines[:max_diff_lines])
                diff_truncated = True
            else:
                diff_content = diff_result.stdout.strip()
    except subprocess.TimeoutExpired as exc:
        return timeout_error_result(exc)

    text = render_text(
        files=files,
        diff_stat=diff_stat_out,
        diff=diff_content,
        diff_truncated=diff_truncated,
        max_diff_lines=max_diff_lines,
    )

    return ToolResult(
        success=True,
        data={
            "files": files,
            "file_count": len(files),
            "diff_stat": diff_stat_out,
            "diff": diff_content,
            "diff_truncated": diff_truncated,
            "clean": len(files) == 0,
        },
        text=text,
    )

render_text(*, files, diff_stat, diff, diff_truncated, max_diff_lines)

Render a compact text summary of preflight results.

Source code in packages/axm-git/src/axm_git/tools/commit_preflight.py
Python
def render_text(
    *,
    files: list[dict[str, str]],
    diff_stat: str,
    diff: str,
    diff_truncated: bool,
    max_diff_lines: int,
) -> str:
    """Render a compact text summary of preflight results."""
    if not files:
        return "git_preflight | clean"

    parts: list[str] = [f"git_preflight | {len(files)} files · dirty", ""]

    for f in files:
        status = f["status"]
        parts.append(f"{status:<{_STATUS_PAD}}{f['path']}")

    if diff_stat:
        parts.append("")
        parts.append(diff_stat)

    if diff:
        parts.append("")
        parts.append(diff)

    if diff_truncated:
        parts.append(f"[diff truncated at {max_diff_lines} lines]")

    return "\n".join(parts)