From f33772d15bd2aad6948e2a1562305f8c97c83c92 Mon Sep 17 00:00:00 2001 From: Mikhail Petrov Date: Fri, 29 May 2026 18:16:43 +0300 Subject: [PATCH] Add Codex map-efficient skill --- {.codex => .agents}/skills/map-check/SKILL.md | 0 .agents/skills/map-efficient/SKILL.md | 258 ++++++++++++++++++ .../map-efficient/efficient-reference.md | 117 ++++++++ .../skills/map-explain/SKILL.md | 0 {.codex => .agents}/skills/map-fast/SKILL.md | 0 {.codex => .agents}/skills/map-plan/SKILL.md | 4 +- .codex/AGENTS.md | 6 +- .map/scripts/map_orchestrator.py | 4 +- .map/scripts/map_step_runner.py | 42 ++- README.md | 2 +- docs/ARCHITECTURE.md | 5 +- docs/USAGE.md | 9 +- scripts/sync-templates.sh | 26 +- src/mapify_cli/__init__.py | 16 +- src/mapify_cli/delivery/codex_copier.py | 9 +- src/mapify_cli/delivery/providers.py | 2 +- src/mapify_cli/skill_ir.py | 2 +- src/mapify_cli/templates/codex/AGENTS.md | 6 +- .../codex/skills/map-efficient/SKILL.md | 258 ++++++++++++++++++ .../map-efficient/efficient-reference.md | 117 ++++++++ .../templates/codex/skills/map-plan/SKILL.md | 4 +- .../templates/map/scripts/map_orchestrator.py | 4 +- .../templates/map/scripts/map_step_runner.py | 42 ++- tests/test_mapify_cli.py | 95 +++++-- tests/test_skill_ir.py | 7 +- tests/test_skills.py | 2 +- tests/test_template_sync.py | 39 +-- 27 files changed, 972 insertions(+), 104 deletions(-) rename {.codex => .agents}/skills/map-check/SKILL.md (100%) create mode 100644 .agents/skills/map-efficient/SKILL.md create mode 100644 .agents/skills/map-efficient/efficient-reference.md rename {.codex => .agents}/skills/map-explain/SKILL.md (100%) rename {.codex => .agents}/skills/map-fast/SKILL.md (100%) rename {.codex => .agents}/skills/map-plan/SKILL.md (98%) create mode 100644 src/mapify_cli/templates/codex/skills/map-efficient/SKILL.md create mode 100644 src/mapify_cli/templates/codex/skills/map-efficient/efficient-reference.md diff --git a/.codex/skills/map-check/SKILL.md b/.agents/skills/map-check/SKILL.md similarity index 100% rename from .codex/skills/map-check/SKILL.md rename to .agents/skills/map-check/SKILL.md diff --git a/.agents/skills/map-efficient/SKILL.md b/.agents/skills/map-efficient/SKILL.md new file mode 100644 index 00000000..d3add824 --- /dev/null +++ b/.agents/skills/map-efficient/SKILL.md @@ -0,0 +1,258 @@ +--- +name: map-efficient +description: "State-machine MAP execution workflow for Codex. Use when implementing an approved MAP plan end to end, resuming from branch MAP task_plan or step_state.json artifacts, or running non-trivial multi-subtask work. Use map-fast for tiny one-shot edits." +--- + +# $map-efficient - MAP Execution + +Execute the approved MAP plan for the current branch. This skill is the Codex +counterpart to Claude `/map-efficient`, but it uses Codex-native instructions: +skills live under `.agents/skills`, configured Codex subagents live under +`.codex/agents`, and the current Codex session is the write-capable Actor and +final verifier unless an explicit subagent dispatch is available and useful. + +Use [efficient-reference.md](efficient-reference.md) for wave details, retry +recipes, TDD mode, commit policy, and troubleshooting. Read only the referenced +section when the workflow below points to it. + +## Mutation Boundary Constraints + +These constraints apply before any write-capable step: + +- Do not edit unrelated files, even if they are nearby or easy to clean up. +- Do not add, remove, or upgrade dependencies unless the current subtask contract explicitly names that dependency change. +- Do not refactor neighboring code unless the current validation criteria cannot pass without that exact refactor. +- If a dependency change, broad refactor, or scope expansion seems necessary, report it as a blocker/tradeoff instead of doing it silently. + +## Core Rules + +1. Run only the next state-machine phase; never skip phases. +2. Treat `.map//step_state.json` as the single source of truth. +3. Never edit `step_state.json` manually. Use `.map/scripts/map_orchestrator.py`. +4. Use `.map/scripts/map_step_runner.py` for analysis, reports, baselines, and sidecar artifacts. +5. Continue across subtask boundaries in the same invocation unless blocked, interrupted by the user, or the circuit breaker trips. +6. Use configured Codex subagents (`researcher`, `decomposer`, `monitor`) only when the workflow explicitly needs independent work. The current Codex session performs Actor edits and final verification. +7. Stop on any Monitor `valid=false` verdict and fix the issue before advancing. + +## Script Routing + +- `python3 .map/scripts/map_orchestrator.py ` owns state transitions: + `resume_from_plan`, `get_next_step`, `validate_step`, + `monitor_failed`, `record_subtask_result`, `check_circuit_breaker`, + `mark_subtask_complete`, `set_tdd_mode`, `set_waves`. +- `python3 .map/scripts/map_step_runner.py ` owns read-only analysis and + sidecar artifacts: `record_test_baseline`, `save_research`, `load_research`, + `build_context_block`, `detect_truncated_agent_output`, + `detect_actor_files_changed_mismatch`, `detect_symbol_blast_radius`, + `detect_cross_subtask_regression_risk`, `write_run_health_report`. + +## Argument Handling + +Parse optional flags, but do not require a task string when a plan or state +already exists. + +```bash +TASK_ARGS="$ARGUMENTS" +TDD_FLAG=false +if echo "$TASK_ARGS" | grep -q -- '--tdd'; then + TDD_FLAG=true + TASK_ARGS=$(echo "$TASK_ARGS" | sed 's/--tdd//g' | xargs) +fi +``` + +Empty `$TASK_ARGS` is a stop condition only when all of these are true: + +1. `.map//step_state.json` is missing. +2. `.map//task_plan_.md` is missing. +3. `$TASK_ARGS` is empty. + +Otherwise proceed to resume detection. + +## Step 0: Resume Existing State Or Plan + +Run this before validating `$TASK_ARGS`. + +```bash +BRANCH=$(git rev-parse --abbrev-ref HEAD | sed -E 's|/|-|g; s|[^a-zA-Z0-9_.-]|-|g; s|-{2,}|-|g; s|^-||; s|-$||') +STATE_FILE=".map/${BRANCH}/step_state.json" +PLAN_FILE=".map/${BRANCH}/task_plan_${BRANCH}.md" + +if [ -f "$STATE_FILE" ]; then + echo "Existing step_state.json found; continuing with get_next_step." +elif [ -f "$PLAN_FILE" ]; then + RESUME_RESULT=$(python3 .map/scripts/map_orchestrator.py resume_from_plan) + RESUME_STATUS=$(echo "$RESUME_RESULT" | jq -r '.status') + if [ "$RESUME_STATUS" != "success" ]; then + echo "resume_from_plan failed: $RESUME_RESULT" >&2 + exit 1 + fi +elif [ -z "$TASK_ARGS" ]; then + echo "No task, step_state.json, or task_plan_${BRANCH}.md found." >&2 + echo "Provide a task or run \$map-plan first." >&2 + exit 1 +fi + +if [ "$TDD_FLAG" = "true" ]; then + python3 .map/scripts/map_orchestrator.py set_tdd_mode true +fi +``` + +## Step 1: Get The Next Phase + +```bash +NEXT_STEP=$(python3 .map/scripts/map_orchestrator.py get_next_step) +STEP_ID=$(echo "$NEXT_STEP" | jq -r '.step_id') +PHASE=$(echo "$NEXT_STEP" | jq -r '.phase') +IS_COMPLETE=$(echo "$NEXT_STEP" | jq -r '.is_complete') +echo "$NEXT_STEP" +``` + +If `IS_COMPLETE=true`, go to final verification. + +## Step 2: Execute The Current Phase + +Execute only the phase returned by `get_next_step`. + +### DECOMPOSE + +Use the configured `decomposer` agent when available, or decompose directly in +the current session. Return blueprint JSON with atomic subtasks, dependencies, +validation criteria, hard/soft constraints, coverage_map, and AAG contracts. +Every coverage_map key owned by a subtask must appear as a bracket tag in that +subtask validation criterion, for example `VC1 [AC-1]: checkout retries`. + +Save `.map//blueprint.json`, then run: + +```bash +python3 .map/scripts/map_step_runner.py validate_blueprint_contract +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +``` + +### INIT_PLAN + +Generate `.map//task_plan_.md` from `blueprint.json`. Include +each subtask's `expected_diff_size`, `concern_type`, `one_logical_step`, +dependencies, AAG contract, acceptance criteria, and verification commands. + +Then validate: + +```bash +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +``` + +### REVIEW_PLAN + +Present the plan and require explicit user approval before implementation. +After approval, validate the step. + +### INIT_STATE + +Let the orchestrator create or update state. Do not write JSON by hand. + +```bash +python3 .map/scripts/map_step_runner.py record_test_baseline "$BRANCH" +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +if [ -f ".map/${BRANCH}/blueprint.json" ]; then + python3 .map/scripts/map_orchestrator.py set_waves --blueprint ".map/${BRANCH}/blueprint.json" +fi +``` + +### RESEARCH + +Use `researcher` when independent exploration is useful; otherwise research in +the current session. Persist concise findings before Actor work: + +```bash +SUBTASK_ID=$(jq -r '.current_subtask_id' ".map/${BRANCH}/step_state.json") +printf '%s' "$RESEARCH_FINDINGS" | \ + python3 .map/scripts/map_step_runner.py save_research "$BRANCH" "$SUBTASK_ID" +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +``` + +### TEST_WRITER And TEST_FAIL_GATE + +Only run these in TDD mode. Write failing tests first, run them, and proceed to +Actor only when the tests fail for the intended reason. Do not edit production +code in `TEST_WRITER`. + +### ACTOR + +Load the current contract and research: + +```bash +SUBTASK_ID=$(jq -r '.current_subtask_id' ".map/${BRANCH}/step_state.json") +MAP_CONTEXT=$(python3 .map/scripts/map_step_runner.py build_context_block "$BRANCH" "$SUBTASK_ID") +RESEARCH_FINDINGS=$(python3 .map/scripts/map_step_runner.py load_research "$BRANCH" "$SUBTASK_ID") +``` + +Implement exactly the current subtask. Preserve validation criteria, +coverage_map tags, hard constraints, and documented tradeoffs. Keep edits +inside the current subtask boundary. + +Before Monitor, run the required pre-dispatch gates from +[efficient-reference.md](efficient-reference.md#pre-monitor-gates): + +```bash +python3 .map/scripts/map_step_runner.py detect_actor_files_changed_mismatch "$BRANCH" "$SUBTASK_ID" --declared "$FILES_CSV" +python3 .map/scripts/map_step_runner.py detect_symbol_blast_radius "$BRANCH" "$SUBTASK_ID" +``` + +### MONITOR + +Use the configured `monitor` agent when available, or run an independent review +pass in the current session. Validate implementation against the subtask AAG +contract, validation criteria, coverage tags, hard constraints, and relevant +soft constraints. + +If Monitor fails: + +```bash +python3 .map/scripts/map_orchestrator.py monitor_failed --feedback "$MONITOR_FEEDBACK" +``` + +Write a durable `.map//code-review-N.md` with exact issues and then fix +the current subtask. Do not advance until Monitor passes. + +On a clean pass, run the regression gate and record the subtask: + +```bash +python3 .map/scripts/map_step_runner.py detect_cross_subtask_regression_risk "$BRANCH" "$SUBTASK_ID" +python3 .map/scripts/map_orchestrator.py record_subtask_result "$SUBTASK_ID" valid \ + --files "$FILES_CSV" --summary "$ONE_LINE" --commit-sha "$SHA" +python3 .map/scripts/map_orchestrator.py validate_step 2.4 \ + --recommendation "$MONITOR_RECOMMENDATION" +python3 .map/scripts/map_step_runner.py refresh_blueprint_affected_files "$BRANCH" "$SUBTASK_ID" +``` + +### ADVANCE_SUBTASK + +This is a synthetic boundary, not a user checkpoint. Call `get_next_step` +again immediately and continue with the next subtask. + +## Step 3: Final Verification + +Run final verification for the whole plan, not only the last subtask. + +```bash +python3 .map/scripts/map_orchestrator.py check_circuit_breaker +``` + +Inspect the task plan, state file, artifact manifest, final diff, tests, and +Monitor artifacts. Run the focused and full verification commands required by +the plan. Close only when the implemented behavior and tests satisfy all +subtasks. + +Write terminal run health: + +```bash +RUN_HEALTH_STATUS="${RUN_HEALTH_STATUS:?complete|pending|blocked|wont_do|superseded}" +python3 .map/scripts/map_step_runner.py write_run_health_report \ + map-efficient \ + "$RUN_HEALTH_STATUS" +``` + +## Step 4: Final Response + +Report completed subtasks, files changed, checks run, final status, and any +remaining blockers. Mention the next command only when useful, such as +`$map-check` for a verification-only pass. diff --git a/.agents/skills/map-efficient/efficient-reference.md b/.agents/skills/map-efficient/efficient-reference.md new file mode 100644 index 00000000..91eab2f2 --- /dev/null +++ b/.agents/skills/map-efficient/efficient-reference.md @@ -0,0 +1,117 @@ +# $map-efficient Supporting Reference + +This file holds lower-frequency details for the Codex `$map-efficient` skill. +Load only the section needed by the active phase. + +## Pre-Monitor Gates + +Before Monitor, verify that Actor output and repository state agree. + +```bash +python3 .map/scripts/map_step_runner.py detect_actor_files_changed_mismatch \ + "$BRANCH" "$SUBTASK_ID" --declared "$FILES_CSV" +python3 .map/scripts/map_step_runner.py detect_symbol_blast_radius \ + "$BRANCH" "$SUBTASK_ID" +``` + +If `detect_actor_files_changed_mismatch` reports `status_mismatch=true`, finish +the missing edits before Monitor. If `detect_symbol_blast_radius` recommends +`validate_callers`, include external callers in Monitor's review context. + +## Cross-Subtask Regression Gate + +Before committing or recording a clean Monitor result, ask whether a scoped test +run is safe: + +```bash +python3 .map/scripts/map_step_runner.py detect_cross_subtask_regression_risk \ + "$BRANCH" "$SUBTASK_ID" +``` + +If `recommended_gate == "full_suite"`, run the full suite. A focused run is +allowed only when the detector returns `scoped` and the subtask contract does +not require broader validation. + +## 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. + +Commands: + +```bash +python3 .map/scripts/map_orchestrator.py set_waves --blueprint ".map/${BRANCH}/blueprint.json" +python3 .map/scripts/map_orchestrator.py get_wave_step +python3 .map/scripts/map_orchestrator.py validate_wave_step "$STEP_ID" +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. + +## TDD Mode + +`--tdd` inserts `TEST_WRITER` and `TEST_FAIL_GATE` before `ACTOR`. + +Rules: + +- Write tests before production code. +- Run the new tests and confirm they fail for the intended reason. +- Treat tests that pass before implementation as weak tests; revise them before + Actor work. +- Do not edit production code in `TEST_WRITER`. + +## Monitor Retry Loop + +Every Monitor failure needs durable evidence: + +1. Write `.map//code-review-N.md` with the exact issue, file path, and + required fix. +2. Run `monitor_failed --feedback "$MONITOR_FEEDBACK"`. +3. Fix only the current subtask. +4. Re-run Monitor. + +If retries start repeating, check the orchestrator response for retry isolation +or circuit-breaker guidance before another Actor attempt. + +## Per-Subtask Commit Policy + +After a clean Monitor pass, a per-subtask commit is allowed and usually +preferred when the repository is in a reviewable state. Stage named files only. + +```bash +git add +git commit -m "ST-NNN: " +SHA=$(git log -1 --format=%H) +python3 .map/scripts/map_orchestrator.py record_subtask_result \ + "$SUBTASK_ID" valid --files "$FILES_CSV" --summary "$ONE_LINE" \ + --commit-sha "$SHA" +``` + +Do not use `git add .`. Do not amend a published commit. Do not bypass hooks. +If the user requested one bundled commit or the intermediate state cannot pass +hooks, document the deferral and record the subtask result without committing. + +## Final Verification + +Final verification must prove the full plan: + +- Read `.map//task_plan_.md`. +- Read `.map//step_state.json`. +- Inspect the final diff. +- Run the verification commands required by the plan. +- Confirm Monitor artifacts do not contain unresolved valid=false findings. +- Write `run_health_report.json` with `write_run_health_report`. + +## Troubleshooting + +- `resume_from_plan` fails: inspect the returned JSON and fix missing plan, + blueprint, or branch artifacts before continuing. +- `validate_blueprint_contract` fails: fix the blueprint before Actor work. +- `validate_step` rejects Monitor close: obey its recovery instruction; do not + force-advance state. +- `step_state.json` disagrees with artifacts: use orchestrator commands to + repair or resume. Do not edit the JSON manually. +- Final closeout lacks `.map//run_health_report.json`: rerun + `write_run_health_report` with an explicit status. diff --git a/.codex/skills/map-explain/SKILL.md b/.agents/skills/map-explain/SKILL.md similarity index 100% rename from .codex/skills/map-explain/SKILL.md rename to .agents/skills/map-explain/SKILL.md diff --git a/.codex/skills/map-fast/SKILL.md b/.agents/skills/map-fast/SKILL.md similarity index 100% rename from .codex/skills/map-fast/SKILL.md rename to .agents/skills/map-fast/SKILL.md diff --git a/.codex/skills/map-plan/SKILL.md b/.agents/skills/map-plan/SKILL.md similarity index 98% rename from .codex/skills/map-plan/SKILL.md rename to .agents/skills/map-plan/SKILL.md index 344cbd5b..70aa2bbf 100644 --- a/.codex/skills/map-plan/SKILL.md +++ b/.agents/skills/map-plan/SKILL.md @@ -1,6 +1,6 @@ --- name: map-plan -description: "ARCHITECT phase — decompose complex tasks into atomic subtasks with research, spec, and plan artifacts in .map//" +description: "ARCHITECT phase - decompose complex tasks into atomic subtasks with research, spec, and branch-scoped plan artifacts under .map." --- # map-plan — ARCHITECT Phase (Decomposition Only) @@ -18,7 +18,7 @@ description: "ARCHITECT phase — decompose complex tasks into atomic subtasks w - `.map//task_plan_.md` — human-readable plan with AAG contracts - `.map//step_state.json` — initialized workflow state -**Related skills:** `$map-fast` (small changes), `$map-check` (post-execution verification) +**Related skills:** `$map-efficient` (execute approved plans), `$map-fast` (small changes), `$map-check` (post-execution verification) --- diff --git a/.codex/AGENTS.md b/.codex/AGENTS.md index d0d3d827..93359884 100644 --- a/.codex/AGENTS.md +++ b/.codex/AGENTS.md @@ -5,7 +5,8 @@ This project uses the MAP (Monitor-Actor-Predictor) Framework for structured dev ## Prerequisites **Important:** You must trust this project in Codex settings for project-scoped -configuration to take effect. Without trust, `.codex/` files are ignored. +configuration to take effect. Without trust, `.codex/` config, hooks, and +agent files are ignored. Codex skills are installed under `.agents/skills`. ## Available Agents @@ -20,6 +21,7 @@ configuration to take effect. Without trust, `.codex/` files are ignored. | Skill | Purpose | |-------|---------| | $map-plan | Plan and decompose complex tasks | +| $map-efficient | Execute approved MAP plans end to end | | $map-fast | Quick implementation for small changes | | $map-check | Quality gates and verification | @@ -44,4 +46,4 @@ For write-capable MAP skills and agents: 1. Trust this project in Codex settings 2. Type `$map-plan ` to start planning -3. Follow the guided workflow +3. Type `$map-efficient` to execute an approved plan diff --git a/.map/scripts/map_orchestrator.py b/.map/scripts/map_orchestrator.py index 2bdd5038..03ea61cb 100755 --- a/.map/scripts/map_orchestrator.py +++ b/.map/scripts/map_orchestrator.py @@ -1129,7 +1129,7 @@ def validate_step( # MONITOR-side validate_mutation_boundary check only flags files # CHANGED during this subtask, not the cumulative branch diff. try: - from map_step_runner import record_subtask_baseline # noqa: WPS433 # pyright: ignore[reportMissingImports] + from map_step_runner import record_subtask_baseline # pyright: ignore[reportMissingImports] record_subtask_baseline(branch, state.current_subtask_id) except ImportError: pass @@ -1142,7 +1142,7 @@ def validate_step( blueprint_present = Path(f".map/{branch}/blueprint.json").exists() if blueprint_present: try: - from map_step_runner import validate_mutation_boundary # noqa: WPS433 # pyright: ignore[reportMissingImports] + from map_step_runner import validate_mutation_boundary # pyright: ignore[reportMissingImports] scope_report = validate_mutation_boundary( branch, state.current_subtask_id ) diff --git a/.map/scripts/map_step_runner.py b/.map/scripts/map_step_runner.py index cce70364..74fbd3d5 100755 --- a/.map/scripts/map_step_runner.py +++ b/.map/scripts/map_step_runner.py @@ -6053,7 +6053,12 @@ def refresh_blueprint_affected_files( if diff_proc.returncode == 0: for raw in diff_proc.stdout.splitlines(): path = raw.strip() - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): actual_set.add(path) except (OSError, subprocess.TimeoutExpired): pass @@ -6078,7 +6083,12 @@ def refresh_blueprint_affected_files( path = raw[3:].strip() if " -> " in path: path = path.split(" -> ", 1)[1] - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): actual_set.add(path) actual_set -= baseline_files current_files = sorted(actual_set) @@ -6707,7 +6717,12 @@ def record_subtask_baseline(branch: str, subtask_id: str) -> dict: path = raw[3:].strip() if " -> " in path: path = path.split(" -> ", 1)[1] - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): files.append(path) # Capture HEAD SHA so downstream commits can be diffed against this # baseline. Fresh repos with no commits return non-zero — fall back to @@ -6923,7 +6938,12 @@ def record_scope_baseline(branch: str) -> dict: path = raw[3:].strip() if " -> " in path: path = path.split(" -> ", 1)[1] - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): files.append(path) path = _scope_baseline_path(branch, project_dir) path.parent.mkdir(parents=True, exist_ok=True) @@ -7092,11 +7112,14 @@ def validate_mutation_boundary( # Filter framework-owned paths that are NEVER part of a subtask's mutation # surface: `.map/` carries orchestrator artifacts (blueprint, step_state, - # research outputs, scope logs) and `.codex/` mirrors Codex-side config. + # research outputs, scope logs), `.codex/` mirrors Codex-side config, and + # `.agents/` holds Codex repository skills. # Treating them as scope leaks would produce a flood of false positives. actual_set = { p for p in actual_set - if not p.startswith(".map/") and not p.startswith(".codex/") + if not p.startswith(".map/") + and not p.startswith(".codex/") + and not p.startswith(".agents/") } # Baseline filter — two layers: @@ -7229,7 +7252,8 @@ def _current_subtask_changed_files( Mirrors ``validate_mutation_boundary``'s diff strategy (commit-range diff against ``last_subtask_commit_sha`` — falling back to ``HEAD`` — unioned with ``git status --porcelain`` for uncommitted work, minus the framework - ``.map/`` / ``.codex/`` paths and the per-subtask baseline). Returns + ``.map/`` / ``.codex/`` / ``.agents/`` paths and the per-subtask baseline). + Returns ``None`` on any git failure so callers can fail safe to a full gate instead of silently assuming "no changes". """ @@ -7301,7 +7325,9 @@ def _current_subtask_changed_files( changed = { p for p in changed - if not p.startswith(".map/") and not p.startswith(".codex/") + if not p.startswith(".map/") + and not p.startswith(".codex/") + and not p.startswith(".agents/") } baseline_path = _subtask_baseline_path(branch_name, subtask_id, project_dir) diff --git a/README.md b/README.md index 920df28e..3a312454 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ Learner -> captures reusable project memory For Claude Code, MAP slash surfaces live in `.claude/skills/map-*/SKILL.md` files created by `mapify init`. The `.claude/commands/` directory is reserved for user-custom commands and a README that points back to the skill-backed MAP surfaces. The shipped skill catalog classifies skills with `skillClass`: MAP slash workflows are `task` skills, while `map-state` is a `hybrid` skill because it combines planning guidance with hooks/scripts that manage `.map//` focus artifacts. -For Codex CLI, `mapify init . --provider codex` creates `.codex/skills/`, `.codex/agents/`, `.codex/config.toml`, hooks, and shared `.map/scripts/`. +For Codex CLI, `mapify init . --provider codex` creates `.agents/skills/` for repository skills, `.codex/agents/`, `.codex/config.toml`, hooks, and shared `.map/scripts/`. Maintainers can audit both provider skill trees with `python -m mapify_cli.skill_ir src/mapify_cli/templates/skills src/mapify_cli/templates/codex/skills`. The command exits non-zero if a shipped skill has unsupported frontmatter, unresolved bundled references, or hidden prompt-injection-like text. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 5b02721e..097b7f5e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -36,7 +36,7 @@ The remainder of this file contains the deeper implementation dive (workflow-spe 1. **Reviewable Diffs**: Prefer small, contract-sized implementation steps with explicit gates over “one big AI diff”. 2. **Artifact Traceability**: Every run produces durable, human-readable artifacts (plans, checks, reviews, learnings) tied to the current git branch. -3. **Provider Portability**: Keep workflow intent stable while allowing provider-specific orchestration surfaces (`.claude/skills/` vs `.codex/skills/`). +3. **Provider Portability**: Keep workflow intent stable while allowing provider-specific orchestration surfaces (`.claude/skills/` for Claude, `.agents/skills/` plus `.codex/` config for Codex). 4. **Deterministic Guardrails**: Enforce token budgets, workflow-fit exits, clean retry, mutation boundaries, prior-stage consumption, run-health checks, and verification gates consistently. 5. **Low Overhead**: Keep the “golden path” usable as a daily driver without excessive ceremony. @@ -68,7 +68,8 @@ The remainder of this file contains the deeper implementation dive (workflow-spe - `.claude/skills/`: Skill-backed slash surfaces that define MAP sequences for Claude Code - `.claude/commands/`: Optional user-custom command directory; MAP does not ship `map-*.md` command files -- `.codex/`: Codex CLI provider surfaces generated by `mapify init . --provider codex` +- `.agents/skills/`: Codex repository skills generated by `mapify init . --provider codex` +- `.codex/`: Codex CLI config, hooks, and TOML agents generated by `mapify init . --provider codex` - `.map//`: Branch-scoped run artifacts (plans/contracts, check outputs, review notes, learning handoffs) Claude skill metadata includes `skillClass` in `.claude/skills/skill-rules.json` so the runtime contract is explicit: `task` skills behave like manual slash workflows, `reference` skills provide inline guidance, and `hybrid` skills combine reference material with declared runtime effects. Today the MAP slash surfaces are `task` skills; `map-state` is `hybrid` because it documents planning state and ships hooks/scripts that interact with `.map//` artifacts. diff --git a/docs/USAGE.md b/docs/USAGE.md index e7dab550..4fa1244d 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -159,10 +159,11 @@ codex --enable codex_hooks or upgrade Codex first. Upgrading is recommended. -This creates a `.codex/` layout instead of `.claude/`: -- `.codex/skills/map-plan/SKILL.md` — main planning skill -- `.codex/skills/map-fast/SKILL.md` — quick implementation -- `.codex/skills/map-check/SKILL.md` — quality gates +This creates a Codex layout instead of `.claude/`: +- `.agents/skills/map-plan/SKILL.md` — main planning skill +- `.agents/skills/map-efficient/SKILL.md` — state-machine plan execution +- `.agents/skills/map-fast/SKILL.md` — quick implementation +- `.agents/skills/map-check/SKILL.md` — quality gates - `.codex/agents/*.toml` — agent definitions (researcher, decomposer, monitor) - `.codex/config.toml` — project configuration - `.codex/hooks.json` + `.codex/hooks/workflow-gate.py` — edit gate enforcement diff --git a/scripts/sync-templates.sh b/scripts/sync-templates.sh index 908b4302..d58c0ebf 100755 --- a/scripts/sync-templates.sh +++ b/scripts/sync-templates.sh @@ -53,18 +53,28 @@ mkdir -p "$templates_root/map/scripts" cp -a .map/scripts/*.py "$templates_root/map/scripts/" clean_generated_artifacts "$templates_root/map" -# Sync .codex/ → templates/codex/ -if [[ -d .codex ]]; then - mkdir -p "$templates_root/codex/skills" "$templates_root/codex/agents" "$templates_root/codex/hooks" - - # Skills (preserve nested structure) +# Sync .agents/skills/ → templates/codex/skills/ +# +# Codex discovers repository skills from .agents/skills. We keep the +# distribution templates under templates/codex/skills so the Codex provider can +# install them into the official root. +if [[ -d .agents/skills ]]; then + mkdir -p "$templates_root/codex/skills" if command -v rsync &> /dev/null; then - rsync -a --delete --exclude '__pycache__' --exclude '*.pyc' --exclude '.DS_Store' .codex/skills/ "$templates_root/codex/skills/" + rsync -a --delete --exclude '__pycache__' --exclude '*.pyc' --exclude '.DS_Store' .agents/skills/ "$templates_root/codex/skills/" else rm -rf "$templates_root/codex/skills" - cp -a .codex/skills "$templates_root/codex/skills" + mkdir -p "$templates_root/codex" + cp -a .agents/skills "$templates_root/codex/skills" clean_generated_artifacts "$templates_root/codex/skills" fi +elif [[ -d "$templates_root/codex/skills" ]]; then + rm -rf "$templates_root/codex/skills" +fi + +# Sync .codex/ → templates/codex/ +if [[ -d .codex ]]; then + mkdir -p "$templates_root/codex/agents" "$templates_root/codex/hooks" # Agents if compgen -G ".codex/agents/*.toml" > /dev/null; then @@ -87,4 +97,4 @@ fi clean_generated_artifacts "$templates_root" -echo "✅ Synced .claude/*, .codex/*, and .map/scripts/* → $templates_root/" +echo "✅ Synced .claude/*, .agents/skills/*, .codex/*, and .map/scripts/* → $templates_root/" diff --git a/src/mapify_cli/__init__.py b/src/mapify_cli/__init__.py index afc3cc84..de885c2e 100644 --- a/src/mapify_cli/__init__.py +++ b/src/mapify_cli/__init__.py @@ -267,7 +267,8 @@ def count_project_markdown_files( def is_map_initialized(project_path: Path) -> bool: """Return True when the current directory looks like a MAP project. - Recognises both Claude Code layout (.claude/) and Codex layout (.codex/). + Recognises both Claude Code layout (.claude/) and Codex layout (.codex/ + config plus .agents/skills). """ claude_paths = [ project_path / ".claude" / "agents", @@ -277,7 +278,7 @@ def is_map_initialized(project_path: Path) -> bool: ] codex_paths = [ project_path / ".codex" / "config.toml", - project_path / ".codex" / "skills", + project_path / ".agents" / "skills", ] return all(p.exists() for p in claude_paths) or all(p.exists() for p in codex_paths) @@ -299,7 +300,7 @@ def get_project_health(project_path: Path) -> Dict[str, Any]: if detected == "codex": required_paths = { ".codex/config.toml": project_path / ".codex" / "config.toml", - ".codex/skills": project_path / ".codex" / "skills", + ".agents/skills": project_path / ".agents" / "skills", ".codex/agents": project_path / ".codex" / "agents", ".map/scripts": project_path / ".map" / "scripts", } @@ -838,7 +839,7 @@ def init( tracker.complete("mcp-select", f"{len(selected_mcp_servers)} servers") if provider == "codex": - # Codex provider: install .codex/ files + .map/scripts/ (skip-if-exists) + # Codex provider: install .agents/.codex files + .map/scripts/ (skip-if-exists) from mapify_cli.delivery.providers import CodexProvider tracker.add("create-codex", "Create Codex files") @@ -971,7 +972,10 @@ def init( ) steps_lines.append(" • [cyan]$map-check[/] - Quality gates and verification") steps_lines.append( - f"{step_num + 1}. Trust this project in Codex settings for .codex/ config to take effect" + " • [cyan]$map-efficient[/] - Execute approved MAP plans end to end" + ) + steps_lines.append( + f"{step_num + 1}. Trust this project in Codex settings for .codex/ config to take effect; skills live in .agents/skills" ) else: steps_lines.append(f"{step_num}. Start using MAP commands with Claude Code:") @@ -1125,7 +1129,7 @@ def doctor(debug: bool = typer.Option(False, "--debug", help="Enable debug loggi codex_dir = project_path / ".codex" codex_checks = { ".codex/config.toml": codex_dir / "config.toml", - ".codex/skills": codex_dir / "skills", + ".agents/skills": project_path / ".agents" / "skills", ".codex/agents": codex_dir / "agents", ".map/scripts": project_path / ".map" / "scripts", } diff --git a/src/mapify_cli/delivery/codex_copier.py b/src/mapify_cli/delivery/codex_copier.py index 08b7b152..a2cdaafd 100644 --- a/src/mapify_cli/delivery/codex_copier.py +++ b/src/mapify_cli/delivery/codex_copier.py @@ -1,7 +1,7 @@ """Codex CLI provider delivery module. -Copies bundled templates/codex/ into a target project's .codex/ directory -and installs AGENTS.md at the project root. +Copies bundled templates/codex/ into a target project's Codex discovery +locations and installs AGENTS.md at the project root. Never touches .claude/. """ @@ -52,7 +52,7 @@ def create_codex_files(project_path: Path) -> dict[str, int]: """Copy Codex template files into target project. Creates: - - .codex/skills/ (map-plan, map-fast, map-check, …) + - .agents/skills/ (map-plan, map-fast, map-check, ...) - .codex/agents/ (*.toml agent definitions) - .codex/config.toml - .codex/hooks.json + .codex/hooks/workflow-gate.py @@ -86,6 +86,7 @@ def create_codex_files(project_path: Path) -> dict[str, int]: counts: dict[str, int] = dict(empty_counts) codex_dir = project_path / ".codex" + agents_dir = project_path / ".agents" # ------------------------------------------------------------------ # 1. Skills @@ -95,7 +96,7 @@ def create_codex_files(project_path: Path) -> dict[str, int]: for skill_dir in skills_src.iterdir(): if not skill_dir.is_dir(): continue - skill_dst = codex_dir / "skills" / skill_dir.name + skill_dst = agents_dir / "skills" / skill_dir.name counts["skills"] += _copy_tree(skill_dir, skill_dst) # ------------------------------------------------------------------ diff --git a/src/mapify_cli/delivery/providers.py b/src/mapify_cli/delivery/providers.py index 3ca8fc88..55de0c0f 100644 --- a/src/mapify_cli/delivery/providers.py +++ b/src/mapify_cli/delivery/providers.py @@ -66,7 +66,7 @@ def install( class CodexProvider(BaseProvider): - """Codex CLI provider — installs .codex/ files from templates.""" + """Codex CLI provider — installs .agents/.codex files from templates.""" def install( self, diff --git a/src/mapify_cli/skill_ir.py b/src/mapify_cli/skill_ir.py index 14a1aa5e..3e58baf4 100644 --- a/src/mapify_cli/skill_ir.py +++ b/src/mapify_cli/skill_ir.py @@ -240,7 +240,7 @@ def parse_skill_file(skill_file: Path, *, provider: str) -> SkillIR: def _provider_from_root(root: Path) -> str: parts = set(root.parts) - if "codex" in parts or root.name == "codex": + if "codex" in parts or root.name == "codex" or ".agents" in parts: return "codex" return "claude" diff --git a/src/mapify_cli/templates/codex/AGENTS.md b/src/mapify_cli/templates/codex/AGENTS.md index d0d3d827..93359884 100644 --- a/src/mapify_cli/templates/codex/AGENTS.md +++ b/src/mapify_cli/templates/codex/AGENTS.md @@ -5,7 +5,8 @@ This project uses the MAP (Monitor-Actor-Predictor) Framework for structured dev ## Prerequisites **Important:** You must trust this project in Codex settings for project-scoped -configuration to take effect. Without trust, `.codex/` files are ignored. +configuration to take effect. Without trust, `.codex/` config, hooks, and +agent files are ignored. Codex skills are installed under `.agents/skills`. ## Available Agents @@ -20,6 +21,7 @@ configuration to take effect. Without trust, `.codex/` files are ignored. | Skill | Purpose | |-------|---------| | $map-plan | Plan and decompose complex tasks | +| $map-efficient | Execute approved MAP plans end to end | | $map-fast | Quick implementation for small changes | | $map-check | Quality gates and verification | @@ -44,4 +46,4 @@ For write-capable MAP skills and agents: 1. Trust this project in Codex settings 2. Type `$map-plan ` to start planning -3. Follow the guided workflow +3. Type `$map-efficient` to execute an approved plan diff --git a/src/mapify_cli/templates/codex/skills/map-efficient/SKILL.md b/src/mapify_cli/templates/codex/skills/map-efficient/SKILL.md new file mode 100644 index 00000000..d3add824 --- /dev/null +++ b/src/mapify_cli/templates/codex/skills/map-efficient/SKILL.md @@ -0,0 +1,258 @@ +--- +name: map-efficient +description: "State-machine MAP execution workflow for Codex. Use when implementing an approved MAP plan end to end, resuming from branch MAP task_plan or step_state.json artifacts, or running non-trivial multi-subtask work. Use map-fast for tiny one-shot edits." +--- + +# $map-efficient - MAP Execution + +Execute the approved MAP plan for the current branch. This skill is the Codex +counterpart to Claude `/map-efficient`, but it uses Codex-native instructions: +skills live under `.agents/skills`, configured Codex subagents live under +`.codex/agents`, and the current Codex session is the write-capable Actor and +final verifier unless an explicit subagent dispatch is available and useful. + +Use [efficient-reference.md](efficient-reference.md) for wave details, retry +recipes, TDD mode, commit policy, and troubleshooting. Read only the referenced +section when the workflow below points to it. + +## Mutation Boundary Constraints + +These constraints apply before any write-capable step: + +- Do not edit unrelated files, even if they are nearby or easy to clean up. +- Do not add, remove, or upgrade dependencies unless the current subtask contract explicitly names that dependency change. +- Do not refactor neighboring code unless the current validation criteria cannot pass without that exact refactor. +- If a dependency change, broad refactor, or scope expansion seems necessary, report it as a blocker/tradeoff instead of doing it silently. + +## Core Rules + +1. Run only the next state-machine phase; never skip phases. +2. Treat `.map//step_state.json` as the single source of truth. +3. Never edit `step_state.json` manually. Use `.map/scripts/map_orchestrator.py`. +4. Use `.map/scripts/map_step_runner.py` for analysis, reports, baselines, and sidecar artifacts. +5. Continue across subtask boundaries in the same invocation unless blocked, interrupted by the user, or the circuit breaker trips. +6. Use configured Codex subagents (`researcher`, `decomposer`, `monitor`) only when the workflow explicitly needs independent work. The current Codex session performs Actor edits and final verification. +7. Stop on any Monitor `valid=false` verdict and fix the issue before advancing. + +## Script Routing + +- `python3 .map/scripts/map_orchestrator.py ` owns state transitions: + `resume_from_plan`, `get_next_step`, `validate_step`, + `monitor_failed`, `record_subtask_result`, `check_circuit_breaker`, + `mark_subtask_complete`, `set_tdd_mode`, `set_waves`. +- `python3 .map/scripts/map_step_runner.py ` owns read-only analysis and + sidecar artifacts: `record_test_baseline`, `save_research`, `load_research`, + `build_context_block`, `detect_truncated_agent_output`, + `detect_actor_files_changed_mismatch`, `detect_symbol_blast_radius`, + `detect_cross_subtask_regression_risk`, `write_run_health_report`. + +## Argument Handling + +Parse optional flags, but do not require a task string when a plan or state +already exists. + +```bash +TASK_ARGS="$ARGUMENTS" +TDD_FLAG=false +if echo "$TASK_ARGS" | grep -q -- '--tdd'; then + TDD_FLAG=true + TASK_ARGS=$(echo "$TASK_ARGS" | sed 's/--tdd//g' | xargs) +fi +``` + +Empty `$TASK_ARGS` is a stop condition only when all of these are true: + +1. `.map//step_state.json` is missing. +2. `.map//task_plan_.md` is missing. +3. `$TASK_ARGS` is empty. + +Otherwise proceed to resume detection. + +## Step 0: Resume Existing State Or Plan + +Run this before validating `$TASK_ARGS`. + +```bash +BRANCH=$(git rev-parse --abbrev-ref HEAD | sed -E 's|/|-|g; s|[^a-zA-Z0-9_.-]|-|g; s|-{2,}|-|g; s|^-||; s|-$||') +STATE_FILE=".map/${BRANCH}/step_state.json" +PLAN_FILE=".map/${BRANCH}/task_plan_${BRANCH}.md" + +if [ -f "$STATE_FILE" ]; then + echo "Existing step_state.json found; continuing with get_next_step." +elif [ -f "$PLAN_FILE" ]; then + RESUME_RESULT=$(python3 .map/scripts/map_orchestrator.py resume_from_plan) + RESUME_STATUS=$(echo "$RESUME_RESULT" | jq -r '.status') + if [ "$RESUME_STATUS" != "success" ]; then + echo "resume_from_plan failed: $RESUME_RESULT" >&2 + exit 1 + fi +elif [ -z "$TASK_ARGS" ]; then + echo "No task, step_state.json, or task_plan_${BRANCH}.md found." >&2 + echo "Provide a task or run \$map-plan first." >&2 + exit 1 +fi + +if [ "$TDD_FLAG" = "true" ]; then + python3 .map/scripts/map_orchestrator.py set_tdd_mode true +fi +``` + +## Step 1: Get The Next Phase + +```bash +NEXT_STEP=$(python3 .map/scripts/map_orchestrator.py get_next_step) +STEP_ID=$(echo "$NEXT_STEP" | jq -r '.step_id') +PHASE=$(echo "$NEXT_STEP" | jq -r '.phase') +IS_COMPLETE=$(echo "$NEXT_STEP" | jq -r '.is_complete') +echo "$NEXT_STEP" +``` + +If `IS_COMPLETE=true`, go to final verification. + +## Step 2: Execute The Current Phase + +Execute only the phase returned by `get_next_step`. + +### DECOMPOSE + +Use the configured `decomposer` agent when available, or decompose directly in +the current session. Return blueprint JSON with atomic subtasks, dependencies, +validation criteria, hard/soft constraints, coverage_map, and AAG contracts. +Every coverage_map key owned by a subtask must appear as a bracket tag in that +subtask validation criterion, for example `VC1 [AC-1]: checkout retries`. + +Save `.map//blueprint.json`, then run: + +```bash +python3 .map/scripts/map_step_runner.py validate_blueprint_contract +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +``` + +### INIT_PLAN + +Generate `.map//task_plan_.md` from `blueprint.json`. Include +each subtask's `expected_diff_size`, `concern_type`, `one_logical_step`, +dependencies, AAG contract, acceptance criteria, and verification commands. + +Then validate: + +```bash +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +``` + +### REVIEW_PLAN + +Present the plan and require explicit user approval before implementation. +After approval, validate the step. + +### INIT_STATE + +Let the orchestrator create or update state. Do not write JSON by hand. + +```bash +python3 .map/scripts/map_step_runner.py record_test_baseline "$BRANCH" +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +if [ -f ".map/${BRANCH}/blueprint.json" ]; then + python3 .map/scripts/map_orchestrator.py set_waves --blueprint ".map/${BRANCH}/blueprint.json" +fi +``` + +### RESEARCH + +Use `researcher` when independent exploration is useful; otherwise research in +the current session. Persist concise findings before Actor work: + +```bash +SUBTASK_ID=$(jq -r '.current_subtask_id' ".map/${BRANCH}/step_state.json") +printf '%s' "$RESEARCH_FINDINGS" | \ + python3 .map/scripts/map_step_runner.py save_research "$BRANCH" "$SUBTASK_ID" +python3 .map/scripts/map_orchestrator.py validate_step "$STEP_ID" +``` + +### TEST_WRITER And TEST_FAIL_GATE + +Only run these in TDD mode. Write failing tests first, run them, and proceed to +Actor only when the tests fail for the intended reason. Do not edit production +code in `TEST_WRITER`. + +### ACTOR + +Load the current contract and research: + +```bash +SUBTASK_ID=$(jq -r '.current_subtask_id' ".map/${BRANCH}/step_state.json") +MAP_CONTEXT=$(python3 .map/scripts/map_step_runner.py build_context_block "$BRANCH" "$SUBTASK_ID") +RESEARCH_FINDINGS=$(python3 .map/scripts/map_step_runner.py load_research "$BRANCH" "$SUBTASK_ID") +``` + +Implement exactly the current subtask. Preserve validation criteria, +coverage_map tags, hard constraints, and documented tradeoffs. Keep edits +inside the current subtask boundary. + +Before Monitor, run the required pre-dispatch gates from +[efficient-reference.md](efficient-reference.md#pre-monitor-gates): + +```bash +python3 .map/scripts/map_step_runner.py detect_actor_files_changed_mismatch "$BRANCH" "$SUBTASK_ID" --declared "$FILES_CSV" +python3 .map/scripts/map_step_runner.py detect_symbol_blast_radius "$BRANCH" "$SUBTASK_ID" +``` + +### MONITOR + +Use the configured `monitor` agent when available, or run an independent review +pass in the current session. Validate implementation against the subtask AAG +contract, validation criteria, coverage tags, hard constraints, and relevant +soft constraints. + +If Monitor fails: + +```bash +python3 .map/scripts/map_orchestrator.py monitor_failed --feedback "$MONITOR_FEEDBACK" +``` + +Write a durable `.map//code-review-N.md` with exact issues and then fix +the current subtask. Do not advance until Monitor passes. + +On a clean pass, run the regression gate and record the subtask: + +```bash +python3 .map/scripts/map_step_runner.py detect_cross_subtask_regression_risk "$BRANCH" "$SUBTASK_ID" +python3 .map/scripts/map_orchestrator.py record_subtask_result "$SUBTASK_ID" valid \ + --files "$FILES_CSV" --summary "$ONE_LINE" --commit-sha "$SHA" +python3 .map/scripts/map_orchestrator.py validate_step 2.4 \ + --recommendation "$MONITOR_RECOMMENDATION" +python3 .map/scripts/map_step_runner.py refresh_blueprint_affected_files "$BRANCH" "$SUBTASK_ID" +``` + +### ADVANCE_SUBTASK + +This is a synthetic boundary, not a user checkpoint. Call `get_next_step` +again immediately and continue with the next subtask. + +## Step 3: Final Verification + +Run final verification for the whole plan, not only the last subtask. + +```bash +python3 .map/scripts/map_orchestrator.py check_circuit_breaker +``` + +Inspect the task plan, state file, artifact manifest, final diff, tests, and +Monitor artifacts. Run the focused and full verification commands required by +the plan. Close only when the implemented behavior and tests satisfy all +subtasks. + +Write terminal run health: + +```bash +RUN_HEALTH_STATUS="${RUN_HEALTH_STATUS:?complete|pending|blocked|wont_do|superseded}" +python3 .map/scripts/map_step_runner.py write_run_health_report \ + map-efficient \ + "$RUN_HEALTH_STATUS" +``` + +## Step 4: Final Response + +Report completed subtasks, files changed, checks run, final status, and any +remaining blockers. Mention the next command only when useful, such as +`$map-check` for a verification-only pass. diff --git a/src/mapify_cli/templates/codex/skills/map-efficient/efficient-reference.md b/src/mapify_cli/templates/codex/skills/map-efficient/efficient-reference.md new file mode 100644 index 00000000..91eab2f2 --- /dev/null +++ b/src/mapify_cli/templates/codex/skills/map-efficient/efficient-reference.md @@ -0,0 +1,117 @@ +# $map-efficient Supporting Reference + +This file holds lower-frequency details for the Codex `$map-efficient` skill. +Load only the section needed by the active phase. + +## Pre-Monitor Gates + +Before Monitor, verify that Actor output and repository state agree. + +```bash +python3 .map/scripts/map_step_runner.py detect_actor_files_changed_mismatch \ + "$BRANCH" "$SUBTASK_ID" --declared "$FILES_CSV" +python3 .map/scripts/map_step_runner.py detect_symbol_blast_radius \ + "$BRANCH" "$SUBTASK_ID" +``` + +If `detect_actor_files_changed_mismatch` reports `status_mismatch=true`, finish +the missing edits before Monitor. If `detect_symbol_blast_radius` recommends +`validate_callers`, include external callers in Monitor's review context. + +## Cross-Subtask Regression Gate + +Before committing or recording a clean Monitor result, ask whether a scoped test +run is safe: + +```bash +python3 .map/scripts/map_step_runner.py detect_cross_subtask_regression_risk \ + "$BRANCH" "$SUBTASK_ID" +``` + +If `recommended_gate == "full_suite"`, run the full suite. A focused run is +allowed only when the detector returns `scoped` and the subtask contract does +not require broader validation. + +## 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. + +Commands: + +```bash +python3 .map/scripts/map_orchestrator.py set_waves --blueprint ".map/${BRANCH}/blueprint.json" +python3 .map/scripts/map_orchestrator.py get_wave_step +python3 .map/scripts/map_orchestrator.py validate_wave_step "$STEP_ID" +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. + +## TDD Mode + +`--tdd` inserts `TEST_WRITER` and `TEST_FAIL_GATE` before `ACTOR`. + +Rules: + +- Write tests before production code. +- Run the new tests and confirm they fail for the intended reason. +- Treat tests that pass before implementation as weak tests; revise them before + Actor work. +- Do not edit production code in `TEST_WRITER`. + +## Monitor Retry Loop + +Every Monitor failure needs durable evidence: + +1. Write `.map//code-review-N.md` with the exact issue, file path, and + required fix. +2. Run `monitor_failed --feedback "$MONITOR_FEEDBACK"`. +3. Fix only the current subtask. +4. Re-run Monitor. + +If retries start repeating, check the orchestrator response for retry isolation +or circuit-breaker guidance before another Actor attempt. + +## Per-Subtask Commit Policy + +After a clean Monitor pass, a per-subtask commit is allowed and usually +preferred when the repository is in a reviewable state. Stage named files only. + +```bash +git add +git commit -m "ST-NNN: " +SHA=$(git log -1 --format=%H) +python3 .map/scripts/map_orchestrator.py record_subtask_result \ + "$SUBTASK_ID" valid --files "$FILES_CSV" --summary "$ONE_LINE" \ + --commit-sha "$SHA" +``` + +Do not use `git add .`. Do not amend a published commit. Do not bypass hooks. +If the user requested one bundled commit or the intermediate state cannot pass +hooks, document the deferral and record the subtask result without committing. + +## Final Verification + +Final verification must prove the full plan: + +- Read `.map//task_plan_.md`. +- Read `.map//step_state.json`. +- Inspect the final diff. +- Run the verification commands required by the plan. +- Confirm Monitor artifacts do not contain unresolved valid=false findings. +- Write `run_health_report.json` with `write_run_health_report`. + +## Troubleshooting + +- `resume_from_plan` fails: inspect the returned JSON and fix missing plan, + blueprint, or branch artifacts before continuing. +- `validate_blueprint_contract` fails: fix the blueprint before Actor work. +- `validate_step` rejects Monitor close: obey its recovery instruction; do not + force-advance state. +- `step_state.json` disagrees with artifacts: use orchestrator commands to + repair or resume. Do not edit the JSON manually. +- Final closeout lacks `.map//run_health_report.json`: rerun + `write_run_health_report` with an explicit status. diff --git a/src/mapify_cli/templates/codex/skills/map-plan/SKILL.md b/src/mapify_cli/templates/codex/skills/map-plan/SKILL.md index 344cbd5b..70aa2bbf 100644 --- a/src/mapify_cli/templates/codex/skills/map-plan/SKILL.md +++ b/src/mapify_cli/templates/codex/skills/map-plan/SKILL.md @@ -1,6 +1,6 @@ --- name: map-plan -description: "ARCHITECT phase — decompose complex tasks into atomic subtasks with research, spec, and plan artifacts in .map//" +description: "ARCHITECT phase - decompose complex tasks into atomic subtasks with research, spec, and branch-scoped plan artifacts under .map." --- # map-plan — ARCHITECT Phase (Decomposition Only) @@ -18,7 +18,7 @@ description: "ARCHITECT phase — decompose complex tasks into atomic subtasks w - `.map//task_plan_.md` — human-readable plan with AAG contracts - `.map//step_state.json` — initialized workflow state -**Related skills:** `$map-fast` (small changes), `$map-check` (post-execution verification) +**Related skills:** `$map-efficient` (execute approved plans), `$map-fast` (small changes), `$map-check` (post-execution verification) --- diff --git a/src/mapify_cli/templates/map/scripts/map_orchestrator.py b/src/mapify_cli/templates/map/scripts/map_orchestrator.py index 2bdd5038..03ea61cb 100755 --- a/src/mapify_cli/templates/map/scripts/map_orchestrator.py +++ b/src/mapify_cli/templates/map/scripts/map_orchestrator.py @@ -1129,7 +1129,7 @@ def validate_step( # MONITOR-side validate_mutation_boundary check only flags files # CHANGED during this subtask, not the cumulative branch diff. try: - from map_step_runner import record_subtask_baseline # noqa: WPS433 # pyright: ignore[reportMissingImports] + from map_step_runner import record_subtask_baseline # pyright: ignore[reportMissingImports] record_subtask_baseline(branch, state.current_subtask_id) except ImportError: pass @@ -1142,7 +1142,7 @@ def validate_step( blueprint_present = Path(f".map/{branch}/blueprint.json").exists() if blueprint_present: try: - from map_step_runner import validate_mutation_boundary # noqa: WPS433 # pyright: ignore[reportMissingImports] + from map_step_runner import validate_mutation_boundary # pyright: ignore[reportMissingImports] scope_report = validate_mutation_boundary( branch, state.current_subtask_id ) diff --git a/src/mapify_cli/templates/map/scripts/map_step_runner.py b/src/mapify_cli/templates/map/scripts/map_step_runner.py index cce70364..74fbd3d5 100755 --- a/src/mapify_cli/templates/map/scripts/map_step_runner.py +++ b/src/mapify_cli/templates/map/scripts/map_step_runner.py @@ -6053,7 +6053,12 @@ def refresh_blueprint_affected_files( if diff_proc.returncode == 0: for raw in diff_proc.stdout.splitlines(): path = raw.strip() - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): actual_set.add(path) except (OSError, subprocess.TimeoutExpired): pass @@ -6078,7 +6083,12 @@ def refresh_blueprint_affected_files( path = raw[3:].strip() if " -> " in path: path = path.split(" -> ", 1)[1] - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): actual_set.add(path) actual_set -= baseline_files current_files = sorted(actual_set) @@ -6707,7 +6717,12 @@ def record_subtask_baseline(branch: str, subtask_id: str) -> dict: path = raw[3:].strip() if " -> " in path: path = path.split(" -> ", 1)[1] - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): files.append(path) # Capture HEAD SHA so downstream commits can be diffed against this # baseline. Fresh repos with no commits return non-zero — fall back to @@ -6923,7 +6938,12 @@ def record_scope_baseline(branch: str) -> dict: path = raw[3:].strip() if " -> " in path: path = path.split(" -> ", 1)[1] - if path and not path.startswith(".map/") and not path.startswith(".codex/"): + if ( + path + and not path.startswith(".map/") + and not path.startswith(".codex/") + and not path.startswith(".agents/") + ): files.append(path) path = _scope_baseline_path(branch, project_dir) path.parent.mkdir(parents=True, exist_ok=True) @@ -7092,11 +7112,14 @@ def validate_mutation_boundary( # Filter framework-owned paths that are NEVER part of a subtask's mutation # surface: `.map/` carries orchestrator artifacts (blueprint, step_state, - # research outputs, scope logs) and `.codex/` mirrors Codex-side config. + # research outputs, scope logs), `.codex/` mirrors Codex-side config, and + # `.agents/` holds Codex repository skills. # Treating them as scope leaks would produce a flood of false positives. actual_set = { p for p in actual_set - if not p.startswith(".map/") and not p.startswith(".codex/") + if not p.startswith(".map/") + and not p.startswith(".codex/") + and not p.startswith(".agents/") } # Baseline filter — two layers: @@ -7229,7 +7252,8 @@ def _current_subtask_changed_files( Mirrors ``validate_mutation_boundary``'s diff strategy (commit-range diff against ``last_subtask_commit_sha`` — falling back to ``HEAD`` — unioned with ``git status --porcelain`` for uncommitted work, minus the framework - ``.map/`` / ``.codex/`` paths and the per-subtask baseline). Returns + ``.map/`` / ``.codex/`` / ``.agents/`` paths and the per-subtask baseline). + Returns ``None`` on any git failure so callers can fail safe to a full gate instead of silently assuming "no changes". """ @@ -7301,7 +7325,9 @@ def _current_subtask_changed_files( changed = { p for p in changed - if not p.startswith(".map/") and not p.startswith(".codex/") + if not p.startswith(".map/") + and not p.startswith(".codex/") + and not p.startswith(".agents/") } baseline_path = _subtask_baseline_path(branch_name, subtask_id, project_dir) diff --git a/tests/test_mapify_cli.py b/tests/test_mapify_cli.py index c2d661c0..9dbfc66e 100644 --- a/tests/test_mapify_cli.py +++ b/tests/test_mapify_cli.py @@ -1121,12 +1121,12 @@ def codex_project(self, tmp_path): return tmp_path # ------------------------------------------------------------------ # - # AC-1: .codex/skills/map-plan/SKILL.md created # + # AC-1: .agents/skills/map-plan/SKILL.md created # # ------------------------------------------------------------------ # def test_ac01_creates_skill_file(self, codex_project): """AC-1: map-plan SKILL.md must exist after init.""" - skill_file = codex_project / ".codex" / "skills" / "map-plan" / "SKILL.md" + skill_file = codex_project / ".agents" / "skills" / "map-plan" / "SKILL.md" assert skill_file.exists(), f"Expected {skill_file} to exist" # ------------------------------------------------------------------ # @@ -1135,7 +1135,7 @@ def test_ac01_creates_skill_file(self, codex_project): def test_ac02_skill_has_valid_frontmatter(self, codex_project): """AC-2: SKILL.md must start with '---' and contain name/description fields.""" - skill_file = codex_project / ".codex" / "skills" / "map-plan" / "SKILL.md" + skill_file = codex_project / ".agents" / "skills" / "map-plan" / "SKILL.md" content = skill_file.read_text(encoding="utf-8") assert content.startswith( "---" @@ -1151,7 +1151,7 @@ def test_ac02_skill_has_valid_frontmatter(self, codex_project): def test_ac03_skill_no_claude_tool_refs(self, codex_project): """AC-3: SKILL.md must not reference Claude-only tool functions.""" - skill_file = codex_project / ".codex" / "skills" / "map-plan" / "SKILL.md" + skill_file = codex_project / ".agents" / "skills" / "map-plan" / "SKILL.md" content = skill_file.read_text(encoding="utf-8") forbidden_patterns = [ "Agent(", @@ -1183,6 +1183,9 @@ def test_ac04_creates_agents_md(self, codex_project): assert agents_md.is_symlink() or len(content) > 0, "AGENTS.md must be non-empty" if not agents_md.is_symlink(): assert "$map-plan" in content, "Codex AGENTS.md must document skill invocation with $" + assert ( + "$map-efficient" in content + ), "Codex AGENTS.md must document the execution skill" assert "codex_hooks" not in content, ( "Codex AGENTS.md must not document deprecated codex_hooks" ) @@ -1287,7 +1290,7 @@ def test_ac08_template_sync_enforced(self): def test_ac09_skill_has_all_steps(self, codex_project): """AC-9: SKILL.md must contain all 9 step section headers.""" - skill_file = codex_project / ".codex" / "skills" / "map-plan" / "SKILL.md" + skill_file = codex_project / ".agents" / "skills" / "map-plan" / "SKILL.md" content = skill_file.read_text(encoding="utf-8") expected_steps = [ "## Step 0", @@ -1304,48 +1307,54 @@ def test_ac09_skill_has_all_steps(self, codex_project): assert step_header in content, f"SKILL.md must contain '{step_header}'" # ------------------------------------------------------------------ # - # AC-10: No Claude references in any .codex/ file # + # AC-10: No Claude references in any Codex provider file # # ------------------------------------------------------------------ # def test_ac10_no_claude_refs_anywhere(self, codex_project): - """AC-10: No .codex/ file should reference Claude-specific tool APIs.""" - codex_dir = codex_project / ".codex" + """AC-10: No Codex provider file should reference Claude-specific tool APIs.""" claude_tool_patterns = [ "Agent(", "AskUserQuestion(", "subagent_type=", ] violations: list[str] = [] - for file_path in codex_dir.rglob("*"): - if not file_path.is_file(): - continue - try: - content = file_path.read_text(encoding="utf-8") - except (UnicodeDecodeError, PermissionError): - continue - for pattern in claude_tool_patterns: - if pattern in content: - rel = file_path.relative_to(codex_project) - violations.append(f"{rel}: contains '{pattern}'") + for root in (codex_project / ".codex", codex_project / ".agents"): + for file_path in root.rglob("*"): + if not file_path.is_file(): + continue + try: + content = file_path.read_text(encoding="utf-8") + except (UnicodeDecodeError, PermissionError): + continue + for pattern in claude_tool_patterns: + if pattern in content: + rel = file_path.relative_to(codex_project) + violations.append(f"{rel}: contains '{pattern}'") assert ( not violations - ), "Claude-specific tool references found in .codex/ files:\n" + "\n".join( + ), "Claude-specific tool references found in Codex provider files:\n" + "\n".join( violations ) # ------------------------------------------------------------------ # - # AC-11: Stub skills map-fast and map-check exist # + # AC-11: Codex skills map-fast, map-check, and map-efficient exist # # ------------------------------------------------------------------ # def test_ac11_stub_skills_exist(self, codex_project): - """AC-11: .codex/skills/map-fast/SKILL.md and map-check/SKILL.md must exist.""" - skills_dir = codex_project / ".codex" / "skills" + """AC-11: Codex skills must exist under the official .agents/skills root.""" + skills_dir = codex_project / ".agents" / "skills" assert ( skills_dir / "map-fast" / "SKILL.md" - ).exists(), ".codex/skills/map-fast/SKILL.md must exist" + ).exists(), ".agents/skills/map-fast/SKILL.md must exist" assert ( skills_dir / "map-check" / "SKILL.md" - ).exists(), ".codex/skills/map-check/SKILL.md must exist" + ).exists(), ".agents/skills/map-check/SKILL.md must exist" + assert ( + skills_dir / "map-efficient" / "SKILL.md" + ).exists(), ".agents/skills/map-efficient/SKILL.md must exist" + assert ( + skills_dir / "map-efficient" / "efficient-reference.md" + ).exists(), ".agents/skills/map-efficient/efficient-reference.md must exist" # ------------------------------------------------------------------ # # AC-12: hooks.json and workflow-gate.py both created # @@ -1402,7 +1411,7 @@ def test_ac14_codex_init_no_claude_dir(self, codex_project): def test_ac15_spec_review_step(self, codex_project): """AC-15: SKILL.md must include a spawn_agent call using 'monitor' agent.""" - skill_file = codex_project / ".codex" / "skills" / "map-plan" / "SKILL.md" + skill_file = codex_project / ".agents" / "skills" / "map-plan" / "SKILL.md" content = skill_file.read_text(encoding="utf-8") # The SPEC_REVIEW step uses spawn_agent with agent_type="monitor" assert "spawn_agent(" in content, "SKILL.md must contain spawn_agent(" @@ -1473,10 +1482,12 @@ def test_ac18_hooks_matcher_is_bash(self, codex_project): def test_ac19_codex_discovery_paths(self, codex_project): """AC-19: Validate that Codex files are at the discovery paths Codex expects.""" codex_dir = codex_project / ".codex" + skills_dir = codex_project / ".agents" / "skills" expected_paths = [ - codex_dir / "skills" / "map-plan" / "SKILL.md", - codex_dir / "skills" / "map-fast" / "SKILL.md", - codex_dir / "skills" / "map-check" / "SKILL.md", + skills_dir / "map-plan" / "SKILL.md", + skills_dir / "map-fast" / "SKILL.md", + skills_dir / "map-check" / "SKILL.md", + skills_dir / "map-efficient" / "SKILL.md", codex_dir / "agents", codex_dir / "config.toml", ] @@ -1489,6 +1500,9 @@ def test_ac19_codex_discovery_paths(self, codex_project): assert ( toml_count >= 1 ), f".codex/agents/ must have at least 1 *.toml for agent discovery, found {toml_count}" + assert not ( + codex_dir / "skills" + ).exists(), "Codex skills must be installed under .agents/skills, not .codex/skills" # ------------------------------------------------------------------ # # AC-20: workflow-gate.py blocks file-modifying commands in RESEARCH # @@ -1555,6 +1569,27 @@ def test_ac21_upgrade_codex_project_no_claude(self, codex_project): "mapify init . --provider codex --force" in result.output ), "upgrade must tell codex users to re-run init with --provider codex" + def test_ac22_map_efficient_state_machine_markers(self, codex_project): + """AC-22: $map-efficient documents the required state-machine commands.""" + skill_file = ( + codex_project / ".agents" / "skills" / "map-efficient" / "SKILL.md" + ) + content = skill_file.read_text(encoding="utf-8") + for marker in [ + "resume_from_plan", + "get_next_step", + "validate_step", + "record_subtask_result", + "write_run_health_report", + ]: + assert marker in content, f"$map-efficient must document {marker}" + + mutation_index = content.index("## Mutation Boundary Constraints") + implement_index = content.index("Implement exactly") + assert ( + mutation_index < implement_index + ), "Mutation boundary constraints must appear before implementation directives" + class TestDetectProviderEdgeCases: """TESTS-1: _detect_provider and is_map_initialized edge cases.""" @@ -1581,7 +1616,7 @@ def test_is_map_initialized_codex_layout(self, tmp_path): (tmp_path / ".codex" / "config.toml").parent.mkdir(parents=True) (tmp_path / ".codex" / "config.toml").write_text("[codex]\n") - (tmp_path / ".codex" / "skills").mkdir(parents=True) + (tmp_path / ".agents" / "skills").mkdir(parents=True) assert is_map_initialized(tmp_path) is True def test_is_map_initialized_neither_layout(self, tmp_path): diff --git a/tests/test_skill_ir.py b/tests/test_skill_ir.py index 15662edb..fc216721 100644 --- a/tests/test_skill_ir.py +++ b/tests/test_skill_ir.py @@ -76,7 +76,12 @@ def test_audit_all_shipped_claude_and_codex_skills_parse_to_ir() -> None: assert not all_findings assert {ir.provider for ir in all_irs} == {"claude", "codex"} - assert {ir.name for ir in all_irs} >= {"map-plan", "map-fast", "map-check"} + assert {ir.name for ir in all_irs} >= { + "map-plan", + "map-efficient", + "map-fast", + "map-check", + } assert all(len(ir.content_hash) == 64 for ir in all_irs) diff --git a/tests/test_skills.py b/tests/test_skills.py index 3eb91aa7..30fbecbd 100644 --- a/tests/test_skills.py +++ b/tests/test_skills.py @@ -100,6 +100,7 @@ CODEX_MUTATION_BOUNDARY_SURFACES = [ Path("AGENTS.md"), Path("skills") / "map-fast" / "SKILL.md", + Path("skills") / "map-efficient" / "SKILL.md", ] MUTATION_BOUNDARY_REQUIRED_PHRASES = [ @@ -2291,4 +2292,3 @@ def test_lightweight_mode_drops_to_monitor_only( f"{skill_path}: lightweight mode must drop Predictor / Evaluator " "to keep speculation off an empty bundle." ) - diff --git a/tests/test_template_sync.py b/tests/test_template_sync.py index fe784567..4a266c58 100644 --- a/tests/test_template_sync.py +++ b/tests/test_template_sync.py @@ -392,21 +392,26 @@ def test_workflow_rules_declare_execution_policies(self, project_root): class TestCodexTemplateSynchronization: - """Test that Codex templates are synchronized between .codex/ and templates/codex/.""" + """Test that Codex source files are synchronized with templates/codex/.""" - # Each tuple: (source relative to .codex/, template relative to templates/codex/) + # Each tuple: (source relative to project root, template relative to templates/codex/) CODEX_FILES = [ - ("skills/map-plan/SKILL.md", "skills/map-plan/SKILL.md"), - ("skills/map-fast/SKILL.md", "skills/map-fast/SKILL.md"), - ("skills/map-check/SKILL.md", "skills/map-check/SKILL.md"), - ("skills/map-explain/SKILL.md", "skills/map-explain/SKILL.md"), - ("agents/researcher.toml", "agents/researcher.toml"), - ("agents/decomposer.toml", "agents/decomposer.toml"), - ("agents/monitor.toml", "agents/monitor.toml"), - ("config.toml", "config.toml"), - ("hooks.json", "hooks.json"), - ("hooks/workflow-gate.py", "hooks/workflow-gate.py"), - ("AGENTS.md", "AGENTS.md"), + (".agents/skills/map-plan/SKILL.md", "skills/map-plan/SKILL.md"), + (".agents/skills/map-fast/SKILL.md", "skills/map-fast/SKILL.md"), + (".agents/skills/map-check/SKILL.md", "skills/map-check/SKILL.md"), + (".agents/skills/map-explain/SKILL.md", "skills/map-explain/SKILL.md"), + (".agents/skills/map-efficient/SKILL.md", "skills/map-efficient/SKILL.md"), + ( + ".agents/skills/map-efficient/efficient-reference.md", + "skills/map-efficient/efficient-reference.md", + ), + (".codex/agents/researcher.toml", "agents/researcher.toml"), + (".codex/agents/decomposer.toml", "agents/decomposer.toml"), + (".codex/agents/monitor.toml", "agents/monitor.toml"), + (".codex/config.toml", "config.toml"), + (".codex/hooks.json", "hooks.json"), + (".codex/hooks/workflow-gate.py", "hooks/workflow-gate.py"), + (".codex/AGENTS.md", "AGENTS.md"), ] @pytest.fixture @@ -416,8 +421,8 @@ def project_root(self): @pytest.fixture def codex_source_dir(self, project_root): - """Get .codex/ directory (development source).""" - return project_root / ".codex" + """Get project root for Codex development sources.""" + return project_root @pytest.fixture def codex_templates_dir(self, project_root): @@ -433,7 +438,7 @@ def test_codex_template_exists( template_file = codex_templates_dir / template_rel assert source_file.exists(), ( - f"Source file missing from .codex/: {source_rel}. " + f"Codex source file missing: {source_rel}. " f"Expected at: {source_file}" ) assert template_file.exists(), ( @@ -453,7 +458,7 @@ def test_codex_template_content_identical( pytest.skip(f"{source_rel} doesn't exist in both locations") assert filecmp.cmp(source_file, template_file, shallow=False), ( - f"Content mismatch between .codex/{source_rel} and " + f"Content mismatch between {source_rel} and " f"templates/codex/{template_rel}. " f"Run 'make sync-templates' to fix" )