Skip to content

feat(map-efficient): #303 Slice 5a — auto-worktree wave-loop, sequential dispatch (flag-gated)#306

Merged
azalio merged 7 commits into
mainfrom
feat-303-slice5-concurrent-dispatch
Jun 29, 2026
Merged

feat(map-efficient): #303 Slice 5a — auto-worktree wave-loop, sequential dispatch (flag-gated)#306
azalio merged 7 commits into
mainfrom
feat-303-slice5-concurrent-dispatch

Conversation

@azalio

@azalio azalio commented Jun 29, 2026

Copy link
Copy Markdown
Owner

Summary

#303 Slice 5a — wire worktree.isolation in {auto,required} so the existing wave-loop creates per-color-group git worktrees and accepts each wave atomically via merge_wave_worktrees, while dispatching Actors sequentially. This exercises real worktree creation + atomic whole-wave merge + whole-wave rollback at full fidelity without introducing concurrency.

Behavior is byte-identical to today under the default worktree.isolation=off (the legacy sequential walker runs). Concurrent N-Task fanout (Slice 5b) and the default flips (Slice 6) are explicitly out of scope and not present.

Implemented per the llm-council deep verdict (conv d5ff5181, tracked on #303), which recommended splitting the original Slice 5 into 5a (this PR) / 5b.

What changed

ST Change
ST-001 concurrency_allowed derived field (pure function of select_execution_strategy) + select_execution_strategy CLI handler. Council Q1: the coordinator owns the "is concurrency permitted" signal.
ST-002 get_wave_step returns structured dispatch_mode / isolation_active / stable reason code, replacing the bare WAVE_CONCURRENCY_ENABLED echo. concurrency_enabled kept as a back-compat alias. dispatch_mode is always "sequential" in 5a (HC-3).
ST-003 concurrency_readyread-only coordinator-owned wave-worktree readiness check (path registered + HEAD==base_sha + clean), reusing existing _wt_* helpers. No skill-reported flag crosses the LLM trust boundary (council Q1).
ST-004 Skill + reference docs: document the 5a sequential-through-worktree flow; relabel the concurrent N-Task example as "Slice 5b only"; scope wave anti-patterns to 5b. Codex tree kept provider-neutral.
ST-005 Dormant max_actors (default 4, clamp [1,8], bool-excluded) + retry_degraded_once (default false) config + clamp_max_actors helper. Read by no 5a execution path.
ST-006 New parallel-aware harness tests/test_wave_parallel_harness.py for the council Q6 merge landmines: orphan-cleanup-on-failure, generated-file conflict → whole-wave rollback (attribution names both subtasks), post-wave-gate failure → rollback inside the transaction, no-op classification, deterministic sorted-id merge order.
ST-007 HC-1 default-config behavior-neutrality proof test — non-tautological (seeds a width≥2 wave so the isolation gate, not an empty plan, keeps dispatch sequential).

Hard constraints (all proven by tests)

  • HC-1 behavior-neutral default + dedicated proof test (ST-007).
  • HC-2 single-source render; make check-render clean.
  • HC-3 dispatch_mode stays "sequential"; WAVE_CONCURRENCY_ENABLED stays False.
  • HC-4 GSD-style fresh-context-per-task: worktree isolation for autonomous execution #284 atomic whole-wave invariant preserved — never merge a successful subset (ST-006 proves rollback on conflict + post-wave-gate failure).
  • HC-5 every surfaced error fixed; full make check green.

Verification

Follow-ups (not in this PR)

  • 5b: real concurrent N-Task fanout (dispatch_mode == 'concurrent'), record_dispatch_actual self-audit, max_actors batch-split wiring — after this slice's matrix is green.
  • 6: flip wave_mode / worktree.isolation defaults after a soak. No shadow-mode rollout.

Part of #303.

Summary by CodeRabbit

  • New Features

    • Added a new readiness check for wave-based worktree coordination.
    • Exposed a command to inspect execution strategy and concurrency status.
    • Added configurable limits for actor parallelism and retry behavior.
  • Documentation

    • Clarified wave execution behavior, including when dispatch is sequential versus concurrent.
    • Updated guidance to better describe worktree isolation, verification, and atomic merge behavior.
  • Tests

    • Expanded coverage for execution strategy, worktree readiness, merge behavior, and config parsing.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements the Slice 5a wave concurrency infrastructure: adds a read-only concurrency_ready() gate in map_step_runner, extends get_wave_step with structured dispatch_mode/isolation_active/reason fields and WAVE_REASON_* constants, adds concurrency_allowed to select_execution_strategy, introduces dormant max_actors/retry_degraded_once config fields, clarifies Slice 5a vs 5b sequencing in all skill documentation copies, and adds regression tests across all new behavior.

Changes

Slice 5a Wave Dispatch Signals, concurrency_ready Gate, and Dormant Config Fields

Layer / File(s) Summary
concurrency_ready gate: reason codes + implementation + CLI wiring
src/mapify_cli/templates_src/map/scripts/map_step_runner.py.jinja, src/mapify_cli/templates/map/scripts/map_step_runner.py, .map/scripts/map_step_runner.py
Adds _WT_REASON_NO_RECORD/PATH_MISSING/NOT_REGISTERED/HEAD_MISMATCH/DIRTY constants and a new read-only concurrency_ready(subtask_ids, branch) function that validates each subtask's worktree sidecar record, on-disk presence, git registration, HEAD==base_sha, and cleanliness (excluding MAP runtime-state paths); extends each file's CLI dispatcher to expose the new command.
get_wave_step dispatch signals + select_execution_strategy concurrency_allowed + CLI
src/mapify_cli/templates_src/map/scripts/map_orchestrator.py.jinja, src/mapify_cli/templates/map/scripts/map_orchestrator.py, .map/scripts/map_orchestrator.py
Adds WAVE_REASON_NO_WAVES/WAVE_COMPLETE/DISPATCH_SEQUENTIAL constants; updates all three get_wave_step return paths to include dispatch_mode, isolation_active, and reason; extends select_execution_strategy to return concurrency_allowed; wires select_execution_strategy as a new argparse CLI command.
MapConfig: max_actors, retry_degraded_once, clamp_max_actors
src/mapify_cli/config/project_config.py
Adds max_actors: int = 4 and retry_degraded_once: bool = False dataclass fields; introduces clamp_max_actors() enforcing [1, 8] with bool/non-int fallback to default; extends load_map_config() dotted-key alias table and post-construction clamping; adds commented entries in generate_default_config().
Skill documentation: Slice 5a vs 5b clarifications
src/mapify_cli/templates_src/skills/map-efficient/*, src/mapify_cli/templates_src/codex/skills/map-efficient/*, src/mapify_cli/templates/skills/map-efficient/*, src/mapify_cli/templates/codex/skills/map-efficient/*, .agents/skills/map-efficient/*, .claude/skills/map-efficient/*
Updates SKILL.md and efficient-reference.md across all copies: documents the Slice 5a sequential worktree flow under isolation_active, re-gates concurrent fan-out to Slice 5b, rewrites the "Concurrent actor dispatch" gated example as Slice 5b-only reference material, re-scopes anti-patterns to Slice 5b, and replaces the Phase 2 "parallel waves" section with Slice 5a sequential protocol.
Tests: concurrency_ready, dispatch signals, config fields, wave parallel harness
tests/test_worktree_isolation.py, tests/test_map_orchestrator.py, tests/test_project_config.py, tests/test_wave_parallel_harness.py
Adds TestConcurrencyReady (happy path, dirty worktree, read-only, disabled); Slice-3 orchestrator tests for concurrency_allowed truth table, get_wave_step sequential contract, reason code stability, and default-config neutrality; test_project_config.py for MapConfig/clamp_max_actors/dormant-key assertions; test_wave_parallel_harness.py with VC1–VC4b merge_wave_worktrees integration scenarios.

Sequence Diagram(s)

sequenceDiagram
  participant Coordinator as Coordinator (Orchestrator)
  participant get_wave_step
  participant concurrency_ready
  participant WorktreeRegistry
  participant GitWorktreeList

  Coordinator->>get_wave_step: get_wave_step(branch)
  get_wave_step-->>Coordinator: {dispatch_mode, isolation_active, reason, concurrency_enabled}

  Note over Coordinator: isolation_active=True → Slice 5a sequential loop

  loop per wave member (one per turn)
    Coordinator->>Coordinator: dispatch Actor subagent pinned to $WT_PATH
  end

  Coordinator->>concurrency_ready: concurrency_ready(subtask_ids, branch)
  concurrency_ready->>WorktreeRegistry: lookup sidecar record per subtask
  concurrency_ready->>GitWorktreeList: git worktree list --porcelain
  concurrency_ready-->>Coordinator: {ready, per_subtask, reason}

  alt ready=True
    Coordinator->>Coordinator: merge_wave_worktrees (atomic)
  else ready=False
    Coordinator->>Coordinator: rollback / retry
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • azalio/map-framework#80: This PR extends the same map_orchestrator.py wave-step dispatch and map_step_runner.py worktree flow introduced in PR #80.
  • azalio/map-framework#304: Both PRs modify get_wave_step()/select_execution_strategy() gating around execution.wave_mode, _worktree_isolation_mode, and concurrency toggles in the same orchestrator files.

Poem

🐇 Hop hop, one turn at a time,
No concurrent leaps — Slice 5a's the rhyme!
Each worktree checked clean, HEAD matched to base,
concurrency_ready sets the pace.
Atomic merge only when all bunnies say "yes,"
Slice 5b waits — no premature mess! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main change: Slice 5a auto-worktree wave-loop behavior with sequential dispatch behind a flag gate.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-303-slice5-concurrent-dispatch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (2)
tests/test_wave_parallel_harness.py (1)

57-62: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the new isolation enum in the harness.

This helper still enables isolation with legacy true, so these tests don't exercise the new worktree.isolation in {auto,required} contract directly. Using required here would make the harness validate the Slice 5a path instead of its backward-compat shim.

Suggested change
     (repo / ".map" / "config.yaml").write_text(
-        "worktree.isolation: true\nworktree.max_deletions: 50\n",
+        "worktree.isolation: required\nworktree.max_deletions: 50\n",
         encoding="utf-8",
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_wave_parallel_harness.py` around lines 57 - 62, Update the test
harness helper _enable so it uses the new worktree.isolation enum value instead
of the legacy boolean true. Adjust the config written by _enable in
tests/test_wave_parallel_harness.py to set worktree.isolation to required, so
the harness exercises the new isolation contract directly and validates the
Slice 5a path rather than the backward-compatibility shim.
tests/test_project_config.py (1)

154-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Mirror this dormant-consumer guard for retry_degraded_once.

This subprocess assertion only scans for max_actors, so a new runner/orchestrator read of retry_degraded_once in Python sources would still pass VC3 even though the Slice 5a dormant contract applies to both fields.

Suggested change
     def test_vc3_grep_subprocess_confirms_no_runner_orchestrator_consumer(self):
         """Subprocess grep across runner/orchestrator/step_runner Python sources.
@@
         """
         src_root = Path(__file__).parent.parent / "src" / "mapify_cli"
-        result = subprocess.run(
-            ["grep", "-rl", "max_actors", str(src_root)],
-            capture_output=True,
-            text=True,
-        )
-        files_with_max_actors = [
-            line for line in result.stdout.splitlines() if line.strip()
-        ]
-        # Runner/orchestrator Python source files are forbidden in Slice 5a.
-        # Documentation (.md, .jinja) and observability modules are allowed.
-        _FORBIDDEN_STEMS = ("runner", "orchestrator", "step_runner", "wave_coordinator")
-        forbidden = [
-            f for f in files_with_max_actors
-            if any(stem in Path(f).stem for stem in _FORBIDDEN_STEMS)
-            and f.endswith(".py")
-        ]
-        assert forbidden == [], (
-            "max_actors found in runner/orchestrator Python sources in Slice 5a "
-            "(DORMANT violation — field must not be consumed until Slice 5b):\n"
-            + "\n".join(forbidden)
-        )
+        for field_name in ("max_actors", "retry_degraded_once"):
+            result = subprocess.run(
+                ["grep", "-rl", field_name, str(src_root)],
+                capture_output=True,
+                text=True,
+            )
+            files_with_field = [
+                line for line in result.stdout.splitlines() if line.strip()
+            ]
+            # Runner/orchestrator Python source files are forbidden in Slice 5a.
+            # Documentation (.md, .jinja) and observability modules are allowed.
+            _FORBIDDEN_STEMS = (
+                "runner",
+                "orchestrator",
+                "step_runner",
+                "wave_coordinator",
+            )
+            forbidden = [
+                f for f in files_with_field
+                if any(stem in Path(f).stem for stem in _FORBIDDEN_STEMS)
+                and f.endswith(".py")
+            ]
+            assert forbidden == [], (
+                f"{field_name} found in runner/orchestrator Python sources in Slice 5a "
+                "(DORMANT violation — field must not be consumed until Slice 5b):\n"
+                + "\n".join(forbidden)
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_project_config.py` around lines 154 - 183, Extend the
dormant-consumer subprocess guard in
test_vc3_grep_subprocess_confirms_no_runner_orchestrator_consumer to also scan
for retry_degraded_once, not just max_actors. Update the grep/filter logic so
any Python source under runner/orchestrator/step_runner/wave_coordinator that
mentions retry_degraded_once is collected into the forbidden list, while keeping
docs and observability modules allowed. Use the existing test structure and
symbols like _FORBIDDEN_STEMS and files_with_max_actors as the place to mirror
the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/map-efficient/efficient-reference.md:
- Around line 665-672: Remove the Evaluator requirement from the Slice 5a merge
recipe so it matches the skill’s phase model. Update the wording around the
merge instructions in the affected section to require only Monitor before
calling merge_wave_worktrees, and keep the guidance consistent with
concurrency_ready, merge_subtask_worktree, and merge_wave_worktrees without
mentioning Evaluator in the normal path.

In @.map/scripts/map_step_runner.py:
- Around line 15345-15350: `concurrency_ready()` is treating a worktree as clean
even when `_wt_git(["status", "--porcelain"], cwd=wt_path)` fails, because an
empty stdout still falls through to the clean path. Update the worktree-status
handling to fail closed: in the status-check logic that sets `ok`/reason codes
(near `_WT_REASON_DIRTY` and related concurrency_ready worktree checks), require
a successful git exit before marking clean, and when the status command returns
non-zero mark the member not ready with the appropriate failure reason instead
of assuming cleanliness. Apply the same fix to the duplicated status-handling
block referenced by the second range.

In `@src/mapify_cli/templates_src/codex/skills/map-efficient/SKILL.md.jinja`:
- Around line 16-20: Add the missing `concurrency_ready` entry to the Script
Routing section so the workflow matches the earlier `isolation_active`
description. Update the routing table in `SKILL.md.jinja` to list
`concurrency_ready` alongside the other subcommands, keeping the wording
consistent with the existing routing entries and the wave-loop verification
flow.

In `@src/mapify_cli/templates_src/map/scripts/map_orchestrator.py.jinja`:
- Around line 2308-2314: The import handling around _worktree_isolation_mode in
map_orchestrator.py.jinja is too broad because the except ImportError also masks
failures raised inside map_step_runner itself. Narrow the fallback so only a
missing import of map_step_runner is treated as isolation_active=False, and let
nested ImportError exceptions propagate or otherwise be distinguished from the
import attempt. Keep the behavior localized to the try/except that wraps the
map_step_runner import and _worktree_isolation_mode lookup.

In
`@src/mapify_cli/templates_src/skills/map-efficient/efficient-reference.md.jinja`:
- Around line 151-160: Remove the mention of parallel_ready from the Slice 5b
gate text in the concurrent actor dispatch example, and align the wording with
the actual fields returned by get_wave_step; use only concurrency_enabled,
dispatch_mode, isolation_active, and reason so the guidance matches the real API
shape.

In `@src/mapify_cli/templates_src/skills/map-efficient/SKILL.md.jinja`:
- Line 210: Add the missing isolation condition to the execution-strategy
summary in SKILL.md.jinja: update the `select_execution_strategy` description so
the wave-loop (`get_wave_step` / `validate_wave_step` / `advance_wave`) only
activates when `execution.wave_mode ∈ {on, auto}`, a color group has at least
two members, and `worktree.isolation != off`. Keep the sequential fallback
(`get_next_step`) described as the default whenever that isolation gate is not
satisfied, so the summary matches the Slice 5a contract and decision table.

In `@src/mapify_cli/templates/map/scripts/map_step_runner.py`:
- Around line 16881-16886: The clean-tree check around `_wt_git(["status",
"--porcelain"], cwd=wt_path)` should fail closed instead of treating a failed
`git status` as clean. In the worktree readiness logic, check `st.returncode`
before building `dirty_lines`; if the status command fails for a registered
worktree, treat it as not clean and block the subtask rather than returning
ready. Keep the existing filtering via `_wt_porcelain_path` and
`_wt_is_runtime_state_path`, but make the readiness decision depend on a
successful `git status` result.

---

Nitpick comments:
In `@tests/test_project_config.py`:
- Around line 154-183: Extend the dormant-consumer subprocess guard in
test_vc3_grep_subprocess_confirms_no_runner_orchestrator_consumer to also scan
for retry_degraded_once, not just max_actors. Update the grep/filter logic so
any Python source under runner/orchestrator/step_runner/wave_coordinator that
mentions retry_degraded_once is collected into the forbidden list, while keeping
docs and observability modules allowed. Use the existing test structure and
symbols like _FORBIDDEN_STEMS and files_with_max_actors as the place to mirror
the check.

In `@tests/test_wave_parallel_harness.py`:
- Around line 57-62: Update the test harness helper _enable so it uses the new
worktree.isolation enum value instead of the legacy boolean true. Adjust the
config written by _enable in tests/test_wave_parallel_harness.py to set
worktree.isolation to required, so the harness exercises the new isolation
contract directly and validates the Slice 5a path rather than the
backward-compatibility shim.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c834d347-a828-4de4-adbb-154005758393

📥 Commits

Reviewing files that changed from the base of the PR and between 1304c59 and eba54f7.

📒 Files selected for processing (23)
  • .agents/skills/map-efficient/SKILL.md
  • .agents/skills/map-efficient/efficient-reference.md
  • .claude/skills/map-efficient/SKILL.md
  • .claude/skills/map-efficient/efficient-reference.md
  • .map/scripts/map_orchestrator.py
  • .map/scripts/map_step_runner.py
  • src/mapify_cli/config/project_config.py
  • src/mapify_cli/templates/codex/skills/map-efficient/SKILL.md
  • src/mapify_cli/templates/codex/skills/map-efficient/efficient-reference.md
  • src/mapify_cli/templates/map/scripts/map_orchestrator.py
  • src/mapify_cli/templates/map/scripts/map_step_runner.py
  • src/mapify_cli/templates/skills/map-efficient/SKILL.md
  • src/mapify_cli/templates/skills/map-efficient/efficient-reference.md
  • src/mapify_cli/templates_src/codex/skills/map-efficient/SKILL.md.jinja
  • src/mapify_cli/templates_src/codex/skills/map-efficient/efficient-reference.md.jinja
  • src/mapify_cli/templates_src/map/scripts/map_orchestrator.py.jinja
  • src/mapify_cli/templates_src/map/scripts/map_step_runner.py.jinja
  • src/mapify_cli/templates_src/skills/map-efficient/SKILL.md.jinja
  • src/mapify_cli/templates_src/skills/map-efficient/efficient-reference.md.jinja
  • tests/test_map_orchestrator.py
  • tests/test_project_config.py
  • tests/test_wave_parallel_harness.py
  • tests/test_worktree_isolation.py

Comment on lines +665 to +672
3. **Verify** all member worktrees with `concurrency_ready` (ST-003) before merge.
4. **Accept atomically** via `merge_wave_worktrees` after every subtask passes
Monitor (+ Evaluator) — never merge one at a time.

Do NOT merge them one at a time: every worktree was cut off the same HEAD, so
the first `merge_subtask_worktree` advances the working branch and the next trips
`BASE_DIVERGED`. Accept the whole wave atomically instead — only after EVERY
subtask in the wave has passed Monitor (+ Evaluator):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Drop the Evaluator gate from this Slice 5a merge recipe.

map-efficient documents Monitor as the terminal validator in normal execution, so requiring Monitor (+ Evaluator) here contradicts the skill’s own phase model and blocks the wave-accept path on a phase operators never run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/map-efficient/efficient-reference.md around lines 665 - 672,
Remove the Evaluator requirement from the Slice 5a merge recipe so it matches
the skill’s phase model. Update the wording around the merge instructions in the
affected section to require only Monitor before calling merge_wave_worktrees,
and keep the guidance consistent with concurrency_ready, merge_subtask_worktree,
and merge_wave_worktrees without mentioning Evaluator in the normal path.

Comment on lines +15345 to +15350
# concurrency_ready reason codes
_WT_REASON_NO_RECORD: str = "no_record"
_WT_REASON_PATH_MISSING: str = "path_missing"
_WT_REASON_NOT_REGISTERED: str = "not_registered"
_WT_REASON_HEAD_MISMATCH: str = "head_mismatch"
_WT_REASON_DIRTY: str = "dirty"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail closed when git status cannot inspect a worktree.

If _wt_git(["status", "--porcelain"], cwd=wt_path) returns non-zero, st.stdout can still be empty, so this member is marked clean and ok=True. That makes concurrency_ready() report a broken worktree as merge-ready.

Proposed fix
 # concurrency_ready reason codes
 _WT_REASON_NO_RECORD: str = "no_record"
 _WT_REASON_PATH_MISSING: str = "path_missing"
 _WT_REASON_NOT_REGISTERED: str = "not_registered"
 _WT_REASON_HEAD_MISMATCH: str = "head_mismatch"
 _WT_REASON_DIRTY: str = "dirty"
+_WT_REASON_STATUS_ERROR: str = "status_error"
...
         # (4) Clean tree (ignoring MAP runtime-state paths)
         st = _wt_git(["status", "--porcelain"], cwd=wt_path)
+        if st.returncode != 0:
+            per_subtask[sid] = {
+                "ok": False,
+                "reason": _WT_REASON_STATUS_ERROR,
+                "path": raw_path,
+            }
+            if first_failure is None:
+                first_failure = _WT_REASON_STATUS_ERROR
+            continue
         dirty_lines = [
             ln
             for ln in st.stdout.splitlines()
             if ln.strip() and not _wt_is_runtime_state_path(_wt_porcelain_path(ln))
         ]

Also applies to: 16881-16896

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.map/scripts/map_step_runner.py around lines 15345 - 15350,
`concurrency_ready()` is treating a worktree as clean even when
`_wt_git(["status", "--porcelain"], cwd=wt_path)` fails, because an empty stdout
still falls through to the clean path. Update the worktree-status handling to
fail closed: in the status-check logic that sets `ok`/reason codes (near
`_WT_REASON_DIRTY` and related concurrency_ready worktree checks), require a
successful git exit before marking clean, and when the status command returns
non-zero mark the member not ready with the appropriate failure reason instead
of assuming cleanliness. Apply the same fix to the duplicated status-handling
block referenced by the second range.

Comment on lines +16 to +20
section when the workflow below points to it. Under `isolation_active` (Slice 5a),
the wave-loop creates per-member worktrees, dispatches actor subagents
**sequentially** (one per turn), verifies via `concurrency_ready`, then accepts
atomically via `merge_wave_worktrees`; concurrent fan-out is Slice 5b
(`dispatch_mode==concurrent`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document concurrency_ready in the routing table too.

This paragraph now tells operators to verify via concurrency_ready, but the later Script Routing section still omits that subcommand. Adding it there will keep the workflow docs self-consistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mapify_cli/templates_src/codex/skills/map-efficient/SKILL.md.jinja`
around lines 16 - 20, Add the missing `concurrency_ready` entry to the Script
Routing section so the workflow matches the earlier `isolation_active`
description. Update the routing table in `SKILL.md.jinja` to list
`concurrency_ready` alongside the other subcommands, keeping the wording
consistent with the existing routing entries and the wave-loop verification
flow.

Comment on lines +2308 to +2314
try:
from map_step_runner import ( # pyright: ignore[reportMissingImports]
_worktree_isolation_mode,
)
isolation_active = _worktree_isolation_mode(Path(".")) != "off"
except ImportError:
isolation_active = False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don't swallow nested import failures here.

This except ImportError also catches failures inside map_step_runner, then reports isolation_active=False. That turns a broken render/package state into misleading sequential metadata instead of surfacing the real error.

Suggested change
     try:
         from map_step_runner import (  # pyright: ignore[reportMissingImports]
             _worktree_isolation_mode,
         )
-        isolation_active = _worktree_isolation_mode(Path(".")) != "off"
-    except ImportError:
+    except ModuleNotFoundError as exc:
+        if exc.name != "map_step_runner":
+            raise
         isolation_active = False
+    else:
+        isolation_active = _worktree_isolation_mode(Path(".")) != "off"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
from map_step_runner import ( # pyright: ignore[reportMissingImports]
_worktree_isolation_mode,
)
isolation_active = _worktree_isolation_mode(Path(".")) != "off"
except ImportError:
isolation_active = False
try:
from map_step_runner import ( # pyright: ignore[reportMissingImports]
_worktree_isolation_mode,
)
except ModuleNotFoundError as exc:
if exc.name != "map_step_runner":
raise
isolation_active = False
else:
isolation_active = _worktree_isolation_mode(Path(".")) != "off"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mapify_cli/templates_src/map/scripts/map_orchestrator.py.jinja` around
lines 2308 - 2314, The import handling around _worktree_isolation_mode in
map_orchestrator.py.jinja is too broad because the except ImportError also masks
failures raised inside map_step_runner itself. Narrow the fallback so only a
missing import of map_step_runner is treated as isolation_active=False, and let
nested ImportError exceptions propagate or otherwise be distinguished from the
import attempt. Keep the behavior localized to the try/except that wraps the
map_step_runner import and _worktree_isolation_mode lookup.

Comment on lines +151 to +160
### Concurrent Actor dispatch — **Slice 5b only** (`dispatch_mode == 'concurrent'`) — GATED EXAMPLE

> **IMPORTANT — read before using this example.**
> Concurrent fan-out (emitting multiple `Task(actor)` calls in a single message) is
> enabled **only when concurrency is shipped: Slice 5+ / `concurrency_enabled: true` /
> `parallel_ready` flag set**. In the **current framework** `concurrency_enabled` is
> **False**, so dispatch stays **SEQUENTIAL even when a wave has `mode=="parallel"`**.
> The example below is reference material for when that capability ships; do NOT
> treat it as an active instruction now.
> **Slice 5b** and is enabled **only when `concurrency_enabled: true` /
> `parallel_ready` flag set / `dispatch_mode == 'concurrent'`**. In the **current
> framework** (`concurrency_enabled=False`, Slice 5a), dispatch stays **SEQUENTIAL
> even when a wave has `mode=="parallel"`** — one Actor per turn, each pinned to
> its own worktree. The example below is reference material for when Slice 5b ships;
> do NOT treat it as an active instruction now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  src/mapify_cli/templates_src/map/scripts/map_orchestrator.py.jinja \
  src/mapify_cli/templates/map/scripts/map_orchestrator.py \
  .map/scripts/map_orchestrator.py
do
  if [ -f "$f" ]; then
    echo "=== $f ==="
    rg -n -C2 'select_execution_strategy|get_wave_step|dispatch_mode|isolation_active|reason|concurrency_allowed|concurrency_enabled|parallel_ready' "$f"
    echo
  fi
done

Repository: azalio/map-framework

Length of output: 32859


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '=== template excerpt ===\n'
sed -n '140,175p' src/mapify_cli/templates_src/skills/map-efficient/efficient-reference.md.jinja

printf '\n=== parallel_ready references ===\n'
rg -n 'parallel_ready|concurrency_enabled|dispatch_mode|isolation_active|concurrency_allowed' src/mapify_cli/templates_src src/mapify_cli/templates .claude .codex .agents 2>/dev/null || true

Repository: azalio/map-framework

Length of output: 22749


Drop parallel_ready from the Slice 5b gate text. get_wave_step exposes concurrency_enabled, dispatch_mode, isolation_active, and reason; parallel_ready is not part of the returned shape and will mislead readers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/mapify_cli/templates_src/skills/map-efficient/efficient-reference.md.jinja`
around lines 151 - 160, Remove the mention of parallel_ready from the Slice 5b
gate text in the concurrent actor dispatch example, and align the wording with
the actual fields returned by get_wave_step; use only concurrency_enabled,
dispatch_mode, isolation_active, and reason so the guidance matches the real API
shape.

```

**Execution strategy:** `select_execution_strategy` chooses between the legacy sequential walker and the wave-loop. The wave-loop (`get_wave_step` / `validate_wave_step` / `advance_wave`) engages only when `execution.wave_mode ∈ {on, auto}` AND a color group has ≥2 members; otherwise `get_next_step` (sequential walker) runs. See [efficient-reference.md](efficient-reference.md#wave-execution) for the decision table and full wave loop.
**Execution strategy:** `select_execution_strategy` chooses between the legacy sequential walker and the wave-loop. The wave-loop (`get_wave_step` / `validate_wave_step` / `advance_wave`) engages only when `execution.wave_mode ∈ {on, auto}` AND a color group has ≥2 members; otherwise `get_next_step` (sequential walker) runs. Under `isolation_active` (Slice 5a), the wave-loop creates per-member worktrees, dispatches Actors **sequentially** (one per turn, `HC-3`), verifies via `concurrency_ready`, then accepts atomically via `merge_wave_worktrees`; concurrent fan-out is Slice 5b (`dispatch_mode==concurrent`). See [efficient-reference.md](efficient-reference.md#wave-execution) for the decision table and full wave loop.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the missing isolation gate to the execution-strategy summary.

Line 210 currently says the wave-loop engages on wave_mode + color-group size alone, but the Slice 5a contract and the reference table also require worktree.isolation != off. As written, this overstates when wave APIs are active and misdocuments the default path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mapify_cli/templates_src/skills/map-efficient/SKILL.md.jinja` at line
210, Add the missing isolation condition to the execution-strategy summary in
SKILL.md.jinja: update the `select_execution_strategy` description so the
wave-loop (`get_wave_step` / `validate_wave_step` / `advance_wave`) only
activates when `execution.wave_mode ∈ {on, auto}`, a color group has at least
two members, and `worktree.isolation != off`. Keep the sequential fallback
(`get_next_step`) described as the default whenever that isolation gate is not
satisfied, so the summary matches the Slice 5a contract and decision table.

Comment on lines +16881 to +16886
st = _wt_git(["status", "--porcelain"], cwd=wt_path)
dirty_lines = [
ln
for ln in st.stdout.splitlines()
if ln.strip() and not _wt_is_runtime_state_path(_wt_porcelain_path(ln))
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail closed when git status errors.

This clean-tree check ignores st.returncode. If git status --porcelain fails for a registered worktree, dirty_lines stays empty and the subtask can be reported ready even though cleanliness was never verified. As per coding guidelines, "All shipped templates must be edited in src/mapify_cli/templates_src/**/*.jinja and re-rendered with make render-templates; never edit the generated trees directly (src/mapify_cli/templates/**, .claude/**, .codex/**, .agents/skills/**)."

Suggested fix
+# add alongside the other _WT_REASON_* constants
+_WT_REASON_STATUS_FAILED: str = "status_failed"
+
         st = _wt_git(["status", "--porcelain"], cwd=wt_path)
+        if st.returncode != 0:
+            per_subtask[sid] = {
+                "ok": False,
+                "reason": _WT_REASON_STATUS_FAILED,
+            }
+            if first_failure is None:
+                first_failure = _WT_REASON_STATUS_FAILED
+            continue
         dirty_lines = [
             ln
             for ln in st.stdout.splitlines()
             if ln.strip() and not _wt_is_runtime_state_path(_wt_porcelain_path(ln))
         ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
st = _wt_git(["status", "--porcelain"], cwd=wt_path)
dirty_lines = [
ln
for ln in st.stdout.splitlines()
if ln.strip() and not _wt_is_runtime_state_path(_wt_porcelain_path(ln))
]
st = _wt_git(["status", "--porcelain"], cwd=wt_path)
if st.returncode != 0:
per_subtask[sid] = {
"ok": False,
"reason": _WT_REASON_STATUS_FAILED,
}
if first_failure is None:
first_failure = _WT_REASON_STATUS_FAILED
continue
dirty_lines = [
ln
for ln in st.stdout.splitlines()
if ln.strip() and not _wt_is_runtime_state_path(_wt_porcelain_path(ln))
]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mapify_cli/templates/map/scripts/map_step_runner.py` around lines 16881 -
16886, The clean-tree check around `_wt_git(["status", "--porcelain"],
cwd=wt_path)` should fail closed instead of treating a failed `git status` as
clean. In the worktree readiness logic, check `st.returncode` before building
`dirty_lines`; if the status command fails for a registered worktree, treat it
as not clean and block the subtask rather than returning ready. Keep the
existing filtering via `_wt_porcelain_path` and `_wt_is_runtime_state_path`, but
make the readiness decision depend on a successful `git status` result.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant