Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .designs/af-5836e28c/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# API & Interface Design

## Summary
The core interface change is how `agent-gen` resolves the af source tree for template placement. The CLI interface (`af formula agent-gen <name>`) must remain unchanged, but internally the command needs to resolve two distinct paths: the factory root (for workspace artifacts) and the af source root (for .md.tmpl files). The resolution should follow the existing `AF_SOURCE_ROOT` pattern already used by mcpstore.

## Constraint Check
- [x] Templates in AF_SRC: This dimension directly addresses where templates land
- [x] Workspace in factory root: API changes don't affect workspace path logic
- [x] quickstart.sh single entry: CLI interface stays the same
- [x] --delete cleans both: --delete path must also resolve source root
- [x] -o works without source tree: -o never writes files, unaffected
- [x] Self-hosted case: Factory root with agentfactory go.mod IS the source root
- [x] External project case: AF_SOURCE_ROOT provides the path
- [x] Reuse AF_SOURCE_ROOT: Yes, extending existing pattern
- [x] No CLI breaking changes: Interface stays identical

## Options Explored

### Option 1: Shared `config.ResolveSourceRoot(factoryRoot)` function
- **Description**: Extract source root resolution from mcpstore into `internal/config/` as a shared function. The function checks: (1) factory root has `go.mod` with "agentfactory" → use factory root, (2) build-time sourceRoot variable, (3) `AF_SOURCE_ROOT` env var. `agent-gen` calls this to determine where to write `.md.tmpl` files.
- **Constraint Compliance**: All constraints pass
- **Pros**: Reuses existing pattern; single source of truth for source root resolution; testable in isolation; no CLI changes needed
- **Cons**: Requires threading build-time sourceRoot from cmd/af/main.go to config package (new package-level var or init function)
- **Effort**: Medium
- **Reversibility**: Easy — function is additive, old behavior is still available

### Option 2: New `--source-root` flag on agent-gen
- **Description**: Add explicit `--source-root` flag to agent-gen that overrides the template output directory.
- **Constraint Compliance**: Technically passes all, but violates the spirit of "no CLI changes" and "reuse AF_SOURCE_ROOT"
- **Pros**: Explicit, easy to understand
- **Cons**: Another flag to manage; doesn't help agent-gen-all.sh unless flag is threaded through; parallel to AF_SOURCE_ROOT rather than reusing it
- **Effort**: Low
- **Reversibility**: Easy
- REJECTED: Violates "reuse existing AF_SOURCE_ROOT pattern" — creates parallel mechanism

### Option 3: Auto-detect via `go.mod` walk-up
- **Description**: Walk up from the `af` binary's location to find the source tree by looking for `go.mod` with "agentfactory".
- **Constraint Compliance**: Fails for installed binaries where source tree isn't at binary location
- **Pros**: Zero configuration needed
- **Cons**: Fragile — binary may be in `~/.local/bin/`, not in source tree; doesn't work for go-installed binaries
- **Effort**: Low
- **Reversibility**: Easy
- REJECTED: Violates self-hosted + external case constraints — binary location != source location

## Recommendation
**Option 1**: Shared `config.ResolveSourceRoot()` function. It aligns with the existing `AF_SOURCE_ROOT` pattern, requires no CLI changes, and handles both self-hosted and external cases.

### Function Signature
```go
// config/source_root.go
func ResolveSourceRoot(factoryRoot string) (string, error)
```

Resolution order:
1. If `factoryRoot` contains `go.mod` referencing "agentfactory" → return factoryRoot
2. If build-time `sourceRoot` is set → return sourceRoot
3. If `AF_SOURCE_ROOT` env var is set → return its value
4. Return error with message listing all three paths checked

### Changes to formula.go
```go
// In runFormulaAgentGen(), after line 109 (factory root found):
srcRoot, err := config.ResolveSourceRoot(root)
if err != nil {
return fmt.Errorf("cannot determine af source tree for template placement: %w\n"+
"Set AF_SOURCE_ROOT to the agentfactory source directory", err)
}

// Line 200 changes from:
tmplDir := filepath.Join(root, "internal", "templates", "roles")
// to:
tmplDir := filepath.Join(srcRoot, "internal", "templates", "roles")
```

## Dependencies Produced
- config.ResolveSourceRoot() must be available before formula.go can use it
- Build-time sourceRoot variable must be accessible from config package
- agent-gen-all.sh must export AF_SOURCE_ROOT=$AF_SRC before calling agent-gen

## Risks Identified
- **Build-time var threading**: sourceRoot is set in cmd/af/main.go via ldflags; need to make it accessible to config package. Severity: Low. Mitigation: Package-level setter function.
- **go.mod detection false positive**: A target project could theoretically have a go.mod referencing "agentfactory" as a dependency. Severity: Low. Mitigation: Check for module path match, not just any reference.
29 changes: 29 additions & 0 deletions .designs/af-5836e28c/conflicts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Cross-Dimension Conflict Matrix

| | API | Data | UX | Scale | Security | Integration |
|--------------|-----|------|----|-------|----------|-------------|
| **API** | - | ○ | ○ | ○ | ○ | ○ |
| **Data** | ○ | - | ○ | ○ | ○ | ○ |
| **UX** | ○ | ○ | - | ○ | ⚠ | ○ |
| **Scale** | ○ | ○ | ○ | - | ○ | ○ |
| **Security** | ○ | ○ | ⚠ | ○ | - | ○ |
| **Integration** | ○ | ○ | ○ | ○ | ○ | - |

Legend: ○ No conflict, ⚠ Tension (trade-off needed), ✗ Direct conflict (resolution required)

## Summary
This is a straightforward design with minimal cross-dimension conflicts. The only tension is between Security (validate all paths) and UX (keep errors minimal/friendly). No direct conflicts exist.

## Tension: Security vs UX

- **Nature**: Security recommends validating go.mod for ALL source root resolution paths (including AF_SOURCE_ROOT). UX recommends friendly error messages and minimal friction. If validation is too strict, users with legitimate but non-standard setups (e.g., forked repos with different module names) get blocked with no workaround.
- **Impact**: Strict validation could reject valid AF_SOURCE_ROOT values that point to a valid source tree but with a renamed go.mod module.
- **Resolution Options**:
1. **Security wins**: Always require go.mod with "agentfactory" in module path. No exceptions. Users with renamed forks must change their module name or contribute upstream.
2. **UX wins**: Accept any AF_SOURCE_ROOT without validation. Users are trusted to set it correctly.
3. **Hybrid**: Validate go.mod when auto-detecting (factory root check), but trust explicit AF_SOURCE_ROOT if `internal/templates/roles/` directory exists there. The explicit env var implies user intent.
- **Chosen Resolution**: Option 1 (Security wins) because:
- The forked-repo scenario is extremely rare for this tool
- Writing templates to the wrong directory is the exact problem we're solving
- The validation cost is one file read
- If someone really needs to override, they can add "agentfactory" to their fork's module path
46 changes: 46 additions & 0 deletions .designs/af-5836e28c/constraint-audit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Pre-Synthesis Constraint Audit

## Constraints Legend
- C1: Templates in AF_SRC internal/templates/roles/
- C2: Workspace artifacts stay in factory root
- C3: quickstart.sh remains single entry point
- C4: --delete cleans both sides
- C5: -o works without source tree detection
- C6: Self-hosted case (af source == factory root)
- C7: External project case (af source != factory root)
- C8: Reuse AF_SOURCE_ROOT pattern
- C9: make build still required
- C10: No CLI breaking changes

## Audit Matrix

| Dimension | Recommendation | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 | C10 | Status |
|-----------|---------------|----|----|----|----|----|----|----|----|----|----|--------|
| API | Shared config.ResolveSourceRoot() | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | PASS |
| Data | go.mod module path check | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | PASS |
| UX | Auto-routing with verbose output | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | PASS |
| Scale | Simple sequential resolution | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | PASS |
| Security | Validate go.mod for all resolution paths | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | PASS |
| Integration | Export AF_SOURCE_ROOT in agent-gen-all.sh | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | PASS |

## Detailed Verification

### C5 Check (most subtle): -o flag without source tree detection
The -o (dry run) flag path in formula.go returns after rendering content to stdout (line 130-133). The source root resolution happens at line ~200, which is AFTER the -o return point. Wait — looking more carefully:

The current code calls `ResolveSourceRoot()` BEFORE the -o check would be useful. The proposed change adds the source root resolution after finding factory root (line 109) but BEFORE the -o check (line 130).

**Issue detected**: If we add `srcRoot, err := config.ResolveSourceRoot(root)` before the -o check, then `-o` will fail when AF_SOURCE_ROOT isn't set and factory root isn't the source tree.

**Fix**: Move source root resolution to AFTER the -o check, right before the template write (line 199). The -o flag only needs the rendered content (which uses factory root for workspace path rendering), not the source root for template placement.

**Revised plan**: In formula.go:
- Lines 108-115: Find factory root, compute wsDir (unchanged)
- Lines 118-132: Render template, -o flag returns (unchanged)
- NEW: After -o check, resolve source root for template placement
- Line 200: Use srcRoot for template directory

This preserves C5.

## Audit Result
All dimensions PASS after the C5 fix for -o flag ordering. No constraint violations found.
61 changes: 61 additions & 0 deletions .designs/af-5836e28c/constraints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Constraint Verification Checklist

## Constraint: Templates must end up in `$AF_SRC/internal/templates/roles/` for `//go:embed` compilation
- **Prohibits:** Writing .md.tmpl files only to the factory root's internal/ tree when it differs from AF_SRC
- **Requires:** agent-gen must resolve the af source tree path and write templates there
- **Verification:** After agent-gen, check `$AF_SRC/internal/templates/roles/<name>.md.tmpl` exists
- **Relaxation Impact:** If relaxed, templates wouldn't compile into the binary — `af prime` and `af install` would fail to render CLAUDE.md for formula-generated agents

## Constraint: Workspace artifacts must stay in factory root
- **Prohibits:** Moving agents.json, workspace dirs, CLAUDE.md, or settings.json out of the factory root
- **Requires:** `.agentfactory/agents/<name>/`, `.agentfactory/agents.json` remain relative to factory root
- **Verification:** After agent-gen, confirm artifacts exist under factory root's `.agentfactory/`
- **Relaxation Impact:** Would break `af prime`, `af up`, `af install` which all use factory root paths

## Constraint: quickstart.sh remains single entry point
- **Prohibits:** Requiring users to run additional scripts or manual copy steps
- **Requires:** quickstart.sh + agent-gen-all.sh handle everything end-to-end
- **Verification:** Fresh clone → quickstart.sh → agents work (no manual intervention)
- **Relaxation Impact:** Increases onboarding friction; newcomers won't know the workflow

## Constraint: agent-gen --delete must clean both sides
- **Prohibits:** Orphaned templates in AF_SRC or orphaned workspaces in factory root
- **Requires:** --delete removes template from AF_SRC and workspace from factory root
- **Verification:** After `agent-gen --delete`, neither template nor workspace exists
- **Relaxation Impact:** Template orphans accumulate in binary, wasting size and causing confusion

## Constraint: -o (dry run) works without source tree detection
- **Prohibits:** Requiring AF_SOURCE_ROOT for stdout-only output
- **Requires:** -o flag renders template content to stdout without writing any files
- **Verification:** `af formula agent-gen <name> -o` succeeds without AF_SOURCE_ROOT set
- **Relaxation Impact:** Would make dry-run mode less useful for debugging/development

## Constraint: Self-hosted case must work (af source == factory root)
- **Prohibits:** Hard-coding separate paths; assuming source tree is always different from factory root
- **Requires:** When factory root contains `go.mod` with agentfactory, template path == factory root path
- **Verification:** Run agent-gen in the agentfactory repo itself — templates land in correct place
- **Relaxation Impact:** Would break development on agentfactory itself

## Constraint: External project case must work (af source != factory root)
- **Prohibits:** Assuming source tree is always the factory root
- **Requires:** AF_SOURCE_ROOT env var or build-time sourceRoot to locate the separate af source tree
- **Verification:** Run agent-gen from ~/af/myproject/ with AF_SOURCE_ROOT=~/projects/agentfactory — templates go to AF_SOURCE_ROOT
- **Relaxation Impact:** Would break the primary use case described in the problem statement

## Constraint: Reuse existing AF_SOURCE_ROOT pattern
- **Prohibits:** Creating a new env var or config mechanism for the same purpose
- **Requires:** Extending the existing `AF_SOURCE_ROOT` / build-time `sourceRoot` pattern from mcpstore to agent-gen
- **Verification:** Same env var works for both mcpstore Python path resolution and agent-gen template placement
- **Relaxation Impact:** Would create parallel discovery mechanisms, increasing cognitive load

## Constraint: make build still required after template changes
- **Prohibits:** Expecting templates to work without recompilation
- **Requires:** agent-gen output message still says "Run 'make build' to compile the new template"
- **Verification:** User message after agent-gen mentions make build requirement
- **Relaxation Impact:** N/A — this is a fundamental Go embed constraint

## Constraint: No breaking changes to agent-gen CLI interface
- **Prohibits:** Changing required arguments, removing flags, altering exit codes
- **Requires:** Existing `af formula agent-gen <name>` invocations continue to work
- **Verification:** Existing scripts (agent-gen-all.sh) work without modification beyond env var setting
- **Relaxation Impact:** Would break existing automation and documentation
66 changes: 66 additions & 0 deletions .designs/af-5836e28c/data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Data Model

## Summary
This problem has minimal data model impact — no new storage, no schemas, no migrations. The only data-relevant change is the path routing for where `.md.tmpl` files land on disk. The existing file formats (Go templates, agents.json, CLAUDE.md) remain unchanged. The key data concern is the go.mod detection heuristic for identifying the af source tree.

## Constraint Check
- [x] Templates in AF_SRC: Addressed by path routing, not data model
- [x] Workspace in factory root: No data model changes needed
- [x] Reuse AF_SOURCE_ROOT: Env var is data input, no storage needed
- [x] Self-hosted case: go.mod detection is a read-only check

## Options Explored

### Option 1: go.mod module path check (recommended)
- **Description**: Read `go.mod` in factory root, parse the `module` line, check if it matches `github.com/stempeck/agentfactory` (or contains "agentfactory" in the module path).
- **Constraint Compliance**: All pass
- **Pros**: Precise — distinguishes the af source tree from projects that merely import it; uses Go standard library (`go/modfile` or simple string parsing)
- **Cons**: Slightly more complex than a simple file existence check
- **Effort**: Low
- **Reversibility**: Easy

### Option 2: Check for `internal/templates/roles/` directory existence
- **Description**: If factory root has `internal/templates/roles/`, assume it's the source tree.
- **Constraint Compliance**: Passes but fragile
- **Pros**: Simple
- **Cons**: False positives if target project happens to have that path; false negatives if directory hasn't been created yet
- **Effort**: Low
- **Reversibility**: Easy
- REJECTED: Too fragile — could match non-af projects with similar structure

### Option 3: Marker file `.agentfactory-source`
- **Description**: Place a marker file in the af source root that agent-gen checks for.
- **Constraint Compliance**: Passes
- **Pros**: Explicit, no false positives
- **Cons**: Extra file to maintain; easy to forget; not self-documenting
- **Effort**: Low
- **Reversibility**: Easy
- REJECTED: Adds maintenance burden for something go.mod already solves

## Recommendation
**Option 1**: go.mod module path check. Simple, precise, uses existing data (go.mod is always present in the source tree). Parse just the first line starting with "module " — no need for full go.mod parser.

### Detection Logic
```go
func isAgentFactorySourceTree(dir string) bool {
data, err := os.ReadFile(filepath.Join(dir, "go.mod"))
if err != nil {
return false
}
for _, line := range strings.SplitN(string(data), "\n", 3) {
if strings.HasPrefix(line, "module ") {
return strings.Contains(line, "agentfactory")
}
}
return false
}
```

## Dependencies Produced
- This detection logic is used by `config.ResolveSourceRoot()` (from API dimension)

## Risks Identified
- **Module path rename**: If the agentfactory module path changes, detection breaks. Severity: Very Low. Mitigation: The detection string "agentfactory" is broad enough to survive minor path changes.

## Constraints Identified
- None new — this dimension operates within existing data structures.
Loading
Loading