Quality — Go testing, linting, coverage, race detection, LSP gates
Each harness specialist agent (moai-harness-hook-ci-specialist,
moai-harness-workflow-specialist, moai-harness-quality-specialist) loads this single
skill for shared context. Supplements general agents (expert-devops, manager-spec,
manager-quality) with project-specific patterns.
27 hook events spanning Session, Tool, Agent, State, Permissions, Interaction, Team,
and Worktree categories. Verify: ls .claude/hooks/moai/handle-*.sh | wc -l == 27. Full
event list in .claude/rules/moai/core/agent-hooks.md.
SPEC Workflow Pipeline
/moai plan "description" → manager-spec → SPEC document with EARS requirements
/moai run SPEC-XXX → manager-develop → Implementation (TDD or DDD)
/moai sync SPEC-XXX → manager-docs → Documentation + PR creation
Quality Targets
go vet ./... and golangci-lint run must be zero errors at all phases.
go test ./... all-pass at all phases; -race must be zero races at run.
Coverage: per-package >= 85% at sync (go test -cover ./...); critical packages
(internal/cli/, internal/template/) >= 90%. LSP gates: run = zero errors / zero type
errors / zero lint errors; sync adds warnings cap (max 10).
Implementation Guide
Section 1 — Hook & CI Patterns
Hook wrapper pattern (thin shell wrapper, Go does the work): read stdin JSON via
INPUT=$(cat), pipe to moai hook {event} <<< "$INPUT". Lives at
.
.claude/hooks/moai/handle-{event}.sh
settings.json invocation rules: always quote $CLAUDE_PROJECT_DIR (e.g.
"$CLAUDE_PROJECT_DIR/.claude/hooks/moai/handle-X.sh"); set timeout (default 5s, max
10s for post-processing); use full path, do not rely on PATH.
Agent-scoped hooks declared in agent YAML frontmatter under hooks: map (per event:
PreToolUse, PostToolUse, SubagentStop, etc.) with matcher regex and command
shell call to handle-agent-hook.sh {action}. Default timeout 5s (10s for post-processing).
GitHub Actions workflows in .github/workflows/: ci.yml (push/PR to main — matrix
ubuntu/macos/windows × Go 1.24, lint + test + build); release.yml (tag v* —
GoReleaser 5 platforms); release-drafter.yml (PR merge to main — auto-label + draft
changelog); auto-merge.yml (Dependabot CI pass — squash); codeql.yml (push/PR — Go
security analysis); spec-lint.yml (PR — SPEC frontmatter validation);
spec-status-auto-sync.yml (schedule — status drift detection); docs-i18n-check.yml
(PR touching docs-site — 4-locale sync verification).
Release process — always use ./scripts/release.sh vX.Y.Z "description" (or
--hotfix); never manual tag push. Chain: script → tag → release.yml → GoReleaser →
5-platform binaries → GitHub Release → Release Drafter updates changelog.
Hook handler testing: Go unit tests in internal/hook/ read JSON from stdin and
exercise the handler logic directly.
Section 2 — Workflow Patterns
SPEC structure under .moai/specs/<SPEC-ID>/: spec.md (EARS + AC), plan.md (M1..Mn
milestones), acceptance.md (binary AC + REQ↔AC traceability, Tier M+), scenarios.md
(Tier L), risks.md (Tier L), progress.md (auto-generated during run). Tier S = 2
artifacts (spec + plan, AC inline); Tier M = 3 (adds acceptance.md); Tier L = 5 (adds
design + research).
EARS requirement patterns: Ubiquitous (The system shall [action]); Event-Driven
(When [event], the system shall [action]); Unwanted (If [bad condition], the system shall [action]); State-Driven (While [state], the system shall [action]); Optional
(Where [feature] enabled, the system shall [action]).
Plan-in-main doctrine: SPEC plan PRs merge to main (not feature branches). Run phase
uses worktree isolation. Sync PRs merge to main with full review history.
Milestones: M1 Foundation → M2 Core → M3 Integration → M4 Edge cases → M5 Polish.
Waves: 30+ task SPECs split into wave-PRs; track in progress.md.
AC format: AC-{SHORT}-{NN}: {verifiable condition} with Verification: command
and Priority: P0|P1|P2.
Critical test isolation rules: (1) always t.TempDir() — auto-cleanup under /tmp;
(2) filepath.Join trap: Join("/a/b", "/var/folders/x") = /a/b/var/folders/x (WRONG)
— use filepath.Abs(); (3) OTEL_* env vars MUST NOT use t.Setenv in parallel tests
(global state race); (4) after any fix, run FULL suite (go test ./...); (5) flaky debug
disables cache via go test -count=1 ./....
Test execution: go test ./... (full), -race (race detection), -cover (coverage),
-run TestX ./pkg/ (specific), -count=1 (disable cache for flaky debug), -v (verbose).
Table-driven test pattern: standard Go convention — tests := []struct{name, input, want string; wantErr bool}{...} + for _, tt := range tests { t.Run(tt.name, ...) }.
Cover happy path + error path + edge cases per row.
LSP quality gate thresholds by phase:
Phase
LSP Errors
Type Errors
Lint Errors
Warnings
plan
Baseline captured
Baseline
Baseline
Baseline
run
Zero required
Zero required
Zero required
—
sync
Zero required
Zero required
Zero required
Max 10
Pre-commit quality gate: go vet ./... && golangci-lint run && go test ./....
Coverage report: go test -coverprofile=coverage.out ./... then
go tool cover -html=coverage.out.
Common quality issues: filepath.Join with absolute path → use filepath.Abs();
OTEL data race in parallel tests → fake/no-op exporter; test writes outside temp dir →
always t.TempDir(); flaky CI → go test -race -count=1.