- strict-mode matrix: missing variables (top-level, in {{#each}},
in {{#if}} then and else branches) fail typed through both
sinks, never silent; the write sink's no-atomicity contract
pinned by its row
- error-surface completeness: six failure paths each mapping to
exactly one typed variant; registrations never half-survive;
hostile context values render clean through both sinks and both
escape policies with no panic paths
- two-sink correctness: byte-identical output over the ssr-shaped
and code-shaped fixture postures, re-exercised over the proof
fixture files (goldens stay pinned by the proof binaries)
- AGENTS.md: the wasm release build becomes standing and
unconditional; the feature-matrix coincide-today posture and the
handlebars feature-inspection posture documented honestly
(ADR-003, ADR-009)
Verification: cargo test (all green), cargo test --all-features
(identical), clippy -D warnings clean, fmt clean, doc clean, wasm
release build clean, handlebars tree features default-only,
taskgraph validate clean
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,cargo build --target wasm32-unknown-unknown --releaseif dependency/wasm-relevant code 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 semver-relevant public API (this crate is headed to crates.io; once published, renames/removals/signature changes on public items are breaking, while additive changes are fine)
- 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 / template codegen crate)
This is alkgen, the codegen engine of the alk* stack: handlebars-rust
templates rendered over alktype BAST documents to produce code artifacts
(binary readers/writers/validators, protocol handlers), plus plain HTML
generation for alkforge's server-side rendering. Conventions 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. Template registration/render failures are data, not crashes: a malformed template or BAST input must produce a typed error, never a panic (generated code feeds alkcall, which accepts BAST from arbitrary peers). -
No
async: template rendering and codegen are synchronous, CPU-bound operations. No tokio, no async/await in the core. (If a future file-watch/rebuild service is wanted, that is an assembly- layer concern, not a lib concern.) -
WASM-clean: the crate must compile to
wasm32-unknown-unknown. No filesystem access in library code (handlebarsdir_sourcefeature is off-limits in lib code; templates come from strings orinclude_str!). No threads, nostd::time, no platform deps. If you reach for a new dependency, first verify it is wasm-compatible and worth the cost. -
serde_jsonpreserve_orderis load-bearing: alktype relies on it (field order in schema JSON determines packed byte order). Do not disable it and do not sort schema keys in contexts built for templates unless the template explicitly requires a sort. -
Strict template mode:
Handlebars::set_strict_mode(true)for any registry the lib renders with. A missing variable in a codegen template must be a render error, not silently-empty output (silent gaps become compile bugs in the consumer's build, far from the cause). alkminer-v1'sShaderBuilderis the precedent. -
Templates are code: templates live as
const &str(inline) orinclude_str!files, versioned in-repo, with render tests pinning their output. Changing a template changes generated code; treat template edits with the same care as source edits (tests must pin golden output before merging). -
Generated-code hygiene: generated artifacts start with a "Auto-generated by alkgen. DO NOT EDIT." banner (typebox-rs precedent). Rendering must be deterministic: same BAST + same templates + same options → byte-identical output (no HashMap iteration order leaking into generated code).
-
Naming: Rust standard:
snake_casefor functions/variables/ modules,PascalCasefor types/traits,SCREAMING_SNAKE_CASEfor constants. Template registry names: lowercase kebab or snake, one name per template kind. -
Module structure: one module per file under
src/, re-exported fromsrc/lib.rs. Public API surface is thelib.rsre-exports. -
No
unsafe: zero unsafe blocks. If a perf argument appears, the answer lives in the template/data-model layer, not in unsafe. -
No em dashes in prose. Do not use the em dash (U+2014) in source doc comments, markdown docs (
docs/,README.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. Vendored third- party files under.opencode/node_modules/are out of scope, as are rendered template outputs meant for consumer code.
Verification Commands
Run these before committing. All must pass.
cargo test # full suite
cargo test --all-features # feature matrix (see below)
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo doc --no-deps # if docs changed
cargo build --target wasm32-unknown-unknown --release # standing, unconditional (see below)
cargo publish --dry-run --allow-dirty # before a release
grep -rn '—' src/ docs/ README.md AGENTS.md .opencode/agents/ tests/ \
Cargo.toml # must be zero hits; see convention 12
taskgraph validate # after touching anything under tasks/
Two postures are standing and unconditional rather than condition-run:
- The wasm build (ADR-003, made standing by the wave-2
conformance battery):
cargo testmay pass everywhere but wasm is where platform-only dependencies betray themselves, and this crate promises wasm-clean always. Build the release target on every change that lands, not only when deps seem affected. Thegrep -rn ... | grep handlebars | headstyle feature inspection is not needed by hand; the posture to preserve is: no handlebars features (dir_source,script_helper) may end up enabled in the final dependency set. - The feature matrix (ADR-009), documented honestly per the
wave-2 battery:
cargo testwithdefaultfeatures and with--all-featuresboth pass, and they coincide today becausebast-codegengates nothing yet. The coincide-today statement holds until the first feature starts gating something; when one does, this list grows the distinct postures that feature needs rather than the matrix staying implicit.
# feature inspection posture (ADR-002):
cargo tree --all-targets -f "{p} {f}" | grep handlebars
# expected: handlebars features shown are only the substrate defaults
# this crate builds with (no dir_source, no script_helper)
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 1 (architecture). Phase 0 completed
with the reader POC; the project was paused 2026-09-08 and unpaused
2026-11-06. The architecture base (spec docs, ADRs 001-010, OQ
tracker in docs/architecture/) was committed 2026-11-06; docs are
in draft status pending OQ resolution and review finalization.
The unpause reason: alkforge (family ADR-002, gitea replacement; see
/workspace/@alkdev/alkfamily/docs/architecture/decisions/002-alkforge-gitea-replacement.md)
needs server-side HTML generation for its web view, and alkgen provides
it. The alkforge SSR use case is simpler than the code-generation
branches and is needed before them, so it jumps the queue as the first
real consumer. The generality constraint it brings: the core is a
text-generation engine (contexts + templates + partials → text) and
must not bake reader/writer assumptions into shared core types; HTML,
Rust, and WGSL are all the same shape, proven by handlebars' native
case, the reader POC, and alkminer respectively.
Family ADR-002 also records the no-deformation rule for consumers: downstream asks file upstream gaps rather than forking or patching the core, so alkforge's SSR needs flow into alkgen's design, not around it.
Phase 0 output lands in docs/research/ (completed: phase-0 findings
and the reader POC summary); Phase 1 output lands in
docs/architecture/ (see Architecture Context). 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). Phase 0 (research/POC) runs before architecture; small changes may skip the ceremony per the doc's "reasonable exceptions" clause.docs/research/phase-0.md: the current phase-0 findings + OQ list. Read it before proposing design changes; extend it when research answers questions. The branch list there reflects the 2026-11-06 unpause (alkforge SSR first, then BAST readers, then call-protocol handlers, then the template-JIT core).docs/architecture/: the authoritative architecture spec (started 2026-11-06, docs indraftstatus).README.mdthere is the index (doc table, ADR table, lifecycle); ADRs are numbered underdecisions/; OQs are tracked inopen-questions.mdwith stable OQ-IDs. Extend it rather than re-litigating recorded decisions.tasks/architecture/: architecture-side taskgraph tasks, including the external-trigger tracker tasks paired with deferred(scope) OQs.
Reference Material (outside this repo)
/workspace/@alkdev/alktype: the BAST engine (crates.io 0.4.0;docs/architecture/there is the schema/layout/validation spec)./workspace/@alkimiadev/typebox-rs/src/codegen: older handlebars codegen precedent (simple context structs + inline templates)./workspace/@alkminer/reference/alkminer-v1/src/compute/kernel.rs: handlebars-as-JIT precedent (strict registry, partials, render-then- compile with cache)./workspace/@alkdev/alkhttp/src/adapters:from_openapi/from_jsonschemahandler shapes the call-protocol codegen mirrors./workspace/@alkdev/alkcall: channels consumer; generated readers/writers/validators serve its channels./workspace/@alkdev/alkforge+ family ADR-002 (/workspace/@alkdev/alkfamily/docs/architecture/decisions/002-alkforge-gitea-replacement.md): the SSR consumer; the no-deformation rule binds its asks to design input here, not forks of the core./workspace/handlebars-rust: vendored handlebars checkout (head; crates.io 6.4.4 is the pinned stable)./workspace/alkgen-reader-poc: the reader POC crate (standalone mode, 2026-09-08); its Cargo.toml tracks the dep stack branch 1 exercises. Findings live indocs/research/reader-poc-summary.md.