Instruction file imported from grvermeulen/H3-Teamy (
.cursor/rules/agentic-workflow.mdc). Copyright stays with the author.
description: Standards for Agentic Coding (Cursor + Opencode) globs: **/.ts, **/.tsx
Agentic Standards
This file defines the strict coding standards for all AI agents (Cursor, Opencode) working on this repository.
1. Testing Strategy (Mandatory)
- Unit Tests: ALL new business logic (functions, hooks, utilities) MUST have a corresponding
.test.tsor.test.tsxfile using Vitest. - Component Tests: Complex UI components must have a
.test.tsxfile using@testing-library/react. - Run Tests: Verify changes with
npm test(ornpx vitest run) before completing a task.
1.1 Mock hygiene (enforced)
- Always use
vi.mocked(fn).mockResolvedValue(...)— never(fn as any).mockResolvedValue(...). - Add
beforeEach(() => { vi.clearAllMocks(); })to every describe block that uses mocks. - Add
afterEach(() => { vi.restoreAllMocks(); })when usingvi.spyOn. - Use
vi.spyOn(global, "fetch")instead of assigningglobal.fetchdirectly. - Use
vi.stubEnv("VAR", "value")for env vars; addafterEach(() => { vi.unstubAllEnvs(); }). - Never assign
process.env.X = "..."in tests without restoring it afterwards.
1.2 Testing Library patterns (enforced)
- Never call
fireEventoruserEventinside awaitForcallback — wait first, then act. - Add
afterEach(() => { cleanup(); })to component test suites. - Use precise assertions:
toHaveBeenCalledTimes(1)andtoHaveBeenCalledWith(...)overtoHaveBeenCalled(). - Mock
fetchresponses as complete Response-like objects (ok,status,headers) — never cast incomplete objects withas Response.
1.3 Test coverage requirements
- Tests for async functions MUST include at least one error/rejection path.
- When a function calls Sentry, add
expect(vi.mocked(Sentry.captureException)).toHaveBeenCalledWith(expect.any(Error))in the error-path test. - Do not use implementation-tracing comments in tests (e.g.
// Implementation: ${y}${m}${day}); the assertion is the specification.
1.4 Timezone safety
- Never use bare date-only strings (
"2023-01-01") in tests — they parse as UTC midnight and fail in western-timezone CI. - Use
"YYYY-MM-DDT12:00:00"(noon local) or explicit UTC strings. - In production code, use
getUTCFullYear/getUTCMonth/getUTCDatewhen building date-based identifiers.
2. Architecture & File Structure
- Service Layer: Business logic belongs in
src/lib/services/, NOT in API routes or Components.- Bad:
src/app/api/users/route.tscontaining DB queries. - Good:
src/app/api/users/route.tscallsUserService.getUsers().
- Bad:
- API Routes: Keep them thin. They should only handle request parsing, auth checks, and response formatting.
- Components: Use functional components with strict TypeScript interfaces for props.
- No duplicate utilities: Before writing a new helper, check
src/lib/for an existing one. Import and reuse; never copy-paste.- Example: use
displayNamefrom@/lib/userUtilseverywhere — do not redeclare it in API routes or components. - Shared one-liners (e.g.
parseError) belong insrc/lib/and must be imported, not duplicated per component.
- Example: use
3. Tech Stack & Styling
- Styling: Use Tailwind CSS utility classes exclusively. No inline
styleprops, no CSS modules, unless documented as an exception.- Next.js error boundary components (
error.tsx) may use Tailwind; there is no justification for inline styles.
- Next.js error boundary components (
- State Management: Prefer React Server Components for data fetching. Use
useState/useReducerfor local interaction state. - Type Safety: Use Zod for all schema validation (API inputs, form data, env vars).
4. Git & Workflow
- Commits: Follow Conventional Commits (feat:, fix:, chore:, refactor:, ci:, test:, docs:).
- Hooks: Pre-commit hooks enforce Lint, Typecheck. Do not bypass them (
--no-verify) unless instructed. - No debug artifacts: Never commit development comments like
// CI: trigger AI reviewinto source files. - CodeRabbit / PR-review: na het implementeren van fixes voor inline review-opmerkingen (CodeRabbit, Cursor bot, …) horen de bijbehorende GitHub-reviewthreads op resolved gezet te worden in dezelfde ronde als de fix + groene CI — zie
verification-loop.mdcfase 7 enAGENTS.md.
5. CI Configuration
- Node version: Always use Node 20 or 22. Node 18 is EOL and incompatible with vitest v4 / vite v7.
- Concurrency: Add a concurrency group to workflows that trigger on both
pushandpull_requestto avoid duplicate runs:concurrency: group: ci-${{ github.ref }} cancel-in-progress: true - Least-privilege tokens: Every workflow job must declare an explicit
permissionsblock. Default tocontents: read. - Pre-commit hooks: Do not run the full test suite on
pre-commit. Move full test runs topre-push, or scope to changed files withnpx vitest run --changed.
5. Agent Behavior
- Refactoring: When refactoring, always run tests BEFORE and AFTER changes to ensure no regression.
- No dead code: If a setup file is created (e.g.
vitest.setup.ts), it MUST be registered invitest.config.tsundersetupFiles. Dead setup files are forbidden.
6. Documentation (CodeRabbit)
- Docstrings: ALL exported functions, classes, and components MUST have JSDoc comments (
/** ... */). - Coverage: Maintain at least 80% docstring coverage to satisfy CodeRabbit checks.
- Accuracy: JSDoc
@paramdescriptions must match the actual behaviour including edge cases (e.g., clamping, fallbacks). - Format:
/** * Calculates the attendance badge based on percentage. * @param percent - The attendance percentage (0-100). Values outside this range are clamped; NaN/Infinity treated as 0. * @returns The corresponding badge object. */ export function getBadge(percent: number) { ... }
7. TypeScript Type Safety
- Never use
as anycasts. Useunknownand narrow withinstanceofor type guards. - Declare array types explicitly:
const list: Foo[] = []— notconst list = [] as Foo[]. - All exported functions must have an explicit return type annotation.
catchblocks must useunknown:catch (err: unknown)— notcatch (err: any).- Avoid
as anyincatchfallbacks: use proper types orRolesfrom the source module.
8. Error Handling & Sentry
- Every
catchblock in production code must either:- Call
Sentry.captureException(error), or - Re-throw the error.
- Silent
catch (() => {})is forbidden.
- Call
- Error boundaries (
error.tsx) must callSentry.captureException(error)insideuseEffect. - Cache writes (KV store) must be wrapped in try/catch; a cache failure must never cause a request failure — log with Sentry and continue.
- Silent
.catch(() => {})on promises is forbidden; use.catch((error) => { Sentry.captureException(error); }). - Component names must not shadow global constructors: name error boundary components
ErrorPageorGlobalError, notError.
9. Localisation
- All user-facing strings must be in Dutch (NL), consistent with the rest of the application.
- English UI strings (
"Something went wrong","Try again") are not acceptable.
10. Continual Learning
- Use the
continual-learningskill to keepAGENTS.mdup to date incrementally from transcript deltas, not full-history rescans. - Read existing
AGENTS.mdfirst, then process only new/changed transcripts using.cursor/hooks/state/continual-learning-index.json. - Retain only durable, reusable memory:
- recurring user preferences/corrections
- stable workspace facts
AGENTS.mdmust contain only:## Learned User Preferences## Learned Workspace Facts- plain bullet points only
- Exclude secrets, one-off instructions, and transient task details.
- After processing, update the incremental index with latest transcript mtimes and remove entries for deleted transcripts.
11. CI Looping (loop-on-ci)
- When asked to monitor/repair CI, use the
loop-on-ciskill and run a tight loop until checks are green:- Identify current branch and latest run.
- Wait for completion (
gh run watch --exit-status). - If failed, inspect failed logs, apply a focused fix, commit, and push.
- Repeat until required checks pass.
- PR review comments: If you addressed inline review threads (CodeRabbit, humans, bots), resolve those threads on GitHub after the fix is on the PR branch so the UI shows them as done—not only “fixed in code.” Use GraphQL
resolveReviewThreadviagh api graphql(listreviewThreadson the PR, then mutate eachidwhereisResolvedis false), or resolve manually in the PR Files changed tab.
- Keep each fix scoped to a single failure cause whenever possible.
- Never bypass hooks (no
--no-verify) to force progress. - If a failure appears flaky, retry once and report flake evidence.
- Always report:
- current CI status
- failure summary and applied fixes
- PR URL once checks are green
- confirmation that addressed review threads were resolved (or N/A if none)