Compare commits
3 Commits
7432f80d61
...
436ccf12b3
| Author | SHA1 | Date | |
|---|---|---|---|
| 436ccf12b3 | |||
| 2f78c72e4e | |||
| dc1e96d8f3 |
@ -41,6 +41,6 @@ workflows:
|
|||||||
solve-issue:
|
solve-issue:
|
||||||
concurrency: 1
|
concurrency: 1
|
||||||
overflow: queue
|
overflow: queue
|
||||||
knowledge-extraction:
|
extract-knowledge:
|
||||||
concurrency: 1
|
concurrency: 1
|
||||||
overflow: queue
|
overflow: queue
|
||||||
|
|||||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@ -183,7 +183,7 @@ importers:
|
|||||||
specifier: ^5.7.0
|
specifier: ^5.7.0
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
|
||||||
workflows/knowledge-extraction:
|
workflows/extract-knowledge:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@uncaged/nerve-adapter-cursor':
|
'@uncaged/nerve-adapter-cursor':
|
||||||
specifier: link:../../../repos/nerve/packages/adapter-cursor
|
specifier: link:../../../repos/nerve/packages/adapter-cursor
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export function createKnowledgeExtractionWorkflow({
|
|||||||
const a = (role: keyof WorkflowMeta) => adapters?.[role] ?? defaultAdapter;
|
const a = (role: keyof WorkflowMeta) => adapters?.[role] ?? defaultAdapter;
|
||||||
const llmAdapter = createLlmAdapter(extract.provider);
|
const llmAdapter = createLlmAdapter(extract.provider);
|
||||||
return {
|
return {
|
||||||
name: "knowledge-extraction",
|
name: "extract-knowledge",
|
||||||
roles: {
|
roles: {
|
||||||
questioner: createQuestionerRole(adapters?.questioner ?? llmAdapter, { extract }),
|
questioner: createQuestionerRole(adapters?.questioner ?? llmAdapter, { extract }),
|
||||||
answerer: createAnswererRole(adapters?.answerer ?? llmAdapter, { extract }),
|
answerer: createAnswererRole(adapters?.answerer ?? llmAdapter, { extract }),
|
||||||
@ -43,7 +43,7 @@ export function explorerPrompt(ctx: ThreadContext): string {
|
|||||||
const unanswered =
|
const unanswered =
|
||||||
am?.results.filter((r) => !r.found).map((r) => r.id) ?? [];
|
am?.results.filter((r) => !r.found).map((r) => r.id) ?? [];
|
||||||
|
|
||||||
return `You are the **explorer** in a knowledge-extraction workflow.
|
return `You are the **explorer** in an extract-knowledge workflow.
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ export type CreateQuestionerRoleDeps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function questionerSystem(): string {
|
function questionerSystem(): string {
|
||||||
return `You are the **questioner** in a knowledge-extraction workflow.
|
return `You are the **questioner** in an extract-knowledge workflow.
|
||||||
|
|
||||||
Read the given markdown knowledge card. Propose exactly **five** technical questions that are **not** already answered or covered by that card.
|
Read the given markdown knowledge card. Propose exactly **five** technical questions that are **not** already answered or covered by that card.
|
||||||
|
|
||||||
@ -3,14 +3,14 @@ import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|||||||
|
|
||||||
import { moderator } from "./moderator.js";
|
import { moderator } from "./moderator.js";
|
||||||
import type { WorkflowMeta } from "./moderator.js";
|
import type { WorkflowMeta } from "./moderator.js";
|
||||||
import { createCommitterRole } from "./roles/committer/index.js";
|
import { createCommitterRole } from "./roles/committer.js";
|
||||||
import { createImplementRole } from "./roles/implement/index.js";
|
import { createImplementRole } from "./roles/implement.js";
|
||||||
import { createPlanRole } from "./roles/plan/index.js";
|
import { createPlanRole } from "./roles/plan.js";
|
||||||
import { createPrepareRole } from "./roles/prepare/index.js";
|
import { createPrepareRole } from "./roles/prepare.js";
|
||||||
import { createPublishRole } from "./roles/publish/index.js";
|
import { createPublishRole } from "./roles/publish.js";
|
||||||
import { createReadIssueRole } from "./roles/read-issue/index.js";
|
import { createReadIssueRole } from "./roles/read-issue.js";
|
||||||
import { createReviewRole } from "./roles/review/index.js";
|
import { createReviewRole } from "./roles/review.js";
|
||||||
import { createTestRole } from "./roles/test/index.js";
|
import { createTestRole } from "./roles/test.js";
|
||||||
|
|
||||||
export type CreateSolveIssueDeps = {
|
export type CreateSolveIssueDeps = {
|
||||||
defaultAdapter: AgentFn;
|
defaultAdapter: AgentFn;
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { END } from "@uncaged/nerve-core";
|
import { END } from "@uncaged/nerve-core";
|
||||||
import type { Moderator } from "@uncaged/nerve-core";
|
import type { Moderator } from "@uncaged/nerve-core";
|
||||||
import type { ReadIssueMeta } from "./roles/read-issue/index.js";
|
import type { ReadIssueMeta } from "./roles/read-issue.js";
|
||||||
import type { PrepareMeta } from "./roles/prepare/index.js";
|
import type { PrepareMeta } from "./roles/prepare.js";
|
||||||
import type { PlanMeta } from "./roles/plan/index.js";
|
import type { PlanMeta } from "./roles/plan.js";
|
||||||
import type { ImplementMeta } from "./roles/implement/index.js";
|
import type { ImplementMeta } from "./roles/implement.js";
|
||||||
import type { CommitterMeta } from "./roles/committer/index.js";
|
import type { CommitterMeta } from "./roles/committer.js";
|
||||||
import type { ReviewMeta } from "./roles/review/index.js";
|
import type { ReviewMeta } from "./roles/review.js";
|
||||||
import type { TestMeta } from "./roles/test/index.js";
|
import type { TestMeta } from "./roles/test.js";
|
||||||
import type { PublishMeta } from "./roles/publish/index.js";
|
import type { PublishMeta } from "./roles/publish.js";
|
||||||
|
|
||||||
export type WorkflowMeta = {
|
export type WorkflowMeta = {
|
||||||
"read-issue": ReadIssueMeta;
|
"read-issue": ReadIssueMeta;
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
export function committerPrompt({ threadId }: { threadId: string }): string {
|
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
||||||
|
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { createRole, decorateRole, withDryRun, onFail } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
function committerPrompt({ threadId }: { threadId: string }): string {
|
||||||
return `You are the committer agent. The **implement** step finished with a passing build; your job is to branch, commit, and push.
|
return `You are the committer agent. The **implement** step finished with a passing build; your job is to branch, commit, and push.
|
||||||
|
|
||||||
1. Read the workflow thread: \`nerve thread show ${threadId}\` — understand what was planned, implemented, and reviewed.
|
1. Read the workflow thread: \`nerve thread show ${threadId}\` — understand what was planned, implemented, and reviewed.
|
||||||
@ -26,3 +31,27 @@ or
|
|||||||
{ "committed": false }
|
{ "committed": false }
|
||||||
\`\`\``;
|
\`\`\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const committerMetaSchema = z.object({
|
||||||
|
committed: z
|
||||||
|
.boolean()
|
||||||
|
.describe("true if branch created, changes committed, and pushed successfully"),
|
||||||
|
});
|
||||||
|
export type CommitterMeta = z.infer<typeof committerMetaSchema>;
|
||||||
|
|
||||||
|
export function createCommitterRole(
|
||||||
|
adapter: AgentFn,
|
||||||
|
extract: LlmExtractorConfig,
|
||||||
|
): Role<CommitterMeta> {
|
||||||
|
const inner = createRole(
|
||||||
|
adapter,
|
||||||
|
async (ctx: ThreadContext) => committerPrompt({ threadId: ctx.start.meta.threadId }),
|
||||||
|
committerMetaSchema,
|
||||||
|
extract,
|
||||||
|
);
|
||||||
|
|
||||||
|
return decorateRole(inner, [
|
||||||
|
withDryRun({ label: "committer", meta: { committed: true } as CommitterMeta }),
|
||||||
|
onFail({ label: "committer", meta: { committed: false } as CommitterMeta }),
|
||||||
|
]) as Role<CommitterMeta>;
|
||||||
|
}
|
||||||
@ -1,30 +0,0 @@
|
|||||||
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
|
||||||
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { createRole, decorateRole, withDryRun, onFail } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { committerPrompt } from "./prompt.js";
|
|
||||||
|
|
||||||
export const committerMetaSchema = z.object({
|
|
||||||
committed: z
|
|
||||||
.boolean()
|
|
||||||
.describe("true if branch created, changes committed, and pushed successfully"),
|
|
||||||
});
|
|
||||||
export type CommitterMeta = z.infer<typeof committerMetaSchema>;
|
|
||||||
|
|
||||||
export function createCommitterRole(
|
|
||||||
adapter: AgentFn,
|
|
||||||
extract: LlmExtractorConfig,
|
|
||||||
): Role<CommitterMeta> {
|
|
||||||
const inner = createRole(
|
|
||||||
adapter,
|
|
||||||
async (ctx: ThreadContext) => committerPrompt({ threadId: ctx.start.meta.threadId }),
|
|
||||||
committerMetaSchema,
|
|
||||||
extract,
|
|
||||||
);
|
|
||||||
|
|
||||||
return decorateRole(inner, [
|
|
||||||
withDryRun({ label: "committer", meta: { committed: true } as CommitterMeta }),
|
|
||||||
onFail({ label: "committer", meta: { committed: false } as CommitterMeta }),
|
|
||||||
]) as Role<CommitterMeta>;
|
|
||||||
}
|
|
||||||
@ -3,8 +3,33 @@ import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|||||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { resolveRepoCwd } from "../../lib/repo-context.js";
|
import { resolveRepoCwd } from "../lib/repo-context.js";
|
||||||
import { buildImplementPrompt } from "./prompt.js";
|
|
||||||
|
function buildImplementPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
||||||
|
return `You are the **implement** agent. You apply code changes for the issue.
|
||||||
|
|
||||||
|
Read workflow context (plan, reviewer/test feedback): \`nerve thread show ${threadId}\`
|
||||||
|
|
||||||
|
Read Nerve workspace conventions: \`cat ${nerveRoot}/CONVENTIONS.md\`
|
||||||
|
|
||||||
|
Your cwd is the target repository.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
1. Implement the planned changes; address reviewer/tester feedback from the thread if any.
|
||||||
|
2. Run the project **build** (\`pnpm build\`, \`npm run build\`, etc.) and fix issues until build passes.
|
||||||
|
3. Multi-step: if you cannot finish this round, explain why and set **done** to false.
|
||||||
|
|
||||||
|
Do **not** run \`git checkout -b\`, \`git add\`, \`git commit\`, or \`git push\`. **Never** create commits on any branch — branching and commits are handled by the **committer** step after you finish.
|
||||||
|
|
||||||
|
Then close with JSON:
|
||||||
|
\`\`\`json
|
||||||
|
{ "done": true }
|
||||||
|
\`\`\`
|
||||||
|
or \`{ "done": false }\` matching whether implementation is complete.
|
||||||
|
|
||||||
|
**done=true** only when changes are complete **and** build passes in this round.`;
|
||||||
|
}
|
||||||
|
|
||||||
export const implementMetaSchema = z.object({
|
export const implementMetaSchema = z.object({
|
||||||
done: z.boolean().describe("true when changes are complete and build passes this round"),
|
done: z.boolean().describe("true when changes are complete and build passes this round"),
|
||||||
@ -1,25 +0,0 @@
|
|||||||
export function buildImplementPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
|
||||||
return `You are the **implement** agent. You apply code changes for the issue.
|
|
||||||
|
|
||||||
Read workflow context (plan, reviewer/test feedback): \`nerve thread show ${threadId}\`
|
|
||||||
|
|
||||||
Read Nerve workspace conventions: \`cat ${nerveRoot}/CONVENTIONS.md\`
|
|
||||||
|
|
||||||
Your cwd is the target repository.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
1. Implement the planned changes; address reviewer/tester feedback from the thread if any.
|
|
||||||
2. Run the project **build** (\`pnpm build\`, \`npm run build\`, etc.) and fix issues until build passes.
|
|
||||||
3. Multi-step: if you cannot finish this round, explain why and set **done** to false.
|
|
||||||
|
|
||||||
Do **not** run \`git checkout -b\`, \`git add\`, \`git commit\`, or \`git push\`. **Never** create commits on any branch — branching and commits are handled by the **committer** step after you finish.
|
|
||||||
|
|
||||||
Then close with JSON:
|
|
||||||
\`\`\`json
|
|
||||||
{ "done": true }
|
|
||||||
\`\`\`
|
|
||||||
or \`{ "done": false }\` matching whether implementation is complete.
|
|
||||||
|
|
||||||
**done=true** only when changes are complete **and** build passes in this round.`;
|
|
||||||
}
|
|
||||||
@ -3,8 +3,35 @@ import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|||||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { resolveRepoCwd } from "../../lib/repo-context.js";
|
import { resolveRepoCwd } from "../lib/repo-context.js";
|
||||||
import { buildPlanPrompt } from "./prompt.js";
|
|
||||||
|
function buildPlanPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
||||||
|
return `You are the **plan** agent (analysis only — ask mode). You produce an implementation plan for fixing the issue.
|
||||||
|
|
||||||
|
Read workflow context: \`nerve thread show ${threadId}\`
|
||||||
|
|
||||||
|
Read Nerve workspace conventions (coding rules for agents): \`cat ${nerveRoot}/CONVENTIONS.md\`
|
||||||
|
|
||||||
|
In the **target repository** (your cwd), skim relevant files and read \`CONVENTIONS.md\` **if it exists** there.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
Write an implementation plan in **markdown** with:
|
||||||
|
|
||||||
|
1. Problem understanding
|
||||||
|
2. Change strategy
|
||||||
|
3. Target files (paths)
|
||||||
|
4. **Test commands** to run (explicit shell commands, e.g. \`pnpm test\`, \`pnpm vitest run\`)
|
||||||
|
5. Risks
|
||||||
|
|
||||||
|
End your reply with a JSON code block (meta signal):
|
||||||
|
\`\`\`json
|
||||||
|
{ "ready": true }
|
||||||
|
\`\`\`
|
||||||
|
Use \`{ "ready": false }\` if the plan cannot be made actionable.
|
||||||
|
|
||||||
|
**ready=true** only when the plan is clear and actionable.`;
|
||||||
|
}
|
||||||
|
|
||||||
export const planMetaSchema = z.object({
|
export const planMetaSchema = z.object({
|
||||||
ready: z.boolean().describe("true if plan is clear and actionable"),
|
ready: z.boolean().describe("true if plan is clear and actionable"),
|
||||||
@ -1,27 +0,0 @@
|
|||||||
export function buildPlanPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
|
||||||
return `You are the **plan** agent (analysis only — ask mode). You produce an implementation plan for fixing the issue.
|
|
||||||
|
|
||||||
Read workflow context: \`nerve thread show ${threadId}\`
|
|
||||||
|
|
||||||
Read Nerve workspace conventions (coding rules for agents): \`cat ${nerveRoot}/CONVENTIONS.md\`
|
|
||||||
|
|
||||||
In the **target repository** (your cwd), skim relevant files and read \`CONVENTIONS.md\` **if it exists** there.
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
Write an implementation plan in **markdown** with:
|
|
||||||
|
|
||||||
1. Problem understanding
|
|
||||||
2. Change strategy
|
|
||||||
3. Target files (paths)
|
|
||||||
4. **Test commands** to run (explicit shell commands, e.g. \`pnpm test\`, \`pnpm vitest run\`)
|
|
||||||
5. Risks
|
|
||||||
|
|
||||||
End your reply with a JSON code block (meta signal):
|
|
||||||
\`\`\`json
|
|
||||||
{ "ready": true }
|
|
||||||
\`\`\`
|
|
||||||
Use \`{ "ready": false }\` if the plan cannot be made actionable.
|
|
||||||
|
|
||||||
**ready=true** only when the plan is clear and actionable.`;
|
|
||||||
}
|
|
||||||
@ -1,4 +1,9 @@
|
|||||||
export function preparePrompt({ threadId }: { threadId: string }): string {
|
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
||||||
|
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
function preparePrompt({ threadId }: { threadId: string }): string {
|
||||||
return `You are the **prepare** agent. You ensure the target repository is ready for work.
|
return `You are the **prepare** agent. You ensure the target repository is ready for work.
|
||||||
|
|
||||||
Read prior messages / thread for issue markers: \`nerve thread show ${threadId}\`
|
Read prior messages / thread for issue markers: \`nerve thread show ${threadId}\`
|
||||||
@ -52,3 +57,17 @@ or \`{ "ready": false }\` if the repo is invalid, or install/build baseline fail
|
|||||||
|
|
||||||
**ready=true** only when the repo exists at \`path\`, is clean, dependencies installed, and baseline build succeeded (or no build script).`;
|
**ready=true** only when the repo exists at \`path\`, is clean, dependencies installed, and baseline build succeeded (or no build script).`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const prepareMetaSchema = z.object({
|
||||||
|
ready: z.boolean().describe("true if repo is ready and baseline build ok"),
|
||||||
|
});
|
||||||
|
export type PrepareMeta = z.infer<typeof prepareMetaSchema>;
|
||||||
|
|
||||||
|
export function createPrepareRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<PrepareMeta> {
|
||||||
|
return createRole(
|
||||||
|
adapter,
|
||||||
|
async (ctx: ThreadContext) => preparePrompt({ threadId: ctx.start.meta.threadId }),
|
||||||
|
prepareMetaSchema,
|
||||||
|
extract,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
|
||||||
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { preparePrompt } from "./prompt.js";
|
|
||||||
|
|
||||||
export const prepareMetaSchema = z.object({
|
|
||||||
ready: z.boolean().describe("true if repo is ready and baseline build ok"),
|
|
||||||
});
|
|
||||||
export type PrepareMeta = z.infer<typeof prepareMetaSchema>;
|
|
||||||
|
|
||||||
export function createPrepareRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<PrepareMeta> {
|
|
||||||
return createRole(
|
|
||||||
adapter,
|
|
||||||
async (ctx: ThreadContext) => preparePrompt({ threadId: ctx.start.meta.threadId }),
|
|
||||||
prepareMetaSchema,
|
|
||||||
extract,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,4 +1,11 @@
|
|||||||
export function buildPublishPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
import { mkdirSync, writeFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import type { AgentFn, Role, RoleResult, ThreadContext } from "@uncaged/nerve-core";
|
||||||
|
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
function buildPublishPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
||||||
return `You are the **publish** agent (Hermes). Test has passed. Open a pull request for the current branch using the **tea** CLI.
|
return `You are the **publish** agent (Hermes). Test has passed. Open a pull request for the current branch using the **tea** CLI.
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
@ -40,3 +47,64 @@ or
|
|||||||
{ "success": false }
|
{ "success": false }
|
||||||
\`\`\``;
|
\`\`\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const publishMetaSchema = z.object({
|
||||||
|
success: z.boolean().describe("true if git push and tea pr create both succeeded"),
|
||||||
|
});
|
||||||
|
export type PublishMeta = z.infer<typeof publishMetaSchema>;
|
||||||
|
|
||||||
|
export type CreatePublishRoleDeps = {
|
||||||
|
extract: LlmExtractorConfig;
|
||||||
|
nerveRoot: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function logPath(nerveRoot: string): string {
|
||||||
|
return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPublishRole(
|
||||||
|
adapter: AgentFn,
|
||||||
|
{ extract, nerveRoot }: CreatePublishRoleDeps,
|
||||||
|
): Role<PublishMeta> {
|
||||||
|
const innerRole = createRole(
|
||||||
|
adapter,
|
||||||
|
async (ctx: ThreadContext) =>
|
||||||
|
buildPublishPrompt({ threadId: ctx.start.meta.threadId, nerveRoot }),
|
||||||
|
publishMetaSchema,
|
||||||
|
extract,
|
||||||
|
);
|
||||||
|
|
||||||
|
return async (ctx: ThreadContext): Promise<RoleResult<PublishMeta>> => {
|
||||||
|
const file = logPath(nerveRoot);
|
||||||
|
mkdirSync(join(file, ".."), { recursive: true });
|
||||||
|
|
||||||
|
if (isDryRun(ctx.start)) {
|
||||||
|
const msg = "[dry-run] publish skipped (no git push / PR)";
|
||||||
|
writeFileSync(file, `${msg}\n`, "utf-8");
|
||||||
|
return {
|
||||||
|
content: `[dry-run] publish skipped — log: ${file}`,
|
||||||
|
meta: { success: true },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const innerCtx: ThreadContext = {
|
||||||
|
...ctx,
|
||||||
|
start: {
|
||||||
|
...ctx.start,
|
||||||
|
meta: { ...ctx.start.meta, workdir: nerveRoot },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await innerRole(innerCtx);
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
|
const body = `publish failed: ${msg}\n`;
|
||||||
|
writeFileSync(file, body, "utf-8");
|
||||||
|
return {
|
||||||
|
content: `publish failed: ${msg}\nLog: ${file}`,
|
||||||
|
meta: { success: false },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,69 +0,0 @@
|
|||||||
import { mkdirSync, writeFileSync } from "node:fs";
|
|
||||||
import { join } from "node:path";
|
|
||||||
import type { AgentFn, Role, RoleResult, ThreadContext, WorkflowMessage } from "@uncaged/nerve-core";
|
|
||||||
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { createRole, isDryRun } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { buildPublishPrompt } from "./prompt.js";
|
|
||||||
|
|
||||||
export const publishMetaSchema = z.object({
|
|
||||||
success: z.boolean().describe("true if git push and tea pr create both succeeded"),
|
|
||||||
});
|
|
||||||
export type PublishMeta = z.infer<typeof publishMetaSchema>;
|
|
||||||
|
|
||||||
export type CreatePublishRoleDeps = {
|
|
||||||
extract: LlmExtractorConfig;
|
|
||||||
nerveRoot: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function logPath(nerveRoot: string): string {
|
|
||||||
return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createPublishRole(
|
|
||||||
adapter: AgentFn,
|
|
||||||
{ extract, nerveRoot }: CreatePublishRoleDeps,
|
|
||||||
): Role<PublishMeta> {
|
|
||||||
const innerRole = createRole(
|
|
||||||
adapter,
|
|
||||||
async (ctx: ThreadContext) =>
|
|
||||||
buildPublishPrompt({ threadId: ctx.start.meta.threadId, nerveRoot }),
|
|
||||||
publishMetaSchema,
|
|
||||||
extract,
|
|
||||||
);
|
|
||||||
|
|
||||||
return async (ctx: ThreadContext): Promise<RoleResult<PublishMeta>> => {
|
|
||||||
const file = logPath(nerveRoot);
|
|
||||||
mkdirSync(join(file, ".."), { recursive: true });
|
|
||||||
|
|
||||||
if (isDryRun(ctx.start)) {
|
|
||||||
const msg = "[dry-run] publish skipped (no git push / PR)";
|
|
||||||
writeFileSync(file, `${msg}\n`, "utf-8");
|
|
||||||
return {
|
|
||||||
content: `[dry-run] publish skipped — log: ${file}`,
|
|
||||||
meta: { success: true },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const innerCtx: ThreadContext = {
|
|
||||||
...ctx,
|
|
||||||
start: {
|
|
||||||
...ctx.start,
|
|
||||||
meta: { ...ctx.start.meta, workdir: nerveRoot },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await innerRole(innerCtx);
|
|
||||||
} catch (e) {
|
|
||||||
const msg = e instanceof Error ? e.message : String(e);
|
|
||||||
const body = `publish failed: ${msg}\n`;
|
|
||||||
writeFileSync(file, body, "utf-8");
|
|
||||||
return {
|
|
||||||
content: `publish failed: ${msg}\nLog: ${file}`,
|
|
||||||
meta: { success: false },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,4 +1,9 @@
|
|||||||
export function readIssuePrompt({ threadId }: { threadId: string }): string {
|
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
||||||
|
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
function readIssuePrompt({ threadId }: { threadId: string }): string {
|
||||||
return `You are the **read-issue** agent. You fetch Gitea issue content via the \`tea\` CLI.
|
return `You are the **read-issue** agent. You fetch Gitea issue content via the \`tea\` CLI.
|
||||||
|
|
||||||
Read the workflow thread start prompt for the issue URL (same run): \`nerve thread show ${threadId}\`
|
Read the workflow thread start prompt for the issue URL (same run): \`nerve thread show ${threadId}\`
|
||||||
@ -32,3 +37,17 @@ Use \`{ "ready": false }\` if you could not fetch or parse the issue.
|
|||||||
|
|
||||||
**ready=true** only if the issue was fetched successfully and the marker block is correct.`;
|
**ready=true** only if the issue was fetched successfully and the marker block is correct.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const readIssueMetaSchema = z.object({
|
||||||
|
ready: z.boolean().describe("true if issue content was fetched and markers are present"),
|
||||||
|
});
|
||||||
|
export type ReadIssueMeta = z.infer<typeof readIssueMetaSchema>;
|
||||||
|
|
||||||
|
export function createReadIssueRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<ReadIssueMeta> {
|
||||||
|
return createRole(
|
||||||
|
adapter,
|
||||||
|
async (ctx: ThreadContext) => readIssuePrompt({ threadId: ctx.start.meta.threadId }),
|
||||||
|
readIssueMetaSchema,
|
||||||
|
extract,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
|
||||||
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { readIssuePrompt } from "./prompt.js";
|
|
||||||
|
|
||||||
export const readIssueMetaSchema = z.object({
|
|
||||||
ready: z.boolean().describe("true if issue content was fetched and markers are present"),
|
|
||||||
});
|
|
||||||
export type ReadIssueMeta = z.infer<typeof readIssueMetaSchema>;
|
|
||||||
|
|
||||||
export function createReadIssueRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<ReadIssueMeta> {
|
|
||||||
return createRole(
|
|
||||||
adapter,
|
|
||||||
async (ctx: ThreadContext) => readIssuePrompt({ threadId: ctx.start.meta.threadId }),
|
|
||||||
readIssueMetaSchema,
|
|
||||||
extract,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,4 +1,9 @@
|
|||||||
export function reviewPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
||||||
|
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
function reviewPrompt({ threadId, nerveRoot }: { threadId: string; nerveRoot: string }): string {
|
||||||
return `You are a **code reviewer** (Hermes). You run after implement and before test.
|
return `You are a **code reviewer** (Hermes). You run after implement and before test.
|
||||||
|
|
||||||
Read Nerve workspace conventions: \`cat ${nerveRoot}/CONVENTIONS.md\`
|
Read Nerve workspace conventions: \`cat ${nerveRoot}/CONVENTIONS.md\`
|
||||||
@ -33,3 +38,22 @@ or
|
|||||||
{ "approved": false }
|
{ "approved": false }
|
||||||
\`\`\``;
|
\`\`\``;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const reviewMetaSchema = z.object({
|
||||||
|
approved: z.boolean().describe("true if diff is clean and ready for tests"),
|
||||||
|
});
|
||||||
|
export type ReviewMeta = z.infer<typeof reviewMetaSchema>;
|
||||||
|
|
||||||
|
export function createReviewRole(
|
||||||
|
adapter: AgentFn,
|
||||||
|
extract: LlmExtractorConfig,
|
||||||
|
nerveRoot: string,
|
||||||
|
): Role<ReviewMeta> {
|
||||||
|
return createRole(
|
||||||
|
adapter,
|
||||||
|
async (ctx: ThreadContext) =>
|
||||||
|
reviewPrompt({ threadId: ctx.start.meta.threadId, nerveRoot }),
|
||||||
|
reviewMetaSchema,
|
||||||
|
extract,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,25 +0,0 @@
|
|||||||
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
|
||||||
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { reviewPrompt } from "./prompt.js";
|
|
||||||
|
|
||||||
export const reviewMetaSchema = z.object({
|
|
||||||
approved: z.boolean().describe("true if diff is clean and ready for tests"),
|
|
||||||
});
|
|
||||||
export type ReviewMeta = z.infer<typeof reviewMetaSchema>;
|
|
||||||
|
|
||||||
export function createReviewRole(
|
|
||||||
adapter: AgentFn,
|
|
||||||
extract: LlmExtractorConfig,
|
|
||||||
nerveRoot: string,
|
|
||||||
): Role<ReviewMeta> {
|
|
||||||
return createRole(
|
|
||||||
adapter,
|
|
||||||
async (ctx: ThreadContext) =>
|
|
||||||
reviewPrompt({ threadId: ctx.start.meta.threadId, nerveRoot }),
|
|
||||||
reviewMetaSchema,
|
|
||||||
extract,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,4 +1,9 @@
|
|||||||
export function testPrompt({ threadId }: { threadId: string }): string {
|
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
||||||
|
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { createRole } from "@uncaged/nerve-workflow-utils";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
function testPrompt({ threadId }: { threadId: string }): string {
|
||||||
return `You are the **test** agent (Hermes). You execute automated tests for the change.
|
return `You are the **test** agent (Hermes). You execute automated tests for the change.
|
||||||
|
|
||||||
Read workflow context: \`nerve thread show ${threadId}\`
|
Read workflow context: \`nerve thread show ${threadId}\`
|
||||||
@ -19,3 +24,17 @@ or \`{ "passed": false }\`
|
|||||||
|
|
||||||
**passed=true** only if every executed command exited 0 (or skip was justified with no failing command).`;
|
**passed=true** only if every executed command exited 0 (or skip was justified with no failing command).`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const testMetaSchema = z.object({
|
||||||
|
passed: z.boolean().describe("true if all test commands passed"),
|
||||||
|
});
|
||||||
|
export type TestMeta = z.infer<typeof testMetaSchema>;
|
||||||
|
|
||||||
|
export function createTestRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<TestMeta> {
|
||||||
|
return createRole(
|
||||||
|
adapter,
|
||||||
|
async (ctx: ThreadContext) => testPrompt({ threadId: ctx.start.meta.threadId }),
|
||||||
|
testMetaSchema,
|
||||||
|
extract,
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core";
|
|
||||||
import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { createRole } from "@uncaged/nerve-workflow-utils";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { testPrompt } from "./prompt.js";
|
|
||||||
|
|
||||||
export const testMetaSchema = z.object({
|
|
||||||
passed: z.boolean().describe("true if all test commands passed"),
|
|
||||||
});
|
|
||||||
export type TestMeta = z.infer<typeof testMetaSchema>;
|
|
||||||
|
|
||||||
export function createTestRole(adapter: AgentFn, extract: LlmExtractorConfig): Role<TestMeta> {
|
|
||||||
return createRole(
|
|
||||||
adapter,
|
|
||||||
async (ctx: ThreadContext) => testPrompt({ threadId: ctx.start.meta.threadId }),
|
|
||||||
testMetaSchema,
|
|
||||||
extract,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user