115 lines
4.3 KiB
Markdown
115 lines
4.3 KiB
Markdown
# M4.1 — `mem skill draft`
|
||
|
||
| Field | Value |
|
||
|---|---|
|
||
| Phase | M4 — Skills |
|
||
| Size | M — 1–3 days |
|
||
| Status | ⬜ Not started |
|
||
| 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.
|
||
|
||
## 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:** seeded vault and log; scripted model client for determinism.
|
||
|
||
**Integration test** — `tests/it_skill_draft.rs`:
|
||
1. `a1_valid_frontmatter` — parse the output; assert `name`, `description`,
|
||
`when_to_use`, `generated_from` present and non-empty.
|
||
2. `a2_generated_from_resolves` — the sha exists in `memory_node`.
|
||
3. `a3_writes_only_to_drafts` — assert the path contains `_drafts/`; attempt to
|
||
pass a path outside it and assert refusal.
|
||
4. `a4_no_promotion_path` — grep the workspace for any code writing to
|
||
`vault/skills/` that is not under `_drafts/`; assert none. Promotion must be
|
||
manual.
|
||
5. `a5_description_is_trigger_shaped` — assert `description` contains at least one
|
||
phrasing cue (a quoted user phrase or "Use when"), matching the installed
|
||
examples.
|
||
6. `a6_dry_run_writes_nothing` — assert no file created.
|
||
7. `a7_idempotent` — same input twice produces identical bytes apart from
|
||
`generated_at`.
|
||
|
||
**Command:** `cargo test -p mem-cli skill_draft`
|
||
|
||
**False pass:**
|
||
- Asserting the file was written without asserting *where*. The entire safety
|
||
property of this task is the location, and a draft written to `vault/skills/`
|
||
is immediately loadable.
|
||
- Accepting any non-empty `description`. A one-line restatement of the title
|
||
never triggers, so the feature appears to work and the skill never fires —
|
||
assertion 5 is a weak but real guard.
|
||
|
||
## Traps
|
||
|
||
- Auto-promoting "when the draft looks good". That closes the loop this design
|
||
deliberately leaves open, and there is no external verifier inside it.
|
||
- Generating the body from L0 evidence. Skills are procedure distilled from
|
||
synthesis; raw transcript produces a narrative, not an instruction.
|
||
|
||
---
|
||
|
||
Background: [DESIGN.md](../DESIGN.md) — Skills, the procedural projection
|