Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions skills/claudemd-export/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
description: Generate or update CLAUDE.md from ZeroDB memories
---

The user has invoked `/memory export --format claudemd` (or you have been delegated here from the `memory` skill). Generate a `CLAUDE.md` file from all ZeroDB memories stored for the current project.

---

## Step 1 — Detect current project

Run:
```bash
git remote get-url origin 2>/dev/null
```

Normalize the result to `org/repo` (lowercase):
- SSH `git@github.com:Org/Repo.git` → `org/repo`
- HTTPS `https://github.com/Org/Repo.git` → `org/repo`
- Strip `.git` suffix, strip credentials (`user:token@`), lowercase both parts.

If the command fails (not a git repo), prompt the user:
```
Not inside a git repository. Please run this command from your project root, or provide the project name manually (e.g. /memory export --format claudemd --project org/repo).
```

---

## Step 2 — Retrieve all memories for the project

Use `zerodb_semantic_search` with broad queries to ensure full coverage. Run the following queries and merge results (deduplicate by `id`):

1. `"project architecture technical decisions"`
2. `"conventions rules coding standards"`
3. `"file ownership responsible person"`
4. `"active work in progress current sprint"`
5. `"bugs known issues problems"`
6. `"corrections mistakes important fixes"`

For each query: `limit=50`, filter `metadata.project = org/repo`.

Paginate: if any query returns exactly 50 results, run an additional offset-based query until fewer than 50 are returned, up to a combined cap of **200 memories total** per type.

After merging: deduplicate by `id`. You will have a master list of up to 200 unique memories.

---

## Step 3 — Group memories by type

Map the `metadata.type` field to CLAUDE.md sections:

| `metadata.type` | CLAUDE.md section |
|-----------------|-------------------|
| `architecture` | `## Architecture` |
| `convention` | `## Conventions` |
| `ownership` | `## File Ownership` |
| `in-progress` | `## Active Work` |
| `bug` | `## Known Issues` |
| `correction` | `## Important Corrections` |

Memories with unrecognized types: append to the closest matching section, or create a `## General` section as a catch-all if needed.

---

## Step 4 — Deduplicate within each group

For each section, scan for semantically equivalent memories (two memories that convey the same fact):
- If the content overlaps > ~80% in meaning, **keep only the most recently created** (`created_at` descending).
- Prefer specificity: if one memory is a subset of another, keep the more detailed one regardless of age.
- Log the count of removed duplicates internally (shown in the generation summary).

---

## Step 5 — Generate CLAUDE.md content

Use this exact template:

```markdown
# Project Memory — {org/repo}
> Auto-generated by ZeroDB Memory plugin on {YYYY-MM-DD}.
> Edit with care — this file will be regenerated on next `/memory export --format claudemd`.
> Source of truth is ZeroDB — use /remember to add new facts.

## Architecture
- {fact}
- {fact}

## Conventions
- {fact}

## File Ownership
- {fact}

## Active Work
- {fact}

## Known Issues
- {fact}

## Important Corrections
- {fact}
```

Rules:
- **Skip empty sections entirely** — do not include a heading if it has no bullet points.
- Each bullet is the raw `content` of the memory, trimmed of leading/trailing whitespace.
- Do not include memory IDs, scores, or metadata fields in the output.
- Use today's date in the `Auto-generated on` line.

---

## Step 6 — Update vs. create

### If `CLAUDE.md` does NOT exist:
Create it immediately. Confirm:
```
Created CLAUDE.md with N memories across K sections.
```

### If `CLAUDE.md` already exists:
1. Compute a diff of the new content vs. the existing file.
2. Present the diff to the user:
```diff
- ## Architecture
- - Old fact that changed
+ - New fact replacing it
```
3. Ask the user:
```
CLAUDE.md already exists. How would you like to proceed?
yes — overwrite with generated content
no — cancel, do not write anything
merge — keep existing manual content, append only new facts not already present
```
4. Wait for user response before writing.

**merge mode**: Compare each generated bullet against all lines in the existing file. If the fact (or a very close paraphrase) does not appear anywhere in the existing file, append it to the appropriate section. Never remove or modify existing lines in merge mode.

---

## Step 7 — Output options

Parse flags from the original command:

| Flag | Behavior |
|------|----------|
| _(no flags)_ | Show generated content inline, then ask `Write to CLAUDE.md? (yes/no/merge)` |
| `--output CLAUDE.md` | Write directly to the specified file, skip inline display, print confirmation |
| `--output <other-path>` | Write to the specified path, skip inline display, print confirmation |
| `--dry-run` | Display what would be written. Do NOT write any file. Print `[dry-run] No files written.` at the end |

`--dry-run` takes precedence over `--output`. If both are provided, behave as dry-run.

---

## Generation summary

After writing (or in dry-run mode), always print a summary:

```
ZeroDB CLAUDE.md Export — {org/repo}
Memories retrieved: 42
Duplicates removed: 3
Sections written: 4 (architecture, conventions, known-issues, corrections)
Output: CLAUDE.md [written | dry-run | skipped]
```

---

## Error handling

| Condition | Action |
|-----------|--------|
| No memories found for project | Print: `No memories found for {org/repo}. Use /remember to store facts first.` Do not create CLAUDE.md. |
| ZeroDB API unreachable | Print: `ZeroDB API unreachable. Check ZERODB_API_KEY and network, then retry.` |
| File write permission denied | Print: `Cannot write to {path}: permission denied.` |
| User says "no" to overwrite | Print: `Export cancelled. CLAUDE.md unchanged.` |
24 changes: 8 additions & 16 deletions skills/memory/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,14 @@ Exported: 2026-04-24

**json**: Raw JSON array of memory objects.

**claudemd**: Generate a CLAUDE.md skeleton from memories, grouped into standard CLAUDE.md sections:
```markdown
# Project Memory (generated by ZeroDB)

## Architecture
...

## Conventions
...

## File Ownership
...

## Active Work
...
```
**claudemd**: Full CLAUDE.md generation from stored memories — **delegate entirely to the `claudemd-export` skill** (`skills/claudemd-export/SKILL.md`). That skill handles:
- Broad semantic retrieval across all 6 memory types (up to 200 memories)
- Deduplication of semantically equivalent facts
- Structured CLAUDE.md output with standard sections (Architecture, Conventions, File Ownership, Active Work, Known Issues, Important Corrections)
- Update vs. create flow with diff preview and merge mode
- `--dry-run` support

Pass all flags through to the delegated skill unchanged (e.g. `--output CLAUDE.md`, `--dry-run`).

4. If `--output <filename>` provided: write to that file and confirm `Exported N memories to <filename>`.
5. Otherwise: display inline and ask `Write to file? (enter filename or press Enter to skip)`.
Expand Down
95 changes: 95 additions & 0 deletions tests/e2e/test_zerodb_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -803,3 +803,98 @@ def test_plugin_json_hooks_reference_existing_files(self):
data = json.loads((PLUGIN_ROOT / ".claude-plugin" / "plugin.json").read_text())
for event, path in data.get("hooks", {}).items():
assert (PLUGIN_ROOT / path).exists(), f"Hook {event} references missing file: {path}"


# ---------------------------------------------------------------------------
# 10. CLAUDE.md Export Skill
# ---------------------------------------------------------------------------

class TestCLAUDEmdExport:
"""Validate the claudemd-export skill file exists and is fully specified."""

SKILL_PATH = PLUGIN_ROOT / "skills" / "claudemd-export" / "SKILL.md"

def _skill_content(self) -> str:
return self.SKILL_PATH.read_text()

def test_export_claudemd_skill_file_exists(self):
"""skills/claudemd-export/SKILL.md must exist."""
assert self.SKILL_PATH.exists(), "Missing: skills/claudemd-export/SKILL.md"

def test_export_claudemd_skill_has_frontmatter(self):
"""Skill file must have valid YAML frontmatter block."""
content = self._skill_content()
assert content.startswith("---"), "claudemd-export/SKILL.md missing opening YAML frontmatter (---)"
lines = content.split("\n")
closing_markers = [i for i, line in enumerate(lines[1:], 1) if line.strip() == "---"]
assert len(closing_markers) >= 1, "claudemd-export/SKILL.md missing closing frontmatter (---)"
# description field must be present inside the frontmatter block
frontmatter_end = closing_markers[0]
frontmatter_body = "\n".join(lines[1:frontmatter_end])
assert "description:" in frontmatter_body, \
"claudemd-export/SKILL.md frontmatter missing 'description:' field"

def test_export_claudemd_skill_covers_all_memory_types(self):
"""Skill must document all 6 memory types and their section mappings."""
content = self._skill_content()
required_types = ["architecture", "convention", "ownership", "in-progress", "bug", "correction"]
for memory_type in required_types:
assert memory_type in content, \
f"claudemd-export/SKILL.md does not mention memory type '{memory_type}'"
# Also verify the corresponding CLAUDE.md section headings are documented
required_sections = [
"## Architecture",
"## Conventions",
"## File Ownership",
"## Active Work",
"## Known Issues",
"## Important Corrections",
]
for section in required_sections:
assert section in content, \
f"claudemd-export/SKILL.md does not document section '{section}'"

def test_export_claudemd_skill_has_dedup_logic(self):
"""Skill must describe deduplication of semantically equivalent memories."""
content = self._skill_content()
# At least one of these terms should appear in the dedup section
dedup_signals = ["dedup", "duplicate", "semantically equivalent", "same fact", "overlaps"]
found = any(signal.lower() in content.lower() for signal in dedup_signals)
assert found, (
"claudemd-export/SKILL.md must describe deduplication logic. "
"Expected one of: " + ", ".join(dedup_signals)
)
# Must also specify what to keep (most recent / most detailed)
keep_signals = ["most recent", "most recently", "more detailed", "specificity", "keep only"]
found_keep = any(signal.lower() in content.lower() for signal in keep_signals)
assert found_keep, (
"claudemd-export/SKILL.md must specify which duplicate to keep (most recent / most detailed)"
)

def test_export_claudemd_skill_has_dry_run_option(self):
"""Skill must document the --dry-run flag."""
content = self._skill_content()
assert "--dry-run" in content, \
"claudemd-export/SKILL.md must document the --dry-run flag"
# Confirm it clarifies that dry-run does NOT write any file
dry_run_signals = ["do not write", "don't write", "No files written", "not write", "without writing"]
found = any(signal.lower() in content.lower() for signal in dry_run_signals)
assert found, \
"claudemd-export/SKILL.md must clarify that --dry-run does not write any file"

def test_export_claudemd_skill_references_update_vs_create(self):
"""Skill must document the update-vs-create flow including merge mode."""
content = self._skill_content()
# Must mention behaviour when CLAUDE.md already exists
exists_signals = ["already exists", "if.*exist", "update vs", "update vs. create"]
found_exists = any(signal.lower() in content.lower() for signal in exists_signals)
assert found_exists, \
"claudemd-export/SKILL.md must describe behaviour when CLAUDE.md already exists"
# Must mention merge mode
assert "merge" in content.lower(), \
"claudemd-export/SKILL.md must describe 'merge' mode for preserving existing manual content"
# Must mention diff preview
diff_signals = ["diff", "what would change", "preview"]
found_diff = any(signal.lower() in content.lower() for signal in diff_signals)
assert found_diff, \
"claudemd-export/SKILL.md must show a diff/preview before overwriting an existing CLAUDE.md"
Loading