chore(workflow): auto-generated commit
This commit is contained in:
parent
0fab8a68c3
commit
495d8d1b60
@ -20,6 +20,11 @@ senses:
|
||||
interval: 1m
|
||||
throttle: 15s
|
||||
timeout: 5s
|
||||
git-workspace-status:
|
||||
group: workspace
|
||||
interval: 2m
|
||||
throttle: 30s
|
||||
timeout: 15s
|
||||
|
||||
workflows:
|
||||
sense-generator:
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@ -33,6 +33,18 @@ importers:
|
||||
specifier: latest
|
||||
version: 0.31.10
|
||||
|
||||
senses/git-workspace-status:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.17
|
||||
esbuild:
|
||||
specifier: ^0.27.0
|
||||
version: 0.27.7
|
||||
typescript:
|
||||
specifier: ^5.7.0
|
||||
version: 5.9.3
|
||||
|
||||
senses/hermes-gateway-health:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
|
||||
122
senses/git-workspace-status/index.js
Normal file
122
senses/git-workspace-status/index.js
Normal file
@ -0,0 +1,122 @@
|
||||
// src/index.ts
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
// src/schema.ts
|
||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
var snapshots = sqliteTable("snapshots", {
|
||||
ts: integer("ts").primaryKey(),
|
||||
branch: text("branch").notNull(),
|
||||
headShort: text("head_short").notNull(),
|
||||
porcelainLines: integer("porcelain_lines").notNull(),
|
||||
hasUpstream: integer("has_upstream").notNull(),
|
||||
aheadCount: integer("ahead_count").notNull(),
|
||||
behindCount: integer("behind_count").notNull(),
|
||||
/** Empty string when the snapshot succeeded; otherwise a short error summary. */
|
||||
gitError: text("git_error").notNull()
|
||||
});
|
||||
|
||||
// src/index.ts
|
||||
var GIT_TIMEOUT_MS = 15e3;
|
||||
function workspaceRoot() {
|
||||
const raw = process.env.GIT_WORKSPACE_ROOT;
|
||||
return raw ? resolve(raw) : resolve(process.cwd());
|
||||
}
|
||||
function gitErrorMessage(err) {
|
||||
if (err instanceof Error) {
|
||||
const m = err.message.trim();
|
||||
return m.length > 200 ? `${m.slice(0, 197)}...` : m;
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
function runGit(cwd, args) {
|
||||
return execFileSync("git", args, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
timeout: GIT_TIMEOUT_MS,
|
||||
maxBuffer: 2 * 1024 * 1024
|
||||
}).trimEnd();
|
||||
}
|
||||
function countPorcelainLines(output) {
|
||||
if (!output) return 0;
|
||||
return output.split("\n").filter((line) => line.length > 0).length;
|
||||
}
|
||||
async function compute(db, _peers) {
|
||||
const root = workspaceRoot();
|
||||
const ts = Date.now();
|
||||
let branch = "";
|
||||
let headShort = "";
|
||||
let porcelainLines = 0;
|
||||
let hasUpstream = 0;
|
||||
let aheadCount = 0;
|
||||
let behindCount = 0;
|
||||
let gitError = "";
|
||||
try {
|
||||
const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim();
|
||||
if (inside !== "true") {
|
||||
gitError = "not a git work tree";
|
||||
await db.insert(snapshots).values({
|
||||
ts,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError
|
||||
});
|
||||
return {
|
||||
workspaceRoot: root,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream: false,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError
|
||||
};
|
||||
}
|
||||
branch = runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
||||
headShort = runGit(root, ["rev-parse", "--short", "HEAD"]);
|
||||
porcelainLines = countPorcelainLines(runGit(root, ["status", "--porcelain"]));
|
||||
try {
|
||||
runGit(root, ["rev-parse", "--abbrev-ref", "@{upstream}"]);
|
||||
hasUpstream = 1;
|
||||
const lb = runGit(root, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]);
|
||||
const parts = lb.split(/[\t\s]+/).filter(Boolean);
|
||||
if (parts.length >= 2) {
|
||||
aheadCount = Number.parseInt(parts[0], 10) || 0;
|
||||
behindCount = Number.parseInt(parts[1], 10) || 0;
|
||||
}
|
||||
} catch {
|
||||
hasUpstream = 0;
|
||||
aheadCount = 0;
|
||||
behindCount = 0;
|
||||
}
|
||||
} catch (e) {
|
||||
gitError = gitErrorMessage(e);
|
||||
}
|
||||
await db.insert(snapshots).values({
|
||||
ts,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError
|
||||
});
|
||||
return {
|
||||
workspaceRoot: root,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream: hasUpstream === 1,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError: gitError || void 0
|
||||
};
|
||||
}
|
||||
export {
|
||||
compute
|
||||
};
|
||||
13
senses/git-workspace-status/migrations/0001_init.sql
Normal file
13
senses/git-workspace-status/migrations/0001_init.sql
Normal file
@ -0,0 +1,13 @@
|
||||
-- Migration: 0001_init
|
||||
-- Creates the snapshots table for git-workspace-status sense.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS snapshots (
|
||||
ts INTEGER PRIMARY KEY,
|
||||
branch TEXT NOT NULL,
|
||||
head_short TEXT NOT NULL,
|
||||
porcelain_lines INTEGER NOT NULL,
|
||||
has_upstream INTEGER NOT NULL,
|
||||
ahead_count INTEGER NOT NULL,
|
||||
behind_count INTEGER NOT NULL,
|
||||
git_error TEXT NOT NULL
|
||||
);
|
||||
14
senses/git-workspace-status/package.json
Normal file
14
senses/git-workspace-status/package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "sense-git-workspace-status",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=index.js --packages=external"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
116
senses/git-workspace-status/src/index.ts
Normal file
116
senses/git-workspace-status/src/index.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { resolve } from "node:path";
|
||||
import type { LibSQLDatabase } from "drizzle-orm/libsql";
|
||||
import { snapshots } from "./schema.ts";
|
||||
|
||||
const GIT_TIMEOUT_MS = 15_000;
|
||||
|
||||
function workspaceRoot(): string {
|
||||
const raw = process.env.GIT_WORKSPACE_ROOT;
|
||||
return raw ? resolve(raw) : resolve(process.cwd());
|
||||
}
|
||||
|
||||
function gitErrorMessage(err: unknown): string {
|
||||
if (err instanceof Error) {
|
||||
const m = err.message.trim();
|
||||
return m.length > 200 ? `${m.slice(0, 197)}...` : m;
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
|
||||
function runGit(cwd: string, args: string[]): string {
|
||||
return execFileSync("git", args, {
|
||||
cwd,
|
||||
encoding: "utf8",
|
||||
timeout: GIT_TIMEOUT_MS,
|
||||
maxBuffer: 2 * 1024 * 1024,
|
||||
}).trimEnd();
|
||||
}
|
||||
|
||||
function countPorcelainLines(output: string): number {
|
||||
if (!output) return 0;
|
||||
return output.split("\n").filter((line) => line.length > 0).length;
|
||||
}
|
||||
|
||||
export async function compute(db: LibSQLDatabase, _peers: unknown) {
|
||||
const root = workspaceRoot();
|
||||
const ts = Date.now();
|
||||
|
||||
let branch = "";
|
||||
let headShort = "";
|
||||
let porcelainLines = 0;
|
||||
let hasUpstream = 0;
|
||||
let aheadCount = 0;
|
||||
let behindCount = 0;
|
||||
let gitError = "";
|
||||
|
||||
try {
|
||||
const inside = runGit(root, ["rev-parse", "--is-inside-work-tree"]).trim();
|
||||
if (inside !== "true") {
|
||||
gitError = "not a git work tree";
|
||||
await db.insert(snapshots).values({
|
||||
ts,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError,
|
||||
});
|
||||
return {
|
||||
workspaceRoot: root,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream: false,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError,
|
||||
};
|
||||
}
|
||||
|
||||
branch = runGit(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
||||
headShort = runGit(root, ["rev-parse", "--short", "HEAD"]);
|
||||
porcelainLines = countPorcelainLines(runGit(root, ["status", "--porcelain"]));
|
||||
|
||||
try {
|
||||
runGit(root, ["rev-parse", "--abbrev-ref", "@{upstream}"]);
|
||||
hasUpstream = 1;
|
||||
const lb = runGit(root, ["rev-list", "--left-right", "--count", "HEAD...@{upstream}"]);
|
||||
const parts = lb.split(/[\t\s]+/).filter(Boolean);
|
||||
if (parts.length >= 2) {
|
||||
aheadCount = Number.parseInt(parts[0], 10) || 0;
|
||||
behindCount = Number.parseInt(parts[1], 10) || 0;
|
||||
}
|
||||
} catch {
|
||||
hasUpstream = 0;
|
||||
aheadCount = 0;
|
||||
behindCount = 0;
|
||||
}
|
||||
} catch (e) {
|
||||
gitError = gitErrorMessage(e);
|
||||
}
|
||||
|
||||
await db.insert(snapshots).values({
|
||||
ts,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError,
|
||||
});
|
||||
|
||||
return {
|
||||
workspaceRoot: root,
|
||||
branch,
|
||||
headShort,
|
||||
porcelainLines,
|
||||
hasUpstream: hasUpstream === 1,
|
||||
aheadCount,
|
||||
behindCount,
|
||||
gitError: gitError || undefined,
|
||||
};
|
||||
}
|
||||
13
senses/git-workspace-status/src/schema.ts
Normal file
13
senses/git-workspace-status/src/schema.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const snapshots = sqliteTable("snapshots", {
|
||||
ts: integer("ts").primaryKey(),
|
||||
branch: text("branch").notNull(),
|
||||
headShort: text("head_short").notNull(),
|
||||
porcelainLines: integer("porcelain_lines").notNull(),
|
||||
hasUpstream: integer("has_upstream").notNull(),
|
||||
aheadCount: integer("ahead_count").notNull(),
|
||||
behindCount: integer("behind_count").notNull(),
|
||||
/** Empty string when the snapshot succeeded; otherwise a short error summary. */
|
||||
gitError: text("git_error").notNull(),
|
||||
});
|
||||
@ -3,6 +3,7 @@ import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
|
||||
import { buildPlannerRole } from "./roles/planner/index.js";
|
||||
import { buildCoderRole } from "./roles/coder/index.js";
|
||||
import { buildTesterRole } from "./roles/tester/index.js";
|
||||
import { buildCommitterRole } from "./roles/committer/index.js";
|
||||
import { moderator } from "./moderator.js";
|
||||
import type { SenseMeta } from "./moderator.js";
|
||||
|
||||
@ -21,6 +22,7 @@ export function buildSenseGenerator({
|
||||
planner: buildPlannerRole({ provider, cwd }),
|
||||
coder: buildCoderRole({ provider, cwd }),
|
||||
tester: buildTesterRole({ provider }),
|
||||
committer: buildCommitterRole({ nerveRoot: cwd }),
|
||||
},
|
||||
moderator,
|
||||
};
|
||||
|
||||
@ -3,13 +3,17 @@ import type { Moderator } from "@uncaged/nerve-core";
|
||||
import type { PlannerMeta } from "./roles/planner/index.js";
|
||||
import type { CoderMeta } from "./roles/coder/index.js";
|
||||
import type { TesterMeta } from "./roles/tester/index.js";
|
||||
import type { CommitterMeta } from "./roles/committer/index.js";
|
||||
|
||||
export type SenseMeta = {
|
||||
planner: PlannerMeta;
|
||||
coder: CoderMeta;
|
||||
tester: TesterMeta;
|
||||
committer: CommitterMeta;
|
||||
};
|
||||
|
||||
const MAX_CODER_ITERATIONS = 5;
|
||||
|
||||
function countRole(steps: { role: string }[], name: string): number {
|
||||
return steps.filter((s) => s.role === name).length;
|
||||
}
|
||||
@ -17,11 +21,19 @@ function countRole(steps: { role: string }[], name: string): number {
|
||||
export const moderator: Moderator<SenseMeta> = (context) => {
|
||||
if (context.steps.length === 0) return "planner";
|
||||
const last = context.steps[context.steps.length - 1];
|
||||
const coderCount = context.steps.filter((s) => s.role === "coder").length;
|
||||
|
||||
if (last.role === "planner") return "coder";
|
||||
if (last.role === "coder") return "tester";
|
||||
if (last.role === "tester") {
|
||||
if (last.meta.passed) return END;
|
||||
return countRole(context.steps, "tester") < 3 ? "coder" : END;
|
||||
if (last.meta.passed) return "committer";
|
||||
const testerCount = countRole(context.steps, "tester");
|
||||
if (testerCount < 3 && coderCount < MAX_CODER_ITERATIONS) return "coder";
|
||||
return END;
|
||||
}
|
||||
if (last.role === "committer") {
|
||||
if (last.meta.success) return END;
|
||||
return coderCount < MAX_CODER_ITERATIONS ? "coder" : END;
|
||||
}
|
||||
return END;
|
||||
};
|
||||
|
||||
83
workflows/sense-generator/roles/committer/index.ts
Normal file
83
workflows/sense-generator/roles/committer/index.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { writeFileSync, mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import type { Role, RoleResult } from "@uncaged/nerve-core";
|
||||
import { isDryRun, spawnSafe } from "@uncaged/nerve-workflow-utils";
|
||||
|
||||
export type CommitterMeta = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
export type BuildCommitterDeps = {
|
||||
nerveRoot: string;
|
||||
};
|
||||
|
||||
function logPath(nerveRoot: string): string {
|
||||
return join(nerveRoot, "logs", `committer-${Date.now()}.log`);
|
||||
}
|
||||
|
||||
function writeLog(path: string, content: string): void {
|
||||
mkdirSync(join(path, ".."), { recursive: true });
|
||||
writeFileSync(path, content, "utf-8");
|
||||
}
|
||||
|
||||
export function buildCommitterRole({ nerveRoot }: BuildCommitterDeps): Role<CommitterMeta> {
|
||||
return async (start, _messages) => {
|
||||
const dry = isDryRun(start);
|
||||
const file = logPath(nerveRoot);
|
||||
|
||||
if (dry) {
|
||||
writeLog(file, "[dry-run] committer skipped\n");
|
||||
return {
|
||||
content: `[dry-run] committer skipped — log: ${file}`,
|
||||
meta: { success: true },
|
||||
} satisfies RoleResult<CommitterMeta>;
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
let success = true;
|
||||
|
||||
const run = async (cmd: string, args: string[]): Promise<boolean> => {
|
||||
const r = await spawnSafe(cmd, args, { cwd: nerveRoot, env: null, timeoutMs: 60_000, dryRun: false });
|
||||
if (r.ok) {
|
||||
lines.push(`$ ${cmd} ${args.join(" ")}`);
|
||||
if (r.value.stdout) lines.push(r.value.stdout);
|
||||
if (r.value.stderr) lines.push(r.value.stderr);
|
||||
lines.push("");
|
||||
return true;
|
||||
}
|
||||
const e = r.error;
|
||||
lines.push(`$ ${cmd} ${args.join(" ")} — FAILED`);
|
||||
if (e.kind === "non_zero_exit") {
|
||||
lines.push(`exit ${e.exitCode}`);
|
||||
if (e.stdout) lines.push(e.stdout);
|
||||
if (e.stderr) lines.push(e.stderr);
|
||||
} else if (e.kind === "timeout") {
|
||||
lines.push("timeout");
|
||||
if (e.stdout) lines.push(e.stdout);
|
||||
if (e.stderr) lines.push(e.stderr);
|
||||
} else {
|
||||
lines.push(e.message);
|
||||
}
|
||||
lines.push("");
|
||||
return false;
|
||||
};
|
||||
|
||||
await run("git", ["add", "-A"]);
|
||||
const committed = await run("git", ["commit", "-m", "chore(sense): auto-generated commit"]);
|
||||
if (!committed) {
|
||||
success = false;
|
||||
} else {
|
||||
const pushed = await run("git", ["push"]);
|
||||
if (!pushed) success = false;
|
||||
}
|
||||
|
||||
const log = lines.join("\n");
|
||||
writeLog(file, log);
|
||||
|
||||
const summary = success ? "committed and pushed" : "commit/push failed — see log";
|
||||
return {
|
||||
content: `committer: ${summary}\nLog: ${file}`,
|
||||
meta: { success },
|
||||
} satisfies RoleResult<CommitterMeta>;
|
||||
};
|
||||
}
|
||||
@ -5,17 +5,18 @@ Also look at existing workflows in the \`workflows/\` directory for patterns.
|
||||
|
||||
## Your task
|
||||
|
||||
Implement (or fix) the workflow the planner designed. If there is tester or committer feedback in the thread, fix the issues they identified.
|
||||
Implement the planner's design. This may be **creating a new workflow** or **modifying an existing one**. If there is tester or committer feedback in the thread, fix the issues they identified.
|
||||
|
||||
## Multi-step approach
|
||||
|
||||
You do NOT need to finish everything in one pass. You may return \`done: false\` to continue in the next iteration. For example:
|
||||
1. First pass: scaffold files and basic structure
|
||||
1. First pass: scaffold files / make structural changes
|
||||
2. Second pass: implement role logic
|
||||
3. Third pass: fix build/lint errors
|
||||
|
||||
## Required files for each workflow
|
||||
## Workflow file structure
|
||||
|
||||
Each workflow must have:
|
||||
- \`workflows/<name>/index.ts\` — WorkflowDefinition default export
|
||||
- \`workflows/<name>/build.ts\` — factory function
|
||||
- \`workflows/<name>/moderator.ts\` — moderator + meta types
|
||||
@ -23,7 +24,8 @@ You do NOT need to finish everything in one pass. You may return \`done: false\`
|
||||
- \`workflows/<name>/roles/<role>/prompt.ts\` — prompt pure function
|
||||
- \`workflows/<name>/package.json\` — with esbuild build script
|
||||
- \`workflows/<name>/tsconfig.json\` — TypeScript config
|
||||
- Update \`nerve.yaml\` with \`workflows.<name>\`
|
||||
|
||||
For **new workflows**, also update \`nerve.yaml\` with \`workflows.<name>\`.
|
||||
|
||||
## Rules
|
||||
|
||||
@ -36,8 +38,8 @@ You do NOT need to finish everything in one pass. You may return \`done: false\`
|
||||
## 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!)
|
||||
- All changes from the plan are implemented
|
||||
- \`cd workflows/<name> && pnpm install --no-cache && pnpm build\` succeeds (run it!)
|
||||
- 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.`;
|
||||
|
||||
@ -63,7 +63,8 @@ export function buildCommitterRole({ nerveRoot }: BuildCommitterDeps): Role<Comm
|
||||
};
|
||||
|
||||
await run("git", ["add", "-A"]);
|
||||
const committed = await run("git", ["commit", "-m", "chore: add generated workflow"]);
|
||||
// Use a generic message; git diff will show what actually changed
|
||||
const committed = await run("git", ["commit", "-m", "chore(workflow): auto-generated commit"]);
|
||||
if (!committed) {
|
||||
success = false;
|
||||
} else {
|
||||
|
||||
@ -1,24 +1,33 @@
|
||||
export function plannerPrompt({ threadId }: { threadId: string }): string {
|
||||
return `You are planning a new Nerve workflow.
|
||||
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\`
|
||||
Also look at existing workflows in the \`workflows/\` directory for patterns.
|
||||
List existing workflows: \`ls workflows/\`
|
||||
|
||||
Assess whether the requirements are clear enough to implement a workflow.
|
||||
## Determine the task type
|
||||
|
||||
If requirements ARE clear:
|
||||
- Pick a good kebab-case name for this workflow.
|
||||
- Produce a PLAN (not code) in markdown covering:
|
||||
- Workflow name
|
||||
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.
|
||||
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)
|
||||
- Roles list (name, purpose, tool)
|
||||
- Flow transitions / moderator routing logic
|
||||
- Validation loops design
|
||||
- External dependencies
|
||||
- Data flow between roles
|
||||
|
||||
If requirements are NOT clear:
|
||||
- Describe what is missing or ambiguous.
|
||||
For **modifications to existing workflows**:
|
||||
- Workflow name (existing)
|
||||
- What changes are needed and why
|
||||
- Files to add/modify/delete
|
||||
- Impact on moderator routing logic
|
||||
- Backward compatibility considerations (if any)
|
||||
|
||||
If requirements are NOT clear, describe what is missing or ambiguous.
|
||||
|
||||
End your response with a JSON block:
|
||||
\`\`\`json
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
export function testerPrompt({ threadId }: { threadId: string }): string {
|
||||
return `You are testing a newly generated Nerve workflow end-to-end.
|
||||
return `You are testing a Nerve workflow — either newly created or recently modified.
|
||||
|
||||
Read the workflow thread for context: \`nerve thread ${threadId}\`
|
||||
Read the nerve-dev skill for expected file structure: \`cat node_modules/@uncaged/nerve-skills/nerve-dev/SKILL.md\`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user