- Cargo workspace with 5 sub-crates: alkgit-core (storage), alkgit-transport (smart protocol), alkgit-http, alkgit-ssh, alkgitd (binary); sha1 pinned through the gix stack, sha256 passthrough feature - docs/research/: vision, gitoxide alignment, alk stack fit, server-side protocol inventory, license/reference policy, POC plan - AGENTS.md: conventions mirroring alkcall (no comments, thiserror, tokio, no secrets on wire/at rest, visible-surface=authorized-surface, gitoxide-only serving path, bounded resources, registry-resolved repos) Verified: cargo build, clippy -D warnings, fmt, test, check --all-features
9.7 KiB
AGENTS.md
Operating instructions for opencode agents working in this repo. opencode
auto-loads this file as instructions, overriding the built-in defaults for
this project. Custom agents in .opencode/agents/ inherit these rules
unless their own prompts say otherwise.
Git Workflow
Commit and push when reasonable. When a change is complete and
verified (build + lint + tests pass), commit and push to origin/main
without asking. This overrides the built-in default of "only commit when
explicitly asked."
Commit in small, focused units — one commit per unit of work (a fix, a feature, a doc change), not one large commit covering many topics. Push regularly so work is never stranded locally.
The workflow:
- Make the change
- Verify:
cargo test,cargo clippy --all-targets -- -D warnings,cargo fmt --check,cargo doc --no-depsif docs changed - Inspect
git statusandgit diffbefore staging — stage only the intended files, never secrets - Write a concise commit message in conventional commits style:
<type>(<scope>): <summary>— types arefeat,fix,docs,chore,refactor,test,perf,build,ci; the scope is optional. For multi-point changes, use a summary line plus a body with bullet points and a verification block. git push origin main- Report the commit hash and the verification summary
Exceptions — do not commit or push without asking:
- The change is exploratory / speculative (you're not sure the user wants it kept)
- The user is actively reviewing the diff and may ask for changes
- The change touches published wire formats or semver-relevant public API
(crates will be published to crates.io once architecture lands; see the
ADRs in
docs/architecture/decisions/for the stable-contract list once it exists) - You'd be force-pushing, amending a published commit, creating an empty commit, or skipping hooks
Never commit secrets, keys, or credentials. If a commit fails or hooks reject it, fix the issue and create a new commit — do not amend the failed one.
Git identity is preconfigured (glm-5.3-flash <glm-5.3-flash@alk.dev>).
Do not change git config, skip hooks, or use git commit -i.
Project Conventions (Rust / git-server workspace)
This is alkgit — a self-hosted git server: repository storage, the git
smart protocol served over HTTP and SSH interfaces, and the alkgitd
binary that assembles it on the alk stack (alkcall, alkhttp, alktls,
alkvault) and gitoxide (gix). The project is a cargo workspace of
sub-crates under crates/. The conventions below apply to all work in
crates/*/src and tests/. They mirror
.opencode/agents/implementation-specialist.md §Project Conventions and
are repeated here so they apply to every session, not just spawned
implementation agents.
-
No comments in code unless the user explicitly asks. This is a project-wide convention. Doc comments (
///,//!) are fine and expected on public API. Inline//comments only when the user asks or when a non-obvious safety/correctness constraint would otherwise be missed (e.g., "ACL must run before the first advertised ref line — ref names leak repository existence"). -
Error handling —
thiserrorfor library error types. No panics in library code. Nounwrap()orexpect()outside tests. If you reach forunwrap, the error path wasn't specified — stop and decide what should actually happen. For poisonedRwLock/Mutex, useunwrap_or_else(|e| e.into_inner())so a panic in one operation does not cascade to other operations. -
tokiois the async runtime — all I/O is async. Git protocol sessions are long-lived streaming conversations; do not introduce blocking I/O on the async path. Usetokio::syncprimitives (oneshot,mpsc) for correlation;parking_lotfor short-held internal locks. -
No secret material on the wire or in plaintext at rest — the git protocol, the call protocol payloads, and the metadata store carry no private keys, API keys, tokens, or decrypted credentials. Stored credential material lives in alkvault; metadata holds vault references. Outbound credentials flow through
Capabilitiesinjected at the assembly layer →OperationContext.capabilities→ handler. See the no-env-vars invariant below and the alkcall ADRs (ADR-010, ADR-017). -
No-env-vars invariant — no handler reads outbound credentials from any source other than
OperationContext.capabilities. The credential injection path is vault → assembly layer →Capabilities→ handler. This is a spec-level invariant, not a runtime convention. -
Visible-surface = authorized-surface — the reason this project exists (a self-hosted gitea got pwned via an internal API reachable over the internet). Every operation exposed on the wire is gated by alkcall's
AccessControl::check(peer_identity); ops withVisibility::Internalare never wire-callable; no endpoint exists that a caller cannot see themselves authorized for. There is no unauthenticated endpoint unless a repo is explicitly public, and push is always authenticated. This is a spec-level invariant. -
gitoxide for git primitives, no shelling out — storage and pkt-line come from the gix crates (pinned in the workspace manifest). The serving path never spawns the
gitbinary (no GPL dependency, no process-injection surface). The server half of the smart protocol is ours; seedocs/research/git-protocol.mdfor the inventory. -
License hygiene — this project is MIT OR Apache-2.0. Never copy or derive code from MPL-2.0-licensed reference projects; reading gitoxide (MIT OR Apache-2.0) is fine. The policy and the rationale live in
docs/research/reference-policy.md— do not name the incompatible-licensed project in docs, README, or commit messages. -
Wire formats are stable one-way doors — once a wire surface is published (the git smart protocol is defined upstream and not ours to change; any alkgit-specific alkgit↔alkgit framing will get ADRs when it exists), its shape must not change. Protocol capability advertisement is honest: never advertise what we don't serve.
-
Feature flags — optional surface is feature-gated:
sha256(hash algorithm passthrough;sha1is the default and pinned in the workspace manifest) andacme(alktls ACME wiring in the binary). Base crates compile lean. Verify bothcargo test(default) andcargo test --all-featurespass if features are added. -
Bounded resources — every protocol session carries wall-clock, size, and round limits (max negotiation rounds, max receive-pack size, session timeout). Git servers are internet-facing; unbounded loops and unbounded buffers are bugs.
-
Repo identity is registry-resolved — repo names arriving on the wire are IDs resolved against a server-side registry to configured storage roots. Wire-supplied names are never used as filesystem paths directly (path traversal is a protocol-level input, not a config value).
-
Naming — Rust standard:
snake_casefor functions/variables/ modules,PascalCasefor types/traits,SCREAMING_SNAKE_CASEfor constants. -
Module structure — one module per file under
src/, re-exported fromsrc/lib.rs. Public API surface islib.rsre-exports. Workspace members undercrates/:alkgit-core(storage),alkgit-transport(smart protocol),alkgit-http(http front door),alkgit-ssh(ssh front door),alkgitd(binary).
Verification Commands
Run these before committing. All must pass.
cargo test # full suite (workspace)
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo doc --no-deps # if docs changed
cargo publish --dry-run --allow-dirty # before a release (per crate)
If feature flags are added, also run cargo test --all-features and
cargo clippy --all-features --all-targets -- -D warnings.
Lifecycle Status
The project is in SDD phase 0 (exploration) — see
docs/sdd_process.md and docs/research/README.md. There is no
architecture yet (docs/architecture/ is empty; ADR numbering starts at
001 when phase 1 begins). POCs land under .worktrees/research/ or as
scratch dirs; their findings go into docs/research/. Until phase 1
produces ADRs, "the architecture says" has no referent — cite
docs/research/ docs instead.
Architecture Context
docs/research/— phase 0 research (current source of truth for design direction):vision.md— vision, guiding principles, non-goalsgitoxide.md— gix capability/version alignment (sha1 feature pinning is mandatory;default-features = falsewithout a hash feature does not compile)alk-stack.md— alkcall/alkhttp/alktls/alkvault integration surfacegit-protocol.md— server-side smart-protocol inventory (what we own: advertisement, ls-refs, fetch negotiation, receive-pack)reference-policy.md— license/reuse policy (MPL-2.0 prior art: facts only, never code)pocs.md— POC-1..3 plan gating phase 0 completion
- Sibling crates (published, MIT OR Apache-2.0):
alkcall 0.8(call + channels RPC with ACL;BiStreamis the git-session substrate),alkhttp 0.5(HTTP serving + call adapters),alktls 0.1(rustls + ACME),alkvault 0.1(secret encryption). Their architecture docs live in their own repos; alkcall ADRs referenced above are at/workspace/@alkdev/alkcall/docs/architecture/decisions/. - gitoxide reference clone:
/workspace/gitoxide(matches published 0.87.1 plus a few unreleased commits — pin crates.io versions; treat the clone as a reading aid only, never a path dependency).