Phase B: personal/repo-global learned-rules layer + MONITOR-gate fix#153
Conversation
…-001) Add three read-only module-level helpers to workflow-context-injector.py (not yet wired into main() — that is ST-002): - _load_personal_rules: reads .map/personal/rules/learned/*.md, symlink-safe, error-swallowing, returns (0,"") when absent/empty (HC-1/INV-1/E2/E4). - _sanitize_fence_content: strips case-insensitive <personal-rules> delimiters so user content cannot close the fence early (INV-6/E7). - _build_personal_block: budget-capped, always-well-formed <personal-rules> fence with [... trimmed] marker on overflow (INV-4/E3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…key (ST-002) Assemble additionalContext = reminder + sep + personal_block on the existing if-reminder path (SC-1). Reminder-first (capped 700), personal block capped at 10000-len(reminder)-len(sep) with a pre-emit len<=10000 assert (INV-4). Both dedup sites key on the full assembled string so same-turn personal edits yield a fresh injection (INV-7). MAP_INVOKED_BY guard stays the first statement of main() (HC-4/INV-3). No personal rules -> assembled == reminder byte-for-byte (HC-3); lint green, 73 scoped tests pass, full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Step 3 now documents: (AC-5) a write-time personal vs public layer choice reusing the same 6-category map and bullet format (personal -> .map/personal/rules/learned/<category>.md, public -> .claude/rules/learned/); (AC-6) promote-existing as a MOVE personal->public, idempotent per the E6 exact bold-title match key (same-title -> skip insert, still remove personal copy); (AC-10) the D2 limitation note that personal rules inject only during active MAP workflows when step_state.json is present, not on every prompt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…004) Explicit .map/personal/ entry adjacent to the .map/* block, immediately followed by a comment marking it user-local and not shipped. Redundant over .map/* but kept explicit for intent + defense in depth (AC-7). .gitignore is not templated, so no src/mapify_cli/templates/ copy is needed (AC-8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-005) make sync-templates mirrors the ST-001/ST-002 injector changes and the ST-003 map-learn/SKILL.md changes into src/mapify_cli/templates/. Template copies are byte-identical to the .claude/ dev copies (INV-5/HC-5); tests/test_template_sync.py passes (53). .gitignore is intentionally not templated (AC-8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (ST-006) Adds 5 tests to tests/test_workflow_context_injector.py (fresh tmp dir per subprocess case to dodge per-turn dedup, INV-7): - test_vc1_personal_present (AC-1): fence + single banner + content - test_vc2_personal_absent (AC-2): structural absence of fence/banner - test_vc3_over_budget (AC-3/E3): [... trimmed] + closing tag + total <= 10000 - test_vc4_delimiter_sanitization (AC-9/INV-6/E7): no early fence close - test_vc5_promote_idempotent (AC-11): E6 bold-title match -> no duplicate, personal copy removed Full suite green (78 passed across injector + hook patterns). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ICT_SCOPE plan MAP_MONITOR_HOTFIX now defaults ON: Edit/Write/MultiEdit are permitted during the MONITOR phase by default (Actor routinely lands a test/nit while the Monitor verdict is captured). Set MAP_MONITOR_HOTFIX=0 to restore strict read-only MONITOR. Applied to both hook trees (.claude + .codex), synced to templates. tests/test_workflow_gate.py: - test_allows_edit_during_monitor_phase_by_default (new default) - test_monitor_strict_mode_blocks_edit (MAP_MONITOR_HOTFIX=0 opt-out; deny msg documents the opt-out + monitor_failed) - placeholder "non-editing phase" tests switched MONITOR -> PREDICTOR so each still asserts a genuinely-gated phase. docs/improvement-plan.md: appended "Phase B run - framework gate findings" with the MONITOR fix, the still-open MAP_STRICT_SCOPE work (#4 ACTOR-diff gate, #6 detect_* receipts) + exact post-fix test plans, and the not-fixed items (#1 state/git reconcile, #3 idempotency, #2 false alarm). Original REGISTRY/FOCUS backlog preserved. Full suite: 1786 passed. lint-hooks green. 4-copy gate parity intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a personal, gitignored learned-rules layer for active MAP workflows and changes the MONITOR-phase gate so edits are allowed by default unless strict mode is explicitly enabled.
Changes:
- Adds personal-rule injection into
workflow-context-injector.pywith sanitization, fencing, dedup integration, and budget enforcement. - Updates
map-learnskill guidance for personal vs public rules and promotion semantics. - Changes
workflow-gate.pyMONITOR behavior across Claude, Codex, and template copies, with corresponding tests and documentation.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
.claude/hooks/workflow-context-injector.py |
Loads and injects .map/personal/rules/learned/*.md into hook context. |
src/mapify_cli/templates/hooks/workflow-context-injector.py |
Template copy of the personal-rule injector changes. |
.claude/hooks/workflow-gate.py |
Makes MONITOR edits allowed by default, with MAP_MONITOR_HOTFIX=0 strict mode. |
.codex/hooks/workflow-gate.py |
Codex copy of the MONITOR gate behavior change. |
src/mapify_cli/templates/hooks/workflow-gate.py |
Claude template copy of the MONITOR gate change. |
src/mapify_cli/templates/codex/hooks/workflow-gate.py |
Codex template copy of the MONITOR gate change. |
.claude/skills/map-learn/SKILL.md |
Documents personal/public learned-rule targeting and promotion. |
src/mapify_cli/templates/skills/map-learn/SKILL.md |
Template copy of the updated map-learn skill. |
tests/test_workflow_gate.py |
Updates gate expectations for permissive MONITOR default and strict opt-out. |
tests/test_workflow_context_injector.py |
Adds subprocess coverage for personal-rule injection behavior. |
.gitignore |
Explicitly documents ignoring .map/personal/. |
docs/improvement-plan.md |
Records MONITOR gate findings and remaining strict-scope follow-up work. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _promote(bullet, personal_file, public_file): | ||
| if _bullet_present_in_file(bullet, public_file): |
There was a problem hiding this comment.
Good catch — addressed in eea4b6f. Added test_vc5_promote_idempotency_rule_documented_in_shipped_skill which pins the exact E6 bold-title idempotency sentence plus the skip-insert / always-remove-personal behaviour markers in BOTH shipped SKILL.md copies (dev + template). Verified the guard fails when the prose is reworded, so prose drift now breaks the suite.
…(Copilot review) Copilot flagged that test_vc5_promote_idempotent only exercises a helper defined inside the test body, so it would still pass if map-learn/SKILL.md dropped or reworded the bold-title idempotency rule it simulates. Add test_vc5_promote_idempotency_rule_documented_in_shipped_skill, which pins the exact E6 match-key sentence plus the skip-insert / always-remove-personal behaviour markers in BOTH shipped copies (dev + template). Verified the guard fails when the prose is reworded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Two related units of work on one branch.
1. Phase B — personal/repo-global learned-rules layer (ST-001…ST-006)
A gitignored
.map/personal/rules/learned/layer surfaced via the PreToolUseworkflow-context-injector.pyhook inside a sanitized, budget-capped<personal-rules>fence, plus/map-learnpersonal-vs-public choice +promotion,
.gitignoreintent, template-sync, and tests.773395b— 3 read-only injector helpers (loader / sanitizer /fence-builder): symlink-safe, error-swallowing,
(0,"")when absent,delimiter-sanitized (INV-6), budget-trimmed well-formed fence (INV-4).
5af9ad2— wire personal block intomain():reminder + sep + block, dedup over the full assembled string (INV-7),MAP_INVOKED_BYguardstays first (HC-4), total ≤ 10000 (INV-4); byte-for-byte unchanged when no
personal rules exist (HC-3).
4ecf0fb—map-learn/SKILL.md: write-time personal-vs-publicchoice (AC-5), promote-existing with the E6 bold-title match key (AC-6),
D2 limitation note (AC-10).
232d3c5— documented.map/personal/entry in.gitignore(AC-7).45ee857—make sync-templates→ template copies byte-identical (INV-5).4066049— 5 personal-layer subprocess tests + promote-idempotency.2. Framework fix — MONITOR-phase Edit gate (
4360d56)MAP_MONITOR_HOTFIXnow defaults on: Edit/Write/MultiEdit are allowedduring the MONITOR phase by default (Actor routinely lands a test/nit while the
Monitor verdict is captured);
MAP_MONITOR_HOTFIX=0restores strict read-onlyMONITOR. Applied to both hook trees (
.claude+.codex) and synced totemplates (4-copy parity).
docs/improvement-plan.mdgains a "Phase B run —framework gate findings" section recording this fix, the still-open
MAP_STRICT_SCOPEwork (#4 ACTOR-diff gate, #6 detect_* receipts) with exactpost-fix test plans, and the deliberately-not-fixed items (#1 state/git
reconcile, #3 idempotency asymmetry, #2 which was a false alarm).
Test plan
python3 -m pytest -q→ 1786 passed, 4 skippedpython3 scripts/lint-hooks.py→ 12/12 hooks conform (recursion guard intact)pytest tests/test_template_sync.py→ green; 4-copy gate parity verifiedNotes
landed as a follow-up commit on the same branch after explicit user request.
🤖 Generated with Claude Code