From c45921cd83cecb32e7de29e74cc62a631e47ea9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Tue, 28 Apr 2026 04:22:53 +0000 Subject: [PATCH] feat(cli): nerve init installs @uncaged/nerve-skills and generates .cursor/rules/nerve-skills.mdc Fixes #211 --- .../src/__tests__/e2e-validate-init.test.ts | 6 +++- packages/cli/src/commands/init.ts | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/__tests__/e2e-validate-init.test.ts b/packages/cli/src/__tests__/e2e-validate-init.test.ts index fd3e017..bccd77a 100644 --- a/packages/cli/src/__tests__/e2e-validate-init.test.ts +++ b/packages/cli/src/__tests__/e2e-validate-init.test.ts @@ -3,7 +3,7 @@ * No running daemon needed — just temp dirs with HOME manipulation. */ -import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -209,6 +209,10 @@ describe("e2e init", () => { expect(existsSync(join(nerveRoot, "senses", "cpu-usage", "migrations", "0001_init.sql"))).toBe( true, ); + expect(existsSync(join(nerveRoot, ".cursor", "rules", "nerve-skills.mdc"))).toBe(true); + + const pkgJson = readFileSync(join(nerveRoot, "package.json"), "utf8"); + expect(pkgJson).toContain('"@uncaged/nerve-skills": "latest"'); }); it("generated nerve.yaml passes validate", { timeout: 10_000 }, async () => { diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 11471c3..8bef2f8 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -50,6 +50,7 @@ const PACKAGE_JSON = `{ "dependencies": { "@uncaged/nerve-core": "latest", "@uncaged/nerve-daemon": "latest", + "@uncaged/nerve-skills": "latest", "drizzle-orm": "latest" }, "devDependencies": { @@ -68,6 +69,35 @@ nerve.pid node_modules/ `; +const NERVE_SKILLS_MDC = `--- +description: >- + Nerve skills package — where bundled Agent Skills live in this workspace and how to use them +alwaysApply: true +--- + +# Nerve skills (\`@uncaged/nerve-skills\`) + +This workspace lists **@uncaged/nerve-skills** in \`package.json\`. It ships **Agent Skills** (one directory per skill, each with a \`SKILL.md\`) for Nerve development and related tasks. + +## After install + +Run your package manager in this workspace (e.g. \`pnpm install\`, \`npm install\` — whatever \`nerve init\` used). Then skills are on disk at: + +- \`node_modules/@uncaged/nerve-skills//SKILL.md\` + +Example (current catalog): + +- **nerve-dev** — Nerve architecture, CLI, sense/workflow patterns, \`nerve.yaml\`, and conventions: read \`node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`. + +## How to use in an agent + +1. For tasks that match a skill’s **description** (in the \`SKILL.md\` frontmatter), open that \`SKILL.md\` and follow its structure and checklists. +2. Prefer the skill as the **source of truth** for Nerve-specific conventions over generic assumptions. +3. If the catalog grows, new skills appear as new sibling directories under \`node_modules/@uncaged/nerve-skills/\`. + +Do not commit \`node_modules\`; the dependency is the supported way to get and update skills to match \`@uncaged/nerve-skills\` on npm. +`; + const execFileAsync = promisify(execFile); const CPU_SCHEMA_TS = `import { integer, real, sqliteTable, text } from "drizzle-orm/sqlite-core"; @@ -276,6 +306,7 @@ async function runInitWorkspace(force: boolean, skipInstall = false): Promise