From 29bcbb89db219ec2dfe2ef044058652f164a8fb8 Mon Sep 17 00:00:00 2001 From: lizikk Date: Mon, 2 Mar 2026 16:58:43 +0800 Subject: [PATCH] feat: add claude-md-guardian, thinking commands, and security fixes - Add claude-md-guardian agent with SessionStart hook for auto CLAUDE.md maintenance - Add 6 thinking commands: /my-world, /emerge, /challenge, /connect, /today, /close - Add my-world skill for one-shot vault context loading - Fix command injection vulnerability in init-vault.sh (use env vars) - Add error handling and logging to vault-sync.sh - Update write-article skill with complete frontmatter fields - Upgrade CLAUDE.md to v1.3.0 with cycle time tracking and exit criteria Co-Authored-By: Claude Opus 4.6 --- .claude/agents/claude-md-guardian.md | 81 ++++++++++++++ .claude/commands/challenge.md | 20 ++++ .claude/commands/close.md | 71 ++++++++++++ .claude/commands/connect.md | 21 ++++ .claude/commands/emerge.md | 23 ++++ .claude/commands/my-world.md | 30 +++++ .claude/commands/today.md | 77 +++++++++++++ .claude/hooks/guardian-check.sh | 109 ++++++++++++++++++ .claude/settings.json | 14 +++ .claude/skills/my-world/SKILL.md | 104 +++++++++++++++++ .claude/skills/write-article/SKILL.md | 41 ++++--- .gitignore | 1 + CLAUDE.md | 154 ++++++++++++++++++++++---- my-commands.md | 53 +++++++++ scripts/init-vault.sh | 7 +- scripts/vault-sync.sh | 44 +++++++- 16 files changed, 811 insertions(+), 39 deletions(-) create mode 100644 .claude/agents/claude-md-guardian.md create mode 100644 .claude/commands/challenge.md create mode 100644 .claude/commands/close.md create mode 100644 .claude/commands/connect.md create mode 100644 .claude/commands/emerge.md create mode 100644 .claude/commands/my-world.md create mode 100644 .claude/commands/today.md create mode 100755 .claude/hooks/guardian-check.sh create mode 100644 .claude/settings.json create mode 100644 .claude/skills/my-world/SKILL.md create mode 100644 my-commands.md diff --git a/.claude/agents/claude-md-guardian.md b/.claude/agents/claude-md-guardian.md new file mode 100644 index 0000000..edc4129 --- /dev/null +++ b/.claude/agents/claude-md-guardian.md @@ -0,0 +1,81 @@ +# CLAUDE.md Guardian Agent + +后台 agent,监控项目变化并自动维护 CLAUDE.md 文件。 + +## 触发条件 + +- Session 启动时(通过 SessionStart hook) +- 重大里程碑后(feature 完成、重构、新依赖、架构变化) + +## 监控范围 + +| 变化类型 | 检测方式 | 更新内容 | +|----------|----------|----------| +| 目录结构变化 | 对比 `.claude/guardian/structure.json` | 项目结构图 | +| 依赖变化 | package.json, requirements.txt 等 | Tech Stack 部分 | +| 新增 skill | `.claude/skills/` 目录变化 | Skills 清单 | +| 新增命令 | `.claude/commands/` 目录变化 | 命令列表 | +| 架构变化 | 手动标记 | 架构说明 | + +## 工作流程 + +``` +SessionStart Hook + ↓ +检查 .claude/guardian/snapshot.json 是否存在 + ↓ + ├─ 不存在 → 首次运行,创建快照,不更新 + └─ 存在 → 对比当前状态与快照 + ↓ + ├─ 无变化 → 静默退出 + └─ 有变化 → 生成更新建议,询问用户 +``` + +## 快照格式 + +```json +{ + "last_check": "2026-03-02T10:00:00+08:00", + "structure_hash": "abc123", + "skills": ["write-article", "my-world"], + "commands": ["/today", "/close", "/emerge", "/challenge", "/connect", "/my-world"], + "dependencies": { + "bash": ["obsidian-cli"], + "obsidian_plugins": ["dataview", "templater-obsidian", "note-to-mp", "obsidian-kanban"] + } +} +``` + +## 更新原则 + +1. **保守更新**:只添加缺失信息,不删除现有内容 +2. **用户确认**:重大变化前询问用户 +3. **版本追踪**:每次更新记录版本号和日期 +4. **保持简洁**:CLAUDE.md 控制在 400 行以内 + +## 配置文件 + +项目级 settings.json 中配置 hook: + +```json +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "bash .claude/hooks/guardian-check.sh" + } + ] + } + ] + } +} +``` + +## 版本 + +- **版本**: 1.0.0 +- **创建日期**: 2026-03-02 +- **适用项目**: content-forge diff --git a/.claude/commands/challenge.md b/.claude/commands/challenge.md new file mode 100644 index 0000000..e98f84f --- /dev/null +++ b/.claude/commands/challenge.md @@ -0,0 +1,20 @@ +Pressure-test beliefs using the vault's own history. + +1. Read all vault notes: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian files + ``` + +2. For each note, read its content: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian read path="" + ``` + +3. Find contradictions, tensions, and counter-evidence WITHIN the notes. Where does one note say something that conflicts with another? Where are the blind spots? + +4. Output format: + - **Tension Found**: [description] + - **Evidence from notes**: [specific quotes with note names] + - **The challenge**: [question that forces reconciliation] diff --git a/.claude/commands/close.md b/.claude/commands/close.md new file mode 100644 index 0000000..2e7c898 --- /dev/null +++ b/.claude/commands/close.md @@ -0,0 +1,71 @@ +# 晚间仪式:收尾与复盘 + +每天结束前执行,确保工作有始有终。 + +## Step 1: 检查今日完成 + +```bash +cd /home/kang/apps/content-forge/content-forge +# 查看今天修改的笔记 +find . -name "*.md" -type f ! -path "./.obsidian/*" ! -path "./.git/*" -mtime 0 +``` + +## Step 2: 更新看板状态 + +将完成的工作移动到正确阶段: + +```bash +# 如果有草稿完成,移到审核 +obsidian move path="02-drafts/xxx.md" to="03-review" +obsidian property:set path="03-review/xxx.md" name="status" value="review" +``` + +## Step 3: 同步到 Git + +```bash +cd /home/kang/apps/content-forge/content-forge +git add -A +git commit -m "session: $(date +%Y-%m-%d) content updates" +git push +``` + +## Step 4: 更新 progress.md + +在项目根目录的 `progress.md` 中添加今日记录: + +```markdown +## Session: YYYY-MM-DD + +### 完成项 +- [ ] 任务1 +- [ ] 任务2 + +### 文件变更 +- 新建: path/to/file.md +- 修改: path/to/another.md + +### 明日 TODO +1. 待办1 +2. 待办2 +``` + +## Step 5: 输出日报 + +``` +🌙 晚间复盘 - YYYY-MM-DD + +✅ 今日完成 + - X 篇文章推进到 Y 阶段 + - Z 个文件变更 + +📈 管道变化 + - 进: X 篇新素材 + - 出: Y 篇发布 + - 净变化: +N/-M + +📋 明日待办 + 1. [首要任务] + 2. [次要任务] + +💾 Git 状态: 已同步 ✅ +``` diff --git a/.claude/commands/connect.md b/.claude/commands/connect.md new file mode 100644 index 0000000..73b9e03 --- /dev/null +++ b/.claude/commands/connect.md @@ -0,0 +1,21 @@ +Bridge two disparate topics using the vault's link graph. + +1. Read all vault notes: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian files + ``` + +2. For each note, read its content: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian read path="" + ``` + +3. Pick two notes that seem unrelated and find the hidden bridge between them. What do they share that isn't obvious? What would combining their insights produce? + +4. Output format: + - **Domain A**: [note/topic] + - **Domain B**: [note/topic] + - **The bridge**: [unexpected connection] + - **The insight**: [what this connection means] diff --git a/.claude/commands/emerge.md b/.claude/commands/emerge.md new file mode 100644 index 0000000..a203c9f --- /dev/null +++ b/.claude/commands/emerge.md @@ -0,0 +1,23 @@ +Surface hidden patterns — ideas the vault implies but were never explicitly stated. + +1. Read all vault notes to understand the full knowledge landscape: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian files + obsidian tags counts sort=count + obsidian orphans + obsidian deadends + ``` + +2. For each note with content, read it: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian read path="" + ``` + +3. Analyze: look for conclusions from scattered premises, unnamed patterns across notes, themes circled around but never articulated, unarticulated directions thinking is heading. + +4. Output format for each pattern found: + - **Emergent Pattern**: [description] + - **Evidence trail**: [which notes contribute to this pattern] + - **The unstated idea**: [what the notes imply but was never written down] diff --git a/.claude/commands/my-world.md b/.claude/commands/my-world.md new file mode 100644 index 0000000..572d3be --- /dev/null +++ b/.claude/commands/my-world.md @@ -0,0 +1,30 @@ +Load my personal context from the content-forge Obsidian vault. + +1. cd to vault directory and scan pipeline state: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian vault + obsidian files folder="00-inbox" + obsidian files folder="01-topics" + obsidian files folder="02-drafts" + obsidian files folder="03-review" + obsidian files folder="04-published" + ``` + +2. Map the knowledge graph: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian tags counts sort=count + obsidian orphans + obsidian deadends + obsidian unresolved + obsidian recents + ``` + +3. For each active item in inbox/topics/drafts/review, read its frontmatter: + ```bash + cd /home/kang/apps/content-forge/content-forge + obsidian properties path="" + ``` + +4. Summarize: what's in the pipeline, what's connected, what needs attention? Reference specific notes. diff --git a/.claude/commands/today.md b/.claude/commands/today.md new file mode 100644 index 0000000..1707fef --- /dev/null +++ b/.claude/commands/today.md @@ -0,0 +1,77 @@ +# 晨间仪式:今日工作扫描 + +执行以下步骤,为今天的工作做准备。 + +## Step 1: 加载 Vault 概况 + +```bash +cd /home/kang/apps/content-forge/content-forge +obsidian files total +``` + +## Step 2: 扫描看板状态 + +检查各阶段 WIP 状态: + +```bash +# 灵感/素材 +obsidian search query="status:inbox" 2>/dev/null | wc -l + +# 选题 +obsidian search query="status:topic" 2>/dev/null | wc -l + +# 写作中 +obsidian search query="status:draft" 2>/dev/null | wc -l + +# 审核 +obsidian search query="status:review" 2>/dev/null | wc -l + +# 发布中 +obsidian search query="status:publishing" 2>/dev/null | wc -l +``` + +## Step 3: 识别超期项目 + +检查 `updated_at` 超过 7 天的笔记: + +```bash +find . -name "*.md" -type f ! -path "./.obsidian/*" ! -path "./.git/*" -mtime +7 | head -10 +``` + +## Step 4: 输出今日建议 + +根据扫描结果,输出: + +1. **管道状态**:各阶段数量 + WIP 限制对比 +2. **超期提醒**:超过 7 天未更新的笔记列表 +3. **今日焦点**:基于 WIP 和超期情况,建议今天优先处理的 1-3 项 + +## WIP 限制参考 + +| 阶段 | WIP 限制 | +|------|----------| +| 选题 | 3 | +| 写作 | 3 | +| 配图/格式化 | 2 | +| 审核 | 2 | +| 发布 | 2 | + +## 输出格式 + +``` +📅 今日扫描 - YYYY-MM-DD + +📊 管道状态 + 灵感/素材: X (无限制) + 选题: X/3 [⚠️ 超限] 或 [✅ 正常] + 写作: X/3 + 审核: X/2 + 发布: X/2 + +⚠️ 超期提醒 (7天+) + - path/to/note.md (最后更新: X天前) + +🎯 今日建议 + 1. [具体建议] + 2. [具体建议] +``` diff --git a/.claude/hooks/guardian-check.sh b/.claude/hooks/guardian-check.sh new file mode 100755 index 0000000..5bf0286 --- /dev/null +++ b/.claude/hooks/guardian-check.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# CLAUDE.md Guardian Check Script +# 在 SessionStart 时运行,检查项目变化 + +set -euo pipefail + +# 获取脚本所在目录的父目录(项目根目录) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +GUARDIAN_DIR="$PROJECT_ROOT/.claude/guardian" +SNAPSHOT_FILE="$GUARDIAN_DIR/snapshot.json" +LOG_FILE="$GUARDIAN_DIR/guardian.log" + +# 确保目录存在 +mkdir -p "$GUARDIAN_DIR" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE" +} + +# 计算目录结构哈希(使用绝对路径) +get_structure_hash() { + cd "$PROJECT_ROOT" + find . -type f \( -name "*.md" -o -name "*.sh" -o -name "*.json" \) \ + ! -path "./content-forge/*" \ + ! -path "./.git/*" \ + ! -path "./node_modules/*" \ + ! -path "./.claude/guardian/*" \ + -exec md5sum {} \; 2>/dev/null | sort | md5sum | cut -d' ' -f1 +} + +# 获取 skills 列表 +get_skills() { + ls -1 "$PROJECT_ROOT/.claude/skills/" 2>/dev/null | sort | tr '\n' ',' | sed 's/,$//' +} + +# 获取 commands 列表 +get_commands() { + ls -1 "$PROJECT_ROOT/.claude/commands/" 2>/dev/null | sed 's/.md$//' | sort | tr '\n' ',' | sed 's/,$//' +} + +# 检查是否首次运行 +if [[ ! -f "$SNAPSHOT_FILE" ]]; then + log "首次运行,创建初始快照" + + HASH=$(get_structure_hash) + SKILLS=$(get_skills) + COMMANDS=$(get_commands) + + # 创建初始快照 + cat > "$SNAPSHOT_FILE" << EOF +{ + "last_check": "$(date -Iseconds)", + "structure_hash": "$HASH", + "skills": "$SKILLS", + "commands": "$COMMANDS", + "version": "1.0.0" +} +EOF + + echo "GUARDIAN: 初始化完成,快照已创建" + exit 0 +fi + +# 对比变化 +CURRENT_HASH=$(get_structure_hash) +STORED_HASH=$(jq -r '.structure_hash' "$SNAPSHOT_FILE") +CURRENT_SKILLS=$(get_skills) +STORED_SKILLS=$(jq -r '.skills' "$SNAPSHOT_FILE") +CURRENT_COMMANDS=$(get_commands) +STORED_COMMANDS=$(jq -r '.commands' "$SNAPSHOT_FILE") + +CHANGES="" + +if [[ "$CURRENT_HASH" != "$STORED_HASH" ]]; then + CHANGES="${CHANGES}结构变化 detected\n" +fi + +if [[ "$CURRENT_SKILLS" != "$STORED_SKILLS" ]]; then + CHANGES="${CHANGES}Skills 变化: $STORED_SKILLS -> $CURRENT_SKILLS\n" +fi + +if [[ "$CURRENT_COMMANDS" != "$STORED_COMMANDS" ]]; then + CHANGES="${CHANGES}Commands 变化: $STORED_COMMANDS -> $CURRENT_COMMANDS\n" +fi + +if [[ -n "$CHANGES" ]]; then + log "检测到变化: $CHANGES" + + # 更新快照 + cat > "$SNAPSHOT_FILE" << EOF +{ + "last_check": "$(date -Iseconds)", + "structure_hash": "$CURRENT_HASH", + "skills": "$CURRENT_SKILLS", + "commands": "$CURRENT_COMMANDS", + "version": "1.0.0" +} +EOF + + echo "GUARDIAN: 检测到项目变化,建议更新 CLAUDE.md" + echo "变化内容:" + echo -e "$CHANGES" + echo "" + echo "运行 /claudeforge-skill 来更新 CLAUDE.md" +else + log "无变化" + # 静默退出,不输出任何内容 +fi diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..047e1b4 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,14 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "bash .claude/hooks/guardian-check.sh" + } + ] + } + ] + } +} diff --git a/.claude/skills/my-world/SKILL.md b/.claude/skills/my-world/SKILL.md new file mode 100644 index 0000000..c9f58bd --- /dev/null +++ b/.claude/skills/my-world/SKILL.md @@ -0,0 +1,104 @@ +--- +name: my-world +description: "One-shot context loader for the content-forge Obsidian vault. This skill should be used at the start of any Claude Code session to load the user's projects, topics, drafts, knowledge graph, and current priorities into context. Replaces repeated re-explanation of who the user is and what they're working on." +--- + +# /my-world — Load My World + +Load the entire content-forge vault context into the current session in one shot. + +## When to Use + +Run at the start of any session that involves content creation, vault operations, or when needing awareness of current projects and priorities. + +## What It Does + +1. Read vault stats (file count, tags, recent activity) +2. Scan all active content (topics, drafts, in-review) +3. Map the knowledge graph (backlinks, wikilinks, orphans) +4. Surface current priorities and blockers +5. Present a concise world-view summary + +## Important: Vault Directory + +All `obsidian` CLI commands must run from within the vault directory to auto-detect the vault: + +```bash +cd /home/kang/apps/content-forge/content-forge +``` + +Run all subsequent commands after this `cd`. The `vault=` parameter is unreliable; directory-based detection is the correct approach. + +## Execution Steps + +### Step 1: Vault Health + +```bash +cd /home/kang/apps/content-forge/content-forge +obsidian vault +obsidian files total +obsidian tags counts sort=count +``` + +### Step 2: Active Content Pipeline + +Scan each pipeline stage for active items: + +```bash +obsidian files folder="00-inbox" +obsidian files folder="01-topics" +obsidian files folder="02-drafts" +obsidian files folder="03-review" +obsidian files folder="04-published" +``` + +For each file found, read its frontmatter to extract `title`, `status`, `content_type`, `channels`: + +```bash +obsidian properties path="" +``` + +### Step 3: Knowledge Graph + +```bash +obsidian orphans +obsidian deadends +obsidian unresolved +``` + +### Step 4: Recent Activity + +```bash +obsidian recents +``` + +### Step 5: Summarize + +Present a concise summary in this format: + +``` +=== My World === + +Vault: content-forge (X files, Y tags) + +Pipeline: + inbox: N items + topics: N items (list titles) + drafts: N items (list titles + status) + review: N items (list titles) + published: N items total + +Knowledge Graph: + Orphans: N (notes with no incoming links) + Dead ends: N (notes with no outgoing links) + Unresolved: N (broken [[wikilinks]]) + +Recent: (last 5 files touched) + +Priorities: + - (inferred from draft/review status and recency) +``` + +## Output + +A structured world-view that gives full context for the session. No files are written — this is read-only. diff --git a/.claude/skills/write-article/SKILL.md b/.claude/skills/write-article/SKILL.md index 8cf4d2f..de44cc3 100644 --- a/.claude/skills/write-article/SKILL.md +++ b/.claude/skills/write-article/SKILL.md @@ -74,27 +74,42 @@ obsidian create path="02-drafts/-.md" content="/content-forge/`(通过 `scripts/init-vault.sh` 初始化,可用 `VAULT_PATH` 环境变量覆盖)。 -Obsidian CLI 用**目录名**作为 vault 名称,因此目录名必须是 `content-forge`(非 `vault`),命令中使用 `vault="content-forge"` 指定。 +**关键操作约束**:Obsidian CLI 的 `vault=` 参数存在已知 bug(静默失效),**必须先 `cd` 到 vault 目录内再执行 CLI 命令**,CLI 会自动检测当前目录所属的 vault: + +```bash +cd /home/kang/apps/content-forge/content-forge +obsidian vault # 正确识别 content-forge vault +obsidian orphans # 在 vault 目录内执行才有效 +``` ```text / @@ -150,6 +173,7 @@ Obsidian CLI 用**目录名**作为 vault 名称,因此目录名必须是 `con ├── 03-review/ # 待审核稿(人工或规则审校) ├── 04-published/ # 已发布内容归档 ├── 05-assets/ # 配图、封面、信息图等资产 +├── 06-archived/ # 废弃/放弃的内容 └── templates/ # 模板(article / tech-blog / social-post) ``` @@ -167,7 +191,7 @@ Obsidian CLI 用**目录名**作为 vault 名称,因此目录名必须是 `con id: "2026-03-01-example-slug" title: "示例标题" slug: "example-slug" -status: "topic" # topic | draft | review | published | archived +status: "topic" # inbox | topic | draft | formatting | review | publishing | published | revision | blocked | archived content_type: "article" # article | thread | x-post | wechat-post | infographic channels: ["wechat", "x"] language: "zh-CN" @@ -179,6 +203,14 @@ owner: "content-forge" created_at: "2026-03-01T00:00:00+08:00" updated_at: "2026-03-01T00:00:00+08:00" published_at: null +# 周期时间追踪(可选) +cycle_days: null # 从创建到发布的总天数 +stage_days: # 各阶段停留天数 + inbox: 0 + topic: 0 + draft: 0 + review: 0 + total: 0 --- ``` @@ -186,6 +218,27 @@ published_at: null - `id` 与 `slug` 一一对应,禁止同 slug 多 id。 - `status` 只能按 `topic -> draft -> review -> published` 前进。 - `assets` 只存 Vault 相对路径(禁止外部临时路径)。 +- `cycle_days` 在发布时自动计算:`published_at - created_at`。 +- `stage_days` 由 `/today` 或 `/close` 命令更新。 + +### 4.3 各阶段退出标准 + +每个阶段必须满足以下条件才能前进到下一阶段: + +| 阶段 | 必填字段 | 退出条件 | +|------|----------|----------| +| **inbox** | `title` | 有标题即可进入 topic | +| **topic** | `slug`, `source_urls` 或明确观点 | 有 slug + 明确写作角度 | +| **draft** | 完整正文 | 正文结构完整,有开头/主体/结尾 | +| **formatting** | `assets`, `cover_image` | 配图已生成,格式化完成 | +| **review** | 审核通过 | 人工审核完成,无事实错误 | +| **publishing** | `channels` | 渠道已确定,准备发布 | +| **published** | `published_at`, `published_url` | 已发布,记录发布时间和链接 | + +**特殊情况**: +- **revision**:从 review 回退,修复后返回 draft 或 review +- **blocked**:等待外部输入,解除后返回原阶段 +- **archived**:废弃内容,不再前进 --- @@ -245,11 +298,20 @@ obsidian search query="2026-03-01-demo" ### 6.1 使用规范(强制) -1. 所有写入必须使用 Vault 相对路径,禁止绝对路径。 -2. 写入前先创建文件,再写 frontmatter 属性,避免属性写入到不存在文件。 -3. 每次阶段切换都要更新 `status` 与 `updated_at`。 -4. 资产路径统一写入 `assets` 数组,不在正文硬编码临时 URL。 -5. 发布前必须能通过 `obsidian search query=""` 找到 topic/draft/review/published 至少一个节点。 +1. **先 cd 再执行**:所有 CLI 命令必须在 `cd /home/kang/apps/content-forge/content-forge` 之后执行。 +2. 所有写入必须使用 Vault 相对路径,禁止绝对路径。 +3. 写入前先创建文件,再写 frontmatter 属性,避免属性写入到不存在文件。 +4. 每次阶段切换都要更新 `status` 与 `updated_at`。 +5. 资产路径统一写入 `assets` 数组,不在正文硬编码临时 URL。 +6. 发布前必须能通过 `obsidian search query=""` 找到 topic/draft/review/published 至少一个节点。 + +### 6.1.1 NEVER 规则(踩坑编纂) + +- **NEVER** 使用 `vault=` 参数 — 已知 bug,静默失效,总是 cd 到 vault 目录。 +- **NEVER** 用 `find`/`ls`/`cat` 替代 CLI — `obsidian search`/`obsidian read` 效率高 70000 倍。 +- **NEVER** 让 Agent 直接写入 Vault 笔记内容 — 黄金法则:Agents Read, Humans Write。通过 `obsidian create`/`obsidian property:set` 的结构化命令操作 frontmatter 和流水线状态是允许的。 +- **NEVER** 在 Vault 中存放非内容文件(脚本、zip、临时文件) — Vault 只放 markdown 笔记和资产。 +- **NEVER** 跳过 frontmatter — 每个 01-04 目录的文件必须有完整 frontmatter。 ### 6.2 故障排查(按优先级) @@ -259,7 +321,7 @@ obsidian search query="2026-03-01-demo" | `obsidian vault` 无输出或报错 | Vault 未注册 | `obsidian vault` | 运行 `scripts/init-vault.sh` 或手动注册 | | `obsidian read` 失败 | 路径拼写或文件不存在 | `obsidian search query=""` | 先 `obsidian create` 再 `obsidian read` | | `obsidian property:set` 不生效 | frontmatter 块损坏或 name 拼写错误 | `obsidian read path=""` | 修复 frontmatter 结构后重试 | -| `obsidian vault` 段错误(segfault) | CLI 版本与系统不兼容或内存问题 | `obsidian --version` | 重新安装 CLI:`npm i -g obsidian-cli` 或 `pip install obsidian-cli`;如持续崩溃,用文件系统直接读写 Vault 作为降级方案 | +| `obsidian vault` 段错误(segfault) | `~/.local/bin/obsidian` 是 symlink 而非 wrapper,缺少 `DISPLAY` 注入 | `file ~/.local/bin/obsidian` 应显示 shell script | 重新部署 wrapper:见 `obsidian-setup` skill Step 2;wrapper 会自动注入 `DISPLAY=:42` | | 配图 skill 调用失败 | `basic-image-gen` API key 缺失 | 检查环境变量 | 补齐 `OPENAI_API_KEY/GOOGLE_API_KEY/DASHSCOPE_API_KEY` | | 微信/X 发布失败 | 登录态失效或接口凭证过期 | 检查浏览器登录态/API 凭证 | 重新登录或刷新凭证 | @@ -275,18 +337,72 @@ obsidian search query="templates" --- -## 7) 已知问题与限制 +## 7) Git 仓库与数据同步 + +项目代码和 Vault 数据使用**独立仓库**管理: + +| 仓库 | 地址 | 内容 | 同步方式 | +|---|---|---|---| +| `content-forge` | `http://gitea.towards-agi.cn/zhukang/content-forge.git` | CLAUDE.md、skills、scripts | 手动 commit + push | +| `content-forge-vault` | `http://gitea.towards-agi.cn/zhukang/content-forge-vault.git` | Vault 数据(选题、草稿、资产) | cron 每天凌晨 2 点自动 | + +### 7.1 认证方式 + +- 使用 `git credential store`,token 统一存放在 `~/.git-credentials`(权限 600)。 +- Token 更换时只需修改 `~/.git-credentials` 一个文件。 +- Remote URL 中不含 token。 + +### 7.2 自动同步 + +`scripts/vault-sync.sh` 由 cron 每天执行,逻辑:有变更时 `git add -A && commit && push`,无变更时跳过。 + +```bash +# 查看 cron 配置 +crontab -l | grep vault-sync + +# 手动触发同步 +bash scripts/vault-sync.sh + +# 查看同步日志 +tail -20 /tmp/vault-sync.log +``` + +### 7.3 其他设备 clone + +```bash +git clone http://gitea.towards-agi.cn/zhukang/content-forge-vault.git +# 首次 push 时 credential store 会提示输入用户名和 token +``` + +--- + +## 8) 已知问题与限制(含 NEVER 规则踩坑记录) | 问题 | 影响 | 状态 | 降级方案 | |---|---|---|---| -| `obsidian vault` 执行时 segfault(核心已转储) | 无法通过 CLI 操作 Vault | 待修复 | 用文件系统直接读写 `content-forge/` 目录 | -| 所有内容目录为空 | 流水线尚未有端到端产出 | 正常(项目刚初始化) | 按 Quick Start 创建首个选题 | -| `obsidian` 插件 CLI 与 Obsidian 桌面客户端为独立进程 | 桌面端修改不会自动触发 CLI 事件 | 设计如此 | 以文件系统为准,CLI 和桌面端都会读取同一目录 | +| ~~`obsidian vault` segfault~~ | ~~无法通过 CLI 操作~~ | **已修复**(wrapper 脚本注入 DISPLAY) | — | +| `obsidian` CLI 与桌面客户端为独立进程 | 桌面端修改不会自动触发 CLI 事件 | 设计如此 | 以文件系统为准,CLI 和桌面端都会读取同一目录 | +| `vault=` 参数静默失效 | CLI 命令作用到错误 vault | **已确认** | 必须 `cd` 到 vault 目录内执行 CLI 命令 | + +--- + +## 9) 自定义命令(`.claude/commands/`) + +基于 Vin Obsidian Workflows 理念构建的思维工具,详见 `my-commands.md`。 + +| 命令 | 用途 | 使用时机 | +|------|------|----------| +| `/my-world` | 一键加载 vault 全量上下文 | 每次 session 开始 | +| `/emerge` | 从笔记中发现隐藏模式 | 周回顾、新增多篇笔记后 | +| `/challenge` | 用 vault 历史压力测试信念 | 发布前、重大决策前 | +| `/connect` | 跨领域桥接两个不相关主题 | 寻找新内容角度 | + +待建:`/today`(晨间仪式)、`/close`(晚间仪式)。 --- ## 版本 -- **CLAUDE.md 版本**:1.1.0 +- **CLAUDE.md 版本**:1.3.0 - **最后更新**:2026-03-02 -- **项目阶段**:初始化完成,待首次端到端验证 +- **项目阶段**:环境就绪,CLI 验证通过,双仓库同步已配置,思维工具命令已部署 diff --git a/my-commands.md b/my-commands.md new file mode 100644 index 0000000..7e357c6 --- /dev/null +++ b/my-commands.md @@ -0,0 +1,53 @@ +# My Commands + +Custom slash commands for content-forge, built from the Vin Obsidian Workflows lesson. + +All command files live in `.claude/commands/`. + +## /my-world + +**File:** `.claude/commands/my-world.md` + +Loads full vault context in one shot. Scans all pipeline folders (00-inbox through 04-published), maps the knowledge graph (tags, orphans, dead ends, unresolved links), and summarizes priorities and connections. + +**When to use:** Start of every session. Replaces re-explaining your project context. + +## /emerge + +**File:** `.claude/commands/emerge.md` + +Surfaces hidden patterns — ideas the vault implies but you never explicitly stated. Reads all notes, runs graph analysis (orphans, tags, dead ends), and finds emergent conclusions from scattered premises. + +**When to use:** Weekly review, or whenever you've added several new notes and want to see what they add up to. + +## /challenge + +**File:** `.claude/commands/challenge.md` + +Pressure-tests your beliefs using the vault's own history. Finds contradictions, tensions, and counter-evidence within your notes. Forces reconciliation of conflicting ideas. + +**When to use:** Before publishing, before major decisions, or when you suspect confirmation bias. + +## /connect + +**File:** `.claude/commands/connect.md` + +Bridges two disparate topics using the vault's link graph. Picks seemingly unrelated notes and finds the hidden bridge between them. + +**When to use:** When looking for cross-domain insights, novel content angles, or creative connections. + +## /today + +**File:** `.claude/commands/today.md` + +晨间仪式:扫描看板状态 + 识别超期项目 + 输出今日建议。检查各阶段 WIP,找出 7 天以上未更新的笔记,给出优先处理建议。 + +**When to use:** 每天开始工作时,快速了解当前状态和优先级。 + +## /close + +**File:** `.claude/commands/close.md` + +晚间仪式:检查今日完成 + 更新看板状态 + Git 同步 + 更新 progress.md。确保工作有始有终。 + +**When to use:** 每天结束工作前,收尾和复盘。 diff --git a/scripts/init-vault.sh b/scripts/init-vault.sh index aaa2219..5507f82 100755 --- a/scripts/init-vault.sh +++ b/scripts/init-vault.sh @@ -35,12 +35,15 @@ register_vault() { # editing the config file (same approach as obsidian-setup/scripts/register_vault.py). local config_path="$HOME/.config/obsidian/obsidian.json" + # Use environment variables to avoid command injection + export VAULT_PATH CONFIG_PATH="$config_path" + python3 -c " import json, os, hashlib, time -vault_path = '${VAULT_PATH}' +vault_path = os.environ['VAULT_PATH'] vault_id = hashlib.md5(vault_path.encode()).hexdigest()[:16] -config_path = os.path.expanduser('${config_path}') +config_path = os.environ['CONFIG_PATH'] os.makedirs(os.path.join(vault_path, '.obsidian'), exist_ok=True) diff --git a/scripts/vault-sync.sh b/scripts/vault-sync.sh index e5ed76d..72ff00c 100755 --- a/scripts/vault-sync.sh +++ b/scripts/vault-sync.sh @@ -1,15 +1,49 @@ #!/usr/bin/env bash -set -euo pipefail +set -Eeuo pipefail VAULT_PATH="${VAULT_PATH:-/home/kang/apps/content-forge/content-forge}" +LOG_FILE="${LOG_FILE:-/tmp/vault-sync.log}" -cd "$VAULT_PATH" +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE" +} + +# Verify vault directory exists +if [[ ! -d "$VAULT_PATH" ]]; then + log "ERROR: Vault directory does not exist: $VAULT_PATH" + exit 1 +fi + +cd "$VAULT_PATH" || { + log "ERROR: Failed to cd into vault: $VAULT_PATH" + exit 1 +} # Skip if no changes if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then + log "No changes to sync" exit 0 fi -git add -A -git commit -m "vault: auto-sync $(date '+%Y-%m-%d %H:%M')" -git push origin main +# Get current branch dynamically +BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main") + +# Stage all changes +if ! git add -A; then + log "ERROR: git add failed" + exit 1 +fi + +# Commit with timestamp +if ! git commit -m "vault: auto-sync $(date '+%Y-%m-%d %H:%M')"; then + log "ERROR: git commit failed" + exit 1 +fi + +# Push with error handling +if ! git push origin "$BRANCH"; then + log "ERROR: git push failed (branch: $BRANCH)" + exit 1 +fi + +log "Sync successful: pushed to $BRANCH"