diff --git a/dev/docs/context-reference-convention.md b/dev/docs/context-reference-convention.md new file mode 100644 index 0000000..a9dc7f5 --- /dev/null +++ b/dev/docs/context-reference-convention.md @@ -0,0 +1,364 @@ +# Context Reference Convention + +## Overview + +All context file references in OpenAgents **MUST** use the standardized format: + +```markdown +@.opencode/context/path/to/file.md +``` + +This convention is **required** for the installation system to work correctly across local and global installations. + +--- + +## The Requirement + +### ✅ Correct Format + +```markdown +@.opencode/context/core/essential-patterns.md +@.opencode/context/project/project-context.md +@.opencode/context/security/auth.md +``` + +### ❌ Incorrect Formats + +```markdown +@context/core/patterns.md ❌ Missing .opencode prefix +@~/context/file.md ❌ Absolute path (breaks local installs) +@$CONTEXT_DIR/file.md ❌ Variables (can't be transformed) +../context/file.md ❌ Relative path (unreliable) +``` + +--- + +## Why This Convention? + +### Problem: Local vs Global Installations + +Users can install OpenAgents in two ways: + +**Local Install:** +```bash +.opencode/ +├── agent/ +├── command/ +└── context/ +``` +References work as: `@.opencode/context/file.md` (relative to current directory) + +**Global Install:** +```bash +~/.config/opencode/ +├── agent/ +├── command/ +└── context/ +``` +References need to be: `@~/.config/opencode/context/file.md` (absolute path) + +### Solution: Install-Time Transformation + +The installation script automatically transforms references based on installation type: + +```bash +# Local install (.opencode/) +@.opencode/context/test.md → @.opencode/context/test.md (no change) + +# Global install (~/.config/opencode/) +@.opencode/context/test.md → @~/.config/opencode/context/test.md (transformed) + +# Custom install (/usr/local/opencode/) +@.opencode/context/test.md → @/usr/local/opencode/context/test.md (transformed) +``` + +--- + +## How It Works + +### Repository Files (Source) + +All files in the repository use the standard format: + +```markdown +# Example Agent + +Load patterns from @.opencode/context/core/essential-patterns.md +``` + +### Installation Process + +When a user installs globally, the installer: + +1. Downloads the file +2. Detects global installation (INSTALL_DIR != ".opencode") +3. Transforms all references: + ```bash + sed -e "s|@\.opencode/context/|@${INSTALL_DIR}/context/|g" \ + -e "s|\.opencode/context|${INSTALL_DIR}/context|g" file.md + ``` +4. Saves the transformed file + +### Result + +**After global install to ~/.config/opencode:** +```markdown +# Example Agent + +Load patterns from @/Users/username/.config/opencode/context/core/essential-patterns.md +``` + +--- + +## What Gets Transformed + +### All File Types + +The transformation applies to **EVERY** file during installation: + +- ✅ Agent files (`.opencode/agent/*.md`) +- ✅ Subagent files (`.opencode/agent/subagents/*.md`) +- ✅ Command files (`.opencode/command/*.md`) +- ✅ Context files (`.opencode/context/**/*.md`) +- ✅ Any other markdown files + +### All Reference Types + +Both patterns are transformed: + +**Pattern 1: @ References (OpenCode syntax)** +```markdown +@.opencode/context/file.md → @/install/path/context/file.md +``` + +**Pattern 2: Shell Commands** +```markdown +.opencode/context/file.md → /install/path/context/file.md +``` + +--- + +## Testing & Validation + +### Why We Enforce This + +During development and testing, we discovered: + +1. **Inconsistent references broke installations** - Some files used `@context/`, others used `@.opencode/context/` +2. **Variable-based paths couldn't be transformed** - `@$CONTEXT_DIR/file.md` can't be reliably replaced +3. **Relative paths were unreliable** - `../context/file.md` broke when files moved +4. **Absolute paths broke local installs** - `@~/.config/opencode/context/` doesn't work for `.opencode/` + +### Test Results + +With the standardized convention: +- ✅ 31/31 tests passed (100% success rate) +- ✅ Works for local installations +- ✅ Works for global installations +- ✅ Works for custom installation paths +- ✅ Transforms all file types correctly +- ✅ Handles multiple references per file + +--- + +## Implementation Details + +### Detection Logic + +The installer determines if transformation is needed: + +```bash +if [[ "$INSTALL_DIR" != ".opencode" ]] && [[ "$INSTALL_DIR" != *"/.opencode" ]]; then + # Global install detected → Transform paths +else + # Local install detected → Keep original paths +fi +``` + +### Transformation Command + +```bash +sed -i.bak -e "s|@\.opencode/context/|@${expanded_path}/context/|g" \ + -e "s|\.opencode/context|${expanded_path}/context|g" "$dest" +rm -f "${dest}.bak" +``` + +**Explanation:** +- `-i.bak` - Edit in-place, create backup +- First pattern - Transform @ references +- Second pattern - Transform shell command paths +- `g` flag - Replace ALL occurrences +- Remove backup file after transformation + +--- + +## Developer Guidelines + +### When Creating New Files + +**Always use the standard format:** + +```markdown +# Good Examples ✅ +@.opencode/context/core/essential-patterns.md +@.opencode/context/project/project-context.md +@.opencode/context/security/auth.md + +# Bad Examples ❌ +@context/file.md +@~/context/file.md +@$CONTEXT_DIR/file.md +../context/file.md +``` + +### When Referencing Context + +**In agents:** +```markdown +Load context from @.opencode/context/core/essential-patterns.md +``` + +**In commands:** +```markdown +Reference: @.opencode/context/project/project-context.md +``` + +**In context files:** +```markdown +See also: @.opencode/context/security/auth.md +``` + +**In shell commands:** +```markdown +!`ls .opencode/context/` +!`find .opencode/context -name "*.md"` +``` + +--- + +## Validation + +### Pre-Commit Validation + +The repository includes a validation script: + +```bash +./scripts/validate-context-refs.sh +``` + +This checks all markdown files for: +- ✅ Correct `@.opencode/context/` format +- ❌ Forbidden dynamic variables (`@$VAR/`) +- ❌ Non-standard references + +### Manual Validation + +Check a file manually: + +```bash +# Should only find @.opencode/context/ references +grep -E '@[^~$]' file.md | grep -v '@.opencode/context/' + +# Should return nothing (empty result = good) +``` + +--- + +## Examples + +### Example 1: Agent File + +**File:** `.opencode/agent/openagent.md` + +```markdown +# OpenAgent + + +Load essential patterns from @.opencode/context/core/essential-patterns.md +Load project context from @.opencode/context/project/project-context.md + + +## Workflow +1. Analyze request +2. Check patterns in @.opencode/context/core/essential-patterns.md +3. Execute task +``` + +**After global install to ~/.config/opencode:** +```markdown +# OpenAgent + + +Load essential patterns from @/Users/username/.config/opencode/context/core/essential-patterns.md +Load project context from @/Users/username/.config/opencode/context/project/project-context.md + + +## Workflow +1. Analyze request +2. Check patterns in @/Users/username/.config/opencode/context/core/essential-patterns.md +3. Execute task +``` + +--- + +### Example 2: Command File + +**File:** `.opencode/command/commit.md` + +```markdown +# Commit Command + +Reference patterns from @.opencode/context/core/essential-patterns.md + +Check .opencode/context/project/project-context.md for commit conventions. +``` + +**After global install to /usr/local/opencode:** +```markdown +# Commit Command + +Reference patterns from @/usr/local/opencode/context/core/essential-patterns.md + +Check /usr/local/opencode/context/project/project-context.md for commit conventions. +``` + +--- + +## Summary + +### The Rule + +**All context references MUST use:** +```markdown +@.opencode/context/path/to/file.md +``` + +### Why + +- ✅ Works for local installations +- ✅ Works for global installations +- ✅ Works for custom installation paths +- ✅ Automatically transformed during installation +- ✅ Tested and validated +- ✅ Consistent across all files + +### Enforcement + +- Pre-commit validation script +- Installation system validation +- Code review guidelines +- This documentation + +--- + +## Related Documentation + +- [Installation Flow Analysis](../../INSTALLATION_FLOW_ANALYSIS.md) +- [Test Report](../../TEST_REPORT.md) +- [Final Review](../../FINAL_REVIEW.md) + +--- + +**Last Updated:** 2024-11-19 +**Status:** Required Convention +**Validation:** Automated via `scripts/validate-context-refs.sh` diff --git a/install.sh b/install.sh index 11727f9..6d7edb6 100755 --- a/install.sh +++ b/install.sh @@ -51,6 +51,9 @@ REGISTRY_URL="${RAW_URL}/registry.json" INSTALL_DIR="${OPENCODE_INSTALL_DIR:-.opencode}" # Allow override via environment variable TEMP_DIR="/tmp/opencode-installer-$$" +# Cleanup temp directory on exit (success or failure) +trap 'rm -rf "$TEMP_DIR" 2>/dev/null || true' EXIT INT TERM + # Global variables SELECTED_COMPONENTS=() INSTALL_MODE="" @@ -915,6 +918,17 @@ perform_installation() { mkdir -p "$(dirname "$dest")" if curl -fsSL "$url" -o "$dest"; then + # Transform paths for global installation (any non-local path) + # Local paths: .opencode or */.opencode + if [[ "$INSTALL_DIR" != ".opencode" ]] && [[ "$INSTALL_DIR" != *"/.opencode" ]]; then + # Expand tilde and get absolute path for transformation + local expanded_path="${INSTALL_DIR/#\~/$HOME}" + # Transform @.opencode/context/ references to actual install path + sed -i.bak -e "s|@\.opencode/context/|@${expanded_path}/context/|g" \ + -e "s|\.opencode/context|${expanded_path}/context|g" "$dest" 2>/dev/null || true + rm -f "${dest}.bak" 2>/dev/null || true + fi + # Show appropriate message based on whether file existed before if [ "$file_existed" = true ]; then print_success "Updated ${type}: ${id}" @@ -1034,6 +1048,11 @@ main() { case "$1" in --install-dir=*) CUSTOM_INSTALL_DIR="${1#*=}" + # Basic validation - check not empty + if [ -z "$CUSTOM_INSTALL_DIR" ]; then + echo "Error: --install-dir requires a non-empty path" + exit 1 + fi shift ;; --install-dir) diff --git a/scripts/uninstall.sh b/scripts/uninstall.sh new file mode 100755 index 0000000..e05c0f7 --- /dev/null +++ b/scripts/uninstall.sh @@ -0,0 +1,195 @@ +#!/bin/bash +# uninstall.sh - Uninstalls OpenCode Agents + +set -e + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo "" +echo -e "${BLUE}OpenCode Agents Uninstaller${NC}" +echo "==============================" +echo "" + +# Parse arguments +UNINSTALL_TYPE="" +FORCE=false + +while [[ $# -gt 0 ]]; do + case $1 in + --global) + UNINSTALL_TYPE="global" + shift + ;; + --local) + UNINSTALL_TYPE="local" + shift + ;; + --force) + FORCE=true + shift + ;; + --help|-h) + echo "Usage: ./uninstall.sh [options]" + echo "" + echo "Options:" + echo " --global Uninstall from ~/.config/opencode" + echo " --local Uninstall from current directory .opencode/" + echo " --force Skip confirmation prompts" + echo " --help Show this help message" + echo "" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +# Determine uninstall location +if [ -z "$UNINSTALL_TYPE" ]; then + echo "Select uninstall location:" + echo " 1) Local (.opencode/ in current directory)" + echo " 2) Global (~/.config/opencode/)" + echo "" + read -p "Enter your choice [1-2]: " choice + + case $choice in + 1) UNINSTALL_TYPE="local" ;; + 2) UNINSTALL_TYPE="global" ;; + *) + echo -e "${RED}Invalid choice${NC}" + exit 1 + ;; + esac +fi + +# Set target directory +if [ "$UNINSTALL_TYPE" == "global" ]; then + TARGET_DIR="$HOME/.config/opencode" +else + TARGET_DIR="$(pwd)/.opencode" +fi + +echo "" +echo -e "${YELLOW}Uninstall location:${NC} $TARGET_DIR" +echo "" + +# Check if installation exists +if [ ! -f "$TARGET_DIR/.opencode-agents-version" ]; then + echo -e "${YELLOW}⚠️ No OpenCode Agents installation found at: $TARGET_DIR${NC}" + echo "" + echo "This directory may contain other files or a manual installation." + echo "" + + if [ "$FORCE" != "true" ]; then + read -p "Continue with uninstall anyway? (y/N): " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Uninstall cancelled." + exit 0 + fi + fi +else + # Show installation info + echo "Installation details:" + cat "$TARGET_DIR/.opencode-agents-version" + echo "" +fi + +# Confirm uninstall +if [ "$FORCE" != "true" ]; then + echo -e "${RED}⚠️ This will remove all OpenCode Agents files from: $TARGET_DIR${NC}" + echo "" + echo "The following will be removed:" + echo " - $TARGET_DIR/agent/" + echo " - $TARGET_DIR/command/" + echo " - $TARGET_DIR/context/" + echo " - $TARGET_DIR/.opencode-agents-version" + echo "" + read -p "Are you sure you want to uninstall? (y/N): " -n 1 -r + echo "" + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Uninstall cancelled." + exit 0 + fi +fi + +# Perform uninstall +echo "" +echo "Uninstalling..." + +removed_count=0 + +# Remove agent directory +if [ -d "$TARGET_DIR/agent" ]; then + rm -rf "$TARGET_DIR/agent" + echo -e "${GREEN}✓${NC} Removed agents" + removed_count=$((removed_count + 1)) +fi + +# Remove command directory +if [ -d "$TARGET_DIR/command" ]; then + rm -rf "$TARGET_DIR/command" + echo -e "${GREEN}✓${NC} Removed commands" + removed_count=$((removed_count + 1)) +fi + +# Remove context directory +if [ -d "$TARGET_DIR/context" ]; then + rm -rf "$TARGET_DIR/context" + echo -e "${GREEN}✓${NC} Removed context files" + removed_count=$((removed_count + 1)) +fi + +# Remove version file +if [ -f "$TARGET_DIR/.opencode-agents-version" ]; then + rm -f "$TARGET_DIR/.opencode-agents-version" + echo -e "${GREEN}✓${NC} Removed installation metadata" +fi + +# Remove AGENTS.md.new if it exists +if [ -f "$TARGET_DIR/AGENTS.md.new" ]; then + rm -f "$TARGET_DIR/AGENTS.md.new" + echo -e "${GREEN}✓${NC} Removed AGENTS.md.new" +fi + +# Check if directory is now empty +if [ -d "$TARGET_DIR" ]; then + if [ -z "$(ls -A "$TARGET_DIR")" ]; then + read -p "Remove empty directory $TARGET_DIR? (y/N): " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + rmdir "$TARGET_DIR" + echo -e "${GREEN}✓${NC} Removed directory" + fi + else + echo "" + echo -e "${YELLOW}ℹ${NC} Directory not empty. Remaining files:" + ls -la "$TARGET_DIR" + fi +fi + +echo "" +echo -e "${GREEN}✅ Uninstall complete!${NC}" +echo "" + +if [ $removed_count -eq 0 ]; then + echo "No OpenCode Agents files were found to remove." +else + echo "Removed $removed_count component(s)." +fi + +echo "" +echo "To reinstall, run:" +echo " ./install.sh" +echo "" + +exit 0 diff --git a/scripts/validate-context-refs.sh b/scripts/validate-context-refs.sh new file mode 100755 index 0000000..a02ff1b --- /dev/null +++ b/scripts/validate-context-refs.sh @@ -0,0 +1,136 @@ +#!/bin/bash +# validate-context-refs.sh - Validates that all context references follow the strict convention + +set -e + +echo "🔍 Validating context references..." +echo "" + +errors=0 +warnings=0 + +# Check if .opencode directory exists +if [ ! -d ".opencode" ]; then + echo "❌ No .opencode directory found" + echo " Run this script from the repository root" + exit 1 +fi + +# Validate agent files +echo "Checking agent files..." +if [ -d ".opencode/agent" ]; then + while IFS= read -r file; do + rel_file="${file#./}" + + # Check for forbidden dynamic variables + if grep -E '@\$[^0-9]|@\$\{' "$file" > /dev/null 2>&1; then + echo "❌ Dynamic context reference in: $rel_file" + grep -n '@\$' "$file" | head -3 + errors=$((errors + 1)) + fi + + # Check for context references that don't follow convention + # Allow: @.opencode/context/, @AGENTS.md, @.cursorrules, @$1, @$2, etc. + if grep -E '@[^~$]' "$file" | \ + grep -v '@\.opencode/context/' | \ + grep -v '@AGENTS\.md' | \ + grep -v '@\.cursorrules' | \ + grep -v '@\$[0-9]' | \ + grep -v '^#' | \ + grep -v 'email' | \ + grep -v 'mailto' > /dev/null 2>&1; then + + echo "⚠️ Non-standard reference in: $rel_file" + grep -E '@[^~$]' "$file" | \ + grep -v '@\.opencode/context/' | \ + grep -v '@AGENTS\.md' | \ + grep -v '@\.cursorrules' | \ + grep -v '@\$[0-9]' | \ + grep -v '^#' | \ + grep -v 'email' | \ + grep -v 'mailto' | head -2 + warnings=$((warnings + 1)) + fi + done < <(find .opencode/agent -type f -name "*.md" 2>/dev/null) +fi + +# Validate command files +echo "Checking command files..." +if [ -d ".opencode/command" ]; then + while IFS= read -r file; do + rel_file="${file#./}" + + # Check for forbidden dynamic variables + if grep -E '@\$[^0-9]|@\$\{' "$file" > /dev/null 2>&1; then + echo "❌ Dynamic context reference in: $rel_file" + grep -n '@\$' "$file" | head -3 + errors=$((errors + 1)) + fi + + # Check for non-standard references + if grep -E '@[^~$]' "$file" | \ + grep -v '@\.opencode/context/' | \ + grep -v '@AGENTS\.md' | \ + grep -v '@\.cursorrules' | \ + grep -v '@\$[0-9]' | \ + grep -v '^#' | \ + grep -v 'email' > /dev/null 2>&1; then + + echo "⚠️ Non-standard reference in: $rel_file" + warnings=$((warnings + 1)) + fi + done < <(find .opencode/command -type f -name "*.md" 2>/dev/null) +fi + +# Validate context files (they can reference other context files) +echo "Checking context files..." +if [ -d ".opencode/context" ]; then + while IFS= read -r file; do + rel_file="${file#./}" + + # Check for dynamic variables + if grep -E '@\$[^0-9]|@\$\{' "$file" > /dev/null 2>&1; then + echo "❌ Dynamic context reference in: $rel_file" + errors=$((errors + 1)) + fi + + # Check for context cross-references + if grep '@' "$file" | grep -v '@\.opencode/context/' | grep -v '^#' | grep -v 'email' > /dev/null 2>&1; then + echo "⚠️ Context file has non-standard reference: $rel_file" + warnings=$((warnings + 1)) + fi + done < <(find .opencode/context -type f -name "*.md" 2>/dev/null) +fi + +# Check for shell commands with hardcoded paths +echo "Checking for shell commands with paths..." +while IFS= read -r file; do + rel_file="${file#./}" + + if grep '!\`.*\.opencode/context' "$file" > /dev/null 2>&1; then + echo "ℹ️ Shell command with path in: $rel_file" + echo " (Will be transformed during installation)" + fi +done < <(find .opencode -type f -name "*.md" 2>/dev/null) + +# Summary +echo "" +echo "==========================================" +if [ $errors -gt 0 ]; then + echo "❌ Validation failed with $errors error(s) and $warnings warning(s)" + echo "" + echo "Errors must be fixed before installation." + echo "All context references must use: @.opencode/context/{category}/{file}.md" + exit 1 +elif [ $warnings -gt 0 ]; then + echo "⚠️ Validation passed with $warnings warning(s)" + echo "" + echo "Warnings indicate non-standard references that may not work correctly." + echo "Consider updating them to use: @.opencode/context/{category}/{file}.md" + exit 0 +else + echo "✅ All validations passed!" + echo "" + echo "All context references follow the correct convention." + exit 0 +fi