diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..80bfc4c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,91 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**Plasma Compose** is a dependency composition tool that operates as a plugin for the Launchr CLI framework. It enables developers to manage project dependencies through a declarative `plasma-compose.yaml` file, fetching and merging dependencies from Git repositories and HTTP archives with sophisticated conflict resolution strategies. + +## Development Commands + +### Build and Test +```bash +# Install dependencies +make deps + +# Run tests +make test + +# Build application +make build + +# Run linter +make lint + +# Complete build process (deps + test + build) +make all + +# Install to GOPATH +make install +``` + +### Debug Builds +Set `DEBUG=1` environment variable for development builds with debug symbols. + +## Architecture + +### Core Components + +1. **Plugin Integration** (`plugin.go`): Launchr CLI plugin that registers four actions: `compose`, `compose:add`, `compose:update`, `compose:delete` + +2. **Composition Engine** (`compose/compose.go`): Main orchestrator managing working directories (`.compose/packages`, `.compose/build`) and cleanup + +3. **Download Manager** (`compose/downloadManager.go`): Handles Git and HTTP sources with recursive dependency resolution and keyring authentication + +4. **Builder** (`compose/builder.go`): Implements filesystem merging with topological sorting for dependency ordering and multiple merge strategies + +5. **Configuration** (`compose/yaml.go`): Parses `plasma-compose.yaml` files and defines data structures + +### Key Dependencies + +- **Launchr framework**: CLI framework integration +- **go-git**: Git repository operations +- **huh**: Interactive TUI forms +- **keyring**: Secure credential management +- **topsort**: Dependency ordering +- **yaml.v3**: Configuration parsing + +### Merge Strategies + +Four conflict resolution strategies: +- `overwrite-local-file`: Package files override local files +- `remove-extra-local-files`: Remove local files not in packages +- `ignore-extra-package-files`: Skip package files not present locally +- `filter-package-files`: Include only specific paths from packages + +### Working Directories + +- `.compose/packages`: Downloaded packages storage +- `.compose/build`: Final composed filesystem output +- `plasma-compose.yaml`: Project dependency configuration + +## Configuration Structure + +```yaml +name: project-name +dependencies: + - name: package-name + source: + type: git|http + url: https://github.com/user/repo.git + ref: branch-or-tag + strategy: + - name: overwrite-local-file + path: ["specific/paths"] +``` + +## Requirements + +- Go 1.24.0+ +- golangci-lint v1.64.5 for linting +- CGO disabled for static builds \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..85d5529 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,89 @@ +# Gemini Code Assistant Project Overview + +## Project Description + +**Plasma Compose** is a dependency composition tool that operates as a plugin for the Launchr CLI framework. It enables developers to manage project dependencies through a declarative `plasma-compose.yaml` file, fetching and merging dependencies from Git repositories and HTTP archives with sophisticated conflict resolution strategies. + +## Development Commands + +### Build and Test +```bash +# Install dependencies +make deps + +# Run tests +make test + +# Build application +make build + +# Run linter +make lint + +# Complete build process (deps + test + build) +make all + +# Install to GOPATH +make install +``` + +### Debug Builds +Set `DEBUG=1` environment variable for development builds with debug symbols. + +## Architecture + +### Core Components + +1. **Plugin Integration** (`plugin.go`): Launchr CLI plugin that registers four actions: `compose`, `compose:add`, `compose:update`, `compose:delete` + +2. **Composition Engine** (`internal/composer/composer.go`): Main orchestrator managing working directories (`.compose/packages`, `.compose/build`) and cleanup + +3. **Download Manager** (`internal/downloader/downloader.go`): Handles Git and HTTP sources with recursive dependency resolution and keyring authentication + +4. **Builder** (`internal/builder/builder.go`): Implements filesystem merging with topological sorting for dependency ordering and multiple merge strategies + +5. **Configuration** (`internal/config/yaml.go`): Parses `plasma-compose.yaml` files and defines data structures + +### Key Dependencies + +- **Launchr framework**: CLI framework integration +- **go-git**: Git repository operations +- **huh**: Interactive TUI forms +- **keyring**: Secure credential management +- **topsort**: Dependency ordering +- **yaml.v3**: Configuration parsing + +### Merge Strategies + +Four conflict resolution strategies: +- `overwrite-local-file`: Package files override local files +- `remove-extra-local-files`: Remove local files not in packages +- `ignore-extra-package-files`: Skip package files not present locally +- `filter-package-files`: Include only specific paths from packages + +### Working Directories + +- `.compose/packages`: Downloaded packages storage +- `.compose/build`: Final composed filesystem output +- `plasma-compose.yaml`: Project dependency configuration + +## Configuration Structure + +```yaml +name: project-name +dependencies: + - name: package-name + source: + type: git|http + url: https://github.com/user/repo.git + ref: branch-or-tag + strategy: + - name: overwrite-local-file + path: ["specific/paths"] +``` + +## Requirements + +- Go 1.24.0+ +- golangci-lint v1.64.5 for linting +- CGO disabled for static builds \ No newline at end of file diff --git a/plasmactl-compose.md b/plasmactl-compose.md new file mode 100644 index 0000000..f4e9a53 --- /dev/null +++ b/plasmactl-compose.md @@ -0,0 +1,601 @@ +# Plasma Compose Documentation + +## Table of Contents + +1. [Overview](#overview) +2. [Configuration Format](#configuration-format) +3. [Composition Process](#composition-process) +4. [Layer Ordering](#layer-ordering) +5. [Merge Strategies](#merge-strategies) +6. [Dependency Resolution](#dependency-resolution) +7. [File System Operations](#file-system-operations) +8. [Parser Implementation Guide](#parser-implementation-guide) +9. [Working Directories](#working-directories) +10. [Error Handling](#error-handling) +11. [Examples](#examples) + +## Overview + +Plasma Compose is a dependency composition tool that merges multiple package sources into a unified filesystem structure. It operates by: + +1. **Reading** a `plasma-compose.yaml` configuration file +2. **Fetching** dependencies from Git repositories or HTTP archives +3. **Resolving** recursive dependencies using topological sorting +4. **Merging** files using configurable conflict resolution strategies +5. **Outputting** a composed filesystem to `.compose/build/` + +### Key Concepts + +- **Package**: A unit of code/configuration fetched from a source +- **Dependency**: A package that your project depends on +- **Strategy**: Rules for handling file conflicts during merge +- **Layer**: The order in which dependencies are applied +- **Composition**: The final merged result of all dependencies + +## Configuration Format + +### Basic Structure + +```yaml +name: project-name +version: "1.0.0" # Optional +dependencies: + - name: package-name + source: + type: git|http + url: https://example.com/repo.git + ref: branch-or-tag-or-commit # For git sources + strategy: + - name: strategy-name + path: ["path/pattern"] +``` + +### Source Types + +#### Git Sources +```yaml +source: + type: git + url: https://github.com/user/repo.git + ref: main # branch name + # ref: v1.2.3 # tag + # ref: abc123def # commit hash +``` + +#### HTTP Sources +```yaml +source: + type: http + url: https://example.com/archive.tar.gz + # Supports: .tar.gz, .tar.bz2, .zip +``` + +### Strategy Configuration + +```yaml +strategy: + - name: overwrite-local-file + path: + - "specific/file.txt" + - "directory/**" + - name: ignore-extra-package-files + path: + - "temp/**" + - "*.log" +``` + +## Composition Process + +### Phase 1: Configuration Parsing + +1. **Load** `plasma-compose.yaml` +2. **Validate** syntax and required fields +3. **Extract** project metadata (name, version) +4. **Parse** dependency definitions +5. **Validate** source URLs and references + +### Phase 2: Dependency Resolution + +1. **Build** dependency graph from direct dependencies +2. **Fetch** each dependency's `plasma-compose.yaml` (if exists) +3. **Resolve** recursive dependencies +4. **Perform** topological sort to determine layer order +5. **Detect** circular dependencies (error condition) + +### Phase 3: Package Fetching + +1. **Create** working directories (`.compose/packages/`) +2. **Download** packages in parallel where possible +3. **Verify** package integrity (Git refs, HTTP checksums) +4. **Extract** archives (for HTTP sources) +5. **Cache** packages for subsequent runs + +### Phase 4: Filesystem Composition + +1. **Initialize** build directory (`.compose/build/`) +2. **Apply** packages in topological order +3. **Execute** merge strategies for each file +4. **Handle** conflicts according to strategy rules +5. **Preserve** file permissions and metadata +6. **Create** symbolic links as needed + +## Layer Ordering + +### Topological Sort Algorithm + +Dependencies are ordered using topological sorting to ensure: +- **Parent dependencies** are applied before child dependencies +- **Circular dependencies** are detected and rejected +- **Consistent ordering** across multiple runs + +### Example Layer Order + +```yaml +# Input configuration +dependencies: + - name: app-core + dependencies: [base-lib] + - name: app-ui + dependencies: [app-core, ui-lib] + - name: base-lib + dependencies: [] + - name: ui-lib + dependencies: [base-lib] +``` + +**Resolved Layer Order:** +1. `base-lib` (no dependencies) +2. `ui-lib` (depends on base-lib) +3. `app-core` (depends on base-lib) +4. `app-ui` (depends on app-core, ui-lib) + +### Layer Processing Rules + +1. **Base Layer**: Local project files (always first) +2. **Dependency Layers**: Applied in topological order +3. **Strategy Application**: Per-file, per-layer basis +4. **Conflict Resolution**: Later layers win by default + +## Merge Strategies + +### 1. overwrite-local-file + +**Behavior**: Package files replace existing files +**When Applied**: File exists in both package and target +**Result**: Package version is kept + +```yaml +strategy: + - name: overwrite-local-file + path: + - "config/settings.yaml" + - "templates/**/*.html" +``` + +**Processing Logic**: +``` +IF file exists in target AND file exists in package: + IF path matches strategy pattern: + COPY package file to target (overwrite) + ELSE: + KEEP target file (default behavior) +``` + +### 2. remove-extra-local-files + +**Behavior**: Remove local files not present in package +**When Applied**: File exists in target but not in package +**Result**: File is deleted from target + +```yaml +strategy: + - name: remove-extra-local-files + path: + - "generated/**" + - "cache/**" +``` + +**Processing Logic**: +``` +FOR each file in target: + IF file does not exist in package: + IF path matches strategy pattern: + DELETE file from target +``` + +### 3. ignore-extra-package-files + +**Behavior**: Skip package files not present locally +**When Applied**: File exists in package but not in target +**Result**: Package file is not copied + +```yaml +strategy: + - name: ignore-extra-package-files + path: + - "docs/**" + - "examples/**" +``` + +**Processing Logic**: +``` +FOR each file in package: + IF file does not exist in target: + IF path matches strategy pattern: + SKIP file (do not copy) + ELSE: + COPY file to target (default behavior) +``` + +### 4. filter-package-files + +**Behavior**: Only include specific paths from package +**When Applied**: All package files are filtered +**Result**: Only matching files are copied + +```yaml +strategy: + - name: filter-package-files + path: + - "src/**/*.go" + - "config/production.yaml" +``` + +**Processing Logic**: +``` +FOR each file in package: + IF path matches strategy pattern: + COPY file to target + ELSE: + SKIP file +``` + +### Strategy Priority and Combination + +1. **Multiple Strategies**: Applied in order of definition +2. **Path Matching**: First matching strategy wins +3. **Default Behavior**: Copy all files if no strategy matches +4. **Strategy Inheritance**: Child dependencies inherit parent strategies + +## Dependency Resolution + +### Resolution Algorithm + +``` +1. START with direct dependencies from plasma-compose.yaml +2. FOR each dependency: + a. FETCH dependency source + b. CHECK for plasma-compose.yaml in dependency + c. IF found, EXTRACT nested dependencies + d. ADD to dependency graph +3. REPEAT until no new dependencies found +4. PERFORM topological sort on complete graph +5. RETURN ordered dependency list +``` + +### Circular Dependency Detection + +``` +FUNCTION detectCircularDependencies(graph): + visited = {} + recursionStack = {} + + FOR each node in graph: + IF detectCircularDependenciesUtil(node, visited, recursionStack): + THROW CircularDependencyError +``` + +### Version Conflict Resolution + +When multiple versions of the same package are required: + +1. **Explicit Version**: Use version specified in root `plasma-compose.yaml` +2. **Latest Version**: Use highest semantic version if no conflicts +3. **First Encountered**: Use first version found in dependency tree +4. **Error**: Throw error if incompatible versions required + +## File System Operations + +### File Processing Pipeline + +``` +FOR each dependency in topological order: + FOR each file in dependency: + 1. CALCULATE relative path + 2. DETERMINE target path in build directory + 3. CHECK if target file exists + 4. APPLY merge strategy + 5. EXECUTE file operation (copy/skip/delete) + 6. PRESERVE file metadata +``` + +### File Metadata Preservation + +- **Permissions**: Unix file permissions (rwx) +- **Timestamps**: Modified/accessed times +- **Symbolic Links**: Link targets and properties +- **Extended Attributes**: Platform-specific metadata + +### Path Resolution + +``` +FUNCTION resolvePath(packagePath, targetPath): + relativePath = makeRelative(packagePath) + absoluteTarget = join(buildDirectory, relativePath) + RETURN absoluteTarget +``` + +## Parser Implementation Guide + +### Configuration Parser + +```go +type PlasmaConfig struct { + Name string `yaml:"name"` + Version string `yaml:"version,omitempty"` + Dependencies []Dependency `yaml:"dependencies"` +} + +type Dependency struct { + Name string `yaml:"name"` + Source Source `yaml:"source"` +} + +type Source struct { + Type string `yaml:"type"` + URL string `yaml:"url"` + Ref string `yaml:"ref,omitempty"` + Strategy []Strategy `yaml:"strategy,omitempty"` +} + +type Strategy struct { + Name string `yaml:"name"` + Path []string `yaml:"path,omitempty"` +} +``` + +### Dependency Graph + +```go +type DependencyGraph struct { + nodes map[string]*Node + edges map[string][]string +} + +type Node struct { + Name string + Dependencies []string + Source Source + Resolved bool +} + +func (g *DependencyGraph) AddDependency(name, parent string) { + if g.edges == nil { + g.edges = make(map[string][]string) + } + g.edges[parent] = append(g.edges[parent], name) +} + +func (g *DependencyGraph) TopologicalSort() ([]string, error) { + // Kahn's algorithm implementation + // ... +} +``` + +### Strategy Engine + +```go +type StrategyEngine struct { + strategies map[string]StrategyHandler +} + +type StrategyHandler interface { + Apply(source, target string, paths []string) error + ShouldApply(filePath string, patterns []string) bool +} + +func (e *StrategyEngine) ProcessFile(file string, strategies []Strategy) error { + for _, strategy := range strategies { + handler := e.strategies[strategy.Name] + if handler.ShouldApply(file, strategy.Path) { + return handler.Apply(file, buildPath, strategy.Path) + } + } + // Default behavior: copy file + return copyFile(file, buildPath) +} +``` + +### Path Matching + +```go +func matchPath(filePath string, patterns []string) bool { + for _, pattern := range patterns { + if matched, _ := filepath.Match(pattern, filePath); matched { + return true + } + // Support for ** glob patterns + if strings.Contains(pattern, "**") { + if matchGlob(filePath, pattern) { + return true + } + } + } + return false +} +``` + +## Working Directories + +### Directory Structure + +``` +project-root/ +├── plasma-compose.yaml # Main configuration +├── .compose/ # Working directory +│ ├── packages/ # Downloaded packages +│ │ ├── package-1/ # Individual package contents +│ │ ├── package-2/ +│ │ └── ... +│ ├── build/ # Final composed output +│ │ ├── src/ +│ │ ├── config/ +│ │ └── ... +│ └── cache/ # Cached metadata +│ ├── dependency-graph.json +│ ├── package-checksums.json +│ └── ... +└── src/ # Original project files +``` + +### Cache Management + +- **Package Cache**: Avoid re-downloading unchanged packages +- **Dependency Graph Cache**: Speed up resolution on subsequent runs +- **Checksum Verification**: Ensure package integrity +- **TTL Expiration**: Configurable cache lifetime + +## Error Handling + +### Common Error Types + +1. **Configuration Errors** + - Invalid YAML syntax + - Missing required fields + - Invalid source URLs + +2. **Dependency Errors** + - Circular dependencies + - Unresolvable dependencies + - Version conflicts + +3. **Network Errors** + - Git clone/fetch failures + - HTTP download failures + - Authentication failures + +4. **File System Errors** + - Permission denied + - Disk space exhausted + - Path length limits + +### Error Recovery + +```go +type CompositionError struct { + Phase string + Package string + Cause error +} + +func (e *CompositionError) Error() string { + return fmt.Sprintf("composition failed in %s phase for package %s: %v", + e.Phase, e.Package, e.Cause) +} + +// Recovery strategies +func recoverFromError(err error) error { + switch e := err.(type) { + case *NetworkError: + return retryWithBackoff(e.Operation, 3) + case *FileSystemError: + return cleanupAndRetry(e.Path) + default: + return err + } +} +``` + +## Examples + +### Basic Composition + +```yaml +name: my-app +dependencies: + - name: base-framework + source: + type: git + url: https://github.com/org/framework.git + ref: v2.1.0 +``` + +**Result**: All files from `base-framework` v2.1.0 are copied to build directory. + +### Selective File Inclusion + +```yaml +name: my-app +dependencies: + - name: ui-components + source: + type: git + url: https://github.com/org/ui-lib.git + ref: main + strategy: + - name: filter-package-files + path: + - "components/**/*.vue" + - "styles/**/*.css" +``` + +**Result**: Only Vue components and CSS files are included from ui-components. + +### Conflict Resolution + +```yaml +name: my-app +dependencies: + - name: config-package + source: + type: git + url: https://github.com/org/configs.git + ref: production + strategy: + - name: overwrite-local-file + path: + - "config/database.yaml" + - name: ignore-extra-package-files + path: + - "config/development.yaml" +``` + +**Result**: +- `config/database.yaml` from package overwrites local version +- `config/development.yaml` from package is ignored +- Other config files follow default merge behavior + +### Multi-Layer Composition + +```yaml +name: enterprise-app +dependencies: + - name: core-platform + source: + type: git + url: https://github.com/corp/platform.git + ref: v3.0.0 + - name: auth-module + source: + type: git + url: https://github.com/corp/auth.git + ref: v1.5.2 + strategy: + - name: overwrite-local-file + path: ["middleware/**"] + - name: monitoring + source: + type: http + url: https://releases.corp.com/monitoring-v2.1.0.tar.gz + strategy: + - name: filter-package-files + path: ["agents/**", "config/monitoring.yaml"] +``` + +**Processing Order**: +1. Local project files (base layer) +2. `core-platform` v3.0.0 (foundational layer) +3. `auth-module` v1.5.2 (middleware overrides) +4. `monitoring` v2.1.0 (selective inclusion) + +This documentation provides a complete reference for implementing parsers and tools that work with the Plasma Compose system. \ No newline at end of file