Obsidian Migration Deep DiveSAFE
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Overview
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
4f83675ca38aOBSERVED · 2026-10-09Install
Commands as the repository documents them. They are shown, not run.
npm install xml2js turndown
npm install turndown
npm install xml2js glob
Host compatibility
What the documentation claims. We have not run a compatibility test.
| Host | Status | Notes |
|---|---|---|
| claude-code | mentioned |
What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
--- name: obsidian-migration-deep-dive description: 'Execute major Obsidian plugin rewrites and migration strategies. Use when migrating to or from Obsidian, performing major plugin rewrites, or re-platforming existing note systems to Obsidian. Trigger with phrases like "migrate to obsidian", "obsidian migration", "convert notes to obsidian", "obsidian replatform". ' allowed-tools: Read, Write, Edit, Bash(npm:*), Bash(node:*) version: 1.13.0 license: MIT author: Jeremy Longshore <[email protected]> tags: - saas - obsidian - migration compatibility: Designed for Claude Code --- # Obsidian Migration Deep Dive ## Current State !`node --version 2>/dev/null || echo 'N/A'` !`ls *.enex *.json *.zip 2>/dev/null | head -10 || echo 'No export files in cwd'` ## Overview Migrate notes from Notion, Evernote, Roam Research, Bear, and Apple Notes into Obsidian -- handling attachment relocation, internal link conversion to `[[wikilinks]]`, tag migration, and frontmatter generation. ## Prerequisites - Exported data from the source application (see each section for format) - A target Obsidian vault created and opened at least once - Node.js 18+ for running migration scripts - Backup of source data before starting ## Instructions ### Step 1: Pre-Migration Assessment ```bash #!/bin/bash # assess-migration.sh <export-directory> EXPORT_DIR="${1:-.}" echo "=== Migration Assessment: $EXPORT_DIR ===" echo "File counts:" for ext in md html enex json csv pdf png jpg gif zip; do count=$(find "$EXPORT_DIR" -name "*.$ext" 2>/dev/null | wc -l) [ "$count" -gt 0 ] && echo " .$ext: $count" done echo "Total size: $(du -sh "$EXPORT_DIR" 2>/dev/null | cut -f1)" echo "Max directory depth: $(find "$EXPORT_DIR" -type d | awk -F/ '{print NF-1}' | sort -n | tail -1)" echo "Sample filenames:" find "$EXPORT_DIR" -type f | head -5 ``` ### Step 2: Notion Export Migration Notion exports as a zip containing markdown files, CSV databases, and attachments. The markdown uses Notion-style links and has UUIDs appended to filenames. ```javascript // notion-to-obsidian.mjs import { readdir, readFile, writeFile, mkdir, copyFile } from 'fs/promises'; import { join, basename, extname, dirname } from 'path'; const NOTION_EXPORT = process.argv[2]; // Unzipped Notion export const VAULT_DIR = process.argv[3]; // Target Obsidian vault if (!NOTION_EXPORT || !VAULT_DIR) { console.error('Usage: node notion-to-obsidian.mjs <notion-export-dir> <vault-dir>'); process.exit(1); } // Step 1: Build a filename map (strip Notion UUIDs from names) // Notion appends " abc123def456" to every filename function cleanNotionName(filename) { return filename.replace(/\s+[a-f0-9]{32}(?=\.\w+$|$)/, ''); } async function* walkDir(dir) { const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dir, entry.name); if (entry.isDirectory()) yield* walkDir(fullPath); else yield fullPath; } } async function migrate() { const fileMap = new Map(); // original path -> clean path const attachments = []; const notes = []; // Categorize files for await (const filePath of walkDir(NOTION_EXPORT)) { const ext = extname(filePath).toLowerCase(); const relPath = filePath.slice(NOTION_EXPORT.length + 1); const cleanPath = relPath.split('/').map(cleanNotionName).join('/'); fileMap.set(relPath, cleanPath); if (ext === '.md') notes.push({ src: filePath, dest: cleanPath }); else if (ext === '.csv') notes.push({ src: filePath, dest: cleanPath.replace('.csv', '.md'), isCSV: true }); else attachments.push({ src: filePath, dest: join('attachments', basename(cleanPath)) }); } // Process markdown notes for (const note of notes) { let content; if (note.isCSV) { content = await convertCSVToMarkdown(note.src); } else { content = await readFile(note.src, 'utf-8'); } // Convert Notion links to Obsidian wikilinks // Notion: Page Title // Obsidian: [[Page Title]] content = content.replace( /\[([^\]]+)\]\(([^)]+\.md)\)/g, (match, text, href) => { const decoded = decodeURIComponent(href); const clean = cleanNotionName(basename(decoded, '.md')); return `[[${clean}]]`; } ); // Convert Notion image references to Obsidian // Notion: !description // Obsidian: ![[image-name.png]] content = content.replace( /!\[([^\]]*)\]\(([^)]+)\)/g, (match, alt, src) => { const decoded = decodeURIComponent(src); if (decoded.startsWith('http')) return match; // Keep external URLs const clean = cleanNotionName(basename(decoded)); return `![[${clean}]]`; } ); // Add frontmatter const title = basename(note.dest, extname(note.dest)); content = `---\ntitle: "${title}"\nsource: notion\nmigrated: ${new Date().toISOString().split('T')[0]}\n---\n\n${content}`; const destPath = join(VAULT_DIR, note.dest); await mkdir(dirname(destPath), { recursive: true }); await writeFile(destPath, content); } // Copy attachments await mkdir(join(VAULT_DIR, 'attachments'), { recursive: true }); for (const att of attachments) { await copyFile(att.src, join(VAULT_DIR, att.dest)); } console.log(`Migrated ${notes.length} notes, ${attachments.length} attachments`); } async function convertCSVToMarkdown(csvPath) { const raw = await readFile(csvPath, 'utf-8'); const lines = raw.trim().split('\n'); if (lines.length === 0) return ''; const headers = lines[0].split(',').map(h => h.replace(/^"|"$/g, '')); const rows = lines.slice(1).map(line => line.split(',').map(c => c.replace(/^"|"$/g, '')) ); let md = `| ${headers.join(' | ')} |\n`; md += `| ${headers.map(() => '---').join(' | ')} |\n`; for (const row of rows) { md += `| ${row.join(' | ')} |\n`; } return md; } migrate().catch(console.error); ``` Run it: ```bash unzip Notion-Export-*.zip -d notion-export
Trust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | PASS |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (0)
No findings outside the package's declared scope.
Gates applied: no_behavioural_pass.
4f83675ca38afull audit observations/trust-audit/skill/jeremylongshore__obsidian-migration-deep-dive.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-09 | 4f83675ca38a | SAFE | B | 89 | first audit |
Questions
What does the Obsidian Migration Deep Dive skill do?
Model-agnostic agent-skills platform with a harness-free canonical layer, verified adapters, and the ccpi package manager. Explore at tonsofskills.com.
Is Obsidian Migration Deep Dive safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can Obsidian Migration Deep Dive access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
Which assistants does Obsidian Migration Deep Dive work with?
Its documentation mentions claude-code. That is what the text claims, not a compatibility test we ran.
How current is this page?
The grade is for one exact copy of the source (4f83675ca38a), read on 2026-10-09. The repository is watched, and a new audit runs when it changes — this is the first audit.