Conversation
Replace full plan injection with two-layer "active window" system: - Hook layer: goal + subtask title in ≤500 char PreToolUse reminders - Actor prompt: structured <map_context> block with current subtask details, sibling summaries, upstream dependency results, and repo delta - New StepState fields (subtask_results, last_subtask_commit_sha) for per-subtask outcome tracking and differential insight baseline - New compute_differential_insight() for git-diff-based change tracking
- Add 23 tests for all new functions: compute_differential_insight, load_blueprint, get_subtask_from_blueprint, get_upstream_ids, build_context_block, StepState.record_subtask_result (867 total) - Wire pipeline: record subtask_results + last_subtask_commit_sha in map-efficient.md after Monitor+tests pass (enables Upstream Results and Repo Delta sections) - Unify goal regex: read_current_goal now matches ## Goal|Overview (consistent with hook's load_goal_and_title) - Fix 500-char truncation: use [:497]+"..." consistently for REQUIRED - Fix misleading comment in load_goal_and_title docstring
- Replace inline subprocess in build_context_block() with call to compute_differential_insight() from repo_insight.py (DRY fix) - Move subprocess import to module-level in map_step_runner.py - Fix shell injection: use stdin-based JSON passing instead of interpolating FILES_JSON into Python source in map-efficient.md - Add optional commit_sha param to StepState.record_subtask_result() - Add 5 tests for Repo Delta path in build_context_block - Add 7 tests for load_goal_and_title in workflow-context-injector - Add 7 tests for format_reminder 500-char truncation logic - Add 2 tests for record_subtask_result commit_sha behavior
…, word truncation - ARCH-1: Add record_subtask_result + build_context_block to CLI dispatch table; replace inline Python in map-efficient.md with single CLI call - ARCH-2: Resolve CLAUDE_PROJECT_DIR in build_context_block() for correct path resolution regardless of CWD - ARCH-3: Add _sanitize_branch() to build_context_block() for path safety - QUALITY-1: Extract GOAL_HEADING_RE constant in both map_step_runner.py and workflow-context-injector.py with "keep in sync" comments - QUALITY-2: Guard against empty subtask_id via CLI dispatch validation - QUALITY-3: Surface deleted_files in Repo Delta context block section - QUALITY-4: Word-boundary truncation via _truncate_at_word() helper - TESTS-1: Integration test for record → build_context_block → upstream - TESTS-2: Edge case tests for empty files_changed/summary - TESTS-3: Updated truncation test to verify word-boundary behavior
There was a problem hiding this comment.
Pull request overview
Adds “context-aware steps” so MAP workflows inject a focused, current-subtask view instead of dumping full plans/logs—improving token efficiency and keeping the Actor oriented on the active work.
Changes:
- Extend the PreToolUse hook reminder to include goal + subtask title with a hard ≤500-char cap.
- Add structured
<map_context>assembly (goal, current subtask details, plan overview, upstream results, repo delta) plus new StepState fields to persist subtask outcomes and baseline SHA. - Add
compute_differential_insight()(git-diff-based) and expand unit tests + documentation/changelog.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_workflow_context_injector.py | Adds direct-function tests for goal/title loading and 500-char reminder truncation behavior. |
| tests/test_repo_insight.py | Adds tests for compute_differential_insight() success/error paths via subprocess mocking. |
| tests/test_map_step_runner.py | Adds tests for blueprint loading/context block generation and Repo Delta behavior (mocked). |
| tests/test_map_orchestrator.py | Adds StepState tests for new subtask_results and last_subtask_commit_sha fields. |
| src/mapify_cli/templates/map/scripts/map_step_runner.py | Implements context block helpers + CLI dispatch for recording subtask results and building context. |
| .map/scripts/map_step_runner.py | Keeps the runtime .map/scripts version in sync with the template updates. |
| src/mapify_cli/templates/map/scripts/map_orchestrator.py | Extends StepState with subtask results recording + SHA baseline persistence. |
| .map/scripts/map_orchestrator.py | Keeps the runtime .map/scripts version in sync with the template updates. |
| src/mapify_cli/templates/hooks/workflow-context-injector.py | Adds goal/title enrichment to hook reminders and enforces ≤500-char truncation. |
| .claude/hooks/workflow-context-injector.py | Keeps the repo’s hook implementation in sync with the template changes. |
| src/mapify_cli/templates/commands/map-efficient.md | Documents <map_context> structure and adds “record subtask result” snippet after test gate. |
| .claude/commands/map-efficient.md | Keeps the repo command doc in sync with the template changes. |
| src/mapify_cli/repo_insight.py | Adds compute_differential_insight() to compute changed/deleted files between SHAs. |
| docs/ARCHITECTURE.md | Documents the two-layer injection strategy and data sources. |
| CHANGELOG.md | Adds an Unreleased entry describing the new context-aware injection capabilities. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| branch = _sanitize_branch(branch) | ||
| project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())) | ||
|
|
||
| blueprint = load_blueprint(branch) | ||
| if not blueprint: | ||
| return "" |
There was a problem hiding this comment.
build_context_block() uses project_dir = Path(os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())) for step_state.json and compute_differential_insight(), but load_blueprint() / read_current_goal() still resolve .map/... relative to the current working directory. If CLAUDE_PROJECT_DIR is set and the process CWD differs from the project root, this can mix state from one directory with blueprint/plan from another (or fail to find them). Consider making all .map/<branch>/... reads consistently relative to a single base (e.g., project_dir) or dropping project_dir entirely if CWD is the intended source of truth.
| branch = _sanitize_branch(branch) | ||
| project_dir = Path(os.environ.get("CLAUDE_PROJECT_DIR", os.getcwd())) | ||
|
|
||
| blueprint = load_blueprint(branch) | ||
| if not blueprint: | ||
| return "" |
There was a problem hiding this comment.
build_context_block() uses project_dir = Path(os.environ.get('CLAUDE_PROJECT_DIR', os.getcwd())) for step_state.json and compute_differential_insight(), but load_blueprint() / read_current_goal() still resolve .map/... relative to the current working directory. If CLAUDE_PROJECT_DIR is set and the process CWD differs from the project root, this can mix state from one directory with blueprint/plan from another (or fail to find them). Consider making all .map/<branch>/... reads consistently relative to a single base (e.g., project_dir) or dropping project_dir entirely if CWD is the intended source of truth.
| Returns: | ||
| Dict with changed_files, deleted_files, since_sha, current_sha. | ||
| On error: dict with empty lists and error key. | ||
| """ | ||
| if since_sha is None: | ||
| return {"changed_files": [], "deleted_files": [], "note": "no baseline SHA"} | ||
|
|
There was a problem hiding this comment.
Docstring/API mismatch: the docstring says the return always includes since_sha and current_sha, but the since_sha is None branch returns only changed_files, deleted_files, and note. Either include since_sha/current_sha in this branch as well (e.g., since_sha: None, current_sha: <HEAD or 'unknown'>) or update the docstring to reflect that these keys are optional.
| "status": status, | ||
| "summary": summary, | ||
| } | ||
| if commit_sha is not None: |
There was a problem hiding this comment.
record_subtask_result() updates last_subtask_commit_sha whenever commit_sha is not None. This will also overwrite the stored SHA if an empty string is passed (e.g., a failed git rev-parse call that still produces commit_sha=''). Consider only updating the baseline when commit_sha is truthy / non-empty, or normalizing empty strings to None, to avoid unintentionally clearing the baseline for Repo Delta.
| if commit_sha is not None: | |
| if commit_sha: |
.map/scripts/map_orchestrator.py
Outdated
| "status": status, | ||
| "summary": summary, | ||
| } | ||
| if commit_sha is not None: |
There was a problem hiding this comment.
record_subtask_result() updates last_subtask_commit_sha whenever commit_sha is not None. This will also overwrite the stored SHA if an empty string is passed (e.g., a failed git rev-parse call that still produces commit_sha=''). Consider only updating the baseline when commit_sha is truthy / non-empty, or normalizing empty strings to None, to avoid unintentionally clearing the baseline for Repo Delta.
| if commit_sha is not None: | |
| # Only update baseline when we have a non-empty commit SHA to avoid | |
| # unintentionally clearing last_subtask_commit_sha with an empty string. | |
| if commit_sha: |
| CURRENT_SHA=$(git rev-parse HEAD) | ||
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\", \"commit_sha\": \"${CURRENT_SHA}\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result |
There was a problem hiding this comment.
The new snippet records commit_sha via CURRENT_SHA=$(git rev-parse HEAD) and always passes it into record_subtask_result. If git rev-parse fails (e.g., not in a git repo), this can pass an empty SHA and potentially wipe the stored baseline used for Repo Delta. Consider guarding this so commit_sha is only sent when non-empty / command succeeds (or omit the field entirely on failure).
| CURRENT_SHA=$(git rev-parse HEAD) | |
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\", \"commit_sha\": \"${CURRENT_SHA}\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result | |
| if CURRENT_SHA=$(git rev-parse HEAD 2>/dev/null); then | |
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\", \"commit_sha\": \"${CURRENT_SHA}\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result | |
| else | |
| # Git SHA unavailable (e.g., not a git repo); record result without commit_sha to avoid corrupting Repo Delta baseline. | |
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result | |
| fi |
.claude/commands/map-efficient.md
Outdated
| CURRENT_SHA=$(git rev-parse HEAD) | ||
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\", \"commit_sha\": \"${CURRENT_SHA}\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result |
There was a problem hiding this comment.
The new snippet records commit_sha via CURRENT_SHA=$(git rev-parse HEAD) and always passes it into record_subtask_result. If git rev-parse fails (e.g., not in a git repo), this can pass an empty SHA and potentially wipe the stored baseline used for Repo Delta. Consider guarding this so commit_sha is only sent when non-empty / command succeeds (or omit the field entirely on failure).
| CURRENT_SHA=$(git rev-parse HEAD) | |
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\", \"commit_sha\": \"${CURRENT_SHA}\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result | |
| CURRENT_SHA=$(git rev-parse HEAD 2>/dev/null || echo "") | |
| if [ -n "$CURRENT_SHA" ]; then | |
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\", \"commit_sha\": \"${CURRENT_SHA}\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result | |
| else | |
| echo "{\"files\": ${FILES_JSON}, \"status\": \"valid\", \"summary\": \"Monitor passed + tests passed\"}" | python3 .map/scripts/map_step_runner.py record_subtask_result | |
| fi |
…SHA, bash guard - load_blueprint() accepts optional project_dir; build_context_block() passes CLAUDE_PROJECT_DIR consistently to all .map/ file reads - compute_differential_insight() docstring reflects optional keys - record_subtask_result uses truthy check (if commit_sha:) to prevent empty string overwriting baseline SHA - map-efficient.md guards git rev-parse HEAD with fallback
No description provided.