Testing
DocSlime is verified behavior-first: the things a user actually does — scaffold a tree, add a
document, create an ADR, list templates — are exercised end-to-end by running the real
compiled binary against throwaway directories and asserting on the files it produces. Small,
fiddly logic (slug normalization, ADR numbering) is covered by fast unit tests. The bar for
shippable is simple: the full suite is green, impeccable can discover docs context, and the
binary’s observable behavior matches the experiences and requirements.
Strategy
| Layer | What it verifies | Tools |
|---|---|---|
| Unit | Pure logic in isolation — slug normalization, next-ADR-number computation, filename prefix parsing. | Rust #[test] in src/ |
| End-to-end / behavior | User-visible CLI behavior from ../experience/README.md — running the actual binary and asserting on exit codes, output, and files written. |
assert_cmd, predicates, tempfile |
There is no separate integration layer: the codebase is small and the components are best
verified together through the CLI, so the black-box tests double as integration coverage.
Behavior coverage
| Experience / Requirement | Scenario (Given/When/Then) | Test |
|---|---|---|
| Scaffold the docs tree / FR-1, FR-11, FR-15, FR-18, FR-19 | Given an empty dir, When init, Then the full 12-file lifecycle tree exists, including product/design context, requirements, publishing, and observability |
tests/cli.rs::init_creates_full_tree |
| Self-explaining templates | Given a scaffolded tree, When inspected, Then every template carries <!-- LLM: --> guidance |
tests/cli.rs::every_template_carries_llm_guidance |
| Non-destructive by default / FR-2 | Given an edited file, When init re-runs, Then the file is left untouched |
tests/cli.rs::init_skips_existing_files |
| Force overwrite / FR-2 | Given an edited file, When init --force, Then the template is restored |
tests/cli.rs::init_force_overwrites |
| Legacy path safety / FR-17 | Given a filled legacy numbered file, When current init or list runs, Then the new counterpart is treated as satisfied without duplicating or moving user work |
tests/cli.rs::init_recognizes_legacy_paths, list_recognizes_legacy_paths |
| Add a single document / FR-3 | Given an empty dir, When add <shorthand>, Then the matching template is created |
tests/cli.rs::add_resolves_shorthand_name |
| Unknown name errors / FR-8 | Given an unknown name, When add, Then it fails with a helpful error |
tests/cli.rs::add_unknown_template_fails |
| Record an architecture decision / FR-4 | Given existing ADRs, When add adr <slug>, Then the next number is used |
tests/cli.rs::add_adr_numbers_sequentially |
| ADR numbering from scratch / FR-4 | Given no ADR dir, When add adr <slug>, Then it starts at 0001 |
tests/cli.rs::add_adr_starts_at_one_without_init |
| ADR slug required / FR-5 | Given no slug, When add adr, Then it fails |
tests/cli.rs::add_adr_requires_slug |
| List templates / FR-6 | When list, Then every template is shown |
tests/cli.rs::list_shows_every_template |
| List reflects disk state / FR-6 | Given some files exist, When list, Then status reflects what’s on disk |
tests/cli.rs::list_reflects_on_disk_status |
| Agent skill structure / FR-9, FR-10 | Given the bundled docslime skills, When validated, Then their frontmatter and metadata are well-formed | skill-creator/scripts/quick_validate.py .agents/skills/<skill> |
| KISS stays a skill / FR-12 | Given the CLI help, When inspected, Then there is no kiss CLI subcommand |
tests/cli.rs::kiss_is_not_a_cli_subcommand |
| Skill robustness / FR-16 | Given the bundled skill folders, When inspected, Then each SKILL.md has frontmatter, guardrails, verification, and failure handling |
tests/cli.rs::agent_skills_have_required_sections |
| Slug normalization / FR-5 | Mixed-case/spaced input normalizes to [a-z0-9-]; empty rejected |
src/commands/add.rs::normalize_slug_lowercases_and_hyphenates |
| ADR numbering helper / FR-4 | Missing dir → 1; filename prefix parsed correctly |
src/commands/add.rs::next_adr_number_is_one_when_dir_missing, leading_number_parses_prefix |
Traceability contract
DocSlime’s TDD+BDD bar is that every important behavior can be followed from intent to
proof:
| Link | Evidence |
|---|---|
| Product goal -> experience | ../PRODUCT.md explains why the docs tree exists; ../experience/README.md captures the evidence and journeys. |
| Experience -> requirement | ../REQUIREMENTS.md gives each behavior a stable FR/NFR ID and names the evidence it serves. |
| Requirement -> BDD scenario | This file records Given/When/Then coverage for the visible behavior. |
| Scenario -> test | tests/cli.rs runs the compiled binary against throwaway directories and asserts on output files, exit codes, and help text. |
| Requirement -> architecture/ADR | ARCHITECTURE.md names the domain boundary; durable choices link to adrs/. |
| Release -> production evidence | PUBLISHING.md defines verification; OBSERVABILITY.md links production signals back to outcomes and discovery. |
The DDD evidence is intentionally lightweight: docs should name the domain concepts and
boundaries that affect behavior, then use ADRs for hard-to-reverse decisions. If a project
has no meaningful domain split, the architecture doc should say that instead of inventing
bounded contexts.
Evaluation against product goals
Tests prove correctness; they don’t prove the product is working. The product-level signals from
../PRODUCT.md are evaluated qualitatively:
- Docs get filled — judged by whether scaffolded docs in real repos end up complete (no
leftover<!-- LLM: -->guidance), rather than abandoned as templates. DocSlime dogfoods this
by filling in its owndocs/tree. - Low friction — judged by the time and number of steps from
docslime initto a first
useful, filled-in document. - Agent context quality — judged by whether agents working in a DocSlime-backed repo give less
speculative answers because product context, requirements, and ADRs are present. - Impeccable context quality — judged by whether
impeccableresolvesdocs/PRODUCT.md
anddocs/DESIGN.mdwithout duplicate root files. - Delivery quality — judged by whether each CLI, skill, and site artifact is verified
through its user-facing distribution path rather than inferred from workflow completion. - Production learning — judged by whether release failures, feedback, and adoption
signals update discovery evidence or requirements when they reveal a material gap. - Skill robustness — judged by whether skill files validate structurally and give agents
enough setup, guardrails, verification, and failure handling to act without guessing. - Adoption — tracked via Homebrew and
npx skillsinstalls and repos using the tree.
Running the tests
cargo test # unit + integration suite (expected: all green)
cargo clippy --all-targets # lint (expected: no warnings)
npm run build # docmd site build (expected: site/ output updates cleanly)
Continuous integration
DocSlime is now a monorepo with three CI-owned surfaces:
| Workflow job | Surface | Gate |
|---|---|---|
Branch policy |
Release flow | Fails pull requests to main unless the source branch is staging; all feature work targets staging. |
CLI |
Rust binary and embedded templates | cargo fmt --check, cargo test, and cargo clippy --all-targets -- -D warnings. |
Site |
docmd.io documentation site |
npm ci followed by npm run build. |
Agent skills |
Bundled .agents/skills/docslime-* package |
Frontmatter, OpenAI metadata, guardrail, verification, and failure-handling checks. |
The daily CI workflow lives in .github/workflows/ci.yml and runs on pull requests to
staging or main, pushes to those branches, and manual dispatches. The release workflow
(.github/workflows/release.yml, generated by cargo-dist) remains tag-driven: it builds
and packages the CLI for all target platforms, creates the GitHub Release, and publishes the
Homebrew formula.
GitHub also has an active repository ruleset named “Protect main and staging from deletion”.
It targets refs/heads/main and refs/heads/staging with the deletion rule so the two
release branches cannot be removed accidentally.
Test data & environments
End-to-end tests run the real binary inside a fresh tempfile::TempDir per test, so each one
gets an isolated throwaway directory that is cleaned up automatically — no shared state, no
fixtures to seed, and no risk of touching the working repo.