Files
poimen-memory/tasks/M4.1-skill-draft.md
T

163 lines
6.8 KiB
Markdown
Raw Normal View History

2026-08-19 09:52:07 -07:00
# M4.1 — `mem skill draft`
| Field | Value |
|---|---|
| Phase | M4 — Skills |
| Size | M — 13 days |
| 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.
## 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. |
## Implementation (Completed 2025-01-26)
**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)
**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) |
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
**Harness:** Path validation, frontmatter parsing, file I/O structure.
2026-08-19 09:52:07 -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
**Command:** `cargo test --test it_skill_draft` ✅ All 10 tests pass
2026-08-19 09:52:07 -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
## Usage
2026-08-19 09:52:07 -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
---
**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