用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/treylom/knowledge-manager --skill km-search命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | km-search |
| description | vault 통합 검색 — GraphRAG(있으면) → Obsidian CLI → (Obsidian MCP) → 텍스트 검색 4단계 자동 폴백. quick(즉답)/deep(분석) 자동 라우팅 |
사용자가 vault 검색을 요청하면(예: "vault에서 X 찾아줘", "km search X", "--deep X") 이 스킬의 절차를 따른다. query = 사용자의 질문 본문(아래 플래그 제거 후).
핵심 설계: 검색 창구는 이 스킬 하나입니다. 뒤에서 어떤 검색 엔진이 도는지는 자동으로 결정됩니다 — ① GraphRAG 서버(설치돼 있으면) → ② Obsidian CLI → ③ Obsidian MCP(연결된 경우) → ④ 텍스트 검색 순서로, 앞 단계가 없거나 실패하면 자동으로 다음 단계로 넘어갑니다. GraphRAG를 아직 설치하지 않았어도 이 스킬은 그대로 동작합니다(②~④가 받아줍니다). 나중에 GraphRAG 스택을 얹으면 같은 절차가 자동으로 ①을 쓰기 시작합니다.
찾는 범위: 이 명령은 vault 안의 문서·개념·문서 사이 관계를 찾습니다. 반면 과거 대화에서 무슨 말이 오갔는지·어떤 결정이 왜 내려졌는지는 성격이 다른 질문이라, 대화 기록을 따로 보관·검색하는 도구가 있다면 그쪽이 먼저입니다. 둘 다 봐야 하는 질문이라면 지금 기준·현재 상태는 문서 쪽, 원래 발언·결정 경위는 대화 기록 쪽을 우선하세요. 두 결과가 어긋나면 감추지 말고
현재 기준과과거 경위로 나눠 적는 편이 낫습니다. 그리고 한쪽에서 안 나왔다고 다른 쪽에도 없다고 단정하지 마세요 — 서로 다른 코퍼스입니다.
🚨 실행 순서 계약 (고정 — 첫 행동을 여기서 정한다): ① Phase -1 로 설정 2개(
VAULT_PATH·SEARCH_ENDPOINT)를 읽는다 → ② 곧바로 Tier 1 서버 검색 curl 을 실행한다. 이 ①② 보다 먼저 vault 파일을 검색·나열·읽기(rg / grep / find / ls / Read) ❌ — 검색의 1차 수단은 서버이고, 로컬 파일은 Tier 1 의 원문 확보 계약(VAULT_MODE=same)이 허용할 때 또는 Tier 1 이 실패로 판정된 뒤(Tier 2~4)에만 연다. "vault 를 확인해보겠다"며 로컬부터 뒤지는 첫 행동 = 이 계약 위반이다.
km-config.json을 찾는다 (현재 폴더 → 플러그인 설치 시 setup이 만든 위치 순).
storage.obsidian.vaultPath → VAULT_PATH. 없으면 사용자에게 vault 경로를 1회 묻고 진행.
vaultPath 가 이미 있으면 아래 판정을 실행하지 않는다 — 그 값이 정답) — obsidian.json 에서 "open": true 인 항목이 사용자가 실제로 열어 두는 vault 다. 백업 사본·형제 폴더도 .obsidian 을 갖고 있어서 그것만으론 안 갈린다.
# 경로를 직접 짚는다 — 넓은 find 로 훑지 말 것(/mnt/c/Users 전수 탐색은 느리고 빈손으로 끝난다).
OBSIDIAN_JSON=$(ls -1 \
"$HOME/Library/Application Support/obsidian/obsidian.json" \
/mnt/c/Users/*/AppData/Roaming/obsidian/obsidian.json \
"$APPDATA/obsidian/obsidian.json" 2>/dev/null | head -1)
python3 -c 'import json,sys;d=json.load(open(sys.argv[1]))["vaults"];print("\n".join(v["path"] for v in d.values() if v.get("open")))' "$OBSIDIAN_JSON"
WSL 에서는 이 값이 윈도우 경로(C:\Users\...)로 나온다 — wslpath -u 로 바꿔 쓴다.
이 파일이나 "open": true 항목을 못 찾았을 때만 사용자에게 묻는다..obsidian 폴더 존재, 최근 수정된 md 유무. 둘 다 아니면 그 경로를 쓰기 전에 다시 확인한다.obsidianCli.path → OBSIDIAN_CLI (비어 있으면 아래 Tier 2의 자동 감지 사용).
SEARCH_ENDPOINT = linking.semantic_adapter.endpoint → 환경변수 GRAPHRAG_API_URL → 기본값 http://127.0.0.1:8400 순. 미설정이어도 기본값을 탐침한다 — 로컬에 서버가 없으면 즉시 연결 거부로 끝나 지연이 거의 없고(--connect-timeout 3은 상한일 뿐), 덕분에 나중에 /tofugraph build로 스택을 얹으면 설정 변경 없이 같은 절차가 자동으로 Tier 1을 쓰기 시작한다.
# 필수 실행(집행 계약): endpoint 는 반드시 아래 셸 할당으로 결정하고, echo 로 확인한 뒤 Tier 1 을 호출한다.
# CONFIG_ENDPOINT = km-config.json 의 linking.semantic_adapter.endpoint 값 (없으면 빈 값 유지)
SEARCH_ENDPOINT="${CONFIG_ENDPOINT:-${GRAPHRAG_API_URL:-http://127.0.0.1:8400}}"
echo "SEARCH_ENDPOINT=${SEARCH_ENDPOINT}"
IF query가 비어있으면:
→ "사용법: km-search <질문> 또는 km-search --deep <질문>"
→ "예시: km-search MCP란? | km-search --deep 프롬프트 엔지니어링 기법 비교"
→ 종료
--quick 또는 -q → QUICK (플래그 제거 후 나머지가 query)--deep 또는 -d → DEEP (플래그 제거 후 나머지가 query)--no-moc → MOC 제외, 원자 노트 전용검색 결과 중 MOC 성격 노트(frontmatter type/tags에 MOC 포함, 또는 파일명에 -MOC)를 최상위로 고정한다:
📌 상위 MOC (N) 섹션 + 📄 원자 노트 (N) 섹션 분리 (MOC 0개면 📌 생략)Why: 노트가 많아질수록 원자 나열은 찾기 어려움 — MOC(지도 노트)가 허브·진입점 역할.
QUERY_ENCODED=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "${QUERY}")
# --max-time 필수: 서버가 "죽은 게 아니라 막힌" 상태면 연결은 성공하므로
# --connect-timeout 은 걸리지 않는다(무한 대기). 실측 근거는 아래 주석 참조.
gr_fetch() { curl -s --connect-timeout 3 --max-time 20 \
"$1/api/search?q=${QUERY_ENCODED}&top_k=${TOP_K}&mode=hybrid"; }
TIER1_JSON="$(gr_fetch "${SEARCH_ENDPOINT}")"; TIER1_RC=$?
# 설정된 곳이 원격(다른 기계)일 수 있다. 거기가 안 되면 로컬 서버를 한 번 더 두드린다.
if { [ $TIER1_RC -ne 0 ] || [ -z "$TIER1_JSON" ]; } \
&& [ "${SEARCH_ENDPOINT}" != "http://127.0.0.1:8400" ]; then
TIER1_JSON="$(gr_fetch http://127.0.0.1:8400)"; TIER1_RC=$?
if [ $TIER1_RC -eq 0 ] && [ -n "$TIER1_JSON" ]; then
ENDPOINT_SWITCHED="${SEARCH_ENDPOINT} → http://127.0.0.1:8400"
SEARCH_ENDPOINT="http://127.0.0.1:8400"
fi
fi
# 폴백 문구를 가르기 위한 상태 판정 — curl exit code 가 병명을 가른다.
# 7 = 연결 거부 → 서버가 없다 (absent)
# 28 = 시한 초과 → 서버는 있는데 막혔다 (unreachable)
if [ $TIER1_RC -eq 0 ] && [ -n "$TIER1_JSON" ]; then GRAPHRAG_STATE=ok
elif [ $TIER1_RC -eq 7 ]; GRAPHRAG_STATE=absent
GRAPHRAG_STATE=unreachable
[ = ] && [ -r /proc/net/tcp ] \
&& [ -le 1 ];
GRAPHRAG_STATE=blocked
GRAPHRAG_STATE=ok → 이 티어 결과를 쓴다. 그 외 → Tier 2로 내려가되 상태값을 들고 간다(Tier 4 표시 문구가 이 값으로 갈린다).entity, source_note 는 채워져 있을 때만 vault 상대 경로다(description 은 비어 있을 수 있으니 근거로 지목하지 말 것). 원문을 읽기 전에 아래 0단 판정을 검색당 1회만 하고, 그 결과(VAULT_MODE)를 이후 모든 절이 따른다. 어느 단계에서도 그 밖의 다른 vault 를 뒤지거나 Obsidian 설정(obsidian.json)과의 대조를 시도하지 말 것 — 무한 "대조 중" 멈춤의 원인이다. (Phase -1 의 obsidian.json vault 판정은 별개 — 그건 VAULT_PATH 가 설정에 없을 때의 설정 단계 1회다.)
0. vault 정합 판정 (검색당 1회 — 이 판정 전에는 로컬 노트를 열지 않는다): 첫 응답에서 source_note 가 있는 결과 하나를 골라 ${VAULT_PATH}/{source_note} 의 파일 존재만 확인한다([ -f ... ] 1회 — Read ❌).
VAULT_MODE=same (서버 = 이 vault. 로컬 Read 허용)source_note 가진 결과가 0건 → VAULT_MODE=other (서버는 다른 vault 를 인덱싱 중 — 예: 강의용 샘플 인덱스, 다른 폴더에서 build 한 인덱스). 이후 이 검색의 모든 단계에서 로컬 파일 접근·경로 변환(wslpath 등)·vault 탐색 = 0회. 원문은 오직 /api/note 로 받는다 — QUICK/DEEP 의 "노트 원문 확보"와 Phase 2.5 도 전부 이 스위치를 따른다.same 전용) source_note 가 있으면 ${VAULT_PATH}/{source_note} 를 Read 한다 (기존 경로).curl -s "${SEARCH_ENDPOINT}/api/note?name=<entity>&max_chars=2500" --connect-timeout 3 --max-time 15 로 조회한다(max_chars 는 100~20000) → 응답 = note_path(vault 상대 경로) + body(원문). other 모드에서는 body 가 원문 근거의 전부다 — 그대로 인용해 답변한다(body 는 그 vault 노트의 실제 본문이므로 hallucination 금지 제약을 충족한다). 답변 말미 티어 표기 = 검색: GraphRAG 서버 (다른 vault 인덱스 — 서버 본문 기반). 사용자 vault 기준 인덱스를 원하면 "/tofugraph build를 이 vault에서 실행하면 서버가 이 vault를 검색 대상으로 제공합니다" 1줄을 덧붙인다./api/note 가 실패하면(404 note not indexed — 엔티티는 그래프에 있으나 인덱싱된 원문이 없는 경우. 이름을 바꿔 재시도하지 말 것) → entity·source_note·점수만으로 답하되, 답변에 "원문 미확보 — 서버 메타데이터 기반" 한계를 명시한다.# km-config의 obsidianCli.path 우선, 비어 있으면 자동 감지:
# mac: /Applications/Obsidian.app/Contents/MacOS/obsidian-cli
# wsl: /mnt/c/Program Files/Obsidian/Obsidian.com
# windows: C:\Program Files\Obsidian\Obsidian.com
"$OBSIDIAN_CLI" search query="${QUERY}" format=json limit=1000
연결된 Obsidian MCP의 검색 도구를 사용한다(서버 구현마다 도구명이 다르다 — 예: simple_search, obsidian_simple_search). MCP 서버 미연결 → Tier 4로.
grep -rn "${QUERY}" "${VAULT_PATH}" --include="*.md" -l | head -20
GRAPHRAG_STATE 로 가른다:
absent → "의미 검색 엔진 미설치로 텍스트 검색 결과입니다"unreachable → "GraphRAG 서버(${SEARCH_ENDPOINT})가 응답하지 않아 텍스트 검색으로 대체했습니다 — 결과가 평소보다 부정확할 수 있습니다"blocked → "이 세션은 네트워크가 막혀 있어 GraphRAG 서버에 접속할 수 없습니다 — 서버 문제가 아닙니다. 에이전트 실행 시 네트워크를 허용하면(codex: -c sandbox_workspace_write.network_access=true) 의미 검색이 살아납니다"💡 top_k 를 더 올리고 싶을 때 — 후보 수를 늘리면 결과가 좋아질 것 같지만, 실제로는 반대로 가는 경우가 많습니다. 풀이 커지면 원래 상위에 있던 정답이 뒤로 밀립니다. 품질을 올리는 지렛대는 후보 수가 아니라 순위라서, DEEP 의 심화는 top_k 보다 Phase 2.5(frontmatter·backlinks 그래프 확장) 쪽이 담당합니다. 올려야 할 때는 하나뿐입니다 — "정말 없는지" 확인할 때. 상위 결과만으로 부재를 단정할 수 없으면 경계 확인용으로 넓히고, 그 결과는 상위 근거와 분리해서 표기하세요.
💡 찾은 결과를 쓸 때 — 검색기를 좋게 만들어도 답이 같은 폭으로 좋아지지는 않습니다. 정답 문서가 결과에 들어와 있는데도 안 쓰이는 일이 흔합니다.
- 답에 근거로 쓸 문서는 실제로 열어 읽으세요. 제목과 미리보기만 보고 "있다 / 없다 / 원인은 이것"을 단정하지 마세요. 위
노트 읽기개수가 그 최소선입니다. (그냥 둘러보는 중이라면 해당 없습니다.)- 상위 몇 건이 같은 주장만 반복하면, 기존 폴백 단계 안에서 다른 성격의 근거가 나올 때까지 다음 결과를 더 여세요.
- 근거는 앞쪽에. 결과를 다음 단계로 넘길 때 결론이 실제로 기대는 문서를 앞에
문서 — 근거 한 줄 — 왜 관련되는지 한 줄로 묶고 보조 자료는 뒤로 보내세요. 같은 근거를 본문·부록·요약에 반복해 넣을 필요는 없습니다.- 여러 건을 넘길 때는 관계를 한 줄로. 세 건 이상을 다른 도구나 에이전트에 넘긴다면
A=원인 · B=재현 · C=해결처럼 문서 사이 관계를 한 줄 적어 주세요. 검색 결과를 통째로 직렬화해 넘기는 것보다 받는 쪽이 훨씬 잘 씁니다.
검색 엔진은 "어느 노트인가"까지만 안다. vault 의 진짜 구조 신호는 노트 안에 있다 — frontmatter(태그·별칭·관련)와 wikilink 그래프(backlinks)를 활용해야 검색이 똑똑해진다.
⚠️ Tier 1
VAULT_MODE=other(서버가 다른 vault 인덱싱 중)면 본 Phase 전체를 생략한다 — 로컬 vault 의 frontmatter·backlinks 는 서버 인덱스와 다른 지식그래프라 근거가 되지 않고, 로컬 grep 은 "로컬 접근 0회" 원칙을 깬다. 서버 본문(body) 기반으로만 답한다.
노트를 Read 하면 본문 전에 frontmatter 를 먼저 해석한다:
aliases: → 재질의 사전: 1차 검색이 0건·빈약하면 별칭(영/한 표기 변형)으로 1회 재검색.tags: · type: → MOC/허브 판정(Phase 0.5 입력) + 답변의 분류 근거.related: · parent: · 본문 [[링크]] → 추가 Read 후보(질문과 키워드가 겹치는 것 1~2개).top 1~2 노트에 대해 backlink(그 노트를 가리키는 노트) 와 outlink(그 노트가 가리키는 노트) 를 실측한다:
# 집행 계약: DEEP 모드에서 top 1~2 노트에 반드시 실행. backlinks = 전 플랫폼 grep 근사 —
# Obsidian CLI 의 backlinks 서브커맨드가 있으면(맥 데스크톱) 그걸 우선, 부재·오류 시 아래가 항상 동작한다.
# 변수 규약: NOTE_PATH = VAULT_PATH 기준 상대경로. (절대경로가 들어와도 아래 NOTE_FILE 라인이 흡수한다.)
NOTE_FILE="${VAULT_PATH}/${NOTE_PATH}"; [ -f "$NOTE_FILE" ] || NOTE_FILE="${NOTE_PATH}"
STEM="$(basename "${NOTE_PATH}" .md)"
grep -rl --include="*.md" -F "[[${STEM}" "${VAULT_PATH}" | head -10
grep -o '\[\[[^]|#]*' "${NOTE_FILE}" | sed 's/^\[\[//' | sort -u | head -15
| wc -l) → 이 정수 N 이 답변 마지막 줄 그래프 확장(backlinks N) 에 들어간다 (제약 §"사용 티어 명시" 형식 고정과 1:1).트리거 (상태 기반 — 부재 문장을 쓸 계획이 있든 없든 무관): 다음 중 하나면 답변을 쓰기 전에 아래 3단을 실행한다.
어느 쪽이든 3단을 각각 독립 실행한 뒤에만 답변을 작성할 수 있다.
① 축약 재질의 (실행) — 핵심 키워드 1~2개로 줄여 현재 티어를 1회 재실행.
② 별칭·표기 변형 재질의 (실행) — 읽은 노트 frontmatter aliases + 영↔한 표기 변형으로 1회 재실행.
③ wikilink 언급 탐색 (실행):
grep -rln --include="*.md" -F "[[${KEYWORD}" "${VAULT_PATH}" | head -10
(노트 제목에는 없어도 다른 노트들이 [[링크]]로 언급하는 경우를 잡는다.)
[[링크]] 언급으로 존재(N개 노트)"를 답하고 언급 노트를 출처로 제시한다.…관련 자료 없음 (재질의 3단: ①"<축약어>" 0건 ②"<변형어>" 0건 ③[[언급]] 0건) — 3단 증빙이 없는 부재 발화는 계약 위반이다.지목 자료: "<대상>" — exact 0건 · 재질의: ①"<축약어>" <n1>건 ②"<변형어>" <n2>건 ③[[언급]] <n3>건. n3 > 0 이면 언급 노트를 출처 목록에 올린다. 이 줄이 없는 lookup 답변은 미완이다.상위 1-2개 노트의 원문 확보 → frontmatter + 핵심 섹션 추출. 원문 = Tier 1 이면 원문 확보 계약을 따른다(VAULT_MODE=same=로컬 Read · other=/api/note 의 body, 로컬 경로 접근 ❌). Tier 2~4 로 검색한 경우 = 로컬 Read.
**답변:**
[3~5줄 직접 답변. 노트 내용 기반.]
📌 **상위 MOC** (N)
1. **[[MOC 제목]]** — [범위·역할 한 줄] (`경로`)
📄 **원자 노트** (N)
1. **[노트 제목]** — [핵심 한 줄] (`경로`)
상위 3-5개 노트의 원문 확보(Tier 1 VAULT_MODE=other 면 /api/note 의 body — 로컬 Read ❌) → Phase 2.5 그래프 확장(frontmatter·backlinks 1-hop — other 면 생략) 실행 → 제목·요약·핵심 섹션 + 연결 맥락을 종합하여 질문에 직접 답변(목록·표·단계 활용).
## {질문 요약}
{답변 본문. 구조화된 분석.}
### 📌 상위 MOC (진입점)
1. [[MOC1]] — {범위·역할 1줄} (`경로`)
### 📄 원자 노트 (출처)
1. [[노트1]] — {핵심 정보 1줄} (`경로`)
### 🔗 연결 맥락 (Phase 2.5)
- [[허브노트]] ← backlinks {N}개 · 따라간 링크: [[관련1]], [[관련2]] (그래프 신호 없으면 섹션 생략)
/api/note 의 body 와 source_note 경로의 원문이 '실제 노트 내용'이다 — 둘 다 그 vault 노트에서 나온다. 로컬 원문 Read 는 경로가 실재할 때의 보강이지, Read 실패가 답변을 막는 게이트가 아니다)경로 는 그 볼트 기준 상대 경로다. 사용자는 이 줄로 자료가 «어떤 볼트의 어디에» 있는지 즉시 안다. 이 줄이 없는 답변 = 미완이다.검색: <티어명> + 그래프 확장(backlinks N). N = Phase 2.5-B backlink grep 결과 줄 수(실측 정수, 생략·"1-hop" 같은 서술 대체 ❌). 그래프 확장을 안 한 답변(QUICK 얕은 질의, 또는 Tier 1 VAULT_MODE=other 로 Phase 2.5 를 생략한 경우)은 검색: <티어명> 단독 허용.vault에서 "{query}" 관련 자료를 찾지 못했습니다.
(재질의 3단: ①"<축약어>" 0건 ②"<변형어>" 0건 ③[[언급]] 0건 — Phase 2.5-C 증빙 형식)
knowledge-manager로 자료를 수집해보세요.
ENDPOINT_SWITCHEDnone⚠️ 원래 서버(<원주소>)가 응답하지 않아 <새주소> 로 검색했습니다 — 색인된 vault 가 다를 수 있습니다source_note 경로를 한 건 그대로 함께 보여 준다 — 사용자가 "내 vault 가 맞나"를 눈으로 가릴 수 있게. ⚠️ 경로 앞부분이 vault 이름이라고 가정하지 말 것: 서버 설정에 따라 vault 이름으로 시작하기도 하고(Tofu_LLM_Wiki/...) vault 안 상대경로로 시작하기도 한다(020-Library/...) — 2026-07-27 두 서버 실측. 접두어는 힌트지 식별자가 아니다.--max-time 20 의 근거(2026-07-27 실측): 정상 응답이 7.5초 걸린 경우가 있었고, 같은 서버가 30초를 넘겨 시한 초과한 경우도 있었다. 5초·3초로 잡으면 멀쩡한 서버를 "없음"으로 만든다. 환경별로 다르면 이 값을 조정하되, 관측된 정상 응답 시간보다 넉넉히 크게 잡는다./health 200 을 서버 정상의 근거로 쓰지 말 것 — /health 는 200 인데 /api/search 만 막히는 형태가 실제로 관측된다. 판정은 위처럼 검색 경로 자체로 한다./tofugraph 명령으로 GraphRAG 스택을 구축하면 이 티어가 자동으로 살아난다.)