This file provides guidance for AI coding agents working in the Pisces codebase.
Always check tests and linting after making changes.
Pisces is a Go-based tool for analyzing phishing attack sites. It uses the Chrome
DevTools Protocol via chromedp to automate browser interactions for security
analysis. The project produces two binaries: pisces (CLI) and pisces-web (REST API).
Go Version: 1.24.4
Module: github.com/mjc-gh/pisces
make build.cli # Build CLI to build/pisces
make build.web # Build web server to build/pisces-web
make go.get # Download dependencies
make go.tidy # Tidy go.modmake test # Run linting then all tests
go test ./... # Run all tests without linting
go test -v ./... # Run all tests with verbose output
# Run a single test by name
go test -v ./engine -run TestNewTask
go test -v ./... -run TestPerformTaskUnknownType
# Run tests matching a pattern
go test -v ./... -run "TestTask.*"zmake check # Run golangci-lintThe project uses golangci-lint v2 with nearly all linters enabled. See
.golangci.yml for the full configuration. Code must pass make check formatting.
pisces/
├── cmd/cli/main.go # CLI entry point
├── cmd/pisces-web/main.go # Web server entry point
├── engine/ # Core analysis engine (crawler, tasks, errors)
├── internal/browser/ # Browser configuration/profiles
├── internal/piscestest/ # Test utilities and fixtures
├── internal/rest/ # REST API server
├── rules/ # Sigma detection rules (YAML)
└── logger.go # Logging setup (root package)
Organize imports in three groups separated by blank lines:
- Standard library packages
- Third-party packages
- Internal project packages
| Element | Convention | Example |
|---|---|---|
| Packages | lowercase, single word | engine, browser, rest |
| Variables/functions | camelCase | userAgent, winWidth |
| Exported names | PascalCase | NewCrawler, AnalyzeResult |
| Constants | SCREAMING_SNAKE_CASE | SIZE_LARGE, PROFILE_DESKTOP |
| Error variables | Err prefix + PascalCase |
ErrNoCrawlerVisit |
| JSON struct tags | snake_case | json:"requested_url" |
| YAML struct tags | camelCase | yaml:"browserProfile" |
- Define sentinel errors at package level:
var ErrNoCrawlerVisit = errors.New("no visit from crawler")- Wrap errors with context:
return fmt.Errorf("create output file: %w", err)- Log errors with zerolog:
logger.Warn().Err(err).Msg("file close error")Use functional options for configurable types:
type Option func(*Engine)
func WithRemoteAllocator(host string, port int) Option {
return func(e *Engine) {
host := net.JoinHostPort(host, strconv.Itoa(port))
e.config.remoteURL = fmt.Sprintf("http://%s/json/version", host)
}
}- Use table-driven tests:
tests := []struct {
name string
action string
expectedAction string
}{
{name: "basic task", action: "navigate", expectedAction: "navigate"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
task := NewTask(tt.action, tt.input)
assert.Equal(t, tt.expectedAction, task.action)
})
}- Use testify assertions:
assert.Equal(t, expected, actual)
require.NoError(t, err) // Fails test immediately
require.Error(t, err)-
Mark parallel-safe tests with
t.Parallel() -
Use test utilities from
internal/piscestest/:NewTestWebServer()- HTTP test server with embedded testdataNewTestContext()- Browser context (respects env vars for remote/headfull)FindByID[T]()- Generic helper for finding structs by ID
| Package | Purpose |
|---|---|
github.com/chromedp/chromedp |
Chrome DevTools Protocol automation |
github.com/bradleyjkemp/sigma-go |
Sigma rule detection |
github.com/rs/zerolog |
Structured logging |
github.com/urfave/cli/v3 |
CLI framework |
github.com/gin-gonic/gin |
HTTP web framework |
github.com/stretchr/testify |
Test assertions |