Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.git
.github
.gitignore
*.md
*.sh
scripts/
test/
node_modules/
.npm
.env
.env.local
.DS_Store
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm test
2 changes: 1 addition & 1 deletion .github/workflows/shell-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
health.sh \
verify.sh \
scripts/test-all.sh \
scripts/pre-commit || true
scripts/pre-commit

syntax-check:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ clausidian/
├── CHANGELOG.md
├── ARCHITECTURE.md (this file)
├── CONTRIBUTING.md
└── .github/workflows/ # CI/CD pipeline (coming in v2.5.0)
└── .github/workflows/ # CI/CD pipeline (test, lint, publish)
```

## Data Flow
Expand Down
46 changes: 44 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,46 @@

All notable changes to the clausidian project are documented in this file. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [2.6.0] — 2026-03-30

### Added
- **embed-search command** — Semantic search using embeddings (Ollama or OpenAI)
- **smart-search command** — BM25 ranked search (now exposed as MCP tool)
- **mcpSchema for import command** — Can now be triggered via MCP
- **mcpSchema for review command** — Can now be triggered via MCP
- **6 new scaffold slash commands** — update, sync, health, stats, tag-list, batch

### Changed
- **Performance fix**: Cache invalidation now conditional on write operations only (read-only tools skip cache clear)
- **SearchCache** wired into search path for 5-min TTL result caching
- Improved MCP configuration documentation (mcp-config-example.README.md)

### Infrastructure
- **Dockerfile** — Containerized deployment (node:18-alpine, MCP server ready)
- **CI/CD improvements** — npm caching, enforced ShellCheck failures
- Cleaned up mcp-config-example.json (removed invalid JavaScript comments)

## [2.5.1] — 2026-03-30

### Added
- **lib/common.sh** — Shared shell library (212 lines), eliminates 85 lines of duplication
- **Shell script refactoring** — All shells now use common functions
- **.editorconfig** — Unified code style across all file types
- **.prettierrc.json** — JavaScript/TypeScript formatting with 100-column width
- **.github/workflows/shell-lint.yml** — Automated ShellCheck + syntax validation
- **SCRIPT_STYLE.md** — 12-part shell scripting best practices guide
- **GitHub release v2.5.1** — npm publication at v2.5.1

### Changed
- install.sh, setup.sh, health.sh, verify.sh refactored to use lib/common.sh
- All scripts now enforce `set -o pipefail` for better error handling
- Improved logging with color-coded output across all shell scripts

### Tested
- 18-item automated test suite (100% pass rate)
- Shell syntax validation with bash -n
- GitHub Actions CI/CD matrix (3 OS × 3 Node versions = 9 jobs)
=======
## [3.0.0] — 2026-03-30

### Added
Expand Down Expand Up @@ -29,6 +69,7 @@ All notable changes to the clausidian project are documented in this file. Forma
- `vault-validator` replaces inline vault checks
- `args-parser` normalizes kebab-case flags to camelCase
- All existing APIs remain compatible
>>>>>>> origin/main

## [2.5.0] — 2026-03-30

Expand All @@ -55,14 +96,15 @@ All notable changes to the clausidian project are documented in this file. Forma

## [Unreleased]

### Planned for v2.6.0+
### Planned for v2.7.0+
- Smart template generation from vault analysis
- Search result caching for improved performance
- Incremental index updates support
- Large vault support (>10,000 files)
- Batch operation parallelization
- Performance benchmarking suite
- Pre-commit hook configuration
- Extended MCP resources (per-note URIs, live stats)
- Advanced embedding models (text-embedding-3-large, custom models)

## [2.0.0] — 2026-03-30

Expand Down
31 changes: 31 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Clausidian Docker image — MCP server for Obsidian vault management
FROM node:18-alpine

WORKDIR /app

# Copy package files
COPY package.json package-lock.json ./

# Install dependencies
RUN npm ci

# Copy application code
COPY bin/ ./bin/
COPY src/ ./src/
COPY scaffold/ ./scaffold/
COPY skill/ ./skill/

# Create vault mount point
RUN mkdir -p /vault

# Set vault as default working directory for MCP operations
ENV VAULT_ROOT=/vault

# Default command: start MCP server
CMD ["node", "bin/cli.mjs", "serve", "--vault", "/vault"]

# Volume for vault data
VOLUME ["/vault"]

# Expose stdio for MCP protocol
EXPOSE 3000
2 changes: 1 addition & 1 deletion bin/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* clausidian CLI — AI agent toolkit for Obsidian vaults
* v1.1.0 — registry-based dispatch
* v2.6.0 — registry-based dispatch, MCP server, 55+ commands
*/

import { getCommand, getCommandNames } from '../src/registry.mjs';
Expand Down
51 changes: 51 additions & 0 deletions mcp-config-example.README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# MCP Configuration Setup

> `mcp-config-example.json` is a template for setting up clausidian as an MCP server in Claude Code.

## Setup Instructions

### 1. Replace the vault path

Edit `mcp-config-example.json` and replace `REPLACE_WITH_YOUR_VAULT_PATH` with your actual vault directory:

- **macOS/Linux**: `/Users/username/my-vault` or `$HOME/my-vault` (use full path, not `~`)
- **Windows**: `C:\\Users\\username\\my-vault`

### 2. Common vault locations

- **iCloud Drive**: `/Users/username/Library/Mobile Documents/com~apple~CloudDocs/my-vault`
- **Dropbox**: `/Users/username/Dropbox/my-vault`
- **Local**: `/Users/username/obsidian-vault`

### 3. Add to Claude Code MCP config

1. Edit `~/.claude/.mcp.json`
2. Paste the entire `mcpServers` block from this example into your existing `mcpServers` object

Example `~/.claude/.mcp.json`:
```json
{
"mcpServers": {
"clausidian": {
"command": "clausidian",
"args": ["serve", "--vault", "/Users/username/my-vault"]
}
}
}
```

### 4. Verify configuration

After editing:
1. Restart Claude Code
2. Run `/obsidian health` in any project
3. If you see vault statistics, configuration is successful

### 5. Troubleshooting

| Issue | Solution |
|-------|----------|
| Permission denied | Ensure your user has access to the vault directory |
| Path not found | Use `echo $HOME` to confirm path; always use full path (no `~`) |
| MCP not loading | Check Claude Code output logs for clausidian connection errors |
| Command not found | Ensure `clausidian` is installed: `npm install -g clausidian` |
26 changes: 0 additions & 26 deletions mcp-config-example.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,3 @@
}
}
}

/*
配置說明:

1. 將 REPLACE_WITH_YOUR_VAULT_PATH 替換為你的 vault 路徑,例如:
- macOS/Linux: "/Users/username/my-vault" 或 "$HOME/my-vault"
- Windows: "C:\\Users\\username\\my-vault"

2. 常見路徑示例:
- iCloud Drive: "/Users/username/Library/Mobile Documents/com~apple~CloudDocs/my-vault"
- Dropbox: "/Users/username/Dropbox/my-vault"
- 本地: "/Users/username/obsidian-vault"

3. 添加到正確位置:
編輯 ~/.claude/.mcp.json,將此整個 mcpServers 塊添加到已有的 "mcpServers" 對象中

4. 驗證配置:
- 重啟 Claude Code
- 在任何項目目錄執行: /obsidian health
- 如果輸出 vault 統計,說明配置成功

5. 故障排查:
- 權限不足: 確保用戶有訪問 vault 目錄的權限
- 路徑錯誤: 使用 echo $HOME 確認路徑,避免使用 ~ (使用完整路徑)
- MCP 未加載: 檢查 Claude Code 輸出日誌是否有 clausidian 連接錯誤
*/
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "clausidian",
"version": "3.0.1",
"description": "CLI toolkit for AI agents to manage Obsidian vaults — journal, notes, search, index sync, knowledge graphs, and more",
"version": "2.6.0",
"description": "CLI toolkit for AI agents to manage Obsidian vaults — journal, notes, search, index sync, AI recommendations, and more",
"type": "module",
"bin": {
"clausidian": "bin/cli.mjs"
Expand Down
11 changes: 11 additions & 0 deletions scaffold/.claude/commands/batch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Apply changes to multiple notes at once.

Run: `clausidian batch tag --tag newtag --filter status:active`

Operations:
- `batch tag`: add/remove tags from matching notes
- `batch update`: change frontmatter (status, goal, summary)
- `batch archive`: bulk-archive matching notes
- Filter by type, tag, status, or keyword

$ARGUMENTS
12 changes: 12 additions & 0 deletions scaffold/.claude/commands/health.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Get vault health score and diagnostics.

Run: `clausidian health --json`

Checks:
- Total notes, archive count, orphan notes
- Index freshness
- Tag coverage, broken link count
- Average note length and update frequency
- Overall health score (0-100)

$ARGUMENTS
13 changes: 13 additions & 0 deletions scaffold/.claude/commands/stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Display vault statistics (note count, types, tags, sizes).

Run: `clausidian stats --json`

Returns:
- Notes by type (area, project, resource, idea)
- Top tags and their usage
- Note length distribution
- Archive ratio
- Active notes (modified in last 7, 30, 90 days)
- Total vault size

$ARGUMENTS
11 changes: 11 additions & 0 deletions scaffold/.claude/commands/sync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Rebuild vault indices (_index.md, _tags.md, _graph.md, directory indexes).

Run: `clausidian sync`

This rescans all notes and updates:
- _index.md: central index of all notes with summaries
- _tags.md: tag-to-note mapping with counts
- _graph.md: Mermaid knowledge graph with relationship suggestions
- directory indexes: one per area/project/resource/idea type

$ARGUMENTS
11 changes: 11 additions & 0 deletions scaffold/.claude/commands/tag-list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
List all tags in vault with usage counts.

Run: `clausidian tag-list`

Shows:
- Each tag name
- How many notes use it
- Most common tags first
- Filtered by prefix if provided (e.g., `tag-list concept-`)

$ARGUMENTS
11 changes: 11 additions & 0 deletions scaffold/.claude/commands/update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Update note frontmatter (tags, status, goal, summary).

Run: `clausidian update <note> --tags tag1,tag2 --status active`

If the CLI is not available:
1. Read the note file
2. Update the YAML frontmatter with provided fields
3. Keep body unchanged
4. Update indices (_tags.md, _graph.md)

$ARGUMENTS
31 changes: 29 additions & 2 deletions src/mcp-server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { Vault } from './vault.mjs';
import { getMcpTools, getMcpDispatch } from './registry.mjs';
import { SearchCache } from './search-cache.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const PKG_VERSION = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8')).version;
Expand All @@ -20,6 +21,13 @@ const PKG_VERSION = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.js
const TOOLS = getMcpTools();
const DISPATCH = getMcpDispatch();

// Tools that modify vault state — invalidate cache on these only
const WRITE_TOOLS = new Set([
'note', 'journal', 'capture', 'update', 'patch', 'delete',
'archive', 'rename', 'move', 'merge', 'relink', 'import', 'sync',
'batch_tag', 'batch_update', 'batch_archive',
Comment on lines +25 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include all mutating MCP tools in WRITE_TOOLS

Cache invalidation now depends on WRITE_TOOLS, but this set omits mutating MCP tools (for example pin/unpin in src/commands/pin.mjs, tag_rename in src/commands/tag.mjs, and review in src/commands/review.mjs). After those tools update files, cached search responses are not cleared, so repeated search calls can return stale data (e.g., search pinned before/after pin still returns the pre-update cached result).

Useful? React with 👍 / 👎.

]);

// ── MCP Resources ──────────────────────────────────────

const RESOURCES = [
Expand Down Expand Up @@ -93,6 +101,7 @@ export class McpServer {
constructor(vaultRoot) {
this.vaultRoot = vaultRoot;
this._vault = null;
this._searchCache = new SearchCache();
}

get vault() {
Expand All @@ -101,21 +110,39 @@ export class McpServer {
}

async handleToolCall(name, args) {
this.vault.invalidateCache();
// Only invalidate cache for write operations
if (WRITE_TOOLS.has(name)) {
this.vault.invalidateCache();
this._searchCache.clear();
}

const handler = DISPATCH[name];
if (!handler) throw new Error(`Unknown tool: ${name}`);

// Try search cache for read-only search tools
if ((name === 'search' || name === 'embed-search' || name === 'smart-search') && !WRITE_TOOLS.has(name)) {
const cached = this._searchCache.get(args.keyword, args);
Comment on lines +123 to +124
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Key embed/smart search cache by query

The MCP cache path uses args.keyword for all three search tools, but embed-search and smart-search accept query (not keyword) in the registry, so these calls are cached under the same key and can return another query’s result. In practice, a second smart-search with a different query can return the first query payload from cache. Normalize the search term per tool before calling _searchCache.get/set.

Useful? React with 👍 / 👎.

if (cached) return cached;
}

const origLog = console.log;
const origError = console.error;
console.log = () => {};
console.error = () => {};
let result;
try {
return await handler(this.vaultRoot, args);
result = await handler(this.vaultRoot, args);
} finally {
console.log = origLog;
console.error = origError;
}

// Cache search results
if ((name === 'search' || name === 'embed-search' || name === 'smart-search') && !WRITE_TOOLS.has(name)) {
this._searchCache.set(args.keyword, args, result);
}

return result;
}

readResource(uri) {
Expand Down
Loading
Loading