refactor: remove redundant context from prompts, delegate to nerve-dev skill

- Remove nerveYaml injection from planner (skill has it)
- Remove sensesDir/nerveRoot from coder and tester (skill has conventions)
- Prompts now just say 'read the skill' instead of inlining knowledge
- BuildSenseGeneratorDeps reduced to { provider, cwd }
- index.ts drops getNerveYaml(), SENSES_DIR, readFileSync
This commit is contained in:
小橘 2026-04-28 04:50:21 +00:00
parent e460d64786
commit 645f0bacf2
8 changed files with 28 additions and 92 deletions

View File

@ -8,23 +8,19 @@ import type { SenseMeta } from "./moderator.js";
export type BuildSenseGeneratorDeps = { export type BuildSenseGeneratorDeps = {
provider: LlmProvider; provider: LlmProvider;
nerveRoot: string; cwd: string;
sensesDir: string;
nerveYaml: string;
}; };
export function buildSenseGenerator({ export function buildSenseGenerator({
provider, provider,
nerveRoot, cwd,
sensesDir,
nerveYaml,
}: BuildSenseGeneratorDeps): WorkflowDefinition<SenseMeta> { }: BuildSenseGeneratorDeps): WorkflowDefinition<SenseMeta> {
return { return {
name: "sense-generator", name: "sense-generator",
roles: { roles: {
planner: buildPlannerRole({ provider, cwd: nerveRoot, nerveYaml }), planner: buildPlannerRole({ provider, cwd }),
coder: buildCoderRole({ provider, cwd: nerveRoot, sensesDir, nerveRoot }), coder: buildCoderRole({ provider, cwd }),
tester: buildTesterRole({ provider, sensesDir, nerveRoot }), tester: buildTesterRole({ provider }),
}, },
moderator, moderator,
}; };

View File

@ -1,4 +1,3 @@
import { readFileSync } from "node:fs";
import { join } from "node:path"; import { join } from "node:path";
import { spawnSafe } from "@uncaged/nerve-workflow-utils"; import { spawnSafe } from "@uncaged/nerve-workflow-utils";
import { buildSenseGenerator } from "./build.js"; import { buildSenseGenerator } from "./build.js";
@ -7,7 +6,6 @@ import { buildSenseGenerator } from "./build.js";
const HOME = process.env.HOME ?? "/home/azureuser"; const HOME = process.env.HOME ?? "/home/azureuser";
const NERVE_ROOT = join(HOME, ".uncaged-nerve"); const NERVE_ROOT = join(HOME, ".uncaged-nerve");
const SENSES_DIR = join(NERVE_ROOT, "senses");
// --- Resolve provider --- // --- Resolve provider ---
@ -28,23 +26,11 @@ if (!apiKey || !baseUrl) {
throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL"); throw new Error("Set DASHSCOPE_API_KEY and DASHSCOPE_BASE_URL");
} }
// --- Build context ---
function getNerveYaml(): string {
try {
return readFileSync(join(NERVE_ROOT, "nerve.yaml"), "utf-8");
} catch {
return "# nerve.yaml unavailable";
}
}
// --- Wire up --- // --- Wire up ---
const workflow = buildSenseGenerator({ const workflow = buildSenseGenerator({
provider: { apiKey, baseUrl, model }, provider: { apiKey, baseUrl, model },
nerveRoot: NERVE_ROOT, cwd: NERVE_ROOT,
sensesDir: SENSES_DIR,
nerveYaml: getNerveYaml(),
}); });
export default workflow; export default workflow;

View File

@ -11,17 +11,13 @@ export const coderMetaSchema = z.object({
export type BuildCoderDeps = { export type BuildCoderDeps = {
provider: LlmProvider; provider: LlmProvider;
cwd: string; cwd: string;
sensesDir: string;
nerveRoot: string;
}; };
export function buildCoderRole({ provider, cwd, sensesDir, nerveRoot }: BuildCoderDeps) { export function buildCoderRole({ provider, cwd }: BuildCoderDeps) {
return createCursorRole<CoderMeta>({ return createCursorRole<CoderMeta>({
cwd, cwd,
mode: "default", mode: "default",
prompt: async (threadId) => coderPrompt({ threadId, sensesDir, nerveRoot }), prompt: async (threadId) => coderPrompt({ threadId }),
extract: { provider, schema: coderMetaSchema }, extract: { provider, schema: coderMetaSchema },
}); });
} }

View File

@ -1,16 +1,7 @@
export function coderPrompt(vars: { export function coderPrompt({ threadId }: { threadId: string }): string {
threadId: string; return `Read the workflow thread for the planner's sense design: \`nerve thread ${threadId}\`
sensesDir: string; Read the nerve-dev skill for sense file structure and conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
nerveRoot: string;
}): string {
return `Read the workflow thread for the planner's sense design: \`nerve thread ${vars.threadId}\`
Implement the sense. Create exactly: Implement the sense following the patterns from existing senses and the skill guide.
1. The sense directory under ${vars.sensesDir}/<sense-name>/ Create all required files and update nerve.yaml.`;
2. index.js export async function compute(db, _peers), import schema from "./schema.ts"
3. schema.ts drizzle-orm/sqlite-core
4. migrations/0001_init.sql must match schema.ts
5. Update ${vars.nerveRoot}/nerve.yaml add sense config + reflex entry
Follow the patterns from existing senses. Create all files now.`;
} }

View File

@ -11,16 +11,13 @@ export const plannerMetaSchema = z.object({
export type BuildPlannerDeps = { export type BuildPlannerDeps = {
provider: LlmProvider; provider: LlmProvider;
cwd: string; cwd: string;
nerveYaml: string;
}; };
export function buildPlannerRole({ provider, cwd, nerveYaml }: BuildPlannerDeps) { export function buildPlannerRole({ provider, cwd }: BuildPlannerDeps) {
return createCursorRole<PlannerMeta>({ return createCursorRole<PlannerMeta>({
cwd, cwd,
mode: "ask", mode: "ask",
prompt: async (threadId) => plannerPrompt({ threadId, nerveYaml }), prompt: async (threadId) => plannerPrompt({ threadId }),
extract: { provider, schema: plannerMetaSchema }, extract: { provider, schema: plannerMetaSchema },
}); });
} }

View File

@ -1,10 +1,9 @@
export function plannerPrompt({ threadId, nerveYaml }: { export function plannerPrompt({ threadId }: { threadId: string }): string {
threadId: string;
nerveYaml: string;
}): string {
return `You are planning a new Nerve sense. return `You are planning a new Nerve sense.
Read the workflow thread for the user's request: \`nerve thread ${threadId}\` Read the workflow thread for the user's request: \`nerve thread ${threadId}\`
Read the nerve-dev skill for sense conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
Also look at existing senses in the \`senses/\` directory for patterns.
Pick a good kebab-case name for this sense. Produce a PLAN (not code) in markdown: Pick a good kebab-case name for this sense. Produce a PLAN (not code) in markdown:
@ -14,14 +13,5 @@ Pick a good kebab-case name for this sense. Produce a PLAN (not code) in markdow
### Compute Logic step-by-step, specific Node.js APIs or shell commands ### Compute Logic step-by-step, specific Node.js APIs or shell commands
### Trigger Config group, interval, throttle, timeout ### Trigger Config group, interval, throttle, timeout
For reference examples of existing senses (schema, compute logic, migrations), read the nerve-dev skill:
\`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
Also look at existing senses in the \`senses/\` directory for patterns.
Current nerve.yaml:
\`\`\`yaml
${nerveYaml}
\`\`\`
Output ONLY the plan. Be precise and implementation-ready.`; Output ONLY the plan. Be precise and implementation-ready.`;
} }

View File

@ -10,15 +10,11 @@ export const testerMetaSchema = z.object({
export type BuildTesterDeps = { export type BuildTesterDeps = {
provider: LlmProvider; provider: LlmProvider;
sensesDir: string;
nerveRoot: string;
}; };
export function buildTesterRole({ provider, sensesDir, nerveRoot }: BuildTesterDeps) { export function buildTesterRole({ provider }: BuildTesterDeps) {
return createHermesRole<TesterMeta>({ return createHermesRole<TesterMeta>({
prompt: async (threadId) => testerPrompt({ threadId, sensesDir, nerveRoot }), prompt: async (threadId) => testerPrompt({ threadId }),
extract: { provider, schema: testerMetaSchema }, extract: { provider, schema: testerMetaSchema },
}); });
} }

View File

@ -1,32 +1,16 @@
export function testerPrompt(vars: { export function testerPrompt({ threadId }: { threadId: string }): string {
threadId: string;
sensesDir: string;
nerveRoot: string;
}): string {
return `You are testing a newly created Nerve sense end-to-end. return `You are testing a newly created Nerve sense end-to-end.
Read the workflow thread for context: \`nerve thread ${vars.threadId}\` Read the workflow thread for context: \`nerve thread ${threadId}\`
The planner named the sense and the coder created the files. Read the nerve-dev skill for expected file structure: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
Verify the full lifecycle: Verify the full lifecycle:
1. Check all required sense files exist
1. Check files exist under ${vars.sensesDir}/<sense-name>/ 2. Check nerve.yaml has the sense config
- index.js, schema.ts, migrations/0001_init.sql 3. Run \`nerve sense list\` — confirm the sense appears
All three must exist.
2. Check ${vars.nerveRoot}/nerve.yaml has the sense config and reflex entry
The sense name should appear under \`senses:\` with group, throttle, etc.
3. Run \`nerve sense list\` — confirm the sense appears in the output
4. Run \`nerve sense trigger <sense-name>\` — should complete without error 4. Run \`nerve sense trigger <sense-name>\` — should complete without error
5. Run \`nerve sense query <sense-name>\` — retry up to 20s until rows appear
5. Wait a few seconds, then run \`nerve sense query <sense-name>\` 6. If any step fails, run \`nerve logs\` and include relevant errors
Keep retrying (up to ~20 seconds) until it returns at least one row.
If it still says "0 rows", that's a failure.
6. If any step fails, run \`nerve logs\` to check for errors and include
relevant log lines in your report.
Output a clear summary: what you checked, what passed, what failed, and why.`; Output a clear summary: what you checked, what passed, what failed, and why.`;
} }