@nurix/etna 0.18.23 → 0.18.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/harness-index.json +1 -5
- package/data/harnesses/code/harness.json +1 -1
- package/data/harnesses/etna/CLAUDE.md +5 -14
- package/data/harnesses/etna/harness.json +2 -2
- package/package.json +1 -1
- package/data/harnesses/mobile/.prettierignore +0 -12
- package/data/harnesses/mobile/.prettierrc.json +0 -3
- package/data/harnesses/mobile/.vscode/settings.json +0 -10
- package/data/harnesses/mobile/CLAUDE.md +0 -151
- package/data/harnesses/mobile/agents/brainstorm.md +0 -35
- package/data/harnesses/mobile/agents/change-review.md +0 -251
- package/data/harnesses/mobile/agents/lld-mode.md +0 -56
- package/data/harnesses/mobile/agents/maintenance.md +0 -216
- package/data/harnesses/mobile/agents/performance-review.md +0 -223
- package/data/harnesses/mobile/harness.json +0 -11
- package/data/harnesses/mobile/hooks/generate-ownership-index.mjs +0 -148
- package/data/harnesses/mobile/hooks/hooks.json +0 -16
- package/data/harnesses/mobile/rules/database.md +0 -38
- package/data/harnesses/mobile/rules/general.md +0 -49
- package/data/harnesses/mobile/rules/nomenclature.md +0 -34
- package/data/harnesses/mobile/rules/nustack.md +0 -150
- package/data/harnesses/mobile/rules/react-native.md +0 -132
- package/data/harnesses/mobile/rules/testing.md +0 -25
- package/data/harnesses/mobile/rules/typescript.md +0 -40
- package/data/harnesses/mobile/skills/boundary/SKILL.md +0 -69
- package/data/harnesses/mobile/skills/boundary/references/index.md +0 -16
- package/data/harnesses/mobile/skills/boundary/references/mechanisms.md +0 -99
- package/data/harnesses/mobile/skills/boundary/references/packs/async-messaging.md +0 -81
- package/data/harnesses/mobile/skills/boundary/references/packs/auth.md +0 -93
- package/data/harnesses/mobile/skills/boundary/references/packs/build-config-runtime.md +0 -93
- package/data/harnesses/mobile/skills/boundary/references/packs/cloudflare.md +0 -93
- package/data/harnesses/mobile/skills/boundary/references/packs/frontend.md +0 -93
- package/data/harnesses/mobile/skills/boundary/references/packs/generic-tail.md +0 -55
- package/data/harnesses/mobile/skills/boundary/references/packs/persistence.md +0 -93
- package/data/harnesses/mobile/skills/boundary/references/packs/wire-api.md +0 -87
- package/data/harnesses/mobile/skills/brainstorm/README.md +0 -162
- package/data/harnesses/mobile/skills/brainstorm/SKILL.md +0 -152
- package/data/harnesses/mobile/skills/brainstorm/exit-artifacts.md +0 -66
- package/data/harnesses/mobile/skills/brainstorm/templates/backlog.md +0 -39
- package/data/harnesses/mobile/skills/brainstorm/templates/build.md +0 -37
- package/data/harnesses/mobile/skills/brainstorm/templates/decisions.md +0 -29
- package/data/harnesses/mobile/skills/brainstorm/templates/doctrine.md +0 -31
- package/data/harnesses/mobile/skills/brainstorm/templates/domain-model.md +0 -25
- package/data/harnesses/mobile/skills/brainstorm/templates/evidence.md +0 -24
- package/data/harnesses/mobile/skills/brainstorm/templates/glossary.md +0 -20
- package/data/harnesses/mobile/skills/brainstorm/templates/milestones.md +0 -43
- package/data/harnesses/mobile/skills/brainstorm/templates/non-goals.md +0 -17
- package/data/harnesses/mobile/skills/brainstorm/templates/plan-doc.md +0 -61
- package/data/harnesses/mobile/skills/brainstorm/templates/questions.md +0 -36
- package/data/harnesses/mobile/skills/brainstorm/templates/references.md +0 -28
- package/data/harnesses/mobile/skills/brainstorm/templates/rejected.md +0 -33
- package/data/harnesses/mobile/skills/brainstorm/templates/source-of-truth.md +0 -49
- package/data/harnesses/mobile/skills/brainstorm/templates/state.md +0 -1
- package/data/harnesses/mobile/skills/brainstorm/templates/tech.md +0 -23
- package/data/harnesses/mobile/skills/brainstorm/templates/thesis.md +0 -21
- package/data/harnesses/mobile/skills/documentation/SKILL.md +0 -172
- package/data/harnesses/mobile/skills/documentation/references/examples.md +0 -203
- package/data/harnesses/mobile/skills/documentation/references/style-guide.md +0 -315
- package/data/harnesses/mobile/skills/documentation/references/templates.md +0 -1091
- package/data/harnesses/mobile/skills/workflow-mapper/SKILL.md +0 -451
- package/data/harnesses/mobile/skills/workflow-mapper/references/patterns.md +0 -214
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Database
|
|
2
|
-
|
|
3
|
-
Fixed conventions for relational schemas — table/column/index **names**, the **key vocabulary** (`id` / `slug` / `label`), column types, and Drizzle mirrors — so the same concept carries the same name in **every** Nurix project. This is a cross-project consistency rule: **one concept, one name, everywhere** — never `slug` in one repo and `handle` / `key` / `code` in another. General code naming (variables, files, casing) is [`nomenclature.md`](nomenclature.md); this rule owns the database.
|
|
4
|
-
|
|
5
|
-
Written for SQLite / Cloudflare D1 + Drizzle (the concrete types below). The **names and key vocabulary are engine-agnostic** — a Postgres project keeps the same column names and semantics with native types (`uuid`, `timestamptz`, `boolean`).
|
|
6
|
-
|
|
7
|
-
## Keys & the shared vocabulary
|
|
8
|
-
|
|
9
|
-
Three column roles are fixed across every table and every project:
|
|
10
|
-
|
|
11
|
-
- **`id`** — the primary key, **always a UUID** (`TEXT PRIMARY KEY` on D1, `uuid` on Postgres). Never a synthetic or natural key (`type_page_button`, `human:default` are ❌). Runtime rows mint a UUIDv7; seed rows derive a deterministic UUIDv5 of the natural key so re-seeds stay stable. Never mix synthetic and UUID ids in one table.
|
|
12
|
-
- **`slug`** — the **stable key other rows reference**: `UNIQUE`, lowercase `snake_case`. **Foreign keys point at `slug`, not `id`.** One concept = one slug name across every table and repo. ❌ Never rename this role `handle`, `key`, `code`, or `name` in a different project — it is always `slug`.
|
|
13
|
-
- **`label`** — the **human display name**. Always `label` — ❌ never `name`, `title`, or `display_name`.
|
|
14
|
-
|
|
15
|
-
Reference columns name what they point at: `<entity>_id` for a UUID-PK reference (`user_id`, `app_id`), `<entity>_slug` for a slug reference (`source_slug`).
|
|
16
|
-
|
|
17
|
-
- **Enforced** (the referenced row must exist) → a real `FOREIGN KEY (<x>_slug) REFERENCES <table>(slug)`.
|
|
18
|
-
- **Drift-tolerant** (a controlled-vocabulary value that may fall off-list) → a plain value column, **no** FK, paired with an `is_*` boolean recording whether the value is in the canonical set.
|
|
19
|
-
|
|
20
|
-
## Naming
|
|
21
|
-
|
|
22
|
-
- **Tables** — `snake_case`, plural noun for what they hold (`users`, `apps`, `devices`).
|
|
23
|
-
- **Junction (M:N) tables** — `<entityA>_<entityB>_map`, **always**, even when the join carries its own columns; singular stems, owning/anchor entity first (`app_user_map`, `app_device_map`). ❌ Never a "nice" noun (`members`, `installs`, `memberships`, `enrolments`) — the name must read as a join, and a reviewer should know both sides from it.
|
|
24
|
-
- **Columns** — `snake_case`; booleans `is_*` / `has_*`; timestamps `created_at` / `updated_at`; foreign keys `<entity>_id`.
|
|
25
|
-
- **Indexes** — `<table>_<col(s)>_idx`; unique `<table>_<col(s)>_unique`; a junction's composite unique reads `<table>_<colA>_<colB>_unique` (`app_user_map_app_user_unique`).
|
|
26
|
-
- **Drizzle mirror** — the exported object is the camelCase table name (`app_user_map` → `appUserMap`); column props are the camelCase of the column (`created_at` → `createdAt`). The `sqliteTable("…")` string and every index name must match the DDL (`init.sql`) exactly — the schema-parity boundary.
|
|
27
|
-
|
|
28
|
-
## Column types (SQLite / D1)
|
|
29
|
-
|
|
30
|
-
- **Timestamps** — `created_at` + `updated_at`, both `TEXT NOT NULL DEFAULT (datetime('now'))`, on **every** table — except append-only logs, which carry only `created_at`.
|
|
31
|
-
- **Booleans** — `INTEGER`, named `is_*` / `has_*` (Drizzle `integer(…, { mode: "boolean" })`).
|
|
32
|
-
- **JSON** — `TEXT` (Drizzle `text(…, { mode: "json" })`).
|
|
33
|
-
|
|
34
|
-
## Vocabulary / lookup tables
|
|
35
|
-
|
|
36
|
-
A controlled-vocabulary table (statuses, kinds, categories) follows a fixed shape: `id` (UUID) · `slug` (UNIQUE) · `label` · optional `description` · `status` · timestamps.
|
|
37
|
-
|
|
38
|
-
- **Seeds are generated, never hand-written** — the source of truth is a CSV or a shared package; a generator emits the `*.seed.sql` with UUIDv5 ids and an idempotent `ON CONFLICT(slug)` upsert that preserves live counts / admin-tuned columns. Hand-editing seed SQL is ❌ — regenerate it.
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# General Standards
|
|
2
|
-
|
|
3
|
-
Runtime versions and the canonical package list — the workspace baseline every Nurix code harness loads. Web-frontend/styling conventions are [`frontend.md`](frontend.md); the Express server and the API response envelope are [`express.md`](express.md).
|
|
4
|
-
|
|
5
|
-
## Runtime Versions
|
|
6
|
-
|
|
7
|
-
- **Node.js:** 22 LTS (latest)
|
|
8
|
-
- **Package manager:** detect from lockfile (`pnpm-lock.yaml` → pnpm, `yarn.lock` → yarn, `package-lock.json` → npm). Don't hardcode one.
|
|
9
|
-
- **PostgreSQL:** 17 · **Redis:** 7.x (latest)
|
|
10
|
-
|
|
11
|
-
## Recommended Packages
|
|
12
|
-
|
|
13
|
-
No alternatives. Just use these.
|
|
14
|
-
|
|
15
|
-
| Package | Version | Use | Don't use |
|
|
16
|
-
| ------------------------ | --------- | ---------------------- | -------------------------- |
|
|
17
|
-
| react | ^19.2.3 | UI components | - |
|
|
18
|
-
| vite | ^8.0.0 | Build tool, dev server | webpack, CRA |
|
|
19
|
-
| tailwindcss | ^4.1.18 | Styling | styled-components |
|
|
20
|
-
| @vitejs/plugin-react-swc | ^4.2.2 | JSX compilation | @vitejs/plugin-react |
|
|
21
|
-
| react-router-dom | ^7.11.0 | Routing | - |
|
|
22
|
-
| @reduxjs/toolkit | ^2.11.2 | State management | zustand, jotai |
|
|
23
|
-
| react-redux | ^9.2.0 | Redux React bindings | - |
|
|
24
|
-
| axios | ^1.13.2 | HTTP requests | fetch |
|
|
25
|
-
| date-fns | ^4.1.0 | Date formatting | moment, dayjs |
|
|
26
|
-
| @dnd-kit/core | ^6.3.1 | Drag and drop | react-beautiful-dnd |
|
|
27
|
-
| motion | ^12.23.26 | Animations | react-spring |
|
|
28
|
-
| sonner | ^2.0.7 | Toast notifications | react-toastify |
|
|
29
|
-
| react-window | ^2.2.3 | List virtualization | react-virtualized |
|
|
30
|
-
| nanoid | ^5.1.6 | ID generation | uuid |
|
|
31
|
-
| express | ^5.2.1 | HTTP server | fastify, koa |
|
|
32
|
-
| drizzle-orm | latest | Database ORM | prisma, sequelize, typeorm |
|
|
33
|
-
| drizzle-kit | latest | Schema tooling | - |
|
|
34
|
-
| zod | ^4.2.1 | Schema validation | yup, joi |
|
|
35
|
-
| jose | ^6.1.3 | JWT handling | jsonwebtoken |
|
|
36
|
-
| microdiff | ^1.5.0 | JSON object diffing | deep-diff, just-diff |
|
|
37
|
-
| react18-json-view | ^0.2.9 | JSON tree view | react-json-view |
|
|
38
|
-
| @tiptap/react | ^3.11.1 | Rich text editor | slate, quill, draft-js |
|
|
39
|
-
| shiki | ^3.22.0 | Syntax highlighting | prism, highlight.js |
|
|
40
|
-
| lucide-react | ^0.562.0 | Icons | react-icons, heroicons |
|
|
41
|
-
| @pdfme/generator | ^5.5.8 | PDF generation | jspdf, pdfkit |
|
|
42
|
-
| cmdk | ^1.1.1 | Command palette | kbar, ninja-keys |
|
|
43
|
-
| @monaco-editor/react | ^4.7.0 | Code editor | codemirror, ace |
|
|
44
|
-
| localforage | ^1.10.0 | Client-side storage | idb, dexie |
|
|
45
|
-
| react-markdown | ^10.1.0 | Markdown rendering | marked, markdown-it |
|
|
46
|
-
| remark-gfm | ^4.0.1 | GFM (tables, tasklists) | remark-github, custom |
|
|
47
|
-
| fast-deep-equal | ^3.1.3 | Deep equality checks | lodash.isEqual |
|
|
48
|
-
| @ngneat/falso | latest | Fake data generation | @faker-js/faker, chance |
|
|
49
|
-
| pino | ^10.1.0 | Logging | winston |
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Nomenclature Guidelines
|
|
2
|
-
|
|
3
|
-
## Naming Guidelines
|
|
4
|
-
|
|
5
|
-
- Use descriptive names that reveal intent, purpose, and usage
|
|
6
|
-
- Names should explain why something exists and how it's used
|
|
7
|
-
- Favor clarity over brevity
|
|
8
|
-
- Maintain consistency within the codebase
|
|
9
|
-
- Avoid abbreviations unless they're universally understood
|
|
10
|
-
|
|
11
|
-
## Naming Styles
|
|
12
|
-
|
|
13
|
-
- Variables : Use nouns or noun phrases. Examples: `userData`, `firstName`, `paymentStatus`, `configOptions`
|
|
14
|
-
- Functions & Methods : Use verbs or verb phrases that describe the action. Examples: `calculateTotal`, `getUserInfo`, `validateInput`, `sendNotification`
|
|
15
|
-
- Boolean Variables : Prefix with is**, has**, can**, or should**. Examples: `isValid`, `hasPermission`, `canEdit`, `shouldRefresh`
|
|
16
|
-
- Arrays : Use plural nouns. Examples: `users`, `productList`, `errorMessages`, `activeConnections`
|
|
17
|
-
- Classes & Components : Use singular nouns or noun phrases. Examples: `RequestHandler`, `UserProfile`, `PaymentProcessor`, `DataValidator`
|
|
18
|
-
- Constants : Use uppercase with meaningful names. Examples: `DEFAULT_TIMEOUT`, `API_BASE_URL`, `MAX_RETRY_COUNT`, `COLOR_PRIMARY`
|
|
19
|
-
- Event Handlers : Use Prefix with handle or on. Examples: `handleSubmit`, `onUserLogin`, `handleInputChange`, `onPageLoad`
|
|
20
|
-
- Interfaces & Types : Use Consider prefixing with I for interfaces (if preferred). Examples: `IUserData`, `IConfigOptions`, `AuthResponse`, `RequestParams`
|
|
21
|
-
- Promises & Async Functions : Use Consider using future tense or suffixes like Async. Examples: `fetchUserData`, `getUserProfileAsync`, `loadConfigAsync`, `processPayment`
|
|
22
|
-
- File & Folder Structure : Use lowercase with descriptive names. Group by feature or function type. Example: `auth/login_service.js`
|
|
23
|
-
|
|
24
|
-
## Casing Styles
|
|
25
|
-
|
|
26
|
-
- camelCase : Used in Variables, function names, method names, property names. Examples: `userData`, `calculateTotal`, `firstName`, `isValidEmail`
|
|
27
|
-
- PascalCase : Used in Class names, component names, type definitions. Examples: `UserProfile`, `DataService`, `AuthProvider`, `RequestHandler`
|
|
28
|
-
- snake_case : Used in File names, folder names. Examples: `user_service`, `api_utils`, `auth_middleware`, `data_models`
|
|
29
|
-
- UPPER_SNAKE_CASE : Used in Constants, environment variables. Examples: `MAX_RETRY_COUNT`, `API_KEY`, `DEFAULT_TIMEOUT`, `COLOR_PRIMARY`
|
|
30
|
-
- kebab-case : Used in URLs, CSS class names, HTML IDs. Examples: `nav-container`, `user-profile`, `button-primary`, `form-wrapper`
|
|
31
|
-
|
|
32
|
-
## Database
|
|
33
|
-
|
|
34
|
-
Table, column, index, and key naming for relational schemas — the `id` / `slug` / `label` vocabulary, junction `_map` tables, and Drizzle mirrors — lives in [`database.md`](database.md), the cross-project database conventions rule. Kept there, not here, so there is one canonical home and the same concept carries the same name in every project.
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# Nustack — `@nurix/*` Packages: naming, monorepo layout, and publishing
|
|
2
|
-
|
|
3
|
-
How a `@nurix/*` multi-package repo is **named**, **structured**, and **shipped**. For consuming an already-published `@nurix/*` package (auth, install, imports), see the Consuming section of [`typescript.md`](typescript.md).
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Package naming
|
|
8
|
-
|
|
9
|
-
Every workspace package is scoped to **`@nurix/`**. The segment after the scope encodes **intent** — the dot vs. hyphen is semantic, not cosmetic:
|
|
10
|
-
|
|
11
|
-
| Form | Meaning | Examples |
|
|
12
|
-
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
13
|
-
| `@nurix/workflows-sdk` (hyphen, single segment) | The **published, externally-destined** surface. A clean `@nurix/*` name is a published _contract_ — changing an export or peer dep is a breaking change for consumers outside this repo. | `@nurix/workflows-sdk` |
|
|
14
|
-
| `@nurix/workflows.<name>` (dot separator) | **Internal** to the workflows product — `private: true`, refactor freely, no external consumers. | `@nurix/workflows.agent-core`, `.worker`, `.gateway`, `.secrets`, `.connector`, `.playground`, `.playground-tests` |
|
|
15
|
-
| `@nurix/<name>` (plain, no `workflows` prefix) | Cross-repo **shared libs** owned _elsewhere_ and merely consumed here — never renamed. | `@nurix/contracts`, `@nurix/components` |
|
|
16
|
-
|
|
17
|
-
- The `<name>` segment is always **kebab-case** (`agent-core`, `playground-tests`).
|
|
18
|
-
- The dot is **not** a path separator, so subpath exports still resolve: `@nurix/workflows.agent-core/nodes`.
|
|
19
|
-
- The dot tracks "internal product package," not the `private` flag exactly — `@nurix/workflows.secrets` is `private: false` because it's extraction-bound (slated to move to a sibling repo/namespace), yet still wears the dotted internal name. Today `-sdk` is the only clean published form.
|
|
20
|
-
|
|
21
|
-
**Naming a new package:** internal to this repo? → `@nurix/workflows.<thing>`, `private: true`, kebab-case `<thing>` (the default). Externally consumed contract? → clean `@nurix/<thing>` (rare). Shared lib from another repo? → keep its existing `@nurix/<name>`.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## 2. Monorepo & package boundaries
|
|
26
|
-
|
|
27
|
-
One `tsconfig.base.json` at the repo root is the **single owner of TS policy** — the strict set (see [`typescript.md`](typescript.md)), `target`, and any option uniform across packages (here, `module`/`moduleResolution`). Each package's `tsconfig.json` is thin: it `extends` the base, then adds only its own *coordinates* (`rootDir`/`outDir`/`include`) and genuine *environment* overrides (`lib`/`types`/`jsx`, plus `module`/`moduleResolution` when a package's module system genuinely differs — a Worker, browser, or NodeNext package). That is environment, not policy. Never re-declare or weaken a *policy* option locally (no per-package `strict: false`, no widening `noUncheckedIndexedAccess`). A framework that owns its own base (e.g. an Astro app on `astro/tsconfigs/strict`) is the one allowed exception.
|
|
28
|
-
|
|
29
|
-
- **Internal packages are source-first.** A `private: true` workspace package exposes its API through the `exports` field pointing at TypeScript source (`"types"`/`"default"` → `./src/index.ts`) — consumed across the workspace with **no build step**. Prefer the `exports` field; don't add a bare `main`/`module` to new packages. Add one typed subpath per public surface (`"./nodes": { "types": "./src/nodes/index.ts", "default": "./src/nodes/index.ts" }`); consumers import the subpath, **never** a deep relative path into another package's `src/`.
|
|
30
|
-
- **Published packages build to `dist` — never ship raw `src`.** The moment a package crosses a publish boundary (`private: false`), it compiles with `tsc` (`declaration: true`, `declarationMap: true` from the base), ships `files: ["dist"]`, and points `exports`/`types` at `./dist/*` (use `publishConfig.exports` to swap source→dist on publish if the dev entry stays on source). `prepublishOnly` runs the build.
|
|
31
|
-
- **Declare host singletons as `peerDependencies`** on published libraries (`react`, `react-dom`, the framework client) so consumers keep one copy; never bundle them. Apps depend on them directly.
|
|
32
|
-
- **Single-source shared dep versions with pnpm `catalog:`** — `typescript`, `zod`, `vitest`, `react`, type packages. One version per dependency across the workspace so every package's types resolve identically; no per-manifest version drift.
|
|
33
|
-
- **Reach for TS project references only when it hurts** — adopt `references` once the typecheck/build graph is slow enough to feel. Until then, `pnpm -r typecheck` over independent `extends` configs is the correct default.
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 3. Publishing & the release pipeline
|
|
38
|
-
|
|
39
|
-
`@nurix/*` packages publish to **npmjs.org with `restricted` access** — only npm users with read rights to the `nurix` org can install them. Reference implementations: `~/dev/components` (`@nurix/components`, library) and `~/dev/etna/packages/cli` (`@nurix/etna`, CLI).
|
|
40
|
-
|
|
41
|
-
### 3.1 Prerequisites (one-time, org-wide)
|
|
42
|
-
|
|
43
|
-
1. **npm org `nurix`** exists and you're a member with publish rights — <https://www.npmjs.com/settings/nurix/packages>.
|
|
44
|
-
2. **An npm automation token** with `Read and write` for the `nurix` scope — npm → Generate New Token → Automation.
|
|
45
|
-
3. **The token added as a GitHub Actions secret** named `NPM_PUBLISH_TOKEN` on every publishing repo. `NPM_PUBLISH_TOKEN` is the convention; pick another name only with a strong reason.
|
|
46
|
-
|
|
47
|
-
### 3.2 `package.json` — required fields
|
|
48
|
-
|
|
49
|
-
```jsonc
|
|
50
|
-
{
|
|
51
|
-
"name": "@nurix/<package-name>", // The scope makes it discoverable to org members.
|
|
52
|
-
"version": "0.1.0", // Start at 0.1.0; the workflow bumps from here.
|
|
53
|
-
"private": false, // MUST be false (or omitted). `npm publish` refuses if true.
|
|
54
|
-
"publishConfig": { "access": "restricted" }, // Scoped + private: only org members can install.
|
|
55
|
-
"files": ["dist"] // Whitelist of what ships. Anything outside is excluded.
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
- **`publishConfig.access`** — `"restricted"` (recommended): visible only to `nurix`-org readers. `"public"`: world-readable (free, but only if you mean it). Omitting it on a scoped package defaults to `restricted`; be explicit anyway.
|
|
60
|
-
- **`files`** — without it, npm publishes everything not in `.gitignore`, **including source**. Library → `["dist"]`; CLI (etna) → `["bin/", "data/", "README.md"]`. Don't list `package.json`/`README.md` — npm always includes them.
|
|
61
|
-
- **Build hooks** — `prepack` for build steps that must produce files in `files` (also runs on install-as-git-dep); `prepublishOnly` for publish-only steps. Either alone is fine; `@nurix/components` runs both.
|
|
62
|
-
- **Other useful fields** — `type: "module"`, `engines.node`, `bin` (CLIs), `main`/`module`/`types`/`exports` (libraries), `sideEffects: ["**/*.css"]`, `repository` (with `directory` for monorepos), `license: "UNLICENSED"`.
|
|
63
|
-
|
|
64
|
-
### 3.3 The publish workflow (generic template)
|
|
65
|
-
|
|
66
|
-
The dual-trigger pattern: a push to the default branch auto-ships a patch; a manual dispatch picks minor/major.
|
|
67
|
-
|
|
68
|
-
```yaml
|
|
69
|
-
name: Publish to npm
|
|
70
|
-
on:
|
|
71
|
-
push:
|
|
72
|
-
branches: [dev] # your default branch
|
|
73
|
-
paths: # ONLY paths that change what ships — else every README edit publishes
|
|
74
|
-
- 'src/**'
|
|
75
|
-
- 'scripts/**'
|
|
76
|
-
- 'package.json'
|
|
77
|
-
- 'package-lock.json'
|
|
78
|
-
- '.github/workflows/publish.yml'
|
|
79
|
-
workflow_dispatch:
|
|
80
|
-
inputs:
|
|
81
|
-
bump: { description: 'Version bump', type: choice, options: [patch, minor, major], default: patch }
|
|
82
|
-
permissions: { contents: write } # push the version-bump commit + tag
|
|
83
|
-
concurrency: { group: publish, cancel-in-progress: false } # monorepo: group: publish-<pkg>
|
|
84
|
-
jobs:
|
|
85
|
-
publish:
|
|
86
|
-
runs-on: ubuntu-latest
|
|
87
|
-
steps:
|
|
88
|
-
- uses: actions/checkout@v5
|
|
89
|
-
with: { fetch-depth: 0, token: '${{ secrets.GITHUB_TOKEN }}' }
|
|
90
|
-
- uses: actions/setup-node@v5
|
|
91
|
-
with: { node-version: 22, registry-url: 'https://registry.npmjs.org' } # writes .npmrc using $NODE_AUTH_TOKEN
|
|
92
|
-
- run: npm ci # skip ONLY if the build is pure-Node with no deps (e.g. @nurix/etna)
|
|
93
|
-
- run: |
|
|
94
|
-
git config user.name "github-actions[bot]"
|
|
95
|
-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
96
|
-
- run: npm version ${{ inputs.bump || 'patch' }} -m "chore: release v%s [skip ci]"
|
|
97
|
-
- run: npm publish # prepublishOnly / prepack runs the build
|
|
98
|
-
env: { NODE_AUTH_TOKEN: '${{ secrets.NPM_PUBLISH_TOKEN }}' }
|
|
99
|
-
- run: git push --follow-tags # pushes the bump commit AND the v<version> tag npm created
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
**Why dual-trigger is safe** — three safeguards prevent an infinite publish loop: (1) `paths:` scopes the push trigger to artifact-affecting files; (2) `[skip ci]` in the bump commit makes GitHub skip the workflow that bump would otherwise re-trigger; (3) `concurrency: cancel-in-progress: false` queues near-simultaneous merges instead of racing for a version number.
|
|
103
|
-
|
|
104
|
-
**`registry-url` is load-bearing** — without `registry-url: https://registry.npmjs.org` on `setup-node`, it never writes the `.npmrc` line that uses `$NODE_AUTH_TOKEN`, and the publish falls back to anonymous auth and fails.
|
|
105
|
-
|
|
106
|
-
### 3.4 The three ways to ship
|
|
107
|
-
|
|
108
|
-
- **A. Auto on merge (common).** Land on the default branch; if a `paths:`-filtered file changed, the workflow fires and ships a patch.
|
|
109
|
-
- **B. Manual dispatch (minor/major).** **Actions → Publish to npm → Run workflow**, pick `bump = minor|major`.
|
|
110
|
-
- **C. Local (CI down / one-off):** `npm version <patch|minor|major>` → `npm publish` → `git push --follow-tags`.
|
|
111
|
-
|
|
112
|
-
Local auth uses two tokens, two homes: a **read** token in `~/.npmrc` (`//registry.npmjs.org/:_authToken=…`) for `npm install`, and `NPM_PUBLISH_TOKEN` as an env var (sourced from `~/.zsh_secrets` or equivalent, **not** in `.npmrc` or the repo) for `npm publish`.
|
|
113
|
-
|
|
114
|
-
> **Agent guard — if `NPM_PUBLISH_TOKEN` isn't in the environment, do not work around it.** No `npm login` to swap the user's auth, no rewriting `~/.npmrc`, no substituting the read token (it lacks publish rights → `403`), no `--registry` overrides, no temp-file token. Instead **stop and report**: "This repo can't publish locally — `NPM_PUBLISH_TOKEN` isn't set. Source `~/.zsh_secrets` (or wherever you keep it) and re-run, or trigger the CI workflow." Some users publish exclusively from CI — that's the default path for a reason.
|
|
115
|
-
|
|
116
|
-
Verify after any flow: `https://www.npmjs.com/package/@nurix/<name>` and a clean-dir `npx @nurix/<name>@latest`.
|
|
117
|
-
|
|
118
|
-
### 3.5 Monorepo deviations
|
|
119
|
-
|
|
120
|
-
When the package lives in `packages/<pkg>/` of a multi-package repo:
|
|
121
|
-
|
|
122
|
-
- **`working-directory: packages/<pkg>`** on the bump and publish steps so they operate on the right `package.json`.
|
|
123
|
-
- **Scoped concurrency** — `group: publish-<pkg>` lets sibling packages publish in parallel.
|
|
124
|
-
- **Tag prefix — only with multiple tagging packages.** `npm version --tag-version-prefix="<pkg>-v"` (→ `<pkg>-v0.2.0`) is worth it only when more than one package cuts release tags, so they don't collide. A single publishable package (etna's case, even though it's laid out as a monorepo) uses plain `v0.2.0`.
|
|
125
|
-
|
|
126
|
-
A repo with one publishable package only needs `working-directory`; skip the prefix, and concurrency scoping is optional.
|
|
127
|
-
|
|
128
|
-
### 3.6 Common pitfalls
|
|
129
|
-
|
|
130
|
-
- **402 Payment Required** — `access: "public"` on an org without a plan for free public scoped packages. Use `"restricted"` (or upgrade the plan).
|
|
131
|
-
- **403 Forbidden** — `NPM_PUBLISH_TOKEN` missing/wrong, or its user lacks `nurix` write access, or it's a read-only (not Automation) token.
|
|
132
|
-
- **"Cannot publish over previously published version x.y.z"** — a bump ran but publish failed and was re-run without rebasing. `git reset` the bump and let CI re-bump, or `npm version patch && git push --follow-tags && npm publish`.
|
|
133
|
-
- **Publish succeeds but `npx @nurix/foo` is "command not found"** — no `bin` field, or `bin` points outside `files`, or the script is missing its `#!/usr/bin/env node` shebang.
|
|
134
|
-
- **"Git working directory not clean" on `npm version`** — the checkout has untracked/modified files; build only into `.gitignore`d paths (the components repo builds into `dist/`).
|
|
135
|
-
- **Published tarball contains source** — missing `files` in `package.json`. Add `["dist"]` and republish.
|
|
136
|
-
- **Auto-publish fires too often** — no `paths:` filter or one that's too broad. Tighten it to artifact-affecting files, or drop the push trigger for `workflow_dispatch`-only.
|
|
137
|
-
- **Workflow loops forever** — the bump commit lacks `[skip ci]`. Invoke `npm version` with `-m "chore: release v%s [skip ci]"`.
|
|
138
|
-
- **A normal commit silently skips its own push** — the inverse trap. GitHub scans the head commit's **whole message** (subject _and_ body) for skip directives (`[skip ci]`, `[ci skip]`, `[no ci]`, `[skip actions]`, `***NO_CI***`). If any hand-written commit merely _mentions_ the literal token in prose, the entire push is skipped and the publish never fires (symptom: branch moved, paths matched, Actions on — yet zero runs for the SHA). Never write the literal token in a commit message or PR body unless you mean it; to ship a push that carried it by accident, re-trigger via **Run workflow** / `gh workflow run … --ref dev`.
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## 4. Quick checklist for a new `@nurix/*` package
|
|
143
|
-
|
|
144
|
-
- [ ] `package.json`: `name: "@nurix/X"`, `version: "0.1.0"`, `private: false`, `publishConfig.access: "restricted"`, `files: [...]`
|
|
145
|
-
- [ ] Build step wired into `prepack` (or `prepublishOnly`)
|
|
146
|
-
- [ ] Name follows §1; if it's a workspace member, boundaries follow §2
|
|
147
|
-
- [ ] `.github/workflows/publish.yml` from §3.3
|
|
148
|
-
- [ ] `NPM_PUBLISH_TOKEN` secret added to the repo
|
|
149
|
-
- [ ] First publish from local (`npm publish`) to verify the pipeline, then a no-op commit to verify CI
|
|
150
|
-
- [ ] README notes the `@nurix` scope so consumers know they need org access
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# React Native + Expo
|
|
2
|
-
|
|
3
|
-
How mobile apps are built. Distilled from a hand-crafted reference app — the gold standard for *style*. This rule is **scale-aware**: a few-screen app uses the small-app form, a feature/auth-heavy app grows into the full form. Where a section offers both, the trigger to graduate is stated.
|
|
4
|
-
|
|
5
|
-
## 1. Stack & packages
|
|
6
|
-
|
|
7
|
-
- **Expo SDK** managed workflow, **config-as-code** (`app.config.ts`, never `app.json`), **expo-router** for navigation, **TypeScript strict**.
|
|
8
|
-
- New Architecture **on** by default; turn it **off** only for a legacy native module that breaks under it — document *why* at the top of `app.config.ts`.
|
|
9
|
-
- `react` / `react-dom` versions must match. Run `npx expo install <pkg>` (not a bare add) for any native/Expo module so the SDK-compatible version is picked.
|
|
10
|
-
|
|
11
|
-
| Need | Use | Not |
|
|
12
|
-
| --- | --- | --- |
|
|
13
|
-
| HTTP | native `fetch` wrapped in a `request<T>()` util | axios |
|
|
14
|
-
| Navigation | `expo-router` (file-based) | bare React Navigation at the root |
|
|
15
|
-
| Storage (sensitive) | `expo-secure-store` | AsyncStorage |
|
|
16
|
-
| Storage (prefs/cache) | `@react-native-async-storage/async-storage` | secure-store |
|
|
17
|
-
| State (non-trivial) | `@reduxjs/toolkit` + `react-redux` | zustand, jotai |
|
|
18
|
-
| Styling | `StyleSheet.create` + token object | nativewind, tamagui, styled |
|
|
19
|
-
| Toasts | `react-native-toast-message` | custom |
|
|
20
|
-
| Haptics | `expo-haptics` behind one dispatcher | scattered call sites |
|
|
21
|
-
|
|
22
|
-
## 2. Structure & naming
|
|
23
|
-
|
|
24
|
-
- **Path alias `@/*` → `./src/*`** (in `tsconfig.json`). Import via `@/...`, never deep-relative (`../../..`). Sibling/child relative imports are fine.
|
|
25
|
-
- **Every file starts with a path + one-line purpose comment.** One-line comment above each function.
|
|
26
|
-
- **No barrel/index re-export files.** Import the source directly: `import { ThemedButton } from "@/components/ThemedButton"`.
|
|
27
|
-
- **Naming:** components & screens `PascalCase.tsx`; hooks `useXxx.ts`; services/utils/reducers/types `camelCase.ts`; route files follow expo-router (`index.tsx`, `[id].tsx`, `_layout.tsx`).
|
|
28
|
-
|
|
29
|
-
**Directory layout** — `app/` is routes only; everything else under `src/`:
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
app/ # expo-router routes (screens) ONLY — see §3
|
|
33
|
-
src/
|
|
34
|
-
components/ # presentational primitives + shared UI (PascalCase)
|
|
35
|
-
hooks/ # useXxx
|
|
36
|
-
services/ # api client, auth storage, background tasks, notifications
|
|
37
|
-
state/ | reducers/+store/ # see §4 (scale decides)
|
|
38
|
-
types/ # shared TS contracts
|
|
39
|
-
utils/ # pure helpers (haptics, deepLink, schemas, ...)
|
|
40
|
-
constants/ # Colors.ts, routes.ts, toast_config.tsx
|
|
41
|
-
navigation/ # custom navigators / BackButton (only if you outgrow Stack/Tabs)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
> **Lean form:** for a handful of screens, group by domain instead of by type — e.g. `src/{data,player,state,ui}` — keeping `app/` routes-only. Graduate to the by-type layout above once you have >~3 cross-cutting concerns (auth + tabs + notifications + ...).
|
|
45
|
-
|
|
46
|
-
## 3. Expo Router & navigation
|
|
47
|
-
|
|
48
|
-
- **Routes live in `app/`** and contain screen logic; **presentational components live in `src/components/`**. A route file may hold its own data/business logic — strict container/presentational split is not required, but pure UI primitives stay pure.
|
|
49
|
-
- `experiments.typedRoutes: true`. Navigate with `router.push()` / `router.replace()` (use `replace` for auth/redirect transitions); reserve `<Link>` for static in-content links. Read params with `useLocalSearchParams()`.
|
|
50
|
-
- **Route groups** `(auth)` / `(tabs)` to partition without affecting the URL. `index.tsx` may be pure redirect logic, not a screen.
|
|
51
|
-
- **Root `_layout.tsx` is the provider tree**, composed outside-in in this order (include only what the app needs):
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
ErrorBoundary → Redux Provider → HydrationGate → GestureHandlerRootView
|
|
55
|
-
→ SafeAreaProvider → StatusBar → <Stack/Tabs> → Toast
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
A lean app collapses this to `StatusBar → Stack` plus a one-time `useEffect` bootstrap.
|
|
59
|
-
- Header styling via `screenOptions` on the `Stack`; per-screen title via `<Stack.Screen options={{ title }}>`.
|
|
60
|
-
- For a bottom-tab app needing a custom header/drawer, `createBottomTabNavigator` inside a `(tabs)/_layout.tsx` is acceptable over expo-router `Tabs` when you need fine layout control.
|
|
61
|
-
|
|
62
|
-
## 4. State
|
|
63
|
-
|
|
64
|
-
**Scale rule:**
|
|
65
|
-
|
|
66
|
-
- **Trivial** (no auth, few screens) — **plain TS modules** under `src/state/`: a module owns one concern, exposes async get/set functions, persists to AsyncStorage with swallowed try/catch, and may cache in a module-level variable.
|
|
67
|
-
- **Non-trivial** (auth, preloaded config, cross-screen data) — **Redux Toolkit**:
|
|
68
|
-
- Slices in `src/reducers/`, store in `src/store/`. Combine slices in one root reducer.
|
|
69
|
-
- **Persist with listener middleware** (debounced ~300ms), **not** `redux-persist`.
|
|
70
|
-
- **Dual storage:** sensitive state (tokens) → `expo-secure-store`; UI prefs → AsyncStorage.
|
|
71
|
-
- **Hydrate before first render:** async hydrate thunks dispatched by a `HydrationGate` that shows a spinner until rehydrated — prevents a flash of unauthenticated UI.
|
|
72
|
-
- Store absolute expiries (`accessExpiresAt`), computed at login from the `expiresIn` the server returns.
|
|
73
|
-
|
|
74
|
-
## 5. Data fetching / networking
|
|
75
|
-
|
|
76
|
-
- **Native `fetch`**, wrapped once: a `request<T>(method, endpoint, body?, opts?)` util in `src/services/api.ts`, and a `useRequest<T>()` hook returning `{ loading, response, error, makeRequest }`.
|
|
77
|
-
- Base URL and auth token are read **at call time** (from Redux state or the settings module), not baked into a client instance. Inject `Authorization: Bearer <token>` only when `authorized: true`.
|
|
78
|
-
- Per-call `onSuccess` / `onFail` callbacks; no global interceptor. Surface failures as toasts or inline error UI.
|
|
79
|
-
- **Every fetched surface renders three states: loading, empty, error** (with a Retry affordance). Caching is in-memory at the data-module level for a lean app; Redux slices serve as the cache for the full app.
|
|
80
|
-
- Types: a `RequestMethod` union and a generic `ApiResponse<T>`; endpoint-specific call wrappers live next to their screen (`app/<feature>/api.ts`).
|
|
81
|
-
|
|
82
|
-
## 6. Styling & theming
|
|
83
|
-
|
|
84
|
-
- `StyleSheet.create` for static styles; inline only for dynamic (theme) values. **No** utility-CSS or styled-components libs.
|
|
85
|
-
- **Color tokens in one place** (`src/constants/Colors.ts`) — components reference tokens, never raw hex.
|
|
86
|
-
- **Theming is a hook, not a Context provider.** `useTheme()` returns `{ colors, colorScheme, isDark }`, resolving **user preference → device `useColorScheme()` → light fallback**. The preference lives in state (e.g. `customize.theme`: `"light" | "dark" | "system"`), so theme survives restart and there's no provider to thread.
|
|
87
|
-
|
|
88
|
-
> **Lean form:** a flat single-mode token object is fine until dark mode is a requirement; adopt the light/dark `Colors` + `useTheme()` shape when it is.
|
|
89
|
-
|
|
90
|
-
## 7. Native modules & permissions
|
|
91
|
-
|
|
92
|
-
- **Wrap each `expo-*` module behind a typed helper** — never sprinkle raw calls. Canonical example: one `triggerHaptic(type)` dispatcher with named exports (`triggerSuccessHaptic`, `triggerTabChangeHaptic`, ...) over `expo-haptics`.
|
|
93
|
-
- **Platform-specific code via filename suffixes** — `Foo.native.tsx` / `Foo.web.tsx` / `Foo.tsx` — preferred over imperative `Platform.select()`. Use `Platform.OS` checks for small inline branches only.
|
|
94
|
-
- Declare Android/iOS permissions and usage strings in `app.config.ts`. Request at point-of-use with a clear pre-prompt where the platform allows.
|
|
95
|
-
- Background work (location, audio) runs through a registered task (`expo-task-manager` / a playback service) so it survives the UI being unmounted — register it in the **app entry** before the router mounts.
|
|
96
|
-
|
|
97
|
-
## 8. Reusable primitives
|
|
98
|
-
|
|
99
|
-
- **`Themed{View,Text,Button}`** as the base layer: extend the native props and add `lightColor?` / `darkColor?`; `ThemedText` takes `type?: "default" | "title" | "subtitle" | "link"`; `ThemedButton` takes `variant?: "contained" | "outlined" | "text"`, `size?`, `fullWidth?`.
|
|
100
|
-
- **`EdgeToEdgeScreen` / `EdgeToEdgeTabScreen` / `EdgeToEdgeTabBar`** own safe-area inset handling so screens never compute insets inline.
|
|
101
|
-
- Loading/feedback primitives (`DotLoader`, `CircularProgress`, `PullToRefreshHint`) are shared, not re-implemented per screen.
|
|
102
|
-
|
|
103
|
-
## 9. TypeScript
|
|
104
|
-
|
|
105
|
-
- **`interface`** for external contracts (API shapes, component props); **`type`** for unions and aliases (`type RequestMethod = "get" | ...`).
|
|
106
|
-
- Shared domain types in `src/types/`; component prop types extend native prop types (`TextProps & { ... }`).
|
|
107
|
-
- **Never weaken `strict` or `noUncheckedIndexedAccess`.** A framework base (`expo/tsconfig.base`) may be extended but not relaxed. Generics for `request<T>` / `useRequest<T>`; no implicit `any` on external data. (See the `typescript` rule for the workspace-wide policy.)
|
|
108
|
-
|
|
109
|
-
## 10. Config & environment
|
|
110
|
-
|
|
111
|
-
- `app.config.ts` (TS) is the single source: scheme, deep-link intent filters, permissions, plugins, `userInterfaceStyle: "automatic"`, `experiments.typedRoutes: true`, splash/icon.
|
|
112
|
-
- Client-readable env via **`EXPO_PUBLIC_*`** only, read through `expo-constants` or `process.env`. **Never commit `.env`**; ship `.env.example`. Secrets/keystore creds stay out of git.
|
|
113
|
-
- Build profiles in `eas.json` (`development` / `preview` / `production`). Local config plugins (`./plugins/withXxx.js`) for native edits the SDK doesn't expose (e.g. Android Auto).
|
|
114
|
-
|
|
115
|
-
## 11. Errors, loading & UX
|
|
116
|
-
|
|
117
|
-
- **`ErrorBoundary` class component wraps the whole app.** On crash it logs, clears *session* state but **preserves user preferences**, and shows an apology + recover affordance.
|
|
118
|
-
- **Toasts** via `react-native-toast-message` with a custom `toast_config.tsx` (success/error/warning variants, `topOffset` below the header). Fire inline: `Toast.show({ type, text1, text2 })`.
|
|
119
|
-
- Gate first paint: hide the splash only after fonts load + state hydrates (`expo-splash-screen`).
|
|
120
|
-
- **Lists:** `FlatList` typed with `ListRenderItemInfo<T>`, `ListEmptyComponent` for empty, `onEndReached` + pagination for infinite scroll, `refreshing`/`onRefresh` for pull-to-refresh.
|
|
121
|
-
- Safe-area through the `EdgeToEdge*` wrappers (§8), not ad-hoc insets.
|
|
122
|
-
|
|
123
|
-
## 12. Web target (`expo start --web`)
|
|
124
|
-
|
|
125
|
-
- Web needs `react-dom`, `react-native-web`, `@expo/metro-runtime`. Add a `"web": "expo start --web"` script.
|
|
126
|
-
- `react-native-track-player` runs on web only with its peer **`shaka-player`** installed; without it the bundle fails to resolve and the page is blank.
|
|
127
|
-
- **Native-only features have no web equivalent** — background audio, lock-screen controls, Android Auto. Guard them behind `Platform.OS` so the web build renders, and treat web as a layout/navigation preview, not a playback/Auto test surface.
|
|
128
|
-
|
|
129
|
-
## 13. Formatting
|
|
130
|
-
|
|
131
|
-
- Prettier: `tabWidth: 4`, `singleQuote: false` (double quotes), `bracketSameLine: true`, `printWidth: 120`, trailing commas.
|
|
132
|
-
- No commented-out code; comments explain *why*, not *what*.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Testing
|
|
2
|
-
|
|
3
|
-
Framework choice is never ad hoc and never the user's problem mid-task: detect the stack, use its row. If the repo already has a runner, that runner wins — never introduce a second framework beside it.
|
|
4
|
-
|
|
5
|
-
## Frameworks by stack
|
|
6
|
-
|
|
7
|
-
No alternatives. Just use these.
|
|
8
|
-
|
|
9
|
-
| Stack | Unit / integration | Component | E2E | HTTP mocking | Don't use |
|
|
10
|
-
| --- | --- | --- | --- | --- | --- |
|
|
11
|
-
| Node + Vite / React (web) | vitest | @testing-library/react (+ @testing-library/user-event, happy-dom) | @playwright/test | msw | jest, mocha, jasmine, cypress, enzyme |
|
|
12
|
-
| Express APIs | vitest | — | supertest against the app instance | msw (outbound calls) | jest, chai-http |
|
|
13
|
-
| React Native / Expo | jest via the jest-expo preset | @testing-library/react-native | maestro | msw | detox, enzyme |
|
|
14
|
-
| Cloudflare Workers | vitest + @cloudflare/vitest-pool-workers | — | @playwright/test against `wrangler dev` | msw | jest |
|
|
15
|
-
| Rust | `cargo test` (built-in) | — | — | — | — |
|
|
16
|
-
|
|
17
|
-
All npm packages at `latest`. Fake data comes from `@ngneat/falso` (the repo standard — never faker/chance).
|
|
18
|
-
|
|
19
|
-
## Conventions
|
|
20
|
-
|
|
21
|
-
- **Suite, not scripts.** Tests live in the canonical test tree — `tests/` mirroring `src/`, or the convention the repo already uses (detect, don't invent) — named `<subject>.test.ts`. Shared fixtures, factories, and helpers live in `tests/helpers/` (or the suite's existing shared location). The harness `CLAUDE.md` Tests guardrail carries the suite-first policy (what gets tests, discipline); this rule owns the technology and placement choices.
|
|
22
|
-
- **Wire the canonical command.** `"test"` in package.json runs the whole unit suite (`vitest run`); e2e runs under `"test:e2e"`. A repo where `pnpm test`/`npm test` doesn't run the suite is misconfigured.
|
|
23
|
-
- **Unit and E2E trees stay separate.** Vitest/Jest suites in `tests/`; Playwright/Maestro suites in `e2e/` with their own config — never mixed into the unit runner's glob.
|
|
24
|
-
- **No test doubles for what you own.** Prefer real instances — in-memory SQLite for drizzle, the actual Express app via supertest — over mocks; mock only true externals (network via msw, time via the runner's fake timers).
|
|
25
|
-
- **Determinism.** No real network, no real time, no shared mutable state between tests; every test must pass alone and in any order.
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# TypeScript
|
|
2
|
-
|
|
3
|
-
TypeScript conventions. Compiles under `strict: true` and `noUncheckedIndexedAccess: true`.
|
|
4
|
-
|
|
5
|
-
## Type Safety
|
|
6
|
-
|
|
7
|
-
- Never `any` to silence the checker. Unknown value → type `unknown`, then narrow (type guard / `zod` parse).
|
|
8
|
-
- Never weaken strictness. No `// @ts-nocheck`; `// @ts-expect-error` only with a same-line reason.
|
|
9
|
-
- Infer locals & return types; annotate at boundaries (exports, props, contracts).
|
|
10
|
-
|
|
11
|
-
## Generics
|
|
12
|
-
|
|
13
|
-
Shared libraries (`@nurix/components`, etc.) are generic on purpose — that's what types `cell(row)`, checks `slug` against `keyof TData`, and ties `data`/`columns` together. Keep the safety; never widen to `any` to dodge it. State the row type **once per file**, then let inference carry it:
|
|
14
|
-
|
|
15
|
-
- **Bind a repeated factory once** (preferred): `const col = defineColumn<BriefRow>;` → `col({...})`. No `<T>` per call; `row` stays typed. Canonical: `modules/briefs/BriefsListPage.tsx`.
|
|
16
|
-
- **Infer JSX type args from props:** `<Table data={rows} columns={columns} />`, not `<Table<BriefRow> …>`.
|
|
17
|
-
- **Anchor with a collection annotation** when nothing else can: `const columns: ColumnDefinition<BriefRow>[] = [...]`.
|
|
18
|
-
|
|
19
|
-
Don't repeat `fn<Type>(…)` / `<Comp<Type>>` at every call site — cosmetic only (same safety, same emitted JS).
|
|
20
|
-
|
|
21
|
-
> Monorepo TS policy (`tsconfig.base.json` + thin per-package `extends`, source-first internal packages, `peerDependencies`, pnpm `catalog:`) lives in [`nustack.md`](nustack.md) §2 — it travels with package boundaries and publishing.
|
|
22
|
-
|
|
23
|
-
## Consuming `@nurix/*` packages
|
|
24
|
-
|
|
25
|
-
`@nurix/*` packages publish to npm with **`restricted`** access — installing needs read rights to the `nurix` org. (Publishing one is [`nustack.md`](nustack.md) §3.)
|
|
26
|
-
|
|
27
|
-
- **Auth** — `npm login` once (the npm user must be a `nurix` member), **or** drop a read token in `.npmrc` (the only CI option; with it you don't need `npm login`):
|
|
28
|
-
```ini
|
|
29
|
-
@nurix:registry=https://registry.npmjs.org/
|
|
30
|
-
//registry.npmjs.org/:_authToken=${NPM_READ_TOKEN}
|
|
31
|
-
```
|
|
32
|
-
A `403` on install means auth failed — check `npm whoami` (local) or the `NPM_READ_TOKEN` secret (CI). The read token only needs `Read` on the `nurix` scope.
|
|
33
|
-
- **Install** — `npm install @nurix/<name>` (or `pnpm add` / `yarn add`).
|
|
34
|
-
- **Import by subpath, never the barrel** — bundlers tree-shake subpaths reliably; root-barrel imports less so. Available subpaths are the package's `exports` keys.
|
|
35
|
-
```tsx
|
|
36
|
-
import { Button } from "@nurix/components/button";
|
|
37
|
-
import { useMobile } from "@nurix/components/hooks/use-mobile";
|
|
38
|
-
```
|
|
39
|
-
- **Styles** — packages that ship CSS need their stylesheet imported **once** at the app entry: `import "@nurix/components/styles.css";`. Unstyled components are almost always a missing styles import. Tailwind v4 doesn't scan `node_modules`, but the shipped stylesheet is precompiled — no `@source` directive needed unless you consume the source via a workspace link.
|
|
40
|
-
- **CLIs** ship a `bin` — run directly: `npx @nurix/etna@latest` (or `npm install -g`).
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: boundary
|
|
3
|
-
description: Boundary diagnostic for implementation plans. Given a plan file, reads the source the plan touches, identifies the cross-cutting contracts it participates in — db schema, wire envelope, auth, events, env/config, … — where editing one side silently breaks the other, and reports whether the planned changes would violate any of them, severity-ranked, with suggested amendments and a pass/block verdict. Read-only; the report lands in chat. Also runs against a diff or named code area. Triggers on "boundary check", "check this plan", "what could break", "will this break anything", "is it safe to rename this column/endpoint/event", "who uses this", "impact analysis", "/boundary".
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Boundary Diagnostic
|
|
7
|
-
|
|
8
|
-
You diagnose whether a plan's changes would break an application boundary.
|
|
9
|
-
|
|
10
|
-
A boundary is a contract between two pieces of code (or code and infra) that share an assumption with **no static link** between them: the column a raw SQL string names, the JSON envelope a client unwraps, the event string a dispatcher parses, the env var a deploy script writes. Changing one side ships green — compiles, lints, tests pass — and breaks the other at runtime. Your job is to find those edges in the plan's path _before_ execution.
|
|
11
|
-
|
|
12
|
-
**The litmus is "would it ship green?"** An edge the compiler or an existing test already guards is not a boundary — a change that fails the build loudly needs no diagnostic. Spend your reading on the edges with no static link.
|
|
13
|
-
|
|
14
|
-
## Contract sources
|
|
15
|
-
|
|
16
|
-
Three layers tell you what to look for; the code tells you what exists.
|
|
17
|
-
|
|
18
|
-
- **The mechanisms** — [`references/mechanisms.md`](references/mechanisms.md) defines the seven failure mechanisms every boundary instantiates (rendezvous-string, serialized-shape, generated-artifact, dual-authority, config-elsewhere, trust-invariant, lifecycle-protocol), each with its trace strategy, universal safe-change pattern, and severity heuristic, plus the two stack-independent domain contracts (entity-model, tenancy). Load it on every check.
|
|
19
|
-
- **The technology packs** — [`references/index.md`](references/index.md) routes to the packs under `references/packs/` by the signals in the touched code. Each pack carries the non-derivable payload for one technology area: exact detect patterns, expensive-to-learn gotchas, and safe-change sequences. Load **only the packs the touch list exhibits**; a technology no pack covers is still in scope — diagnose it from its mechanism.
|
|
20
|
-
- **Repo doctrine** — `.claude/rules/`, `docs/architecture/` (including `adr/`), and any contracts package name the repo's bespoke contracts (a documented response envelope, a schema-parity rule, an entity model). A documented contract is a real side of a boundary: a plan that contradicts one is a finding **even when no code consumer exists yet** — this is how greenfield plans, whose modules have no code to trace, still get checked.
|
|
21
|
-
|
|
22
|
-
The packs are a floor, not a fence: report any contract that passes the ships-green litmus, pack entry or not.
|
|
23
|
-
|
|
24
|
-
## Run
|
|
25
|
-
|
|
26
|
-
**Input:** a plan path (given, or the newest in `docs/history/plans/`) — or a diff / named code area for non-plan work. Everything below is identical either way; a diff's hunks stand in for the plan's steps.
|
|
27
|
-
|
|
28
|
-
1. **Build the touch list.** Parse the plan's Proposed Changes: files created or modified, entities/state added or changed, API changes, anything the author flags as a cross-module contract. Note missing subsections.
|
|
29
|
-
|
|
30
|
-
2. **Identify the contracts.** Read each touched file (and its immediate imports). Against the mechanisms, the routed packs, and the repo doctrine, list the contracts this code participates in — the schema it queries, the envelope it emits, the events it publishes or parses, the auth checks it sits behind, the env it reads.
|
|
31
|
-
|
|
32
|
-
3. **Trace the other side.** A boundary has two sides; the danger lives on the one the plan _doesn't_ mention. For each contract, grep the shared name or shape across the repo — the column name in other SQL strings, the response field in client unwrappers, the event kind in subscribers, the route in fetch calls and scripts, the env key in deploy tooling. Record consumers as `file:line`; record doctrine as the doc path. When the touch list is large (≳10 contracts), fan the tracing out to parallel sub-agents — one per pack area, each with a self-contained prompt naming its contracts and grep targets — and judge the returned evidence yourself; a small diff traces inline.
|
|
33
|
-
|
|
34
|
-
4. **Judge each intersection.** Does the planned change alter a shape a traced consumer depends on, break an invariant the code or doctrine establishes (append-only vocabularies, idempotency keys, fail-closed checks, ordering guarantees), or introduce a new cross-module edge? Severity:
|
|
35
|
-
- **CRITICAL** — can corrupt persisted data, or open an auth/money path.
|
|
36
|
-
- **HIGH** — silent cross-module breakage that would ship green.
|
|
37
|
-
- **MEDIUM** — drift-prone coupling (string-matched, duplicated constants).
|
|
38
|
-
- **LOW** — smell. Loud, immediately-visible runtime breakage caps at HIGH.
|
|
39
|
-
|
|
40
|
-
5. **Report in chat:**
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
# Boundary check — {plan}
|
|
44
|
-
|
|
45
|
-
**Verdict: PASS | PASS WITH AMENDMENTS | BLOCK**
|
|
46
|
-
|
|
47
|
-
Touched files read: {N} · Contracts identified: {N} · Consumers traced: {N}
|
|
48
|
-
|
|
49
|
-
## Violations
|
|
50
|
-
| # | Boundary | Mechanism | Planned change | Other side (file:line or doc) | Severity |
|
|
51
|
-
|
|
52
|
-
## New edges the plan introduces
|
|
53
|
-
| # | Contract | Sides | Hardening to fold into the plan |
|
|
54
|
-
|
|
55
|
-
## Coverage
|
|
56
|
-
| Area touched | Contracts checked | Result |
|
|
57
|
-
|
|
58
|
-
## Suggested plan amendments
|
|
59
|
-
## Missing plan subsections
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**BLOCK** = any CRITICAL. HIGH findings yield **PASS WITH AMENDMENTS** — fold the amendments into the plan before coding. Order amendments sequencing-first: many violations dissolve by reordering steps (expand-then-contract, migrate-before-code) rather than changing what ships — prefer that fix, drawn from the entry's safe-change sequence.
|
|
63
|
-
|
|
64
|
-
## Rules
|
|
65
|
-
|
|
66
|
-
- **Read-only.** You never edit the plan, the code, or any doc. The user folds your amendments into the plan.
|
|
67
|
-
- **Verify in source.** Never flag from plan text alone — read both sides of every finding and cite them: code as `file:line`, doctrine as the doc path.
|
|
68
|
-
- **No other side, no violation.** An intersection with no traced consumer and no doctrine behind it isn't a violation — it's a **new edge**: report it in its own section with the hardening its mechanism prescribes (version the cache key now, make the enum append-only now), so the fragile edge is born safe.
|
|
69
|
-
- **A clean report is valid.** Zero violations means say so — the coverage table is the evidence it was checked, not skipped. Don't pad findings.
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Pack routing index
|
|
2
|
-
|
|
3
|
-
Load when: every boundary check, right after `mechanisms.md`. Match the touch list against the signals below and load **only** the packs whose signals appear in the touched files or the plan's Proposed Changes. Several packs matching is normal (a full-stack feature typically routes 2–4). A technology matching nothing here is still in scope — diagnose it from its mechanism, and check `packs/generic-tail.md` for a thin entry.
|
|
4
|
-
|
|
5
|
-
| Pack | Load when the touch list shows |
|
|
6
|
-
| --- | --- |
|
|
7
|
-
| `packs/persistence.md` | `schema.ts`, `init.sql`, `sqliteTable(`, `drizzle-kit`, `pgTable`, migrations, `*.seed.sql`, seed generators, JSON columns, `pgEnum`, pgvector, Redis `get`/`set` caching |
|
|
8
|
-
| `packs/wire-api.md` | Express routes/middleware (`app.use(`, `router.`), `res.json({ status`, zod schemas on requests, axios clients/interceptors, `cors(`, inbound webhooks, `rateLimit(` |
|
|
9
|
-
| `packs/frontend.md` | `createSlice`/`configureStore`, RTK Query (`tagTypes`), react-router tables/`useParams`, Tailwind CSS (`@theme`, `personality.css`, `globals.css`), `vite.config.*` (alias, env), `@nurix/components`, localforage/redux-persist, date formatting |
|
|
10
|
-
| `packs/auth.md` | `jwtVerify(`/jose, `betterAuth(`/`auth.api.`, session cookies, CSRF, OAuth callbacks/`redirect_uri`, `tenantId`/`organizationId` scoping, password hashing, auth middleware mounts |
|
|
11
|
-
| `packs/cloudflare.md` | `wrangler.jsonc`/`wrangler.toml`, `env.X` bindings, `extends DurableObject`/`[[migrations]]`, KV/R2/D1/Queues, `scheduled(`/crons, `WorkerEntrypoint`, `compatibility_date`, static assets config |
|
|
12
|
-
| `packs/async-messaging.md` | BullMQ (`new Queue(`/`new Worker(`), Redis pub/sub or Streams, WebSockets/socket.io, SSE (`text/event-stream`), channel-name templates, outbound webhook senders |
|
|
13
|
-
| `packs/build-config-runtime.md` | `package.json` (deps/`exports`/scripts/`packageManager`), lockfiles, `.env*`, tsconfig `paths`, Dockerfiles/compose, `.github/workflows/`, pino config, `trust proxy`, ports/healthchecks |
|
|
14
|
-
| `packs/generic-tail.md` | GraphQL, gRPC/protobuf, tRPC, OpenAPI codegen, Kafka/schema registry/CDC, workflow engines, event sourcing, Postgres RLS/pooling/collation, SSR/hydration, module federation, i18n, analytics plans, hosted feature flags, K8s probes/shutdown/limits, service mesh, IaC outputs, SAML, Lambda, IPC |
|
|
15
|
-
|
|
16
|
-
Two files never route — they always load: `mechanisms.md` (the seven failure mechanisms + domain contracts) and this index.
|