Two documents are the source of truth for how the project should be built:
- DESIGN.md - Architecture design document covering the overall structure, goals, and design rationale.
- SPEC.md - Detailed technical specification covering configuration, CLI commands, flags, store, sync engine, TUI, plugin system, output formats, error handling, and the phased implementation plan.
Together they run to roughly 10,000 lines, so start each task from their tables of contents and read every section your change touches (and the sections those cross-reference) before planning. Code that contradicts them is wrong even if it compiles.
All changes MUST be reflected in SPEC.md and DESIGN.md before implementation begins:
- Propose the change by describing what needs updating in the spec/design docs.
- Update SPEC.md and/or DESIGN.md with the agreed-upon design.
- Implement the code changes to match the updated spec.
Never implement code that contradicts or isn't documented in the spec. If the spec is wrong or incomplete, fix the spec first.
If a go.work file is present in the repository root, agents MUST read it and take into account all replace directives defined there. Local module replacements affect import resolution and must be followed when navigating or editing dependency code.
This repository currently has no go.work of its own, but a parent directory does and Go discovers it automatically. That workspace does not use this module, so a plain go command resolves against it and fails before compiling. Every direct go invocation (go build, go test, go list, go vet) MUST therefore run with GOWORK=off — which is what .envrc exports in a direnv shell and what CI sets globally in .github/workflows/ci.yml.
All commit messages MUST follow the Conventional Commits specification:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
Types (required):
feat— a new featurefix— a bug fixdocs— documentation-only changesstyle— formatting, missing semicolons, etc. (no code change)refactor— code change that neither fixes a bug nor adds a featureperf— performance improvementtest— adding or correcting testsbuild— changes to the build system or dependenciesci— changes to CI configurationchore— other changes that don't modify src or test files
Rules:
- Subject line: lowercase, imperative mood, no period at the end, max 72 characters
- Body: wrap at 72 characters, explain what and why (not how)
- Breaking changes: add
!after type/scope (e.g.,feat!:) or aBREAKING CHANGE:footer
- Language: Go
- Module path:
pkg.akt.dev/akt - Config format: YAML with 2-space indentation
- Config management: Viper (reading, writing, env binding, flag binding, live-reload)
- Global variables: Default to no package-level
varusage. If you believe a global is necessary, you MUST ask for direction first (you may offer alternatives or suggestions). Only proceed after explicit approval and, if needed, a spec update. init- everyinitusage must be explicitly approved- No reading flags into Go variables: Do not use
cmd.Flags().StringVar(&myVar, ...). Bind flags to Viper keys; read values from Viper or the cobra command at point of use. - Addresses: Never truncate or shorten addresses in output. Addresses (bech32, operator, consensus) must always be displayed in full. Truncation risks ambiguity and breaks copy-paste.
- Amounts and prices: All micro-denominated values (
u-prefixed denoms) must be scaled to the most readable unit in pretty output: base (>= 1M micro, e.g.,5.3 AKT), milli (>= 1K micro, e.g.,3 mAKT), or micro (< 1K, e.g.,500 uAKT). Trailing zeros must always be stripped. This applies uniformly to every pretty output: balances, prices, escrow, staking, rewards, fees. UseFormatCoin()frominternal/output/pretty/helpers.go— never format amounts manually. - Rendering parity: The pretty-printed output of a single-shot CLI command (e.g.
akt q bme status) and the corresponding section of theakt monitordashboard must be visually identical. Both paths must call the sameRender*functions frominternal/output/pretty/. Never duplicate formatting logic in a full-screen view — always delegate to the shared renderers. This applies to every consumer of the renderers, includinginternal/tui/. - Build:
GOWORK=off make akt— the compiled binary is placed in.cache/bin/. The Makefile defaults the direnv-managed cache variables to the same.cachelayout when they are unset, so this command also works in a bare checkout. - Tests:
GOWORK=off go test ./.... Thee2e/package shells out to the compiled binary at.cache/bin/akt, so build before running the full suite; to run unit tests alone, exclude it the way CI does (go test $(go list ./... | grep -v /e2e)withGOWORK=offexported). The localnet and live-Console e2e tests self-skip unlessAKT_E2E_LOCALNET/AKT_E2E_RPCorAKT_E2E_CONSOLE_API_KEYis set.
- Positional-primary commands: A command's primary value(s) MUST be positional arguments; flags are optional overrides and may never be required (SPEC.md §3.8).
TestNoUnapprovedRequiredFlagsininternal/cli/required_flags_test.gowalks the entire command tree and fails on any flag marked required, so a new required flag breaks the build unless it is added to therequiredFlagAllowlistthere with a comment justifying it. Adding an allowlist entry is a last resort, not a substitute for accepting the value positionally. - Disabled-flag markers: During the positional-only UX trial, a flag that duplicates a positional is commented out — never deleted — so it can be restored verbatim. Use the uniform marker: a comment reading
// FEEDBACK(2026-07): --<flag> disabled for the positional-only UX trial (use the positional form instead). Restore by uncommenting if users ask for the flag form back.immediately followed by the original registration line, commented out and otherwise unchanged (seeinternal/cli/console/wallet.goandinternal/cli/chain/flags/deployment.go). The same marker prefixes the commented-out read sites (cmd.Flags().GetString(...)), with the wording adjusted to name the positional that replaces the flag. Deleting the registration instead of commenting it out is wrong while the trial runs. - Capability annotations: A command that needs a transport MUST declare it with the
capability.AnnotationKey(akt.requires) cobra annotation frominternal/capability—chain-query,chain-tx,provider, orconsole— with|separating alternatives (e.g.chain-tx|consolefor workflow commands that run on either rail). Every new command group registered ininternal/cli/root.gothat talks to a chain RPC, a provider gateway, or the Console API needs the right annotation; without one the group is offered in configurations that cannot run it and fails deep inside the transport instead of failing fast with a remedy (SPEC.md §2.10). - Transports, not per-rail commands: A new action is defined exactly once as a workflow definition in
internal/workflow/builtin/and MUST work on every rail — the CLI surface is generated from the definition and the rail is chosen per context at execution time. Per-rail behavior belongs ininternal/transportand the adapters it wraps (internal/workflow/adapters/), never in the command layer. Cross-rail user input is normalized in exactly one place: every deposit form goes throughtransport.ParseDeposit, so identical arguments work on the chain and console rails (SPEC.md §2.3 Transports, §7.4–§7.5). - Action log coverage: Every state-changing action MUST record an action log entry, and read-only queries MUST NOT (SPEC.md §5.6). Hook a new mutating command into an existing write path rather than logging ad hoc: chain transactions through the tx decorator in
internal/cli/chain/actionlog.go, context management throughrecordContextActionininternal/cli/context/actionlog.go, provider gateway operations throughrecordProviderActionininternal/cli/provider/actionlog.go, Console API calls throughClient.WithActionLogininternal/console/client.go, and workflow steps through the engine'sLogger(internal/workflow/actionlog.go).
- Plan before implementing. Every solution — regardless of agent mode (plan, build, or any other) — MUST be presented as a plan and approved before any code changes are made. Do not skip the planning step even if the change seems trivial.
Every ordinary PR MUST add a uniquely named .changelog/<slug>.<category>.md
fragment describing the feature or issue and its implementation. Categories
are added, changed, fixed, deprecated, removed, and security. Use
Markdown bullets and a PR number or descriptive slug; a PR number is optional.
Do not edit AICHANGELOG.md or another PR's fragments during ordinary work.
Run GOWORK=off make changelog-check CHANGELOG_BASE=origin/main before review.
Before tagging a release, run GOWORK=off make changelog-assemble in a separate
release-preparation PR and commit the resulting archive update and consumed
fragment deletions together. Merge that PR before tagging. CI checks ordinary
PRs for new fragments and accepts archive edits only when they exactly match
assembly of the base branch's fragments. See .changelog/README.md
for examples and migration instructions. This requirement applies to small
changes too.
- Never guess or hack. If something is not working, and you don't understand why, ask for instructions. Do not invent workarounds, nil-guards, or fallback paths to mask a problem you haven't fully traced.