-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Work Unit 003: Agent/Command Embedding & CLI Distribution
1. Behavioral Goal
As a sow repository owner using Claude Code web,
I need all sow agents, commands, prompts, and installation scripts automatically available after cloning,
So that new contributors can start working in ephemeral web VMs without manual setup or plugin installation.
Success Criteria
Users can verify this work unit is complete when:
- Embedded artifacts accessible: Running
sow claude initin any repository creates.claude/agents/,.claude/commands/,.sow/scripts/with all necessary files - Idempotent initialization: Running
sow claude initmultiple times doesn't duplicate files or overwrite customizations - SessionStart hook configured:
.claude/settings.jsonis created or merged with existing hooks preserved - Binary self-contained: The sow CLI binary contains all files needed for web VM operation (no external downloads during init)
- Repository-committed: All distributed files can be committed to git and survive VM recreation
- Minimal size impact: Total embedded content adds <100KB to binary size
2. Existing Code Context
This work unit leverages the proven embed.FS pattern already used successfully in multiple places in the sow codebase:
Prompt Template Embedding (cli/internal/prompts/prompts.go:31-32):
The prompts package demonstrates the exact pattern we'll replicate. It uses //go:embed templates to bundle markdown files into the binary and exposes them via a public FS variable. The templates package then renders these using templates.Render(prompts.FS, "templates/guidance/research.md", nil). This pattern is clean, type-safe, and proven in production.
Schema Embedding (cli/schemas/embed.go:9-10):
The schemas package shows how to embed multiple file types and subdirectories using //go:embed *.cue project/*.cue cue.mod/module.cue. It demonstrates selective file inclusion and directory traversal.
Repository Initialization (cli/internal/sow/sow.go:38-134):
The sow.Init() function provides the blueprint for our claude.Init() implementation. It shows the established pattern for:
- Directory creation with
os.MkdirAll() - File writing with
os.WriteFile() - Error handling with wrapped errors
- Version tracking
- Idempotency checking (lines 46-48 check if already initialized)
Command Structure (cli/cmd/init.go, cli/cmd/project/project.go):
The existing command organization demonstrates the cobra pattern for both simple commands (init.go:12-38) and command groups (project/project.go). New commands should follow this structure: use cmdutil.GetContext(cmd.Context()) to access the sow context, implement business logic in an internal package, and provide clear error messages.
Existing Agents and Commands (.claude/agents/*.md, .claude/commands/*.md):
These 5 agent files (implementer, planner, reviewer, researcher, decomposer) and 9 command files already exist in the repository. We'll copy them into cli/internal/claude/ to embed them, then distribute them back to repositories via sow claude init.
Reference List
Key Files to Study:
cli/internal/prompts/prompts.go:31-32- Embedding pattern with//go:embedcli/internal/sow/sow.go:38-134- Init function pattern (directory creation, file writing)cli/schemas/embed.go:9-10- Multi-file embedding patterncli/cmd/init.go:12-38- Simple command structurecli/cmd/project/project.go:8-38- Command group structure.claude/agents/- 5 agent files to embed (implementer.md, planner.md, reviewer.md, researcher.md, decomposer.md).claude/commands/- 9 command files to embed (bootstrap.md, create-adr.md, design-doc.md, fix-bug.md, implement-feature.md, index.md, project-new.md, project.md, sow-greet.md)
Rendering Infrastructure (exists, will be reused):
cli/internal/templates/renderer.go:54-91-Render(embedFS embed.FS, path string, data interface{})function
3. Design Context
Reference: .sow/knowledge/designs/claude-code-web-integration.md section 2 (Agent Distribution)
The design document specifies that all files needed for Claude Code web integration must be embedded in the CLI binary to achieve a zero-dependency distribution model. This work unit implements the "Agent Distribution" component (design section 2) which has two key requirements:
- Everything embedded: Agents, commands, prompts, scripts, and hook templates must all be embedded using
//go:embeddirectives in a newcli/internal/claudepackage - Single command setup: The
sow claude initcommand must create all necessary repository structure (.claude/agents/,.claude/commands/,.sow/scripts/) and merge hook configuration idempotently
The design emphasizes idempotency (section 2, "Key Design Decisions"): running sow claude init multiple times must be safe. This means:
- Skip files that already exist (don't overwrite user customizations)
- Merge
.claude/settings.jsonhooks array (preserve existing hooks) - Update only what's missing
The design also specifies minimal size impact (<5MB total for all web VM features, section "Performance Targets"). This work unit contributes <100KB of embedded markdown and shell script files.
4. Implementation Approach
Package Structure
Create a new package at cli/internal/claude/ with this structure:
cli/internal/claude/
├── claude.go # Init() function and embed directives
├── agents/ # Copied from .claude/agents/
│ ├── implementer.md
│ ├── planner.md
│ ├── reviewer.md
│ ├── researcher.md
│ └── decomposer.md
├── commands/ # Copied from .claude/commands/
│ ├── bootstrap.md
│ ├── create-adr.md
│ ├── design-doc.md
│ ├── fix-bug.md
│ ├── implement-feature.md
│ ├── index.md
│ ├── project-new.md
│ ├── project.md
│ └── sow-greet.md
├── prompts/ # New templates (TBD in work unit 005)
│ └── general.md
├── scripts/ # New scripts (TBD in work unit 006)
│ ├── install-sow.sh
│ └── session-start-prompt.sh
└── config/ # New configuration (TBD in work unit 006)
└── settings.json
Embedding Pattern
Follow the established pattern from prompts.go:
// cli/internal/claude/claude.go
package claude
import (
"embed"
"encoding/json"
"fmt"
"io/fs"
"os"
"path/filepath"
)
//go:embed agents commands prompts scripts config
var FS embed.FS
func Init(repoRoot string) error {
// Similar structure to sow.Init()
// 1. Create directories
// 2. Copy files from FS to repository
// 3. Merge settings.json
// 4. Handle idempotency
}Init Function Logic
The claude.Init() function should:
-
Create directory structure:
.claude/agents/.claude/commands/.sow/scripts/
-
Copy embedded files (if they don't exist):
- Iterate through
FSusingfs.WalkDir - For each file, check if destination exists
- Skip if exists (idempotency)
- Otherwise, copy from embedded FS to repository
- Iterate through
-
Merge
.claude/settings.json:- Read existing settings.json if it exists
- Parse JSON
- Append SessionStart hook if not present
- Write merged result
- Handle case where settings.json doesn't exist (create new)
-
Return clear errors for edge cases:
- Not in git repository
- Permission errors
- Invalid existing settings.json
CLI Command
Create cli/cmd/claude.go with command group:
func NewClaudeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "claude",
Short: "Manage Claude Code integration",
}
cmd.AddCommand(newInitCmd())
return cmd
}
func newInitCmd() *cobra.Command {
// Similar to existing init.go pattern
// Call claude.Init(ctx.RepoRoot())
}Register in root.go via rootCmd.AddCommand(NewClaudeCmd()).
Settings.json Merge Strategy
The SessionStart hook configuration must be merged carefully:
type Settings struct {
Hooks map[string][]Hook `json:"hooks,omitempty"`
// ... other fields preserved
}
type Hook struct {
Matcher string `json:"matcher"`
Hooks []HookItem `json:"hooks"`
}
type HookItem struct {
Type string `json:"type"`
Command string `json:"command"`
}Merge algorithm:
- Load existing settings.json (or create empty struct)
- Check if SessionStart hook with matcher "startup" exists
- If not, append our hook
- Preserve all other hooks and settings
- Marshal back to JSON with indentation
5. Dependencies
None - This is an independent work unit.
While the design document describes a complete system with 5 components (installation, agent distribution, Claude Code integration, GitHub integration, project initialization), this work unit focuses solely on the embedding infrastructure and sow claude init command. The embedded files (install scripts, prompt templates) will be created in later work units but can have placeholder files initially.
6. Acceptance Criteria
Functional Requirements
The following behaviors must be demonstrable:
-
Embedding works:
- Run
go buildsuccessfully - Binary size increases by <100KB
- Embedded FS accessible programmatically (unit test can read files)
- Run
-
Init creates structure:
cd /path/to/repository sow claude init # Verify directories created: ls .claude/agents/ # 5 agent files ls .claude/commands/ # 9 command files ls .sow/scripts/ # 2 script files (or placeholders)
-
Idempotency verified:
sow claude init # First run sow claude init # Second run - no errors, no duplicates git status # No changes
-
Settings merge works:
# Create settings with existing hook echo '{"hooks":{"OtherHook":[...]}}' > .claude/settings.json sow claude init # Verify both hooks present in settings.json cat .claude/settings.json # Should have OtherHook + SessionStart
-
Error handling:
cd /tmp # Not a git repo sow claude init # Should error clearly: "not in git repository"
Non-Functional Requirements
- Performance:
sow claude initcompletes in <1 second - Size: Embedded content adds <100KB to binary
- Compatibility: Existing sow workflows unchanged (no breaking changes)
7. Testing Strategy
Test-Driven Development Approach
Write tests before implementation in this order:
Phase 1: Embedding Tests
// cli/internal/claude/claude_test.go
func TestFS_ContainsAgents(t *testing.T)
func TestFS_ContainsCommands(t *testing.T)
func TestFS_FileReadable(t *testing.T)Phase 2: Init Function Tests
func TestInit_CreatesDirectories(t *testing.T)
func TestInit_CopiesAgentFiles(t *testing.T)
func TestInit_CopiesCommandFiles(t *testing.T)
func TestInit_Idempotent(t *testing.T)
func TestInit_NotInGitRepo_ReturnsError(t *testing.T)Phase 3: Settings Merge Tests
func TestInit_CreatesSettingsJSON(t *testing.T)
func TestInit_MergesExistingSettings(t *testing.T)
func TestInit_PreservesExistingHooks(t *testing.T)
func TestInit_InvalidJSON_ReturnsError(t *testing.T)Phase 4: CLI Command Tests
// cli/cmd/claude_test.go
func TestClaudeInit_Success(t *testing.T)
func TestClaudeInit_OutputMessage(t *testing.T)Test Patterns
Follow existing test conventions:
- Table-driven tests where appropriate (see
cli/cmd/worktree_test.go) - Use
t.TempDir()for filesystem tests (seecli/internal/sow/sow.gotests) - Mock git repositories with
.gitdirectory - Use
testdata/for fixture files if needed
Integration Validation
Manual verification steps (not automated):
- Build sow CLI:
go build -o sow ./cli - Create test repository:
mkdir test-repo && cd test-repo && git init - Run init:
../sow claude init - Verify structure:
tree .claude .sow/scripts - Run again:
../sow claude init(verify no errors) - Commit and verify:
git status(all files tracked)
8. Implementation Notes
Binary Size Monitoring
The design document specifies <5MB total increase for all web VM features. This work unit should contribute <100KB. To monitor:
# Before changes
go build -o sow-before ./cli
ls -lh sow-before
# After changes
go build -o sow-after ./cli
ls -lh sow-after
# Compare
du -h sow-before sow-afterIf size exceeds 100KB, investigate:
- Are test files being embedded? (exclude
_test.go) - Are hidden files being embedded? (exclude
.DS_Store) - Use
//go:embedselectively to exclude unnecessary files
Idempotency Implementation
Two approaches for file copying idempotency:
Option 1: Check before copy
destPath := filepath.Join(repoRoot, ".claude/agents", filename)
if _, err := os.Stat(destPath); err == nil {
continue // File exists, skip
}
// Copy fileOption 2: Use OpenFile with O_EXCL
f, err := os.OpenFile(destPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
if os.IsExist(err) {
continue // File exists, skip
}
return err
}
// Write to fRecommendation: Option 1 (simpler, clearer intent)
Settings.json Merge Algorithm
Use the standard library encoding/json package:
// Minimal example (expand with error handling)
existing := Settings{}
data, err := os.ReadFile(settingsPath)
if err == nil {
json.Unmarshal(data, &existing)
}
// Add SessionStart hook if not present
if !hasSessionStartHook(existing) {
existing.Hooks["SessionStart"] = append(
existing.Hooks["SessionStart"],
sessionStartHook,
)
}
// Write back with indentation
data, _ = json.MarshalIndent(existing, "", " ")
os.WriteFile(settingsPath, data, 0644)Handle edge cases:
- Missing
hooksfield (initialize map) - Missing
SessionStartarray (initialize slice) - Malformed JSON (return clear error)
Directory Permissions
Follow existing sow conventions:
- Directories:
0755(rwxr-xr-x) - Files:
0644(rw-r--r--) - No executable scripts yet (work unit 006 will handle install-sow.sh)
Error Messages
Provide actionable error messages following existing sow patterns:
// Good (existing pattern from sow.Init)
return fmt.Errorf("failed to create .claude directory: %w", err)
// Good (from github.go)
return fmt.Errorf("not in git repository")
// Avoid
return err // No contextPlaceholder Files
For files that will be implemented in later work units, create minimal placeholders:
cli/internal/claude/prompts/general.md:
# General Mode Operator Prompt
(Implementation pending - Work Unit 005)cli/internal/claude/scripts/install-sow.sh:
#!/bin/bash
# Installation script
# (Implementation pending - Work Unit 006)
exit 0This allows the embedding and distribution infrastructure to be tested before the actual content is finalized.
Code Organization
Keep concerns separated:
- claude.go: Init function, file operations, settings merge
- claude_test.go: All tests for Init function
- claude/: Only data files (markdown, scripts, JSON)
- cmd/claude.go: CLI command definition only (thin wrapper)
Avoid putting business logic in cmd/ - follow the pattern where cmd/init.go calls sow.Init().
Summary
This work unit establishes the foundation for Claude Code web integration by embedding all necessary files in the sow CLI binary and providing a single command (sow claude init) to distribute them to repositories. The implementation follows proven patterns from the existing codebase (embed.FS, Init function structure) and maintains backward compatibility. Future work units will build on this foundation by implementing the actual scripts and prompts, but the distribution infrastructure will be complete and tested.
Estimated Effort: 2-3 days (includes TDD, testing, documentation)
Risk Level: Low (proven patterns, no breaking changes)
Value: High (enables all other web VM features)