- replace .opencode/agents/*.md with the cleaned alkssh versions, adapted to alkforge constraints (coordinator prompt, implementation conventions, poc dir name, fail-closed feature wording) - adopt the alkssh sdd_process.md (project name alkforge) - write AGENTS.md for the assembly: family stack conventions, no-deformation rule, alkvault/no-env posture, fail-closed door features, alkgen SSR rules, license hygiene, and the em-dash rule (convention 16) - placeholder README pending the 0.1.0 push Verified: no em dashes or spaced-hyphen separators in AGENTS.md, README.md, docs/, .opencode/agents/
14 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 wire-relevant or semver-relevant public API (the
user-facing surface alkforge presents becomes a de facto contract once
a deployment consumes it; ledger-gated family contracts are
recorded in
/workspace/@alkdev/alkfamily/docs/ledger.md; see the ADRs indocs/architecture/decisions/once they exist) - 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 / assembly crate)
This is alkforge, the family's near-term assembly (family ADR-002): a
minimal forge replacing the gitea deployment, composed from family
crates rather than re-implementing them. The stack it embeds: alkgit
(+ alkgit-substrate: alkstore + alkblobs) as the git core, alkssh
(git feature) as the SSH door, alkhttp as the HTTP gate,
alkgen as the SSR engine, alkstore/alkblobs as the persistence
and reactive layer, alkvault for secrets, alkcall as the
substrate underneath, with reverse-proxy as the standing front
door. Assemblies bind layers; they never deform them. The conventions
below apply to all work in 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. -
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. The forge serves long-lived git conversations and web traffic; do not introduce blocking I/O on the async path. Usetokio::syncprimitives (oneshot,mpsc) for correlation;parking_lotfor short-held internal locks. -
The no-deformation rule (the binding constraint): alkforge is an assembly on alkgit's core; it may not pull, patch, or fork the core to fit assembly needs (family ADR-002 decision 3). Gaps and asks are filed upstream first (alkcall is ours; alkgit/alkgen/alkssh likewise; see the ledger's pin policy). A shortcut inside alkforge that would otherwise deform a family crate is a family-level decision, by ADR. Third-party-upstream posture: file asks upstream; never silently absorb a workaround.
-
Secrets via alkvault, never env vars: no secret material on the wire, in plaintext at rest, or in environment variables (family ADR-002 decision 5). Configuration is not an env-var sprawl; stored credentials live in alkvault, config stores hold references, and outbound credentials are injected at the assembly layer (
OperationContext.capabilities). -
Visible-surface = authorized-surface: every endpoint and operation exposed to the outside is checked against alkcall's
AccessControl::check(peer_identity); ops withVisibility::Internalare never wire-callable. "OSS projects are public read" is an explicit, explicitly granted capability per repo and per door, never a global anonymous default. This is a spec-level invariant. -
Fail-closed capability features (the alkssh feature-graph shape at the doors): every optional capability of the doors alkforge mounts is a cargo feature and unfeatured requests decline fail-closed; a build serving only public clones is structurally incapable of serving shells, tunnels, or sftp (family ADR-003 decision 3, which alkforge's ssh endpoint inherits by construction). Nothing is advertised that isn't served; every decline path is contract territory and gets tested.
-
Git transport belongs to the doors, not to alkforge: vanilla git over SSH arrives via alkssh's git feature; git over HTTP arrives via alkhttp's git feature; the payload is served by alkgit's protocol halves. alkforge never speaks pkt-line or packetizes git itself; no shelling out to the
gitbinary in any serving path. -
SSR via alkgen, one engine: web pages render through alkgen (handlebars-rust templates over serde contexts; alkgen ADR-006). Templates are code: versioned in-repo, strict mode (a missing variable is a render error, not silently-blank output), golden-output render tests, deterministic rendering (same templates + same context → byte-identical HTML). No client-side framework in the minimal forge; no rendering machinery outside alkgen.
-
Persistence via alkstore, objects via alkblobs: relational/user state (accounts, repos, permissions, mirror config) lives in alkstore (sqlite or postgres backend, the contract suite is the backend-acceptance gate); large binary blobs ride alkblobs (git LFS media keyed per alkgit ADR-022). No bespoke database code and no hand-rolled reactive plumbing alongside the alkstore pattern.
-
Bounded resources: every externally reachable door carries wall-clock, size, and concurrency limits (alkssh's per-session limits, alkhttp's bounded body/streaming posture). The forge is internet-facing; unbounded loops and unbounded buffers are bugs.
-
Wire formats are stable one-way doors: alkforge introduces no new wire protocol of its own in v1; the doors' ALPNs (
alk/git,alk/ssh) and the call/channels wire are already ledger-gated (ledger rule 1, alkcall ADRs 013/034). If a future surface needs a pin, it gets one via ADR before the first consumer exists. -
License hygiene: this project is MIT OR Apache-2.0. Check before adopting any new dependency, and never copy or derive code from incompatible-licensed reference projects. Reference code is for reading and facts, never copying. gitea's code is deliberately a black box: read its behavior from the outside, never its source (it is MPL-2.0 and this rule also keeps the family's license posture clean).
-
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 from the crate root (the alktty crate-root pattern). Public API surface islib.rsre-exports; the assembly's composition (doors, repos, identity, config) is its spine. -
No em dashes in prose. Do not use the em dash (U+2014) in source doc comments, markdown docs (
docs/,README.md,CHANGELOG.md,AGENTS.md), agent prompts under.opencode/agents/, or commit messages. Restructure instead: split the sentence, use parentheses, commas, a colon, or a semicolon, whichever reads naturally, and vary the choice rather than converting every instance the same way. A spaced hyphen (-) is not an acceptable substitute; it reads as a different but equally recognizable writing tell. The same rule covers en dashes used as clause separators, though en dashes inside numeric ranges keep their normal meaning. Hyphens in compound words (fail-closed,client-side) are untouched. In test fixtures and scripts the rule applies to prose-y content only: comment lines are rewritten too, while fixture data, wire examples, and tool transcripts survive verbatim.
Verification Commands
Run these before committing. All must pass.
cargo test # full suite
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
grep -rn '—' src/ docs/ README.md CHANGELOG.md AGENTS.md \
.opencode/agents/ tests/ scripts/ # must be zero hits;
# see convention 16
If feature flags are added, also run cargo test --all-features and
cargo clippy --all-features --all-targets -- -D warnings. Once the
door feature graph lands, each disabled capability's decline path gets
a test; the fail-closed invariant is contract territory on both sides
of every feature.
Task files (taskgraph)
Any file under tasks/ is a taskgraph task. Frontmatter is required
(id, name) and its enum fields are closed sets (taskgraph
0.1.3): status: pending|in-progress|completed|failed|blocked (never
done or finished), scope: single|narrow|moderate|broad|system,
risk: trivial|low|medium|high|critical, impact: isolated|component|phase|project, level: planning|decomposition|implementation|review|research. A file without
frontmatter is silently dropped from the graph (validate does not flag
it); prose-only trackers belong under docs/. Run taskgraph validate after writing or editing anything under tasks/; full field
semantics live in docs/sdd_process.md §"Task File Format" /
§"Categorical Estimates".
Lifecycle Status
The project is in SDD phase 0 (exploration; docs only, no
architecture committed, no implementation). Family ADR-002 (2026-10-20,
Accepted) records the structural decision already made: alkforge is the
gitea replacement, an assembly on alkgit's core under the
no-deformation rule, with the family's security posture (alkvault
secrets, no env vars, minimal wire surface) as a day-one design
requirement. OQ-06 (this repo's phase 0 shape) and OQ-07 (the alkgit
asks list) are the family open questions this repo's phase 0 feeds; see
/workspace/@alkdev/alkfamily/docs/architecture/open-questions.md.
Phase 0 output lands in docs/research/ (the phase 0 document is the
current work item); docs/architecture/ + ADR-001 come with phase 1.
POC code never merges to main; two modes exist: branch mode (POC builds
on repo code; git branch, findings merged into research docs, branch
dropped) or standalone mode (POC independent of repo code; scratch
project at /workspace/<poc-name>). POCs have relaxed constraints
(comments/unwrap acceptable) since they are exploration tools, not
production code.
Architecture Context
docs/sdd_process.md: the SDD process (phases, roles, task format)docs/research/: phase 0 research (current source of truth for design direction);phase-0.mdis the current work item- family docs:
/workspace/@alkdev/alkfamily/docs/;architecture/decisions/002-alkforge-gitea-replacement.mdis the structural decision this repo builds from (assembly on alkgit's core, no-deformation, security posture);layering.mdandledger.mdrecord where alkforge sits and what is wire-stable;architecture/open-questions.mdOQ-06/OQ-07 hold the phase 0 scope - Sibling crates (published, MIT OR Apache-2.0, each with per-repo
AGENTS.md +
docs/architecture/decisions/):alkcall 0.10.1(call + channels RPC with ACL; the substrate and the identity/ACL model),alkgit 0.1.0+alkgit-substrate 0.1.0(git protocol overalk/git; objects in alkblobs, refs in alkstore;docs/architecture/doors.mdrecords the alkhttp/alkssh door shapes),alkssh 0.2.0(russh-wrapped SSH adapter,alk/sshALPN; the git channel-type mapping is itsgitcargo feature),alkhttp 0.8.1(HTTP adapter shape; the HTTP gate),alkgen 0.1.0(SSR engine;docs/research/phase-0.mdthere records the alkforge SSR branch and OQ-GN-09),alkstore 0.1.0/alkblobs 0.2.2(persistence + blobs),alkvault 0.2.0(secrets;decryptreturnsZeroizing<String>),alktls 0.1.0(TLS support),reverse-proxy(produced, outside the alk* namespace; the standing front door). - The gitea deployment alkforge replaces: read its actual usage (not
its source, see convention 13); the load-bearing surface enumeration
is phase 0 work item #1 (see
docs/research/phase-0.md).