feat: Implement foundation layer with domain entities and repository interfaces

- Add complete domain layer: Note, Vault, WikiLink, Tag, Frontmatter, Graph entities
- Implement repository interfaces for data access abstraction
- Create comprehensive configuration system with YAML and env support
- Add CLI entry point with signal handling and graceful shutdown
- Fix mermaid diagram syntax in design.md (array notation)
- Add CLAUDE.md for development guidance

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev
2025-10-08 10:22:28 +04:00
parent b655c58ba1
commit da289d4a7e
24 changed files with 1840 additions and 7 deletions

241
internal/config/config.go Normal file
View File

@@ -0,0 +1,241 @@
package config
import (
"fmt"
"os"
"time"
"gopkg.in/yaml.v3"
)
type Config struct {
Server ServerConfig `yaml:"server"`
Vault VaultConfig `yaml:"vault"`
Git GitConfig `yaml:"git"`
Index IndexConfig `yaml:"index"`
Search SearchConfig `yaml:"search"`
Cache CacheConfig `yaml:"cache"`
Logging LoggingConfig `yaml:"logging"`
Performance PerformanceConfig `yaml:"performance"`
Security SecurityConfig `yaml:"security"`
}
type ServerConfig struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Transport string `yaml:"transport"` // "stdio" or "sse"
}
type VaultConfig struct {
Path string `yaml:"path"`
ExcludePaths []string `yaml:"exclude_paths"`
WatchChanges bool `yaml:"watch_changes"`
}
type GitConfig struct {
Enabled bool `yaml:"enabled"`
AutoPull bool `yaml:"auto_pull"`
AutoPush bool `yaml:"auto_push"`
AutoCommit bool `yaml:"auto_commit"`
CommitPrefix string `yaml:"commit_prefix"`
Remote string `yaml:"remote"`
Branch string `yaml:"branch"`
}
type IndexConfig struct {
BuildOnStartup bool `yaml:"build_on_startup"`
RebuildInterval time.Duration `yaml:"rebuild_interval"`
MaxNotesInMemory int `yaml:"max_notes_in_memory"`
}
type SearchConfig struct {
MaxResults int `yaml:"max_results"`
ContextLines int `yaml:"context_lines"`
CaseSensitive bool `yaml:"case_sensitive"`
}
type CacheConfig struct {
Enabled bool `yaml:"enabled"`
MaxSize int `yaml:"max_size"`
TTL time.Duration `yaml:"ttl"`
}
type LoggingConfig struct {
Level string `yaml:"level"` // debug, info, warn, error
Format string `yaml:"format"` // text or json
File string `yaml:"file"`
}
type PerformanceConfig struct {
MaxConcurrentOperations int `yaml:"max_concurrent_operations"`
ReadTimeout time.Duration `yaml:"read_timeout"`
WriteTimeout time.Duration `yaml:"write_timeout"`
}
type RateLimitConfig struct {
Enabled bool `yaml:"enabled"`
RequestsPerSecond int `yaml:"requests_per_second"`
Burst int `yaml:"burst"`
}
type SecurityConfig struct {
RateLimit RateLimitConfig `yaml:"rate_limit"`
MaxFileSize string `yaml:"max_file_size"`
}
func NewDefaultConfig() *Config {
return &Config{
Server: ServerConfig{
Name: "obsidian-mcp",
Version: "1.0.0",
Transport: "stdio",
},
Vault: VaultConfig{
Path: "",
ExcludePaths: []string{
".obsidian/",
".git/",
".trash/",
},
WatchChanges: true,
},
Git: GitConfig{
Enabled: true,
AutoPull: true,
AutoPush: false,
AutoCommit: true,
CommitPrefix: "[MCP]",
Remote: "origin",
Branch: "main",
},
Index: IndexConfig{
BuildOnStartup: true,
RebuildInterval: 5 * time.Minute,
MaxNotesInMemory: 10000,
},
Search: SearchConfig{
MaxResults: 50,
ContextLines: 2,
CaseSensitive: false,
},
Cache: CacheConfig{
Enabled: true,
MaxSize: 1000,
TTL: 5 * time.Minute,
},
Logging: LoggingConfig{
Level: "info",
Format: "text",
File: "",
},
Performance: PerformanceConfig{
MaxConcurrentOperations: 10,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
},
Security: SecurityConfig{
RateLimit: RateLimitConfig{
Enabled: true,
RequestsPerSecond: 10,
Burst: 20,
},
MaxFileSize: "10MB",
},
}
}
func LoadConfig(path string) (*Config, error) {
config := NewDefaultConfig()
if path == "" {
return config, nil
}
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return config, nil
}
return nil, fmt.Errorf("failed to read config file: %w", err)
}
if err := yaml.Unmarshal(data, config); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
if err := config.Validate(); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
return config, nil
}
func LoadConfigFromEnv() (*Config, error) {
config := NewDefaultConfig()
if vaultPath := os.Getenv("VAULT_PATH"); vaultPath != "" {
config.Vault.Path = vaultPath
}
if logLevel := os.Getenv("LOG_LEVEL"); logLevel != "" {
config.Logging.Level = logLevel
}
if gitEnabled := os.Getenv("GIT_ENABLED"); gitEnabled == "false" {
config.Git.Enabled = false
}
if gitAutoPush := os.Getenv("GIT_AUTO_PUSH"); gitAutoPush == "true" {
config.Git.AutoPush = true
}
if cacheEnabled := os.Getenv("CACHE_ENABLED"); cacheEnabled == "false" {
config.Cache.Enabled = false
}
return config, config.Validate()
}
func (c *Config) Validate() error {
if c.Vault.Path == "" {
return fmt.Errorf("vault path is required")
}
if c.Server.Transport != "stdio" && c.Server.Transport != "sse" {
return fmt.Errorf("invalid transport: %s", c.Server.Transport)
}
if c.Search.MaxResults <= 0 {
return fmt.Errorf("max results must be positive")
}
if c.Cache.MaxSize <= 0 {
return fmt.Errorf("cache max size must be positive")
}
validLogLevels := map[string]bool{
"debug": true,
"info": true,
"warn": true,
"error": true,
}
if !validLogLevels[c.Logging.Level] {
return fmt.Errorf("invalid log level: %s", c.Logging.Level)
}
return nil
}
func (c *Config) SaveToFile(path string) error {
data, err := yaml.Marshal(c)
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(path, data, 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}