This guide is for AI coding agents and developers working on the lp-api project.
lp-api is a Go CLI tool for interacting with the Launchpad API (https://api.launchpad.net/devel.html). It supports querying resources, modifying data, file uploads, and downloading build artifacts.
- Language: Go 1.20+
- Type: Single-binary CLI tool
- Main files:
lp-api.go,lp-api_test.go - Dependencies: Minimal (only
github.com/pelletier/go-toml/v2)
# Build the binary for current platform
go build -o lp-api lp-api.go
# Build with verbose output
go build -v -o lp-api lp-api.go
# Check if code compiles without producing binary
go build -o /dev/null lp-api.go# Run all tests
go test
# Run all tests with verbose output
go test -v
# Run a single test function
go test -v -run TestFunctionName
# Run tests matching a pattern
go test -v -run "Test_file.*"
# Run with race detection
go test -race
# Run with coverage
go test -cover
go test -coverprofile=coverage.out
go tool cover -html=coverage.out # View coverage in browser
# Run integration test script
./tests/test_common_workflows.sh # Dry run mode
./tests/test_common_workflows.sh -staging # Real staging server# Format code (always run before committing)
go fmt ./...
gofmt -w .
# Run go vet for static analysis
go vet ./...
# Check for suspicious constructs
go vet lp-api.go lp-api_test.go
# Ensure dependencies are tidy
go mod tidy
go mod verify# Run without building
go run lp-api.go get bugs/1
# Build and run
go build && ./lp-api get bugs/1
# Enable debug output
./lp-api -debug get bugs/1
# Use staging server
./lp-api -staging get bugs/1- MUST use
gofmt- All code must be formatted withgofmtbefore committing - Indentation: Use tabs (handled by
gofmt) - Line length: No strict limit, let
gofmthandle wrapping - Braces: Required for all control structures (enforced by Go)
- Exported names: Start with uppercase (e.g.,
LaunchpadAPI,FileAttachment) - Unexported names: Start with lowercase (e.g.,
isFileAttachment,extractFilePath) - Use MixedCaps: Not snake_case (e.g.,
readFileContent, notread_file_content) - No "Get" prefix: For getters, use
Owner()notGetOwner() - Interface names: One-method interfaces end in
-er(e.g.,Reader,Writer) - Package names: Short, concise, single-word, lowercase
Order imports in groups (handled by gofmt):
- Standard library packages (alphabetically)
- Third-party packages (alphabetically)
- Local packages (alphabetically)
Example from lp-api.go:
import (
"bytes"
"encoding/json"
"errors"
"flag"
// ... more stdlib
"github.com/pelletier/go-toml/v2"
)- Define structs with clear field names and types
- Use struct tags for serialization (e.g.,
toml:"oauth_token") - Example:
type Credential struct {
Key string `toml:"oauth_consumer_key"`
Token string `toml:"oauth_token"`
Secret string `toml:"oauth_token_secret"`
}- Multiple returns: Return
(result, error)for operations that can fail - Named returns: Use for documentation, but not required
- Defer: Use for cleanup (e.g.,
defer resp.Body.Close()) - Receiver names: Short and consistent (e.g.,
lpforLaunchpadAPI)
- Always check errors explicitly - Never use
_to discard errors - Return errors up the call stack when possible
- Use
log.Fatalonly inmain()or when recovery is impossible - Provide context: Wrap errors with helpful messages
- Check specific errors: Use
os.IsNotExist(err),os.IsPermission(err), etc.
Example from lp-api.go:
data, err := readFileContent(filePath)
if err != nil {
if os.IsNotExist(err) {
return "", fmt.Errorf("Error: File not found: %s", filePath)
}
if os.IsPermission(err) {
return "", fmt.Errorf("Error: Cannot read file: permission denied")
}
return "", fmt.Errorf("Error: Failed to read file: %v", err)
}- No parentheses around conditions (enforced by Go syntax)
- Braces mandatory for all blocks
- Initialize in if: Use
if err := foo(); err != nil { ... } - Range loops: Use
for...rangefor slices, maps, channels - Switch: No fallthrough by default (use
fallthroughif needed)
- Package comment: Every package should have a package comment
- Exported functions: Document with a comment starting with the function name
- Inline comments: Use sparingly, code should be self-documenting
- TODO comments: Mark incomplete work with
// TODO: description
Example:
// isFileAttachment checks if a parameter value starts with @ indicating a file path
func isFileAttachment(param string) bool {
return strings.HasPrefix(param, "@")
}- Test files: Named
*_test.go(e.g.,lp-api_test.go) - Test functions: Start with
Testfollowed by name (e.g.,Test_get,TestIsFileAttachment) - Table-driven tests: Use structs with test cases for comprehensive testing
- Cleanup: Use
t.Cleanup()for test resource cleanup - Subtests: Use
t.Run()for organizing related tests - Temporary files: Use
t.TempDir()for test file operations
Example from lp-api_test.go:
func TestIsFileAttachment(t *testing.T) {
tests := []struct {
name string
param string
want bool
}{
{"valid file with @ prefix", "@file.log", true},
{"no @ prefix", "file.log", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isFileAttachment(tt.param); got != tt.want {
t.Errorf("isFileAttachment(%q) = %v, want %v", tt.param, got, tt.want)
}
})
}
}lp-api/
├── lp-api.go # Main CLI implementation
├── lp-api_test.go # Unit and integration tests
├── go.mod # Go module definition
├── go.sum # Dependency checksums
├── README.md # User-facing documentation
├── LICENSE # Apache 2.0 license
├── conductor/ # Development guidelines and specs
│ ├── workflow.md # Development workflow
│ ├── tech-stack.md # Technology decisions
│ └── code_styleguides/
│ └── go.md # Go style guide summary
├── launchpad/ # Launchpad API documentation
│ ├── SKILL.md # Comprehensive usage guide
│ ├── scripts/ # Bash helper functions
│ └── references/ # API reference docs
├── tests/ # Integration test scripts
│ └── test_common_workflows.sh
├── specs/ # Feature specifications
└── .github/
└── workflows/
└── release.yaml # CI/CD for releases
- Write failing tests first (TDD approach)
- Implement the feature in
lp-api.go - Ensure all tests pass:
go test -v - Format code:
go fmt ./... - Run static analysis:
go vet ./... - Update documentation if needed
When updating the Launchpad skill documentation (anything in launchpad/ directory), follow this SOP:
1. Review Changes
# Check what has been modified
git --no-pager status
git --no-pager diff launchpad/2. Update Version Number
Update version in FOUR files (use semantic versioning):
launchpad/SKILL.md- Line 5:version: "X.Y.Z"launchpad/gemini-extension.json- Line 4:"version": "X.Y.Z",launchpad/README.md- Near bottom:Skill Version: X.Y.ZandLast Updated: YYYY-MM-DDlaunchpad/CHANGELOG.md- Add new entry at top
Version scheme:
- Major (X): Breaking changes or major redesign
- Minor (Y): New features, significant improvements
- Patch (Z): Bug fixes, documentation clarifications, minor improvements
3. Write Changelog Entry
Add entry to launchpad/CHANGELOG.md using this format:
## [X.Y.Z] - YYYY-MM-DD
### Added
- New features or documentation sections
### Improved
- Enhancements to existing functionality or docs
### Fixed
- Bug fixes or corrections
### Changed
- Breaking changes or significant modifications
### Removed
- Deprecated features or sectionsGuidelines for changelog entries:
- Use past tense ("Added", "Improved", not "Add", "Improve")
- Be specific about what changed and where (mention file names)
- Include context if the change addresses a common pain point
- Group related changes under appropriate category
- Lead with user impact, not implementation details
4. Verify All Files Updated
# Ensure all 4 files show the new version
grep -n "1\.2\.6" launchpad/SKILL.md launchpad/gemini-extension.json launchpad/README.md launchpad/CHANGELOG.md5. Commit Changes
# Stage version files and documentation changes together
git add launchpad/
# Use descriptive commit message
git commit -m "skill: bump version to X.Y.Z - brief description"Example Workflow:
# After updating launchpad/references/bugs.md
git diff launchpad/
# Update all 4 version files (SKILL.md, gemini-extension.json, README.md, CHANGELOG.md)
# Verify
grep "1\.2\.6" launchpad/SKILL.md launchpad/gemini-extension.json launchpad/README.md launchpad/CHANGELOG.md
# Commit
git add launchpad/
git commit -m "skill: bump version to 1.2.6 - improve addTask documentation"# Use -debug flag to see HTTP requests and responses
./lp-api -debug get bugs/1
# Use logging in code
log.Print("Debug message") # Only appears with -debug flag
log.Fatal("Fatal error") # Always appears and exits- Use
os.ReadFile()for reading files - Use
os.WriteFile()for writing files - Check specific errors:
os.IsNotExist(),os.IsPermission() - Always close file handles with
defer file.Close()
All API calls go through LaunchpadAPI methods:
Get()- Retrieve resourcesPost()- Create or invoke operationsPatch()- Update resources (JSON)Put()- Replace resources (JSON from file)Delete()- Remove resourcesDownload()- Download files
OAuth 1.0a authentication using PLAINTEXT signature:
- Credentials stored in
~/.config/lp-api.toml - Or set via
LAUNCHPAD_TOKENenvironment variable - Format:
token:secret:consumer_key
For file attachments (e.g., ws.op=addAttachment):
- Detect
@prefix in parameters - Read file content into memory
- Build multipart/form-data request
- Required parameter:
comment(Launchpad API requirement) - Optional parameters:
description,is_patch,filename
- Effective Go: https://go.dev/doc/effective_go
- Go Testing:
go help testandgo help testfunc - Launchpad API: https://api.launchpad.net/devel.html
- Project workflow:
conductor/workflow.md - Tech stack:
conductor/tech-stack.md