- Go 1.25 or later
golangci-lint(for linting)goreleaser(for releases, optional)
git clone https://github.com/initializ/forge.git
cd forge
make buildmake vet
make testinternal/cmd/ # CLI command definitions (one file per command)
internal/config/ # Configuration parsing
internal/models/ # Data structures (AgentSpec, ToolSpec, etc.)
internal/pipeline/ # Build pipeline engine
internal/build/ # Build stage implementations
internal/plugins/ # Framework plugin system
internal/runtime/ # Agent local runner
internal/runtime/llm # LLM client abstraction
internal/container/ # Container image building
internal/channels/ # Channel adapter system
internal/tools/ # Tool registry and implementations
internal/validate/ # Validation logic
pkg/a2a/ # Shared A2A protocol types
schemas/ # Embedded JSON schemas
templates/ # Go templates for code generation
testdata/ # Test fixtures
- Create
internal/cmd/yourcommand.go - Define a
cobra.Commandvariable - Register it in
internal/cmd/root.go'sinit()function - Add tests in
internal/cmd/yourcommand_test.go
- Create
internal/build/yourstage.go - Implement the
pipeline.Stageinterface (Name()andExecute()) - Add the stage to the pipeline in
internal/cmd/build.go - Add tests in
internal/build/yourstage_test.go
Builtin tool:
- Create
internal/tools/builtins/your_tool.go - Implement the
tools.Toolinterface - Register in
internal/tools/builtins/register.go'sRegisterAll()function
Custom tool (script-based):
- Create
tools/tool_yourname.py(or.ts/.js) in the agent project - Forge discovers it automatically via
tools.DiscoverTools()
- Create
internal/channels/yourplatform/yourplatform.go - Implement the
channels.ChannelPlugininterface - Register in
internal/cmd/channel.go'screatePlugin()anddefaultRegistry() - Add config generation in
generateChannelConfig()andgenerateEnvVars() - Add tests
- Create
internal/runtime/llm/providers/yourprovider.go - Implement the
llm.Clientinterface (Chat(),ChatStream(),ModelID()) - Add the provider to the factory in
internal/runtime/llm/providers/factory.go - Add tests
- Every package should have
*_test.gofiles - Test files use the same package name (white-box) or
_testsuffix (black-box) - Use table-driven tests where appropriate
- Mock external dependencies (HTTP servers, file system, etc.)
- Use the
//go:build integrationbuild tag - Run with
go test -tags=integration ./... - Integration tests may use test fixtures from
testdata/ - No external services required (use
httptestfor mock servers)
Test fixtures live in testdata/ at the project root:
forge-valid.yaml— Full valid forge.yamlforge-minimal.yaml— Bare-minimum valid configforge-invalid.yaml— Invalid config for error testingagentspec-valid.json— Valid AgentSpec JSONagentspec-invalid.json— Invalid AgentSpec for error testingtool-schema.json— Minimal tool input schema
# All unit tests
make test
# Integration tests
make test-integration
# Coverage report
make cover
# Specific package
go test -v ./internal/pipeline/...- Run
go fmton all files - Run
go vetbefore committing - Run
golangci-lint run ./...for additional checks - Keep functions focused and small
- Use meaningful variable names
- Add comments for non-obvious logic only
- Create a feature branch from
develop - Make your changes with tests
- Ensure all checks pass:
make vet && make test && make fmt - Push and open a pull request against
develop - PRs require passing CI checks before merge
Releases are automated via GoReleaser:
- Ensure
developis stable and all tests pass - Merge
developintomain - Tag the release:
git tag v0.1.0 - Push the tag:
git push origin v0.1.0 - GitHub Actions runs GoReleaser to build and publish binaries