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.
Files changed (50) hide show
  1. package/Dockerfile.codex +3 -3
  2. package/README.md +24 -10
  3. package/dist/artifacts.js +30 -0
  4. package/dist/executors/configs/fetch-gitlab-diff-config.js +3 -0
  5. package/dist/executors/configs/opencode-config.js +6 -0
  6. package/dist/executors/fetch-gitlab-diff-executor.js +26 -0
  7. package/dist/executors/jira-fetch-executor.js +8 -2
  8. package/dist/executors/opencode-executor.js +35 -0
  9. package/dist/gitlab.js +199 -5
  10. package/dist/index.js +160 -121
  11. package/dist/interactive-ui.js +45 -10
  12. package/dist/jira.js +116 -14
  13. package/dist/pipeline/auto-flow.js +1 -1
  14. package/dist/pipeline/declarative-flows.js +41 -6
  15. package/dist/pipeline/flow-catalog.js +66 -0
  16. package/dist/pipeline/flow-specs/auto.json +183 -1
  17. package/dist/pipeline/flow-specs/bug-analyze.json +1 -1
  18. package/dist/pipeline/flow-specs/gitlab-diff-review.json +226 -0
  19. package/dist/pipeline/flow-specs/gitlab-review.json +1 -31
  20. package/dist/pipeline/flow-specs/plan-opencode.json +603 -0
  21. package/dist/pipeline/flow-specs/plan.json +183 -1
  22. package/dist/pipeline/flow-specs/run-go-linter-loop.json +83 -7
  23. package/dist/pipeline/flow-specs/run-go-tests-loop.json +83 -7
  24. package/dist/pipeline/flow-specs/task-describe.json +1 -1
  25. package/dist/pipeline/node-registry.js +80 -8
  26. package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +34 -0
  27. package/dist/pipeline/nodes/flow-run-node.js +2 -2
  28. package/dist/pipeline/nodes/jira-fetch-node.js +26 -2
  29. package/dist/pipeline/nodes/local-script-check-node.js +50 -8
  30. package/dist/pipeline/nodes/opencode-prompt-node.js +32 -0
  31. package/dist/pipeline/nodes/planning-questions-form-node.js +69 -0
  32. package/dist/pipeline/nodes/user-input-node.js +9 -1
  33. package/dist/pipeline/prompt-registry.js +4 -1
  34. package/dist/pipeline/registry.js +10 -0
  35. package/dist/pipeline/spec-loader.js +37 -3
  36. package/dist/pipeline/spec-types.js +43 -1
  37. package/dist/pipeline/spec-validator.js +53 -7
  38. package/dist/pipeline/value-resolver.js +25 -1
  39. package/dist/prompts.js +54 -14
  40. package/dist/scope.js +25 -16
  41. package/dist/structured-artifact-schemas.json +560 -0
  42. package/dist/structured-artifacts.js +103 -262
  43. package/dist/user-input.js +7 -0
  44. package/docker-compose.yml +2 -2
  45. package/package.json +3 -3
  46. package/run_go_linter.py +128 -0
  47. package/run_go_tests.py +120 -0
  48. package/verify_build.sh +3 -3
  49. package/run_go_linter.sh +0 -89
  50. 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 || "artifact" in value || "artifactList" 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-файлы сделай как краткое производное представление этих JSON-артефактов для пользователя. " +
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
- "Для {design_json_file} используй объект: { summary: string, goals: string[], non_goals: string[], components: string[], decisions: [{ component: string, decision: string, rationale: string }], risks: string[], open_questions: string[] }. " +
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
- "Для {plan_json_file} используй объект: { summary: string, prerequisites: string[], implementation_steps: [{ id: string, title: string, details: string }], tests: string[], rollout_notes: string[] }. " +
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
- "Для {qa_json_file} используй объект: { summary: string, test_scenarios: [{ id: string, title: string, expected_result: string }], non_functional_checks: string[] }. " +
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
- "Для {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[] }. ";
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
- "Сначала запиши структурированный результат в {review_json_file} в виде объекта { summary: string, ready_to_merge: boolean, findings: [{ severity: string, title: string, description: string }] }. " +
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
- "Сначала запиши структурированный результат в {review_json_file} в виде объекта { summary: string, ready_to_merge: boolean, findings: [{ severity: string, title: string, description: string }] }. " +
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
- "Сначала запиши структурированный ответ в {review_reply_json_file} в виде объекта { summary: string, ready_to_merge: boolean, responses: [{ finding_title: string, disposition: string, action: string }] }. " +
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
- "Сначала запиши структурированный ответ в {review_reply_json_file} в виде объекта { summary: string, ready_to_merge: boolean, responses: [{ finding_title: string, disposition: string, action: string }] }. " +
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
- "По завершении сначала запиши структурированный отчёт в {review_fix_json_file} в виде объекта { summary: string, completed_actions: string[], validation_steps: string[] }, затем производную markdown-версию в {review_fix_file}.";
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 RUN_GO_TESTS_LOOP_FIX_PROMPT_TEMPLATE = "Запусти ./run_go_tests.sh, проанализируй последнюю ошибку проверки, исправь код и подготовь изменения так, чтобы следующий прогон run_go_tests.sh прошёл успешно.";
69
- export const RUN_GO_LINTER_LOOP_FIX_PROMPT_TEMPLATE = "Запусти ./run_go_linter.sh, проанализируй последнюю ошибку линтера или генерации, исправь код и подготовь изменения так, чтобы следующий прогон run_go_linter.sh прошёл успешно.";
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 resolveTaskScope(jiraRef, explicitScope) {
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
- return {
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 requestTaskScope(requestUserInput) {
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 resolveTaskScope(jiraRef);
126
+ return parseJiraContext(jiraRef);
118
127
  }