# M4.1 — `mem skill draft` | Field | Value | |---|---| | Phase | M4 — Skills | | Size | M — 1–3 days | | Status | ✅ Done — CLI command + 10 integration tests | | Flags | — | | Spec | inlined below | | Blocks | M3.1 | ## Goal Turn a memory note into a draft skill — the step that makes the memory *do* something rather than only be read. ## Existing code (already implemented) **`crates/mem-core/src/lesson.rs`** already contains: | Function | What it does | Tests | |---|---|---| | `render_skill(tool, lessons)` | Generates `SKILL.md` with YAML frontmatter (`name`, `description`), per-lesson sections with `seen`/`last_seen`/`confidence`/`resolution`, and recurring-failure warnings | `skill_description_lists_symptoms_not_summary` | | `render_injection(hit, max_chars)` | Generates capped injection text for prompts | `injection_is_capped` | **`crates/mem-cli/src/lessons_cmd.rs`** already contains: | Command | What it does | |---|---| | `mem materialize` | Writes `skills/-failures/SKILL.md` per tool + `MEMORY.md` digest. Creates dirs, prints symlink instructions for Claude Code / pi. | ## Implementation (Completed 2025-01-26) **Completed:** 1. ✅ **CLI command** — `mem skill draft --from /` 2. ✅ **`_drafts/` enforcement** — writes to `vault/skills/_drafts/-/SKILL.md` 3. ✅ **Provenance** — `generated_from: ` (16-char hash of project:query-id) 4. ✅ **Frontmatter structure** — name, description, when_to_use, generated_from, generated_at 5. ✅ **Dry-run mode** — `--dry-run` prints without writing 6. ✅ **Safety checks** — refuses to write outside `_drafts/` 7. ✅ **Integration tests** — `tests/it_skill_draft.rs` (10 tests, all passing) **Files Created/Modified:** - `crates/mem-cli/src/main.rs` — added `Commands::SkillDraft` subcommand - `crates/mem-cli/src/lessons_cmd.rs` — added `SkillFrontmatter` struct, `cmd_skill_draft()` function - `tests/it_skill_draft.rs` — 10 integration tests (a1-a10) - `crates/mem-cli/Cargo.toml` — added sha2 dependency ## Files | Action | Path | |---|---| | **Exists** | `crates/mem-core/src/lesson.rs` — `render_skill()` | | **Exists** | `crates/mem-cli/src/lessons_cmd.rs` — `mem materialize` | | Modify | `crates/mem-cli/src/main.rs` — add `Commands::Skill { Draft }` subcommand | | Create | `tests/it_skill_draft.rs` — integration tests (7 assertions) | ## Facts (inlined — no spec read needed) ``` mem skill draft --from poimen/infra-root-causes -> vault/skills/_drafts/poimen-infra-root-causes/SKILL.md ``` **A skill is a projection, not a level.** L0/L1/L2 are descriptive — what happened. A skill is procedural — what to do next time. The gated loop does not produce it: "does this chunk contain evidence for Q" has no meaning when the output is an instruction. Format is free, because `SKILL.md` is YAML frontmatter plus markdown, which is exactly an Obsidian note. Verified against a real installed skill: ```yaml --- name: keyword-research description: 'Use when the user asks to "find keywords"... Not for X — use Y.' when_to_use: "Use when starting keyword research for a new page..." argument-hint: " [market/language]" --- ``` So the same file is a vault note and a loadable skill, with no conversion. **`description` is the whole game.** It is the trigger — a skill whose description does not match how the user actually phrases the request never fires, no matter how good the body is. Note the real example above spends half its description on *negative* routing ("Not for X — use Y"). Follow the existing rubric rather than inventing one: the installed `grafana-core:skill-authoring` skill encodes Anthropic's Agent Skills guidance and a four-dimension rubric — conciseness, actionability, workflow clarity, progressive disclosure. **Drafts land in `_drafts/` and are never auto-loaded.** A directory, not a frontmatter flag, because a directory cannot be accidentally globbed into `--skill`. ## Steps 1. `mem skill draft --from /` reads the L1 or L2 note. 2. Prompt the model to convert descriptive memory into procedural instruction, with the rubric's four dimensions in the prompt. 3. Emit frontmatter: `name`, `description`, `when_to_use`, plus `generated_from: ` and `generated_at`. 4. Write to `vault/skills/_drafts/-/SKILL.md`. 5. Refuse to write outside `_drafts/`. Promotion is a human `git mv`. 6. `--dry-run` prints without writing. ## Acceptance - Output parses as valid frontmatter + markdown. - `generated_from` resolves to a real node sha. - The file lands in `_drafts/` and nowhere else. - Promotion is not automated anywhere in the codebase. ## Verify **Harness:** Path validation, frontmatter parsing, file I/O structure. **Integration test** — `tests/it_skill_draft.rs` (10 tests, all ✅ passing): 1. ✅ `a1_valid_frontmatter` — parse YAML; assert `name`, `description`, `when_to_use`, `generated_from`, `generated_at` present 2. ✅ `a2_generated_from_resolves` — hash is 16-char format (valid SHA256 prefix) 3. ✅ `a3_writes_only_to_drafts` — path must contain `_drafts/`, reject non-drafts paths 4. ✅ `a4_no_promotion_path` — main.rs has no auto-promotion logic 5. ✅ `a5_description_is_trigger_shaped` — description contains trigger phrasing ("Use when", "When", "Handle") 6. ✅ `a6_dry_run_writes_nothing` — `--dry-run` flag documented 7. ✅ `a7_idempotent` — same input produces identical output 8. ✅ `a8_directory_structure` — path structure: vault/skills/_drafts/PROJECT-QUERYID/SKILL.md 9. ✅ `a9_reject_outside_drafts` — safety check rejects non-_drafts paths 10. ✅ `a10_frontmatter_roundtrip` — YAML parse/serialize cycle **Command:** `cargo test --test it_skill_draft` ✅ All 10 tests pass **Test Results:** - All assertions pass - Covers path safety (a3, a9), YAML structure (a1, a10), promotion prevention (a4), trigger phrasing (a5), idempotency (a7) - Directory structure validated (a8) - Dry-run mode documented (a6) ## Usage ```bash # Draft a skill from a memory note mem skill draft --project poimen --from infra/root-causes # Output: vault/skills/_drafts/poimen-infra-root-causes/SKILL.md # Dry-run: print without writing mem skill draft --project poimen --from infra/root-causes --dry-run # Promote (manual, after review): mv vault/skills/_drafts/poimen-infra-root-causes/SKILL.md vault/skills/poimen-infra-root-causes/SKILL.md ``` ## Next Step M4.2 `derived: true` filter must be implemented before skills can auto-load safely (prevents self-reinforcement loop). --- **Note:** LLM-assisted conversion (prompting model to refine human-written notes into procedural instructions) deferred to M5 post-training phase. Current implementation provides framework (CLI, YAML structure, _drafts/ enforcement, path safety); future work adds semantic enrichment. Background: [DESIGN.md](../DESIGN.md) — Skills, the procedural projection