go-session/docs/development.md

230 lines
7.9 KiB
Markdown
Raw Normal View History

---
title: Development Guide
description: How to build, test, lint, and contribute to go-session.
---
# Development Guide
## Prerequisites
- **Go 1.26 or later** -- the module requires Go 1.26 (`go.mod`). The benchmark suite uses `b.Loop()`, introduced in Go 1.25.
- **`github.com/stretchr/testify`** -- test-only dependency, fetched automatically by `go test`.
- **`vhs`** (`github.com/charmbracelet/vhs`) -- optional, required only for `RenderMP4`. Install with `go install github.com/charmbracelet/vhs@latest`.
- **`golangci-lint`** -- optional, for running the full lint suite. Configuration is in `.golangci.yml`.
## Build and Test
```bash
# Run all tests
go test ./...
# Run a single test by name
go test -v -run TestParseTranscript_ToolCalls_Good
# Run with race detector
go test -race ./...
# Run benchmarks
go test -bench=. -benchmem ./...
# Vet the package
go vet ./...
# Format code
gofmt -w .
# Lint (requires golangci-lint)
golangci-lint run ./...
# Check test coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
```
If this module is part of the Go workspace at `~/Code/go.work`, you can also use the `core` CLI:
```bash
core go test
core go qa # fmt + vet + lint + test
core go cov --open # coverage with HTML report
```
## Test Structure
All tests are in the `session` package (white-box). Test files are co-located with their corresponding source files:
| Source file | Test file | What it covers |
|-------------|-----------|---------------|
| `parser.go` | `parser_test.go` | `ParseTranscript`, `ParseTranscriptReader`, `ListSessions`, `ListSessionsSeq`, `FetchSession`, `extractToolInput`, `extractResultContent`, `truncate`, `shortID`, `formatDuration`, edge cases (malformed JSON, truncated lines, binary garbage, null bytes, 5 MiB lines) |
| `analytics.go` | `analytics_test.go` | `Analyse` (nil, empty, single tool, mixed tools with errors, latency calculations, token estimation), `FormatAnalytics` |
| `html.go` | `html_test.go` | `RenderHTML` (basic session, empty session, error events, XSS protection, invalid path, label-per-tool-type) |
| `video.go` | `video_test.go` | `generateTape` (basic, skips non-tool events, failed commands, long output truncation, Task/Edit/Write events, empty session, empty command), `extractCommand`, `RenderMP4` error path |
| `search.go` | `search_test.go` | `Search` and `SearchSeq` (empty directory, no matches, single/multiple matches, case-insensitive, output matching, skips non-tool events, ignores non-JSONL files, skips malformed sessions) |
| -- | `bench_test.go` | Performance benchmarks for parsing, listing, and searching |
### Test Naming Convention
Tests use a `_Good`, `_Bad`, `_Ugly` suffix pattern:
- **`_Good`**: happy path; valid inputs, expected successful output.
- **`_Bad`**: expected error conditions or graceful degradation (malformed input, missing optional data).
- **`_Ugly`**: panic-inducing or extreme edge cases (missing files, nil input, binary garbage, path errors).
### Test Helpers
`parser_test.go` defines helpers for building synthetic JSONL content without external fixtures:
```go
// Fixed epoch: 2026-02-20 10:00:00 UTC, offset by seconds
ts(offsetSec int) string
// Marshal arbitrary map to a single JSONL line
jsonlLine(m map[string]any) string
// Convenience builders for specific entry types
userTextEntry(timestamp, text string) string
assistantTextEntry(timestamp, text string) string
toolUseEntry(timestamp, toolName, toolID string, input map[string]any) string
toolResultEntry(timestamp, toolUseID string, content any, isError bool) string
// Write lines to a temp .jsonl file, return the file path
writeJSONL(t *testing.T, dir string, name string, lines ...string) string
```
All test output uses `t.TempDir()`, which Go cleans up automatically after each test.
### Benchmarks
The benchmark suite generates synthetic JSONL files with a realistic distribution of tool types (Bash, Read, Edit, Grep, Glob):
| Benchmark | File size | Tool pairs |
|-----------|-----------|------------|
| `BenchmarkParseTranscript` | ~2.2 MB | 5,000 |
| `BenchmarkParseTranscript_Large` | ~11 MB | 25,000 |
| `BenchmarkListSessions` | 20 files, 100 pairs each | -- |
| `BenchmarkSearch` | 10 files, 500 pairs each | -- |
Run with:
```bash
go test -bench=. -benchmem ./...
```
### Coverage Targets
The current statement coverage is 90.9%. New contributions should maintain or improve this figure. When adding a function, add tests covering at minimum:
- The success path.
- Nil or zero-value inputs where applicable.
- At least one error path.
## Coding Standards
### Language
UK English throughout all source code comments, documentation, and commit messages. Examples: `colour`, `organisation`, `licence`, `initialise`, `centre`.
### Formatting and Lint
Code must be formatted with `gofmt`. The project uses `golangci-lint` with the following linters enabled (see `.golangci.yml`):
- `govet`, `errcheck`, `staticcheck`, `unused`, `gosimple`
- `ineffassign`, `typecheck`, `gocritic`, `gofmt`
Both `go vet ./...` and `golangci-lint run ./...` must be clean before committing.
### Types and Declarations
- Use explicit types on struct fields and function signatures.
- Avoid `interface{}` in public APIs; use typed parameters where possible.
- Handle all errors explicitly; do not use blank `_` for error returns in non-test code.
- Exported declarations must have Go doc comments beginning with the identifier name.
### Imports and Error Handling
- Do not import `errors` or `github.com/pkg/errors` in non-test Go files; use `coreerr.E(op, msg, err)` from `dappco.re/go/core/log`.
- Do not reintroduce legacy `forge.lthn.ai/...` module paths; use `dappco.re/go/core/...` imports.
### Test Naming
Test functions should follow `TestFunctionName_Context_Good/Bad/Ugly`.
The conventions test suite checks test naming, banned imports, and exported usage comments during `go test ./...`.
### File Headers
Source files should carry the SPDX licence identifier:
```go
// SPDX-Licence-Identifier: EUPL-1.2
package session
```
### Licence
EUPL-1.2. All new source files must include the SPDX header. By contributing, you agree that your contributions will be licensed under the European Union Public Licence 1.2.
## Commit Guidelines
Use conventional commits:
```
type(scope): description
```
Common types: `feat`, `fix`, `test`, `refactor`, `docs`, `chore`.
Examples:
```
feat(parser): add ParseTranscriptReader for streaming parse
fix(html): escape data-text attribute value
test(analytics): add latency calculation edge cases
docs(architecture): update scanner buffer size
```
All commits must include the co-author trailer:
```
Co-Authored-By: Virgil <virgil@lethean.io>
```
`go test ./...` must pass before committing.
## Adding a New Tool Type
1. Define an input struct in `parser.go`:
```go
type myToolInput struct {
SomeField string `json:"some_field"`
}
```
2. Add a `case "MyTool":` branch in `extractToolInput` that unmarshals the struct and returns a human-readable string.
3. Add a corresponding case in `html.go`'s input label logic (inside `RenderHTML`) if the label should differ from the default `"Command"`. For example, if MyTool targets a URL, use `"Target"`.
4. Add a case in `video.go`'s `generateTape` switch if the tool should appear in VHS tape output.
5. Add tests in `parser_test.go`:
- A `TestExtractToolInput_MyTool_Good` test for `extractToolInput`.
- An integration test using `toolUseEntry` + `toolResultEntry` to exercise the full parse pipeline.
## Adding Analytics Fields
`analytics.go` is a pure computation layer with no I/O. To add a new metric:
1. Add the field to the `SessionAnalytics` struct.
2. Populate it in the `Analyse` function's event iteration loop.
3. Add a row to `FormatAnalytics` if it should appear in CLI output.
4. Add a test case in `analytics_test.go`.
## Module Path and Go Workspace
The module path is `dappco.re/go/core/session`. If this package is used within a Go workspace, add it with:
```bash
go work use ./go-session
go work sync
```