agentweaver 0.1.7 → 0.1.9
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/Dockerfile.codex +3 -3
- package/README.md +24 -10
- package/dist/artifacts.js +30 -0
- package/dist/executors/configs/fetch-gitlab-diff-config.js +3 -0
- package/dist/executors/configs/opencode-config.js +6 -0
- package/dist/executors/fetch-gitlab-diff-executor.js +26 -0
- package/dist/executors/jira-fetch-executor.js +8 -2
- package/dist/executors/opencode-executor.js +35 -0
- package/dist/gitlab.js +199 -5
- package/dist/index.js +160 -121
- package/dist/interactive-ui.js +45 -10
- package/dist/jira.js +116 -14
- package/dist/pipeline/auto-flow.js +1 -1
- package/dist/pipeline/declarative-flows.js +41 -6
- package/dist/pipeline/flow-catalog.js +66 -0
- package/dist/pipeline/flow-specs/auto.json +183 -1
- package/dist/pipeline/flow-specs/bug-analyze.json +1 -1
- package/dist/pipeline/flow-specs/gitlab-diff-review.json +226 -0
- package/dist/pipeline/flow-specs/gitlab-review.json +1 -31
- package/dist/pipeline/flow-specs/plan-opencode.json +603 -0
- package/dist/pipeline/flow-specs/plan.json +183 -1
- package/dist/pipeline/flow-specs/run-go-linter-loop.json +83 -7
- package/dist/pipeline/flow-specs/run-go-tests-loop.json +83 -7
- package/dist/pipeline/flow-specs/task-describe.json +1 -1
- package/dist/pipeline/node-registry.js +80 -8
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +34 -0
- package/dist/pipeline/nodes/flow-run-node.js +2 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +26 -2
- package/dist/pipeline/nodes/local-script-check-node.js +50 -8
- package/dist/pipeline/nodes/opencode-prompt-node.js +32 -0
- package/dist/pipeline/nodes/planning-questions-form-node.js +69 -0
- package/dist/pipeline/nodes/user-input-node.js +9 -1
- package/dist/pipeline/prompt-registry.js +4 -1
- package/dist/pipeline/registry.js +10 -0
- package/dist/pipeline/spec-loader.js +37 -3
- package/dist/pipeline/spec-types.js +43 -1
- package/dist/pipeline/spec-validator.js +53 -7
- package/dist/pipeline/value-resolver.js +25 -1
- package/dist/prompts.js +54 -14
- package/dist/scope.js +25 -16
- package/dist/structured-artifact-schemas.json +560 -0
- package/dist/structured-artifacts.js +103 -262
- package/dist/user-input.js +7 -0
- package/docker-compose.yml +2 -2
- package/package.json +3 -3
- package/run_go_linter.py +128 -0
- package/run_go_tests.py +120 -0
- package/verify_build.sh +3 -3
- package/run_go_linter.sh +0 -89
- package/run_go_tests.sh +0 -83
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { TaskRunnerError } from "../errors.js";
|
|
2
|
+
import { STRUCTURED_ARTIFACT_SCHEMA_IDS } from "../structured-artifacts.js";
|
|
2
3
|
import { isPromptTemplateRef } from "./prompt-registry.js";
|
|
4
|
+
import { ARTIFACT_LIST_REF_KINDS as artifactListRefKinds, ARTIFACT_REF_KINDS as artifactRefKinds, } from "./spec-types.js";
|
|
5
|
+
function isArtifactRefKind(value) {
|
|
6
|
+
return artifactRefKinds.includes(value);
|
|
7
|
+
}
|
|
8
|
+
function isArtifactListRefKind(value) {
|
|
9
|
+
return artifactListRefKinds.includes(value);
|
|
10
|
+
}
|
|
11
|
+
function validateArtifactRefSpec(spec, path) {
|
|
12
|
+
assert(isArtifactRefKind(spec.kind), `Unknown artifact kind '${spec.kind}' at ${path}.kind`);
|
|
13
|
+
validateValueSpec(spec.taskKey, `${path}.taskKey`);
|
|
14
|
+
if (spec.iteration) {
|
|
15
|
+
validateValueSpec(spec.iteration, `${path}.iteration`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function validateArtifactListRefSpec(spec, path) {
|
|
19
|
+
assert(isArtifactListRefKind(spec.kind), `Unknown artifact list kind '${spec.kind}' at ${path}.kind`);
|
|
20
|
+
validateValueSpec(spec.taskKey, `${path}.taskKey`);
|
|
21
|
+
}
|
|
3
22
|
function assert(condition, message) {
|
|
4
23
|
if (!condition) {
|
|
5
24
|
throw new TaskRunnerError(message);
|
|
6
25
|
}
|
|
7
26
|
}
|
|
8
27
|
function validateValueSpec(value, path) {
|
|
9
|
-
if ("const" in value || "ref" in value
|
|
28
|
+
if ("const" in value || "ref" in value) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if ("artifact" in value) {
|
|
32
|
+
validateArtifactRefSpec(value.artifact, `${path}.artifact`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if ("artifactList" in value) {
|
|
36
|
+
validateArtifactListRefSpec(value.artifactList, `${path}.artifactList`);
|
|
10
37
|
return;
|
|
11
38
|
}
|
|
12
39
|
if ("template" in value) {
|
|
@@ -64,9 +91,12 @@ function validateCondition(condition, path) {
|
|
|
64
91
|
}
|
|
65
92
|
throw new TaskRunnerError(`Unsupported condition at ${path}`);
|
|
66
93
|
}
|
|
67
|
-
function validateStep(step, nodeRegistry, path) {
|
|
94
|
+
function validateStep(step, nodeRegistry, executorRegistry, path, options) {
|
|
68
95
|
assert(nodeRegistry.has(step.node), `Unknown node kind '${step.node}' at ${path}.node`);
|
|
69
96
|
const nodeMeta = nodeRegistry.getMeta(step.node);
|
|
97
|
+
for (const executorId of nodeMeta.executors ?? []) {
|
|
98
|
+
assert(executorRegistry.has(executorId), `Unknown executor '${executorId}' required by node '${step.node}' at ${path}.node`);
|
|
99
|
+
}
|
|
70
100
|
validateCondition(step.when, `${path}.when`);
|
|
71
101
|
if (step.prompt) {
|
|
72
102
|
assert(nodeMeta.prompt !== "forbidden", `Node '${step.node}' does not accept prompt binding at ${path}.prompt`);
|
|
@@ -92,6 +122,21 @@ function validateStep(step, nodeRegistry, path) {
|
|
|
92
122
|
validateValueSpec(value, `${path}.params.${key}`);
|
|
93
123
|
}
|
|
94
124
|
}
|
|
125
|
+
if (nodeMeta.nestedFlowParam) {
|
|
126
|
+
const nestedValue = step.params?.[nodeMeta.nestedFlowParam];
|
|
127
|
+
if (nestedValue && "const" in nestedValue && typeof nestedValue.const === "string" && nestedValue.const.trim().length > 0) {
|
|
128
|
+
const resolveFlowByName = options.resolveFlowByName;
|
|
129
|
+
if (typeof resolveFlowByName !== "function") {
|
|
130
|
+
throw new TaskRunnerError(`Flow validator cannot resolve nested flow '${nestedValue.const}' at ${path}.params.${nodeMeta.nestedFlowParam}`);
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
resolveFlowByName(nestedValue.const);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
throw new TaskRunnerError(`Unknown nested flow '${nestedValue.const}' at ${path}.params.${nodeMeta.nestedFlowParam}: ${error.message}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
95
140
|
if (step.expect) {
|
|
96
141
|
step.expect.forEach((expectation, index) => validateExpectation(expectation, `${path}.expect[${index}]`));
|
|
97
142
|
}
|
|
@@ -111,6 +156,7 @@ function validateExpectation(expectation, path) {
|
|
|
111
156
|
if (expectation.kind === "require-structured-artifacts") {
|
|
112
157
|
expectation.items.forEach((item, index) => {
|
|
113
158
|
validateValueSpec(item.path, `${path}.items[${index}].path`);
|
|
159
|
+
assert(STRUCTURED_ARTIFACT_SCHEMA_IDS.includes(item.schemaId), `Unknown structured artifact schema '${item.schemaId}' at ${path}.items[${index}].schemaId`);
|
|
114
160
|
});
|
|
115
161
|
return;
|
|
116
162
|
}
|
|
@@ -135,10 +181,10 @@ function validateAfterAction(action, path) {
|
|
|
135
181
|
}
|
|
136
182
|
throw new TaskRunnerError(`Unsupported after action at ${path}`);
|
|
137
183
|
}
|
|
138
|
-
function validatePhase(phase, nodeRegistry, path) {
|
|
184
|
+
function validatePhase(phase, nodeRegistry, executorRegistry, path, options) {
|
|
139
185
|
assert(phase.id.trim().length > 0, `Phase id must be non-empty at ${path}.id`);
|
|
140
186
|
validateCondition(phase.when, `${path}.when`);
|
|
141
|
-
phase.steps.forEach((step, index) => validateStep(step, nodeRegistry, `${path}.steps[${index}]
|
|
187
|
+
phase.steps.forEach((step, index) => validateStep(step, nodeRegistry, executorRegistry, `${path}.steps[${index}]`, options));
|
|
142
188
|
}
|
|
143
189
|
function validateRefPath(ref, phases, currentPhaseIndex, currentStepIndex, path, allowCurrentStepRef = false) {
|
|
144
190
|
const [scope, ...rest] = ref.split(".");
|
|
@@ -230,17 +276,17 @@ function validateExpandedCondition(condition, phases, currentPhaseIndex, current
|
|
|
230
276
|
validateExpandedValueSpec(condition.exists, phases, currentPhaseIndex, currentStepIndex, `${path}.exists`, allowCurrentStepRef);
|
|
231
277
|
}
|
|
232
278
|
}
|
|
233
|
-
export function validateFlowSpec(spec, nodeRegistry) {
|
|
279
|
+
export function validateFlowSpec(spec, nodeRegistry, executorRegistry, options = {}) {
|
|
234
280
|
assert(spec.kind.trim().length > 0, "Flow spec kind must be non-empty");
|
|
235
281
|
assert(Number.isInteger(spec.version) && spec.version > 0, "Flow spec version must be a positive integer");
|
|
236
282
|
spec.phases.forEach((item, index) => {
|
|
237
283
|
if ("repeat" in item) {
|
|
238
284
|
assert(item.repeat.var.trim().length > 0, `Repeat var must be non-empty at phases[${index}].repeat.var`);
|
|
239
285
|
assert(item.repeat.to >= item.repeat.from, `Repeat range is invalid at phases[${index}].repeat`);
|
|
240
|
-
item.phases.forEach((phase, phaseIndex) => validatePhase(phase, nodeRegistry, `phases[${index}].phases[${phaseIndex}]
|
|
286
|
+
item.phases.forEach((phase, phaseIndex) => validatePhase(phase, nodeRegistry, executorRegistry, `phases[${index}].phases[${phaseIndex}]`, options));
|
|
241
287
|
return;
|
|
242
288
|
}
|
|
243
|
-
validatePhase(item, nodeRegistry, `phases[${index}]
|
|
289
|
+
validatePhase(item, nodeRegistry, executorRegistry, `phases[${index}]`, options);
|
|
244
290
|
});
|
|
245
291
|
}
|
|
246
292
|
export function validateExpandedPhases(phases) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { artifactFile, bugAnalyzeArtifacts, bugAnalyzeFile, bugAnalyzeJsonFile, bugFixDesignFile, bugFixDesignJsonFile, bugFixPlanFile, bugFixPlanJsonFile, designFile, designJsonFile, gitlabReviewFile, gitlabReviewInputJsonFile, gitlabReviewJsonFile, jiraDescriptionFile, jiraDescriptionJsonFile, jiraTaskFile, mrDescriptionFile, mrDescriptionJsonFile, planArtifacts, planFile, planJsonFile, qaFile, qaJsonFile, readyToMergeFile, reviewFile, reviewFixFile, reviewFixJsonFile, reviewJsonFile, reviewReplyFile, reviewReplyJsonFile, taskSummaryFile, taskSummaryJsonFile, } from "../artifacts.js";
|
|
2
|
+
import { artifactFile, bugAnalyzeArtifacts, bugAnalyzeFile, bugAnalyzeJsonFile, bugFixDesignFile, bugFixDesignJsonFile, bugFixPlanFile, bugFixPlanJsonFile, designFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, gitlabDiffReviewInputJsonFile, gitlabReviewFile, gitlabReviewInputJsonFile, gitlabReviewJsonFile, jiraAttachmentsContextFile, jiraAttachmentsManifestFile, jiraDescriptionFile, jiraDescriptionJsonFile, jiraTaskFile, mrDescriptionFile, mrDescriptionJsonFile, planningAnswersJsonFile, planningQuestionsJsonFile, planArtifacts, planFile, planJsonFile, qaFile, qaJsonFile, readyToMergeFile, reviewFile, reviewFixFile, reviewFixJsonFile, reviewJsonFile, reviewReplyFile, reviewReplyJsonFile, runGoLinterResultJsonFile, runGoTestsResultJsonFile, 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) {
|
|
@@ -88,12 +88,22 @@ function resolveArtifact(spec, context) {
|
|
|
88
88
|
return designFile(taskKey);
|
|
89
89
|
case "design-json-file":
|
|
90
90
|
return designJsonFile(taskKey);
|
|
91
|
+
case "gitlab-diff-file":
|
|
92
|
+
return gitlabDiffFile(taskKey);
|
|
93
|
+
case "gitlab-diff-json-file":
|
|
94
|
+
return gitlabDiffJsonFile(taskKey);
|
|
95
|
+
case "gitlab-diff-review-input-json-file":
|
|
96
|
+
return gitlabDiffReviewInputJsonFile(taskKey);
|
|
91
97
|
case "gitlab-review-file":
|
|
92
98
|
return gitlabReviewFile(taskKey);
|
|
93
99
|
case "gitlab-review-input-json-file":
|
|
94
100
|
return gitlabReviewInputJsonFile(taskKey);
|
|
95
101
|
case "gitlab-review-json-file":
|
|
96
102
|
return gitlabReviewJsonFile(taskKey);
|
|
103
|
+
case "jira-attachments-context-file":
|
|
104
|
+
return jiraAttachmentsContextFile(taskKey);
|
|
105
|
+
case "jira-attachments-manifest-file":
|
|
106
|
+
return jiraAttachmentsManifestFile(taskKey);
|
|
97
107
|
case "jira-description-file":
|
|
98
108
|
return jiraDescriptionFile(taskKey);
|
|
99
109
|
case "jira-description-json-file":
|
|
@@ -104,6 +114,10 @@ function resolveArtifact(spec, context) {
|
|
|
104
114
|
return mrDescriptionFile(taskKey);
|
|
105
115
|
case "mr-description-json-file":
|
|
106
116
|
return mrDescriptionJsonFile(taskKey);
|
|
117
|
+
case "planning-answers-json-file":
|
|
118
|
+
return planningAnswersJsonFile(taskKey);
|
|
119
|
+
case "planning-questions-json-file":
|
|
120
|
+
return planningQuestionsJsonFile(taskKey);
|
|
107
121
|
case "plan-file":
|
|
108
122
|
return planFile(taskKey);
|
|
109
123
|
case "plan-json-file":
|
|
@@ -144,6 +158,16 @@ function resolveArtifact(spec, context) {
|
|
|
144
158
|
throw new TaskRunnerError("review-reply-json-file requires iteration");
|
|
145
159
|
}
|
|
146
160
|
return reviewReplyJsonFile(taskKey, iteration);
|
|
161
|
+
case "run-go-linter-result-json-file":
|
|
162
|
+
if (iteration === undefined) {
|
|
163
|
+
throw new TaskRunnerError("run-go-linter-result-json-file requires iteration");
|
|
164
|
+
}
|
|
165
|
+
return runGoLinterResultJsonFile(taskKey, iteration);
|
|
166
|
+
case "run-go-tests-result-json-file":
|
|
167
|
+
if (iteration === undefined) {
|
|
168
|
+
throw new TaskRunnerError("run-go-tests-result-json-file requires iteration");
|
|
169
|
+
}
|
|
170
|
+
return runGoTestsResultJsonFile(taskKey, iteration);
|
|
147
171
|
case "review-reply-summary-file":
|
|
148
172
|
if (iteration === undefined) {
|
|
149
173
|
throw new TaskRunnerError("review-reply-summary-file requires iteration");
|
package/dist/prompts.js
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
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
|
+
"Обязательно проанализируй дополнительные материалы из Jira attachments manifest {jira_attachments_manifest_file} и текстовый контекст {jira_attachments_context_file}; если attachment содержит более детальную постановку, ограничения, список файлов, migration strategy или инварианты, считай attachment source of truth для planning наравне с Jira issue. " +
|
|
4
5
|
"Сначала создай структурированные JSON-артефакты, они являются source of truth для следующих flow. " +
|
|
5
|
-
"Человекочитаемые markdown-файлы сделай как
|
|
6
|
+
"Человекочитаемые markdown-файлы сделай как подробное производное представление этих JSON-артефактов для пользователя, а не как краткое summary. " +
|
|
7
|
+
"Markdown не должен влиять на структуру JSON: сначала определи корректные JSON-типы, затем строй markdown как производное представление. " +
|
|
8
|
+
"Не схлопывай конкретику из задачи и attachment: сохраняй явные файлы, методы, API, инварианты, migration steps, DB-ограничения, business rules и acceptance criteria. " +
|
|
6
9
|
"Разработай системный дизайн решения и запиши JSON в {design_json_file}, затем markdown в {design_file}. " +
|
|
7
|
-
|
|
10
|
+
'Для {design_json_file} используй строго JSON-объект вида { "summary": "string", "goals": ["string"], "non_goals": ["string"], "components": ["string"], "current_state": ["string"], "target_state": ["string"], "affected_code": [{ "area": "string", "files": ["string"], "details": "string" }], "business_rules": ["string"], "decisions": [{ "component": "string", "decision": "string", "rationale": "string" }], "migration_strategy": ["string"], "database_changes": ["string"], "api_changes": ["string"], "risks": ["string"], "acceptance_criteria": ["string"], "open_questions": ["string"] }. ' +
|
|
11
|
+
'Строго соблюдай типы. В частности, current_state и target_state всегда должны быть массивами строк, даже если пункт только один: ["..."], а не "...". ' +
|
|
12
|
+
'Точно так же files, goals, non_goals, components, business_rules, migration_strategy, database_changes, api_changes, risks, acceptance_criteria и open_questions должны быть массивами, а не одиночными строками. ' +
|
|
8
13
|
"Разработай подробный план реализации и запиши JSON в {plan_json_file}, затем markdown в {plan_file}. " +
|
|
9
|
-
|
|
14
|
+
'Для {plan_json_file} используй строго JSON-объект вида { "summary": "string", "prerequisites": ["string"], "workstreams": ["string"], "implementation_steps": [{ "id": "string", "title": "string", "details": "string", "affected_files": ["string"], "verification": ["string"], "dependencies": ["string"] }], "tests": ["string"], "rollout_notes": ["string"], "follow_up_items": ["string"] }. ' +
|
|
15
|
+
'Строго соблюдай типы. prerequisites, workstreams, tests, rollout_notes, follow_up_items, affected_files, verification и dependencies всегда должны быть массивами, даже если элемент только один. implementation_steps должен быть массивом объектов, а не одним объектом. ' +
|
|
16
|
+
'Каждый элемент implementation_steps должен иметь вид { "id": "step-1", "title": "string", "details": "string", "affected_files": ["string"], "verification": ["string"], "dependencies": ["string"] }. ' +
|
|
17
|
+
'Нельзя использовать "verification": "..." или "affected_files": "...". Нужно использовать массивы: ["..."]. ' +
|
|
10
18
|
"Разработай план тестирования для QA и запиши JSON в {qa_json_file}, затем markdown в {qa_file}. " +
|
|
11
|
-
|
|
19
|
+
'Для {qa_json_file} используй строго JSON-объект вида { "summary": "string", "test_scenarios": [{ "id": "string", "title": "string", "expected_result": "string" }], "non_functional_checks": ["string"] }. ' +
|
|
20
|
+
'Строго соблюдай типы. test_scenarios должен быть массивом объектов, а non_functional_checks должен быть массивом строк, даже если пункт только один. ' +
|
|
21
|
+
"Markdown для design и plan оформи развёрнуто, с отдельными секциями Summary, Current State, Target State, Affected Code, Decisions, Migration/DB Changes, Risks, Implementation Steps, Tests, Rollout. " +
|
|
12
22
|
"JSON-файлы должны быть валидными и содержать только JSON без markdown-обёртки. ";
|
|
23
|
+
export const PLAN_QUESTIONS_PROMPT_TEMPLATE = "Посмотри и проанализируй задачу в {jira_task_file}. " +
|
|
24
|
+
"Обязательно проанализируй дополнительные материалы из Jira attachments manifest {jira_attachments_manifest_file} и текстовый контекст {jira_attachments_context_file}; если attachment содержит более детальную постановку, ограничения, список файлов, migration strategy или инварианты, считай attachment source of truth для planning наравне с Jira issue. " +
|
|
25
|
+
"Перед финальным planning определи, нужны ли уточнения от пользователя. " +
|
|
26
|
+
'Если уточнения не нужны, запиши в {planning_questions_json_file} строго JSON-объект { "summary": "string", "questions": [] }. ' +
|
|
27
|
+
'Если уточнения нужны, запиши в {planning_questions_json_file} строго JSON-объект { "summary": "string", "questions": [{ "id": "string", "question": "string", "details": "string", "required": true, "multiline": false, "placeholder": "string" }] }. ' +
|
|
28
|
+
'questions всегда должен быть массивом. required и multiline должны быть boolean, а не строками "true"/"false". ' +
|
|
29
|
+
"Задавай только вопросы, без ответа на которые design/plan могут оказаться неверными или слишком предположительными. " +
|
|
30
|
+
"Не задавай очевидные, декоративные или дублирующие вопросы. " +
|
|
31
|
+
"Обычно достаточно 1-5 вопросов. " +
|
|
32
|
+
"JSON-файл должен быть валидным и содержать только JSON без markdown-обёртки. ";
|
|
13
33
|
export const BUG_ANALYZE_PROMPT_TEMPLATE = "Посмотри и проанализируй баг в {jira_task_file}. " +
|
|
14
34
|
"Сначала создай структурированные JSON-артефакты, они являются source of truth для следующих flow. " +
|
|
15
35
|
"Человекочитаемые markdown-файлы сделай как краткое производное представление этих JSON-артефактов для пользователя. " +
|
|
@@ -17,9 +37,12 @@ export const BUG_ANALYZE_PROMPT_TEMPLATE = "Посмотри и проанали
|
|
|
17
37
|
"Запиши структурированный дизайн исправления в {bug_fix_design_json_file}, затем краткую markdown-версию в {bug_fix_design_file}. " +
|
|
18
38
|
"Запиши структурированный план реализации в {bug_fix_plan_json_file}, затем краткую markdown-версию в {bug_fix_plan_file}. " +
|
|
19
39
|
"JSON-файлы должны быть валидными и содержать только JSON без markdown-обёртки. " +
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
'Для {bug_analyze_json_file} используй строго JSON-объект { "summary": "string", "suspected_root_cause": { "hypothesis": "string", "confidence": "string" }, "reproduction_steps": ["string"], "affected_components": ["string"], "evidence": ["string"], "risks": ["string"], "open_questions": ["string"] }. ' +
|
|
41
|
+
'reproduction_steps, affected_components, evidence, risks и open_questions всегда должны быть массивами строк. suspected_root_cause всегда должен быть объектом, а не строкой. ' +
|
|
42
|
+
'Для {bug_fix_design_json_file} используй строго JSON-объект { "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"] }. ' +
|
|
43
|
+
'goals, non_goals, target_components, risks и validation_strategy всегда должны быть массивами строк. proposed_changes и alternatives_considered всегда должны быть массивами объектов. ' +
|
|
44
|
+
'Для {bug_fix_plan_json_file} используй строго JSON-объект { "summary": "string", "prerequisites": ["string"], "implementation_steps": [{ "id": "string", "title": "string", "details": "string" }], "tests": ["string"], "rollout_notes": ["string"] }. ' +
|
|
45
|
+
'prerequisites, tests и rollout_notes всегда должны быть массивами строк. implementation_steps всегда должен быть массивом объектов. ';
|
|
23
46
|
export const BUG_FIX_PROMPT_TEMPLATE = "Используй только структурированные артефакты как source of truth. " +
|
|
24
47
|
"Проанализируй баг по {bug_analyze_json_file}. " +
|
|
25
48
|
"Используй дизайн исправления из {bug_fix_design_json_file}. " +
|
|
@@ -34,21 +57,32 @@ export const IMPLEMENT_PROMPT_TEMPLATE = "Используй только стр
|
|
|
34
57
|
"Markdown-артефакты предназначены только для чтения человеком и не должны определять реализацию. ";
|
|
35
58
|
export const REVIEW_PROMPT_TEMPLATE = "Проведи код-ревью текущих изменений. " +
|
|
36
59
|
"Используй только структурированные артефакты как source of truth: задачу в {jira_task_file}, дизайн в {design_json_file} и план в {plan_json_file}. " +
|
|
37
|
-
|
|
60
|
+
'Сначала запиши структурированный результат в {review_json_file} в виде строго JSON-объекта { "summary": "string", "ready_to_merge": true, "findings": [{ "severity": "string", "title": "string", "description": "string" }] }. ' +
|
|
61
|
+
'ready_to_merge должен быть boolean, а не строкой. findings всегда должен быть массивом объектов, даже если замечание одно или их нет. ' +
|
|
38
62
|
"Затем запиши производную markdown-версию в {review_file}. " +
|
|
39
63
|
"Если ready_to_merge=true и нет блокеров, препятствующих merge - создай файл ready-to-merge.md.";
|
|
40
64
|
export const REVIEW_PROJECT_PROMPT_TEMPLATE = "Проведи код-ревью текущих изменений в проекте без Jira-контекста. " +
|
|
41
65
|
"Оцени качество изменений по текущему коду, тестам, рискам регрессий и общему инженерному качеству. " +
|
|
42
|
-
|
|
66
|
+
'Сначала запиши структурированный результат в {review_json_file} в виде строго JSON-объекта { "summary": "string", "ready_to_merge": true, "findings": [{ "severity": "string", "title": "string", "description": "string" }] }. ' +
|
|
67
|
+
'ready_to_merge должен быть boolean, а findings всегда должен быть массивом объектов. ' +
|
|
68
|
+
"Затем запиши производную markdown-версию в {review_file}. " +
|
|
69
|
+
"Если ready_to_merge=true и нет блокеров, создай файл {ready_to_merge_file}.";
|
|
70
|
+
export const GITLAB_DIFF_REVIEW_PROMPT_TEMPLATE = "Проведи код-ревью diff merge request из GitLab. " +
|
|
71
|
+
"Используй structured diff artifact {gitlab_diff_json_file} как source of truth, а markdown {gitlab_diff_file} только как удобное представление для чтения человеком. " +
|
|
72
|
+
"Оцени только изменения из diff: корректность, риски регрессий, отсутствие тестов, опасные edge cases, нарушения контрактов и поддерживаемость. " +
|
|
73
|
+
'Сначала запиши структурированный результат в {review_json_file} в виде строго JSON-объекта { "summary": "string", "ready_to_merge": true, "findings": [{ "severity": "string", "title": "string", "description": "string" }] }. ' +
|
|
74
|
+
'ready_to_merge должен быть boolean, а findings всегда должен быть массивом объектов. ' +
|
|
43
75
|
"Затем запиши производную markdown-версию в {review_file}. " +
|
|
44
76
|
"Если ready_to_merge=true и нет блокеров, создай файл {ready_to_merge_file}.";
|
|
45
77
|
export const REVIEW_REPLY_PROMPT_TEMPLATE = "Твой коллега провёл код-ревью и записал структурированный результат в {review_json_file}. " +
|
|
46
78
|
"Используй только структурированные артефакты как source of truth: задачу в {jira_task_file}, дизайн в {design_json_file}, план в {plan_json_file} и review в {review_json_file}. " +
|
|
47
|
-
|
|
79
|
+
'Сначала запиши структурированный ответ в {review_reply_json_file} в виде строго JSON-объекта { "summary": "string", "ready_to_merge": true, "responses": [{ "finding_title": "string", "disposition": "string", "action": "string" }] }. ' +
|
|
80
|
+
'ready_to_merge должен быть boolean, а responses всегда должен быть массивом объектов. ' +
|
|
48
81
|
"Затем запиши производную markdown-версию в {review_reply_file}.";
|
|
49
82
|
export const REVIEW_REPLY_PROJECT_PROMPT_TEMPLATE = "Твой коллега провёл код-ревью и записал структурированный результат в {review_json_file}. " +
|
|
50
83
|
"Используй review в {review_json_file} как source of truth, разберись в замечаниях и подготовь структурированный ответ. " +
|
|
51
|
-
|
|
84
|
+
'Сначала запиши структурированный ответ в {review_reply_json_file} в виде строго JSON-объекта { "summary": "string", "ready_to_merge": true, "responses": [{ "finding_title": "string", "disposition": "string", "action": "string" }] }. ' +
|
|
85
|
+
'ready_to_merge должен быть boolean, а responses всегда должен быть массивом объектов. ' +
|
|
52
86
|
"Затем запиши производную markdown-версию в {review_reply_file}.";
|
|
53
87
|
export const REVIEW_SUMMARY_PROMPT_TEMPLATE = "Посмотри в {review_file}. " +
|
|
54
88
|
"Сделай краткий список комментариев без подробностей, 3-7 пунктов. " +
|
|
@@ -61,12 +95,18 @@ export const REVIEW_FIX_PROMPT_TEMPLATE = "Используй только ст
|
|
|
61
95
|
"Исправь то, что содержится в дополнительных указаниях, а если таковых нет - исправь все пункты. " +
|
|
62
96
|
"По окончании обязательно прогони вне песочницы линтер, все тесты, сгенерируй make swagger. " +
|
|
63
97
|
"Исправь ошибки линтера и тестов, если будут. " +
|
|
64
|
-
|
|
98
|
+
'По завершении сначала запиши структурированный отчёт в {review_fix_json_file} в виде строго JSON-объекта { "summary": "string", "completed_actions": ["string"], "validation_steps": ["string"] }, затем производную markdown-версию в {review_fix_file}. ' +
|
|
99
|
+
'completed_actions и validation_steps всегда должны быть массивами строк, даже если пункт только один.';
|
|
65
100
|
export const TASK_SUMMARY_PROMPT_TEMPLATE = "Посмотри в {jira_task_file}. " +
|
|
66
101
|
"Сделай краткое резюме задачи, на 1-2 абзаца. " +
|
|
67
102
|
"Сначала запиши source-of-truth JSON в {task_summary_json_file} в виде объекта { summary: string }, затем markdown-версию в {task_summary_file}.";
|
|
68
|
-
export const
|
|
69
|
-
|
|
103
|
+
export const JIRA_DESCRIPTION_PROMPT_TEMPLATE = "Посмотри задачу в {jira_task_file}. " +
|
|
104
|
+
"Проанализируй код и оформи краткое описание для Jira, упомяни ключевые точки, модели данных, сервисы, REST-методы. " +
|
|
105
|
+
"Сначала запиши source-of-truth JSON в {jira_description_json_file} в виде объекта { summary: string }, затем markdown-версию в {jira_description_file}.";
|
|
106
|
+
export const RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE = "Используй структурированный результат последнего запуска run_go_tests.py из {tests_result_json_file} как source of truth. " +
|
|
107
|
+
"Проанализируй последнюю ошибку проверки, исправь код и подготовь изменения так, чтобы следующий прогон run_go_tests.py прошёл успешно.";
|
|
108
|
+
export const RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE = "Используй структурированный результат последнего запуска run_go_linter.py из {linter_result_json_file} как source of truth. " +
|
|
109
|
+
"Проанализируй последнюю ошибку линтера или генерации, исправь код и подготовь изменения так, чтобы следующий прогон run_go_linter.py прошёл успешно.";
|
|
70
110
|
export const AUTO_REVIEW_FIX_EXTRA_PROMPT = "Исправлять только блокеры, критикалы и важные";
|
|
71
111
|
export function formatTemplate(template, values) {
|
|
72
112
|
let result = template;
|
package/dist/scope.js
CHANGED
|
@@ -35,10 +35,7 @@ export function sanitizeScopeName(value) {
|
|
|
35
35
|
}
|
|
36
36
|
export function detectGitBranchName() {
|
|
37
37
|
const branchName = gitOutput(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
38
|
-
if (!branchName) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
if (branchName === "HEAD") {
|
|
38
|
+
if (!branchName || branchName === "HEAD") {
|
|
42
39
|
return null;
|
|
43
40
|
}
|
|
44
41
|
return branchName;
|
|
@@ -66,30 +63,42 @@ export function buildProjectScopeKey(explicitScope) {
|
|
|
66
63
|
projectRoot,
|
|
67
64
|
};
|
|
68
65
|
}
|
|
69
|
-
export function
|
|
70
|
-
const jiraIssueKey = extractIssueKey(jiraRef);
|
|
71
|
-
const scopeKey = explicitScope?.trim() ? sanitizeScopeName(explicitScope) : jiraIssueKey;
|
|
72
|
-
ensureScopeWorkspaceDir(scopeKey);
|
|
66
|
+
export function parseJiraContext(jiraRef) {
|
|
73
67
|
return {
|
|
74
|
-
scopeType: "task",
|
|
75
|
-
scopeKey,
|
|
76
68
|
jiraRef,
|
|
77
|
-
jiraIssueKey,
|
|
69
|
+
jiraIssueKey: extractIssueKey(jiraRef),
|
|
78
70
|
jiraBrowseUrl: buildJiraBrowseUrl(jiraRef),
|
|
79
71
|
jiraApiUrl: buildJiraApiUrl(jiraRef),
|
|
80
|
-
jiraTaskFile: jiraTaskFile(scopeKey),
|
|
81
72
|
};
|
|
82
73
|
}
|
|
83
|
-
export function resolveProjectScope(explicitScope) {
|
|
74
|
+
export function resolveProjectScope(explicitScope, jiraRef) {
|
|
84
75
|
const { scopeKey, gitBranchName, worktreeHash, projectRoot } = buildProjectScopeKey(explicitScope);
|
|
85
76
|
ensureScopeWorkspaceDir(scopeKey);
|
|
86
|
-
|
|
77
|
+
const baseScope = {
|
|
87
78
|
scopeType: "project",
|
|
88
79
|
scopeKey,
|
|
89
80
|
gitBranchName,
|
|
90
81
|
worktreeHash,
|
|
91
82
|
projectRoot,
|
|
92
83
|
};
|
|
84
|
+
if (!jiraRef?.trim()) {
|
|
85
|
+
return baseScope;
|
|
86
|
+
}
|
|
87
|
+
const jiraContext = parseJiraContext(jiraRef);
|
|
88
|
+
return {
|
|
89
|
+
...baseScope,
|
|
90
|
+
...jiraContext,
|
|
91
|
+
jiraTaskFile: jiraTaskFile(scopeKey),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function attachJiraContext(scope, jiraRef) {
|
|
95
|
+
const jiraContext = parseJiraContext(jiraRef);
|
|
96
|
+
ensureScopeWorkspaceDir(scope.scopeKey);
|
|
97
|
+
return {
|
|
98
|
+
...scope,
|
|
99
|
+
...jiraContext,
|
|
100
|
+
jiraTaskFile: jiraTaskFile(scope.scopeKey),
|
|
101
|
+
};
|
|
93
102
|
}
|
|
94
103
|
export function buildJiraTaskInputForm() {
|
|
95
104
|
return {
|
|
@@ -108,11 +117,11 @@ export function buildJiraTaskInputForm() {
|
|
|
108
117
|
],
|
|
109
118
|
};
|
|
110
119
|
}
|
|
111
|
-
export async function
|
|
120
|
+
export async function requestJiraContext(requestUserInput) {
|
|
112
121
|
const result = await requestUserInput(buildJiraTaskInputForm());
|
|
113
122
|
const jiraRef = String(result.values.jira_ref ?? "").trim();
|
|
114
123
|
if (!jiraRef) {
|
|
115
124
|
throw new TaskRunnerError("Jira issue key or browse URL is required.");
|
|
116
125
|
}
|
|
117
|
-
return
|
|
126
|
+
return parseJiraContext(jiraRef);
|
|
118
127
|
}
|