Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions libs/cli/deepagents_cli/local_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ def execute(self, command: str) -> ExecuteResponse: ...
# with `command -v` before use.
#
# The script is built from section functions so each piece can be tested
# independently.
# independently. Independent sections run as parallel background subshells;
# see build_detect_script() for the orchestration logic.
# ---------------------------------------------------------------------------


Expand Down Expand Up @@ -278,7 +279,7 @@ def _section_makefile() -> str:
"""First 20 lines of Makefile (falls back to git root in monorepos).

Returns:
Bash snippet (requires `ROOT` from `_section_project`).
Bash snippet (requires `ROOT` from `_section_project` and `CWD` from header).
"""
return r"""# --- Makefile ---
MK=""
Expand All @@ -300,21 +301,41 @@ def _section_makefile() -> str:
def build_detect_script() -> str:
"""Concatenate all section functions into the full detection script.

Independent sections run as parallel background jobs writing to temp
files, then results are concatenated in the original display order.
The header (CWD / IN_GIT) and project section (sets ROOT) run first
because later sections depend on their variables.

Returns:
Complete bash heredoc ready for `backend.execute()`.
"""
sections = [
_section_header(),
_section_project(),
_section_package_managers(),
_section_runtimes(),
_section_git(),
_section_test_command(),
_section_files(),
_section_tree(),
_section_makefile(),
# Header + project run synchronously (set CWD, IN_GIT, ROOT for others)
serial_prefix = f"{_section_header()}\n{_section_project()}"

# These sections are independent — run them in parallel.
# Subshells inherit parent variables (IN_GIT, ROOT, CWD) via fork.
# Individual exit codes are not tracked because sections legitimately
# exit non-zero when they have nothing to report (e.g. no runtimes).
parallel_sections = [
("02_pkgmgr", _section_package_managers()),
("03_runtimes", _section_runtimes()),
("04_git", _section_git()),
("05_testcmd", _section_test_command()),
("06_files", _section_files()),
("07_tree", _section_tree()),
("08_makefile", _section_makefile()),
]
body = "\n".join(sections)

# Build parallel wrapper: each section runs in a subshell writing to a
# temp file. Stderr is captured per-section to prevent noise leakage.
parallel_setup = "_DCT=$(mktemp -d) || exit 1\ntrap 'rm -rf \"$_DCT\"' EXIT"
parallel_block = "\n".join(
f'(\n{body}\n) > "$_DCT/{name}" 2>"$_DCT/{name}.err" &'
for name, body in parallel_sections
)
cat_line = "cat " + " ".join(f'"$_DCT/{name}"' for name, _ in parallel_sections)

body = f"{serial_prefix}\n{parallel_setup}\n{parallel_block}\nwait\n{cat_line}"
return f"bash <<'__DETECT_CONTEXT_EOF__'\n{body}\n__DETECT_CONTEXT_EOF__\n"


Expand Down