Skip to main content

test-writer

Writes tests in mecatl (hexagonal / strict DDD, two Go modules) following the invariant-first discipline. Picks the right layer, the right naming convention, and the right fake pattern (offline reference adapters — mockllm / memfs / memstore — plus the shared conformance suites, never a live network call or a mock-framework mock of a port). Use when adding tests, writing a new package's test surface, porting a failing scenario into a regression test, or pinning an ADR rule / AGENTS.md invariant. NOT for authoring the acceptance plan (use /to-acceptance-plan).

跳到安装

来源信息

仓库
stacklok/mecatl
最近来源活动
2026年9月14日 15:20
检测到的 SKILL.md 语言
英语
星标
119
分支
10

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
test-writer
description
Writes tests in mecatl (hexagonal / strict DDD, two Go modules) following the invariant-first discipline. Picks the right layer, the right naming convention, and the right fake pattern (offline reference adapters — mockllm / memfs / memstore — plus the shared conformance suites, never a live network call or a mock-framework mock of a port). Use when adding tests, writing a new package's test surface, porting a failing scenario into a regression test, or pinning an ADR rule / AGENTS.md invariant. NOT for authoring the acceptance plan (use /to-acceptance-plan).
# test-writer ## Purpose Every mecatl test answers four questions in order: what behavior, invariant, ADR, or acceptance scenario does it defend? what layer does it live at? what naming convention does it follow? what fake or fixture does it need? This skill walks you through those four questions and emits a test stub. ## Prerequisites - Read [`AGENTS.md`](../../../AGENTS.md) — the layering rule and the invariants under "Things That Will Bite You" (the domain model's equivalent: each bullet there is a defended invariant, most with a named pinning test already). - Read [`docs/architecture.md`](../../../docs/architecture.md) — the layers, the ports, where each kind of test lives. ## Workflow ### Step 1: Identify the invariant or ADR being defended Name the test after the rule it defends. The first two patterns are what `ac-trace` gates against: - `TestInvariant_<id>` — an invariant from `AGENTS.md` ("Things That Will Bite You") or `docs/design/IMPLEMENTATION-NOTES.md`, id kebab → snake. Example: `TestInvariant_deny_dominant_scope_resolution`. - `TestADR_NNNN_*` — a rule codified in `docs/adr/NNNN-*.md`. Example: `TestADR_0041_DirectWriteSubagent`. - `Test<Plan>_Scenario<N>_*` — a scenario test a `docs/acceptance/<plan>.md` scenario claims (the orphan gate fails a scenario test no landed plan tracks). Example: `TestSessionProfiles_Scenario2_NoFSRestart`. - Descriptive unit names (`TestFoo_Bar`) for everything else — fine for ordinary coverage, but an AC's `verify:` line should name one of the pinned forms above when the AC defends a rule. Rule: every test answers "if this fails, which behavior, invariant, ADR, or scenario is now wrong?" Descriptive unit behavior is enough for Routine and Bounded work; do not manufacture an ADR or repository-wide invariant merely to name a test. If the answer is "nothing observable", consider whether the test earns its place. ### Step 2: Pick the layer Pick the **lowest** layer that actually exercises the rule, and the right **module**: `engine/` is its own Go module — a `go test ./...` from the repo root does NOT cross the boundary; engine tests are a second invocation from `engine/`. - **Engine domain unit** — pure aggregate / value-object / invariant tests. No I/O, no adapters beyond the reference ones. `engine/session/`, `engine/governance/`, `engine/tool/`, `engine/prompt/` `*_test.go`. Most invariants pin here. - **Engine app unit** — the agent loop, dispatch, supervisor against the **reference adapters**: `engine/adapter/mockllm` (scripted LLM), `engine/adapter/memfs` (in-memory workspace), `engine/adapter/memstore`, `engine/adapter/permpolicy`. `engine/agent/*_test.go`. Nothing under `engine/` imports `internal/...` — integration tests that need a heavy adapter live next to that adapter under `internal/adapter/`. - **Port conformance** — one suite per port family, run against **every** adapter of that port so a fake can never drift: `engine/adapter/memconformance`, `engine/adapter/storeconformance`, `engine/adapter/fsconformance`, `engine/adapter/sourceconformance`, `engine/adapter/leaseconformance`. A new adapter plugs into the EXISTING suite; it does not hand-roll its own. - **Adapter integration** — a heavy adapter against its real dependency, offline: `internal/adapter/<name>/*_test.go` (SSE→Chunk paths are tested from fixtures; miniredis for the Redis store). - **End-to-end** — the offline demo path (`cmd/mecademo`) or a full `app.Build` loop test under `internal/app/`. The LIVE provider e2e (`task e2e`, real money) is never part of the gate. **Tests are offline, always.** Never hit a live model or network — the depguard + CI enforce it. If a test seems to need the network, the seam is wrong: script the `mockllm` or record a fixture. ### Step 3: Pick the fake or fixture mecatl is hexagonal: the engine owns the port interfaces (`engine/port`); adapters implement them. - **Mock-framework mock of a port:** banned for ports. A mocked `LLMProvider` or `SessionStore` passes tests that fail against the real adapter. Use the reference adapters instead — they ARE the fakes, and the conformance suites keep them honest. - **`mockllm` for the provider:** script chunks/tool calls per turn; never the network. - **`memfs` for the workspace:** in-memory FileSystem/Workspace. (For a no-fs profile test, `engine/adapter/nofs`.) - **`memstore`/`memlease` for persistence seams**, exercised through the conformance suites. - **Fixtures:** deterministic — no `time.Now()`-sensitive assertions in domain tests (the `port.Clock` seam exists; `engine/adapter/wallclock` is production-only), no randomised ids where an id matters. - **Mutation checks where the repo already has them:** the oracle-style tests (e.g. `command_runner_secret_scrub_test.go`) plant the violation and assert red. Follow that pattern for negative invariants. ### Step 4: Emit the test stub Do not copy a constructor from this document: test helpers and `agent.Deps` change as the engine evolves. Locate the nearest current test that exercises the same layer and seam, then adapt its fixture and constructor shape. Confirm every field and helper against the current package before writing the failing test. Prefer an existing `newTest*` helper or reference-adapter fixture over creating a new harness. ### Step 4.5: Make sure the test can actually fail A test that passes for the wrong reason is worse than no test — it manufactures false confidence. Two cases need an explicit "watch it fail" step before you trust a green result: - **Negative / absence assertions** ("X is NOT forwarded", "no child content enters the parent log", "the env is scrubbed"). These pass trivially when they inspect the wrong field or surface. Plant the violation (make the thing happen), confirm the test goes **red**, then revert. Where you can, derive the forbidden set from the run's own output rather than a hard-coded literal that drifts. - **Timing / lifecycle assertions** ("the run cancels", "no goroutine leak"). A check with an already-expired context and no assertion can only fail by panic. Use a real bound plus a would-block guard, and assert the result. `goleak` is available in the engine module for leak assertions. Every test carries at least one assertion that can fail on a real regression. `_ = err` is not verification. ### Step 5: Verify with the Taskfile ```bash task test # both modules + the engine-standalone hygiene proof cd engine && go test ./agent/ -run TestYourNewTest # a single engine test ``` Then check whether the implementation contradicts its declared work classification or introduces an unplanned durable decision. Stop as contract drift rather than silently upgrading/downgrading it. Only Architectural work with a genuinely new or superseding durable decision adds an ADR and its `TestADR_NNNN_*` pin; a current invariant may instead belong in AGENTS.md / IMPLEMENTATION-NOTES.md with `TestInvariant_<id>`. Routine and Bounded rationale stays in the issue, PR, plan, or ordinary test name. If you touched the engine's exported API: `task api:update` plus the `engine/CHANGELOG.md` note. ## Anti-patterns - "I'll mock the `LLMProvider` just for this test." Forbidden — `mockllm` scripts the stream; the conformance suites pin adapter parity. - "This behaviour is proven — my fake asserts it." Only if the same conformance suite runs against the real adapter. Fake-only is not proven. - "I'll hit the real provider to check." Never — offline only; the SSE adapters are fixture-tested, and `task e2e` is a separate, manual gate. - "I'll assert that a guide contains these phrases." Do not pin arbitrary prose or keyword lists. Test links/anchors, parsed executable examples, schemas, and generated-output freshness; leave prose semantics and completeness to human review. Model-visible prompt affordance tests remain required because runtime behavior depends on them. - "I changed a test because the implementation changed." When tests fail, fix the implementation, not the tests. - "My negative test passes." Did you watch it fail when the violation is planted? A green absence-assertion you never saw red is hollow — it often checks the wrong field while the value rides another. - "I'll run `go test ./...` from the repo root to check the engine." The module boundary swallows it — engine tests run from `engine/`. ## See also - `AGENTS.md` — the layering rule, the invariants, the Taskfile contract. - `docs/architecture.md` — the layers and the dependency rule. - `docs/adr/` — the frozen decisions your tests may pin. - `docs/acceptance/README.md` — the `verify:` contract ac-trace gates.
在 GitHub 查看