Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .agents/docs/architecture/testing-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ What is tested where, and what CI blocks a merge on. Writing an e2e test is
is the only code path that touches a real database: a permissions or row-scoping change is untested
until an e2e spec covers it.

## Scoping a run

Scope a package script with `pnpm --filter @opendatacapture/<pkg> <script>` or turbo's
`--filter=<pkg>`. Scope vitest to one project with `pnpm exec vitest --project <name>` (names come
from the `name` field in each package's `vitest.config.ts`, e.g. `api`, `web`, `schemas`), or run a
single file with `pnpm exec vitest path/to/file.test.ts`. A scoped run with `--coverage` measures
only what it ran against the whole coverage scope, so add `--coverage.thresholds.100=false` to it.

## A test nothing collects passes silently

Root `vitest.config.ts` collects only `apps/*/vitest.config.ts`, `packages/*/vitest.config.ts` and
Expand Down
17 changes: 17 additions & 0 deletions .agents/docs/coding-guidelines.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
**Principles**

- Correctness is structural, not vigilant. A correct system is one where the invalid state cannot be written, not one where a careful person catches it. Anything a human must remember, the compiler should remember instead.
- One source of truth; everything derived. Two artifacts that must agree is a bug waiting for its trigger.
- Strict data validation at the boundary, trusting inside. Data is distrusted exactly once, at the perimeter, then the interior runs clean, with no defensive re-checking cluttering the logic. The perimeter is where data arrives from outside the system's control: a request, the environment, a third-party response. What the system wrote, such as a JSON column, is inside.
- A declaration's name and type carry its meaning. When you reach for a doc comment to say what something is, returns or contains, use a more descriptive name instead. A comment explaining what code does usually means the code should be clearer. Save comments for what code can't say: facts about the outside world (a vendor bug, a protocol quirk) and navigation aids (e.g., section banners in long declaration files).
- Complexity concentrated, periphery boring. Sophistication lives in a small framework core so that ordinary code stays trivial, short, and fully inferred. Clever call sites mean the cleverness is in the wrong place.
- Never silently fail on errors. Fail loudly on an undeclared policy.

**Style**

- A name says what the thing is. A request's parameters are not its result, a container is not its content, and raw input is not parsed data. When a name is another name plus a word, that word says how the two differ.
- Name a database model or entity type as a domain noun: a thing a user would recognize, not a process.
- Prefer one general function or component to several near-duplicates. Delete code that exists only to keep variants alike.
- Match existing code of the same role. New code takes the naming, parameters and shape of the code that already does the same kind of job.

_Break any of these guidelines sooner than write anything outright barbarous._
60 changes: 60 additions & 0 deletions .agents/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Where to look

Read the `AGENTS.md` in the directory you are editing; each covers only that workspace. New here?
`.agents/docs/playbooks/run-locally.md`, then `.agents/docs/workspace-map.md`.

| Working on | Read first |
| ----------------------------------------------- | --------------------------------------- |
| Clinician-facing React SPA | `apps/web/AGENTS.md` |
| NestJS backend | `apps/api/AGENTS.md` |
| Patient-facing SSR app for remote assignments | `apps/gateway/AGENTS.md` |
| In-browser instrument editor | `apps/playground/AGENTS.md` |
| Marketing site and user docs | `apps/outreach/AGENTS.md` |
| Shared Zod schemas / cross-tier contracts | `packages/schemas/AGENTS.md` |
| Compiling instrument source into bundles | `packages/instrument-bundler/AGENTS.md` |
| Building the library catalog instruments import | `packages/runtime-bundler/AGENTS.md` |
| The `defineInstrument` public API | `packages/runtime-core/AGENTS.md` |
| Adding or editing a built-in instrument | `packages/instrument-library/AGENTS.md` |
| Pinned library wrappers instruments import | `vendor/AGENTS.md` |
| The published runtime artifact | `runtime/v1/AGENTS.md` |
| End-to-end tests | `testing/AGENTS.md` |
| User documentation | `docs/AGENTS.md` |

Every other workspace has its own `AGENTS.md` too — check the directory before assuming there
isn't one.

> **`packages/instrument-guidelines/AGENTS.md` is not this repo's conventions.** Its bin installs it
> into _external_ instrument repositories as their `AGENTS.md`, so it is the specification for
> authoring an instrument and a published npm package: treat a change there as a release, and keep
> Open Data Capture repo rules out of it.

## Deeper reference, read on demand

| Topic | Path |
| --------------------------------------------------------------- | --------------------------------------------------- |
| Which workspace depends on which, what is built, what publishes | `.agents/docs/workspace-map.md` |
| The 11 `@douglasneuroinformatics/*` packages this repo consumes | `.agents/docs/packages/index.md` |
| Instrument source → bundle → store → interpret → render | `.agents/docs/architecture/instrument-pipeline.md` |
| The six similarly-named `runtime-*` things, and `vendor/` | `.agents/docs/architecture/runtime-and-vendor.md` |
| CASL, `@RouteAccess`, and group scoping | `.agents/docs/architecture/auth-and-permissions.md` |
| What is tested where, and what CI gates | `.agents/docs/architecture/testing-strategy.md` |

## Step-by-step playbooks

Follow these in full. Each covers a task where **skipping a step compiles cleanly and fails at
runtime**.

| Task | Playbook |
| -------------------------------------- | ------------------------------------------------- |
| Add an API endpoint | `.agents/docs/playbooks/add-api-endpoint.md` |
| Add a route to `apps/web` | `.agents/docs/playbooks/add-web-route.md` |
| Add a data-fetching hook to `apps/web` | `.agents/docs/playbooks/add-web-data-hook.md` |
| Add an end-to-end test | `.agents/docs/playbooks/add-e2e-test.md` |
| Add a built-in instrument | `.agents/docs/playbooks/add-instrument.md` |
| Add a vendored library for instruments | `.agents/docs/playbooks/add-vendor-package.md` |
| Add an environment variable | `.agents/docs/playbooks/add-env-var.md` |
| Add a blog post | `.agents/docs/playbooks/add-blog-post.md` |
| Run the stack on a fresh clone | `.agents/docs/playbooks/run-locally.md` |
| Cut a release | `.agents/docs/playbooks/cut-a-release.md` |
| Add a vitest project to a package | `.agents/docs/playbooks/add-vitest-project.md` |
| Promote a component to react-core | `.agents/docs/playbooks/promote-to-react-core.md` |
7 changes: 4 additions & 3 deletions .agents/docs/playbooks/cut-a-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ them. Never hand-edit a version field.
## Steps

1. **Get the branch green before you bump.** Nothing after the PR runs a test against this code: the
release's `validate` job runs `pnpm lint` only. Run the root `AGENTS.md` "Before you are done"
checks; root `pnpm test` has no turbo `^build` behind it, so run `pnpm build` once first. `pnpm lint`
rewrites files, so commit any churn on its own before the bump. Also confirm
release's `validate` job runs `pnpm lint` only. Run `pnpm check`
(`.agents/skills/finishing-changes/SKILL.md`); root `pnpm test` has no turbo `^build` behind it, so
run `pnpm build` once first. `pnpm lint` rewrites files, so commit any churn on its own before the
bump. Also confirm
`gh run list --workflow=Release --limit 1` is green on `main`: nothing local builds the Docker
images, and `build` gates both publishes.

Expand Down
31 changes: 31 additions & 0 deletions .agents/skills/adding-dependencies/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: adding-dependencies
description: Add, upgrade or replace a third-party npm package in Open Data Capture. Use before touching a dependencies block in any package.json or the catalog in pnpm-workspace.yaml, including when a task seems to need a library the repo does not have.
---

**Ask in-conversation before adding anything**: name the package, what it is for, and what you checked first.
Do not install while waiting. A literal version in a `package.json` installs cleanly and is caught only by the
grep under Verify; a version too new for the release-age gate is rejected at `pnpm install`.

1. **A library that instruments import is not a dependency of this repo.** It goes through `vendor/`, where
each wrapper pins a literal version on purpose: `.agents/docs/playbooks/add-vendor-package.md`, and none
of the steps below.
2. **Check what the repo already has.** `.agents/docs/packages/index.md` lists the `@douglasneuroinformatics/*`
packages this repo consumes; one of them usually covers a utility, crypto, stats, form-typing or UI need.
Then grep the `catalog:` block of `pnpm-workspace.yaml`: a package already there is added to a workspace
with `"catalog:"` and needs no question.
3. **Pin the version in the catalog, not the package.** Every version lives once, in the `catalog:` block of
`pnpm-workspace.yaml`; a `package.json` references `"catalog:"`, never a literal version, so two workspaces
cannot drift apart.
4. **Pick a version older than `minimumReleaseAge`.** It is 7 days (`pnpm-workspace.yaml`), so a release newer
than that is rejected at install time. Take the previous release rather than extending
`minimumReleaseAgeExclude`; that list is for the repo's own `@douglasneuroinformatics/*` packages and
pins the user has explicitly approved.
5. **Run `pnpm install`** so the lockfile change lands in the same commit as the `package.json` change.

## Verify

```sh
pnpm install --frozen-lockfile # ERR_PNPM_OUTDATED_LOCKFILE means step 5 was skipped
grep -n '<package>' pnpm-workspace.yaml # no line means the version was pinned in a package.json, not the catalog
```
3 changes: 3 additions & 0 deletions .agents/skills/adding-dependencies/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: 'Adding Dependencies'
short_description: 'Add an npm package through the catalog, after asking'
47 changes: 47 additions & 0 deletions .agents/skills/adversarial-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
name: adversarial-review
description: Review the current branch against upstream main before a pull request is opened, or before a follow-up that adds code is pushed to one — a prosecutor sub-agent finds defects, a defence sub-agent tries to refute each, and only confirmed findings come back to the caller. Runs only after `pnpm check` is green; changes no code.
---

Two `general-purpose` sub-agents in sequence: the prosecutor finds, the defence refutes, and the caller sees
only what survives, because the caller acts on every line it is given and has no human to filter for it.
`pnpm check` has already passed when this runs, so neither agent runs lint, tsc or a test, and anything those
would report is not a finding.

## 1. Select the diff

```sh
git fetch upstream main
BASE=$(git merge-base upstream/main HEAD)
git diff --name-only --diff-filter=d "$BASE"
git ls-files --others --exclude-standard
```

`origin` is the fork (`CONTRIBUTING.md`), so a base taken from it reviews against a stale main and misses
what upstream has changed underneath. A clone without the `upstream` remote adds it first:
`git remote add upstream https://github.com/DouglasNeuroInformatics/OpenDataCapture.git`.
Keep only `.ts`, `.tsx`, `.js`, `.jsx`, `.css` and `.prisma` files, and drop `apps/web/src/route-tree.ts`,
`testing/src/generated/` and `*.stories.*`. An empty list ends the review: reply `CLEAN`.

## 2. Prosecute

Spawn one sub-agent:

> Follow `.agents/skills/adversarial-review/references/prosecutor.md`. Base: `<BASE>`. Files: `<the list>`.

## 3. Defend

When the prosecutor returned anything but `NONE`, spawn one sub-agent with its output verbatim:

> Follow `.agents/skills/adversarial-review/references/defence.md`. Base: `<BASE>`. Findings: `<the output>`.

## 4. Report

Reply with the `CONFIRMED` findings only, worst first, one line each under the prosecutor's number, and
nothing else:

```
<n>. <path>:<line> — <claim> — <fix>
```

`CLEAN` when nothing survived. A refuted finding is not mentioned.
3 changes: 3 additions & 0 deletions .agents/skills/adversarial-review/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface:
display_name: 'Adversarial Review'
short_description: 'Prosecute and defend a branch against upstream main before a PR'
13 changes: 13 additions & 0 deletions .agents/skills/adversarial-review/references/defence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Defence

You try to refute each finding. Read root `AGENTS.md` and `.agents/docs/coding-guidelines.md`. For each
finding, read the cited location in full, trace the callers or data flow its evidence names, and look for
what the prosecutor missed: a guard upstream, a type that excludes the case, a test that would fail if the defect were real,
a rule quoted out of context. Run nothing that writes, and not lint, tsc or any test.

A finding is `REFUTED` only by evidence you cite; one you merely doubt is `CONFIRMED`. Return one line per
finding, keeping its number, and nothing else:

```
<n>. CONFIRMED | REFUTED — <one sentence: what you traced and what it showed>
```
30 changes: 30 additions & 0 deletions .agents/skills/adversarial-review/references/prosecutor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Prosecutor

You review the change on the current branch. Read root `AGENTS.md`, `.agents/docs/coding-guidelines.md`, the
`AGENTS.md` of every workspace a listed file sits in, and `.agents/docs/architecture/auth-and-permissions.md`
when a listed file is under `apps/api/`. Read every listed file in full, then `git diff <base> -- <files>`;
an untracked file is new in full. Read callers and the rest of a module when a claim depends on them. Run
nothing that writes, and not lint, tsc or any test: the caller has already run `pnpm check` green.

Findings concern changed code and code the change makes wrong, wherever that code is. Look for, worst
first:

- Data crossing a group boundary: a Prisma `where` without `accessibleQuery`, or a handler that forwards
neither `@CurrentUser('ability')` nor, to a service taking `RequestUser`, `@CurrentUser()`.
- Access wrongly granted, or no check for the action taken.
- Data lost, corrupted or silently dropped: a logic error, an unhandled state, a race, a swallowed error.
- Data from outside the system — a request, the environment, a third-party response — used before a Zod
schema parses it.
- A test that would still pass if the code under it were wrong.
- A breach of a rule in the files you read, with the rule quoted.
- Complexity its purpose does not justify: a helper with one caller, an abstraction with one user, pure
indirection, a near-duplicate, a defensive check on data the system wrote, a step that changes nothing.

Before returning, re-read each location and check every quote word for word; drop what does not hold. Return
`NONE`, or one finding per paragraph:

```
<n>. <path>:<line> — <claim in one sentence>
Evidence: <what the code does and the callers or data flow the claim rests on; the quoted rule when one applies>
Fix: <the change to make>
```
Loading
Loading