add template

This commit is contained in:
김경종
2026-04-17 10:35:09 +09:00
parent 5c308e14b2
commit e8f6208df1
41 changed files with 1133 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
---
name: output-drafter
description: Draft deliverables in `output/` from existing `wiki/` pages. Use for articles, lecture outlines, book drafts, service concepts, and other user-facing synthesis grounded in the vault. 한국어 요청에도 사용.
kind: local
max_turns: 16
timeout_mins: 12
---
You are the output composer for this LLM Wiki.
Rules:
1. Base new drafts on `wiki/` pages whenever possible.
2. If the knowledge base is thin, say what should be compiled into `wiki/` first.
3. Structure the deliverable for the intended audience and purpose.
4. Leave a concise note about the key `wiki/` pages that support the draft.
5. Avoid claiming certainty that is not supported by the vault.
+16
View File
@@ -0,0 +1,16 @@
---
name: vault-researcher
description: Search and reason over the vault with wiki-first retrieval. Use for questions like "내 볼트에 뭐가 있어?", comparisons between concepts, source tracing, or finding material for synthesis. 한국어 요청에도 사용.
kind: local
max_turns: 12
timeout_mins: 10
---
You are the vault researcher for this workspace.
Operating rules:
1. Start from `wiki/index.md`.
2. Search `wiki/` pages before touching `raw/`.
3. Distinguish clearly between grounded facts and synthesized interpretation.
4. When possible, mention the most relevant page paths you relied on.
5. Do not edit files unless the user explicitly asks for an update or a new page.
+20
View File
@@ -0,0 +1,20 @@
---
name: wiki-compiler
description: Compile `raw/` sources into `wiki/` pages. Use for requests such as compiling articles, book chapters, transcripts, or updating existing wiki pages with new raw material. 한국어 요청에도 사용.
kind: local
max_turns: 16
timeout_mins: 12
---
You are the dedicated compiler for this Obsidian LLM Wiki.
Rules:
1. Never mutate `raw/`.
2. Read `wiki/index.md` before deciding whether to create or update a page.
3. Prefer updating an existing wiki page over creating a new one.
4. Every wiki page must keep YAML frontmatter with `title`, `tags`, `sources`, `created`, `updated`.
5. Use `[[wikilink]]` for internal references.
6. After wiki changes, update `wiki/index.md` and append an entry to `wiki/log.md`.
7. Keep source summaries factual. Put synthesis in concept or connection pages.
Return a concise summary of what you changed and which sources you used.
+19
View File
@@ -0,0 +1,19 @@
description = "Answer a question from the vault using wiki-first retrieval."
prompt = """
다음 질문에 대해 내 볼트에서 답하라:
{{args}}
탐색 규칙:
1. 먼저 아래 인덱스를 보고 관련 범위를 좁힌다.
2. `wiki/` 페이지를 우선적으로 탐색한다.
3. `wiki/`로 충분하지 않을 때만 `raw/`를 본다.
4. 답변은 사실, 해석, 추가 탐색 필요 항목을 구분해서 쓴다.
5. 마지막에 참고한 핵심 파일 경로를 짧게 남긴다.
현재 인덱스:
@{wiki/index.md}
가능하면 `llm-wiki-operator` skill과 `vault-researcher` agent를 활용하라.
"""
+18
View File
@@ -0,0 +1,18 @@
description = "Compile one or more raw sources into structured wiki pages."
prompt = """
다음 입력을 기준으로 `raw/` 원본을 `wiki/`로 컴파일하라:
{{args}}
작업 원칙:
1. 먼저 `wiki/index.md`와 관련 `GEMINI.md` 파일을 확인한다.
2. `raw/`는 읽기만 하고 절대 수정하지 않는다.
3. 기존 wiki 페이지가 있으면 우선 업데이트하고, 꼭 필요할 때만 새 페이지를 만든다.
4. 모든 wiki 페이지에는 YAML frontmatter(`title`, `tags`, `sources`, `created`, `updated`)를 유지한다.
5. 내부 참조는 `[[wikilink]]`를 사용한다.
6. 작업 후 `wiki/index.md`와 `wiki/log.md`를 함께 갱신한다.
7. 소스 요약은 사실 위주로 쓰고, 해석은 개념 페이지에 둔다.
가능하면 `llm-wiki-operator` skill과 `wiki-compiler` agent를 활용하라.
"""
+19
View File
@@ -0,0 +1,19 @@
description = "Connect and integrate multiple concepts or documents in the vault."
prompt = """
다음 개념, 주제, 문서를 연계·융합하여 체계적으로 정리하라:
{{args}}
작업 방식:
1. `wiki/index.md`에서 관련 개념 후보를 찾는다.
2. 관련 `wiki/` 페이지를 읽어 공통점, 긴장점, 빈틈을 정리한다.
3. 사실로 확인된 내용, 관계 구조, 통합 해석을 분리해서 쓴다.
4. 가능하면 서로 다른 두 개 이상의 소스 축을 연결한다.
5. 결과가 재사용 가능하면 새로운 `wiki/` 연결·융합 페이지가 필요한지도 판단한다.
현재 인덱스:
@{wiki/index.md}
가능하면 `llm-wiki-operator` skill과 `vault-researcher` agent를 활용하라.
"""
+19
View File
@@ -0,0 +1,19 @@
description = "Draft an output document from the existing wiki knowledge base."
prompt = """
다음 요청에 맞는 결과물을 `output/`에 초안으로 작성하라:
{{args}}
작성 규칙:
1. 먼저 `wiki/index.md`와 관련 `wiki/` 페이지를 확인한다.
2. 가능하면 `raw/`를 직접 재해석하지 말고, `wiki/` 기반으로 합성한다.
3. 결과물의 구조는 목적에 맞게 설계하되, 근거가 되는 핵심 `wiki/` 페이지를 본문 또는 말미에 남긴다.
4. 새 초안을 쓰거나 기존 초안을 업데이트한 뒤, 필요하면 `wiki/log.md`에도 관련 작업을 기록한다.
5. 부족한 근거가 있으면 어떤 wiki 페이지를 먼저 보강해야 하는지 제안한다.
현재 인덱스:
@{wiki/index.md}
가능하면 `llm-wiki-operator` skill과 `output-drafter` agent를 활용하라.
"""
+16
View File
@@ -0,0 +1,16 @@
description = "Initialize or repair the LLM Wiki scaffolding for this vault."
prompt = """
이 옵시디언 볼트를 Gemini CLI 기반 LLM Wiki로 초기화하거나 점검하라.
필수 확인 항목:
1. 루트와 주요 하위 폴더에 `GEMINI.md`가 있는지 확인한다.
2. `wiki/index.md`와 `wiki/log.md`가 없으면 생성하고, 있으면 구조를 점검한다.
3. `raw/`는 읽기 전용 원칙을 유지한다.
4. `.gemini/commands/`, `.gemini/skills/`, `.gemini/agents/`, `.gemini/settings.json`, `.gemini/hooks/`가 의도대로 정리되어 있는지 확인한다.
5. 필요한 수정이 있으면 직접 반영한다.
6. 끝나면 현재 상태와 다음에 바로 쓸 수 있는 명령 예시를 간단히 요약한다.
참고 인덱스:
@{wiki/index.md}
"""
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
function readStdin() {
return fs.readFileSync(0, 'utf8');
}
function isInside(parent, target) {
const relative = path.relative(parent, target);
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}
function stripQuotes(value) {
return value.replace(/^["']|["']$/g, '');
}
function collectPathStrings(value, key = '', output = []) {
if (value == null) {
return output;
}
if (typeof value === 'string') {
if (/path|file/i.test(key)) {
output.push(value);
}
return output;
}
if (Array.isArray(value)) {
value.forEach((item) => collectPathStrings(item, key, output));
return output;
}
if (typeof value === 'object') {
Object.entries(value).forEach(([childKey, childValue]) => {
collectPathStrings(childValue, childKey, output);
});
}
return output;
}
function resolveCandidate(candidate, rootDir) {
const cleaned = stripQuotes(String(candidate).trim());
if (!cleaned || /^(https?:|file:|data:)/i.test(cleaned)) {
return null;
}
const resolved = path.isAbsolute(cleaned)
? path.resolve(cleaned)
: path.resolve(rootDir, cleaned);
return resolved;
}
function commandTouchesRaw(command, rawDir) {
if (!command || typeof command !== 'string') {
return false;
}
const normalizedRaw = rawDir.toLowerCase().replace(/\//g, path.sep);
const lower = command.toLowerCase();
const mentionsRaw =
lower.includes(normalizedRaw.toLowerCase()) ||
/(^|[\s"'`])raw([\\/]|$)/i.test(command);
if (!mentionsRaw) {
return false;
}
return /(remove-item|move-item|copy-item|set-content|add-content|out-file|new-item|\brm\b|\bdel\b|\berase\b|\bmv\b|\bmove\b|\bcp\b|\bcopy\b|>>|(?<!\d)>(?!\d)|sed\s+-i|perl\s+-pi|\btouch\b)/i.test(command);
}
function deny(reason) {
process.stdout.write(JSON.stringify({ decision: 'deny', reason }));
}
try {
const input = JSON.parse(readStdin() || '{}');
const projectDir = process.env.GEMINI_PROJECT_DIR || input.cwd || process.cwd();
const rawDir = path.resolve(projectDir, 'raw');
const toolName = input.tool_name || '';
const toolInput = input.tool_input || {};
if (toolName === 'run_shell_command') {
const command = toolInput.command || toolInput.cmd || '';
if (commandTouchesRaw(command, rawDir)) {
deny('raw/는 불변 원본이다. raw/를 수정하지 말고, 필요하면 wiki/ 또는 output/에 결과를 작성하라.');
process.exit(0);
}
process.exit(0);
}
const candidates = collectPathStrings(toolInput);
const targets = candidates
.map((candidate) => resolveCandidate(candidate, projectDir))
.filter(Boolean);
const rawTargets = targets.filter((target) => isInside(rawDir, target));
if (rawTargets.length > 0) {
deny('raw/는 불변 원본이다. 해당 경로를 수정하지 말고, 요약·해석·구조화는 wiki/에서 수행하라.');
}
} catch (error) {
process.stderr.write(String(error));
process.exit(1);
}
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
function readStdin() {
return fs.readFileSync(0, 'utf8');
}
function isInside(parent, target) {
const relative = path.relative(parent, target);
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}
function collectPathStrings(value, key = '', output = []) {
if (value == null) {
return output;
}
if (typeof value === 'string') {
if (/path|file/i.test(key)) {
output.push(value);
}
return output;
}
if (Array.isArray(value)) {
value.forEach((item) => collectPathStrings(item, key, output));
return output;
}
if (typeof value === 'object') {
Object.entries(value).forEach(([childKey, childValue]) => {
collectPathStrings(childValue, childKey, output);
});
}
return output;
}
function resolveCandidate(candidate, rootDir) {
const cleaned = String(candidate).trim().replace(/^["']|["']$/g, '');
if (!cleaned) {
return null;
}
return path.isAbsolute(cleaned)
? path.resolve(cleaned)
: path.resolve(rootDir, cleaned);
}
try {
const input = JSON.parse(readStdin() || '{}');
if (input.tool_response && input.tool_response.error) {
process.exit(0);
}
const projectDir = process.env.GEMINI_PROJECT_DIR || input.cwd || process.cwd();
const wikiDir = path.resolve(projectDir, 'wiki');
const outputDir = path.resolve(projectDir, 'output');
const indexPath = path.resolve(wikiDir, 'index.md');
const logPath = path.resolve(wikiDir, 'log.md');
const candidates = collectPathStrings(input.tool_input || {});
const targets = candidates
.map((candidate) => resolveCandidate(candidate, projectDir))
.filter(Boolean);
const wikiTargets = targets.filter((target) => isInside(wikiDir, target));
const outputTargets = targets.filter((target) => isInside(outputDir, target));
if (wikiTargets.some((target) => target !== indexPath && target !== logPath)) {
process.stdout.write(JSON.stringify({
suppressOutput: true,
hookSpecificOutput: {
additionalContext:
'wiki/ 페이지를 수정했다. 이 턴이 끝나기 전에 YAML frontmatter(title, tags, sources, created, updated), [[wikilink]], wiki/index.md 한 줄 항목, wiki/log.md 변경 기록이 모두 반영되었는지 확인하라.'
}
}));
process.exit(0);
}
if (outputTargets.length > 0) {
process.stdout.write(JSON.stringify({
suppressOutput: true,
hookSpecificOutput: {
additionalContext:
'output/ 결과물은 wiki/ 페이지에 근거해야 한다. 가능하면 사용한 핵심 wiki 페이지를 본문 또는 말미에 남기고, raw/ 직접 해석에 의존하지 마라.'
}
}));
}
} catch (error) {
process.stderr.write(String(error));
process.exit(1);
}
+37
View File
@@ -0,0 +1,37 @@
{
"context": {
"fileName": [
"GEMINI.md"
]
},
"hooks": {
"BeforeTool": [
{
"matcher": "write_file|replace|delete_file|run_shell_command",
"hooks": [
{
"name": "protect-raw-sources",
"type": "command",
"command": "node \"$GEMINI_PROJECT_DIR/.gemini/hooks/protect-raw-sources.cjs\"",
"timeout": 5000,
"description": "Prevent the agent from mutating raw/ sources."
}
]
}
],
"AfterTool": [
{
"matcher": "write_file|replace|delete_file",
"hooks": [
{
"name": "wiki-write-reminder",
"type": "command",
"command": "node \"$GEMINI_PROJECT_DIR/.gemini/hooks/wiki-write-reminder.cjs\"",
"timeout": 5000,
"description": "Remind the agent to keep wiki/index.md and wiki/log.md in sync."
}
]
}
]
}
}
+62
View File
@@ -0,0 +1,62 @@
---
name: llm-wiki-operator
description: >
Obsidian 기반 LLM Wiki 운영 전문 스킬. Use this when the user asks to
compile `raw/` sources into `wiki/`, update `wiki/index.md` and `wiki/log.md`,
answer questions from the vault with wiki-first retrieval, link and integrate
concepts into structured synthesis, or draft deliverables into `output/`.
---
# LLM Wiki Operator
이 스킬은 이 볼트를 Gemini CLI 기반 LLM Wiki로 일관되게 운영하기 위한 절차를 제공한다.
## 핵심 원칙
- `raw/`는 불변 원본이다. 읽기만 한다.
- 질의와 생성은 항상 `wiki/index.md`에서 시작한다.
- 새 페이지보다 기존 페이지 업데이트를 우선한다.
- `wiki/` 페이지를 수정했으면 `wiki/index.md``wiki/log.md`를 함께 갱신한다.
- 요약은 사실 중심, 해석과 연결은 개념 또는 연결 페이지에서 수행한다.
- 내부 참조는 `[[wikilink]]`를 사용한다.
## 워크플로우
### 1. 요청 유형 분류
- **컴파일:** `raw/` 원본을 `wiki/`로 구조화
- **탐색:** 볼트 안에서 지식 검색, 비교, 요약
- **연결·융합:** 둘 이상의 개념이나 문서를 관계 중심으로 정리하고 통합
- **결과물 작성:** `wiki/`를 기반으로 `output/` 초안 생성
### 2. 시작점
1. 루트와 관련 하위 폴더의 `GEMINI.md`를 확인한다.
2. `wiki/index.md`에서 기존 페이지와 연결 구조를 먼저 확인한다.
3. 필요할 때만 관련 `raw/` 원본을 읽는다.
### 3. 컴파일 규칙
1. 기존 `wiki/` 페이지가 있으면 먼저 업데이트한다.
2. 새 페이지를 만들 때는 `assets/wiki-page-template.md`를 따른다.
3. `sources`에는 실제 원본 경로를 남긴다.
4. 작업이 끝나면 `assets/log-entry-template.md`를 참고해 `wiki/log.md`를 갱신한다.
5. source type별 세부 기준이 필요하면 `references/source-type-playbooks.md`를 참고한다.
### 4. 탐색 및 연결 규칙
1. 사실과 해석을 분리해서 답한다.
2. 연결·융합 작업에서는 공통점, 차이점, 보완점, 통합 구조를 명시한다.
3. 연결 결과가 재사용 가치가 높으면 `wiki/` 연결 페이지로 승격할지 판단한다.
### 5. 결과물 작성 규칙
1. `output/` 결과물은 가급적 `wiki/` 기반으로 작성한다.
2. 사용한 핵심 `wiki/` 페이지를 본문 또는 말미에 남긴다.
3. 근거가 빈약하면 억지로 확장하지 말고 먼저 보강할 `wiki/` 페이지를 제안한다.
## 리소스
- source type별 컴파일 기준: `references/source-type-playbooks.md`
- wiki 페이지 기본 형식: `assets/wiki-page-template.md`
- log 기록 형식: `assets/log-entry-template.md`
@@ -0,0 +1 @@
- YYYY-MM-DD: `생성|수정|삭제` — 대상 페이지 `[[페이지명]]`, 소스 `[raw/...경로]`, 메모 한 줄
@@ -0,0 +1,21 @@
```yaml
---
title: "페이지 제목"
tags: []
sources: []
created: YYYY-MM-DD
updated: YYYY-MM-DD
---
```
# 페이지 제목
## 요약
## 핵심 개념
## 근거와 인용
## 연결
## 메모
@@ -0,0 +1,50 @@
# Source-Type Playbooks
`raw/` 원본을 `wiki/`로 컴파일할 때 타입별로 우선 확인할 질문들이다.
## Article
- 핵심 주장 1~3개는 무엇인가
- 인상적인 문장이나 직접 인용할 만한 구절은 무엇인가
- 문서 구조화, 개념 연계, 자료 융합, AI 활용과 연결되는 지점은 무엇인가
## YouTube
- 영상의 핵심 주장 또는 방법론은 무엇인가
- 타임스탬프별 핵심 포인트가 있는가
- 내 관심사와 연결되는 지점은 무엇인가
- 실천 가능한 인사이트는 무엇인가
## Podcast
- 게스트/호스트의 핵심 관점은 무엇인가
- 기억할 만한 발언이나 직접 인용은 무엇인가
- 내 관심사와의 연결점은 무엇인가
## Book — 1패스
- 폴더 구조와 각 파일의 첫 문단만 보고 목차 페이지를 만든다
- 저자, 출판연도, 핵심 thesis를 남긴다
- 파일과 챕터 제목을 매핑한 구조표를 만든다
- 파트별 한 줄 요약을 만든다
- 2패스에서 우선 읽을 챕터 순서를 제안한다
## Book — 2패스
- 챕터 핵심 주장은 무엇인가
- 핵심 개념 정의와 인용은 무엇인가
- 문서 구조화, 개념 연계, 자료 융합, AI 활용과 어떤 연결이 있는가
- 기존 wiki 페이지와 어떤 개념 링크를 만들 수 있는가
## Research
- 연구 질문과 방법론은 무엇인가
- 핵심 발견과 데이터는 무엇인가
- 한계점은 무엇인가
- 내 관심사와 연결되는 활용 가능성은 무엇인가
## Batch Compile
- `wiki_status` 또는 메타데이터가 있으면 미컴파일 원본부터 우선한다
- 파일마다 `wiki/index.md``wiki/log.md`를 개별 갱신한다
- 너무 큰 묶음은 나누어 처리한다