docs(skills): add nerve-dev coding agent skill

Comprehensive development guide for AI coding agents covering:
- Architecture and core concepts (sense → signal → workflow)
- CLI commands reference
- Sense and workflow development patterns
- nerve.yaml configuration (inline interval/on)
- Coding conventions and pitfalls

Fixes #187
This commit was merged in pull request #201.
This commit is contained in:
2026-04-27 11:48:29 +00:00
parent ea7e064177
commit 787f864732
2 changed files with 419 additions and 0 deletions
+413
View File
@@ -0,0 +1,413 @@
---
name: nerve-dev
description: >
Nerve development guide for AI coding agents. Covers architecture (sense → signal → workflow),
CLI commands, sense and workflow development patterns, nerve.yaml configuration, coding conventions,
and common pitfalls. Use when developing senses, workflows, or contributing to the Nerve codebase.
license: MIT
compatibility: Requires Node.js 22+, pnpm, TypeScript. Nerve monorepo with packages/core, packages/cli, packages/daemon.
metadata:
author: uncaged
version: "1.0"
---
# Nerve 开发指南
Nerve 是一个轻量级本地感知引擎,为自主 agent 持续观察外部状态、响应变化、编排多步骤工作流。
## 核心数据流
```
External World → Sense → Signal → Workflow → Log
↑ ↑
"观察什么" "做什么"
```
| 概念 | 说明 |
|------|------|
| **Sense** | `compute()` 函数,采样或派生数据。返回 `T \| null` — 非 null 发出 Signal,null 静默。每个 Sense 有自己的 SQLite 数据库 |
| **Signal** | Sense 返回非 null 时发出的通知。纯事实,无意图。通过内存 Signal Bus 分发,不持久化 |
| **Workflow** | 有状态的多步骤执行。包含 **Roles**(有副作用的执行者)和 **Moderator**(纯路由器)。每个实例是一个 **Thread** |
| **Log** | 不可变审计日志。记录执行、状态变更、错误 |
| **Kernel** | 编排核心。持有 Signal Bus、调度器、进程管理、Workflow Manager |
| **Daemon** | `nerve-daemon` — 后台运行的引擎进程 |
## 用户工作区 `~/.uncaged-nerve/`
```
~/.uncaged-nerve/
nerve.yaml # 主配置
package.json # 依赖(@uncaged/nerve-core, drizzle-orm 等)
senses/ # sense 模块
cpu-usage/
index.js # compute 函数
schema.ts # Drizzle ORM schema
migrations/ # SQL 迁移
workflows/ # workflow 模块
alert/
index.ts # WorkflowDefinition
data/ # SQLite 数据库、signal 存储
logs/ # 日志
```
## nerve.yaml 配置
```yaml
senses:
cpu-usage:
group: system # worker 分组(同组 sense 共享一个 worker 进程)
throttle: 5s # 最小计算间隔
timeout: 10s # compute 超时
grace_period: null # 首次错误后的宽限期
retention: 10000 # _signals 表最大行数
interval: 10s # 定时轮询周期
on: [disk-usage] # 响应其他 sense 的 signal 触发计算
disk-usage:
group: system
throttle: 30s
workflows:
alert:
concurrency: 1 # 最大并发 thread 数
overflow: queue # 超出时排队(或 drop)
max_rounds: 10 # workflow 默认最大轮数
api: # HTTP API 配置
bind: "127.0.0.1:9800"
token: null # loopback 不强制 token
```
**调度方式**
- `interval: 10s` — 每 10 秒触发一次 compute
- `on: [other-sense]` — 当 other-sense 发出 signal 时触发
- 两者可组合
## Sense 开发
### Sense Anatomy
一个 sense 由以下文件组成(`nerve create sense <name>` 自动生成):
```
senses/<name>/
index.js # compute() 函数 — 必须
schema.ts # Drizzle ORM 表定义 — 推荐
migrations/ # SQL 迁移文件
0001_init.sql # 建表 SQL — Drizzle Kit 生成
```
命名规范:sense ID 用 kebab-case(如 `cpu-usage`),对应 SQL 表名自动转 snake_case(`cpu_usage`)。
### compute 函数
compute 返回 `null`(静默)或 `{ signal, workflow }` 结构:
```typescript
// senses/cpu-usage/index.js
export async function compute(db, peers, options) {
// db: Drizzle ORM SQLite 实例(读写,当前 sense 专用)
// peers: Record<string, DrizzleDB>(只读,同组其他 sense 的 DB)
// options: { signal: AbortSignal, blobStore: BlobStore }
const usage = getCpuUsage();
// 返回 null → 静默(不发 Signal)
if (usage <= 80) return null;
// 返回 { signal, workflow } → 发 Signal,可选触发 Workflow
return {
signal: { cpu: usage, timestamp: Date.now() },
workflow: null, // 不触发 workflow
};
}
```
**Signal + Workflow 联动**:Signal 和 Workflow 是蕴含关系 — 有 signal 才可能触发 workflow,两者不互斥:
```typescript
export async function compute(db) {
const anomaly = detectAnomaly();
if (!anomaly) return null;
return {
signal: { level: "critical", cpu: anomaly.cpu },
workflow: {
name: "alert",
maxRounds: 5,
prompt: "CPU 持续高负载,需要分析",
dryRun: false,
},
};
// → 先发 Signal,再启动 alert workflow
}
```
**`WorkflowTrigger` 类型**(定义在 `@uncaged/nerve-core`):
```typescript
type WorkflowTrigger = {
name: string; // workflow 名称
maxRounds: number; // 最大轮数(>= 1)
prompt: string; // 传递给 workflow 的 prompt
dryRun: boolean; // 是否 dry-run
};
```
**compute 返回值路由规则**(由 `routeSenseComputeOutput()` 决定):
| 返回值 | 行为 |
|--------|------|
| `null` | 静默,不发 Signal |
| `{ signal: T, workflow: null }` | 发出 **Signal**,不触发 Workflow |
| `{ signal: T, workflow: WorkflowTrigger }` | 先发 **Signal**,再启动 **Workflow** |
| `{ signal: T, workflow: 非法对象 }` | 降级为 signal-only(workflow 被忽略) |
| 裸值(无 `signal` 键) | 兼容模式:整个值作为 signal payload,不触发 workflow |
### Drizzle Schema 与迁移
Schema 使用 Drizzle ORM 定义,迁移 SQL 由 `drizzle-kit generate` 生成:
```typescript
// senses/cpu-usage/schema.ts
import { sqliteTable, integer, real } from "drizzle-orm/sqlite-core";
export const cpuUsage = sqliteTable("cpu_usage", {
id: integer("id").primaryKey({ autoIncrement: true }),
usage: real("usage").notNull(),
ts: integer("ts").notNull(),
});
```
生成迁移:
```bash
npx drizzle-kit generate # 生成 migrations/0001_*.sql
```
迁移文件放在 `senses/<name>/migrations/`,daemon 启动时自动执行。
### 进程隔离
- 同一 `group` 的 sense 共享一个 long-lived worker 进程
- Worker 通过 Node.js IPC(fork + `process.send`)与 kernel 通信
- 每个 sense 有独立的 SQLite 数据库
### `nerve sense` 命令
```bash
nerve sense list # 列出所有 sense 及状态(group、throttle、trigger schedule、last signal)
nerve sense trigger <name> # 手动触发一次 compute
nerve sense query <name> # 查询 sense 的 SQLite 数据(默认查 _signals 表)
nerve sense query <name> "SELECT * FROM cpu_usage ORDER BY ts DESC LIMIT 10"
nerve sense schema <name> # 查看 sense 数据库的 CREATE TABLE 语句
nerve sense schema <name> --json # JSON 格式输出
```
## Workflow 开发
### Workflow Anatomy
一个 workflow 由以下文件组成(`nerve create workflow <name>` 自动生成):
```
workflows/<name>/
index.ts # WorkflowDefinition — import roles,定义 moderator
roles/
analyst/
index.ts # role execute 函数
prompt.md # prompt 模板(可选)
reporter/
index.ts
```
每个 role 是一个独立目录,包含 `index.ts`(execute 函数)和可选的 prompt 模板、配置等资源文件。
### WorkflowDefinition
```typescript
// workflows/alert/index.ts
import type { WorkflowDefinition } from "@uncaged/nerve-core";
import { analyst } from "./roles/analyst/index.js";
import { reporter } from "./roles/reporter/index.js";
export const definition: WorkflowDefinition = {
name: "alert",
roles: { analyst, reporter },
// Moderator: 纯路由函数,决定下一个 role
moderator: (context) => {
if (context.rounds === 0) return "analyst";
if (context.rounds === 1) return "reporter";
return "END";
},
};
```
```typescript
// workflows/alert/roles/analyst/index.ts
export const analyst = {
async execute(start, messages) {
// start: { prompt, maxRounds, dryRun }
// messages: 历史消息数组
const analysis = await callLLM(start.prompt);
return { content: analysis, meta: {} };
},
};
```
Role 可以附带 prompt 模板:
```typescript
// workflows/alert/roles/reporter/index.ts
import { readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const PROMPT = readFileSync(join(__dirname, "prompt.md"), "utf8");
export const reporter = {
async execute(start, messages) {
const lastMessage = messages[messages.length - 1];
await sendNotification(PROMPT.replace("{{content}}", lastMessage.content));
return { content: "已通知", meta: {} };
},
};
```
### 公共工具(`@uncaged/nerve-workflow-utils`)
Role 中可使用以下公共工具:
| 工具 | 用途 |
|------|------|
| `spawnSafe(cmd, args, opts)` | 安全启动子进程,`shell: false`,支持 timeout 和 dry-run,返回 `Result<SpawnResult>` |
| `nerveCommandEnv()` | 返回配置好 pnpm/nerve PATH 的环境变量,用于在 role 中调用 CLI |
| `cursorAgent(prompt, opts)` | 调用 cursor-agent CLI(mode: `"plan"` / `"ask"` / `"default"`),返回 `Result<string>` |
| `llmExtract(opts)` | 调用 OpenAI 兼容 API + Zod schema 提取结构化数据,返回 `Result<T>` |
| `readNerveYaml(dir)` | 安全读取 nerve.yaml(防路径穿越),返回 `Result<string>` |
| `isDryRun(startStep)` | 从 workflow StartStep 提取 dry-run 标志 |
### Workflow 进程模型
- 每个 workflow **类型**有一个 on-demand worker 进程(可复用)
- 每个运行实例是一个 **Thread**,有唯一 `runId`
- 支持崩溃恢复和 resume
### `nerve workflow` & `nerve thread` 命令
```bash
# Workflow 管理
nerve workflow list # 列出 workflow 定义(从 nerve.yaml 读取)
nerve workflow status # 查看运行中的 workflow(从 daemon 实时获取)
nerve workflow trigger <name> # 触发 workflow
nerve workflow trigger <name> --max-rounds 5 --prompt '分析 CPU 异常' --dry-run
# Thread(运行实例)管理
nerve thread list # 列出运行记录(默认只显示 running/queued)
nerve thread list --all # 包括已完成的
nerve thread show <runId> # 查看 role 轮次对话内容
nerve thread inspect <runId> # 查看底层事件详情
nerve thread kill <runId> # 终止运行中的 thread
```
## 其他 CLI 命令
### 初始化与开发
```bash
nerve init # 初始化工作区
nerve init --from <git-url> # 从 git 克隆
nerve create sense <name> # 脚手架 sense 模板
nerve create workflow <name> # 脚手架 workflow 模板
nerve dev # 前台开发模式(热重载)
nerve validate # 校验 nerve.yaml
```
### Daemon 管理
```bash
nerve daemon start # 后台启动
nerve daemon stop # 停止
nerve daemon status # 状态(pid、uptime、senses、workers)
nerve daemon restart # 重启
nerve status # 同 daemon status
nerve stop # 同 daemon stop
nerve logs # 查看日志
nerve logs -f # 实时跟踪日志
```
### Remote(多节点管理)
```bash
nerve remote add <name> <host> --token <secret>
nerve remote list
nerve remote default <name>
nerve --host remote-host:9800 sense list
```
### 存储维护
```bash
nerve store archive # 归档 30 天前的日志
nerve store archive --vacuum # 归档并压缩 SQLite
```
## 编码规范
### 语言与范式
- **Functional-first**:用 `type` + `function`,不用 `class` + `interface`
- **No `this`**、**No inheritance**
- **No optional properties**:用 `T | null` 替代 `?:`
- **`Result<T, E>` 类型** 处理预期错误,`throw` 仅用于不可恢复的 bug
- **Always `async/await`**,不用 `.then()`
- **No dynamic import**(除 sense-runtime.ts 和 workflow-worker.ts)
### 命名
| 类型 | 风格 | 示例 |
|------|------|------|
| 文件 | kebab-case | `signal-bus.ts` |
| 类型 | PascalCase | `SignalBus` |
| 函数/变量 | camelCase | `createSignalBus` |
| 常量 | UPPER_SNAKE | `MAX_RETRY_COUNT` |
### 工具链
```bash
pnpm run check # biome check(lint + format)
pnpm run format # biome format --write
pnpm run build # 全量构建
pnpm test # 运行测试
```
### Commit 规范
```
<type>(<scope>): <description>
type: feat | fix | refactor | docs | chore | test
scope: core | cli | daemon | ...
```
## 开发流程
1. **修改代码** → 在对应 package 目录下开发
2. **`pnpm run build`** → 确保编译通过
3. **`pnpm test`** → 确保测试通过
4. **`pnpm run check`** → 确保 biome lint/format 通过
5. **`nerve dev`** → 在工作区前台运行验证
6. **`nerve sense list`** / **`nerve sense query <name>`** → 检查 sense 输出
7. **`nerve logs -f`** → 实时查看日志排查问题
## Pitfalls
- Sense `compute()` 返回 `undefined` 和返回 `null` 不同 — `undefined` 会被当作非 null 值尝试持久化
- 同 group 的 sense 共享 worker 进程,一个 sense 的阻塞会影响同组其他 sense
- Workflow 的 `moderator` 必须是纯函数(无副作用),所有副作用放在 `roles`
- Log 不能触发后续动作 — 这是防止反馈循环的设计
- `nerve.yaml` 中所有 duration 字段支持 `5s``10m` 等人类可读格式
- Workflow directive 格式是 `{ workflow: { name, maxRounds, prompt, dryRun } }`,signal 和 workflow 是蕴含关系(先 signal 后 workflow)
+6
View File
@@ -0,0 +1,6 @@
{
"name": "@uncaged/nerve-skills",
"version": "0.1.0",
"private": true,
"description": "Coding agent skills for Nerve development"
}