agentweaver 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -14
- package/dist/artifacts.js +86 -3
- package/dist/executors/verify-build-executor.js +110 -9
- package/dist/index.js +170 -18
- package/dist/interactive-ui.js +525 -33
- package/dist/pipeline/checks.js +5 -0
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/declarative-flow-runner.js +16 -0
- package/dist/pipeline/flow-specs/auto.json +191 -3
- package/dist/pipeline/flow-specs/bug-analyze.json +140 -0
- package/dist/pipeline/flow-specs/bug-fix.json +44 -0
- package/dist/pipeline/flow-specs/implement.json +12 -0
- package/dist/pipeline/flow-specs/mr-description.json +89 -0
- package/dist/pipeline/flow-specs/plan.json +52 -0
- package/dist/pipeline/flow-specs/preflight.json +32 -0
- package/dist/pipeline/flow-specs/review-fix.json +79 -1
- package/dist/pipeline/flow-specs/review.json +79 -0
- package/dist/pipeline/flow-specs/run-linter-loop.json +149 -0
- package/dist/pipeline/flow-specs/run-tests-loop.json +149 -0
- package/dist/pipeline/flow-specs/task-describe.json +89 -0
- package/dist/pipeline/node-registry.js +19 -0
- package/dist/pipeline/nodes/flow-run-node.js +40 -0
- package/dist/pipeline/nodes/review-findings-form-node.js +65 -0
- package/dist/pipeline/nodes/user-input-node.js +93 -0
- package/dist/pipeline/nodes/verify-build-node.js +1 -0
- package/dist/pipeline/prompt-registry.js +6 -1
- package/dist/pipeline/spec-compiler.js +13 -0
- package/dist/pipeline/spec-validator.js +12 -0
- package/dist/pipeline/value-resolver.js +49 -4
- package/dist/prompts.js +46 -14
- package/dist/structured-artifacts.js +272 -0
- package/dist/user-input.js +171 -0
- package/package.json +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "task-describe-flow",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"phases": [
|
|
5
|
+
{
|
|
6
|
+
"id": "task_describe",
|
|
7
|
+
"steps": [
|
|
8
|
+
{
|
|
9
|
+
"id": "run_codex_task_describe",
|
|
10
|
+
"node": "codex-local-prompt",
|
|
11
|
+
"prompt": {
|
|
12
|
+
"inlineTemplate": "Посмотри задачу в {jira_task_file}. Проанализируй код и оформи краткое описание для Jira, без лишних подробностей. Сначала запиши source-of-truth JSON в {jira_description_json_file} в виде объекта { summary: string }, затем markdown-версию в {jira_description_file}.",
|
|
13
|
+
"vars": {
|
|
14
|
+
"jira_task_file": {
|
|
15
|
+
"artifact": {
|
|
16
|
+
"kind": "jira-task-file",
|
|
17
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"jira_description_file": {
|
|
21
|
+
"artifact": {
|
|
22
|
+
"kind": "jira-description-file",
|
|
23
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"jira_description_json_file": {
|
|
27
|
+
"artifact": {
|
|
28
|
+
"kind": "jira-description-json-file",
|
|
29
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"extraPrompt": { "ref": "params.extraPrompt" },
|
|
34
|
+
"format": "task-prompt"
|
|
35
|
+
},
|
|
36
|
+
"params": {
|
|
37
|
+
"labelText": { "const": "Running Codex task description mode" },
|
|
38
|
+
"model": { "const": "gpt-5.4" },
|
|
39
|
+
"requiredArtifacts": {
|
|
40
|
+
"list": [
|
|
41
|
+
{
|
|
42
|
+
"artifact": {
|
|
43
|
+
"kind": "jira-description-file",
|
|
44
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"artifact": {
|
|
49
|
+
"kind": "jira-description-json-file",
|
|
50
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"expect": [
|
|
57
|
+
{
|
|
58
|
+
"kind": "require-file",
|
|
59
|
+
"when": { "not": { "ref": "context.dryRun" } },
|
|
60
|
+
"path": {
|
|
61
|
+
"artifact": {
|
|
62
|
+
"kind": "jira-description-file",
|
|
63
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"message": "Task describe mode did not produce the Jira description artifact."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"kind": "require-structured-artifacts",
|
|
70
|
+
"when": { "not": { "ref": "context.dryRun" } },
|
|
71
|
+
"items": [
|
|
72
|
+
{
|
|
73
|
+
"path": {
|
|
74
|
+
"artifact": {
|
|
75
|
+
"kind": "jira-description-json-file",
|
|
76
|
+
"taskKey": { "ref": "params.taskKey" }
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"schemaId": "jira-description/v1"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"message": "Task describe mode produced invalid structured artifacts."
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
@@ -4,11 +4,14 @@ import { codexDockerPromptNode } from "./nodes/codex-docker-prompt-node.js";
|
|
|
4
4
|
import { codexLocalPromptNode } from "./nodes/codex-local-prompt-node.js";
|
|
5
5
|
import { commandCheckNode } from "./nodes/command-check-node.js";
|
|
6
6
|
import { fileCheckNode } from "./nodes/file-check-node.js";
|
|
7
|
+
import { flowRunNode } from "./nodes/flow-run-node.js";
|
|
7
8
|
import { jiraFetchNode } from "./nodes/jira-fetch-node.js";
|
|
8
9
|
import { planCodexNode } from "./nodes/plan-codex-node.js";
|
|
9
10
|
import { reviewClaudeNode } from "./nodes/review-claude-node.js";
|
|
11
|
+
import { reviewFindingsFormNode } from "./nodes/review-findings-form-node.js";
|
|
10
12
|
import { reviewReplyCodexNode } from "./nodes/review-reply-codex-node.js";
|
|
11
13
|
import { summaryFileLoadNode } from "./nodes/summary-file-load-node.js";
|
|
14
|
+
import { userInputNode } from "./nodes/user-input-node.js";
|
|
12
15
|
import { verifyBuildNode } from "./nodes/verify-build-node.js";
|
|
13
16
|
const builtInNodes = {
|
|
14
17
|
"build-failure-summary": buildFailureSummaryNode,
|
|
@@ -17,11 +20,14 @@ const builtInNodes = {
|
|
|
17
20
|
"codex-local-prompt": codexLocalPromptNode,
|
|
18
21
|
"command-check": commandCheckNode,
|
|
19
22
|
"file-check": fileCheckNode,
|
|
23
|
+
"flow-run": flowRunNode,
|
|
20
24
|
"jira-fetch": jiraFetchNode,
|
|
21
25
|
"plan-codex": planCodexNode,
|
|
22
26
|
"review-claude": reviewClaudeNode,
|
|
27
|
+
"review-findings-form": reviewFindingsFormNode,
|
|
23
28
|
"review-reply-codex": reviewReplyCodexNode,
|
|
24
29
|
"summary-file-load": summaryFileLoadNode,
|
|
30
|
+
"user-input": userInputNode,
|
|
25
31
|
"verify-build": verifyBuildNode,
|
|
26
32
|
};
|
|
27
33
|
const builtInNodeMetadata = {
|
|
@@ -36,6 +42,7 @@ const builtInNodeMetadata = {
|
|
|
36
42
|
"codex-local-prompt": { kind: "codex-local-prompt", version: 1, prompt: "required", requiredParams: ["labelText"] },
|
|
37
43
|
"command-check": { kind: "command-check", version: 1, prompt: "forbidden", requiredParams: ["commands"] },
|
|
38
44
|
"file-check": { kind: "file-check", version: 1, prompt: "forbidden", requiredParams: ["path"] },
|
|
45
|
+
"flow-run": { kind: "flow-run", version: 1, prompt: "forbidden", requiredParams: ["fileName"] },
|
|
39
46
|
"jira-fetch": { kind: "jira-fetch", version: 1, prompt: "forbidden", requiredParams: ["jiraApiUrl", "outputFile"] },
|
|
40
47
|
"plan-codex": { kind: "plan-codex", version: 1, prompt: "forbidden", requiredParams: ["prompt", "requiredArtifacts"] },
|
|
41
48
|
"review-claude": {
|
|
@@ -44,6 +51,12 @@ const builtInNodeMetadata = {
|
|
|
44
51
|
prompt: "forbidden",
|
|
45
52
|
requiredParams: ["jiraTaskFile", "taskKey", "iteration", "claudeCmd"],
|
|
46
53
|
},
|
|
54
|
+
"review-findings-form": {
|
|
55
|
+
kind: "review-findings-form",
|
|
56
|
+
version: 1,
|
|
57
|
+
prompt: "forbidden",
|
|
58
|
+
requiredParams: ["reviewJsonFile", "formId", "title"],
|
|
59
|
+
},
|
|
47
60
|
"review-reply-codex": {
|
|
48
61
|
kind: "review-reply-codex",
|
|
49
62
|
version: 1,
|
|
@@ -51,6 +64,12 @@ const builtInNodeMetadata = {
|
|
|
51
64
|
requiredParams: ["jiraTaskFile", "taskKey", "iteration", "codexCmd"],
|
|
52
65
|
},
|
|
53
66
|
"summary-file-load": { kind: "summary-file-load", version: 1, prompt: "forbidden", requiredParams: ["path"] },
|
|
67
|
+
"user-input": {
|
|
68
|
+
kind: "user-input",
|
|
69
|
+
version: 1,
|
|
70
|
+
prompt: "forbidden",
|
|
71
|
+
requiredParams: ["formId", "title", "fields", "outputFile"],
|
|
72
|
+
},
|
|
54
73
|
"verify-build": { kind: "verify-build", version: 1, prompt: "forbidden", requiredParams: ["dockerComposeFile", "labelText"] },
|
|
55
74
|
};
|
|
56
75
|
export function createNodeRegistry() {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { printInfo } from "../../tui.js";
|
|
2
|
+
import { runExpandedPhase } from "../declarative-flow-runner.js";
|
|
3
|
+
import { loadDeclarativeFlow } from "../declarative-flows.js";
|
|
4
|
+
export const flowRunNode = {
|
|
5
|
+
kind: "flow-run",
|
|
6
|
+
version: 1,
|
|
7
|
+
async run(context, params) {
|
|
8
|
+
const { fileName, labelText, ...flowParams } = params;
|
|
9
|
+
if (typeof fileName !== "string" || fileName.trim().length === 0) {
|
|
10
|
+
throw new Error("flow-run node requires non-empty 'fileName' param");
|
|
11
|
+
}
|
|
12
|
+
if (labelText) {
|
|
13
|
+
printInfo(String(labelText));
|
|
14
|
+
}
|
|
15
|
+
const flow = loadDeclarativeFlow(fileName);
|
|
16
|
+
const executionState = {
|
|
17
|
+
flowKind: flow.kind,
|
|
18
|
+
flowVersion: flow.version,
|
|
19
|
+
terminated: false,
|
|
20
|
+
phases: [],
|
|
21
|
+
};
|
|
22
|
+
for (const phase of flow.phases) {
|
|
23
|
+
await runExpandedPhase(phase, context, flowParams, flow.constants, {
|
|
24
|
+
executionState,
|
|
25
|
+
flowKind: flow.kind,
|
|
26
|
+
flowVersion: flow.version,
|
|
27
|
+
});
|
|
28
|
+
if (executionState.terminated) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
value: {
|
|
34
|
+
flowKind: flow.kind,
|
|
35
|
+
flowVersion: flow.version,
|
|
36
|
+
executionState,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { TaskRunnerError } from "../../errors.js";
|
|
3
|
+
export const reviewFindingsFormNode = {
|
|
4
|
+
kind: "review-findings-form",
|
|
5
|
+
version: 1,
|
|
6
|
+
async run(_context, params) {
|
|
7
|
+
let parsed;
|
|
8
|
+
try {
|
|
9
|
+
parsed = JSON.parse(readFileSync(params.reviewJsonFile, "utf8"));
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new TaskRunnerError(`Failed to read review findings from ${params.reviewJsonFile}: ${error.message}`);
|
|
13
|
+
}
|
|
14
|
+
const review = parsed;
|
|
15
|
+
const findings = Array.isArray(review.findings) ? review.findings : [];
|
|
16
|
+
const selectableFindings = findings
|
|
17
|
+
.map((finding) => ({
|
|
18
|
+
severity: typeof finding.severity === "string" ? finding.severity.trim() : "",
|
|
19
|
+
title: typeof finding.title === "string" ? finding.title.trim() : "",
|
|
20
|
+
description: typeof finding.description === "string" ? finding.description.trim() : "",
|
|
21
|
+
}))
|
|
22
|
+
.filter((finding) => finding.title.length > 0);
|
|
23
|
+
const fields = [
|
|
24
|
+
{
|
|
25
|
+
id: "apply_all",
|
|
26
|
+
type: "boolean",
|
|
27
|
+
label: "Исправить все findings в этой итерации",
|
|
28
|
+
help: "Если включено, выбор списка ниже не ограничивает scope исправлений.",
|
|
29
|
+
default: selectableFindings.length === 0,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
if (selectableFindings.length > 0) {
|
|
33
|
+
fields.push({
|
|
34
|
+
id: "selected_findings",
|
|
35
|
+
type: "multi-select",
|
|
36
|
+
label: "Какие findings исправить сейчас",
|
|
37
|
+
help: "Space переключает пункт. Если apply_all=false, выберите хотя бы один finding.",
|
|
38
|
+
options: selectableFindings.map((finding) => ({
|
|
39
|
+
value: finding.title,
|
|
40
|
+
label: `[${finding.severity || "info"}] ${finding.title}`,
|
|
41
|
+
...(finding.description ? { description: finding.description } : {}),
|
|
42
|
+
})),
|
|
43
|
+
default: [],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
fields.push({
|
|
47
|
+
id: "extra_notes",
|
|
48
|
+
type: "text",
|
|
49
|
+
label: "Дополнительные указания",
|
|
50
|
+
help: "Короткий комментарий для этой итерации review-fix.",
|
|
51
|
+
default: "",
|
|
52
|
+
placeholder: "Например: исправить только блокеры",
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
value: {
|
|
56
|
+
formId: params.formId,
|
|
57
|
+
title: params.title,
|
|
58
|
+
...(params.description ? { description: params.description } : {}),
|
|
59
|
+
submitLabel: "Запустить review-fix",
|
|
60
|
+
fields,
|
|
61
|
+
findingCount: selectableFindings.length,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { TaskRunnerError } from "../../errors.js";
|
|
3
|
+
import { printSummary } from "../../tui.js";
|
|
4
|
+
import { requestUserInputInTerminal, validateUserInputValues, } from "../../user-input.js";
|
|
5
|
+
function labelForSingleValue(field, value) {
|
|
6
|
+
if (field.type !== "single-select" && field.type !== "multi-select") {
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
return field.options.find((option) => option.value === value)?.label ?? value;
|
|
10
|
+
}
|
|
11
|
+
function buildReviewFixPromptSuffix(params, values) {
|
|
12
|
+
const applyAll = values.apply_all === true;
|
|
13
|
+
const selectedFindings = Array.isArray(values.selected_findings)
|
|
14
|
+
? values.selected_findings.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
15
|
+
: [];
|
|
16
|
+
const extraNotes = typeof values.extra_notes === "string" ? values.extra_notes.trim() : "";
|
|
17
|
+
if (!applyAll && selectedFindings.length === 0) {
|
|
18
|
+
throw new TaskRunnerError("Review-fix requires selecting at least one finding or enabling 'apply all'.");
|
|
19
|
+
}
|
|
20
|
+
const selectionSummary = applyAll
|
|
21
|
+
? "Выбраны все findings."
|
|
22
|
+
: `Выбраны findings:\n- ${selectedFindings.join("\n- ")}`;
|
|
23
|
+
const promptSuffix = [
|
|
24
|
+
"Используй пользовательский выбор ниже как source of truth для scope текущего review-fix.",
|
|
25
|
+
`Файл выбора: ${params.outputFile}`,
|
|
26
|
+
`apply_all: ${applyAll ? "true" : "false"}`,
|
|
27
|
+
applyAll ? "Исправляй все findings текущей итерации." : `Исправляй только выбранные findings:\n- ${selectedFindings.join("\n- ")}`,
|
|
28
|
+
extraNotes ? `Дополнительные указания пользователя:\n${extraNotes}` : "",
|
|
29
|
+
]
|
|
30
|
+
.filter((item) => item.trim().length > 0)
|
|
31
|
+
.join("\n\n");
|
|
32
|
+
const summaryText = extraNotes ? `${selectionSummary}\n\nЗаметка:\n${extraNotes}` : selectionSummary;
|
|
33
|
+
return { promptSuffix, summaryText };
|
|
34
|
+
}
|
|
35
|
+
function buildPromptSuffix(params, values) {
|
|
36
|
+
if (params.formId === "review-fix-selection") {
|
|
37
|
+
return buildReviewFixPromptSuffix(params, values);
|
|
38
|
+
}
|
|
39
|
+
const lines = params.fields.map((field) => {
|
|
40
|
+
const raw = values[field.id];
|
|
41
|
+
if (typeof raw === "boolean") {
|
|
42
|
+
return `${field.label}: ${raw ? "yes" : "no"}`;
|
|
43
|
+
}
|
|
44
|
+
if (typeof raw === "string") {
|
|
45
|
+
return `${field.label}: ${raw || "-"}`;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(raw)) {
|
|
48
|
+
const labels = raw.map((item) => labelForSingleValue(field, item));
|
|
49
|
+
return `${field.label}: ${labels.length > 0 ? labels.join(", ") : "-"}`;
|
|
50
|
+
}
|
|
51
|
+
return `${field.label}: -`;
|
|
52
|
+
});
|
|
53
|
+
const summaryText = lines.join("\n");
|
|
54
|
+
return {
|
|
55
|
+
promptSuffix: `Используй пользовательский ввод из файла ${params.outputFile}.\n\n${summaryText}`,
|
|
56
|
+
summaryText,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export const userInputNode = {
|
|
60
|
+
kind: "user-input",
|
|
61
|
+
version: 1,
|
|
62
|
+
async run(context, params) {
|
|
63
|
+
const form = {
|
|
64
|
+
formId: params.formId,
|
|
65
|
+
title: params.title,
|
|
66
|
+
...(params.description ? { description: params.description } : {}),
|
|
67
|
+
...(params.submitLabel ? { submitLabel: params.submitLabel } : {}),
|
|
68
|
+
fields: params.fields,
|
|
69
|
+
};
|
|
70
|
+
const requester = context.requestUserInput ?? requestUserInputInTerminal;
|
|
71
|
+
const result = await requester(form);
|
|
72
|
+
validateUserInputValues(form, result.values);
|
|
73
|
+
const rendered = buildPromptSuffix(params, result.values);
|
|
74
|
+
const artifact = {
|
|
75
|
+
form_id: result.formId,
|
|
76
|
+
submitted_at: result.submittedAt,
|
|
77
|
+
values: result.values,
|
|
78
|
+
};
|
|
79
|
+
writeFileSync(params.outputFile, `${JSON.stringify(artifact, null, 2)}\n`, "utf8");
|
|
80
|
+
printSummary(params.title, rendered.summaryText);
|
|
81
|
+
return {
|
|
82
|
+
value: {
|
|
83
|
+
formId: result.formId,
|
|
84
|
+
submittedAt: result.submittedAt,
|
|
85
|
+
values: result.values,
|
|
86
|
+
outputFile: params.outputFile,
|
|
87
|
+
promptSuffix: rendered.promptSuffix,
|
|
88
|
+
summaryText: rendered.summaryText,
|
|
89
|
+
},
|
|
90
|
+
outputs: [{ kind: "artifact", path: params.outputFile, required: true }],
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
};
|
|
@@ -8,6 +8,7 @@ export const verifyBuildNode = {
|
|
|
8
8
|
const executor = context.executors.get("verify-build");
|
|
9
9
|
const value = await executor.execute(toExecutorContext(context), {
|
|
10
10
|
dockerComposeFile: params.dockerComposeFile,
|
|
11
|
+
...(params.service ? { service: params.service } : {}),
|
|
11
12
|
}, executor.defaultConfig);
|
|
12
13
|
return { value };
|
|
13
14
|
},
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { IMPLEMENT_PROMPT_TEMPLATE, PLAN_PROMPT_TEMPLATE, REVIEW_FIX_PROMPT_TEMPLATE, REVIEW_PROMPT_TEMPLATE, REVIEW_REPLY_PROMPT_TEMPLATE, REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, TASK_SUMMARY_PROMPT_TEMPLATE, TEST_FIX_PROMPT_TEMPLATE, TEST_LINTER_FIX_PROMPT_TEMPLATE, } from "../prompts.js";
|
|
1
|
+
import { BUG_ANALYZE_PROMPT_TEMPLATE, BUG_FIX_PROMPT_TEMPLATE, IMPLEMENT_PROMPT_TEMPLATE, MR_DESCRIPTION_PROMPT_TEMPLATE, PLAN_PROMPT_TEMPLATE, REVIEW_FIX_PROMPT_TEMPLATE, REVIEW_PROMPT_TEMPLATE, REVIEW_REPLY_PROMPT_TEMPLATE, REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE, REVIEW_SUMMARY_PROMPT_TEMPLATE, RUN_LINTER_LOOP_FIX_PROMPT_TEMPLATE, RUN_TESTS_LOOP_FIX_PROMPT_TEMPLATE, TASK_SUMMARY_PROMPT_TEMPLATE, TEST_FIX_PROMPT_TEMPLATE, TEST_LINTER_FIX_PROMPT_TEMPLATE, } from "../prompts.js";
|
|
2
2
|
const promptTemplates = {
|
|
3
|
+
"bug-analyze": BUG_ANALYZE_PROMPT_TEMPLATE,
|
|
4
|
+
"bug-fix": BUG_FIX_PROMPT_TEMPLATE,
|
|
3
5
|
implement: IMPLEMENT_PROMPT_TEMPLATE,
|
|
6
|
+
"mr-description": MR_DESCRIPTION_PROMPT_TEMPLATE,
|
|
4
7
|
plan: PLAN_PROMPT_TEMPLATE,
|
|
5
8
|
review: REVIEW_PROMPT_TEMPLATE,
|
|
6
9
|
"review-fix": REVIEW_FIX_PROMPT_TEMPLATE,
|
|
7
10
|
"review-reply": REVIEW_REPLY_PROMPT_TEMPLATE,
|
|
8
11
|
"review-reply-summary": REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE,
|
|
9
12
|
"review-summary": REVIEW_SUMMARY_PROMPT_TEMPLATE,
|
|
13
|
+
"run-linter-loop-fix": RUN_LINTER_LOOP_FIX_PROMPT_TEMPLATE,
|
|
14
|
+
"run-tests-loop-fix": RUN_TESTS_LOOP_FIX_PROMPT_TEMPLATE,
|
|
10
15
|
"task-summary": TASK_SUMMARY_PROMPT_TEMPLATE,
|
|
11
16
|
"test-fix": TEST_FIX_PROMPT_TEMPLATE,
|
|
12
17
|
"test-linter-fix": TEST_LINTER_FIX_PROMPT_TEMPLATE,
|
|
@@ -123,6 +123,18 @@ function interpolateExpectation(expectation, repeatVars) {
|
|
|
123
123
|
message: interpolateText(expectation.message, repeatVars),
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
+
if (expectation.kind === "require-structured-artifacts") {
|
|
127
|
+
const when = expectation.when ? interpolateCondition(expectation.when, repeatVars) : undefined;
|
|
128
|
+
return {
|
|
129
|
+
kind: expectation.kind,
|
|
130
|
+
...(when ? { when } : {}),
|
|
131
|
+
items: expectation.items.map((item) => ({
|
|
132
|
+
path: interpolateValueSpec(item.path, repeatVars),
|
|
133
|
+
schemaId: item.schemaId,
|
|
134
|
+
})),
|
|
135
|
+
message: interpolateText(expectation.message, repeatVars),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
126
138
|
const when = expectation.when ? interpolateCondition(expectation.when, repeatVars) : undefined;
|
|
127
139
|
if (expectation.kind === "require-file") {
|
|
128
140
|
return {
|
|
@@ -180,6 +192,7 @@ function expandRepeat(block) {
|
|
|
180
192
|
for (let index = block.repeat.from; index <= block.repeat.to; index += 1) {
|
|
181
193
|
const repeatVars = {
|
|
182
194
|
[block.repeat.var]: index,
|
|
195
|
+
[`${block.repeat.var}_minus_one`]: index - 1,
|
|
183
196
|
};
|
|
184
197
|
for (const phase of block.phases) {
|
|
185
198
|
phases.push(expandPhase(phase, repeatVars));
|
|
@@ -108,6 +108,12 @@ function validateExpectation(expectation, path) {
|
|
|
108
108
|
validateValueSpec(expectation.paths, `${path}.paths`);
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
|
+
if (expectation.kind === "require-structured-artifacts") {
|
|
112
|
+
expectation.items.forEach((item, index) => {
|
|
113
|
+
validateValueSpec(item.path, `${path}.items[${index}].path`);
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
111
117
|
if (expectation.kind === "require-file") {
|
|
112
118
|
validateValueSpec(expectation.path, `${path}.path`);
|
|
113
119
|
return;
|
|
@@ -268,6 +274,12 @@ export function validateExpandedPhases(phases) {
|
|
|
268
274
|
validateExpandedValueSpec(expectation.paths, phases, phaseIndex, stepIndex, `phases.${phase.id}.steps.${step.id}.expect[${index}].paths`);
|
|
269
275
|
return;
|
|
270
276
|
}
|
|
277
|
+
if (expectation.kind === "require-structured-artifacts") {
|
|
278
|
+
expectation.items.forEach((item, itemIndex) => {
|
|
279
|
+
validateExpandedValueSpec(item.path, phases, phaseIndex, stepIndex, `phases.${phase.id}.steps.${step.id}.expect[${index}].items[${itemIndex}].path`);
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
271
283
|
if (expectation.kind === "require-file") {
|
|
272
284
|
validateExpandedValueSpec(expectation.path, phases, phaseIndex, stepIndex, `phases.${phase.id}.steps.${step.id}.expect[${index}].path`);
|
|
273
285
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { artifactFile, designFile, jiraTaskFile, planArtifacts, planFile, qaFile, readyToMergeFile, taskSummaryFile, } from "../artifacts.js";
|
|
2
|
+
import { artifactFile, bugAnalyzeArtifacts, bugAnalyzeFile, bugAnalyzeJsonFile, bugFixDesignFile, bugFixDesignJsonFile, bugFixPlanFile, bugFixPlanJsonFile, designFile, designJsonFile, jiraDescriptionFile, jiraDescriptionJsonFile, jiraTaskFile, mrDescriptionFile, mrDescriptionJsonFile, planArtifacts, planFile, planJsonFile, qaFile, qaJsonFile, readyToMergeFile, reviewFile, reviewFixFile, reviewFixJsonFile, reviewJsonFile, reviewReplyFile, reviewReplyJsonFile, taskSummaryFile, taskSummaryJsonFile, } from "../artifacts.js";
|
|
3
3
|
import { TaskRunnerError } from "../errors.js";
|
|
4
4
|
import { formatTemplate } from "../prompts.js";
|
|
5
5
|
function readStepRef(segments, context, originalPath) {
|
|
@@ -72,31 +72,72 @@ function resolveArtifact(spec, context) {
|
|
|
72
72
|
const taskKey = String(resolveValue(spec.taskKey, context));
|
|
73
73
|
const iteration = spec.iteration === undefined ? undefined : Number(resolveValue(spec.iteration, context));
|
|
74
74
|
switch (spec.kind) {
|
|
75
|
+
case "bug-analyze-file":
|
|
76
|
+
return bugAnalyzeFile(taskKey);
|
|
77
|
+
case "bug-analyze-json-file":
|
|
78
|
+
return bugAnalyzeJsonFile(taskKey);
|
|
79
|
+
case "bug-fix-design-file":
|
|
80
|
+
return bugFixDesignFile(taskKey);
|
|
81
|
+
case "bug-fix-design-json-file":
|
|
82
|
+
return bugFixDesignJsonFile(taskKey);
|
|
83
|
+
case "bug-fix-plan-file":
|
|
84
|
+
return bugFixPlanFile(taskKey);
|
|
85
|
+
case "bug-fix-plan-json-file":
|
|
86
|
+
return bugFixPlanJsonFile(taskKey);
|
|
75
87
|
case "design-file":
|
|
76
88
|
return designFile(taskKey);
|
|
89
|
+
case "design-json-file":
|
|
90
|
+
return designJsonFile(taskKey);
|
|
91
|
+
case "jira-description-file":
|
|
92
|
+
return jiraDescriptionFile(taskKey);
|
|
93
|
+
case "jira-description-json-file":
|
|
94
|
+
return jiraDescriptionJsonFile(taskKey);
|
|
77
95
|
case "jira-task-file":
|
|
78
96
|
return jiraTaskFile(taskKey);
|
|
97
|
+
case "mr-description-file":
|
|
98
|
+
return mrDescriptionFile(taskKey);
|
|
99
|
+
case "mr-description-json-file":
|
|
100
|
+
return mrDescriptionJsonFile(taskKey);
|
|
79
101
|
case "plan-file":
|
|
80
102
|
return planFile(taskKey);
|
|
103
|
+
case "plan-json-file":
|
|
104
|
+
return planJsonFile(taskKey);
|
|
81
105
|
case "qa-file":
|
|
82
106
|
return qaFile(taskKey);
|
|
107
|
+
case "qa-json-file":
|
|
108
|
+
return qaJsonFile(taskKey);
|
|
83
109
|
case "ready-to-merge-file":
|
|
84
110
|
return readyToMergeFile(taskKey);
|
|
85
111
|
case "review-file":
|
|
86
112
|
if (iteration === undefined) {
|
|
87
113
|
throw new TaskRunnerError("review-file requires iteration");
|
|
88
114
|
}
|
|
89
|
-
return
|
|
115
|
+
return reviewFile(taskKey, iteration);
|
|
116
|
+
case "review-json-file":
|
|
117
|
+
if (iteration === undefined) {
|
|
118
|
+
throw new TaskRunnerError("review-json-file requires iteration");
|
|
119
|
+
}
|
|
120
|
+
return reviewJsonFile(taskKey, iteration);
|
|
90
121
|
case "review-fix-file":
|
|
91
122
|
if (iteration === undefined) {
|
|
92
123
|
throw new TaskRunnerError("review-fix-file requires iteration");
|
|
93
124
|
}
|
|
94
|
-
return
|
|
125
|
+
return reviewFixFile(taskKey, iteration);
|
|
126
|
+
case "review-fix-json-file":
|
|
127
|
+
if (iteration === undefined) {
|
|
128
|
+
throw new TaskRunnerError("review-fix-json-file requires iteration");
|
|
129
|
+
}
|
|
130
|
+
return reviewFixJsonFile(taskKey, iteration);
|
|
95
131
|
case "review-reply-file":
|
|
96
132
|
if (iteration === undefined) {
|
|
97
133
|
throw new TaskRunnerError("review-reply-file requires iteration");
|
|
98
134
|
}
|
|
99
|
-
return
|
|
135
|
+
return reviewReplyFile(taskKey, iteration);
|
|
136
|
+
case "review-reply-json-file":
|
|
137
|
+
if (iteration === undefined) {
|
|
138
|
+
throw new TaskRunnerError("review-reply-json-file requires iteration");
|
|
139
|
+
}
|
|
140
|
+
return reviewReplyJsonFile(taskKey, iteration);
|
|
100
141
|
case "review-reply-summary-file":
|
|
101
142
|
if (iteration === undefined) {
|
|
102
143
|
throw new TaskRunnerError("review-reply-summary-file requires iteration");
|
|
@@ -109,11 +150,15 @@ function resolveArtifact(spec, context) {
|
|
|
109
150
|
return artifactFile("review-summary", taskKey, iteration);
|
|
110
151
|
case "task-summary-file":
|
|
111
152
|
return taskSummaryFile(taskKey);
|
|
153
|
+
case "task-summary-json-file":
|
|
154
|
+
return taskSummaryJsonFile(taskKey);
|
|
112
155
|
}
|
|
113
156
|
}
|
|
114
157
|
function resolveArtifactList(spec, context) {
|
|
115
158
|
const taskKey = String(resolveValue(spec.taskKey, context));
|
|
116
159
|
switch (spec.kind) {
|
|
160
|
+
case "bug-analyze-artifacts":
|
|
161
|
+
return bugAnalyzeArtifacts(taskKey);
|
|
117
162
|
case "plan-artifacts":
|
|
118
163
|
return planArtifacts(taskKey);
|
|
119
164
|
}
|
package/dist/prompts.js
CHANGED
|
@@ -1,33 +1,65 @@
|
|
|
1
1
|
export const BASE_PROMPT_HEADER = "Основная задача:";
|
|
2
2
|
export const EXTRA_PROMPT_HEADER = "Дополнительные указания:";
|
|
3
3
|
export const PLAN_PROMPT_TEMPLATE = "Посмотри и проанализируй задачу в {jira_task_file}. " +
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"Разработай
|
|
7
|
-
|
|
4
|
+
"Сначала создай структурированные JSON-артефакты, они являются source of truth для следующих flow. " +
|
|
5
|
+
"Человекочитаемые markdown-файлы сделай как краткое производное представление этих JSON-артефактов для пользователя. " +
|
|
6
|
+
"Разработай системный дизайн решения и запиши JSON в {design_json_file}, затем markdown в {design_file}. " +
|
|
7
|
+
"Для {design_json_file} используй объект: { summary: string, goals: string[], non_goals: string[], components: string[], decisions: [{ component: string, decision: string, rationale: string }], risks: string[], open_questions: string[] }. " +
|
|
8
|
+
"Разработай подробный план реализации и запиши JSON в {plan_json_file}, затем markdown в {plan_file}. " +
|
|
9
|
+
"Для {plan_json_file} используй объект: { summary: string, prerequisites: string[], implementation_steps: [{ id: string, title: string, details: string }], tests: string[], rollout_notes: string[] }. " +
|
|
10
|
+
"Разработай план тестирования для QA и запиши JSON в {qa_json_file}, затем markdown в {qa_file}. " +
|
|
11
|
+
"Для {qa_json_file} используй объект: { summary: string, test_scenarios: [{ id: string, title: string, expected_result: string }], non_functional_checks: string[] }. " +
|
|
12
|
+
"JSON-файлы должны быть валидными и содержать только JSON без markdown-обёртки. ";
|
|
13
|
+
export const BUG_ANALYZE_PROMPT_TEMPLATE = "Посмотри и проанализируй баг в {jira_task_file}. " +
|
|
14
|
+
"Сначала создай структурированные JSON-артефакты, они являются source of truth для следующих flow. " +
|
|
15
|
+
"Человекочитаемые markdown-файлы сделай как краткое производное представление этих JSON-артефактов для пользователя. " +
|
|
16
|
+
"Запиши структурированный анализ бага в {bug_analyze_json_file}, затем краткую markdown-версию в {bug_analyze_file}. " +
|
|
17
|
+
"Запиши структурированный дизайн исправления в {bug_fix_design_json_file}, затем краткую markdown-версию в {bug_fix_design_file}. " +
|
|
18
|
+
"Запиши структурированный план реализации в {bug_fix_plan_json_file}, затем краткую markdown-версию в {bug_fix_plan_file}. " +
|
|
19
|
+
"JSON-файлы должны быть валидными и содержать только JSON без markdown-обёртки. " +
|
|
20
|
+
"Для {bug_analyze_json_file} используй объект: { summary: string, suspected_root_cause: { hypothesis: string, confidence: string }, reproduction_steps: string[], affected_components: string[], evidence: string[], risks: string[], open_questions: string[] }. " +
|
|
21
|
+
"Для {bug_fix_design_json_file} используй объект: { summary: string, goals: string[], non_goals: string[], target_components: string[], proposed_changes: [{ component: string, change: string, rationale: string }], alternatives_considered: [{ option: string, decision: string, rationale: string }], risks: string[], validation_strategy: string[] }. " +
|
|
22
|
+
"Для {bug_fix_plan_json_file} используй объект: { summary: string, prerequisites: string[], implementation_steps: [{ id: string, title: string, details: string }], tests: string[], rollout_notes: string[] }. ";
|
|
23
|
+
export const BUG_FIX_PROMPT_TEMPLATE = "Используй только структурированные артефакты как source of truth. " +
|
|
24
|
+
"Проанализируй баг по {bug_analyze_json_file}. " +
|
|
25
|
+
"Используй дизайн исправления из {bug_fix_design_json_file}. " +
|
|
26
|
+
"Используй план реализации из {bug_fix_plan_json_file}. " +
|
|
27
|
+
"Markdown-артефакты предназначены только для чтения человеком и не должны определять реализацию. " +
|
|
28
|
+
"После этого приступай к реализации исправления в коде. ";
|
|
29
|
+
export const MR_DESCRIPTION_PROMPT_TEMPLATE = "Посмотри задачу в {jira_task_file} и текущие изменения в репозитории. " +
|
|
30
|
+
"Подготовь очень краткое intent-описание для merge request без подробностей реализации, списков файлов и технических деталей. " +
|
|
31
|
+
"Сначала запиши source-of-truth JSON в {mr_description_json_file} в виде объекта { summary: string }, затем производную markdown-версию в {mr_description_file}. ";
|
|
32
|
+
export const IMPLEMENT_PROMPT_TEMPLATE = "Используй только структурированные артефакты как source of truth. " +
|
|
33
|
+
"Проанализируй системный дизайн {design_json_file}, план реализации {plan_json_file} и приступай к реализации по плану. " +
|
|
34
|
+
"Markdown-артефакты предназначены только для чтения человеком и не должны определять реализацию. ";
|
|
8
35
|
export const REVIEW_PROMPT_TEMPLATE = "Проведи код-ревью текущих изменений. " +
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
36
|
+
"Используй только структурированные артефакты как source of truth: задачу в {jira_task_file}, дизайн в {design_json_file} и план в {plan_json_file}. " +
|
|
37
|
+
"Сначала запиши структурированный результат в {review_json_file} в виде объекта { summary: string, ready_to_merge: boolean, findings: [{ severity: string, title: string, description: string }] }. " +
|
|
38
|
+
"Затем запиши производную markdown-версию в {review_file}. " +
|
|
39
|
+
"Если ready_to_merge=true и нет блокеров, препятствующих merge - создай файл ready-to-merge.md.";
|
|
40
|
+
export const REVIEW_REPLY_PROMPT_TEMPLATE = "Твой коллега провёл код-ревью и записал структурированный результат в {review_json_file}. " +
|
|
41
|
+
"Используй только структурированные артефакты как source of truth: задачу в {jira_task_file}, дизайн в {design_json_file}, план в {plan_json_file} и review в {review_json_file}. " +
|
|
42
|
+
"Сначала запиши структурированный ответ в {review_reply_json_file} в виде объекта { summary: string, ready_to_merge: boolean, responses: [{ finding_title: string, disposition: string, action: string }] }. " +
|
|
43
|
+
"Затем запиши производную markdown-версию в {review_reply_file}.";
|
|
15
44
|
export const REVIEW_SUMMARY_PROMPT_TEMPLATE = "Посмотри в {review_file}. " +
|
|
16
45
|
"Сделай краткий список комментариев без подробностей, 3-7 пунктов. " +
|
|
17
46
|
"Запиши результат в {review_summary_file}.";
|
|
18
47
|
export const REVIEW_REPLY_SUMMARY_PROMPT_TEMPLATE = "Посмотри в {review_reply_file}. " +
|
|
19
48
|
"Сделай краткий список ответов и итоговых действий без подробностей, 3-7 пунктов. " +
|
|
20
49
|
"Запиши результат в {review_reply_summary_file}.";
|
|
21
|
-
export const REVIEW_FIX_PROMPT_TEMPLATE = "
|
|
50
|
+
export const REVIEW_FIX_PROMPT_TEMPLATE = "Используй только структурированные артефакты как source of truth. " +
|
|
51
|
+
"Проанализируй комментарии в {review_reply_json_file}. " +
|
|
22
52
|
"Исправь то, что содержится в дополнительных указаниях, а если таковых нет - исправь все пункты. " +
|
|
23
53
|
"По окончании обязательно прогони вне песочницы линтер, все тесты, сгенерируй make swagger. " +
|
|
24
54
|
"Исправь ошибки линтера и тестов, если будут. " +
|
|
25
|
-
"По завершении
|
|
55
|
+
"По завершении сначала запиши структурированный отчёт в {review_fix_json_file} в виде объекта { summary: string, completed_actions: string[], validation_steps: string[] }, затем производную markdown-версию в {review_fix_file}.";
|
|
26
56
|
export const TASK_SUMMARY_PROMPT_TEMPLATE = "Посмотри в {jira_task_file}. " +
|
|
27
|
-
"Сделай краткое резюме задачи, на 1-2
|
|
28
|
-
"запиши в {task_summary_file}.";
|
|
57
|
+
"Сделай краткое резюме задачи, на 1-2 абзаца. " +
|
|
58
|
+
"Сначала запиши source-of-truth JSON в {task_summary_json_file} в виде объекта { summary: string }, затем markdown-версию в {task_summary_file}.";
|
|
29
59
|
export const TEST_FIX_PROMPT_TEMPLATE = "Прогони тесты, исправь ошибки.";
|
|
30
60
|
export const TEST_LINTER_FIX_PROMPT_TEMPLATE = "Прогони линтер, исправь замечания.";
|
|
61
|
+
export const RUN_TESTS_LOOP_FIX_PROMPT_TEMPLATE = "Запусти ./run_tests.sh, проанализируй последнюю ошибку проверки, исправь код и подготовь изменения так, чтобы следующий прогон run_tests.sh прошёл успешно.";
|
|
62
|
+
export const RUN_LINTER_LOOP_FIX_PROMPT_TEMPLATE = "Запусти ./run_linter.sh, проанализируй последнюю ошибку линтера или генерации, исправь код и подготовь изменения так, чтобы следующий прогон run_linter.sh прошёл успешно.";
|
|
31
63
|
export const AUTO_REVIEW_FIX_EXTRA_PROMPT = "Исправлять только блокеры, критикалы и важные";
|
|
32
64
|
export function formatTemplate(template, values) {
|
|
33
65
|
let result = template;
|