Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
761e0fa
ST-001: add execution.wave_mode config reader (off|auto|on), absent->…
azalio Jun 28, 2026
2e674bf
ST-002: widen worktree.isolation to off|auto|required enum, legacy bo…
azalio Jun 28, 2026
7cf3e77
ST-003: add no-op lint/observability config toggles (lint.dependency_…
azalio Jun 28, 2026
3c43abf
ST-004: add canned wave/repo-shape fixtures for eval harness (part of…
azalio Jun 28, 2026
0622c62
ST-005: eval/regression test for wave+color computation and default-p…
azalio Jun 28, 2026
05aca1d
ST-006: dependency-graph lint Layer A (self-loop/cycle/unknown-dep/du…
azalio Jun 28, 2026
7ebf473
ST-007: dependency-graph lint Layer B (thin-edge/same-file/fully-seri…
azalio Jun 28, 2026
017fd4c
ST-008: safe detached worktree probe + clean-target check (dormant wh…
azalio Jun 28, 2026
9bbccd4
ST-009: worktree fallback matrix (auto->seq+warn / required->hard-fai…
azalio Jun 28, 2026
819bba5
ST-010: predicate-gated wave-loop selection + sequential-inside signa…
azalio Jun 28, 2026
5cc9142
ST-011: dormant observability scaffolding (parallelism.json schema + …
azalio Jun 28, 2026
c8cb958
ST-012: map-efficient doc surgery — remove 5x sequential-by-default a…
azalio Jun 28, 2026
a235acb
ST-013: final gate — fix ST-004 unused imports + ST-012 codex Claude-…
azalio Jun 29, 2026
e2625be
chore(map): strip internal workflow IDs
azalio Jun 29, 2026
ba70db6
docs(learned): capture 5 patterns from #303 Slices 0-4 /map-efficient…
azalio Jun 29, 2026
238f733
fix(#303): address CodeRabbit review on PR #304
azalio Jun 29, 2026
e48aa3a
Merge origin/main into feat-303-parallel-wave-foundation; reconcile S…
azalio Jun 29, 2026
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
72 changes: 68 additions & 4 deletions .agents/skills/map-efficient/efficient-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,34 @@ envelope/anti-gaming checks.

## Wave Execution

Sequential execution is the default. Use wave APIs only when the blueprint has
multiple ready subtasks whose writes are low-risk and disjoint, or when the user
explicitly requests parallel execution.
### Execution strategy decision table

Commands:
`select_execution_strategy` picks between the legacy sequential walker and the
wave-loop on every run. The wave-loop engages **only when ALL THREE hold**
(otherwise the legacy sequential walker `get_next_step` runs):

1. `execution.wave_mode` ∈ {`auto`, `on`}, **AND**
2. `worktree.isolation` ≠ `off`, **AND**
3. at least one color group has ≥2 members.

| `execution.wave_mode` | `worktree.isolation` | Color group ≥2? | Dispatcher selected |
|---|---|---|---|
| any | `off` (default) | any | Legacy sequential walker (`get_next_step`) |
| `off` | any | any | Legacy sequential walker (`get_next_step`) |
| `auto` / `on` | `auto` / `required` | no (all groups size 1) | Legacy sequential walker (`get_next_step`) |
| `auto` / `on` | `auto` / `required` | yes | Wave-loop (`get_wave_step` / `validate_wave_step` / `advance_wave`) |

**Defaults (canonical MapConfig):** `execution.wave_mode=auto`,
`worktree.isolation=off`. The isolation gate fails by default, so a stock
`mapify init` config always runs the legacy sequential walker. Even when the
wave-loop engages, dispatch stays sequential until concurrency ships (Slice 5+).

### Sequential walker

Use `get_next_step` for all sequential (default) execution. Do not mix wave
APIs with the sequential cursor for the same workflow.

### Wave-loop commands

```bash
python3 .map/scripts/map_orchestrator.py set_waves --blueprint ".map/${BRANCH}/blueprint.json"
Expand All @@ -109,6 +132,10 @@ python3 .map/scripts/map_orchestrator.py advance_wave
Do not mix wave APIs with the sequential `get_next_step` cursor for the same
wave unless the orchestrator response explicitly tells you to fall back.

Use wave APIs only when the blueprint has multiple ready subtasks whose writes
are low-risk and disjoint, or when the user explicitly requests parallel
execution.

When `worktree.isolation` is enabled and a wave runs in parallel (≥2 disjoint
subtasks), give each subtask its own worktree and accept the whole wave
atomically after all pass Monitor — never merge them one at a time (the first
Expand All @@ -123,6 +150,43 @@ to base on any conflict or gate failure (worktrees kept for retry). On a single
subtask's Monitor failure, `discard_subtask_worktree` that subtask and retry it
before calling `merge_wave_worktrees`.

### Concurrent Actor dispatch — GATED EXAMPLE

> **IMPORTANT — read before using this example.**
> Concurrent fan-out (dispatching multiple actor subagents in a single turn) 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. Use your
> Codex runtime's own parallel actor-subagent dispatch mechanism — this is the
> provider-neutral shape, not a literal API call.

When concurrency is enabled (Slice 5+ only), a parallel wave with N subtasks
dispatches all N actor subagents in **one turn**:

```text
# CORRECT (Slice 5+ / concurrency_enabled=True only) — one turn, N actor subagents:
dispatch actor subagent -> ST-003 (pinned to its own worktree)
dispatch actor subagent -> ST-004 (pinned to its own worktree)

# INCORRECT — one actor per turn (serial, defeats the wave):
# Turn 1: actor -> ST-003
# Turn 2: actor -> ST-004
```

**Self-audit before dispatch:** "I will dispatch {n} actor subagents in one turn."

**`max_actors` cap:** Default 4–8 per wave. Groups larger than `max_actors` are
pre-split into sequential batches before dispatch.

### Anti-patterns

- One actor dispatch per turn across N turns — serial loop, no concurrency.
- Writing between dispatches (TodoWrite, etc.) — serializes the batch.
- Waiting for one actor result before dispatching the next.
- Mixing `get_next_step` and `get_wave_step` for the same wave.

## TDD Mode

`--tdd` inserts `TEST_WRITER` and `TEST_FAIL_GATE` before `ACTOR`.
Expand Down
14 changes: 14 additions & 0 deletions .claude/rules/learned/architecture-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,3 +249,17 @@
git commit -m "ST-006: ..."
python3 .map/scripts/map_step_runner.py refresh_blueprint_affected_files "$BRANCH" ST-006
```

- **Reserve-Parameter Forward-Compat: Pin a Public Function Signature Early With Unused Params to Prevent Caller-Rewrite Cascades Across Subtasks** (2026-06-29): In a multi-subtask agentic workflow where subtask N defines a function and subtasks N+1..N+K extend it, pin the FINAL intended public signature in subtask N even if some parameters are unused until a later subtask. Suppress unused-parameter warnings with `del param` in the function body (valid in `def`, not `lambda` — see [[del-is-illegal-inside-a-python-lambda-body]]). Without early pinning, each subtask that adds a parameter forces callers and tests from all previous subtasks to be rewritten — a cascade that risks regressions in already-validated outputs and forces Monitor re-validation of untouched subtasks. The unused-param comment must name the subtask that will consume it so Monitor can verify the suppression is intentional; remove the `del` and comment when the parameter becomes live. [workflow: map-efficient]
```python
# WRONG: add parameters one subtask at a time; each addition cascades to all callers
# ST-006: def lint_dependency_graph(graph): ...
# ST-007: def lint_dependency_graph(graph, affected_files_map, node_io, enforcement, auto_prune): ... # rewrites every ST-006 caller/test

# CORRECT: pin the final signature in ST-006; del-suppress params consumed later
def lint_dependency_graph(graph, affected_files_map=None, node_io=None,
enforcement="warn", auto_prune=False) -> list[str]:
del affected_files_map, node_io, enforcement, auto_prune # consumed in ST-007 (Layer B)
return _lint_layer_a(graph)
# ST-006 callers/tests already call the full signature; ST-007 just removes the del.
```
22 changes: 22 additions & 0 deletions .claude/rules/learned/error-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,25 @@
python -c "import pytest; import truststore; import hypothesis"
# ImportError here means venv-setup issue, not a code bug -> uv sync --all-extras --all-groups
```

- **Decomposer Existence Claims Are Assumptions — Grep Before Dispatching Any 'Create New X' Subtask** (2026-06-29): A task-decomposer or plan that asserts "X does not exist yet" or scopes a subtask to "add X" is making an assumption about the live codebase, not a verified fact. Decomposers work from context summaries and miss existing implementations — especially in mature multi-module codebases. Acting on a false existence claim makes the actor implement a DUPLICATE of an existing component (e.g. duplicating a state machine from the orchestrator into the runner). Protocol: before dispatching any actor on a "create new function/class/module" subtask, grep the relevant source trees for the symbol; if it exists, fix the subtask scope before dispatch. A wrong existence claim that goes unverified propagates into duplicated machinery across all dependent subtasks. [workflow: map-efficient]
```bash
# Plan: 'ST-010: add get_wave_step/validate_wave_step/advance_wave to runner — do not exist yet.'
# WRONG: trust the claim -> actor creates a duplicate state machine
# CORRECT: grep before dispatch
grep -r 'get_wave_step\|validate_wave_step\|advance_wave' src/mapify_cli/templates_src/
# -> map_orchestrator.py.jinja already defines them: claim is FALSE.
# Re-scope ST-010 to GATE the existing orchestrator functions, not recreate them.
# Any subtask verb 'add'/'create'/'implement new' requires a pre-dispatch grep.
```

- **Targeted Per-File Checks During a Subtask Are Not a Substitute for the Full Aggregate Lint Gate** (2026-06-29): Running `pyright <single-file>` / `mypy <single-file>` during a subtask is faster but has two systematic blind spots vs the project's aggregate gate (`make check` / `ruff check src tests`): (1) SCOPE — targeted checks run on one file; ruff F401/F811/E501 run across all of `src/` and `tests/`, surfacing unused imports introduced in any file touched this subtask; (2) RULE SET — pyright enforces type correctness; ruff enforces lint rules pyright ignores (unused imports, line length). A file that passes targeted pyright can still fail ruff at the aggregate level (it did: `sys`/`Any` F401 surfaced only at the final `make check`). Run the project's aggregate gate as part of each code subtask's verification step, not only at the final gate; per-file targeted checks are for fast iteration, not the commit-before-monitor gate. [workflow: map-efficient]
```bash
# WRONG: per-subtask verification uses only a targeted single-file type-check
pyright src/mapify_cli/parallelism_observability.py # passes; ruff F401 not checked
# CORRECT: run the aggregate gate the CI runs, at each code subtask:
ruff check src/ tests/ # ALL files, ALL rules incl. F401
python -m pyright src/ # ALL source files
pytest tests/ -x -q
# If make check fails on F401 after per-file pyright passed -> scope mismatch.
```
27 changes: 27 additions & 0 deletions .claude/rules/learned/implementation-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,30 @@ paths:
from typing import Any
def validate(bp) -> dict[str, Any]: ... # 34 downstream errors cleared by 1 change site
```

- **Behavior-Neutral Foundation: Gate Every New Path Behind a Flag That Defaults to Current Behavior, Plus Add a Default-Config Proof Test** (2026-06-29): When shipping a foundation subtask for a risky feature (parallel execution, new state machine, schema migration), every new code path must be predicate-gated behind a config key or compile-time constant that defaults to the existing behavior. A `WAVE_CONCURRENCY_ENABLED=False` constant (or equivalent) prevents any new dispatch path from being exercised by default. The proof is NOT "the tests still pass" — it is a dedicated test that loads the default (empty) config and asserts the old/sequential path is selected. Without this test, a subtle config-default bug can silently activate the new path on upgrade in every consumer repo. Ship the flag, ship the test, then the foundation can land without a soak period. [workflow: map-efficient]
```python
# WRONG: new path always reachable; no behavioral gate
def select_wave_loop(config: MapConfig) -> WaveLoop:
if config.execution_wave_mode == 'parallel':
return ParallelWaveLoop(config) # reachable if user misconfigures
return SequentialWaveLoop(config)

# CORRECT: compile-time kill-switch + proof test
WAVE_CONCURRENCY_ENABLED = False # PR-level constant; flip only when feature is ready
def select_wave_loop(config: MapConfig) -> WaveLoop:
if not WAVE_CONCURRENCY_ENABLED or config.execution_wave_mode != 'parallel':
return SequentialWaveLoop(config) # old path, guaranteed in this PR
return ParallelWaveLoop(config)

def test_default_config_selects_sequential_loop(tmp_path):
cfg = load_map_config(tmp_path / '.map' / 'config.yaml') # no file -> defaults
assert isinstance(select_wave_loop(cfg), SequentialWaveLoop)
```

- **Shared .jinja Templates Rendering Into Multiple Provider Trees Must Not Contain Provider-Specific API Tokens in the Codex Variant** (2026-06-29): When a `.jinja` template in `templates_src/` renders into BOTH a Claude-family tree (`.claude/skills/`) AND a Codex/agents-family tree (`.agents/skills/`, `templates/codex/`), provider-specific Claude API identifiers — `subagent_type=`, `Agent(`, `AskUserQuestion(`, `Task(` — must not appear in the codex-rendered output. A CI test (`test_ac10_no_claude_refs_anywhere`) enforces this and hard-fails `make check`. The leak is easy to introduce when editing doc examples in a shared `.jinja` (you write from the Claude perspective). Fix: after `make render-templates`, grep the codex output paths for forbidden tokens; if found, use provider-neutral prose or a jinja conditional. Do not rely on the CI gate as first-line detection — grep after rendering, before commit. [workflow: map-efficient]
```bash
make render-templates
grep -r 'subagent_type=\|AskUserQuestion\|Agent(\|Task(' .agents/skills/ src/mapify_cli/templates/codex/
# must be empty; otherwise use provider-neutral phrasing or {% if provider == 'claude' %}...{% else %}...{% endif %}
```
18 changes: 4 additions & 14 deletions .claude/skills/map-efficient/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ Use [efficient-reference.md](efficient-reference.md) for wave examples, TDD deta

```yaml
thinking_policy: medium/adaptive
parallel_tool_policy: guarded_wave_only
parallel_tool_policy: wave_mode_aware
```

- Use deeper reasoning only when a subtask is risky, blocked, under-specified, or repeatedly failing Monitor.
- Keep execution sequential by default. Parallel waves are allowed only under the existing wave rules: all dependencies satisfied, low risk, disjoint new-file writes, and the wave API is used.
- Execution strategy is determined by `select_execution_strategy`: the wave-loop engages only when `execution.wave_mode` is `on` or `auto` AND a color group has ≥2 members; otherwise the legacy sequential walker runs. With default config this is the sequential walker. See [efficient-reference.md](efficient-reference.md#wave-execution) for the decision table and dispatch mechanics.
- Do not parallelize state transitions, Monitor retries for the same subtask, or writes to shared branch artifacts.

## Execution Rules

1. Execute the next state-machine step only; never skip phases.
2. Use the exact agent type for the current phase.
3. Max 5 retry iterations per subtask.
4. Batch mode is default. Sequential subtask execution is default.
4. Batch mode is default. Execution strategy is selected by `select_execution_strategy` (sequential walker by default; wave-loop only when wave_mode is enabled and a color group has ≥2 members).
5. After Monitor pass, record files changed in `step_state.json` for guard isolation.
6. Validate planning metadata before Actor starts: `expected_diff_size`, `concern_type`, `one_logical_step`, `split_rationale`, `concern_justification`, `coverage_map`, `hard_constraints`, `soft_constraints`, `validation_criteria`, `[AC-1]` bracket tags, and `tradeoff_rationale`.
7. Script routing: `map_orchestrator.py` owns state-machine transitions (`get_next_step`, `validate_step`, `monitor_failed`, `record_subtask_result`, `set_waves`, `resume_from_plan`, …); `map_step_runner.py` owns every `detect_*` / `build_*` / `save_*` / `load_*` / `refresh_*` / `log_*` helper plus baseline `record_*` and artifact writers. Full table + the `record_*` / `validate_*` disambiguation in [efficient-reference.md#script-routing-dispatcher-reference](efficient-reference.md#script-routing-dispatcher-reference).
Expand Down Expand Up @@ -207,23 +207,13 @@ else
fi
```

Default to sequential execution. Use wave APIs only for low-risk disjoint new-file waves or explicit user-requested parallel execution. See [efficient-reference.md](efficient-reference.md#wave-execution) for the 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. See [efficient-reference.md](efficient-reference.md#wave-execution) for the decision table and full wave loop.

**Note on resume:** `resume_from_plan` (Step 0) now auto-invokes `set_waves`
when `blueprint.json` is present, so resumed workflows do not need a manual
`set_waves` dispatch. The result is reported in the `waves_computed` field of
the resume response (`"success"`, `"error"`, or `"skipped"` if no blueprint).

**Wave-loop vs sequential dispatcher:** `get_next_step` is the **sequential**
walker (one phase at a time, in `subtask_sequence` order). The **wave-loop**
(`get_wave_step` / `validate_wave_step` / `advance_wave`) honors
`execution_waves` and is the canonical path when waves contain >1 subtask.
For a single-Actor batch run with a fully-linear plan, `get_next_step` and
the wave-loop converge on the same order, so the skill defaults to the
sequential walker for simplicity. Switch to the wave-loop when (a) waves
have ≥2 subtasks AND (b) the subtasks in that wave touch disjoint files
(see `split_wave_by_file_conflicts`).

### No-op subtask short-circuit (before RESEARCH)

Some subtasks are already-done historically (rename/refactor landed in a prior PR), or truly do not need Actor/Monitor because the requested work is already satisfied by repo state. Skip them up-front to save tokens:
Expand Down
Loading
Loading