feat: persist /map-learn lessons to .claude/rules/#91
Conversation
…oading Previously /map-learn extracted lessons via Reflector but output was ephemeral — never persisted where Claude Code loads automatically. The learning loop was broken. Now /map-learn writes lessons to .claude/rules/learned/*.md files that Claude Code natively loads at session start (unconditional or path-scoped via YAML frontmatter). This closes the LEARN loop from the SPEC→PLAN→TEST→CODE→REVIEW→LEARN cycle. Changes: - Rewrite map-learn.md: add Step 2a (read existing rules for dedup), Step 3 (write rules files with section mapping and path frontmatter), Step 4 (updated summary with persistence info) - Add create_rules_dir() to file_copier.py: creates .claude/rules/learned/ with README during mapify init, never touched by upgrade - Wire create_rules_dir into init flow - Add rules template: src/mapify_cli/templates/rules/learned/README.md - Add tests: rules dir creation, preservation, idempotency, map-learn references rules directory
There was a problem hiding this comment.
Pull request overview
Adds first-class support for persisting /map-learn outputs into a project-owned .claude/rules/learned/ directory so future Claude Code sessions can auto-load lessons.
Changes:
- Introduces
create_rules_dir()and wires it intomapify initto create.claude/rules/learned/and install a README template. - Updates the
map-learn.mdcommand template to read existing learned rules (dedup context) and write new topic-based rules files under.claude/rules/learned/. - Adds tests covering rules-dir creation/idempotency and verifying the
map-learn.mdtemplate references persistence behavior.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_decomposition.py | Adds unit tests for .claude/rules/learned/ creation + README preservation/idempotency. |
| tests/test_command_templates.py | Adds template-level assertions that /map-learn persists to .claude/rules/learned/ and mentions dedup + “Write Rules Files”. |
| src/mapify_cli/templates/rules/learned/README.md | Adds the shipped README template describing learned rules usage. |
| src/mapify_cli/templates/commands/map-learn.md | Updates the canonical /map-learn instructions to dedup from existing rules and write topic files. |
| src/mapify_cli/delivery/file_copier.py | Implements create_rules_dir() to create the directory and install README from templates. |
| src/mapify_cli/delivery/init.py | Exports create_rules_dir. |
| src/mapify_cli/init.py | Calls create_rules_dir() during mapify init and reports installed file count. |
| .claude/commands/map-learn.md | Keeps the repo’s .claude command in sync with the canonical template. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| """Create .claude/rules/learned/ directory with README. | ||
|
|
||
| Creates the directory structure for persisting lessons extracted by | ||
| /map-learn. The README is copied from templates and managed; existing | ||
| user rules files are never touched. | ||
|
|
||
| Returns: | ||
| Number of files installed (0 or 1 for README). | ||
| """ | ||
| rules_dir = project_path / ".claude" / "rules" / "learned" | ||
| rules_dir.mkdir(parents=True, exist_ok=True) | ||
|
|
||
| templates_dir = get_templates_dir() | ||
| readme_template = templates_dir / "rules" / "learned" / "README.md" | ||
|
|
||
| count = 0 | ||
| if readme_template.exists(): | ||
| dest = rules_dir / "README.md" | ||
| if not dest.exists(): | ||
| version = _get_version() | ||
| result = copy_managed_file(readme_template, dest, version) | ||
| if drift_report is not None: | ||
| drift_report.results.append(result) | ||
| count += 1 | ||
|
|
There was a problem hiding this comment.
create_rules_dir copies README via copy_managed_file, which injects a MAP-MANAGED header into a directory that the PR description calls user-owned and that the function never overwrites on upgrade. Consider copying README without managed metadata (or update the docstring/behavior so the 'managed' claim is accurate and expectations are clear).
| Before calling the Reflector, read all existing `.claude/rules/learned/*.md` files (excluding README.md). Extract the bullet points from each file. | ||
|
|
||
| --- | ||
| ```bash | ||
| ls .claude/rules/learned/*.md 2>/dev/null || echo "NO_EXISTING_RULES" | ||
| ``` | ||
|
|
||
| If files exist, read each one and collect all lines starting with `- **`. These are existing lessons that the Reflector should NOT duplicate. |
There was a problem hiding this comment.
Step 2a says to read .claude/rules/learned/*.md files "excluding README.md", but the suggested ls .claude/rules/learned/*.md command will include README.md. Either adjust the command to exclude README or remove the exclusion to avoid contradictory instructions (and accidental README content being treated as dedup context).
|
|
||
| --- | ||
| ```bash | ||
| ls .claude/rules/learned/*.md 2>/dev/null || echo "NO_EXISTING_RULES" |
There was a problem hiding this comment.
Step 2a says to read .claude/rules/learned/*.md files "excluding README.md", but the suggested ls .claude/rules/learned/*.md command will include README.md. Either adjust the command to exclude README or remove the exclusion to avoid contradictory instructions (and accidental README content being treated as dedup context).
| ls .claude/rules/learned/*.md 2>/dev/null || echo "NO_EXISTING_RULES" | |
| ls .claude/rules/learned/*.md 2>/dev/null | grep -v 'README.md' || echo "NO_EXISTING_RULES" |
| ## Step 3: Write Rules Files | ||
|
|
||
| Transform Reflector output into `.claude/rules/learned/` markdown files. | ||
|
|
There was a problem hiding this comment.
The command assumes .claude/rules/learned/ already exists when writing new rule files, but mapify upgrade intentionally does not create it and users may run /map-learn in older projects. Add an explicit step to mkdir -p .claude/rules/learned (or equivalent) before attempting to create/append files.
| ## Step 3: Write Rules Files | ||
|
|
||
| Transform Reflector output into `.claude/rules/learned/` markdown files. | ||
|
|
There was a problem hiding this comment.
The command assumes .claude/rules/learned/ already exists when writing new rule files, but mapify upgrade intentionally does not create it and users may run /map-learn in older projects. Add an explicit step to mkdir -p .claude/rules/learned (or equivalent) before attempting to create/append files.
| rules_count = create_rules_dir(project_path) | ||
| tracker.complete( | ||
| "rules-dir", | ||
| f"{rules_count} file" if rules_count <= 1 else f"{rules_count} files", |
There was a problem hiding this comment.
The status message pluralization is incorrect for rules_count == 0 (it will print 0 file). Use the same singular/plural handling as elsewhere (singular only when count == 1).
| f"{rules_count} file" if rules_count <= 1 else f"{rules_count} files", | |
| f"{rules_count} file" if rules_count == 1 else f"{rules_count} files", |
- Add SKILL.md with proper frontmatter (disable-model-invocation, trigger
phrases, negative triggers, troubleshooting section)
- Add supporting templates: rules-unconditional.md, rules-with-paths.md,
example-rules.md (real-world Go controller examples with code snippets)
- SKILL.md references templates via ${CLAUDE_SKILL_DIR}/templates/ for
reliable format when creating new rules files
- Add map-learn entry to skill-rules.json with trigger keywords
- Keep .claude/commands/map-learn.md for backward compatibility (skill
takes precedence per Claude Code docs)
First /map-learn invocation on map-learn-improvement workflow. Rules auto-load in future Claude Code sessions via .claude/rules/. - architecture-patterns.md: contract-first JSON schemas, extract shared helpers before monolith decomposition - implementation-patterns.md (Python-scoped): dataclass type validation, validation function return semantics - error-patterns.md: timestamped backups, control char sanitization - testing-strategies.md (test-scoped): Monitor bugs → regression tests
Summary
/map-learnnow writes extracted lessons to.claude/rules/learned/*.md— Claude Code natively loads these at session start, closing the LEARN looppaths:frontmatter for scoped loading (Go lessons load only when working with*.go)mapify initcreates.claude/rules/learned/with README;mapify upgradenever touches it (user-owned)How it works
Test plan
test_create_rules_dir_creates_directory— dir + README createdtest_create_rules_dir_preserves_existing— existing README not overwrittentest_create_rules_dir_idempotent— second call is no-optest_map_learn_persists_to_rules— command references.claude/rules/learned/mapify initcreates.claude/rules/learned/README.md/map-learnafter workflow writes rules files