Merge pull request 'refactor: align develop prompts and .knowledge with flat workspace' (#288) from refactor/287-align-prompts-knowledge into main
This commit was merged in pull request #288.
This commit is contained in:
+22
-1
@@ -28,8 +28,29 @@ For long-running or incremental agent outputs:
|
||||
|---------|---------|------|
|
||||
| `@uncaged/nerve-adapter-cursor` | `cursorAdapter` / `createCursorAdapter()` | cursor-agent CLI |
|
||||
| `@uncaged/nerve-adapter-hermes` | `hermesAdapter` / `createHermesAdapter()` | hermes chat CLI |
|
||||
| `@uncaged/nerve-workflow-utils` | `createLlmAdapter(provider)` | OpenAI-compatible HTTP chat (single-turn) |
|
||||
|
||||
Each exports a **default instance** (sensible defaults) and a **factory** for custom config.
|
||||
The Cursor and Hermes adapter packages each export a **default instance** (sensible defaults) and a **factory** for custom config. `createLlmAdapter` is a factory on `@uncaged/nerve-workflow-utils` only.
|
||||
|
||||
## createLlmAdapter
|
||||
|
||||
`createLlmAdapter` builds an `AgentFn` from an `LlmProvider` (`baseUrl`, `apiKey`, `model`). One chat completion per role step: **system** = the string passed by `createRole` (your prompt); **user** = `ctx.start.content` (the thread’s start frame). On failure it throws with a formatted LLM error.
|
||||
|
||||
```ts
|
||||
import { createLlmAdapter, createRole } from "@uncaged/nerve-workflow-utils";
|
||||
import { z } from "zod";
|
||||
|
||||
const metaSchema = z.object({ ok: z.boolean() });
|
||||
|
||||
const planner = createRole(
|
||||
createLlmAdapter({ baseUrl: "https://api.example.com/v1", apiKey: "…", model: "gpt-4o-mini" }),
|
||||
"You are a planner…",
|
||||
metaSchema,
|
||||
extractConfig,
|
||||
);
|
||||
```
|
||||
|
||||
Use this when you want a role backed by an HTTP LLM instead of a subprocess CLI adapter.
|
||||
|
||||
## Usage in Workflows
|
||||
|
||||
|
||||
@@ -2,6 +2,20 @@
|
||||
|
||||
Stateful multi-step execution driven by Roles and a Moderator.
|
||||
|
||||
## Workspace Layout (authoring)
|
||||
|
||||
User Nerve workspaces use a **flat** build: one root `package.json`, one root bundle script (typically `scripts/build.mjs` wired from `scripts.build`), and **no** per-workflow `package.json` or `tsconfig.json`.
|
||||
|
||||
| Location | Purpose |
|
||||
|----------|---------|
|
||||
| `workflows/<name>/index.ts` | Default export: `WorkflowDefinition` (moderator + role map). |
|
||||
| `workflows/<name>/roles/<role>.ts` | One module per role — schemas, prompts, `createRole` factories, or hand-written async role functions. |
|
||||
| `dist/workflows/<name>/index.js` | Emit of the root build; this is what the daemon loads. |
|
||||
|
||||
**Naming:** Workflow ids should be **verb-first** kebab-case phrases (e.g. `deploy-staging`, `scan-dependencies`), not opaque nouns alone.
|
||||
|
||||
Senses follow the same flat pattern: `senses/<name>/src/*.ts`, `migrations/`, root build → `dist/senses/<name>/index.js`. See `.knowledge/sense.md`.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
- **Workflow** — definition with concurrency strategy
|
||||
|
||||
@@ -205,6 +205,10 @@ describe("e2e init", () => {
|
||||
expect(existsSync(join(nerveRoot, "scripts", "build.mjs"))).toBe(true);
|
||||
expect(existsSync(join(nerveRoot, "biome.json"))).toBe(true);
|
||||
expect(existsSync(join(nerveRoot, ".gitignore"))).toBe(true);
|
||||
expect(existsSync(join(nerveRoot, "AGENT.md"))).toBe(true);
|
||||
const agentMd = readFileSync(join(nerveRoot, "AGENT.md"), "utf8");
|
||||
expect(agentMd).toContain("verb-first");
|
||||
expect(agentMd).toContain("createRole");
|
||||
expect(existsSync(join(nerveRoot, "senses", "cpu-usage", "src", "index.ts"))).toBe(true);
|
||||
expect(existsSync(join(nerveRoot, "senses", "cpu-usage", "src", "schema.ts"))).toBe(true);
|
||||
expect(existsSync(join(nerveRoot, "senses", "cpu-usage", "migrations", "0001_init.sql"))).toBe(
|
||||
|
||||
@@ -127,6 +127,70 @@ node_modules/
|
||||
knowledge.db
|
||||
`;
|
||||
|
||||
/** Generated at workspace root so agents can \`cat AGENT.md\` instead of npm skill paths. */
|
||||
const AGENT_MD = `# Nerve workspace — agent guide
|
||||
|
||||
This file is created by \`nerve init\`. Read it before implementing senses or workflows.
|
||||
|
||||
## Directory layout
|
||||
|
||||
| Path | Purpose |
|
||||
|------|---------|
|
||||
| \`nerve.yaml\` | Senses, workflows, intervals, groups |
|
||||
| \`package.json\` | Single root package — no per-sense/per-workflow packages |
|
||||
| \`scripts/build.mjs\` | Root esbuild step; output under \`dist/\` |
|
||||
| \`senses/<name>/src/index.ts\` | Sense \`compute()\` entry |
|
||||
| \`senses/<name>/src/schema.ts\` | Drizzle SQLite schema (TypeScript) |
|
||||
| \`senses/<name>/migrations/*.sql\` | SQL migrations (next to \`src/\`, not inside it) |
|
||||
| \`workflows/<name>/index.ts\` | Default export: \`WorkflowDefinition\` |
|
||||
| \`workflows/<name>/roles/<role>.ts\` | One TypeScript file per role |
|
||||
| \`dist/senses/<name>/index.js\` | Bundled sense (after build) |
|
||||
| \`dist/workflows/<name>/index.js\` | Bundled workflow (after build) |
|
||||
|
||||
There is **no** \`package.json\` or \`tsconfig.json\` inside individual senses or workflows.
|
||||
|
||||
## Naming
|
||||
|
||||
- **Workflows:** verb-first kebab-case (e.g. \`review-pull-request\`, \`deploy-staging\`). Avoid bare nouns like \`notifications\`.
|
||||
- **Senses:** kebab-case descriptive nouns (e.g. \`cpu-usage\`).
|
||||
|
||||
## Workflow roles — four-tuple pattern
|
||||
|
||||
Wire each role with \`createRole\` from \`@uncaged/nerve-workflow-utils\`:
|
||||
|
||||
1. **Adapter** — \`AgentFn\` (LLM call)
|
||||
2. **Prompt builder** — \`async (ctx: ThreadContext) => string\`
|
||||
3. **Meta schema** — Zod object (routing / structured output from the model)
|
||||
4. **Extractor config** — how JSON meta is parsed from replies
|
||||
|
||||
Keep meta small (often one boolean per role). The **moderator** in \`WorkflowDefinition\` routes between role names.
|
||||
|
||||
## Build commands
|
||||
|
||||
Always run from the **workspace root**:
|
||||
|
||||
\`\`\`bash
|
||||
pnpm run build
|
||||
# or: npm run build
|
||||
\`\`\`
|
||||
|
||||
Fix errors until this succeeds. New workflows must appear under \`workflows/<name>/\` and be registered in \`nerve.yaml\`; new senses under \`senses/<name>/\` with matching \`nerve.yaml\` entries.
|
||||
|
||||
## Coding style (Nerve conventions)
|
||||
|
||||
- Use \`type\`, not \`interface\`; prefer \`function\` over classes (except errors / library requirements).
|
||||
- **Named exports only** — no \`export default\` (exception: \`workflows/<name>/index.ts\` uses default export for the daemon loader).
|
||||
- Nullable fields: \`T | null\`, not TypeScript optional \`?:\`.
|
||||
- No dynamic \`import()\` in workspace code (bundling and tooling assume static imports).
|
||||
- Use \`async\`/\`await\`; use a \`Result\` type for expected failures instead of control-flow try/catch.
|
||||
|
||||
## Extra references (optional)
|
||||
|
||||
- \`CONVENTIONS.md\` — project-specific overrides at repo root.
|
||||
- \`.knowledge/*.md\` — deeper docs when working inside the Nerve monorepo.
|
||||
- \`.cursor/skills/\` — Cursor Agent Skills (\`SKILL.md\` per skill).
|
||||
`;
|
||||
|
||||
const NERVE_SKILLS_MDC = `---
|
||||
description: >-
|
||||
Where Agent Skills live in this Nerve workspace and how to use them with Cursor
|
||||
@@ -362,6 +426,7 @@ async function runInitWorkspace(force: boolean, skipInstall = false): Promise<vo
|
||||
writeFile(join(nerveRoot, "scripts", "build.mjs"), BUILD_MJS);
|
||||
writeFile(join(nerveRoot, "biome.json"), BIOME_JSON);
|
||||
writeFile(join(nerveRoot, ".gitignore"), GITIGNORE);
|
||||
writeFile(join(nerveRoot, "AGENT.md"), AGENT_MD);
|
||||
writeFile(join(nerveRoot, "senses", "cpu-usage", "src", "index.ts"), CPU_INDEX_TS);
|
||||
writeFile(join(nerveRoot, "senses", "cpu-usage", "src", "schema.ts"), CPU_SCHEMA_TS);
|
||||
writeFile(
|
||||
|
||||
@@ -10,7 +10,7 @@ export type CoderMeta = z.infer<typeof coderMetaSchema>;
|
||||
|
||||
export function coderPrompt({ threadId }: { threadId: string }): string {
|
||||
return `Read the workflow thread for the planner's sense design and any tester feedback: \`nerve thread ${threadId}\`
|
||||
Read the nerve-dev skill for sense file structure and conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
||||
Read \`cat AGENT.md\` from the repository root, then \`CONVENTIONS.md\` and \`.knowledge/sense.md\` if present.
|
||||
|
||||
## Your task
|
||||
|
||||
@@ -20,21 +20,21 @@ Implement (or fix) the sense the planner designed. If there is tester feedback i
|
||||
|
||||
You do NOT need to finish everything in one pass. You may return \`done: false\` to continue in the next iteration.
|
||||
|
||||
## File structure for each sense
|
||||
## File structure for each sense (flat workspace)
|
||||
|
||||
- \`senses/<name>/src/index.ts\` — TypeScript compute source; import schema as \`./schema.ts\`
|
||||
The workspace has **one root** \`package.json\` and root \`scripts/build.mjs\` (or equivalent) that bundles all senses. There is **no** per-sense \`package.json\`. Bundled output is \`dist/senses/<name>/index.js\` after a root build.
|
||||
|
||||
- \`senses/<name>/src/index.ts\` — compute entry; import schema as \`./schema.ts\`
|
||||
- \`senses/<name>/src/schema.ts\` — Drizzle schema (TypeScript)
|
||||
- \`senses/<name>/migrations/\` — Drizzle migration files (at sense root, not inside src/)
|
||||
- \`senses/<name>/package.json\` — with esbuild build script
|
||||
- \`senses/<name>/index.js\` — bundled output generated by \`pnpm build\` (do NOT edit by hand)
|
||||
- \`senses/<name>/migrations/\` — SQL migration files (at sense root, not inside \`src/\`)
|
||||
|
||||
Look at existing senses for the package.json template and patterns.
|
||||
Look at existing senses for patterns.
|
||||
|
||||
## When to return done: true
|
||||
|
||||
Return \`done: true\` ONLY when ALL of the following are true:
|
||||
- All required files are created
|
||||
- \`pnpm install --no-cache && pnpm build\` succeeds (run it!)
|
||||
- From the **workspace root**, \`pnpm run build\` or \`npm run build\` succeeds (run it!) and \`dist/senses/<name>/index.js\` exists
|
||||
- \`nerve.yaml\` is updated with the sense config
|
||||
|
||||
Return \`done: false\` if you made progress but there is still work to do.`;
|
||||
|
||||
@@ -12,7 +12,7 @@ export function plannerPrompt({ threadId }: { threadId: string }): string {
|
||||
return `You are planning a new Nerve sense.
|
||||
|
||||
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\`
|
||||
Read the workspace guide: \`cat AGENT.md\` from the repository root (created by \`nerve init\`). Also read \`CONVENTIONS.md\` and \`.knowledge/sense.md\` if present. Optional skills live under \`.cursor/skills/\`.
|
||||
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:
|
||||
|
||||
@@ -17,21 +17,20 @@ export function testerPrompt({
|
||||
**IMPORTANT: The Nerve workspace is at \`${nerveRoot}\`. All paths below are relative to this directory. Always \`cd ${nerveRoot}\` first.**
|
||||
|
||||
Read the workflow thread for context: \`nerve thread ${threadId}\`
|
||||
Read the nerve-dev skill for expected file structure: \`cat ${nerveRoot}/node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
||||
Read \`cat ${nerveRoot}/AGENT.md\`, then \`${nerveRoot}/CONVENTIONS.md\` and \`${nerveRoot}/.knowledge/sense.md\` if they exist.
|
||||
|
||||
Verify the full lifecycle in this order:
|
||||
|
||||
1. **File check** — all required sense files exist:
|
||||
1. **File check** — all required sense files exist (no per-sense \`package.json\`):
|
||||
- \`senses/<name>/src/index.ts\`
|
||||
- \`senses/<name>/src/schema.ts\`
|
||||
- \`senses/<name>/migrations/\`
|
||||
- \`senses/<name>/package.json\`
|
||||
|
||||
2. **Build** — run inside the sense directory:
|
||||
2. **Build** — from the workspace root:
|
||||
\`\`\`
|
||||
cd ${nerveRoot}/senses/<name> && pnpm install --no-cache && pnpm build
|
||||
cd ${nerveRoot} && pnpm run build
|
||||
\`\`\`
|
||||
Must produce \`index.js\` at sense root without errors.
|
||||
(or \`npm run build\` per root \`package.json\`.) Must produce \`${nerveRoot}/dist/senses/<name>/index.js\` without errors.
|
||||
|
||||
3. **Config check** — \`nerve validate\` passes, confirming nerve.yaml is valid.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export type CoderMeta = z.infer<typeof coderMetaSchema>;
|
||||
|
||||
export function coderPrompt({ threadId }: { threadId: string }): string {
|
||||
return `Read the workflow thread to get the planner's design and any reviewer/tester/committer feedback: \`nerve thread ${threadId}\`
|
||||
Read the nerve-dev skill for workflow file structure and conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
||||
Read \`cat AGENT.md\` from the repository root, then \`CONVENTIONS.md\` and \`.knowledge/workflow.md\` if present. Optional skills live under \`.cursor/skills/\`.
|
||||
Also look at existing workflows in the \`workflows/\` directory for patterns.
|
||||
|
||||
## Your task
|
||||
@@ -29,15 +29,13 @@ You do NOT need to finish everything in one pass. You may return \`done: false\`
|
||||
2. Second pass: implement role logic
|
||||
3. Third pass: fix build/lint errors
|
||||
|
||||
## Workflow file structure
|
||||
## Workflow file structure (flat workspace)
|
||||
|
||||
The workspace has **one root** \`package.json\` and **one** root build (\`pnpm run build\` or \`npm run build\`), implemented by \`scripts/build.mjs\`, which emits bundles under \`dist/workflows/<name>/index.js\`. There is **no** per-workflow \`package.json\` or \`tsconfig.json\`.
|
||||
|
||||
Each workflow must have:
|
||||
- \`workflows/<name>/index.ts\` — WorkflowDefinition default export
|
||||
- \`workflows/<name>/build.ts\` — factory function
|
||||
- \`workflows/<name>/moderator.ts\` — moderator + meta types
|
||||
- \`workflows/<name>/roles/<role>.ts\` — meta schema and prompt function per role
|
||||
- \`workflows/<name>/package.json\` — with esbuild build script
|
||||
- \`workflows/<name>/tsconfig.json\` — TypeScript config
|
||||
- \`workflows/<name>/index.ts\` — default export \`WorkflowDefinition\` (moderator and meta types typically live here or are imported from co-located modules)
|
||||
- \`workflows/<name>/roles/<role>.ts\` — one TypeScript file per role (schemas, prompts, \`createRole\` wiring, or plain async role functions)
|
||||
|
||||
For **new workflows**, also update \`nerve.yaml\` with \`workflows.<name>\`.
|
||||
|
||||
@@ -53,7 +51,7 @@ For **new workflows**, also update \`nerve.yaml\` with \`workflows.<name>\`.
|
||||
|
||||
Return \`done: true\` ONLY when ALL of the following are true:
|
||||
- All changes from the plan are implemented
|
||||
- \`cd workflows/<name> && pnpm install --no-cache && pnpm build\` succeeds (run it!)
|
||||
- From the **workspace root**, \`pnpm run build\` or \`npm run build\` succeeds (run it!) so \`dist/workflows/<name>/index.js\` is produced
|
||||
- No lint or type errors remain
|
||||
|
||||
Return \`done: false\` if you made progress but there is still work to do, or if build/lint has errors you plan to fix in the next iteration.`;
|
||||
|
||||
@@ -12,18 +12,18 @@ export function plannerPrompt({ threadId }: { threadId: string }): string {
|
||||
return `You are a Nerve workflow planner. You can **create new workflows** or **modify existing ones**.
|
||||
|
||||
Read the workflow thread for the user's request: \`nerve thread ${threadId}\`
|
||||
Read the nerve-dev skill for workflow conventions: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
||||
Read the workspace guide: \`cat AGENT.md\` from the repository root (created by \`nerve init\`). Also read \`CONVENTIONS.md\` if it exists; if \`.knowledge/workflow.md\` exists (e.g. Nerve monorepo), read it for layout and engine behavior. Optional Cursor skills live under \`.cursor/skills/\`.
|
||||
List existing workflows: \`ls workflows/\`
|
||||
|
||||
## Determine the task type
|
||||
|
||||
1. If the user wants to **modify an existing workflow** — read its current code (\`cat workflows/<name>/moderator.ts\`, \`cat workflows/<name>/build.ts\`, \`ls workflows/<name>/roles/\`, etc.) and understand its current structure before planning changes.
|
||||
1. If the user wants to **modify an existing workflow** — read its current code (\`cat workflows/<name>/index.ts\`, \`ls workflows/<name>/roles/\`, \`cat workflows/<name>/roles/<role>.ts\`, etc.) and understand its current structure before planning changes.
|
||||
2. If the user wants to **create a new workflow** — look at existing workflows in \`workflows/\` for patterns to follow.
|
||||
|
||||
## Produce a PLAN (not code) in markdown
|
||||
|
||||
For **new workflows**:
|
||||
- Workflow name (kebab-case)
|
||||
- Workflow name — **verb-first** kebab-case phrase (e.g. \`review-pull-request\`, \`deploy-staging\`), not a bare noun
|
||||
- Roles list (name, purpose, tool)
|
||||
- Flow transitions / moderator routing logic
|
||||
- Validation loops design
|
||||
|
||||
@@ -17,24 +17,22 @@ export function testerPrompt({
|
||||
**IMPORTANT: The Nerve workspace is at \`${nerveRoot}\`. All paths below are relative to this directory. Always \`cd ${nerveRoot}\` first.**
|
||||
|
||||
Read the workflow thread for context: \`nerve thread ${threadId}\`
|
||||
Read the nerve-dev skill for expected file structure: \`cat ${nerveRoot}/node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
||||
Read \`cat ${nerveRoot}/AGENT.md\`, then \`${nerveRoot}/CONVENTIONS.md\` and \`${nerveRoot}/.knowledge/workflow.md\` if they exist.
|
||||
|
||||
Get the workflow name from the thread (the planner's output).
|
||||
|
||||
Verify the full lifecycle in this order:
|
||||
|
||||
1. **File check** — all required workflow files exist (under \`${nerveRoot}/\`):
|
||||
1. **File check** — all required workflow sources exist (under \`${nerveRoot}/\`):
|
||||
- \`workflows/<name>/index.ts\`
|
||||
- \`workflows/<name>/build.ts\`
|
||||
- \`workflows/<name>/moderator.ts\`
|
||||
- \`workflows/<name>/roles/\` with one \`.ts\` file per role
|
||||
- \`workflows/<name>/package.json\`
|
||||
- \`workflows/<name>/roles/\` with one \`.ts\` file per role (flat files, not per-role packages)
|
||||
- **No** \`workflows/<name>/package.json\` or \`tsconfig.json\` expected
|
||||
|
||||
2. **Build** — run inside the workflow directory:
|
||||
2. **Build** — from the workspace root:
|
||||
\`\`\`
|
||||
cd ${nerveRoot}/workflows/<name> && pnpm install --no-cache && pnpm build
|
||||
cd ${nerveRoot} && pnpm run build
|
||||
\`\`\`
|
||||
Must produce \`dist/index.js\` without errors.
|
||||
(or \`npm run build\` if that is what the root \`package.json\` defines.) Must produce \`${nerveRoot}/dist/workflows/<name>/index.js\` without errors.
|
||||
|
||||
3. **Config check** — \`cd ${nerveRoot} && nerve validate\` passes, confirming nerve.yaml is valid.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user