agentweaver 0.1.10 → 0.1.12

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 (110) hide show
  1. package/README.md +218 -224
  2. package/dist/artifacts.js +109 -55
  3. package/dist/executors/{codex-local-executor.js → codex-executor.js} +6 -5
  4. package/dist/executors/configs/{codex-local-config.js → codex-config.js} +1 -1
  5. package/dist/executors/configs/jira-fetch-config.js +2 -0
  6. package/dist/executors/configs/telegram-notifier-config.js +3 -0
  7. package/dist/executors/fetch-gitlab-diff-executor.js +1 -1
  8. package/dist/executors/fetch-gitlab-review-executor.js +1 -1
  9. package/dist/executors/git-commit-executor.js +25 -0
  10. package/dist/executors/jira-fetch-executor.js +1 -0
  11. package/dist/executors/opencode-executor.js +22 -11
  12. package/dist/executors/process-executor.js +3 -0
  13. package/dist/executors/telegram-notifier-executor.js +54 -0
  14. package/dist/flow-state.js +46 -1
  15. package/dist/gitlab.js +13 -8
  16. package/dist/index.js +477 -514
  17. package/dist/interactive-ui.js +609 -88
  18. package/dist/jira.js +109 -5
  19. package/dist/pipeline/auto-flow.js +6 -6
  20. package/dist/pipeline/context.js +1 -0
  21. package/dist/pipeline/flow-catalog.js +34 -4
  22. package/dist/pipeline/flow-model-settings.js +77 -0
  23. package/dist/pipeline/flow-specs/auto-common.json +446 -0
  24. package/dist/pipeline/flow-specs/auto-golang.json +563 -0
  25. package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
  26. package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
  27. package/dist/pipeline/flow-specs/git-commit.json +196 -0
  28. package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
  29. package/dist/pipeline/flow-specs/{gitlab-review.json → gitlab/gitlab-review.json} +65 -133
  30. package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
  31. package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
  32. package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
  33. package/dist/pipeline/flow-specs/implement.json +5 -4
  34. package/dist/pipeline/flow-specs/plan.json +40 -148
  35. package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +74 -13
  36. package/dist/pipeline/flow-specs/review/review-loop.json +282 -0
  37. package/dist/pipeline/flow-specs/review/review-project.json +87 -0
  38. package/dist/pipeline/flow-specs/review/review.json +126 -0
  39. package/dist/pipeline/flow-specs/task-describe.json +252 -11
  40. package/dist/pipeline/launch-profile-config.js +38 -0
  41. package/dist/pipeline/node-registry.js +75 -45
  42. package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
  43. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
  44. package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
  45. package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
  46. package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
  47. package/dist/pipeline/nodes/git-commit-node.js +28 -0
  48. package/dist/pipeline/nodes/git-status-node.js +221 -0
  49. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
  50. package/dist/pipeline/nodes/jira-context-node.js +10 -0
  51. package/dist/pipeline/nodes/jira-fetch-node.js +3 -0
  52. package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
  53. package/dist/pipeline/nodes/plan-codex-node.js +1 -1
  54. package/dist/pipeline/nodes/read-file-node.js +11 -0
  55. package/dist/pipeline/nodes/review-findings-form-node.js +48 -14
  56. package/dist/pipeline/nodes/select-files-form-node.js +72 -0
  57. package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
  58. package/dist/pipeline/nodes/user-input-node.js +43 -8
  59. package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
  60. package/dist/pipeline/prompt-registry.js +3 -4
  61. package/dist/pipeline/prompt-runtime.js +13 -3
  62. package/dist/pipeline/registry.js +6 -8
  63. package/dist/pipeline/spec-compiler.js +5 -0
  64. package/dist/pipeline/spec-types.js +9 -3
  65. package/dist/pipeline/spec-validator.js +4 -0
  66. package/dist/pipeline/types.js +1 -0
  67. package/dist/pipeline/value-resolver.js +50 -38
  68. package/dist/prompts.js +119 -110
  69. package/dist/runtime/agentweaver-home.js +8 -0
  70. package/dist/runtime/command-resolution.js +0 -38
  71. package/dist/runtime/env-loader.js +43 -0
  72. package/dist/runtime/process-runner.js +9 -3
  73. package/dist/structured-artifact-schema-registry.js +54 -0
  74. package/dist/structured-artifact-schemas.json +22 -20
  75. package/dist/structured-artifacts.js +3 -43
  76. package/dist/user-input.js +38 -3
  77. package/package.json +2 -6
  78. package/Dockerfile.codex +0 -56
  79. package/dist/executors/claude-executor.js +0 -46
  80. package/dist/executors/codex-docker-executor.js +0 -27
  81. package/dist/executors/configs/claude-config.js +0 -12
  82. package/dist/executors/configs/codex-docker-config.js +0 -10
  83. package/dist/executors/configs/verify-build-config.js +0 -7
  84. package/dist/executors/verify-build-executor.js +0 -123
  85. package/dist/pipeline/flow-specs/auto.json +0 -979
  86. package/dist/pipeline/flow-specs/opencode/auto-opencode.json +0 -1365
  87. package/dist/pipeline/flow-specs/opencode/bugz/bug-analyze-opencode.json +0 -382
  88. package/dist/pipeline/flow-specs/opencode/bugz/bug-fix-opencode.json +0 -56
  89. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-diff-review-opencode.json +0 -308
  90. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-review-opencode.json +0 -437
  91. package/dist/pipeline/flow-specs/opencode/gitlab/mr-description-opencode.json +0 -117
  92. package/dist/pipeline/flow-specs/opencode/go/run-go-linter-loop-opencode.json +0 -321
  93. package/dist/pipeline/flow-specs/opencode/go/run-go-tests-loop-opencode.json +0 -321
  94. package/dist/pipeline/flow-specs/opencode/implement-opencode.json +0 -64
  95. package/dist/pipeline/flow-specs/opencode/plan-opencode.json +0 -603
  96. package/dist/pipeline/flow-specs/opencode/review/review-fix-opencode.json +0 -209
  97. package/dist/pipeline/flow-specs/opencode/review/review-opencode.json +0 -452
  98. package/dist/pipeline/flow-specs/opencode/task-describe-opencode.json +0 -148
  99. package/dist/pipeline/flow-specs/review-project.json +0 -243
  100. package/dist/pipeline/flow-specs/review.json +0 -312
  101. package/dist/pipeline/flows/preflight-flow.js +0 -19
  102. package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
  103. package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
  104. package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
  105. package/dist/pipeline/nodes/review-claude-node.js +0 -38
  106. package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
  107. package/dist/pipeline/nodes/verify-build-node.js +0 -15
  108. package/dist/runtime/docker-runtime.js +0 -51
  109. package/docker-compose.yml +0 -445
  110. package/verify_build.sh +0 -105
package/dist/index.js CHANGED
@@ -1,29 +1,37 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
- import { REVIEW_FILE_RE, REVIEW_REPLY_FILE_RE, autoStateFile, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewFile, reviewReplyJsonFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, taskSummaryFile, } from "./artifacts.js";
6
+ import { REVIEW_FILE_RE, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, latestArtifactIteration, nextArtifactIteration, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
7
7
  import { FlowInterruptedError, TaskRunnerError } from "./errors.js";
8
- import { createFlowRunState, hasResumableFlowState, loadFlowRunState, prepareFlowStateForResume, resetFlowRunState, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
8
+ import { createFlowRunState, hasResumableFlowState, loadFlowRunState, prepareFlowStateForResume, resetFlowRunState, rewindFlowRunStateToPhase, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
9
9
  import { requireJiraTaskFile } from "./jira.js";
10
10
  import { validateStructuredArtifacts } from "./structured-artifacts.js";
11
11
  import { summarizeBuildFailure as summarizeBuildFailureViaPipeline } from "./pipeline/build-failure-summary.js";
12
+ import { runNodeChecks } from "./pipeline/checks.js";
12
13
  import { createPipelineContext } from "./pipeline/context.js";
13
- import { loadAutoFlow } from "./pipeline/auto-flow.js";
14
14
  import { loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
15
- import { findPhaseById, runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
15
+ import { runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
16
16
  import { findCatalogEntry, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef } from "./pipeline/flow-catalog.js";
17
- import { resolveCmd, resolveDockerComposeCmd } from "./runtime/command-resolution.js";
18
- import { agentweaverHome, defaultDockerComposeFile, dockerRuntimeEnv } from "./runtime/docker-runtime.js";
17
+ import { ALLOWED_MODELS_BY_EXECUTOR, defaultModelForExecutor, DEFAULT_LAUNCH_PROFILE, LLM_EXECUTOR_IDS, resolveLaunchProfile, } from "./pipeline/launch-profile-config.js";
18
+ import { evaluateCondition, resolveValue } from "./pipeline/value-resolver.js";
19
+ import { resolveCmd } from "./runtime/command-resolution.js";
20
+ import { loadTieredEnv } from "./runtime/env-loader.js";
21
+ import { agentweaverHome } from "./runtime/agentweaver-home.js";
19
22
  import { runCommand } from "./runtime/process-runner.js";
20
23
  import { InteractiveUi } from "./interactive-ui.js";
21
24
  import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
22
25
  import { requestUserInputInTerminal } from "./user-input.js";
23
26
  import { attachJiraContext, detectGitBranchName, requestJiraContext, resolveProjectScope, } from "./scope.js";
24
27
  const COMMANDS = [
28
+ "auto-golang",
29
+ "auto-common",
30
+ "auto-status",
31
+ "auto-reset",
25
32
  "bug-analyze",
26
33
  "bug-fix",
34
+ "git-commit",
27
35
  "gitlab-diff-review",
28
36
  "gitlab-review",
29
37
  "mr-description",
@@ -32,20 +40,15 @@ const COMMANDS = [
32
40
  "implement",
33
41
  "review",
34
42
  "review-fix",
43
+ "review-loop",
35
44
  "run-go-tests-loop",
36
45
  "run-go-linter-loop",
37
- "auto",
38
- "auto-status",
39
- "auto-reset",
40
46
  ];
41
- const AUTO_STATE_SCHEMA_VERSION = 3;
42
47
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
43
48
  const PACKAGE_ROOT = path.resolve(MODULE_DIR, "..");
44
49
  function createRuntimeServices(signal) {
45
50
  return {
46
51
  resolveCmd,
47
- resolveDockerComposeCmd,
48
- dockerRuntimeEnv: () => dockerRuntimeEnv(PACKAGE_ROOT),
49
52
  runCommand: (argv, options = {}) => runCommand(argv, { ...options, ...(signal ? { signal } : {}) }),
50
53
  };
51
54
  }
@@ -81,26 +84,31 @@ function formatProcessFailure(error) {
81
84
  }
82
85
  return `${baseMessage}\nПричина:\n${preview}`;
83
86
  }
87
+ function escapeRegExp(value) {
88
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
89
+ }
84
90
  function usage() {
85
91
  return `Usage:
86
92
  agentweaver
87
93
  agentweaver <jira-browse-url|jira-issue-key>
88
94
  agentweaver --force <jira-browse-url|jira-issue-key>
95
+ agentweaver git-commit [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
89
96
  agentweaver gitlab-diff-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
90
97
  agentweaver gitlab-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
91
98
  agentweaver bug-analyze [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
92
99
  agentweaver bug-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
93
100
  agentweaver mr-description [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
94
- agentweaver plan [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
95
- agentweaver task-describe [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
101
+ agentweaver plan [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
102
+ agentweaver task-describe [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
96
103
  agentweaver implement [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
97
104
  agentweaver review [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
98
105
  agentweaver review-fix [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
106
+ agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
99
107
  agentweaver run-go-tests-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
100
108
  agentweaver run-go-linter-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
101
- agentweaver auto [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
102
- agentweaver auto [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
103
- agentweaver auto --help-phases
109
+ agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
110
+ agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
111
+ agentweaver auto-golang --help-phases
104
112
  agentweaver auto-status [<jira-browse-url|jira-issue-key>]
105
113
  agentweaver auto-reset [<jira-browse-url|jira-issue-key>]
106
114
 
@@ -112,28 +120,28 @@ Interactive Mode:
112
120
  Flags:
113
121
  --version Show package version
114
122
  --force In interactive mode, regenerate task summary in Jira-backed flows
115
- --dry Fetch Jira task, but print docker/codex/claude commands instead of executing them
123
+ --dry Fetch Jira task, but print codex/opencode commands instead of executing them
116
124
  --verbose Show live stdout/stderr of launched commands
117
125
  --scope Explicit workflow scope name for non-Jira runs
118
126
  --prompt Extra prompt text appended to the base prompt
127
+ --md-lang Language for markdown output files: en (English) or ru (Russian, default)
119
128
 
120
129
  Required environment variables:
121
- JIRA_API_KEY Jira API key used in Authorization: Bearer <token> for Jira-backed flows
130
+ JIRA_API_KEY Jira API token used for Jira-backed flows (Bearer by default, or Basic with Jira Cloud)
122
131
 
123
132
  Optional environment variables:
133
+ JIRA_USERNAME Required for Jira Cloud Basic auth (usually Atlassian account email)
134
+ JIRA_AUTH_MODE Override Jira auth mode: auto | basic | bearer
124
135
  JIRA_BASE_URL
125
136
  GITLAB_TOKEN
126
137
  AGENTWEAVER_HOME
127
- DOCKER_COMPOSE_BIN
128
138
  CODEX_BIN
129
139
  CODEX_MODEL
130
140
  OPENCODE_BIN
131
141
  OPENCODE_MODEL
132
- CLAUDE_BIN
133
- CLAUDE_MODEL
134
142
 
135
143
  Notes:
136
- - Task-only flows will ask for Jira task via user-input when it is not passed as an argument.
144
+ - Jira-backed task flows will ask for Jira task via user-input when it is not passed as an argument. task-describe can also work from a manual task description without Jira.
137
145
  - All flow state and artifacts are stored in the current project scope by default.
138
146
  - gitlab-review and gitlab-diff-review ask for GitLab merge request URL via user-input.`;
139
147
  }
@@ -145,212 +153,240 @@ function packageVersion() {
145
153
  }
146
154
  return raw.version;
147
155
  }
148
- function nowIso8601() {
149
- return new Date().toISOString();
150
- }
151
156
  function normalizeAutoPhaseId(phaseId) {
152
157
  return phaseId.trim().toLowerCase().replaceAll("-", "_");
153
158
  }
154
- function buildAutoSteps() {
155
- return loadAutoFlow().phases.map((phase) => ({
156
- id: phase.id,
157
- status: "pending",
158
- }));
159
- }
160
159
  function autoPhaseIds() {
161
- return buildAutoSteps().map((step) => step.id);
160
+ return loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }).phases.map((phase) => phase.id);
162
161
  }
163
162
  function validateAutoPhaseId(phaseId) {
164
163
  const normalized = normalizeAutoPhaseId(phaseId);
165
164
  if (!autoPhaseIds().includes(normalized)) {
166
- throw new TaskRunnerError(`Unknown auto phase: ${phaseId}\nUse 'agentweaver auto --help-phases' or '/help auto' to list valid phases.`);
165
+ throw new TaskRunnerError(`Unknown auto-golang phase: ${phaseId}\nUse 'agentweaver auto-golang --help-phases' or '/help auto-golang' to list valid phases.`);
167
166
  }
168
167
  return normalized;
169
168
  }
170
- function createAutoPipelineState(config) {
171
- const autoFlow = loadAutoFlow();
172
- const maxReviewIterations = autoFlow.phases.filter((phase) => /^review_\d+$/.test(phase.id)).length;
173
- return {
174
- schemaVersion: AUTO_STATE_SCHEMA_VERSION,
175
- issueKey: config.taskKey,
176
- jiraRef: config.jiraRef,
177
- status: "pending",
178
- currentStep: null,
179
- maxReviewIterations,
180
- updatedAt: nowIso8601(),
181
- steps: buildAutoSteps(),
182
- executionState: {
183
- flowKind: autoFlow.kind,
184
- flowVersion: autoFlow.version,
185
- terminated: false,
186
- phases: [],
187
- },
188
- };
189
- }
190
- function loadAutoPipelineState(config) {
191
- const filePath = autoStateFile(config.taskKey);
192
- if (!existsSync(filePath)) {
193
- return null;
194
- }
195
- let raw;
196
- try {
197
- raw = JSON.parse(readFileSync(filePath, "utf8"));
169
+ function buildFlowResumeDetails(state) {
170
+ const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
171
+ const lines = [
172
+ "Interrupted run found.",
173
+ `Current step: ${currentStep}`,
174
+ `Updated: ${state.updatedAt}`,
175
+ ];
176
+ if (state.launchProfile) {
177
+ lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
198
178
  }
199
- catch (error) {
200
- throw new TaskRunnerError(`Failed to parse auto state file ${filePath}: ${error.message}`);
201
- }
202
- if (!raw || typeof raw !== "object") {
203
- throw new TaskRunnerError(`Invalid auto state file format: ${filePath}`);
204
- }
205
- const state = raw;
206
- if (state.schemaVersion !== AUTO_STATE_SCHEMA_VERSION) {
207
- throw new TaskRunnerError(`Unsupported auto state schema in ${filePath}: ${state.schemaVersion}`);
208
- }
209
- if (!state.executionState) {
210
- const autoFlow = loadAutoFlow();
211
- state.executionState = {
212
- flowKind: autoFlow.kind,
213
- flowVersion: autoFlow.version,
214
- terminated: false,
215
- phases: [],
216
- };
179
+ if (state.lastError) {
180
+ lines.push(`Last error: ${state.lastError.message ?? "-"} (exit ${state.lastError.returnCode ?? "-"})`);
217
181
  }
218
- syncAutoStepsFromExecutionState(state);
219
- return state;
182
+ return lines.join("\n");
183
+ }
184
+ function launchProfileSelectionForm() {
185
+ const defaultExecutor = DEFAULT_LAUNCH_PROFILE.executor;
186
+ return {
187
+ formId: "flow-launch-profile",
188
+ title: "Настройки запуска LLM",
189
+ description: `Выберите executor для запуска flow. Текущий default: ${defaultExecutor}.`,
190
+ submitLabel: "Continue",
191
+ fields: [
192
+ {
193
+ id: "executor",
194
+ type: "single-select",
195
+ label: "Executor",
196
+ required: true,
197
+ default: defaultExecutor,
198
+ options: LLM_EXECUTOR_IDS.map((id) => ({
199
+ value: id,
200
+ label: id === defaultExecutor ? `${id} [default]` : id,
201
+ })),
202
+ },
203
+ ],
204
+ };
220
205
  }
221
- function saveAutoPipelineState(state) {
222
- state.updatedAt = nowIso8601();
223
- ensureScopeWorkspaceDir(state.issueKey);
224
- writeFileSync(autoStateFile(state.issueKey), `${JSON.stringify({
225
- ...state,
226
- executionState: stripExecutionStatePayload(state.executionState),
227
- }, null, 2)}\n`, "utf8");
206
+ function launchModelSelectionForm(executor) {
207
+ const resolvedExecutor = executor === "default" ? DEFAULT_LAUNCH_PROFILE.executor : executor;
208
+ const defaultModel = defaultModelForExecutor(resolvedExecutor);
209
+ const options = ALLOWED_MODELS_BY_EXECUTOR[resolvedExecutor].map((model) => ({
210
+ value: model,
211
+ label: model === defaultModel ? `${model} [default]` : model,
212
+ }));
213
+ return {
214
+ formId: "flow-launch-model",
215
+ title: "Настройки запуска LLM",
216
+ description: `Выберите модель для запуска flow. Текущий default для ${resolvedExecutor}: ${defaultModel}.`,
217
+ submitLabel: "Start",
218
+ fields: [
219
+ {
220
+ id: "model",
221
+ type: "single-select",
222
+ label: "Model",
223
+ required: true,
224
+ default: defaultModel,
225
+ options,
226
+ },
227
+ ],
228
+ };
228
229
  }
229
- function syncAndSaveAutoPipelineState(state) {
230
- syncAutoStepsFromExecutionState(state);
231
- saveAutoPipelineState(state);
230
+ function isFormCancellation(error, formId) {
231
+ return error instanceof TaskRunnerError && error.message === `User cancelled form '${formId}'.`;
232
232
  }
233
- function resetAutoPipelineState(config) {
234
- const filePath = autoStateFile(config.taskKey);
235
- if (!existsSync(filePath)) {
236
- return false;
233
+ async function requestInteractiveLaunchProfile(requestUserInput) {
234
+ for (;;) {
235
+ const executorFormResult = await requestUserInput(launchProfileSelectionForm());
236
+ const rawExecutor = String(executorFormResult.values.executor ?? DEFAULT_LAUNCH_PROFILE.executor);
237
+ const executor = LLM_EXECUTOR_IDS.find((id) => id === rawExecutor);
238
+ if (!executor) {
239
+ throw new TaskRunnerError(`Unsupported launch executor '${rawExecutor}'.`);
240
+ }
241
+ try {
242
+ const modelFormResult = await requestUserInput(launchModelSelectionForm(executor));
243
+ const rawModel = String(modelFormResult.values.model ?? defaultModelForExecutor(executor)).trim();
244
+ return resolveLaunchProfile({
245
+ executor,
246
+ model: rawModel.length > 0 ? rawModel : defaultModelForExecutor(executor),
247
+ }, DEFAULT_LAUNCH_PROFILE);
248
+ }
249
+ catch (error) {
250
+ if (isFormCancellation(error, "flow-launch-model")) {
251
+ continue;
252
+ }
253
+ throw error;
254
+ }
237
255
  }
238
- rmSync(filePath);
239
- return true;
240
256
  }
241
- function nextAutoStep(state) {
242
- return state.steps.find((step) => ["running", "failed", "pending"].includes(step.status)) ?? null;
257
+ function buildResolverContext(pipelineContext, flowParams, flowConstants, repeatVars, executionState) {
258
+ return {
259
+ flowParams,
260
+ flowConstants,
261
+ pipelineContext,
262
+ repeatVars,
263
+ executionState,
264
+ };
243
265
  }
244
- function findCurrentExecutionStep(state) {
245
- for (const phase of state.executionState.phases) {
246
- const runningStep = phase.steps.find((step) => step.status === "running");
247
- if (runningStep) {
248
- return `${phase.id}:${runningStep.id}`;
266
+ function resolveResumeChecks(step, context) {
267
+ return (step.expect ?? [])
268
+ .filter((expectation) => evaluateCondition(expectation.when, context))
269
+ .flatMap((expectation) => {
270
+ if (expectation.kind === "step-output") {
271
+ const value = resolveValue(expectation.value, context);
272
+ if (expectation.equals !== undefined) {
273
+ const expected = resolveValue(expectation.equals, context);
274
+ if (value !== expected) {
275
+ throw new TaskRunnerError(expectation.message);
276
+ }
277
+ return [];
278
+ }
279
+ if (!value) {
280
+ throw new TaskRunnerError(expectation.message);
281
+ }
282
+ return [];
249
283
  }
250
- }
251
- return null;
284
+ if (expectation.kind === "require-artifacts") {
285
+ const value = resolveValue(expectation.paths, context);
286
+ if (!Array.isArray(value) || value.some((candidate) => typeof candidate !== "string")) {
287
+ throw new TaskRunnerError("Expectation 'require-artifacts' must resolve to string[]");
288
+ }
289
+ return [{ kind: "require-artifacts", paths: value, message: expectation.message }];
290
+ }
291
+ if (expectation.kind === "require-file") {
292
+ const value = resolveValue(expectation.path, context);
293
+ if (typeof value !== "string") {
294
+ throw new TaskRunnerError("Expectation 'require-file' must resolve to string");
295
+ }
296
+ return [{ kind: "require-file", path: value, message: expectation.message }];
297
+ }
298
+ const items = expectation.items.map((item) => {
299
+ const value = resolveValue(item.path, context);
300
+ if (typeof value !== "string") {
301
+ throw new TaskRunnerError("Expectation 'require-structured-artifacts' item path must resolve to string");
302
+ }
303
+ return {
304
+ path: value,
305
+ schemaId: item.schemaId,
306
+ };
307
+ });
308
+ return [{ kind: "require-structured-artifacts", items, message: expectation.message }];
309
+ });
252
310
  }
253
- function deriveAutoPipelineStatus(state) {
254
- if (state.lastError || state.steps.some((candidate) => candidate.status === "failed")) {
255
- return "blocked";
256
- }
257
- if (state.executionState.terminated) {
258
- return "completed";
259
- }
260
- if (state.steps.some((candidate) => candidate.status === "running")) {
261
- return "running";
262
- }
263
- if (state.steps.some((candidate) => candidate.status === "pending")) {
264
- return "pending";
265
- }
266
- if (state.steps.some((candidate) => candidate.status === "skipped")) {
267
- return "completed";
311
+ function validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext, flowParams, flowConstants, executionState) {
312
+ if (phaseState.status === "done") {
313
+ return;
268
314
  }
269
- if (state.steps.every((candidate) => candidate.status === "done")) {
270
- return "completed";
315
+ for (const [stepIndex, step] of phase.steps.entries()) {
316
+ const stepState = phaseState.steps[stepIndex];
317
+ if (!stepState || stepState.status !== "done") {
318
+ continue;
319
+ }
320
+ const context = buildResolverContext(pipelineContext, flowParams, flowConstants, step.repeatVars, executionState);
321
+ const checks = resolveResumeChecks(step, context);
322
+ try {
323
+ runNodeChecks(checks);
324
+ }
325
+ catch (error) {
326
+ throw new TaskRunnerError(`Resume is impossible for '${phase.id}:${step.id}' because required artifacts are missing or invalid. Use restart.\n${error.message}`);
327
+ }
271
328
  }
272
- return state.status;
273
329
  }
274
- function printAutoState(state) {
275
- const currentStep = findCurrentExecutionStep(state) ?? state.currentStep ?? "-";
276
- const lines = [
277
- `Issue: ${state.issueKey}`,
278
- `Status: ${deriveAutoPipelineStatus(state)}`,
279
- `Current step: ${currentStep}`,
280
- `Updated: ${state.updatedAt}`,
281
- ];
282
- if (state.lastError) {
283
- lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
284
- }
285
- lines.push("");
286
- for (const step of state.steps) {
287
- lines.push(`[${step.status}] ${step.id}${step.note ? ` (${step.note})` : ""}`);
288
- const phaseState = state.executionState.phases.find((candidate) => candidate.id === step.id);
289
- for (const childStep of phaseState?.steps ?? []) {
290
- lines.push(` - [${childStep.status}] ${childStep.id}`);
330
+ function validateDeclarativeFlowResumeState(flowEntry, config, state, launchProfile, runtime = runtimeServices) {
331
+ if (state.launchProfile) {
332
+ if (!launchProfile) {
333
+ throw new TaskRunnerError("Resume is impossible because launch profile is missing. Use restart.");
334
+ }
335
+ if (state.launchProfile.fingerprint !== launchProfile.fingerprint) {
336
+ throw new TaskRunnerError(`Resume is impossible because launch profile changed (${state.launchProfile.executor}/${state.launchProfile.model} -> ${launchProfile.executor}/${launchProfile.model}). Use restart.`);
291
337
  }
292
338
  }
293
- if (state.executionState.terminated) {
294
- lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
339
+ if (flowRequiresTaskScope(flowEntry) && !config.jiraRef) {
340
+ throw new TaskRunnerError("Resume is impossible because Jira context is missing for this flow state. Use restart.");
295
341
  }
296
- printPanel("Auto Status", lines.join("\n"), "cyan");
297
- }
298
- function syncAutoStepsFromExecutionState(state) {
299
- for (const step of state.steps) {
300
- const phaseState = state.executionState.phases.find((candidate) => candidate.id === step.id);
342
+ const pipelineContext = createPipelineContext({
343
+ issueKey: config.taskKey,
344
+ jiraRef: config.jiraRef,
345
+ dryRun: config.dryRun,
346
+ verbose: config.verbose,
347
+ ...(config.mdLang !== undefined ? { mdLang: config.mdLang } : {}),
348
+ runtime,
349
+ requestUserInput: requestUserInputInTerminal,
350
+ });
351
+ const flowParams = defaultDeclarativeFlowParams(config, false, launchProfile ? { launchProfile } : {});
352
+ for (const phase of flowEntry.flow.phases) {
353
+ const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
301
354
  if (!phaseState) {
302
355
  continue;
303
356
  }
304
- step.status = phaseState.status;
305
- step.startedAt = phaseState.startedAt ?? null;
306
- step.finishedAt = phaseState.finishedAt ?? null;
307
- step.note = null;
308
- if (phaseState.status === "skipped") {
309
- step.note = "condition not met";
310
- step.returnCode ??= 0;
311
- }
312
- else if (phaseState.status === "done") {
313
- step.returnCode ??= 0;
314
- if (state.executionState.terminated && state.executionState.terminationReason?.startsWith(`Stopped by ${step.id}:`)) {
315
- step.note = "stop condition met";
316
- }
317
- }
318
- }
319
- state.currentStep = findCurrentExecutionStep(state);
320
- state.status = deriveAutoPipelineStatus(state);
321
- }
322
- function buildAutoResumeDetails(state) {
323
- const currentStep = findCurrentExecutionStep(state) ?? state.currentStep ?? "-";
324
- const lines = [
325
- "Interrupted auto run found.",
326
- `Current step: ${currentStep}`,
327
- `Updated: ${state.updatedAt}`,
328
- ];
329
- if (state.lastError) {
330
- lines.push(`Last error: ${state.lastError.message ?? "-"} (exit ${state.lastError.returnCode ?? "-"})`);
357
+ validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext, flowParams, flowEntry.flow.constants, state.executionState);
331
358
  }
332
- return lines.join("\n");
333
359
  }
334
- function buildFlowResumeDetails(state) {
335
- const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
336
- const lines = [
337
- "Interrupted run found.",
338
- `Current step: ${currentStep}`,
339
- `Updated: ${state.updatedAt}`,
340
- ];
341
- if (state.lastError) {
342
- lines.push(`Last error: ${state.lastError.message ?? "-"} (exit ${state.lastError.returnCode ?? "-"})`);
360
+ function scopeWithRestoredJiraContext(scope, state) {
361
+ if (scope.jiraRef || !state?.jiraRef?.trim()) {
362
+ return scope;
343
363
  }
344
- return lines.join("\n");
364
+ return attachJiraContext(scope, state.jiraRef);
345
365
  }
346
366
  function lookupInteractiveFlowResume(flowEntry, currentScope) {
347
367
  const directState = loadFlowRunState(currentScope.scopeKey, flowEntry.id);
348
368
  if (directState && hasResumableFlowState(directState)) {
349
- return {
350
- resumeAvailable: true,
351
- hasExistingState: true,
352
- details: buildFlowResumeDetails(directState),
353
- };
369
+ try {
370
+ const effectiveScope = scopeWithRestoredJiraContext(currentScope, directState);
371
+ const baseConfig = buildBaseConfig(flowEntry.id, {
372
+ ...(effectiveScope.jiraRef ? { jiraRef: effectiveScope.jiraRef } : {}),
373
+ scopeName: effectiveScope.scopeKey,
374
+ });
375
+ const config = buildRuntimeConfig(baseConfig, effectiveScope);
376
+ validateDeclarativeFlowResumeState(flowEntry, config, directState, directState.launchProfile);
377
+ return {
378
+ resumeAvailable: true,
379
+ hasExistingState: true,
380
+ details: buildFlowResumeDetails(directState),
381
+ };
382
+ }
383
+ catch (error) {
384
+ return {
385
+ resumeAvailable: false,
386
+ hasExistingState: true,
387
+ details: `Interrupted run found, but resume is unavailable.\n${error.message}`,
388
+ };
389
+ }
354
390
  }
355
391
  return {
356
392
  resumeAvailable: false,
@@ -358,37 +394,17 @@ function lookupInteractiveFlowResume(flowEntry, currentScope) {
358
394
  };
359
395
  }
360
396
  function printAutoPhasesHelp() {
361
- const phaseLines = ["Available auto phases:", "", ...autoPhaseIds()];
362
- phaseLines.push("", "You can resume auto from a phase with:", "agentweaver auto --from <phase> <jira>", "or in interactive mode:", "/auto --from <phase>");
363
- printPanel("Auto Phases", phaseLines.join("\n"), "magenta");
397
+ const phaseLines = ["Available auto-golang phases:", "", ...autoPhaseIds()];
398
+ phaseLines.push("", "You can resume auto-golang from a phase with:", "agentweaver auto-golang --from <phase> <jira>", "or in interactive mode:", "/auto-golang --from <phase>");
399
+ printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
364
400
  }
365
- function loadEnvFile(envFilePath) {
366
- if (!existsSync(envFilePath)) {
367
- return;
368
- }
369
- const lines = readFileSync(envFilePath, "utf8").split(/\r?\n/);
370
- for (const rawLine of lines) {
371
- let line = rawLine.trim();
372
- if (!line || line.startsWith("#")) {
373
- continue;
374
- }
375
- if (line.startsWith("export ")) {
376
- line = line.slice(7).trim();
377
- }
378
- const separatorIndex = line.indexOf("=");
379
- if (separatorIndex < 0) {
380
- continue;
381
- }
382
- const key = line.slice(0, separatorIndex).trim();
383
- if (!key || process.env[key] !== undefined) {
384
- continue;
385
- }
386
- let value = line.slice(separatorIndex + 1).trim();
387
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
388
- value = value.slice(1, -1);
389
- }
390
- process.env[key] = value;
391
- }
401
+ function autoCommonPhaseIds() {
402
+ return loadDeclarativeFlow({ source: "built-in", fileName: "auto-common.json" }).phases.map((phase) => phase.id);
403
+ }
404
+ function printAutoCommonPhasesHelp() {
405
+ const phaseLines = ["Available auto-common phases:", "", ...autoCommonPhaseIds()];
406
+ phaseLines.push("", "You can run auto-common with:", "agentweaver auto-common <jira>");
407
+ printPanel("Auto-Common Phases", phaseLines.join("\n"), "magenta");
392
408
  }
393
409
  function nextReviewIterationForTask(taskKey) {
394
410
  let maxIndex = 0;
@@ -400,33 +416,14 @@ function nextReviewIterationForTask(taskKey) {
400
416
  if (!entry.isFile()) {
401
417
  continue;
402
418
  }
403
- const match = REVIEW_FILE_RE.exec(entry.name) ?? REVIEW_REPLY_FILE_RE.exec(entry.name);
419
+ const match = REVIEW_FILE_RE.exec(entry.name);
404
420
  if (match && match[1] === taskKey) {
405
421
  maxIndex = Math.max(maxIndex, Number.parseInt(match[2] ?? "0", 10));
406
422
  }
407
423
  }
408
424
  return maxIndex + 1;
409
425
  }
410
- function latestReviewReplyIteration(taskKey) {
411
- let maxIndex = null;
412
- const workspaceDir = scopeWorkspaceDir(taskKey);
413
- if (!existsSync(workspaceDir)) {
414
- return null;
415
- }
416
- for (const entry of readdirSync(workspaceDir, { withFileTypes: true })) {
417
- if (!entry.isFile()) {
418
- continue;
419
- }
420
- const match = REVIEW_REPLY_FILE_RE.exec(entry.name);
421
- if (match && match[1] === taskKey) {
422
- const current = Number.parseInt(match[2] ?? "0", 10);
423
- maxIndex = maxIndex === null ? current : Math.max(maxIndex, current);
424
- }
425
- }
426
- return maxIndex;
427
- }
428
426
  function buildBaseConfig(command, options = {}) {
429
- const homeDir = agentweaverHome(PACKAGE_ROOT);
430
427
  return {
431
428
  command,
432
429
  jiraRef: options.jiraRef ?? null,
@@ -434,12 +431,9 @@ function buildBaseConfig(command, options = {}) {
434
431
  reviewFixPoints: options.reviewFixPoints ?? null,
435
432
  extraPrompt: options.extraPrompt ?? null,
436
433
  autoFromPhase: options.autoFromPhase ? validateAutoPhaseId(options.autoFromPhase) : null,
434
+ mdLang: options.mdLang ?? null,
437
435
  dryRun: options.dryRun ?? false,
438
436
  verbose: options.verbose ?? false,
439
- dockerComposeFile: defaultDockerComposeFile(PACKAGE_ROOT),
440
- runGoTestsScript: path.join(homeDir, "run_go_tests.py"),
441
- runGoLinterScript: path.join(homeDir, "run_go_linter.py"),
442
- runGoCoverageScript: path.join(homeDir, "run_go_coverage.sh"),
443
437
  };
444
438
  }
445
439
  function commandRequiresTask(command) {
@@ -447,17 +441,20 @@ function commandRequiresTask(command) {
447
441
  command === "bug-analyze" ||
448
442
  command === "bug-fix" ||
449
443
  command === "mr-description" ||
450
- command === "task-describe" ||
451
- command === "auto" ||
444
+ command === "auto-golang" ||
445
+ command === "auto-common" ||
452
446
  command === "auto-status" ||
453
447
  command === "auto-reset");
454
448
  }
455
449
  function commandSupportsProjectScope(command) {
456
- return (command === "gitlab-diff-review" ||
450
+ return (command === "git-commit" ||
451
+ command === "gitlab-diff-review" ||
457
452
  command === "gitlab-review" ||
453
+ command === "task-describe" ||
458
454
  command === "implement" ||
459
455
  command === "review" ||
460
456
  command === "review-fix" ||
457
+ command === "review-loop" ||
461
458
  command === "run-go-tests-loop" ||
462
459
  command === "run-go-linter-loop");
463
460
  }
@@ -506,41 +503,40 @@ function checkPrerequisites(config) {
506
503
  config.command === "run-go-linter-loop") {
507
504
  resolveCmd("codex", "CODEX_BIN");
508
505
  }
509
- if (config.command === "review" || config.command === "gitlab-diff-review") {
510
- resolveCmd("claude", "CLAUDE_BIN");
511
- }
512
506
  }
513
507
  function checkAutoPrerequisites(config) {
514
508
  resolveCmd("codex", "CODEX_BIN");
515
- resolveCmd("claude", "CLAUDE_BIN");
516
509
  }
517
510
  function autoFlowParams(config, forceRefreshSummary = false) {
518
511
  return {
519
512
  jiraApiUrl: config.jiraApiUrl,
520
513
  taskKey: config.taskKey,
521
- dockerComposeFile: config.dockerComposeFile,
522
- runGoTestsScript: config.runGoTestsScript,
523
- runGoLinterScript: config.runGoLinterScript,
524
- runGoCoverageScript: config.runGoCoverageScript,
525
514
  extraPrompt: config.extraPrompt,
526
515
  reviewFixPoints: config.reviewFixPoints,
527
516
  forceRefresh: forceRefreshSummary,
517
+ mdLang: config.mdLang,
518
+ runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
519
+ runGoLinterScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_linter.py"),
520
+ runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
521
+ runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
528
522
  };
529
523
  }
530
524
  const FLOW_DESCRIPTIONS = {
531
- auto: "Полный пайплайн задачи: планирование, реализация, проверки, ревью, ответы на ревью и повторные итерации до готовности к merge.",
532
- "bug-analyze": "Анализирует баг по Jira и создаёт структурированные артефакты: гипотезу причины, дизайн исправления и план работ.",
533
- "gitlab-diff-review": "Запрашивает GitLab MR URL через user-input, загружает diff merge request по API и запускает код-ревью через Claude Opus с сохранением markdown и structured JSON artifacts.",
534
- "gitlab-review": "Запрашивает GitLab MR URL через user-input, загружает комментарии код-ревью по API и сохраняет markdown плюс structured JSON artifact.",
535
- "bug-fix": "Берёт результаты bug-analyze как source of truth и реализует исправление бага в коде.",
536
- "mr-description": "Готовит краткое intent-описание для merge request на основе задачи и текущих изменений.",
537
- plan: "Загружает задачу из Jira и создаёт дизайн, план реализации и QA-план в structured JSON и markdown.",
538
- "task-describe": "Строит короткое резюме задачи на основе Jira-артефакта для быстрого ознакомления.",
539
- implement: "Реализует задачу по утверждённым design/plan артефактам и при необходимости запускает post-verify сборки.",
540
- review: "Запускает Claude-код-ревью текущих изменений, валидирует structured findings, затем готовит ответ на замечания через Codex.",
541
- "review-fix": "Исправляет замечания после review-reply, обновляет код и прогоняет обязательные проверки после правок.",
542
- "run-go-tests-loop": "Циклически запускает `./run_go_tests.py` локально, анализирует последнюю ошибку и правит код до успешного прохождения или исчерпания попыток.",
543
- "run-go-linter-loop": "Циклически запускает `./run_go_linter.py` локально, исправляет проблемы линтера или генерации и повторяет попытки до успеха.",
525
+ "auto-golang": "Full task pipeline: planning, implementation, checks, review, review replies, and repeated iterations until ready to merge.",
526
+ "bug-analyze": "Analyzes bug from Jira and creates structured artifacts: root cause hypothesis, fix design, and implementation plan.",
527
+ "git-commit": "Collects git status/diff, generates commit message via LLM, allows file selection and commit confirmation.",
528
+ "gitlab-diff-review": "Requests GitLab MR URL via user-input, downloads merge request diff via API, and runs code review with markdown and structured JSON artifacts.",
529
+ "gitlab-review": "Requests GitLab MR URL via user-input, downloads code review comments via API, assesses which findings are fair and proposes fixes, then runs review-fix for the selected findings.",
530
+ "bug-fix": "Takes bug-analyze results as source of truth and implements the bug fix in code.",
531
+ "mr-description": "Prepares a brief intent description for a merge request based on the task and current changes.",
532
+ plan: "Loads task from Jira and creates design, implementation plan, and QA plan in structured JSON and markdown.",
533
+ "task-describe": "Builds a brief task description either from Jira or from quick user-input without Jira.",
534
+ implement: "Implements the task from approved design/plan artifacts and runs post-verify builds if needed.",
535
+ review: "Runs code review of current changes and writes structured findings artifacts.",
536
+ "review-fix": "Fixes issues after review-reply, updates code, and runs mandatory checks after modifications.",
537
+ "review-loop": "Iteratively runs review and review-fix cycles up to 5 times until ready-to-merge is achieved.",
538
+ "run-go-tests-loop": "Cycles through `./run_go_tests.py` locally, analyzes the last error, and fixes code until successful or attempts exhausted.",
539
+ "run-go-linter-loop": "Cycles through `./run_go_linter.py` locally, fixes linter or generation issues, and retries until success.",
544
540
  };
545
541
  function flowDescription(id) {
546
542
  return FLOW_DESCRIPTIONS[id] ?? "Описание для этого flow пока не задано.";
@@ -602,12 +598,13 @@ function findCurrentFlowExecutionStep(state) {
602
598
  }
603
599
  return null;
604
600
  }
605
- async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
601
+ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
606
602
  const context = createPipelineContext({
607
603
  issueKey: config.taskKey,
608
604
  jiraRef: config.jiraRef,
609
605
  dryRun: config.dryRun,
610
606
  verbose: config.verbose,
607
+ ...(config.mdLang !== undefined ? { mdLang: config.mdLang } : {}),
611
608
  runtime,
612
609
  ...(setSummary ? { setSummary } : {}),
613
610
  requestUserInput,
@@ -621,13 +618,25 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requ
621
618
  };
622
619
  let persistedState = launchMode === "resume" ? loadFlowRunState(config.scope.scopeKey, flowId) : null;
623
620
  if (persistedState && launchMode === "resume") {
621
+ validateDeclarativeFlowResumeState({
622
+ id: flowId,
623
+ source: flow.source,
624
+ fileName: flow.fileName,
625
+ absolutePath: flow.absolutePath,
626
+ treePath: [],
627
+ flow,
628
+ }, config, persistedState, overrides.launchProfile, runtime);
624
629
  persistedState = prepareFlowStateForResume(persistedState);
625
630
  }
626
631
  else if (launchMode === "restart") {
627
632
  resetFlowRunState(config.scope.scopeKey, flowId);
628
633
  }
629
634
  const executionState = persistedState?.executionState ?? initialExecutionState;
630
- const state = persistedState ?? createFlowRunState(config.scope.scopeKey, flowId, executionState);
635
+ const state = persistedState
636
+ ?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.jiraRef, overrides.launchProfile);
637
+ if (overrides.launchProfile) {
638
+ state.launchProfile = overrides.launchProfile;
639
+ }
631
640
  state.status = "running";
632
641
  state.lastError = null;
633
642
  state.currentStep = findCurrentFlowExecutionStep(state);
@@ -673,12 +682,17 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requ
673
682
  throw error;
674
683
  }
675
684
  }
676
- async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
677
- await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, flowParams, requestUserInput, setSummary, launchMode, runtime);
685
+ async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
686
+ const mergedFlowParams = {
687
+ ...defaultDeclarativeFlowParams(config, false, overrides),
688
+ ...flowParams,
689
+ };
690
+ await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, mergedFlowParams, overrides, requestUserInput, setSummary, launchMode, runtime);
678
691
  }
679
- function defaultDeclarativeFlowParams(config, forceRefreshSummary = false) {
692
+ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overrides = {}) {
680
693
  const iteration = nextReviewIterationForTask(config.taskKey);
681
- const latestIteration = latestReviewReplyIteration(config.taskKey);
694
+ const latestIteration = latestArtifactIteration(config.taskKey, "review");
695
+ const launchProfile = overrides.launchProfile ?? resolveLaunchProfile({ executor: "default", model: "default" }, DEFAULT_LAUNCH_PROFILE);
682
696
  return {
683
697
  taskKey: config.taskKey,
684
698
  jiraRef: config.jiraRef,
@@ -686,14 +700,19 @@ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false) {
686
700
  jiraApiUrl: config.jiraApiUrl,
687
701
  jiraTaskFile: config.jiraTaskFile,
688
702
  scopeKey: config.scope.scopeKey,
689
- dockerComposeFile: config.dockerComposeFile,
690
- runGoTestsScript: config.runGoTestsScript,
691
- runGoLinterScript: config.runGoLinterScript,
692
- runGoCoverageScript: config.runGoCoverageScript,
703
+ workspaceDir: scopeWorkspaceDir(config.taskKey),
693
704
  extraPrompt: config.extraPrompt,
694
705
  reviewFixPoints: config.reviewFixPoints,
706
+ mdLang: config.mdLang,
707
+ llmExecutor: launchProfile.executor,
708
+ llmModel: launchProfile.model,
709
+ launchProfile,
695
710
  iteration,
696
711
  latestIteration,
712
+ taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
713
+ designIteration: nextArtifactIteration(config.taskKey, "design"),
714
+ planIteration: nextArtifactIteration(config.taskKey, "plan"),
715
+ qaIteration: nextArtifactIteration(config.taskKey, "qa"),
697
716
  ...(latestIteration !== null ? { reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration) } : {}),
698
717
  forceRefresh: forceRefreshSummary,
699
718
  };
@@ -719,88 +738,13 @@ function flowRequiresTaskScope(entry) {
719
738
  }
720
739
  return valueReferencesTaskScopeParams(entry.flow.phases);
721
740
  }
722
- async function runAutoPhaseViaSpec(config, phaseId, executionState, state, setSummary, forceRefreshSummary = false, runtime = runtimeServices) {
723
- const context = createPipelineContext({
724
- issueKey: config.taskKey,
725
- jiraRef: config.jiraRef,
726
- dryRun: config.dryRun,
727
- verbose: config.verbose,
728
- runtime,
729
- ...(setSummary ? { setSummary } : {}),
730
- requestUserInput: requestUserInputInTerminal,
731
- });
732
- const autoFlow = loadAutoFlow();
733
- const phase = findPhaseById(autoFlow.phases, phaseId);
734
- publishFlowState("auto", executionState);
735
- try {
736
- const result = await runExpandedPhase(phase, context, autoFlowParams(config, forceRefreshSummary), autoFlow.constants, {
737
- executionState,
738
- flowKind: autoFlow.kind,
739
- flowVersion: autoFlow.version,
740
- onStateChange: async (state) => {
741
- publishFlowState("auto", state);
742
- },
743
- onStepStart: async (_phase, step) => {
744
- if (!state) {
745
- return;
746
- }
747
- state.currentStep = `${phaseId}:${step.id}`;
748
- saveAutoPipelineState(state);
749
- },
750
- });
751
- if (state) {
752
- state.executionState = result.executionState;
753
- syncAndSaveAutoPipelineState(state);
754
- }
755
- return result.status === "skipped" ? "skipped" : "done";
756
- }
757
- catch (error) {
758
- if (!config.dryRun) {
759
- const output = String(error.output ?? "");
760
- if (output.trim()) {
761
- printError("Build verification failed");
762
- printSummary("Build Failure Summary", await summarizeBuildFailure(output));
763
- }
764
- }
765
- throw error;
766
- }
767
- }
768
- function rewindAutoPipelineState(state, phaseId) {
769
- const targetPhaseId = validateAutoPhaseId(phaseId);
770
- let phaseSeen = false;
771
- for (const step of state.steps) {
772
- if (step.id === targetPhaseId) {
773
- phaseSeen = true;
774
- }
775
- if (phaseSeen) {
776
- step.status = "pending";
777
- step.startedAt = null;
778
- step.finishedAt = null;
779
- step.returnCode = null;
780
- step.note = null;
781
- }
782
- else {
783
- step.status = "done";
784
- step.returnCode = 0;
785
- step.finishedAt ??= nowIso8601();
786
- }
787
- }
788
- state.status = "pending";
789
- state.currentStep = null;
790
- state.lastError = null;
791
- const targetIndex = state.executionState.phases.findIndex((phase) => phase.id === targetPhaseId);
792
- if (targetIndex >= 0) {
793
- state.executionState.phases = state.executionState.phases.slice(0, targetIndex);
794
- }
795
- state.executionState.terminated = false;
796
- delete state.executionState.terminationReason;
797
- }
798
741
  async function summarizeBuildFailure(output) {
799
742
  return summarizeBuildFailureViaPipeline(createPipelineContext({
800
743
  issueKey: "build-failure-summary",
801
744
  jiraRef: "build-failure-summary",
802
745
  dryRun: false,
803
746
  verbose: false,
747
+ mdLang: null,
804
748
  runtime: runtimeServices,
805
749
  requestUserInput: requestUserInputInTerminal,
806
750
  }), output);
@@ -810,27 +754,78 @@ function requireJiraConfig(config) {
810
754
  throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
811
755
  }
812
756
  }
813
- async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", runtime = runtimeServices) {
757
+ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", launchProfile, runtime = runtimeServices) {
814
758
  const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
815
- if (config.command === "auto") {
816
- if (launchMode === "restart") {
817
- resetAutoPipelineState(config);
759
+ if (config.command === "auto-golang") {
760
+ requireJiraConfig(config);
761
+ checkAutoPrerequisites(config);
762
+ process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
763
+ process.env.JIRA_API_URL = config.jiraApiUrl;
764
+ process.env.JIRA_TASK_FILE = config.jiraTaskFile;
765
+ let effectiveLaunchMode = launchMode;
766
+ let effectiveLaunchProfile = launchProfile;
767
+ if (config.autoFromPhase) {
768
+ const flow = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" });
769
+ const persistedState = loadFlowRunState(config.scope.scopeKey, "auto-golang");
770
+ if (!persistedState) {
771
+ throw new TaskRunnerError(`Cannot restart auto-golang from phase '${config.autoFromPhase}' because persisted flow state was not found.`);
772
+ }
773
+ rewindFlowRunStateToPhase(persistedState, flow.phases, config.autoFromPhase);
774
+ saveFlowRunState(persistedState);
775
+ effectiveLaunchMode = "resume";
776
+ effectiveLaunchProfile ??= persistedState.launchProfile;
777
+ printPanel("Auto-Golang Resume", `Auto-golang pipeline will continue from phase: ${config.autoFromPhase}`, "yellow");
818
778
  }
819
- await runAutoPipeline(config, setSummary, forceRefreshSummary, runtime);
779
+ await runDeclarativeFlowBySpecFile("auto-golang.json", config, autoFlowParams(config, forceRefreshSummary), effectiveLaunchProfile ? { launchProfile: effectiveLaunchProfile } : {}, requestUserInput, setSummary, effectiveLaunchMode, runtime);
780
+ return false;
781
+ }
782
+ if (config.command === "auto-common") {
783
+ requireJiraConfig(config);
784
+ checkAutoPrerequisites(config);
785
+ process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
786
+ process.env.JIRA_API_URL = config.jiraApiUrl;
787
+ process.env.JIRA_TASK_FILE = config.jiraTaskFile;
788
+ await runDeclarativeFlowBySpecFile("auto-common.json", config, autoFlowParams(config, forceRefreshSummary), launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
820
789
  return false;
821
790
  }
822
791
  if (config.command === "auto-status") {
823
- const state = loadAutoPipelineState(config);
792
+ const state = loadFlowRunState(config.scope.scopeKey, "auto-golang");
824
793
  if (!state) {
825
- printPanel("Auto Status", `No auto state file found for ${config.taskKey}.`, "yellow");
794
+ printPanel("Auto-Golang Status", `No flow state file found for ${config.taskKey}.`, "yellow");
826
795
  return false;
827
796
  }
828
- printAutoState(state);
797
+ const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
798
+ const phaseOrder = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }).phases;
799
+ const lines = [
800
+ `Issue: ${config.taskKey}`,
801
+ `Status: ${state.status}`,
802
+ `Current step: ${currentStep}`,
803
+ `Updated: ${state.updatedAt}`,
804
+ ];
805
+ if (state.launchProfile) {
806
+ lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
807
+ }
808
+ if (state.lastError) {
809
+ lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
810
+ }
811
+ lines.push("");
812
+ for (const phase of phaseOrder) {
813
+ const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
814
+ lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}`);
815
+ for (const step of phase.steps) {
816
+ const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
817
+ lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
818
+ }
819
+ }
820
+ if (state.executionState.terminated) {
821
+ lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
822
+ }
823
+ printPanel("Auto-Golang Status", lines.join("\n"), "cyan");
829
824
  return false;
830
825
  }
831
826
  if (config.command === "auto-reset") {
832
- const removed = resetAutoPipelineState(config);
833
- printPanel("Auto Reset", removed ? `State file ${autoStateFile(config.taskKey)} removed.` : "No auto state file found.", "yellow");
827
+ const removed = resetFlowRunState(config.scope.scopeKey, "auto-golang");
828
+ printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
834
829
  return false;
835
830
  }
836
831
  checkPrerequisites(config);
@@ -854,9 +849,13 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
854
849
  await runDeclarativeFlowBySpecFile("plan.json", config, {
855
850
  jiraApiUrl: config.jiraApiUrl,
856
851
  taskKey: config.taskKey,
852
+ taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
853
+ designIteration: nextArtifactIteration(config.taskKey, "design"),
854
+ planIteration: nextArtifactIteration(config.taskKey, "plan"),
855
+ qaIteration: nextArtifactIteration(config.taskKey, "qa"),
857
856
  extraPrompt: config.extraPrompt,
858
857
  forceRefresh: forceRefreshSummary,
859
- }, requestUserInput, setSummary, launchMode, runtime);
858
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
860
859
  return false;
861
860
  }
862
861
  if (config.command === "bug-analyze") {
@@ -866,33 +865,43 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
866
865
  process.stdout.write(`Resolved Jira API URL: ${config.jiraApiUrl}\n`);
867
866
  process.stdout.write(`Saving Jira issue JSON to: ${config.jiraTaskFile}\n`);
868
867
  }
869
- await runDeclarativeFlowBySpecFile("bug-analyze.json", config, {
868
+ await runDeclarativeFlowBySpecFile("bugz/bug-analyze.json", config, {
870
869
  jiraApiUrl: config.jiraApiUrl,
871
870
  taskKey: config.taskKey,
871
+ taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
872
+ bugAnalyzeIteration: nextArtifactIteration(config.taskKey, "bug-analyze"),
873
+ bugFixDesignIteration: nextArtifactIteration(config.taskKey, "bug-fix-design"),
874
+ bugFixPlanIteration: nextArtifactIteration(config.taskKey, "bug-fix-plan"),
872
875
  extraPrompt: config.extraPrompt,
873
876
  forceRefresh: forceRefreshSummary,
874
- }, requestUserInput, setSummary, launchMode, runtime);
877
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
875
878
  return false;
876
879
  }
877
880
  if (config.command === "gitlab-review") {
878
881
  const iteration = nextReviewIterationForTask(config.taskKey);
879
- await runDeclarativeFlowBySpecFile("gitlab-review.json", config, {
882
+ const gitlabReviewIteration = nextArtifactIteration(config.taskKey, "gitlab-review");
883
+ await runDeclarativeFlowBySpecFile("gitlab/gitlab-review.json", config, {
880
884
  taskKey: config.taskKey,
881
885
  iteration,
886
+ gitlabReviewIteration,
882
887
  extraPrompt: config.extraPrompt,
883
- }, requestUserInput, undefined, launchMode, runtime);
888
+ reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, iteration),
889
+ reviewFixPoints: config.reviewFixPoints,
890
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
884
891
  if (!config.dryRun) {
885
- printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}`);
892
+ printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}\n${reviewFile(config.taskKey, iteration)}\n${reviewJsonFile(config.taskKey, iteration)}\n${reviewAssessmentFile(config.taskKey, iteration)}\n${reviewAssessmentJsonFile(config.taskKey, iteration)}`);
886
893
  }
887
894
  return false;
888
895
  }
889
896
  if (config.command === "gitlab-diff-review") {
890
897
  const iteration = nextReviewIterationForTask(config.taskKey);
891
- await runDeclarativeFlowBySpecFile("gitlab-diff-review.json", config, {
898
+ const gitlabDiffIteration = nextArtifactIteration(config.taskKey, "gitlab-diff");
899
+ await runDeclarativeFlowBySpecFile("gitlab/gitlab-diff-review.json", config, {
892
900
  taskKey: config.taskKey,
893
901
  iteration,
902
+ gitlabDiffIteration,
894
903
  extraPrompt: config.extraPrompt,
895
- }, requestUserInput, undefined, launchMode, runtime);
904
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
896
905
  if (!config.dryRun) {
897
906
  printSummary("GitLab Diff Review", `Artifacts:\n${gitlabDiffFile(config.taskKey)}\n${gitlabDiffJsonFile(config.taskKey)}\n${reviewFile(config.taskKey, iteration)}\n${reviewJsonFile(config.taskKey, iteration)}`);
898
907
  }
@@ -907,28 +916,30 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
907
916
  { path: bugFixDesignJsonFile(config.taskKey), schemaId: "bug-fix-design/v1" },
908
917
  { path: bugFixPlanJsonFile(config.taskKey), schemaId: "bug-fix-plan/v1" },
909
918
  ], "Bug-fix mode requires valid structured artifacts from the bug analysis phase.");
910
- await runDeclarativeFlowBySpecFile("bug-fix.json", config, {
919
+ await runDeclarativeFlowBySpecFile("bugz/bug-fix.json", config, {
911
920
  taskKey: config.taskKey,
912
921
  extraPrompt: config.extraPrompt,
913
- }, requestUserInput, undefined, launchMode, runtime);
922
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
914
923
  return false;
915
924
  }
916
925
  if (config.command === "mr-description") {
917
926
  requireJiraConfig(config);
918
927
  requireJiraTaskFile(config.jiraTaskFile);
919
- await runDeclarativeFlowBySpecFile("mr-description.json", config, {
928
+ await runDeclarativeFlowBySpecFile("gitlab/mr-description.json", config, {
920
929
  taskKey: config.taskKey,
930
+ iteration: nextArtifactIteration(config.taskKey, "mr-description"),
921
931
  extraPrompt: config.extraPrompt,
922
- }, requestUserInput, undefined, launchMode, runtime);
932
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
923
933
  return false;
924
934
  }
925
935
  if (config.command === "task-describe") {
926
- requireJiraConfig(config);
936
+ const iteration = nextArtifactIteration(config.taskKey, "jira-description");
927
937
  await runDeclarativeFlowBySpecFile("task-describe.json", config, {
928
938
  jiraApiUrl: config.jiraApiUrl,
929
939
  taskKey: config.taskKey,
940
+ iteration,
930
941
  extraPrompt: config.extraPrompt,
931
- }, requestUserInput, undefined, launchMode, runtime);
942
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
932
943
  return false;
933
944
  }
934
945
  if (config.command === "implement") {
@@ -941,7 +952,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
941
952
  await runDeclarativeFlowBySpecFile("implement.json", config, {
942
953
  taskKey: config.taskKey,
943
954
  extraPrompt: config.extraPrompt,
944
- }, requestUserInput, undefined, launchMode);
955
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode);
945
956
  return false;
946
957
  }
947
958
  if (config.command === "review") {
@@ -952,142 +963,74 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
952
963
  { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
953
964
  { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
954
965
  ], "Review mode requires valid structured plan artifacts from the planning phase.");
955
- await runDeclarativeFlowBySpecFile("review.json", config, {
966
+ await runDeclarativeFlowBySpecFile("review/review.json", config, {
956
967
  taskKey: config.taskKey,
957
968
  iteration,
958
969
  extraPrompt: config.extraPrompt,
959
- }, requestUserInput, undefined, launchMode, runtime);
970
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
960
971
  }
961
972
  else {
962
- await runDeclarativeFlowBySpecFile("review-project.json", config, {
973
+ await runDeclarativeFlowBySpecFile("review/review-project.json", config, {
963
974
  taskKey: config.taskKey,
964
975
  iteration,
965
976
  extraPrompt: config.extraPrompt,
966
- }, requestUserInput, undefined, launchMode, runtime);
977
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
967
978
  }
968
979
  return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
969
980
  }
970
981
  if (config.command === "review-fix") {
971
- const latestIteration = latestReviewReplyIteration(config.taskKey);
982
+ const latestIteration = latestArtifactIteration(config.taskKey, "review");
972
983
  if (latestIteration === null) {
973
- throw new TaskRunnerError("Review-fix mode requires at least one review-reply artifact.");
984
+ throw new TaskRunnerError("Review-fix mode requires at least one review artifact.");
974
985
  }
975
986
  validateStructuredArtifacts([
976
987
  { path: reviewJsonFile(config.taskKey, latestIteration), schemaId: "review-findings/v1" },
977
- { path: reviewReplyJsonFile(config.taskKey, latestIteration), schemaId: "review-reply/v1" },
978
988
  ], "Review-fix mode requires valid structured review artifacts.");
979
- await runDeclarativeFlowBySpecFile("review-fix.json", config, {
989
+ await runDeclarativeFlowBySpecFile("review/review-fix.json", config, {
980
990
  taskKey: config.taskKey,
981
991
  latestIteration,
992
+ reviewAssessmentJsonFile: null,
982
993
  reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
983
994
  extraPrompt: config.extraPrompt,
984
995
  reviewFixPoints: config.reviewFixPoints,
985
- }, requestUserInput, undefined, launchMode, runtime);
996
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
986
997
  return false;
987
998
  }
999
+ if (config.command === "review-loop") {
1000
+ const iteration = nextReviewIterationForTask(config.taskKey);
1001
+ if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
1002
+ requireJiraConfig(config);
1003
+ validateStructuredArtifacts([
1004
+ { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
1005
+ { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
1006
+ ], "Review-loop mode requires valid structured plan artifacts from the planning phase.");
1007
+ }
1008
+ await runDeclarativeFlowBySpecFile("review/review-loop.json", config, {
1009
+ taskKey: config.taskKey,
1010
+ iteration,
1011
+ extraPrompt: config.extraPrompt,
1012
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1013
+ return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
1014
+ }
988
1015
  if (config.command === "run-go-tests-loop" || config.command === "run-go-linter-loop") {
989
- await runDeclarativeFlowBySpecFile(config.command === "run-go-tests-loop" ? "run-go-tests-loop.json" : "run-go-linter-loop.json", config, {
1016
+ await runDeclarativeFlowBySpecFile(config.command === "run-go-tests-loop" ? "go/run-go-tests-loop.json" : "go/run-go-linter-loop.json", config, {
990
1017
  taskKey: config.taskKey,
991
- runGoTestsScript: config.runGoTestsScript,
992
- runGoLinterScript: config.runGoLinterScript,
1018
+ runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
1019
+ runGoLinterScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_linter.py"),
1020
+ runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
1021
+ runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
993
1022
  extraPrompt: config.extraPrompt,
994
- }, requestUserInput, undefined, launchMode, runtime);
1023
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
995
1024
  return false;
996
1025
  }
997
- throw new TaskRunnerError(`Unsupported command: ${config.command}`);
998
- }
999
- async function runAutoPipelineDryRun(config, setSummary, forceRefreshSummary = false, runtime = runtimeServices) {
1000
- checkAutoPrerequisites(config);
1001
- printInfo("Dry-run auto pipeline from declarative spec");
1002
- const autoFlow = loadAutoFlow();
1003
- const executionState = {
1004
- flowKind: autoFlow.kind,
1005
- flowVersion: autoFlow.version,
1006
- terminated: false,
1007
- phases: [],
1008
- };
1009
- publishFlowState("auto", executionState);
1010
- for (const phase of autoFlow.phases) {
1011
- printInfo(`Dry-run auto phase: ${phase.id}`);
1012
- await runAutoPhaseViaSpec(config, phase.id, executionState, undefined, setSummary, forceRefreshSummary, runtime);
1013
- if (executionState.terminated) {
1014
- break;
1015
- }
1016
- }
1017
- }
1018
- async function runAutoPipeline(config, setSummary, forceRefreshSummary = false, runtime = runtimeServices) {
1019
- requireJiraConfig(config);
1020
- if (config.dryRun) {
1021
- await runAutoPipelineDryRun(config, setSummary, forceRefreshSummary, runtime);
1022
- return;
1023
- }
1024
- checkAutoPrerequisites(config);
1025
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
1026
- process.env.JIRA_API_URL = config.jiraApiUrl;
1027
- process.env.JIRA_TASK_FILE = config.jiraTaskFile;
1028
- let state = loadAutoPipelineState(config) ?? createAutoPipelineState(config);
1029
- if (config.autoFromPhase) {
1030
- rewindAutoPipelineState(state, config.autoFromPhase);
1031
- printPanel("Auto Resume", `Auto pipeline will continue from phase: ${config.autoFromPhase}`, "yellow");
1032
- saveAutoPipelineState(state);
1033
- }
1034
- else if (!existsSync(autoStateFile(config.taskKey))) {
1035
- saveAutoPipelineState(state);
1036
- }
1037
- printInfo("Running auto pipeline with persisted state");
1038
- while (true) {
1039
- const step = nextAutoStep(state);
1040
- if (!step) {
1041
- syncAndSaveAutoPipelineState(state);
1042
- if (state.status === "completed") {
1043
- printPanel("Auto", "Auto pipeline finished", "green");
1044
- }
1045
- else {
1046
- printInfo(`Auto pipeline finished with status: ${state.status}`);
1047
- }
1048
- return;
1049
- }
1050
- state.status = "running";
1051
- state.currentStep = step.id;
1052
- step.status = "running";
1053
- step.startedAt = nowIso8601();
1054
- step.finishedAt = null;
1055
- step.returnCode = null;
1056
- step.note = null;
1057
- state.lastError = null;
1058
- saveAutoPipelineState(state);
1059
- try {
1060
- printInfo(`Running auto step: ${step.id}`);
1061
- const status = await runAutoPhaseViaSpec(config, step.id, state.executionState, state, setSummary, forceRefreshSummary, runtime);
1062
- step.status = status;
1063
- step.finishedAt = nowIso8601();
1064
- step.returnCode = 0;
1065
- if (status === "skipped") {
1066
- step.note = "condition not met";
1067
- }
1068
- syncAndSaveAutoPipelineState(state);
1069
- }
1070
- catch (error) {
1071
- const returnCode = Number(error.returnCode ?? 1);
1072
- step.status = "failed";
1073
- step.finishedAt = nowIso8601();
1074
- step.returnCode = returnCode;
1075
- state.status = "blocked";
1076
- state.currentStep = step.id;
1077
- state.lastError = {
1078
- step: step.id,
1079
- returnCode,
1080
- message: "command failed",
1081
- };
1082
- saveAutoPipelineState(state);
1083
- throw error;
1084
- }
1085
- if (state.executionState.terminated) {
1086
- syncAndSaveAutoPipelineState(state);
1087
- printPanel("Auto", "Auto pipeline finished", "green");
1088
- return;
1089
- }
1026
+ if (config.command === "git-commit") {
1027
+ await runDeclarativeFlowBySpecFile("git-commit.json", config, {
1028
+ taskKey: config.taskKey,
1029
+ extraPrompt: config.extraPrompt,
1030
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1031
+ return false;
1090
1032
  }
1033
+ throw new TaskRunnerError(`Unsupported command: ${config.command}`);
1091
1034
  }
1092
1035
  function parseCliArgs(argv) {
1093
1036
  if (argv.includes("--version") || argv.includes("-v")) {
@@ -1114,6 +1057,7 @@ function parseCliArgs(argv) {
1114
1057
  let scopeName;
1115
1058
  let helpPhases = false;
1116
1059
  let jiraRef;
1060
+ let mdLang;
1117
1061
  for (let index = 1; index < argv.length; index += 1) {
1118
1062
  const token = argv[index] ?? "";
1119
1063
  if (token === "--dry") {
@@ -1143,12 +1087,39 @@ function parseCliArgs(argv) {
1143
1087
  index += 1;
1144
1088
  continue;
1145
1089
  }
1090
+ if (token === "--md-lang") {
1091
+ const langValue = argv[index + 1];
1092
+ if (langValue === "en" || langValue === "ru") {
1093
+ mdLang = langValue;
1094
+ }
1095
+ else {
1096
+ process.stderr.write("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
1097
+ process.exit(1);
1098
+ }
1099
+ index += 1;
1100
+ continue;
1101
+ }
1102
+ if (token.startsWith("--md-lang=")) {
1103
+ const langValue = token.slice("--md-lang=".length);
1104
+ if (langValue === "en" || langValue === "ru") {
1105
+ mdLang = langValue;
1106
+ }
1107
+ else {
1108
+ process.stderr.write("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
1109
+ process.exit(1);
1110
+ }
1111
+ continue;
1112
+ }
1146
1113
  jiraRef = token;
1147
1114
  }
1148
- if (command === "auto" && helpPhases) {
1115
+ if (command === "auto-golang" && helpPhases) {
1149
1116
  printAutoPhasesHelp();
1150
1117
  process.exit(0);
1151
1118
  }
1119
+ if (command === "auto-common" && helpPhases) {
1120
+ printAutoCommonPhasesHelp();
1121
+ process.exit(0);
1122
+ }
1152
1123
  return {
1153
1124
  command: command,
1154
1125
  dry,
@@ -1158,6 +1129,7 @@ function parseCliArgs(argv) {
1158
1129
  ...(scopeName !== undefined ? { scopeName } : {}),
1159
1130
  ...(prompt !== undefined ? { prompt } : {}),
1160
1131
  ...(autoFromPhase !== undefined ? { autoFromPhase } : {}),
1132
+ ...(mdLang !== undefined ? { mdLang } : {}),
1161
1133
  };
1162
1134
  }
1163
1135
  function buildConfigFromArgs(args) {
@@ -1166,6 +1138,7 @@ function buildConfigFromArgs(args) {
1166
1138
  ...(args.scopeName !== undefined ? { scopeName: args.scopeName } : {}),
1167
1139
  ...(args.prompt !== undefined ? { extraPrompt: args.prompt } : {}),
1168
1140
  ...(args.autoFromPhase !== undefined ? { autoFromPhase: args.autoFromPhase } : {}),
1141
+ ...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
1169
1142
  dryRun: args.dry,
1170
1143
  verbose: args.verbose,
1171
1144
  });
@@ -1183,34 +1156,13 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1183
1156
  summaryText: "",
1184
1157
  cwd: process.cwd(),
1185
1158
  gitBranchName,
1159
+ version: packageVersion(),
1186
1160
  flows: interactiveFlowDefinitions(flowCatalog),
1187
1161
  getRunConfirmation: async (flowId) => {
1188
1162
  const flowEntry = findCatalogEntry(flowId, flowCatalog);
1189
1163
  if (!flowEntry) {
1190
1164
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
1191
1165
  }
1192
- if (flowId === "auto") {
1193
- if (!currentScope.jiraRef) {
1194
- return { resumeAvailable: false, hasExistingState: false };
1195
- }
1196
- const baseConfig = buildBaseConfig("auto", {
1197
- jiraRef: currentScope.jiraRef,
1198
- scopeName: currentScope.scopeKey,
1199
- });
1200
- const state = loadAutoPipelineState(buildRuntimeConfig(baseConfig, currentScope));
1201
- if (!state) {
1202
- return { resumeAvailable: false, hasExistingState: false };
1203
- }
1204
- const status = deriveAutoPipelineStatus(state);
1205
- if (status === "completed") {
1206
- return { resumeAvailable: false, hasExistingState: true };
1207
- }
1208
- return {
1209
- resumeAvailable: true,
1210
- hasExistingState: true,
1211
- details: buildAutoResumeDetails(state),
1212
- };
1213
- }
1214
1166
  const resumeLookup = lookupInteractiveFlowResume(flowEntry, currentScope);
1215
1167
  return resumeLookup;
1216
1168
  },
@@ -1223,6 +1175,16 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1223
1175
  if (!flowEntry) {
1224
1176
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
1225
1177
  }
1178
+ const resumeState = launchMode === "resume" ? loadFlowRunState(currentScope.scopeKey, flowId) : null;
1179
+ if (resumeState) {
1180
+ currentScope = scopeWithRestoredJiraContext(currentScope, resumeState);
1181
+ }
1182
+ const launchProfile = launchMode === "resume"
1183
+ ? resumeState?.launchProfile
1184
+ : await requestInteractiveLaunchProfile((form) => ui.requestUserInput(form));
1185
+ if (!launchProfile) {
1186
+ throw new TaskRunnerError("Resume is impossible because launch profile was not saved. Use restart.");
1187
+ }
1226
1188
  const previousScopeKey = currentScope.scopeKey;
1227
1189
  const baseConfig = buildBaseConfig(flowId, {
1228
1190
  ...(currentScope.jiraRef ? { jiraRef: currentScope.jiraRef } : {}),
@@ -1240,12 +1202,13 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1240
1202
  if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
1241
1203
  syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
1242
1204
  }
1205
+ printPanel("Effective Launch Config", `executor: ${launchProfile.executor}\nmodel: ${launchProfile.model}\nmode: ${launchMode}`, "cyan");
1243
1206
  if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
1244
- await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, createRuntimeServices(abortController.signal));
1207
+ await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, launchProfile, createRuntimeServices(abortController.signal));
1245
1208
  return;
1246
1209
  }
1247
1210
  const runtimeConfig = buildRuntimeConfig(baseConfig, currentScope);
1248
- await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh), (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode, createRuntimeServices(abortController.signal));
1211
+ await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh, { launchProfile }), { launchProfile }, (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode, createRuntimeServices(abortController.signal));
1249
1212
  }
1250
1213
  catch (error) {
1251
1214
  if (error instanceof FlowInterruptedError) {
@@ -1309,7 +1272,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1309
1272
  });
1310
1273
  }
1311
1274
  export async function main(argv = process.argv.slice(2)) {
1312
- loadEnvFile(path.join(process.cwd(), ".env"));
1275
+ loadTieredEnv(process.cwd());
1313
1276
  let forceRefresh = false;
1314
1277
  const args = [...argv];
1315
1278
  if (args[0] === "--force") {