Documentation
¶
Overview ¶
Package engineframework provides MCP tool registration utilities for forge engines.
This package EXTENDS existing infrastructure:
- Engines use pkg/enginecli.Bootstrap for lifecycle management (main, version, --mcp flags)
- Engines use engineframework for MCP tool registration (build, run, create, delete tools)
DO NOT use this package to replace cli.Bootstrap. Use it to simplify MCP tool registration.
Architecture ¶
Forge engines have a two-layer architecture:
Lifecycle Layer (pkg/enginecli.Bootstrap): - Handles main() function - Parses command-line flags (version, help, --mcp) - Manages MCP server lifecycle - Provides version information
MCP Registration Layer (pkg/engineframework): - Registers MCP tools (build, run, create, delete) - Handles batch operations automatically - Provides input validation and result formatting - Eliminates duplicate MCP handler code
Framework Types ¶
The package provides three specialized frameworks:
Builder Framework (builder.go): - For engines that build artifacts (binaries, containers, generated code) - Registers "build" and "buildBatch" MCP tools - Used by: go-build, container-build, generic-builder, go-gen-openapi, go-gen-mocks, go-format, go-lint
TestRunner Framework (testrunner.go): - For engines that execute tests and generate reports - Registers "run" MCP tool - Used by: go-test, go-lint-tags, generic-test-runner, forge-e2e
TestEnv Subengine Framework (testenvsubengine.go): - For engines that create and delete test environments - Registers "create" and "delete" MCP tools - Used by: testenv-kind, testenv-lcr, testenv-helm-install - NOTE: testenv orchestrator does NOT use this (different pattern)
Function Type Approach ¶
This framework uses function types (not interface embedding):
type BuilderFunc func(ctx context.Context, input mcptypes.BuildInput) (*forge.Artifact, error) type TestRunnerFunc func(ctx context.Context, input mcptypes.RunInput) (*forge.TestReport, error) type CreateFunc func(ctx context.Context, input CreateInput) (*TestEnvArtifact, error)
This approach:
- Compiles cleanly in Go (no impossible interface constraints)
- Matches existing engine handler patterns (standalone functions)
- Simplifies implementation (no struct requirements)
- Maintains type safety
Usage Examples ¶
Builder Framework Example ¶
Using cli.Bootstrap + BuilderFramework together:
package main
import (
"context"
"fmt"
"github.com/alexandremahdhaoui/forge/pkg/enginecli"
"github.com/alexandremahdhaoui/forge/pkg/mcpserver"
"github.com/alexandremahdhaoui/forge/pkg/engineframework"
"github.com/alexandremahdhaoui/forge/pkg/forge"
"github.com/alexandremahdhaoui/forge/pkg/mcptypes"
)
var versionInfo cli.VersionInfo
func main() {
// Use cli.Bootstrap for lifecycle (main, version, --mcp)
cli.Bootstrap(runMCPServer, &versionInfo)
}
func runMCPServer() error {
v, _, _ := versionInfo.Get()
server := mcpserver.New("my-builder", v)
// Use engineframework for MCP registration
config := engineframework.BuilderConfig{
Name: "my-builder",
Version: v,
BuildFunc: buildFunc,
}
if err := engineframework.RegisterBuilderTools(server, config); err != nil {
return err
}
return server.RunDefault()
}
func buildFunc(ctx context.Context, input mcptypes.BuildInput) (*forge.Artifact, error) {
// Extract spec configuration
outputDir := engineframework.ExtractStringWithDefault(input.Spec, "outputDir", "./build")
// Perform build
if err := runBuild(input.Name, outputDir); err != nil {
return nil, fmt.Errorf("build failed: %w", err)
}
// Return versioned artifact
return engineframework.CreateVersionedArtifact(input.Name, "binary", outputDir+"/"+input.Name)
}
TestRunner Framework Example ¶
For test runners that execute tests and return reports:
func runMCPServer() error {
v, _, _ := versionInfo.Get()
server := mcpserver.New("my-test-runner", v)
config := engineframework.TestRunnerConfig{
Name: "my-test-runner",
Version: v,
RunTestFunc: runTests,
}
if err := engineframework.RegisterTestRunnerTools(server, config); err != nil {
return err
}
return server.RunDefault()
}
func runTests(ctx context.Context, input mcptypes.RunInput) (*forge.TestReport, error) {
// Extract spec configuration
testPattern := engineframework.ExtractStringWithDefault(input.Spec, "pattern", "./...")
// Run tests
output, err := executeTests(input.Stage, testPattern)
if err != nil {
// Execution error - couldn't run tests
return nil, fmt.Errorf("failed to execute tests: %w", err)
}
// Parse test results
report := parseTestOutput(output)
// CRITICAL: Return report even if tests failed
// Framework will use ErrorResultWithArtifact for failed tests
return report, nil
}
TestEnv Subengine Framework Example ¶
For test environment provisioners (clusters, registries, databases):
func runMCPServer() error {
v, _, _ := versionInfo.Get()
server := mcpserver.New("my-testenv", v)
config := engineframework.TestEnvSubengineConfig{
Name: "my-testenv",
Version: v,
CreateFunc: createResource,
DeleteFunc: deleteResource,
}
if err := engineframework.RegisterTestEnvSubengineTools(server, config); err != nil {
return err
}
return server.RunDefault()
}
func createResource(ctx context.Context, input engineframework.CreateInput) (*engineframework.TestEnvArtifact, error) {
// Extract spec configuration
version := engineframework.ExtractStringWithDefault(input.Spec, "version", "latest")
// Create resource
resourceName := fmt.Sprintf("myapp-%s", input.TestID)
if err := provisionResource(resourceName, version); err != nil {
return nil, fmt.Errorf("failed to create resource: %w", err)
}
// Return artifact with files, metadata, and managed resources
return &engineframework.TestEnvArtifact{
TestID: input.TestID,
Files: map[string]string{
"my-testenv.config": "config.yaml",
},
Metadata: map[string]string{
"my-testenv.resourceName": resourceName,
"my-testenv.version": version,
},
ManagedResources: []string{input.TmpDir + "/config.yaml"},
}, nil
}
func deleteResource(ctx context.Context, input engineframework.DeleteInput) error {
// Best-effort cleanup
resourceName := input.Metadata["my-testenv.resourceName"]
if err := cleanupResource(resourceName); err != nil {
log.Printf("Warning: failed to cleanup: %v", err)
return nil // Don't fail on cleanup errors
}
return nil
}
Utilities ¶
The package also provides common utilities:
- Spec Extraction (spec.go): Extract typed values from map[string]any Spec fields
- Git Versioning (version.go): Standardize artifact versioning with git commit hashes
See individual framework files (builder.go, testrunner.go, testenvsubengine.go) for detailed usage.
Index ¶
- func CallDetector(ctx context.Context, cmd string, args []string, toolName string, input any) ([]forge.ArtifactDependency, error)
- func CreateArtifact(name, artifactType, location string) *forge.Artifact
- func CreateCustomArtifact(name, artifactType, location, version string) *forge.Artifact
- func CreateVersionedArtifact(name, artifactType, location string) (*forge.Artifact, error)
- func ExtractBool(spec map[string]any, key string) (bool, bool)
- func ExtractBoolWithDefault(spec map[string]any, key string, defaultValue bool) bool
- func ExtractInt(spec map[string]any, key string) (int, bool)
- func ExtractIntWithDefault(spec map[string]any, key string, defaultValue int) int
- func ExtractMap(spec map[string]any, key string) (map[string]any, bool)
- func ExtractMapWithDefault(spec map[string]any, key string, defaultValue map[string]any) map[string]any
- func ExtractString(spec map[string]any, key string) (string, bool)
- func ExtractStringMap(spec map[string]any, key string) (map[string]string, bool)
- func ExtractStringMapWithDefault(spec map[string]any, key string, defaultValue map[string]string) map[string]string
- func ExtractStringSlice(spec map[string]any, key string) ([]string, bool)
- func ExtractStringSliceWithDefault(spec map[string]any, key string, defaultValue []string) []string
- func ExtractStringWithDefault(spec map[string]any, key, defaultValue string) string
- func FindDetector(name string) (string, error)deprecated
- func GetGitVersion() (string, error)
- func RegisterBuilderTools(server *mcpserver.Server, config BuilderConfig) error
- func RegisterTestEnvSubengineTools(server *mcpserver.Server, config TestEnvSubengineConfig) error
- func RegisterTestRunnerTools(server *mcpserver.Server, config TestRunnerConfig) error
- func RequireBool(spec map[string]any, key string) (bool, error)
- func RequireInt(spec map[string]any, key string) (int, error)
- func RequireMap(spec map[string]any, key string) (map[string]any, error)
- func RequireString(spec map[string]any, key string) (string, error)
- func RequireStringMap(spec map[string]any, key string) (map[string]string, error)
- func RequireStringSlice(spec map[string]any, key string) ([]string, error)
- func ResolveDetector(detectorURI, forgeVersion string) (cmd string, args []string, err error)
- type BuilderConfig
- type BuilderFunc
- type CreateFunc
- type CreateInput
- type DeleteFunc
- type DeleteInput
- type TestEnvArtifact
- type TestEnvSubengineConfig
- type TestRunnerConfig
- type TestRunnerFunc
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CallDetector ¶ added in v0.16.0
func CallDetector(ctx context.Context, cmd string, args []string, toolName string, input any) ([]forge.ArtifactDependency, error)
CallDetector calls a detector MCP server and returns dependencies. It spawns the detector as a subprocess, connects via MCP, calls the specified tool, and converts the response to []forge.ArtifactDependency.
Parameters:
- ctx: context for the operation
- cmd: command to execute (e.g., "go")
- args: arguments for the command (e.g., ["run", "github.com/.../cmd/detector@v0.9.0"])
- toolName: name of the MCP tool to call (e.g., "detectDependencies")
- input: input parameters for the tool (will be serialized to JSON)
Returns:
- []forge.ArtifactDependency: list of detected dependencies
- error: if connection fails, tool call fails, or response parsing fails
func CreateArtifact ¶
CreateArtifact creates an artifact with current timestamp but NO version field. Use this for artifacts that don't have git versioning (e.g., generated code, test reports).
Parameters:
- name: Artifact name (from BuildInput.Name)
- artifactType: Type of artifact (e.g., "generated", "test-report")
- location: Location of the artifact (path or directory)
Returns:
- *forge.Artifact with Name, Type, Location, and Timestamp set
- Version field is empty (generated artifacts don't have versions)
Example:
artifact := CreateArtifact("openapi-client", "generated", "./pkg/generated")
// artifact.Version = "" (empty - generated code has no version)
// artifact.Timestamp = "2025-01-15T10:30:00Z" (current time)
func CreateCustomArtifact ¶
CreateCustomArtifact creates an artifact with a custom version string and current timestamp. Use this when you need a specific version that's not from git (e.g., semantic version, build number).
Parameters:
- name: Artifact name (from BuildInput.Name)
- artifactType: Type of artifact (e.g., "binary", "container")
- location: Location of the artifact (path or registry URL)
- version: Custom version string (e.g., "v1.2.3", "build-123")
Returns:
- *forge.Artifact with Name, Type, Location, Version (custom), and Timestamp set
Example:
artifact := CreateCustomArtifact("my-app", "container", "localhost:5000/my-app:v1.2.3", "v1.2.3")
// artifact.Version = "v1.2.3" (custom version)
// artifact.Timestamp = "2025-01-15T10:30:00Z" (current time)
func CreateVersionedArtifact ¶
CreateVersionedArtifact creates an artifact with git version and current timestamp. The version field is populated with the current git commit hash. The timestamp field is set to the current time in RFC3339 format.
Parameters:
- name: Artifact name (from BuildInput.Name)
- artifactType: Type of artifact (e.g., "binary", "container", "generated")
- location: Location of the artifact (path or registry URL)
Returns:
- *forge.Artifact with Name, Type, Location, Version (git SHA), and Timestamp set
- error if git version cannot be determined
Example:
artifact, err := CreateVersionedArtifact("my-app", "binary", "./build/bin/my-app")
if err != nil {
return nil, fmt.Errorf("failed to create artifact: %w", err)
}
// artifact.Version = "a1b2c3d4..." (git commit SHA)
// artifact.Timestamp = "2025-01-15T10:30:00Z" (current time)
func ExtractBool ¶
ExtractBool safely extracts a bool value from a spec map. Returns the bool value and true if the key exists and is a bool. Returns false and false if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"enabled": true, "name": "foo"}
enabled, ok := ExtractBool(spec, "enabled") // true, true
missing, ok := ExtractBool(spec, "missing") // false, false
wrong, ok := ExtractBool(spec, "name") // false, false (wrong type)
func ExtractBoolWithDefault ¶
ExtractBoolWithDefault safely extracts a bool value from a spec map with a default value. Returns the bool value if the key exists and is a bool. Returns the default value if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"enabled": true}
enabled := ExtractBoolWithDefault(spec, "enabled", false) // true
missing := ExtractBoolWithDefault(spec, "missing", false) // false
func ExtractInt ¶
ExtractInt safely extracts an int value from a spec map. Returns the int value and true if the key exists and is an int, int64, or float64 that represents an integer. Returns 0 and false if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"count": 42, "rate": 3.14, "name": "foo"}
count, ok := ExtractInt(spec, "count") // 42, true
missing, ok := ExtractInt(spec, "missing") // 0, false
wrong, ok := ExtractInt(spec, "name") // 0, false (wrong type)
func ExtractIntWithDefault ¶
ExtractIntWithDefault safely extracts an int value from a spec map with a default value. Returns the int value if the key exists and is a valid integer. Returns the default value if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"count": 42}
count := ExtractIntWithDefault(spec, "count", 10) // 42
missing := ExtractIntWithDefault(spec, "missing", 10) // 10
func ExtractMap ¶
ExtractMap safely extracts a map[string]any value from a spec map. Returns the map and true if the key exists and is a map[string]any. Returns nil and false if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"config": map[string]any{"timeout": 30}}
config, ok := ExtractMap(spec, "config") // {"timeout": 30}, true
missing, ok := ExtractMap(spec, "missing") // nil, false
func ExtractMapWithDefault ¶
func ExtractMapWithDefault(spec map[string]any, key string, defaultValue map[string]any) map[string]any
ExtractMapWithDefault safely extracts a map[string]any value from a spec map with a default value. Returns the map if the key exists and is a valid map[string]any. Returns the default value if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"config": map[string]any{"timeout": 30}}
config := ExtractMapWithDefault(spec, "config", map[string]any{"default": true}) // {"timeout": 30}
missing := ExtractMapWithDefault(spec, "missing", map[string]any{"default": true}) // {"default": true}
func ExtractString ¶
ExtractString safely extracts a string value from a spec map. Returns the string value and true if the key exists and is a string. Returns empty string and false if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"name": "my-app", "count": 42}
name, ok := ExtractString(spec, "name") // "my-app", true
missing, ok := ExtractString(spec, "missing") // "", false
wrong, ok := ExtractString(spec, "count") // "", false (wrong type)
func ExtractStringMap ¶
ExtractStringMap safely extracts a map[string]string value from a spec map. Returns the map and true if the key exists and is a map[string]string or map[string]any with string values. Returns nil and false if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"labels": map[string]string{"app": "foo"}}
labels, ok := ExtractStringMap(spec, "labels") // {"app": "foo"}, true
missing, ok := ExtractStringMap(spec, "missing") // nil, false
func ExtractStringMapWithDefault ¶
func ExtractStringMapWithDefault(spec map[string]any, key string, defaultValue map[string]string) map[string]string
ExtractStringMapWithDefault safely extracts a map[string]string value from a spec map with a default value. Returns the map if the key exists and is a valid map[string]string. Returns the default value if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"labels": map[string]string{"app": "foo"}}
labels := ExtractStringMapWithDefault(spec, "labels", map[string]string{"default": "value"}) // {"app": "foo"}
missing := ExtractStringMapWithDefault(spec, "missing", map[string]string{"default": "value"}) // {"default": "value"}
func ExtractStringSlice ¶
ExtractStringSlice safely extracts a []string value from a spec map. Returns the slice and true if the key exists and is a []string or []any containing only strings. Returns nil and false if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"tags": []string{"a", "b"}, "numbers": []int{1, 2}}
tags, ok := ExtractStringSlice(spec, "tags") // ["a", "b"], true
missing, ok := ExtractStringSlice(spec, "missing") // nil, false
wrong, ok := ExtractStringSlice(spec, "numbers") // nil, false
func ExtractStringSliceWithDefault ¶
ExtractStringSliceWithDefault safely extracts a []string value from a spec map with a default value. Returns the slice if the key exists and is a valid []string. Returns the default value if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"tags": []string{"a", "b"}}
tags := ExtractStringSliceWithDefault(spec, "tags", []string{"default"}) // ["a", "b"]
missing := ExtractStringSliceWithDefault(spec, "missing", []string{"default"}) // ["default"]
func ExtractStringWithDefault ¶
ExtractStringWithDefault safely extracts a string value from a spec map with a default value. Returns the string value if the key exists and is a string. Returns the default value if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"name": "my-app"}
name := ExtractStringWithDefault(spec, "name", "default") // "my-app"
missing := ExtractStringWithDefault(spec, "missing", "default") // "default"
func FindDetector
deprecated
added in
v0.16.0
FindDetector locates a dependency detector binary by name. It searches in the following order:
- PATH environment variable
- ./build/bin directory (common for forge self-build)
Returns the absolute path to the binary or an error if not found.
Deprecated: FindDetector only works when CWD is the forge repository. Use ResolveDetector() + CallDetector() instead, which works from any directory by using `go run` with versioned module paths.
func GetGitVersion ¶
GetGitVersion returns the current git commit hash. Returns the commit SHA and nil error on success. Returns "unknown" and an error if git operations fail.
Example:
version, err := GetGitVersion()
if err != nil {
log.Printf("Warning: could not get git version: %v", err)
version = "unknown"
}
fmt.Printf("Building version: %s\n", version)
func RegisterBuilderTools ¶
func RegisterBuilderTools(server *mcpserver.Server, config BuilderConfig) error
RegisterBuilderTools registers build and buildBatch tools with the MCP server.
This function automatically:
- Registers "build" tool that calls the BuildFunc
- Registers "buildBatch" tool that handles multiple builds in parallel
- Validates required input fields (Name, Engine)
- Converts BuilderFunc errors to MCP error responses
- Formats successful results with artifact information
- Uses mcputil.HandleBatchBuild for batch processing
Parameters:
- server: The MCP server instance
- config: Builder configuration with Name, Version, and BuildFunc
Returns:
- nil on success
- error if tool registration fails (e.g., duplicate tool names)
Example:
func runMCPServer() error {
server := mcpserver.New("my-builder", "1.0.0")
config := BuilderConfig{
Name: "my-builder",
Version: "1.0.0",
BuildFunc: myBuildFunc,
}
if err := RegisterBuilderTools(server, config); err != nil {
return err
}
return server.RunDefault()
}
func RegisterTestEnvSubengineTools ¶
func RegisterTestEnvSubengineTools(server *mcpserver.Server, config TestEnvSubengineConfig) error
RegisterTestEnvSubengineTools registers create and delete tools with the MCP server.
This function automatically:
- Registers "create" tool that calls the CreateFunc
- Registers "delete" tool that calls the DeleteFunc
- Validates required input fields (TestID, Stage, TmpDir for create; TestID for delete)
- Converts function errors to MCP error responses
- Returns TestEnvArtifact on successful create
- Uses SuccessResultWithArtifact for create operations
- Uses SuccessResult for delete operations
Parameters:
- server: The MCP server instance
- config: TestEnvSubengine configuration with Name, Version, CreateFunc, and DeleteFunc
Returns:
- nil on success
- error if tool registration fails (e.g., duplicate tool names)
Example:
func runMCPServer() error {
server := mcpserver.New("testenv-kind", "1.0.0")
config := TestEnvSubengineConfig{
Name: "testenv-kind",
Version: "1.0.0",
CreateFunc: myCreateFunc,
DeleteFunc: myDeleteFunc,
}
if err := RegisterTestEnvSubengineTools(server, config); err != nil {
return err
}
return server.RunDefault()
}
func RegisterTestRunnerTools ¶
func RegisterTestRunnerTools(server *mcpserver.Server, config TestRunnerConfig) error
RegisterTestRunnerTools registers the run tool with the MCP server.
This function automatically:
- Registers "run" tool that calls the RunTestFunc
- Validates required input fields (Stage, Runner)
- Converts TestRunnerFunc errors to MCP error responses
- Returns TestReport as artifact even when tests fail
- Uses ErrorResultWithArtifact for failed tests (report still returned)
- Uses SuccessResultWithArtifact for passed tests
Parameters:
- server: The MCP server instance
- config: TestRunner configuration with Name, Version, and RunTestFunc
Returns:
- nil on success
- error if tool registration fails (e.g., duplicate tool names)
Example:
func runMCPServer() error {
server := mcpserver.New("my-test-runner", "1.0.0")
config := TestRunnerConfig{
Name: "my-test-runner",
Version: "1.0.0",
RunTestFunc: myTestRunnerFunc,
}
if err := RegisterTestRunnerTools(server, config); err != nil {
return err
}
return server.RunDefault()
}
func RequireBool ¶
RequireBool extracts a required bool value from a spec map. Returns the bool value and nil error if the key exists and is a bool. Returns an error if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"enabled": true}
enabled, err := RequireBool(spec, "enabled") // true, nil
missing, err := RequireBool(spec, "missing") // false, error
func RequireInt ¶
RequireInt extracts a required int value from a spec map. Returns the int value and nil error if the key exists and is a valid integer. Returns an error if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"count": 42}
count, err := RequireInt(spec, "count") // 42, nil
missing, err := RequireInt(spec, "missing") // 0, error
func RequireMap ¶
RequireMap extracts a required map[string]any value from a spec map. Returns the map and nil error if the key exists and is a valid map[string]any. Returns an error if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"config": map[string]any{"timeout": 30}}
config, err := RequireMap(spec, "config") // {"timeout": 30}, nil
missing, err := RequireMap(spec, "missing") // nil, error
func RequireString ¶
RequireString extracts a required string value from a spec map. Returns the string value and nil error if the key exists and is a string. Returns an error if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"name": "my-app"}
name, err := RequireString(spec, "name") // "my-app", nil
missing, err := RequireString(spec, "missing") // "", error
func RequireStringMap ¶
RequireStringMap extracts a required map[string]string value from a spec map. Returns the map and nil error if the key exists and is a valid map[string]string. Returns an error if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"labels": map[string]string{"app": "foo"}}
labels, err := RequireStringMap(spec, "labels") // {"app": "foo"}, nil
missing, err := RequireStringMap(spec, "missing") // nil, error
func RequireStringSlice ¶
RequireStringSlice extracts a required []string value from a spec map. Returns the slice and nil error if the key exists and is a valid []string. Returns an error if the key doesn't exist or has the wrong type.
Example:
spec := map[string]any{"tags": []string{"a", "b"}}
tags, err := RequireStringSlice(spec, "tags") // ["a", "b"], nil
missing, err := RequireStringSlice(spec, "missing") // nil, error
func ResolveDetector ¶ added in v0.24.0
ResolveDetector parses a detector URI and returns the command and args to execute it. Detectors only support go:// URIs.
Parameters:
- detectorURI: URI of the detector (e.g., "go://go-dependency-detector")
- forgeVersion: Version of forge to use (e.g., "v0.9.0")
Returns:
- cmd: The command to execute (always "go")
- args: Arguments for the command (e.g., ["run", "github.com/.../cmd/detector@v0.9.0"])
- err: Error if the URI is invalid or resolution fails
Example usage:
cmd, args, err := ResolveDetector("go://go-dependency-detector", "v0.9.0")
// cmd = "go"
// args = ["run", "github.com/alexandremahdhaoui/forge/cmd/go-dependency-detector@v0.9.0"]
Types ¶
type BuilderConfig ¶
type BuilderConfig struct {
Name string // Engine name (e.g., "go-build")
Version string // Engine version
BuildFunc BuilderFunc // Build implementation
}
BuilderConfig configures builder tool registration.
Fields:
- Name: Engine name (e.g., "go-build", "container-build")
- Version: Engine version string (e.g., "1.0.0" or git commit hash)
- BuildFunc: The build implementation function
Example:
config := BuilderConfig{
Name: "my-builder",
Version: "1.0.0",
BuildFunc: myBuildFunc,
}
type BuilderFunc ¶
BuilderFunc is the signature for build operations.
Implementations must:
- Validate input fields (required fields should be checked, use RequireString etc. from spec.go)
- Execute the build operation (compile code, generate files, etc.)
- Return Artifact on success (with Name, Type, Location, Version if applicable, Timestamp)
- Return error on failure (business logic errors, not MCP errors)
The framework handles:
- MCP tool registration
- Batch operation support
- Result formatting
- Error conversion to MCP responses
Example:
func myBuildFunc(ctx context.Context, input mcptypes.BuildInput) (*forge.Artifact, error) {
// Extract and validate spec fields
sourceFile, err := RequireString(input.Spec, "sourceFile")
if err != nil {
return nil, err
}
// Execute build logic
if err := compileSources(sourceFile); err != nil {
return nil, fmt.Errorf("compilation failed: %w", err)
}
// Return artifact
return CreateArtifact(input.Name, "binary", "./build/bin/"+input.Name), nil
}
type CreateFunc ¶
type CreateFunc func(ctx context.Context, input CreateInput) (*TestEnvArtifact, error)
CreateFunc is the signature for testenv subengine create operations.
Implementations must:
- Validate input fields (testID, stage, tmpDir are required)
- Create the test environment resource (cluster, registry, etc.)
- Return TestEnvArtifact on success with files, metadata, and managedResources
- Return error on failure
The framework handles:
- MCP tool registration
- Result formatting
- Error conversion to MCP responses
- Artifact serialization
Example:
func myCreateFunc(ctx context.Context, input CreateInput) (*TestEnvArtifact, error) {
// Create resource (e.g., kind cluster)
clusterName := fmt.Sprintf("myapp-%s", input.TestID)
if err := createCluster(clusterName); err != nil {
return nil, fmt.Errorf("failed to create cluster: %w", err)
}
// Return artifact
return &TestEnvArtifact{
TestID: input.TestID,
Files: map[string]string{
"kubeconfig": "kubeconfig",
},
Metadata: map[string]string{
"clusterName": clusterName,
},
ManagedResources: []string{"/path/to/kubeconfig"},
}, nil
}
type CreateInput ¶
type CreateInput struct {
TestID string `json:"testID" jsonschema:"Unique identifier for this test environment instance"`
Stage string `json:"stage" jsonschema:"Test stage name from forge.yaml test[].name"`
TmpDir string `json:"tmpDir" jsonschema:"Temporary directory allocated for this test environment"`
RootDir string `json:"rootDir,omitempty" jsonschema:"Project root directory for path resolution"`
Metadata map[string]string `json:"metadata" jsonschema:"Metadata from previous testenv subengines in the chain"`
Spec map[string]any `json:"spec,omitempty" jsonschema:"Engine-specific configuration from forge.yaml testenv[].spec"`
Env map[string]string `json:"env,omitempty" jsonschema:"Accumulated environment variables from previous subengines in the chain"`
EnvPropagation *forge.EnvPropagation `json:"envPropagation,omitempty" jsonschema:"Configuration for filtering environment variable propagation"`
}
CreateInput represents the input for testenv subengine create operations.
This is the standard input format for all testenv subengines (e.g., testenv-kind, testenv-lcr). The testenv orchestrator calls each subengine's "create" tool with this input.
Fields:
- TestID: Unique identifier for this test environment instance (required)
- Stage: Test stage name from forge.yaml (required)
- TmpDir: Temporary directory allocated for this test environment (required)
- RootDir: Project root directory for path resolution (optional)
- Metadata: Metadata from previous subengines in the chain (optional)
- Spec: Optional spec for configuration override from forge.yaml
- Env: Accumulated environment variables from previous sub-engines (optional)
- EnvPropagation: Optional EnvPropagation configuration from spec (optional)
Example:
input := CreateInput{
TestID: "test-abc123",
Stage: "integration",
TmpDir: "/tmp/forge-test-abc123",
RootDir: "/home/user/project",
Metadata: map[string]string{"testenv-lcr.registryFQDN": "testenv-lcr.testenv-lcr.svc.cluster.local:31906"},
Spec: map[string]any{"kindVersion": "v1.27.0"},
Env: map[string]string{"TESTENV_LCR_FQDN": "testenv-lcr.testenv-lcr.svc.cluster.local:31906"},
}
type DeleteFunc ¶
type DeleteFunc func(ctx context.Context, input DeleteInput) error
DeleteFunc is the signature for testenv subengine delete operations.
Implementations must:
- Validate input fields (testID is required)
- Delete the test environment resource (cluster, registry, etc.)
- Return error on failure (or nil for best-effort cleanup)
The framework handles:
- MCP tool registration
- Result formatting
- Error conversion to MCP responses
IMPORTANT: Delete operations should be best-effort. If resources are already gone, don't return an error. Only return errors for actual failures that need attention.
Example:
func myDeleteFunc(ctx context.Context, input DeleteInput) error {
// Reconstruct cluster name from testID
clusterName := input.Metadata["clusterName"]
if clusterName == "" {
clusterName = fmt.Sprintf("myapp-%s", input.TestID)
}
// Delete cluster (best-effort)
if err := deleteCluster(clusterName); err != nil {
log.Printf("Warning: failed to delete cluster: %v", err)
return nil // Don't fail on cleanup errors
}
return nil
}
type DeleteInput ¶
type DeleteInput struct {
TestID string `json:"testID" jsonschema:"Unique identifier of the test environment instance to delete"`
Metadata map[string]string `json:"metadata" jsonschema:"Metadata from the test environment used for resource cleanup"`
}
DeleteInput represents the input for testenv subengine delete operations.
This is the standard input format for all testenv subengines. The testenv orchestrator calls each subengine's "delete" tool with this input.
Fields:
- TestID: Unique identifier for the test environment instance to delete (required)
- Metadata: Metadata from the test environment (optional, useful for cleanup)
Example:
input := DeleteInput{
TestID: "test-abc123",
Metadata: map[string]string{"testenv-kind.clusterName": "myapp-test-abc123"},
}
type TestEnvArtifact ¶
type TestEnvArtifact struct {
TestID string `json:"testID"` // Test environment ID
Files map[string]string `json:"files"` // Map of logical names to relative file paths
Metadata map[string]string `json:"metadata"` // Metadata for downstream consumers
ManagedResources []string `json:"managedResources"` // Resources to clean up
Env map[string]string `json:"env,omitempty"` // Environment variables exported by this sub-engine
}
TestEnvArtifact represents the artifact returned by testenv subengine create operations.
This is the standard artifact format for all testenv subengines. The artifact is passed to the test runner and returned to the caller.
Fields:
- TestID: Test environment ID
- Files: Map of logical names to relative file paths (relative to TmpDir)
- Metadata: Key-value metadata for downstream consumers
- ManagedResources: List of resources to clean up (file paths, cluster names, etc.)
- Env: Environment variables exported by this sub-engine (optional)
Example:
artifact := TestEnvArtifact{
TestID: "test-abc123",
Files: map[string]string{
"testenv-kind.kubeconfig": "kubeconfig",
},
Metadata: map[string]string{
"testenv-kind.clusterName": "myapp-test-abc123",
"testenv-kind.kubeconfigPath": "/tmp/forge-test-abc123/kubeconfig",
},
ManagedResources: []string{"/tmp/forge-test-abc123/kubeconfig"},
Env: map[string]string{
"KUBECONFIG": "/tmp/forge-test-abc123/kubeconfig",
},
}
type TestEnvSubengineConfig ¶
type TestEnvSubengineConfig struct {
Name string // Engine name (e.g., "testenv-kind")
Version string // Engine version
CreateFunc CreateFunc // Create operation implementation
DeleteFunc DeleteFunc // Delete operation implementation
}
TestEnvSubengineConfig configures testenv subengine tool registration.
Fields:
- Name: Engine name (e.g., "testenv-kind", "testenv-lcr")
- Version: Engine version string (e.g., "1.0.0" or git commit hash)
- CreateFunc: The create operation implementation function
- DeleteFunc: The delete operation implementation function
Example:
config := TestEnvSubengineConfig{
Name: "testenv-kind",
Version: "1.0.0",
CreateFunc: myCreateFunc,
DeleteFunc: myDeleteFunc,
}
type TestRunnerConfig ¶
type TestRunnerConfig struct {
Name string // Engine name (e.g., "go-test")
Version string // Engine version
RunTestFunc TestRunnerFunc // Test execution implementation
}
TestRunnerConfig configures test runner tool registration.
Fields:
- Name: Engine name (e.g., "go-test", "generic-test-runner")
- Version: Engine version string (e.g., "1.0.0" or git commit hash)
- RunTestFunc: The test execution implementation function
Example:
config := TestRunnerConfig{
Name: "my-test-runner",
Version: "1.0.0",
RunTestFunc: myTestRunnerFunc,
}
type TestRunnerFunc ¶
TestRunnerFunc is the signature for test execution.
Implementations must:
- Validate input fields (required fields should be checked)
- Execute tests (run test commands, collect results)
- Return TestReport on success (with Status "passed" or "failed")
- Return TestReport even on test failure (Status field indicates pass/fail)
- Return error only for execution failures (not test failures)
The framework handles:
- MCP tool registration
- Result formatting
- Error conversion to MCP responses
- Report return even on test failure
IMPORTANT: Test failures are NOT errors. Return a TestReport with Status="failed". Only return error for execution failures (can't run tests, can't parse results, etc.).
Example:
func myTestRunnerFunc(ctx context.Context, input mcptypes.RunInput) (*forge.TestReport, error) {
// Execute tests
output, err := runTests(input.Stage)
if err != nil {
// Execution error - couldn't run tests
return nil, fmt.Errorf("failed to execute tests: %w", err)
}
// Parse results
report := parseTestOutput(output)
// Return report (even if tests failed)
// Framework will use ErrorResultWithArtifact for failed tests
return report, nil
}