From ccfa7e1b9ddf0488292ac0ea153e1af6a50aff18 Mon Sep 17 00:00:00 2001 From: Tomi Eckert Date: Wed, 11 Mar 2026 14:28:37 +0100 Subject: [PATCH] chore: Remove obsolete planning and documentation files. --- IDEAS.md | 81 ------------- IMPROVEME.md | 90 -------------- PROVIDERS.md | 293 -------------------------------------------- SANDBOX.md | 334 --------------------------------------------------- 4 files changed, 798 deletions(-) delete mode 100644 IDEAS.md delete mode 100644 IMPROVEME.md delete mode 100644 PROVIDERS.md delete mode 100644 SANDBOX.md diff --git a/IDEAS.md b/IDEAS.md deleted file mode 100644 index bf7a356..0000000 --- a/IDEAS.md +++ /dev/null @@ -1,81 +0,0 @@ -# Command Ideas for AnchorCli - -## Session & Help - -### `/help` -Show available commands, version info, and tool capabilities. Combines `/help`, `/version`, `/about`, and `/tools`. - -### `/clear` -Clear the terminal screen and optionally reset conversation with `/clear --reset`. - -### `/history` -Show the current chat history. Use `/history ` to show last N messages. - -## Navigation - -### `/cd [path]` -Change directory. With no argument, shows current working directory (combines `/cwd`, `/pwd`, `/cd`). - -### `/ls` -List files in current directory (alias for ListDir tool). - -## Configuration - -### `/config` -Show or modify settings. Subcommands: -- `/config model ` - Change AI model -- `/config endpoint ` - Change API endpoint -- `/config debug ` - Toggle debug mode - -## Conversation Management - -### `/save [filename]` -Save current conversation to a file (JSON or markdown format). - -### `/load ` -Load a previous conversation from a file. - -### `/export ` -Export chat history in a specific format (JSON, markdown, plain text). - -## Advanced Features - -### `/undo` -Undo the last file edit (requires edit history tracking). - -### `/diff [file]` -Show differences between current and original file state. With no argument, shows all pending changes. - -### `/search ` -Quick file/content search across the project. - -### `/stats` -Show session statistics (files edited, tokens used, commands run, estimated costs). - -### `/macro [commands...]` -Create and execute multi-step command sequences. - -### `/alias ` -Create custom command shortcuts. - -## Safety & Integration - -### `--dry-run` / Read-only Mode -Run Anchor without mutating any files. Shows what *would* happen (edits, deletes, renames) without applying changes. Perfect for reviewing AI suggestions before committing. - -### Git Integration -Seamless version control integration: -- Auto-create a branch per session (`anchor session --git-branch`) -- Auto-commit after successful edits with descriptive messages -- Show git diff before/after operations -- Revert to pre-session state if something goes wrong - -### Mutation Rate Limits -Prevent runaway AI from trashing a project: -- Configurable max file edits per conversation turn -- Hard cap on delete/rename operations without confirmation -- Cooldown period after N rapid mutations -- Warning when approaching limits - -### File Type Restrictions -Config to block edits on sensitive patterns (`*.config`, `*.sql`, `*.production.*`, etc.). Requires explicit override flag. diff --git a/IMPROVEME.md b/IMPROVEME.md deleted file mode 100644 index bd44c2a..0000000 --- a/IMPROVEME.md +++ /dev/null @@ -1,90 +0,0 @@ -# Improvements for AnchorCli - -This document contains criticisms and suggestions for improving the AnchorCli project. - -## Architecture - -1. **Program.cs is too large (433 lines)** - Split into smaller classes: ChatSession, ReplLoop, ResponseStreamer -2. **No dependency injection** - Use Microsoft.Extensions.DependencyInjection for testability -3. **Static tool classes with global Log delegates** - Convert to instance classes with injected ILogger - -## Testing - -4. **No unit tests** - Add xUnit project, test HashlineEncoder/Validator, tools, and ContextCompactor -5. **No integration tests** - Use Spectre.Console.Testing for TUI workflows -6. **No CI/CD** - Add GitHub Actions for test runs on push/PR - -## Documentation - -7. **Missing XML docs** - Add summary docs to public APIs -8. **Incomplete README** - Add contributing, development, troubleshooting sections -9. **No CHANGELOG.md** - Track releases and changes - -## Security & Safety - -10. **Command execution unsandboxed** - Add allowlist/denylist, time limits, output size limits -11. **No mutation rate limiting** - Track edits per turn, add configurable limits -12. **API key in plain text** - Use OS keychain or env var, set restrictive file permissions - -## Performance - -13. **No file read caching** - Cache file content per-turn with invalidation on write -14. **Regex not static** - Make compiled regexes static readonly - -## User Experience - -15. **No undo** - Store edit history, add /undo command -16. **No session persistence** - Add /save and /load commands -17. **Limited error recovery** - Better error messages, /debug mode - -## Developer Experience - -18. **No .editorconfig** - Add code style enforcement -19. **No solution file** - Create AnchorCli.sln -20. **Hardcoded model list** - Fetch from OpenRouter API dynamically -21. **No version info** - Add to .csproj, display in /help - -## Code Quality - -22. **Inconsistent error handling** - Standardize on error strings, avoid empty catch blocks -23. **Magic numbers** - Extract to named constants (150_000, 300, KeepRecentTurns=2) -24. **Commented-out debug code** - Remove or use #if DEBUG -25. **Weak hash algorithm** - Adler-8 XOR only has 256 values; consider 4-char hex - -## Build & Dependencies - -26. **No LICENSE file** - Add MIT LICENSE file - -## Priority - -### High -- [ ] Add unit tests -- [ ] Implement undo functionality -- [ ] Add mutation rate limiting -- [x] Refactor Program.cs -- [x] Add LICENSE file - -### Medium -- [ ] Session persistence -- [ ] XML documentation -- [ ] Error handling consistency -- [x] .editorconfig -- [ ] Dynamic model list - -### Low -- [ ] CHANGELOG.md -- [ ] CI/CD pipeline -- [ ] Stronger hash algorithm -- [ ] Code coverage reporting - -## Quick Wins (<1 hour each) - -- [x] Add to .csproj -- [x] Create LICENSE file -- [x] Add .editorconfig -- [x] Remove commented code -- [x] Extract magic numbers to constants -- [x] Add XML docs to Hashline classes -- [x] Make regexes static readonly - -*Prioritize based on goals: safety, testability, or user experience.* diff --git a/PROVIDERS.md b/PROVIDERS.md deleted file mode 100644 index d0e3cfe..0000000 --- a/PROVIDERS.md +++ /dev/null @@ -1,293 +0,0 @@ -# Provider Support Plan - -## Current Problems - -1. **OpenRouter Hardcoded**: Endpoint, headers, and pricing API calls are hardcoded to OpenRouter -2. **Config Ineffective**: SetupTui allows "custom endpoint" but Program.cs ignores it -3. **Token Count**: Token usage tracking only works with OpenRouter response headers -4. **Pricing Only for One Provider**: Models list shows pricing, but only when using OpenRouter - ---- - -## Goals - -1. Make the system **endpoint-agnostic** -2. Support pricing/token tracking for **multiple providers** -3. Keep **OpenRouter as the default** (familiar) -4. Allow users to configure any OpenAI-compatible endpoint -5. Show pricing/token info **only when available** for each provider - ---- - -## Provider Categories - -### Tier 1: Native Support (Built-in) -- OpenRouter (default) -- Ollama (local, no auth) -- Groq (high-speed inference) -- Anthropic (native or via API) -- OpenAI (official api) - -### Tier 2: Config-Based Support -- Cerebras -- DeepSeek -- Any OpenAI-compatible endpoint that supports custom headers - -### Tier 3: Manual Configuration Required -- Self-hosted endpoints -- Corporate proxies -- Custom middleware layers - ---- - -```csharp -// Example: Provider interface -class PricingProvider -{ - // Get pricing info from provider's API - async Task> GetModelsAsync(string apiKey); - - // Get tokens from response - async Task GetTokensFromResponseAsync(HttpResponseMessage response); - - // Add provider-specific headers if needed - void AddHeaders(HttpRequestMessage request, string apiKey); -} -``` - -**Supported Implementations:** -- `OpenRouterProvider` (uses `/api/v1/models` + `x-total-tokens`) -- `GroqProvider` (uses Groq's pricing API + response headers) -- `OllamaProvider` (free tier, no pricing lookup, basic token counting) -- `OpenAIProvider` (uses OpenAI's model list + token counting) -- `GenericProvider` (fallback for any OpenAI-compatible endpoint) - -**Configuration:** -Store provider selection in `anchor.config.json`: -```json -{ - "apiKey": "your-key", - "model": "qwen3.5-27b", - "endpoint": "https://openrouter.ai/api/v1", - "provider": "openrouter" -} -``` - -Auto-detect provider from endpoint URL if not specified. ---- - -## Pricing System - -### Current State -- Uses OpenRouter's `/api/v1/models` endpoint -- Displays pricing in a table during startup -- Only works when using OpenRouter - -### Improved Behavior - -**When endpoint matches known provider:** -1. Fetch pricing from that provider's API -2. Display pricing in the startup table -3. Show per-prompt costs in chat output - -**When endpoint is generic/unsupported:** -1. Skip API call (no pricing lookup) -2. Display `---` or `$` placeholders -3. Optional: Show "Pricing not available" note - -**User Feedback:** -- Show clear messaging: "Pricing data loaded from OpenRouter" -- Show: "Pricing not available for this endpoint" (for unsupported) -- Don't break chat functionality if pricing fails - -### Pricing Data Format - -Store in `ModelPricing` class: -```csharp -class ModelPricing -{ - string ModelId; - decimal InputPricePerMTokens; - decimal OutputPricePerMTokens; - double? CacheCreationPricePerMTokens; // if supported -} -``` - ---- - -## Token Tracking System - -### Current State -- Uses `x-total-tokens` from OpenRouter headers -- Only works with OpenRouter responses - -### Multi-Provider Strategy - -**OpenRouter:** -- Use `x-total-tokens` header -- Use `x-response-timing` for latency tracking - -**Groq:** -- Use `x-groq-tokens` header -- Use `x-groq-response-time` for latency - -**OpenAI:** -- Use `x-ai-response-tokens` header (if available) -- Fall back to response body if needed - -**Ollama:** -- No official token counting -- Use output length as proxy estimate -- Optional: Show message token estimates - -**Generic/Fallback:** -- Parse `total_tokens` from response JSON -- Fall back to character count estimates -- Show placeholder when unavailable - -### Integration Points - -**During Chat Session:** -1. After each response, extract tokens from response headers -2. Store in `ChatSession.TokensUsed` object -3. Display in status bar: `Tokens: 128/2048 • Cost: $0.002` - -**At Session End:** -1. Show summary: `Total tokens: 1,024 | Total cost: $0.015` -2. Write to session log or history file - ---- - -## Implementation Roadmap - -### Phase 1: Conditional Pricing (Current Issues First) -- [ ] Check if endpoint is OpenRouter before fetching pricing -- [ ] Skip pricing API call for non-OpenRouter endpoints -- [ ] Show placeholder message if pricing not available -- [ ] **Time estimate:** 2 hours - -### Phase 2: Provider Configuration -- [ ] Add `provider` field to `AnchorConfig` model -- [ ] Update `SetupTui` to ask "Which provider?" (openrouter, ollama, groq, etc.) -- [ ] Auto-detect provider from endpoint URL (smart default) -- [ ] Write provider to config file on setup -- [ ] **Time estimate:** 3 hours - -### Phase 3: Provider Abstraction -- [ ] Create `IPricingProvider` interface -- [ ] Move existing `PricingProvider` to `OpenRouterProvider` -- [ ] Create `GenericPricingProvider` for fallback -- [ ] Add provider factory: `ProviderFactory.Create(providerName)` -- [ ] **Time estimate:** 5 hours - -### Phase 4: Token Tracking Enhancement -- [ ] Create `ITokenTracker` interface -- [ ] Implement token extraction for multiple providers -- [ ] Display token usage in status bar -- [ ] Add per-prompt cost calculation -- [ ] **Time estimate:** 6 hours - -### Phase 5: Second Provider Implementation -- [ ] Implement `GroqProvider` (similar to OpenRouter) -- [ ] Test with Groq API -- [ ] Update documentation -- [ ] **Time estimate:** 4 hours - -### Phase 6: Future-Proofing (Optional) -- [ ] Add plugin system for custom providers -- [ ] Allow users to define custom pricing rules -- [ ] Support OpenRouter-compatible custom endpoints -- [ ] **Time estimate:** 8+ hours - ---- - -## User Configuration Guide - -### Automatic Setup -Run `/setup` in the chat or `anchor setup` in CLI: -``` -Which provider are you using? -1) OpenRouter (qwen models) -2) Groq (qwen/gemma models) -3) Ollama (local models) -4) OpenAI (gpt models) -5) Custom endpoint -``` - -### Manual Configuration -Edit `anchor.config.json` directly: -```json -{ - "apiKey": "your-api-key", - "model": "qwen3.5-27b", - "endpoint": "https://api.groq.com/openai/v1", - "provider": "groq" // optional, auto-detected if missing -} -``` - -### Environment Variables -For custom setup: -``` -ANCHOR_ENDPOINT=https://api.groq.com/openai/v1 -ANCHOR_PROVIDER=groq -ANCHOR_API_KEY=... -ANCHOR_MODEL=qwen3.5-27b -``` - ---- - -## Known Limitations - -### Tier 1 Providers (Full Support) -**✓ OpenRouter** -- Pricing: ✓ (native API) -- Tokens: ✓ (response headers) -- Cost tracking: ✓ - -**✓ Groq** (after Phase 4) -- Pricing: ✓ (will add) -- Tokens: ✓ (response headers) -- Cost tracking: ✓ - -### Tier 2 Providers (Partial Support) -**○ Ollama** -- Pricing: ○ (free, no lookup needed) -- Tokens: ○ (estimated from output) -- Cost tracking: ○ (placeholder) - -**○ OpenAI** -- Pricing: ○ (manual pricing display) -- Tokens: ○ (header extraction) -- Cost tracking: ○ (config-based) - -### Tier 3 Providers (Basic Support) -**□ Custom Endpoints** -- Pricing: □ (manual only) -- Tokens: □ (fallback parsing) -- Cost tracking: □ (user-defined) - ---- - -## Future Enhancements - -1. **Pricing Database**: Maintain own pricing database (like OpenRouter's) -2. **Cost Estimator**: Predict costs before sending message -3. **Usage Alerts**: Warn user when approaching budget limits -4. **Multi-Model Support**: Compare costs between different providers -5. **Plugin System**: Allow community to add new providers - ---- - -## Success Criteria - -- ✅ Users can choose from 3+ providers in setup -- ✅ Pricing displays only for supported endpoints -- ✅ Token tracking works for all Tier 1 providers -- ✅ No breaking changes to existing OpenRouter users -- ✅ Clear documentation on what each provider supports -- ✅ Graceful degradation for unsupported features - ---- - -*Last Updated: 2025-12-23* - diff --git a/SANDBOX.md b/SANDBOX.md deleted file mode 100644 index 232baca..0000000 --- a/SANDBOX.md +++ /dev/null @@ -1,334 +0,0 @@ -# Sandbox Implementation Plan for AnchorCli - -## Overview - -By default, all file and directory operations are restricted to the current working directory (CWD). -Users can bypass this restriction with the `--no-sandbox` flag. - -## Usage - -```bash -# Default: sandbox enabled (operations limited to CWD) -anchor - -# Disable sandbox (allow operations anywhere) -anchor --no-sandbox -``` - -## Architecture - -The implementation leverages the existing `ResolvePath()` methods in `FileTools` and `DirTools`. -Since tools are static classes without dependency injection, we use a static `SandboxContext` class. - ---- - -## Implementation Steps - -### Step 1: Create `SandboxContext.cs` - -Create a new file `Core/SandboxContext.cs`: - -```csharp -using System; - -namespace AnchorCli; - -/// -/// Static context holding sandbox configuration. -/// Checked by ResolvePath() to validate paths are within working directory. -/// -internal static class SandboxContext -{ - private static string? _workingDirectory; - private static bool _enabled = true; - - public static bool Enabled - { - get => _enabled; - set => _enabled = value; - } - - public static string WorkingDirectory - { - get => _workingDirectory ?? Environment.CurrentDirectory; - set => _workingDirectory = value; - } - - /// - /// Validates that a resolved path is within the working directory (if sandbox is enabled). - /// Returns the resolved path if valid, or null if outside sandbox (no exception thrown). - /// When null is returned, the calling tool should return an error message to the agent. - /// - public static string? ValidatePath(string resolvedPath) - { - if (!_enabled) - return resolvedPath; - - var workDir = WorkingDirectory; - - // Normalize paths for comparison - var normalizedPath = Path.GetFullPath(resolvedPath).TrimEnd(Path.DirectorySeparatorChar); - var normalizedWorkDir = Path.GetFullPath(workDir).TrimEnd(Path.DirectorySeparatorChar); - - // Check if path starts with working directory - if (!normalizedPath.StartsWith(normalizedWorkDir, StringComparison.OrdinalIgnoreCase)) - { - // Return null to signal violation - caller handles error messaging - return null; - } - - return resolvedPath; - } - - public static void Initialize(bool sandboxEnabled) - { - _enabled = sandboxEnabled; - _workingDirectory = Environment.CurrentDirectory; - } -} -``` - ---- - -### Step 2: Modify `Program.cs` - -Add argument parsing and initialize the sandbox context: - -**After line 15** (after the `setup` subcommand check), add: - -```csharp -// ── Parse sandbox flag ────────────────────────────────────────────────── -bool sandboxEnabled = !args.Contains("--no-sandbox"); -SandboxContext.Initialize(sandboxEnabled); - -if (!sandboxEnabled) -{ - AnsiConsole.MarkupLine("[dim grey]Sandbox disabled (--no-sandbox)[/]"); -} -``` - ---- - -### Step 3: Update `FileTools.ResolvePath()` - -**Replace lines 322-323** with: - - internal static string? ResolvePath(string path, out string? errorMessage) - { - errorMessage = null; - var resolved = Path.IsPathRooted(path) - ? path - : Path.GetFullPath(path, Environment.CurrentDirectory); - - var validated = SandboxContext.ValidatePath(resolved); - if (validated == null) - { - errorMessage = $"Sandbox violation: Path '{path}' is outside working directory '{SandboxContext.WorkingDirectory}'. Use --no-sandbox to disable restrictions."; - return null; - } - - return validated; - } - ---- - -### Step 4: Update `DirTools.ResolvePath()` - -**Replace lines 84-85** with: - -```csharp - internal static string? ResolvePath(string path, out string? errorMessage) - { - errorMessage = null; - var resolved = Path.IsPathRooted(path) - ? path - : Path.GetFullPath(path, Environment.CurrentDirectory); - - var validated = SandboxContext.ValidatePath(resolved); - if (validated == null) - { - errorMessage = $"Sandbox violation: Path '{path}' is outside working directory '{SandboxContext.WorkingDirectory}'. Use --no-sandbox to disable restrictions."; - return null; - } - - return validated; - } - ---- - -### Step 5: Update Tool Descriptions (Optional but Recommended) - -Update the `[Description]` attributes to mention sandbox behavior: - -**FileTools.cs - ReadFile** (line 23): -```csharp -[Description("Read a file. Max 200 lines per call. Returns lines with line:hash| anchors. Sandbox: restricted to working directory unless --no-sandbox is used. IMPORTANT: Call GrepFile first...")] -``` - -**DirTools.cs - CreateDir** (line 63): -```csharp -[Description("Create a new directory. Creates parent directories if they don't exist. Sandbox: restricted to working directory unless --no-sandbox is used. Returns OK on success...")] -``` - -Repeat for other tools as needed. - ---- - -## How Tools Handle Sandbox Violations - -Each tool that uses `ResolvePath()` must check for `null` return and handle it gracefully: - -### FileTools Pattern - -```csharp -// Before (old code): -var resolvedPath = ResolvePath(path); -var content = File.ReadAllText(resolvedPath); - -// After (new code): -var resolvedPath = ResolvePath(path, out var errorMessage); -if (resolvedPath == null) - return $"ERROR: {errorMessage}"; - -var content = File.ReadAllText(resolvedPath); -``` - -### DirTools Pattern - -```csharp -// Before (old code): -var resolvedPath = ResolvePath(path); -Directory.CreateDirectory(resolvedPath); - -// After (new code): -var resolvedPath = ResolvePath(path, out var errorMessage); -if (resolvedPath == null) - return $"ERROR: {errorMessage}"; - -Directory.CreateDirectory(resolvedPath); -return "OK"; -``` - -### EditTools - -No changes needed - it already calls `FileTools.ResolvePath()`, so the sandbox check happens there. - -### Tools That Don't Use ResolvePath - -- `ListDir` with no path argument (uses current directory) -- `GetFileInfo` - needs to be updated to use `ResolvePath()` -- `FindFiles` - needs to be updated to validate the search path - ---- ---- - -## Error Handling - No Crashes - -When a sandbox violation occurs, the program **does not crash**. Instead: - -1. `ResolvePath()` returns `null` and sets `errorMessage` -2. The tool returns the error message to the agent -3. The agent sees the error and can continue the conversation -4. The user sees a clear error message in the chat - -**Example tool implementation pattern:** - -```csharp -public static async Task ReadFile(string path, int startLine, int endLine) -{ - var resolvedPath = ResolvePath(path, out var errorMessage); - if (resolvedPath == null) - return $"ERROR: {errorMessage}"; // Return error, don't throw - - // ... rest of the tool logic -} -``` - -**What the agent sees:** -``` -Tool result: ERROR: Sandbox violation: Path '/home/tomi/.ssh' is outside working directory '/home/tomi/dev/anchor'. Use --no-sandbox to disable restrictions. -``` - -**What the user sees in chat:** -> The agent tried to read `/home/tomi/.ssh` but was blocked by the sandbox. The agent can now adjust its approach or ask you to run with `--no-sandbox`. - ---- - -## Edge Cases Handled - -| Case | Behavior | -|------|----------| -| **Symlinks inside CWD pointing outside** | Follows symlink (user-created link = intentional) | -| **Path traversal (`../..`)** | Blocked if result is outside CWD | -| **Absolute paths** | Validated against CWD | -| **Network paths** | Blocked (not under CWD) | -| **Case sensitivity** | Uses `OrdinalIgnoreCase` for cross-platform compatibility | - ---- - -## Security Notes - -⚠️ **The sandbox is a safety feature, not a security boundary.** - -- It prevents **accidental** modifications to system files -- It does **not** protect against malicious intent -- `CommandTool.ExecuteCommand()` can still run arbitrary shell commands -- A determined user can always use `--no-sandbox` - -For true isolation, run anchor in a container or VM. - ---- - -## Testing Checklist - -- [ ] `ReadFile` on file inside CWD → **Success** -- [ ] `ReadFile` on file outside CWD → **Sandbox violation error** -- [ ] `ReadFile` with `../` traversal outside CWD → **Sandbox violation error** -- [ ] `CreateDir` outside CWD → **Sandbox violation error** -- [ ] `anchor --no-sandbox` then read `/etc/passwd` → **Success** -- [ ] Symlink inside CWD pointing to `/etc/passwd` → **Success** (user-created link) -- [ ] Case variations on Windows (`C:\Users` vs `c:\users`) → **Success** - ---- - -## Migration Guide - -### Existing Workflows - -If you have scripts or workflows that rely on accessing files outside the project: - -```bash -# Update your scripts to use --no-sandbox -anchor --no-sandbox -``` - -### CI/CD Integration - -For CI environments where sandbox may not be needed: - -```yaml -# GitHub Actions example -- name: Run anchor - run: anchor --no-sandbox -``` - ---- - -## Files Modified - -| File | Changes | -|------|---------| -| `Core/SandboxContext.cs` | **New file** - Static sandbox state and validation | -| `Program.cs` | Add `--no-sandbox` parsing, call `SandboxContext.Initialize()` | -| `Tools/FileTools.cs` | Update `ResolvePath()` signature to return `string?` with `out errorMessage`; update all tool methods to check for null | -| `Tools/DirTools.cs` | Update `ResolvePath()` signature to return `string?` with `out errorMessage`; update all tool methods to check for null | -| `Tools/EditTools.cs` | No changes (uses `FileTools.ResolvePath()`, sandbox check happens there) | -| `Tools/CommandTool.cs` | **Not sandboxed** - shell commands can access any path (documented limitation) | ---- - -## Future Enhancements - -- **Allowlist**: Let users specify additional safe directories via config -- **Per-tool sandbox**: Some tools (e.g., `GrepRecursive`) could have different rules -- **Audit mode**: Log all file operations for review -- **Interactive prompt**: Ask for confirmation before violating sandbox instead of hard fail