2026-08-19 09:52:07 -07:00
|
|
|
|
# M4.1 — `mem skill draft`
|
|
|
|
|
|
|
|
|
|
|
|
| Field | Value |
|
|
|
|
|
|
|---|---|
|
|
|
|
|
|
| Phase | M4 — Skills |
|
|
|
|
|
|
| Size | M — 1–3 days |
|
2026-08-26 13:50:22 -07:00
|
|
|
|
| Status | ✅ Done — CLI command + 10 integration tests |
|
2026-08-19 09:52:07 -07:00
|
|
|
|
| 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.
|
|
|
|
|
|
|
2026-08-22 21:43:23 -07:00
|
|
|
|
## 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/<tool>-failures/SKILL.md` per tool + `MEMORY.md` digest. Creates dirs, prints symlink instructions for Claude Code / pi. |
|
|
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
## Implementation (Completed 2025-01-26)
|
2026-08-22 21:43:23 -07:00
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
**Completed:**
|
|
|
|
|
|
1. ✅ **CLI command** — `mem skill draft --from <project>/<query-id>`
|
|
|
|
|
|
2. ✅ **`_drafts/` enforcement** — writes to `vault/skills/_drafts/<project>-<query-id>/SKILL.md`
|
|
|
|
|
|
3. ✅ **Provenance** — `generated_from: <sha256>` (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)
|
2026-08-22 21:43:23 -07:00
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
**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
|
2026-08-22 21:43:23 -07:00
|
|
|
|
|
|
|
|
|
|
## 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) |
|
|
|
|
|
|
|
2026-08-19 09:52:07 -07:00
|
|
|
|
## 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: "<topic or seed keyword> [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 <project>/<query-id>` 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: <sha>` and `generated_at`.
|
|
|
|
|
|
4. Write to `vault/skills/_drafts/<project>-<query-id>/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
|
|
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
**Harness:** Path validation, frontmatter parsing, file I/O structure.
|
2026-08-19 09:52:07 -07:00
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
**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
|
2026-08-19 09:52:07 -07:00
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
**Command:** `cargo test --test it_skill_draft` ✅ All 10 tests pass
|
2026-08-19 09:52:07 -07:00
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
**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)
|
2026-08-19 09:52:07 -07:00
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
## Usage
|
2026-08-19 09:52:07 -07:00
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
```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).
|
2026-08-19 09:52:07 -07:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-08-26 13:50:22 -07:00
|
|
|
|
**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.
|
|
|
|
|
|
|
2026-08-19 09:52:07 -07:00
|
|
|
|
Background: [DESIGN.md](../DESIGN.md) — Skills, the procedural projection
|