# P4LF Developer Guide

This guide is for developers working on p4lf itself (as opposed to end users
installing/operating it — see `README.md` and `install.sh` for that). It
covers required tooling, the build system, versioning, and the Perforce
stream/promotion workflow.

## Required Tools

* **Go 1.20+** — install from https://go.dev/dl/ if not already present.
  Verify with `go version`.
* **make** — the project's build/test/release automation is driven entirely
  through the `Makefile`; no other build tool is required.
* **gofmt** — ships with the Go toolchain; used by `make fmt` / `make fmt-fix`.
* **A P4 client** — mapped to the `//p4lf/dev` stream (or a `//p4lf/dev_TAG`
  stream parented by it, for experimental/disposable work). See
  [Perforce Workflow](#perforce-workflow) below.
* **curl** — only needed if testing `install.sh` end-to-end (it downloads
  release artifacts from workshop.perforce.com).

No other dependencies are required; p4lf's Go module has no third-party
runtime dependencies beyond the Go standard library (check `go.mod` if that
ever changes).

## Building

All builds are driven by the `Makefile`. Common targets:

```bash
make build              # Build for the current platform → bin/p4lf
make build-linux        # Cross-compile Linux amd64 (primary deployment target)
make build-linux-arm64  # Cross-compile Linux arm64
make build-darwin       # Cross-compile macOS arm64 (Apple Silicon dev machines)
make build-darwin-amd64 # Cross-compile macOS Intel
make build-all          # All of the above
```

Each binary embeds the current `SemVer` (see [Versioning](#versioning) below)
via `-ldflags`, plus Perforce keyword expansion (`$Id$`, `$Change$`,
`$DateTime$`) baked into `internal/version/version.go` at submit time. Check
what's embedded in a given binary with:

```bash
bin/p4lf -version
# or, without running the binary:
strings bin/p4lf | grep '\$'
```

## Testing and Quality Checks

```bash
make test          # go test -race -count=1 ./...
make test-verbose   # same, with -v
make vet            # go vet ./...
make fmt            # Check gofmt compliance (fails if files need formatting)
make fmt-fix        # Apply gofmt and rewrite files in place
make check          # vet + fmt + test — run this before every submit
```

Always run `make check` before submitting a changelist. CI/reviewers expect
a clean `make check` as a baseline.

## Versioning

p4lf follows [Semantic Versioning](https://semver.org/) (Major.Minor.Patch).

The version is hand-maintained in `internal/version/version.go`:

```go
var SemVer = "1.0.4"
```

**To bump the version:**

1. `p4 edit internal/version/version.go`
2. Update the `SemVer` string, e.g. `"1.0.4"` → `"1.0.5"`.
3. Run `make check` to confirm nothing is broken.
4. Submit as part of (or immediately alongside) the change(s) that justify
   the bump.

Guidelines for which part to bump:

* **Patch** (`X.Y.Z+1`) — bug fixes, doc-only changes, internal refactors
  with no behavior change visible to operators.
* **Minor** (`X.Y+1.0`) — new config options, new features, backward-
  compatible behavior changes.
* **Major** (`X+1.0.0`) — breaking changes (e.g. config format changes,
  removed options, changed on-disk state/chunk file formats).

The version can also be overridden at build time without touching the
source, e.g. for a CI/release pipeline that wants to stamp a different value:

```bash
make build VERSION=1.2.3
# equivalent to:
go build -ldflags "-X workshop.perforce.com/p4lf/internal/version.SemVer=1.2.3" ...
```

`version.go` also embeds Perforce keywords (`$Id$`, `$Change$`, `$DateTime$`)
which are expanded automatically by Perforce on submit — these are informational
(depot path/revision, changelist, submit date) and require no manual
maintenance. Do not hand-edit the keyword lines; just leave them as `$Id$` etc.
in new files and let Perforce expand them.

## Perforce Workflow

See `ai/P4WorkflowNotes.md` for full details on streams, jobs, and how AI
sessions interact with version control. Summary for day-to-day development:

* **`//p4lf/dev`** — the main development stream. Iterate freely here; the
  quality bar is "passes `make check`", not "release-ready". This is where
  we are currently working.
* **`//p4lf/dev_TAG`** — optional short-lived streams parented by `dev`, for
  prototyping, R&D, or disposable/experimental work (`TAG` is a job-style
  identifier, e.g. `P4LF-3`). Use only when a change is risky/experimental
  enough to want an easy way to discard it.
* **`//p4lf/main`** — populated only via a **Copy Up** from `dev`, after the
  change has had some testing in `dev`. `main` is expected to always be
  usable as "latest release" — e.g. `install.sh` defaults to installing
  from `main`, and `https://workshop.perforce.com/download/p4lf/main/bin/...`
  is treated as the stable download URL.
* **`//p4lf/rX.Y`** — release streams, populated via a Copy Up from `main`
  when cutting a release. Emergency fixes can be made directly in a release
  stream if needed, but must then be **Merged Down** to `main`, then to
  `dev`, and from there to any active `dev_TAG` streams.

### Promoting dev → main

There are two equally valid ways to work across streams:

* **A dedicated workspace per stream** — e.g. one client mapped to
  `//p4lf/dev` and a separate client mapped to `//p4lf/main`. This is the
  more traditional approach and tends to be simpler and more predictable for
  larger or more complex workspaces.
* **A single stream-switchable client**, repurposed on the fly with
  `p4 switch`. This works fine for a small project like this one (it's how
  this promotion was actually done), but `p4 switch` has more edge cases and
  gotchas at larger scale or with more complex workspace mappings, so don't
  treat it as the only or default approach — use whichever fits your setup.

If using `p4 switch`:

```bash
p4 switch -l          # list available streams; '*' marks the current one
p4 switch main        # switch this client to //p4lf/main
```

If using a dedicated `main` workspace instead, just `cd` into it — no
`p4 switch` needed.

Preview what would be promoted before doing it for real:

```bash
p4 copy -n -S //p4lf/dev    # dry run — shows what would sync/integrate/branch
p4 copy -S //p4lf/dev       # opens the files for integrate/branch
p4 submit -d "Promote vX.Y.Z from dev to main: <summary>."
```

Only promote code from `dev` that has passed `make check` and had at least
some manual/integration testing. `main` should always be safe to treat as
the current stable release.

**Important — rebuild binaries in `main` after promoting.** Because `bin/`
is an `isolate`d path (see [The `ai/` Folder](#the-ai-folder) below), the
copy-up above will **not** bring `main`'s binaries up to date — the copy
only touches source/docs/config files. You must rebuild and submit binaries
separately, from within the `main` stream:

```bash
make build-all
p4 reconcile bin/...    # or: p4 rec bin/...
p4 submit -d "Rebuild platform binaries for vX.Y.Z in main." bin/...
```

Then verify:

```bash
./bin/p4lf-<platform> -version   # confirm the new SemVer and CL are stamped in
p4 status ...                    # confirm the workspace is clean (no reconcile needed)
```

When you're done, switch back to `dev` to resume development (if using
`p4 switch`; if using a dedicated workspace per stream, just `cd` back):

```bash
p4 switch dev
```

### Cutting a release stream

Run from the new `//p4lf/rX.Y` workspace, after promoting to `main`:

```bash
p4 copy //p4lf/main/... //p4lf/rX.Y/...
# Then, in the rX.Y workspace, build and add platform binaries:
make build-all
p4 add -t binary bin/p4lf-linux-amd64 bin/p4lf-linux-arm64
p4 add -t binary bin/p4lf-darwin-arm64 bin/p4lf-darwin-amd64
p4 submit -d "Release vX.Y.Z: add platform binaries."
```

See the `## Release` section of the `Makefile` for the exact commands, and
`make release VERSION=X.Y.Z` to build all platforms and produce `dist/`
tarballs locally for testing before cutting a real release stream.

### Jobs

Perforce jobs (e.g. `P4LF-3`) are used in place of a separate issue tracker.
See `ai/P4WorkflowNotes.md` for the job spec format, `Type`/`Severity`
values, and how to link a job to a changelist with `p4 fix -c <change>
<job>`.

## The `ai/` Folder

The `ai/` directory (this repo's root also symlinks `ai/AGENTS.md` to
`copilot-instructions.md`) holds AI-assistant working notes: the current
`AGENTS.md` task brief and dated session logs (e.g.
`ai/session_log_2026-08-25.md`). These are **not part of the shipped
product** — they exist purely to help a human or a future AI session pick up
context on prior work, decisions, and rationale.

Because of that, `ai/` (along with `bin/`) is configured as an **isolated**
path in the `//p4lf/main` stream spec (`Paths:` includes `share ...` plus
`isolate ai/...` and `isolate bin/...`). This means:

* `ai/` content is versioned in `//p4lf/dev` (and any `dev_TAG` streams
  parented by it), but a Copy Up from `dev` to `main` will **not** bring
  `ai/` along — it stays isolated to the dev side of the tree.
* `bin/` is isolated for a different reason: it's expected to exist in every
  stream, but each stream builds and commits its own binaries rather than
  inheriting them from `dev` (e.g. `main` gets freshly built binaries as
  part of promotion/release, per the release workflow above).

Net effect: when reading `main` or a release stream, you'll see the product
source and binaries, but not the `ai/` session history — that history is
intentionally kept dev-side.
