writing-tests
How to write tests in the Onlooker ecosystem repo — bats integration tests for hooks and the node:test schema suite.
Skill by onlooker-community
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/onlooker-community-writing-tests
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: testing, writing
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/writing-tests
What's inside
- Skill name
writing-tests- Description
- How to write tests in the Onlooker ecosystem repo — bats integration tests for hooks and the node:test schema suite. Use when adding or changing anything under test/, when writing a new hook or plugin that needs tests, or when deciding which shared helper to reach for instead of hand-rolling setup, dates, CLI stubs, project keys, or event-log assertions. Steers toward test/helpers/setup.bash and each plugin's own libs so tests stay isolated and never become time bombs.
- License
- MIT
- Instructions
- 328 lines in SKILL.md · view on GitHub
Agent Skills spec issues
- files: referenced but missing: scripts/lib/validate-path.sh, scripts/lib/onlooker-event.mjs
Files (1)
- SKILL.md
SKILL.md (first lines)
# Writing tests
This repo has two test suites. Use the right one for what you're testing, and lean on the shared helpers below — most fragility in this codebase has come from tests hand-rolling things a helper already does.
- **bats** (`test/bats/*.bats`) — bash hooks and end-to-end plugin behavior. This is where almost all tests live.
- **node:test** (`test/node/*.test.mjs`) — schema mapping, manifest, and reference validation. Pure data-in/data-out, no hooks.
## Run the suite
```bash
npm test # bats + node schema suite (what most changes need)
npm run test:bats # just bats (all of test/bats, or the files you pass after --)
npm run test:schema # just node (node --test test/node/*.test.mjs)
npm run test:ci # shellcheck + bats + schema + biome + manifest/reference lint
```
Run a single bats file while iterating: `npm run test:bats -- test/bats/<name>.bats`. Files you pass replace the default target. The runner refuses overlapping targets (say, a file plus the directory it's in) and fails any run that executed fewer tests than it planned, because under `-j` bats would otherwise drop a duplicated file and exit 0 (ONL-109). Bare `bats test/bats/<name>.bats` also works, but it skips `ONLOOKER_VALIDATE=1`, so a negative schema test fails locally even though it passes in CI. `bats` comes from mise (it is on `PATH`), not `npx`.
## Golden rules
1. Every bats test starts by isolating the filesystem — source `setup.bash` and call `setup_test_env`. Never touch the real `$HOME` or `~/.onlooker`.
2. Always reference `$ONLOOKER_DIR` / `$ONLOOKER_EVENTS_LOG`, never a literal `~/.onlooker`.
3. Never hardcode a date that feeds a relative window. Use `relative_iso_days_ago`.
4. Resolve project keys, emit events, and generate ULIDs through the plugin's own libs — don't reimplement them.
5. Assert behavior through the **event log** and on-disk artifacts, the same surfaces real consumers read.
## bats tests
### Isolate the environment first
Every test file's `setup()` sources the shared helper and calls `setup_test_env`, which repoints `HOME`, `ONLOOKER_DIR`, `CLAUDE_HOME`, and `CLAUDE_PLUGIN_ROOT` into `$BATS_TEST_TMPDIR` and severs git from your global config:
```bash
setup() {
source "${BATS_TEST_DIRNAME}/../helpers/setup.bash"
setup_test_env
PLUGIN_ROOT="${REPO_ROOT}/plugins/<plugin>"
export CLAUDE_PLUGIN_ROOT="$PLUGIN_ROOT"
export ONLOOKER_ECOSYSTEM_ROOT="$REPO_ROOT"
}
```
If your hook needs the resolved Onlooker paths (`$ONLOOKER_EVENTS_LOG`, the session/metrics dirs) materialized, call `load_validate_path` instead — it runs `setup_test_env`, sources `scripts/lib/validate-path.sh`, and `mkdir -p`s the standard directories.
`setup_test_env` exports (read `test/helpers/setup.bash` for the authoritative list):
| Variable | Points at |
|----------|-----------|
| `HOME` | `$BATS_TEST_TMPDIR/home` (isolated) |
| `ONLOOKER_DIR` | `$HOME/.onlooker` |
| `ONLOOKER_EVENTS_LOG` | `$ONLOOKER_DIR/logs/onlooker-events.jsonl` (after `validate-path.sh` is sourced) |
| `CLAUDE_HOME` | `$HOME/.claude` |
| `REPO_ROOT` | the repo root (set when `setup.bash` is sourced) |
### Dates: use `relative_iso_days_ago`, never a literal
A hardcoded ISO date is a **time bomb** when the code under test computes a window from "now" (for example librarian's "now − `bootstrap_lookback_days`" scan window). The fixture passes today and silently fails once wall-clock now drifts past the threshold. Date such fixtures relative to now:
```bash
# yesterday, UTC — comfortably inside a 14-day lookback window
created_at=$(relative_iso_days_ago 1)
```
`relative_iso_days_ago N` lives in `test/helpers/setup.bash` and returns an ISO-8601 UTC timestamp N days in the past (0 = now, negative = future). It uses `python3` for portable date math, since `date -d` (GNU) and `date -v` (BSD/macOS) disagree. For a plain "now" timestamp, `date -u +%Y-%m-%dT%H:%M:%SZ` is fine and portable; only the *offset* math needs the helper.
### Resolve project keys through the plugin lib
Artifacts are partitioned by a project key. Don't recompute the SHA — source the vendored `onlooker-project-key.sh` and call its function (`onlooker_project_key <repo-root>`):
```bash
source "${PLUGIN_ROOT}/scripts/lib/onlooker-project-key.sh"
PROJECT_KEY=$(onlooker_project_key "$PROJECT_REPO")
ARTIFACT_DIR="${ONLOOKER_DIR}/<plugin>/${PROJECT_KEY}"
```
For key resolution to succeed the test needs a git context — stand up a throwaway repo in `setup()`:
```bash
PROJECT_REPO="${BATS_TEST_TMPDIR}/repo"
mkdir -p "$PROJECT_REPO"
git -C "$PROJECT_REPO" init -q
git -C "$PROJECT_REPO" config user.email t@example.com
git -C "$PROJECT_REPO" config user.name "Test"
git -C "$PROJECT_REPO" remote add origin git@github.com:org/fixture.git
```
### Stub the `claude` CLI on `PATH`
Hooks that classify or judge shell out to `claude`. Stub it deterministically — branch on the prompt so each case returns a known response — and prepend the stub dir to `PATH`:
```bash
STUB_BIN="${BATS_TEST_TMPDIR}/bin"
mkdir -p "$STUB_BIN"
cat > "${STUB_BIN}/claude" <<'STUB'
#!/usr/bin/env bash
prompt=$(cat)
if [[ "$prompt" == *"some-marker"* ]]; then
printf '%s' '{"type":"feedback","title":"...","body":"...","confidence":0.84}'
else
printf '%s' '{"type":null,"title":"","body":"","confidence":0.2}'
fi
STUB
chmod +x "${STUB_BIN}/claude"
export PATH="${STUB_BIN}:${PATH}"
```
Same pattern stubs any external CLI a hook depends on (`curl`, `git`, …). Keep the responses minimal but schema-valid.
### Build hook input with `jq`
Hooks read a JSON payload on stdin. Build it with `jq -cn` and feed it in — don't hand-concatenate JSON strings:
```bash
_hook_input() {
jq -cn --arg cwd "$PROJECT_REPO" --arg sid "sess-test" \
'{cwd: $cwd, session_id: $sid, hook_event_name: "SessionEnd"}'
}From onlooker-community/ecosystem, MIT.
More from onlooker-community/ecosystem
- 0.565score
cartographer
onlooker-community
Audit CLAUDE.md, AGENTS.md, and .claude/rules/ instruction files for contradictions, stale references, dead rules, scope collisions, and entities no…
SkillAFreshness: AClaude Code +9 - 0.565score
counsel
onlooker-community
Run the weekly observability synthesis on demand and render the coaching brief in the conversation.
SkillAFreshness: AClaude Code +9 - 0.565score
librarian
onlooker-community
Review the librarian's pending memory promotion proposals and lesson candidates queued from past sessions.
SkillAFreshness: AClaude Code +9 - 0.565score
lineage
onlooker-community
Answer "why does this line exist?" — trace a file, or a specific line, back to the change, prompt, agent, and session that produced it.
SkillAFreshness: AClaude Code +9 - 0.565score
tribunal
onlooker-community
Run a task under multi-agent quality gates.
SkillAFreshness: AClaude Code +9 - 0.565score
warden
onlooker-community
Inspect and control the Warden content gate.
SkillAFreshness: AClaude Code +9 - 0.555score
list-prompt-rules
onlooker-community
List all prompt rules (global + project) with their pattern, guidance, and per-session fire status.
SkillAFreshness: AClaude Code +9
Signals
- Stars
- 0
- Forks
- 0
- Open issues
- 0
- Last commit (this folder)
- Oct 21, 2026 (8 days ago)
- Last release
- Oct 24, 2026 (5 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/onlooker-community/ecosystem
- Website
- onlooker.dev
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from onlooker-community
- 0.565score
cartographer
onlooker-community
Audit CLAUDE.md, AGENTS.md, and .claude/rules/ instruction files for contradictions, stale references, dead rules, scope collisions, and entities no…
SkillAFreshness: AClaude Code +9 - 0.565score
counsel
onlooker-community
Run the weekly observability synthesis on demand and render the coaching brief in the conversation.
SkillAFreshness: AClaude Code +9 - 0.565score
librarian
onlooker-community
Review the librarian's pending memory promotion proposals and lesson candidates queued from past sessions.
SkillAFreshness: AClaude Code +9 - 0.565score
lineage
onlooker-community
Answer "why does this line exist?" — trace a file, or a specific line, back to the change, prompt, agent, and session that produced it.
SkillAFreshness: AClaude Code +9 - 0.565score
tribunal
onlooker-community
Run a task under multi-agent quality gates.
SkillAFreshness: AClaude Code +9 - 0.565score
warden
onlooker-community
Inspect and control the Warden content gate.
SkillAFreshness: AClaude Code +9
Cite this page
writing-tests skill. Agent Mart. https://agentmart.dev/skills/onlooker-community-writing-tests (last updated 2026-10-24)