From ceb5998fa3d8441aac455beb5e4f30a90dcab69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=A2=A8?= Date: Thu, 23 Apr 2026 12:01:03 +0000 Subject: [PATCH 1/3] docs: add cursor rules and CLAUDE.md from coding conventions - .cursor/rules/global.mdc: coding conventions as Cursor agent rules - CLAUDE.md: same conventions for Claude Code / Hermes agents - Content derived from docs/coding-conventions.md - Includes no-dynamic-import rule in CLAUDE.md for completeness --- .cursor/rules/global.mdc | 159 +++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 150 ++++++++++++++++++++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 .cursor/rules/global.mdc create mode 100644 CLAUDE.md diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc new file mode 100644 index 0000000..0f44e13 --- /dev/null +++ b/.cursor/rules/global.mdc @@ -0,0 +1,159 @@ +--- +description: Nerve project coding conventions — style, patterns, and toolchain +globs: packages/*/src/**/*.ts +alwaysApply: true +--- + +# Nerve Coding Conventions + +## Language & Paradigm + +### Functional-first + +Use `function` + `type`, not `class` + `interface`. + +```typescript +// ✅ Good +type Signal = { + senseId: string; + value: unknown; + ts: number; +}; + +function createSignal(senseId: string, value: unknown): Signal { + return { senseId, value, ts: Date.now() }; +} + +// ❌ Bad — no class, no interface +class Signal implements ISignal { ... } +``` + +### Rules + +| Rule | Description | +|------|-------------| +| `type` over `interface` | All type definitions use `type` | +| `function` over `class` | Pure functions + closures, no class | +| No `this` | Functions must not depend on `this` context | +| No inheritance | No `extends`, `implements`, `abstract` | +| Composition over inheritance | Use function composition | +| Immutability first | Use `Readonly`, `as const`, avoid mutation | +| No optional properties | Use `T \| null` instead of `?:` — see below | + +### Exceptions + +Classes are allowed when: +- Required by a third-party library (e.g. Drizzle's `sqliteTable`) +- Error subclasses (`class NerveError extends Error`) + +### No Optional Properties + +Never use `?:`. All nullable fields must be explicit `T | null`. + +```typescript +// ✅ Good +type SenseConfig = { + group: string; + throttle: string | null; + timeout: string | null; +}; + +// ❌ Bad +type SenseConfig = { + group: string; + throttle?: string; + timeout?: string; +}; +``` + +For mutually exclusive fields, use discriminated unions: + +```typescript +// ✅ Good +type ReflexConfig = + | { kind: "sense"; sense: string; interval: string | null; on: string[] | null } + | { kind: "workflow"; workflow: string; on: string[] | null }; +``` + +## Modules & Exports + +- Always named exports, never default exports +- One module = one responsibility, filename = purpose + +```typescript +// ✅ Named exports only +export function startEngine(config: EngineConfig): Engine { ... } +export type EngineConfig = { ... }; + +// ❌ No default exports +export default function startEngine() { ... } +``` + +## Naming + +| Type | Style | Example | +|------|-------|---------| +| Files | kebab-case | `signal-bus.ts` | +| Types | PascalCase | `SignalBus` | +| Functions/variables | camelCase | `createSignalBus` | +| Constants | UPPER_SNAKE | `MAX_RETRY_COUNT` | +| Generics | Single letter or descriptive | `T`, `TValue` | + +## Error Handling + +- Use `Result` type for expected failures +- `throw` only for unrecoverable bugs (programmer errors) +- No try-catch for flow control + +```typescript +type Result = { ok: true; value: T } | { ok: false; error: E }; + +function parseSenseConfig(raw: unknown): Result { ... } +``` + +## Async + +- Always `async/await`, never `.then()` chains + +## Toolchain + +| Tool | Purpose | +|------|---------| +| **pnpm** | Package manager | +| **TypeScript** | Type checking (strict mode) | +| **Biome** | Lint + format (replaces ESLint + Prettier) | +| **tsup** | Bundling | + +### Commands + +```bash +pnpm run check # biome check (lint + format) +pnpm run format # biome format --write +pnpm run build # full build +pnpm test # run tests +``` + +## Monorepo Structure + +``` +nerve/ + packages/ + core/ # @nerve/core — shared types and utils + cli/ # @nerve/cli — CLI entry point + daemon/ # @nerve/daemon — engine runtime + docs/ # RFCs, conventions + biome.json # root Biome config + tsconfig.json # root TypeScript config (composite project references) +``` + +- `core` is the shared layer; `cli` and `daemon` both depend on it +- `cli` and `daemon` must NOT depend on each other + +## Commit Convention + +``` +(): + +type: feat | fix | refactor | docs | chore | test +scope: core | cli | daemon | rfc-001 | ... +``` diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..1d709ad --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,150 @@ +# Nerve Coding Conventions + +## Language & Paradigm + +### Functional-first + +Use `function` + `type`, not `class` + `interface`. + +```typescript +// ✅ Good +type Signal = { + senseId: string; + value: unknown; + ts: number; +}; + +function createSignal(senseId: string, value: unknown): Signal { + return { senseId, value, ts: Date.now() }; +} + +// ❌ Bad — no class, no interface +class Signal implements ISignal { ... } +``` + +### Rules + +| Rule | Description | +|------|-------------| +| `type` over `interface` | All type definitions use `type` | +| `function` over `class` | Pure functions + closures, no class | +| No `this` | Functions must not depend on `this` context | +| No inheritance | No `extends`, `implements`, `abstract` | +| Composition over inheritance | Use function composition | +| Immutability first | Use `Readonly`, `as const`, avoid mutation | +| No optional properties | Use `T \| null` instead of `?:` — see below | + +### Exceptions + +Classes are allowed when: +- Required by a third-party library (e.g. Drizzle's `sqliteTable`) +- Error subclasses (`class NerveError extends Error`) + +### No Optional Properties + +Never use `?:`. All nullable fields must be explicit `T | null`. + +```typescript +// ✅ Good +type SenseConfig = { + group: string; + throttle: string | null; + timeout: string | null; +}; + +// ❌ Bad +type SenseConfig = { + group: string; + throttle?: string; + timeout?: string; +}; +``` + +For mutually exclusive fields, use discriminated unions: + +```typescript +// ✅ Good +type ReflexConfig = + | { kind: "sense"; sense: string; interval: string | null; on: string[] | null } + | { kind: "workflow"; workflow: string; on: string[] | null }; +``` + +## Modules & Exports + +- Always named exports, never default exports +- One module = one responsibility, filename = purpose + +## Naming + +| Type | Style | Example | +|------|-------|---------| +| Files | kebab-case | `signal-bus.ts` | +| Types | PascalCase | `SignalBus` | +| Functions/variables | camelCase | `createSignalBus` | +| Constants | UPPER_SNAKE | `MAX_RETRY_COUNT` | +| Generics | Single letter or descriptive | `T`, `TValue` | + +## Error Handling + +- Use `Result` type for expected failures +- `throw` only for unrecoverable bugs (programmer errors) +- No try-catch for flow control + +```typescript +type Result = { ok: true; value: T } | { ok: false; error: E }; +``` + +## Async + +- Always `async/await`, never `.then()` chains + +## No Dynamic Import + +Do NOT use `await import()` in production code. Always use static top-level `import`. + +Exceptions (must include a comment): +1. `sense-runtime.ts` — user module paths known only at runtime +2. `workflow-worker.ts` — user module paths known only at runtime + +Test files (`__tests__/**`) are exempt. + +## Toolchain + +| Tool | Purpose | +|------|---------| +| **pnpm** | Package manager | +| **TypeScript** | Type checking (strict mode) | +| **Biome** | Lint + format (replaces ESLint + Prettier) | +| **tsup** | Bundling | + +### Commands + +```bash +pnpm run check # biome check (lint + format) +pnpm run format # biome format --write +pnpm run build # full build +pnpm test # run tests +``` + +## Monorepo Structure + +``` +nerve/ + packages/ + core/ # @nerve/core — shared types and utils + cli/ # @nerve/cli — CLI entry point + daemon/ # @nerve/daemon — engine runtime + docs/ # RFCs, conventions +``` + +- `core` is the shared layer; `cli` and `daemon` both depend on it +- `cli` and `daemon` must NOT depend on each other + +## Commit Convention + +``` +(): + +type: feat | fix | refactor | docs | chore | test +scope: core | cli | daemon | rfc-001 | ... +``` -- 2.43.0 From 554a79775c410f3b2fd58cd2a46d43f0902b7bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=A2=A8?= Date: Thu, 23 Apr 2026 12:03:15 +0000 Subject: [PATCH 2/3] docs: add .github/copilot-instructions.md for GitHub Copilot --- .github/copilot-instructions.md | 150 ++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..1d709ad --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,150 @@ +# Nerve Coding Conventions + +## Language & Paradigm + +### Functional-first + +Use `function` + `type`, not `class` + `interface`. + +```typescript +// ✅ Good +type Signal = { + senseId: string; + value: unknown; + ts: number; +}; + +function createSignal(senseId: string, value: unknown): Signal { + return { senseId, value, ts: Date.now() }; +} + +// ❌ Bad — no class, no interface +class Signal implements ISignal { ... } +``` + +### Rules + +| Rule | Description | +|------|-------------| +| `type` over `interface` | All type definitions use `type` | +| `function` over `class` | Pure functions + closures, no class | +| No `this` | Functions must not depend on `this` context | +| No inheritance | No `extends`, `implements`, `abstract` | +| Composition over inheritance | Use function composition | +| Immutability first | Use `Readonly`, `as const`, avoid mutation | +| No optional properties | Use `T \| null` instead of `?:` — see below | + +### Exceptions + +Classes are allowed when: +- Required by a third-party library (e.g. Drizzle's `sqliteTable`) +- Error subclasses (`class NerveError extends Error`) + +### No Optional Properties + +Never use `?:`. All nullable fields must be explicit `T | null`. + +```typescript +// ✅ Good +type SenseConfig = { + group: string; + throttle: string | null; + timeout: string | null; +}; + +// ❌ Bad +type SenseConfig = { + group: string; + throttle?: string; + timeout?: string; +}; +``` + +For mutually exclusive fields, use discriminated unions: + +```typescript +// ✅ Good +type ReflexConfig = + | { kind: "sense"; sense: string; interval: string | null; on: string[] | null } + | { kind: "workflow"; workflow: string; on: string[] | null }; +``` + +## Modules & Exports + +- Always named exports, never default exports +- One module = one responsibility, filename = purpose + +## Naming + +| Type | Style | Example | +|------|-------|---------| +| Files | kebab-case | `signal-bus.ts` | +| Types | PascalCase | `SignalBus` | +| Functions/variables | camelCase | `createSignalBus` | +| Constants | UPPER_SNAKE | `MAX_RETRY_COUNT` | +| Generics | Single letter or descriptive | `T`, `TValue` | + +## Error Handling + +- Use `Result` type for expected failures +- `throw` only for unrecoverable bugs (programmer errors) +- No try-catch for flow control + +```typescript +type Result = { ok: true; value: T } | { ok: false; error: E }; +``` + +## Async + +- Always `async/await`, never `.then()` chains + +## No Dynamic Import + +Do NOT use `await import()` in production code. Always use static top-level `import`. + +Exceptions (must include a comment): +1. `sense-runtime.ts` — user module paths known only at runtime +2. `workflow-worker.ts` — user module paths known only at runtime + +Test files (`__tests__/**`) are exempt. + +## Toolchain + +| Tool | Purpose | +|------|---------| +| **pnpm** | Package manager | +| **TypeScript** | Type checking (strict mode) | +| **Biome** | Lint + format (replaces ESLint + Prettier) | +| **tsup** | Bundling | + +### Commands + +```bash +pnpm run check # biome check (lint + format) +pnpm run format # biome format --write +pnpm run build # full build +pnpm test # run tests +``` + +## Monorepo Structure + +``` +nerve/ + packages/ + core/ # @nerve/core — shared types and utils + cli/ # @nerve/cli — CLI entry point + daemon/ # @nerve/daemon — engine runtime + docs/ # RFCs, conventions +``` + +- `core` is the shared layer; `cli` and `daemon` both depend on it +- `cli` and `daemon` must NOT depend on each other + +## Commit Convention + +``` +(): + +type: feat | fix | refactor | docs | chore | test +scope: core | cli | daemon | rfc-001 | ... +``` -- 2.43.0 From 24a8ec927dea69129ea661a34322ccaa0945356a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=A2=A8?= Date: Thu, 23 Apr 2026 12:05:31 +0000 Subject: [PATCH 3/3] docs: add core concepts (sense, signal, reflex, workflow) to agent rules --- .cursor/rules/global.mdc | 30 ++++++++++++++++++++++++++++++ .github/copilot-instructions.md | 30 ++++++++++++++++++++++++++++++ CLAUDE.md | 30 ++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc index 0f44e13..c9ff589 100644 --- a/.cursor/rules/global.mdc +++ b/.cursor/rules/global.mdc @@ -6,6 +6,36 @@ alwaysApply: true # Nerve Coding Conventions +## Core Concepts + +``` +External World → Sense → Signal → Reflex → Workflow → Log + ↑ ↑ + "what to observe" "what to do" +``` + +**Nerve** is a lightweight observation engine daemon for autonomous agents. It continuously observes external state, reacts to changes via declarative rules, and orchestrates multi-step workflows. + +### Key Terms + +| Concept | What it is | +|---------|-----------| +| **Sense** | A `compute()` function that samples or derives data. Returns `T \| null` — non-null emits a Signal, null is silent. Each Sense has its own SQLite database. | +| **Signal** | A notification emitted when a Sense returns non-null. Pure fact, no intent. Distributed via an in-memory Signal Bus. Not persisted. | +| **Reflex** | A declarative trigger (YAML) connecting Senses to actions. Trigger types: `interval` (periodic), `on` (react to Signals). Action types: trigger a Sense, or start a Workflow. | +| **Workflow** | A stateful multi-step execution. Contains **Roles** (actors with side effects) and a **Moderator** (pure router). Each instance is a **Thread** with a unique `runId`. | +| **Log** | Immutable audit trail. Records executions, state transitions, errors. **Cannot trigger Reflexes** — prevents feedback loops. | +| **Engine** | The kernel orchestrating everything. Holds Signal Bus, Reflex Scheduler, Process Manager, Workflow Manager. Never loads user code directly — all user code runs in isolated Workers. | +| **Daemon** | The `nerve-daemon` package — engine runtime. Runs as a background process. | + +### Architecture Rules + +- **Three orthogonal extension points**: Sense (what to compute), Reflex (when to compute), Workflow (what to do) +- **Process isolation**: One worker per Sense group (long-lived), one per Workflow type (on-demand). Workers never talk to each other. +- **Causality is one-directional**: External world → Sense → Signal → Reflex → Action + Log. Logs are the end of the chain. + + + ## Language & Paradigm ### Functional-first diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1d709ad..5972be8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,5 +1,35 @@ # Nerve Coding Conventions +## Core Concepts + +``` +External World → Sense → Signal → Reflex → Workflow → Log + ↑ ↑ + "what to observe" "what to do" +``` + +**Nerve** is a lightweight observation engine daemon for autonomous agents. It continuously observes external state, reacts to changes via declarative rules, and orchestrates multi-step workflows. + +### Key Terms + +| Concept | What it is | +|---------|-----------| +| **Sense** | A `compute()` function that samples or derives data. Returns `T \| null` — non-null emits a Signal, null is silent. Each Sense has its own SQLite database. | +| **Signal** | A notification emitted when a Sense returns non-null. Pure fact, no intent. Distributed via an in-memory Signal Bus. Not persisted. | +| **Reflex** | A declarative trigger (YAML) connecting Senses to actions. Trigger types: `interval` (periodic), `on` (react to Signals). Action types: trigger a Sense, or start a Workflow. | +| **Workflow** | A stateful multi-step execution. Contains **Roles** (actors with side effects) and a **Moderator** (pure router). Each instance is a **Thread** with a unique `runId`. | +| **Log** | Immutable audit trail. Records executions, state transitions, errors. **Cannot trigger Reflexes** — prevents feedback loops. | +| **Engine** | The kernel orchestrating everything. Holds Signal Bus, Reflex Scheduler, Process Manager, Workflow Manager. Never loads user code directly — all user code runs in isolated Workers. | +| **Daemon** | The `nerve-daemon` package — engine runtime. Runs as a background process. | + +### Architecture Rules + +- **Three orthogonal extension points**: Sense (what to compute), Reflex (when to compute), Workflow (what to do) +- **Process isolation**: One worker per Sense group (long-lived), one per Workflow type (on-demand). Workers never talk to each other. +- **Causality is one-directional**: External world → Sense → Signal → Reflex → Action + Log. Logs are the end of the chain. + + + ## Language & Paradigm ### Functional-first diff --git a/CLAUDE.md b/CLAUDE.md index 1d709ad..5972be8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,35 @@ # Nerve Coding Conventions +## Core Concepts + +``` +External World → Sense → Signal → Reflex → Workflow → Log + ↑ ↑ + "what to observe" "what to do" +``` + +**Nerve** is a lightweight observation engine daemon for autonomous agents. It continuously observes external state, reacts to changes via declarative rules, and orchestrates multi-step workflows. + +### Key Terms + +| Concept | What it is | +|---------|-----------| +| **Sense** | A `compute()` function that samples or derives data. Returns `T \| null` — non-null emits a Signal, null is silent. Each Sense has its own SQLite database. | +| **Signal** | A notification emitted when a Sense returns non-null. Pure fact, no intent. Distributed via an in-memory Signal Bus. Not persisted. | +| **Reflex** | A declarative trigger (YAML) connecting Senses to actions. Trigger types: `interval` (periodic), `on` (react to Signals). Action types: trigger a Sense, or start a Workflow. | +| **Workflow** | A stateful multi-step execution. Contains **Roles** (actors with side effects) and a **Moderator** (pure router). Each instance is a **Thread** with a unique `runId`. | +| **Log** | Immutable audit trail. Records executions, state transitions, errors. **Cannot trigger Reflexes** — prevents feedback loops. | +| **Engine** | The kernel orchestrating everything. Holds Signal Bus, Reflex Scheduler, Process Manager, Workflow Manager. Never loads user code directly — all user code runs in isolated Workers. | +| **Daemon** | The `nerve-daemon` package — engine runtime. Runs as a background process. | + +### Architecture Rules + +- **Three orthogonal extension points**: Sense (what to compute), Reflex (when to compute), Workflow (what to do) +- **Process isolation**: One worker per Sense group (long-lived), one per Workflow type (on-demand). Workers never talk to each other. +- **Causality is one-directional**: External world → Sense → Signal → Reflex → Action + Log. Logs are the end of the chain. + + + ## Language & Paradigm ### Functional-first -- 2.43.0