Tips & Tricks (업데이트: 2026. 6. 6.)

Obsidian 메모가 AI로 '저절로 자란다'. 두 번째 뇌를 만드는 구조

흩어진 Obsidian 메모를 AI에게 읽혀, 링크 걸기·요약·정리를 반자동화하는 법. 쓰기는 신중하게, 읽기 중심으로 '저절로 자라는 Vault'를 만드는 실체험.

Obsidian 메모가 AI로 '저절로 자란다'. 두 번째 뇌를 만드는 구조

새벽 0시, 저는 Obsidian 검색창에 “그 명령어, 뭐였더라”라고 치고 있었습니다.

2년 전에 메모한, 고작 세 줄짜리 셸 명령어. 있는 건 확실해요. 그런데 500개를 넘긴 노트 어디에 묻혀 있는지, 도무지 떠오르지 않았습니다. 결국 그날은 찾기를 포기하고, 다시 구글에서 검색했어요.

이거, 저만 그런 게 아닐 거예요.

Obsidian(메모 앱)은 쓰면 쓸수록 편해집니다. 그런데 동시에, 확실하게 어질러져요. 일일 로그, 독서 메모, 코드 조각, 글 소재, 회의 메모. 전부 Markdown 파일로 쌓여 가다가, 반년만 지나면 “나만을 위한, 검색하기 어려운 쓰레기집”이 됩니다.

그래서 제가 시작한 게, Vault(메모 보관소)를 AI에게 읽혀, 정리를 절반쯤 대신 시키는 것이었습니다. 링크를 걸고, 요약을 만들고, 미아가 된 메모를 주워 올린다. 이걸 매일 조금씩 돌렸더니, 흩어져 있던 메모가 저절로 이어지기 시작해서, 정말로 “두 번째 뇌” 같아졌어요. 오늘은 그 이야기를 합니다.

애초에 ‘메모가 자란다’는 게 무슨 뜻?

보통 메모는, 쓴 순간이 정점입니다. 이후엔 잊혀 갈 뿐이죠.

그런데 Obsidian에는 [[노트 이름]] 이라는 표기로 노트끼리 잇는 기능이 있어서(Wikilink라고 합니다), 이으면 이을수록 한 장의 지도처럼 됩니다. “TypeScript 타입 오류” 메모에서 “그날 헤맸던 Docker 이야기”로 날아가고, 거기서 “지난달 장애 대응 회고”로 이어진다. 점이 선이 되고, 선이 면이 되는 감각이에요.

문제는, 이 잇기를 사람이 하면 너무 귀찮아서 안 이어진다는 것.

여기를 AI에게 맡깁니다. 구체적으로는 짝꿍으로 Claude Code라는 도구를 씁니다. 이건 코드를 짜기 위한 AI인데, 요는 “Markdown 파일이 잔뜩 든 폴더를, 안전하게 읽고 쓸 수 있는 작업 담당”으로 쓸 수 있어요. Obsidian의 로컬 퍼스트(자기 PC에 파일이 놓인다)한 장점은 그대로, 정리 수고만 AI에게 넘긴다. 이게 오늘의 작전입니다.

단, 딱 하나 처음에 못을 박게 해주세요. Vault 전체에 AI의 편집 권한을 주는 건, 절대 안 하는 게 좋습니다.

.obsidian/(설정 폴더)도, 공개 완료된 글도, 계약서 같은 개인 메모도, API 키도, AI에게 읽힐 이유가 없습니다. 그래서 저는 “읽기는 넓게, 쓰기는 극도로 신중하게”라는 방침으로 합니다. 이건 전에 쓴 AI에게 일을 맡기는 ‘발판’ 만드는 법의 사고방식 그대로로, 요는 보조 바퀴를 단 뒤에 달리게 한다는 이야기예요.

이런 장면에서 효과를 본다 (3가지)

추상론으로는 안 와닿으니, 제가 실제로 “오, 효과 있네” 싶었던 순간을 3가지.

첫째는, 아침의 일일 로그. 매일 아침, 전날 노트를 AI에게 읽혀 “안 끝난 작업만 오늘로 옮기고, 어제의 배움을 세 줄로 압축해줘”라고 부탁합니다. 그러면 어제의 내가 어질러 놓은 메모가, 오늘의 내가 움직일 수 있는 형태로 다듬어져 나와요. “아, 이거 어제 막혔던 거다” 하고 한순간에 떠오릅니다. 인수인계 상대가, 미래의 나인 거죠.

둘째는, 코드 조각의 구출. 아까의 “2년 전 명령어” 문제. snippets/ 폴더에 돌아간 명령어를 던져 두면, AI가 “이거, 무엇을 위한 명령어?”라는 설명문과, 관련된 다른 메모로의 링크를 멋대로 달아줍니다. 벌거벗은 명령어가, 검색으로 찾히는 ‘제대로 된 메모’로 둔갑해요.

셋째는, 글 소재 다지기. 블로그를 쓰는 날, content-ops/ 폴더의 소재장을 AI에게 보여주고 “이 글, 어느 과거 글에 내부 링크 걸 수 있을까?”라고 묻습니다. 저도 잊고 있던 반년 전 글을 끌어와 줄 때가 있어서, 이게 은근히 효과적이에요. 점과 점이 이어지는, 그 순간입니다.

내가 저지른 실패 3가지

여기서부터가 본론일지도 모릅니다. 처음 몇 주, 제 Vault는 AI에게 마구 헤집어졌어요. 솔직히 까발립니다.

첫째. 다짜고짜 Vault 전체를 “정리해줘”라고 부탁했다. 이게 가장 심했어요. AI는 2년 전의 알 수 없는 메모까지 “친절하게” 정리하려 들어서, 멋대로 태그와 제목을 잔뜩 자라게 했습니다. Graph view(메모의 연결을 시각화하는 화면)가, 모르는 태그투성이 정글이 됐어요. 오래된 메모의 의도 따위 AI는 알 수 없습니다. 그런데도 추측으로 움직여요. 그 뒤로 대상은 반드시 한 폴더만으로 좁힙니다.

둘째. 노트 이름을 AI에게 멋대로 바꾸게 했다. “제목, 알기 쉽게 해둬.” 이걸로 [[옛 노트 이름]] 의 링크가 일제히 끊겼습니다. Obsidian은 자기가 이름을 바꾸면 링크도 따라와 주는데, 외부 AI가 일괄로 파일명을 갈아엎으면 그 따라오기가 안 먹혀요. 링크 끊김의 시산혈해. 지금은 “이름 변경은 반드시 사람에게 확인”을 철의 계율로 삼고 있습니다.

셋째. private/ 를 읽혀 버렸다. 이건 가슴 철렁 정도가 아니었어요. 계약 금액 메모가 든 폴더를, 권한 설정을 게을리한 탓에 AI가 읽을 수 있는 상태로 두고 있었습니다. 사고는 안 났지만, 운영 DB를 신입에게 맨손으로 만지게 한 것이나 마찬가지. deny(거부 리스트)에 제대로 적어 뒀으면, 애초에 사고 날 수가 없어요. 설정을 아낀 내가 잘못, 이라는 교훈입니다.

시작하는 법: 파일 3개만 둔다

어렵지 않습니다. Vault 바로 아래에 3개만 준비하면 돼요.

먼저, 폴더를 나눕니다. 처음부터 너무 잘게 분류하면 반드시 무너지니, 대충으로 충분합니다.

# Vault 바로 아래에서 실행. 매일 쓰는 곳·프로젝트·공개 작업·보호 영역만 나눈다
mkdir -p inbox daily projects content-ops snippets templates scripts archive private

각 폴더의 역할과, AI에게 어디까지 맡길지는 이렇게 배분합니다.

폴더역할AI에게 맡길 범위
inbox/미정리 메모, 웹 클립 받이읽기와 신규 작성
daily/일일 로그, 작업 메모작성·추가·전날 요약
projects/진행 중 작업, 인수인계작성·갱신·미결 사항 정리
content-ops/글 초안, 내부 링크, 공개 점검초안 정리·링크 확인
snippets/코드 조각과 사용법설명 추가·태그 정리
templates/Obsidian 템플릿원칙은 확인 후 편집
archive/완료·보관 완료편집 금지
private/개인정보·계약·비밀읽기도 금지

다음으로, Vault 바로 아래에 CLAUDE.md 를 둡니다. 이건 AI에게 “우리 규칙”을 적은 종이예요. 긴 이념을 늘어놓기보다, 지켜줬으면 하는 입출력 규칙을 짧게 적는 편이 효과 있습니다.

# Obsidian Vault 의 규칙

## 역할
- 이 Vault 는 일일 로그·프로젝트 인수인계·코드 조각·글 초안을 두는 곳.
- private 을 안 읽어도 도움이 되도록 움직일 것.

## 폴더 방침
- `daily/`: `YYYY-MM-DD.md` 로 일일 노트를 작성·갱신한다.
- `projects/`: 활성 안건마다 인수인계 노트를 1장 유지한다.
- `snippets/`: 돌아간 명령어와, 그 배경·실패 예를 세트로 남긴다.
- `templates/`: 읽는 건 자유. 편집하기 전에 반드시 묻는다.
- `archive/` 와 `private/`: 편집하지 않는다. private 의 내용은 요약도 안 한다.

## 쓰는 법 규칙
- 내부 링크는 `[[프로젝트 이름]]` 의 Wikilink 형식으로 쓴다.
- 노트 맨 앞에 YAML 속성(status 등)을 붙인다.
- 한 문단은 5줄 이내로 담는다.

## 안전 규칙
- 기존 노트의 이름 변경은, 반드시 먼저 확인한다.
- `.obsidian/` 의 설정은 갈아엎지 않는다.
- 일괄 편집 전에, 대상 파일을 목록으로 내고 승인을 기다린다.
- 편집 후에는 반드시 `node scripts/audit-wikilinks.cjs .` 를 돌린다.

마지막이 가장 중요합니다. .claude/settings.json 으로 권한을 물리적으로 묶어요. CLAUDE.md 는 ‘부탁’이지만, 이쪽은 ‘물리 잠금’. 실패담 셋째를 두 번 다시 반복하지 않기 위한 문지기입니다. 읽기는 넓게, 쓰기는 작업 폴더만, 위험한 조작은 deny 로 완전 봉쇄. 설정의 자세한 내용은 공식 권한 문서를 보세요.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Read(./CLAUDE.md)",
      "Read(./daily/**)",
      "Read(./projects/**)",
      "Read(./content-ops/**)",
      "Read(./snippets/**)",
      "Read(./templates/**)",
      "Read(./scripts/**)",
      "Edit(./daily/**)",
      "Edit(./projects/**)",
      "Edit(./content-ops/**)",
      "Edit(./snippets/**)",
      "Write(./inbox/**)",
      "Write(./daily/**)",
      "Write(./projects/**)",
      "Write(./content-ops/**)",
      "Write(./snippets/**)",
      "Bash(node scripts/audit-wikilinks.cjs .)"
    ],
    "ask": [
      "Edit(./templates/**)",
      "Bash(git *)"
    ],
    "deny": [
      "Read(./private/**)",
      "Read(./**/.env)",
      "Read(./**/.env.*)",
      "Edit(./.obsidian/**)",
      "Edit(./archive/**)",
      "Write(./archive/**)",
      "Bash(rm *)",
      "Bash(del *)"
    ]
  }
}

이 세 장을 두었으면, 이후엔 부탁하기만 하면 됩니다. 요령은 “알아서 잘 정리해줘”라고 말하지 않는 것. 범위·결과물·금지 사항·검증까지, 매번 전부 지정합니다.

daily/2026-06-04.md 와 projects/site-refresh.md 를 읽어줘.
templates/daily.md 를 바탕으로 daily/2026-06-05.md 를 만들어줘.
안 끝난 작업은, 담당이 지금도 내 것인 것만 인계해줘.
.obsidian/ 와 archive/ 와 private/ 는 건드리지 마.
편집이 끝나면 node scripts/audit-wikilinks.cjs . 를 실행하고 결과를 보고해줘.

짧지만, 입력·출력·금지 범위·검증 명령이 전부 들어 있습니다. “알아서 잘”보다 몇 배나 재현성이 높아요.

마무리 문지기: 링크 끊김을 기계로 점검한다

외부 AI에게 파일을 편집시키면, 가끔 Wikilink가 깨집니다. 사람 눈으로는 놓쳐요. 그래서 마지막에, 깨진 링크를 기계적으로 골라내는 스크립트를 끼웁니다. 이걸 scripts/audit-wikilinks.cjs 로 저장하세요.

#!/usr/bin/env node
// Vault 내의 깨진·모호한 내부 링크([[...]])를 골라내는 문지기 스크립트
const fs = require("node:fs");
const path = require("node:path");

const vaultRoot = path.resolve(process.argv[2] || ".");
const ignoredDirs = new Set([".git", ".obsidian", ".trash", "node_modules"]);
const allFiles = [];
const markdownFiles = [];

function walk(dir) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (ignoredDirs.has(entry.name)) continue;
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      walk(full);
    } else if (entry.isFile()) {
      allFiles.push(full);
      if (entry.name.toLowerCase().endsWith(".md")) markdownFiles.push(full);
    }
  }
}

function toPosix(filePath) {
  return filePath.split(path.sep).join("/");
}

function withoutMd(value) {
  return value.replace(/\.md$/i, "");
}

function stripTarget(raw) {
  return raw.split("|")[0].split("#")[0].split("^")[0].trim();
}

function safeDecode(value) {
  try {
    return decodeURIComponent(value);
  } catch {
    return value;
  }
}

function isExternal(target) {
  return /^(https?:|mailto:|obsidian:|#|\/)/i.test(target);
}

function candidateKeys(target, fromFile) {
  const clean = safeDecode(stripTarget(target));
  if (!clean) return [];
  const keys = new Set();
  const normalized = toPosix(path.normalize(clean));
  keys.add(normalized);
  keys.add(withoutMd(normalized));

  if (clean.includes("/") || clean.includes("\\")) {
    const fromDir = path.dirname(toPosix(path.relative(vaultRoot, fromFile)));
    const relative = toPosix(path.normalize(path.join(fromDir, clean)));
    keys.add(relative);
    keys.add(withoutMd(relative));
  } else {
    keys.add(withoutMd(path.posix.basename(normalized)));
    keys.add(path.posix.basename(normalized));
  }

  return [...keys].filter(Boolean);
}

walk(vaultRoot);

const byPath = new Map();
const byBase = new Map();

for (const file of allFiles) {
  const rel = toPosix(path.relative(vaultRoot, file));
  const lowerRel = rel.toLowerCase();
  byPath.set(lowerRel, file);
  if (rel.toLowerCase().endsWith(".md")) byPath.set(withoutMd(lowerRel), file);

  const base = rel.toLowerCase().endsWith(".md")
    ? withoutMd(path.posix.basename(rel)).toLowerCase()
    : path.posix.basename(rel).toLowerCase();
  const list = byBase.get(base) || [];
  list.push(file);
  byBase.set(base, list);
}

const problems = [];
const wikilink = /!?\[\[([^\]]+)\]\]/g;
const markdownLink = /!?\[[^\]]*\]\(([^)]+)\)/g;

for (const file of markdownFiles) {
  const text = fs.readFileSync(file, "utf8");
  const rel = toPosix(path.relative(vaultRoot, file));
  const targets = [];
  let match;

  while ((match = wikilink.exec(text))) targets.push(match[1]);
  while ((match = markdownLink.exec(text))) {
    const target = match[1].replace(/^<|>$/g, "").trim();
    if (!isExternal(target)) targets.push(target);
  }

  for (const target of targets) {
    const clean = stripTarget(target);
    if (!clean || isExternal(clean)) continue;
    const keys = candidateKeys(clean, file);
    const pathHit = keys.some((key) => byPath.has(key.toLowerCase()));
    const baseHits = keys.flatMap((key) => byBase.get(path.posix.basename(key).toLowerCase()) || []);

    if (!pathHit && baseHits.length === 0) {
      problems.push(`${rel} -> missing [[${clean}]]`);
    } else if (!pathHit && baseHits.length > 1) {
      problems.push(`${rel} -> ambiguous [[${clean}]] (${baseHits.length} matches)`);
    }
  }
}

if (problems.length) {
  console.error("깨진/모호한 내부 링크가 발견됐습니다:");
  for (const problem of problems) console.error(`- ${problem}`);
  process.exit(1);
}

console.log(`OK: ${vaultRoot} 의 Markdown ${markdownFiles.length} 건을 확인했습니다`);

실행은 이게 전부입니다.

node scripts/audit-wikilinks.cjs .

링크가 전부 살아 있으면 OK 가 나옵니다. 끊겼으면, 어느 파일의 어느 링크가 죽었는지 목록으로 알려줘요. AI가 정리한 직후에 이걸 돌리는 게, 제 매일의 마무리입니다.

참고로 Obsidian 쪽 구조(Daily notes, Templates, Properties, 내부 링크)의 정확한 사양은 Obsidian 공식 도움말에 닿아 주세요. AI에게 맡기기 전에, 자기가 모선의 사양을 알아 두면 이상한 지시를 안 내려도 됩니다.

실제로 해본 결과

3개월, 이 구조로 제 Vault를 돌려 봤습니다.

가장 효과적이었던 건, 역시 “일일 로그 인수인계”와 “공개 전 링크 감사”였어요. 아침 일찍 AI에게 전날을 요약시키면, 시동이 걸리는 게 확연히 빠릅니다. “어제의 나, 여기서 멈춰 있었구나”가 한눈에 보이니까, 커피를 내리는 사이에 머리가 오늘 모드로 전환돼요. 링크 감사를 끼우게 된 뒤로는, 글을 공개한 다음에 “아, 내부 링크 끊겼네” 하고 핏기가 가시는 밤이 0이 됐습니다. 숫자로 말하면, 감사 전에는 한 달에 2~3번은 링크 끊김을 공개했는데, 지금은 0이에요.

또 하나, 뜻밖의 부산물이 있었습니다. AI에게 매일 만지게 한다는 전제가 생기면서, 제 메모 쓰는 법 자체가 바뀐 거예요. 나중에 기계에게 읽힐 거라 생각하면, 자연스럽게 status: 를 붙이거나, 거친 메모에도 한 줄만 맥락을 보태게 됩니다. AI를 위해 다듬고 있었던 셈인데, 결국 가장 읽기 편해진 건 사람인 나 자신이었어요.

반대로, 처음에 전체 Vault를 한 번에 정리시킨 회차는 확실히 실패였습니다. 오래된 메모의 의미를 AI가 너무 추측해서, 태그와 제목이 오히려 늘고 어질러졌어요. 결론은 단순합니다. “허가 범위는 좁게·템플릿은 얇게·마지막에 Node 감사” 세 가지를 지킬수록 잘 굴러갑니다.

재미있는 건, 반년 전이라면 절대 못 떠올렸을 2년 전 그 명령어에, 지금은 5초 만에 닿을 수 있다는 겁니다. AI가 꾸준히 달아준 링크를, 제가 그저 따라가기만 하면 돼요. 메모가, 제대로 뇌가 되어 왔습니다.

정리

“메모가 저절로 자란다”의 정체는, 마법이 아닙니다. 읽기는 넓게, 쓰기는 좁게, 마지막에 기계로 검산한다. 이 당연한 걸 구조로 만들었을 뿐이에요.

할 일은 3가지. 폴더를 나누고, CLAUDE.md 로 규칙을 적고, .claude/settings.json 으로 위험한 조작을 물리적으로 막는다. 이후엔 “알아서 잘”이라고 말하지 말고, 범위를 좁혀 부탁하기만 하면 됩니다. 오늘은 우선 daily/ 하나부터 시작해 보세요. 내일의 내가, 조금 편해집니다.

권한과 샌드박스 사고방식을 더 자세히 알고 싶은 분은 Claude Code의 승인·샌드박스 설계도 보세요. 손을 움직이는 템플릿이나, 팀 도입 상담처는 교재 목록에 정리해 뒀습니다.

#obsidian #claude-code #second-brain #pkm #markdown #automation
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.