agentweaver 0.1.10 → 0.1.11

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 (106) hide show
  1. package/README.md +218 -224
  2. package/dist/artifacts.js +100 -55
  3. package/dist/executors/{codex-local-executor.js → codex-executor.js} +4 -4
  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/telegram-notifier-executor.js +54 -0
  11. package/dist/flow-state.js +46 -1
  12. package/dist/gitlab.js +13 -8
  13. package/dist/index.js +469 -514
  14. package/dist/interactive-ui.js +144 -43
  15. package/dist/jira.js +52 -5
  16. package/dist/pipeline/auto-flow.js +6 -6
  17. package/dist/pipeline/context.js +1 -0
  18. package/dist/pipeline/flow-catalog.js +34 -4
  19. package/dist/pipeline/flow-model-settings.js +77 -0
  20. package/dist/pipeline/flow-specs/auto-common.json +446 -0
  21. package/dist/pipeline/flow-specs/auto-golang.json +563 -0
  22. package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
  23. package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
  24. package/dist/pipeline/flow-specs/git-commit.json +196 -0
  25. package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
  26. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +165 -0
  27. package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
  28. package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
  29. package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
  30. package/dist/pipeline/flow-specs/implement.json +5 -4
  31. package/dist/pipeline/flow-specs/plan.json +40 -148
  32. package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +73 -13
  33. package/dist/pipeline/flow-specs/review/review-loop.json +280 -0
  34. package/dist/pipeline/flow-specs/review/review-project.json +87 -0
  35. package/dist/pipeline/flow-specs/review/review.json +126 -0
  36. package/dist/pipeline/flow-specs/task-describe.json +191 -11
  37. package/dist/pipeline/launch-profile-config.js +38 -0
  38. package/dist/pipeline/node-registry.js +75 -45
  39. package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
  40. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
  41. package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
  42. package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
  43. package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
  44. package/dist/pipeline/nodes/git-commit-node.js +28 -0
  45. package/dist/pipeline/nodes/git-status-node.js +221 -0
  46. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
  47. package/dist/pipeline/nodes/jira-context-node.js +10 -0
  48. package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
  49. package/dist/pipeline/nodes/plan-codex-node.js +1 -1
  50. package/dist/pipeline/nodes/read-file-node.js +11 -0
  51. package/dist/pipeline/nodes/review-findings-form-node.js +18 -14
  52. package/dist/pipeline/nodes/select-files-form-node.js +72 -0
  53. package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
  54. package/dist/pipeline/nodes/user-input-node.js +29 -8
  55. package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
  56. package/dist/pipeline/prompt-registry.js +2 -4
  57. package/dist/pipeline/prompt-runtime.js +13 -3
  58. package/dist/pipeline/registry.js +6 -8
  59. package/dist/pipeline/spec-compiler.js +5 -0
  60. package/dist/pipeline/spec-types.js +7 -3
  61. package/dist/pipeline/spec-validator.js +4 -0
  62. package/dist/pipeline/types.js +1 -0
  63. package/dist/pipeline/value-resolver.js +40 -38
  64. package/dist/prompts.js +104 -110
  65. package/dist/runtime/agentweaver-home.js +8 -0
  66. package/dist/runtime/command-resolution.js +0 -38
  67. package/dist/runtime/env-loader.js +43 -0
  68. package/dist/structured-artifact-schema-registry.js +53 -0
  69. package/dist/structured-artifact-schemas.json +0 -20
  70. package/dist/structured-artifacts.js +3 -43
  71. package/dist/user-input.js +30 -2
  72. package/package.json +2 -6
  73. package/Dockerfile.codex +0 -56
  74. package/dist/executors/claude-executor.js +0 -46
  75. package/dist/executors/codex-docker-executor.js +0 -27
  76. package/dist/executors/configs/claude-config.js +0 -12
  77. package/dist/executors/configs/codex-docker-config.js +0 -10
  78. package/dist/executors/configs/verify-build-config.js +0 -7
  79. package/dist/executors/verify-build-executor.js +0 -123
  80. package/dist/pipeline/flow-specs/auto.json +0 -979
  81. package/dist/pipeline/flow-specs/gitlab-review.json +0 -317
  82. package/dist/pipeline/flow-specs/opencode/auto-opencode.json +0 -1365
  83. package/dist/pipeline/flow-specs/opencode/bugz/bug-analyze-opencode.json +0 -382
  84. package/dist/pipeline/flow-specs/opencode/bugz/bug-fix-opencode.json +0 -56
  85. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-diff-review-opencode.json +0 -308
  86. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-review-opencode.json +0 -437
  87. package/dist/pipeline/flow-specs/opencode/gitlab/mr-description-opencode.json +0 -117
  88. package/dist/pipeline/flow-specs/opencode/go/run-go-linter-loop-opencode.json +0 -321
  89. package/dist/pipeline/flow-specs/opencode/go/run-go-tests-loop-opencode.json +0 -321
  90. package/dist/pipeline/flow-specs/opencode/implement-opencode.json +0 -64
  91. package/dist/pipeline/flow-specs/opencode/plan-opencode.json +0 -603
  92. package/dist/pipeline/flow-specs/opencode/review/review-fix-opencode.json +0 -209
  93. package/dist/pipeline/flow-specs/opencode/review/review-opencode.json +0 -452
  94. package/dist/pipeline/flow-specs/opencode/task-describe-opencode.json +0 -148
  95. package/dist/pipeline/flow-specs/review-project.json +0 -243
  96. package/dist/pipeline/flow-specs/review.json +0 -312
  97. package/dist/pipeline/flows/preflight-flow.js +0 -19
  98. package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
  99. package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
  100. package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
  101. package/dist/pipeline/nodes/review-claude-node.js +0 -38
  102. package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
  103. package/dist/pipeline/nodes/verify-build-node.js +0 -15
  104. package/dist/runtime/docker-runtime.js +0 -51
  105. package/docker-compose.yml +0 -445
  106. 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, 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,235 @@ 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");
220
183
  }
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");
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: "default",
198
+ options: [
199
+ { value: "default", label: `default (${defaultExecutor})` },
200
+ ...LLM_EXECUTOR_IDS.map((id) => ({
201
+ value: id,
202
+ label: id === defaultExecutor ? `${id} [default]` : id,
203
+ })),
204
+ ],
205
+ },
206
+ ],
207
+ };
228
208
  }
229
- function syncAndSaveAutoPipelineState(state) {
230
- syncAutoStepsFromExecutionState(state);
231
- saveAutoPipelineState(state);
209
+ function launchModelSelectionForm(executor) {
210
+ const resolvedExecutor = executor === "default" ? DEFAULT_LAUNCH_PROFILE.executor : executor;
211
+ const defaultModel = defaultModelForExecutor(resolvedExecutor);
212
+ const options = executor === "default"
213
+ ? [{ value: "default", label: `default (${DEFAULT_LAUNCH_PROFILE.model})` }]
214
+ : [
215
+ { value: "default", label: `default (${defaultModel})` },
216
+ ...ALLOWED_MODELS_BY_EXECUTOR[executor].map((model) => ({
217
+ value: model,
218
+ label: model === defaultModel ? `${model} [default]` : model,
219
+ })),
220
+ ];
221
+ return {
222
+ formId: "flow-launch-model",
223
+ title: "Настройки запуска LLM",
224
+ description: `Выберите модель для запуска flow. Текущий default для ${resolvedExecutor}: ${defaultModel}.`,
225
+ submitLabel: "Start",
226
+ fields: [
227
+ {
228
+ id: "model",
229
+ type: "single-select",
230
+ label: "Model",
231
+ required: true,
232
+ default: "default",
233
+ options,
234
+ },
235
+ ],
236
+ };
232
237
  }
233
- function resetAutoPipelineState(config) {
234
- const filePath = autoStateFile(config.taskKey);
235
- if (!existsSync(filePath)) {
236
- return false;
237
- }
238
- rmSync(filePath);
239
- return true;
238
+ async function requestInteractiveLaunchProfile(requestUserInput) {
239
+ const executorFormResult = await requestUserInput(launchProfileSelectionForm());
240
+ const rawExecutor = String(executorFormResult.values.executor ?? "default");
241
+ const executor = rawExecutor === "default" ? "default" : LLM_EXECUTOR_IDS.find((id) => id === rawExecutor);
242
+ if (!executor) {
243
+ throw new TaskRunnerError(`Unsupported launch executor '${rawExecutor}'.`);
244
+ }
245
+ const modelFormResult = await requestUserInput(launchModelSelectionForm(executor));
246
+ const rawModel = String(modelFormResult.values.model ?? "default").trim();
247
+ return resolveLaunchProfile({
248
+ executor,
249
+ model: rawModel.length > 0 ? rawModel : "default",
250
+ }, DEFAULT_LAUNCH_PROFILE);
240
251
  }
241
- function nextAutoStep(state) {
242
- return state.steps.find((step) => ["running", "failed", "pending"].includes(step.status)) ?? null;
252
+ function buildResolverContext(pipelineContext, flowParams, flowConstants, repeatVars, executionState) {
253
+ return {
254
+ flowParams,
255
+ flowConstants,
256
+ pipelineContext,
257
+ repeatVars,
258
+ executionState,
259
+ };
243
260
  }
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}`;
261
+ function resolveResumeChecks(step, context) {
262
+ return (step.expect ?? [])
263
+ .filter((expectation) => evaluateCondition(expectation.when, context))
264
+ .flatMap((expectation) => {
265
+ if (expectation.kind === "step-output") {
266
+ const value = resolveValue(expectation.value, context);
267
+ if (expectation.equals !== undefined) {
268
+ const expected = resolveValue(expectation.equals, context);
269
+ if (value !== expected) {
270
+ throw new TaskRunnerError(expectation.message);
271
+ }
272
+ return [];
273
+ }
274
+ if (!value) {
275
+ throw new TaskRunnerError(expectation.message);
276
+ }
277
+ return [];
249
278
  }
250
- }
251
- return null;
279
+ if (expectation.kind === "require-artifacts") {
280
+ const value = resolveValue(expectation.paths, context);
281
+ if (!Array.isArray(value) || value.some((candidate) => typeof candidate !== "string")) {
282
+ throw new TaskRunnerError("Expectation 'require-artifacts' must resolve to string[]");
283
+ }
284
+ return [{ kind: "require-artifacts", paths: value, message: expectation.message }];
285
+ }
286
+ if (expectation.kind === "require-file") {
287
+ const value = resolveValue(expectation.path, context);
288
+ if (typeof value !== "string") {
289
+ throw new TaskRunnerError("Expectation 'require-file' must resolve to string");
290
+ }
291
+ return [{ kind: "require-file", path: value, message: expectation.message }];
292
+ }
293
+ const items = expectation.items.map((item) => {
294
+ const value = resolveValue(item.path, context);
295
+ if (typeof value !== "string") {
296
+ throw new TaskRunnerError("Expectation 'require-structured-artifacts' item path must resolve to string");
297
+ }
298
+ return {
299
+ path: value,
300
+ schemaId: item.schemaId,
301
+ };
302
+ });
303
+ return [{ kind: "require-structured-artifacts", items, message: expectation.message }];
304
+ });
252
305
  }
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";
306
+ function validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext, flowParams, flowConstants, executionState) {
307
+ if (phaseState.status === "done") {
308
+ return;
268
309
  }
269
- if (state.steps.every((candidate) => candidate.status === "done")) {
270
- return "completed";
310
+ for (const [stepIndex, step] of phase.steps.entries()) {
311
+ const stepState = phaseState.steps[stepIndex];
312
+ if (!stepState || stepState.status !== "done") {
313
+ continue;
314
+ }
315
+ const context = buildResolverContext(pipelineContext, flowParams, flowConstants, step.repeatVars, executionState);
316
+ const checks = resolveResumeChecks(step, context);
317
+ try {
318
+ runNodeChecks(checks);
319
+ }
320
+ catch (error) {
321
+ throw new TaskRunnerError(`Resume is impossible for '${phase.id}:${step.id}' because required artifacts are missing or invalid. Use restart.\n${error.message}`);
322
+ }
271
323
  }
272
- return state.status;
273
324
  }
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}`);
325
+ function validateDeclarativeFlowResumeState(flowEntry, config, state, launchProfile, runtime = runtimeServices) {
326
+ if (state.launchProfile) {
327
+ if (!launchProfile) {
328
+ throw new TaskRunnerError("Resume is impossible because launch profile is missing. Use restart.");
329
+ }
330
+ if (state.launchProfile.fingerprint !== launchProfile.fingerprint) {
331
+ throw new TaskRunnerError(`Resume is impossible because launch profile changed (${state.launchProfile.executor}/${state.launchProfile.model} -> ${launchProfile.executor}/${launchProfile.model}). Use restart.`);
291
332
  }
292
333
  }
293
- if (state.executionState.terminated) {
294
- lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
334
+ if (flowRequiresTaskScope(flowEntry) && !config.jiraRef) {
335
+ throw new TaskRunnerError("Resume is impossible because Jira context is missing for this flow state. Use restart.");
295
336
  }
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);
337
+ const pipelineContext = createPipelineContext({
338
+ issueKey: config.taskKey,
339
+ jiraRef: config.jiraRef,
340
+ dryRun: config.dryRun,
341
+ verbose: config.verbose,
342
+ ...(config.mdLang !== undefined ? { mdLang: config.mdLang } : {}),
343
+ runtime,
344
+ requestUserInput: requestUserInputInTerminal,
345
+ });
346
+ const flowParams = defaultDeclarativeFlowParams(config, false, launchProfile ? { launchProfile } : {});
347
+ for (const phase of flowEntry.flow.phases) {
348
+ const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
301
349
  if (!phaseState) {
302
350
  continue;
303
351
  }
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
- }
352
+ validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext, flowParams, flowEntry.flow.constants, state.executionState);
318
353
  }
319
- state.currentStep = findCurrentExecutionStep(state);
320
- state.status = deriveAutoPipelineStatus(state);
321
354
  }
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 ?? "-"})`);
355
+ function scopeWithRestoredJiraContext(scope, state) {
356
+ if (scope.jiraRef || !state?.jiraRef?.trim()) {
357
+ return scope;
331
358
  }
332
- return lines.join("\n");
333
- }
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 ?? "-"})`);
343
- }
344
- return lines.join("\n");
359
+ return attachJiraContext(scope, state.jiraRef);
345
360
  }
346
361
  function lookupInteractiveFlowResume(flowEntry, currentScope) {
347
362
  const directState = loadFlowRunState(currentScope.scopeKey, flowEntry.id);
348
363
  if (directState && hasResumableFlowState(directState)) {
349
- return {
350
- resumeAvailable: true,
351
- hasExistingState: true,
352
- details: buildFlowResumeDetails(directState),
353
- };
364
+ try {
365
+ const effectiveScope = scopeWithRestoredJiraContext(currentScope, directState);
366
+ const baseConfig = buildBaseConfig(flowEntry.id, {
367
+ ...(effectiveScope.jiraRef ? { jiraRef: effectiveScope.jiraRef } : {}),
368
+ scopeName: effectiveScope.scopeKey,
369
+ });
370
+ const config = buildRuntimeConfig(baseConfig, effectiveScope);
371
+ validateDeclarativeFlowResumeState(flowEntry, config, directState, directState.launchProfile);
372
+ return {
373
+ resumeAvailable: true,
374
+ hasExistingState: true,
375
+ details: buildFlowResumeDetails(directState),
376
+ };
377
+ }
378
+ catch (error) {
379
+ return {
380
+ resumeAvailable: false,
381
+ hasExistingState: true,
382
+ details: `Interrupted run found, but resume is unavailable.\n${error.message}`,
383
+ };
384
+ }
354
385
  }
355
386
  return {
356
387
  resumeAvailable: false,
@@ -358,37 +389,17 @@ function lookupInteractiveFlowResume(flowEntry, currentScope) {
358
389
  };
359
390
  }
360
391
  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");
392
+ const phaseLines = ["Available auto-golang phases:", "", ...autoPhaseIds()];
393
+ 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>");
394
+ printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
364
395
  }
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
- }
396
+ function autoCommonPhaseIds() {
397
+ return loadDeclarativeFlow({ source: "built-in", fileName: "auto-common.json" }).phases.map((phase) => phase.id);
398
+ }
399
+ function printAutoCommonPhasesHelp() {
400
+ const phaseLines = ["Available auto-common phases:", "", ...autoCommonPhaseIds()];
401
+ phaseLines.push("", "You can run auto-common with:", "agentweaver auto-common <jira>");
402
+ printPanel("Auto-Common Phases", phaseLines.join("\n"), "magenta");
392
403
  }
393
404
  function nextReviewIterationForTask(taskKey) {
394
405
  let maxIndex = 0;
@@ -400,33 +411,14 @@ function nextReviewIterationForTask(taskKey) {
400
411
  if (!entry.isFile()) {
401
412
  continue;
402
413
  }
403
- const match = REVIEW_FILE_RE.exec(entry.name) ?? REVIEW_REPLY_FILE_RE.exec(entry.name);
414
+ const match = REVIEW_FILE_RE.exec(entry.name);
404
415
  if (match && match[1] === taskKey) {
405
416
  maxIndex = Math.max(maxIndex, Number.parseInt(match[2] ?? "0", 10));
406
417
  }
407
418
  }
408
419
  return maxIndex + 1;
409
420
  }
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
421
  function buildBaseConfig(command, options = {}) {
429
- const homeDir = agentweaverHome(PACKAGE_ROOT);
430
422
  return {
431
423
  command,
432
424
  jiraRef: options.jiraRef ?? null,
@@ -434,12 +426,9 @@ function buildBaseConfig(command, options = {}) {
434
426
  reviewFixPoints: options.reviewFixPoints ?? null,
435
427
  extraPrompt: options.extraPrompt ?? null,
436
428
  autoFromPhase: options.autoFromPhase ? validateAutoPhaseId(options.autoFromPhase) : null,
429
+ mdLang: options.mdLang ?? null,
437
430
  dryRun: options.dryRun ?? false,
438
431
  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
432
  };
444
433
  }
445
434
  function commandRequiresTask(command) {
@@ -447,17 +436,20 @@ function commandRequiresTask(command) {
447
436
  command === "bug-analyze" ||
448
437
  command === "bug-fix" ||
449
438
  command === "mr-description" ||
450
- command === "task-describe" ||
451
- command === "auto" ||
439
+ command === "auto-golang" ||
440
+ command === "auto-common" ||
452
441
  command === "auto-status" ||
453
442
  command === "auto-reset");
454
443
  }
455
444
  function commandSupportsProjectScope(command) {
456
- return (command === "gitlab-diff-review" ||
445
+ return (command === "git-commit" ||
446
+ command === "gitlab-diff-review" ||
457
447
  command === "gitlab-review" ||
448
+ command === "task-describe" ||
458
449
  command === "implement" ||
459
450
  command === "review" ||
460
451
  command === "review-fix" ||
452
+ command === "review-loop" ||
461
453
  command === "run-go-tests-loop" ||
462
454
  command === "run-go-linter-loop");
463
455
  }
@@ -506,41 +498,40 @@ function checkPrerequisites(config) {
506
498
  config.command === "run-go-linter-loop") {
507
499
  resolveCmd("codex", "CODEX_BIN");
508
500
  }
509
- if (config.command === "review" || config.command === "gitlab-diff-review") {
510
- resolveCmd("claude", "CLAUDE_BIN");
511
- }
512
501
  }
513
502
  function checkAutoPrerequisites(config) {
514
503
  resolveCmd("codex", "CODEX_BIN");
515
- resolveCmd("claude", "CLAUDE_BIN");
516
504
  }
517
505
  function autoFlowParams(config, forceRefreshSummary = false) {
518
506
  return {
519
507
  jiraApiUrl: config.jiraApiUrl,
520
508
  taskKey: config.taskKey,
521
- dockerComposeFile: config.dockerComposeFile,
522
- runGoTestsScript: config.runGoTestsScript,
523
- runGoLinterScript: config.runGoLinterScript,
524
- runGoCoverageScript: config.runGoCoverageScript,
525
509
  extraPrompt: config.extraPrompt,
526
510
  reviewFixPoints: config.reviewFixPoints,
527
511
  forceRefresh: forceRefreshSummary,
512
+ mdLang: config.mdLang,
513
+ runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
514
+ runGoLinterScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_linter.py"),
515
+ runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
516
+ runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
528
517
  };
529
518
  }
530
519
  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` локально, исправляет проблемы линтера или генерации и повторяет попытки до успеха.",
520
+ "auto-golang": "Full task pipeline: planning, implementation, checks, review, review replies, and repeated iterations until ready to merge.",
521
+ "bug-analyze": "Analyzes bug from Jira and creates structured artifacts: root cause hypothesis, fix design, and implementation plan.",
522
+ "git-commit": "Collects git status/diff, generates commit message via LLM, allows file selection and commit confirmation.",
523
+ "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.",
524
+ "gitlab-review": "Requests GitLab MR URL via user-input, downloads code review comments via API, and saves markdown plus structured JSON artifact.",
525
+ "bug-fix": "Takes bug-analyze results as source of truth and implements the bug fix in code.",
526
+ "mr-description": "Prepares a brief intent description for a merge request based on the task and current changes.",
527
+ plan: "Loads task from Jira and creates design, implementation plan, and QA plan in structured JSON and markdown.",
528
+ "task-describe": "Builds a brief task description either from Jira or from quick user-input without Jira.",
529
+ implement: "Implements the task from approved design/plan artifacts and runs post-verify builds if needed.",
530
+ review: "Runs code review of current changes, validates structured findings, then prepares a reply to comments.",
531
+ "review-fix": "Fixes issues after review-reply, updates code, and runs mandatory checks after modifications.",
532
+ "review-loop": "Iteratively runs review and review-fix cycles up to 5 times until ready-to-merge is achieved.",
533
+ "run-go-tests-loop": "Cycles through `./run_go_tests.py` locally, analyzes the last error, and fixes code until successful or attempts exhausted.",
534
+ "run-go-linter-loop": "Cycles through `./run_go_linter.py` locally, fixes linter or generation issues, and retries until success.",
544
535
  };
545
536
  function flowDescription(id) {
546
537
  return FLOW_DESCRIPTIONS[id] ?? "Описание для этого flow пока не задано.";
@@ -602,12 +593,13 @@ function findCurrentFlowExecutionStep(state) {
602
593
  }
603
594
  return null;
604
595
  }
605
- async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
596
+ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
606
597
  const context = createPipelineContext({
607
598
  issueKey: config.taskKey,
608
599
  jiraRef: config.jiraRef,
609
600
  dryRun: config.dryRun,
610
601
  verbose: config.verbose,
602
+ ...(config.mdLang !== undefined ? { mdLang: config.mdLang } : {}),
611
603
  runtime,
612
604
  ...(setSummary ? { setSummary } : {}),
613
605
  requestUserInput,
@@ -621,13 +613,25 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requ
621
613
  };
622
614
  let persistedState = launchMode === "resume" ? loadFlowRunState(config.scope.scopeKey, flowId) : null;
623
615
  if (persistedState && launchMode === "resume") {
616
+ validateDeclarativeFlowResumeState({
617
+ id: flowId,
618
+ source: flow.source,
619
+ fileName: flow.fileName,
620
+ absolutePath: flow.absolutePath,
621
+ treePath: [],
622
+ flow,
623
+ }, config, persistedState, overrides.launchProfile, runtime);
624
624
  persistedState = prepareFlowStateForResume(persistedState);
625
625
  }
626
626
  else if (launchMode === "restart") {
627
627
  resetFlowRunState(config.scope.scopeKey, flowId);
628
628
  }
629
629
  const executionState = persistedState?.executionState ?? initialExecutionState;
630
- const state = persistedState ?? createFlowRunState(config.scope.scopeKey, flowId, executionState);
630
+ const state = persistedState
631
+ ?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.jiraRef, overrides.launchProfile);
632
+ if (overrides.launchProfile) {
633
+ state.launchProfile = overrides.launchProfile;
634
+ }
631
635
  state.status = "running";
632
636
  state.lastError = null;
633
637
  state.currentStep = findCurrentFlowExecutionStep(state);
@@ -673,12 +677,17 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requ
673
677
  throw error;
674
678
  }
675
679
  }
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);
680
+ async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
681
+ const mergedFlowParams = {
682
+ ...defaultDeclarativeFlowParams(config, false, overrides),
683
+ ...flowParams,
684
+ };
685
+ await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, mergedFlowParams, overrides, requestUserInput, setSummary, launchMode, runtime);
678
686
  }
679
- function defaultDeclarativeFlowParams(config, forceRefreshSummary = false) {
687
+ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overrides = {}) {
680
688
  const iteration = nextReviewIterationForTask(config.taskKey);
681
- const latestIteration = latestReviewReplyIteration(config.taskKey);
689
+ const latestIteration = latestArtifactIteration(config.taskKey, "review");
690
+ const launchProfile = overrides.launchProfile ?? resolveLaunchProfile({ executor: "default", model: "default" }, DEFAULT_LAUNCH_PROFILE);
682
691
  return {
683
692
  taskKey: config.taskKey,
684
693
  jiraRef: config.jiraRef,
@@ -686,14 +695,19 @@ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false) {
686
695
  jiraApiUrl: config.jiraApiUrl,
687
696
  jiraTaskFile: config.jiraTaskFile,
688
697
  scopeKey: config.scope.scopeKey,
689
- dockerComposeFile: config.dockerComposeFile,
690
- runGoTestsScript: config.runGoTestsScript,
691
- runGoLinterScript: config.runGoLinterScript,
692
- runGoCoverageScript: config.runGoCoverageScript,
698
+ workspaceDir: scopeWorkspaceDir(config.taskKey),
693
699
  extraPrompt: config.extraPrompt,
694
700
  reviewFixPoints: config.reviewFixPoints,
701
+ mdLang: config.mdLang,
702
+ llmExecutor: launchProfile.executor,
703
+ llmModel: launchProfile.model,
704
+ launchProfile,
695
705
  iteration,
696
706
  latestIteration,
707
+ taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
708
+ designIteration: nextArtifactIteration(config.taskKey, "design"),
709
+ planIteration: nextArtifactIteration(config.taskKey, "plan"),
710
+ qaIteration: nextArtifactIteration(config.taskKey, "qa"),
697
711
  ...(latestIteration !== null ? { reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration) } : {}),
698
712
  forceRefresh: forceRefreshSummary,
699
713
  };
@@ -719,88 +733,13 @@ function flowRequiresTaskScope(entry) {
719
733
  }
720
734
  return valueReferencesTaskScopeParams(entry.flow.phases);
721
735
  }
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
736
  async function summarizeBuildFailure(output) {
799
737
  return summarizeBuildFailureViaPipeline(createPipelineContext({
800
738
  issueKey: "build-failure-summary",
801
739
  jiraRef: "build-failure-summary",
802
740
  dryRun: false,
803
741
  verbose: false,
742
+ mdLang: null,
804
743
  runtime: runtimeServices,
805
744
  requestUserInput: requestUserInputInTerminal,
806
745
  }), output);
@@ -810,27 +749,78 @@ function requireJiraConfig(config) {
810
749
  throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
811
750
  }
812
751
  }
813
- async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", runtime = runtimeServices) {
752
+ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", launchProfile, runtime = runtimeServices) {
814
753
  const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
815
- if (config.command === "auto") {
816
- if (launchMode === "restart") {
817
- resetAutoPipelineState(config);
754
+ if (config.command === "auto-golang") {
755
+ requireJiraConfig(config);
756
+ checkAutoPrerequisites(config);
757
+ process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
758
+ process.env.JIRA_API_URL = config.jiraApiUrl;
759
+ process.env.JIRA_TASK_FILE = config.jiraTaskFile;
760
+ let effectiveLaunchMode = launchMode;
761
+ let effectiveLaunchProfile = launchProfile;
762
+ if (config.autoFromPhase) {
763
+ const flow = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" });
764
+ const persistedState = loadFlowRunState(config.scope.scopeKey, "auto-golang");
765
+ if (!persistedState) {
766
+ throw new TaskRunnerError(`Cannot restart auto-golang from phase '${config.autoFromPhase}' because persisted flow state was not found.`);
767
+ }
768
+ rewindFlowRunStateToPhase(persistedState, flow.phases, config.autoFromPhase);
769
+ saveFlowRunState(persistedState);
770
+ effectiveLaunchMode = "resume";
771
+ effectiveLaunchProfile ??= persistedState.launchProfile;
772
+ printPanel("Auto-Golang Resume", `Auto-golang pipeline will continue from phase: ${config.autoFromPhase}`, "yellow");
818
773
  }
819
- await runAutoPipeline(config, setSummary, forceRefreshSummary, runtime);
774
+ await runDeclarativeFlowBySpecFile("auto-golang.json", config, autoFlowParams(config, forceRefreshSummary), effectiveLaunchProfile ? { launchProfile: effectiveLaunchProfile } : {}, requestUserInput, setSummary, effectiveLaunchMode, runtime);
775
+ return false;
776
+ }
777
+ if (config.command === "auto-common") {
778
+ requireJiraConfig(config);
779
+ checkAutoPrerequisites(config);
780
+ process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
781
+ process.env.JIRA_API_URL = config.jiraApiUrl;
782
+ process.env.JIRA_TASK_FILE = config.jiraTaskFile;
783
+ await runDeclarativeFlowBySpecFile("auto-common.json", config, autoFlowParams(config, forceRefreshSummary), launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
820
784
  return false;
821
785
  }
822
786
  if (config.command === "auto-status") {
823
- const state = loadAutoPipelineState(config);
787
+ const state = loadFlowRunState(config.scope.scopeKey, "auto-golang");
824
788
  if (!state) {
825
- printPanel("Auto Status", `No auto state file found for ${config.taskKey}.`, "yellow");
789
+ printPanel("Auto-Golang Status", `No flow state file found for ${config.taskKey}.`, "yellow");
826
790
  return false;
827
791
  }
828
- printAutoState(state);
792
+ const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
793
+ const phaseOrder = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }).phases;
794
+ const lines = [
795
+ `Issue: ${config.taskKey}`,
796
+ `Status: ${state.status}`,
797
+ `Current step: ${currentStep}`,
798
+ `Updated: ${state.updatedAt}`,
799
+ ];
800
+ if (state.launchProfile) {
801
+ lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
802
+ }
803
+ if (state.lastError) {
804
+ lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
805
+ }
806
+ lines.push("");
807
+ for (const phase of phaseOrder) {
808
+ const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
809
+ lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}`);
810
+ for (const step of phase.steps) {
811
+ const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
812
+ lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
813
+ }
814
+ }
815
+ if (state.executionState.terminated) {
816
+ lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
817
+ }
818
+ printPanel("Auto-Golang Status", lines.join("\n"), "cyan");
829
819
  return false;
830
820
  }
831
821
  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");
822
+ const removed = resetFlowRunState(config.scope.scopeKey, "auto-golang");
823
+ printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
834
824
  return false;
835
825
  }
836
826
  checkPrerequisites(config);
@@ -854,9 +844,13 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
854
844
  await runDeclarativeFlowBySpecFile("plan.json", config, {
855
845
  jiraApiUrl: config.jiraApiUrl,
856
846
  taskKey: config.taskKey,
847
+ taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
848
+ designIteration: nextArtifactIteration(config.taskKey, "design"),
849
+ planIteration: nextArtifactIteration(config.taskKey, "plan"),
850
+ qaIteration: nextArtifactIteration(config.taskKey, "qa"),
857
851
  extraPrompt: config.extraPrompt,
858
852
  forceRefresh: forceRefreshSummary,
859
- }, requestUserInput, setSummary, launchMode, runtime);
853
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
860
854
  return false;
861
855
  }
862
856
  if (config.command === "bug-analyze") {
@@ -866,21 +860,27 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
866
860
  process.stdout.write(`Resolved Jira API URL: ${config.jiraApiUrl}\n`);
867
861
  process.stdout.write(`Saving Jira issue JSON to: ${config.jiraTaskFile}\n`);
868
862
  }
869
- await runDeclarativeFlowBySpecFile("bug-analyze.json", config, {
863
+ await runDeclarativeFlowBySpecFile("bugz/bug-analyze.json", config, {
870
864
  jiraApiUrl: config.jiraApiUrl,
871
865
  taskKey: config.taskKey,
866
+ taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
867
+ bugAnalyzeIteration: nextArtifactIteration(config.taskKey, "bug-analyze"),
868
+ bugFixDesignIteration: nextArtifactIteration(config.taskKey, "bug-fix-design"),
869
+ bugFixPlanIteration: nextArtifactIteration(config.taskKey, "bug-fix-plan"),
872
870
  extraPrompt: config.extraPrompt,
873
871
  forceRefresh: forceRefreshSummary,
874
- }, requestUserInput, setSummary, launchMode, runtime);
872
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
875
873
  return false;
876
874
  }
877
875
  if (config.command === "gitlab-review") {
878
876
  const iteration = nextReviewIterationForTask(config.taskKey);
879
- await runDeclarativeFlowBySpecFile("gitlab-review.json", config, {
877
+ const gitlabReviewIteration = nextArtifactIteration(config.taskKey, "gitlab-review");
878
+ await runDeclarativeFlowBySpecFile("gitlab/gitlab-review.json", config, {
880
879
  taskKey: config.taskKey,
881
880
  iteration,
881
+ gitlabReviewIteration,
882
882
  extraPrompt: config.extraPrompt,
883
- }, requestUserInput, undefined, launchMode, runtime);
883
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
884
884
  if (!config.dryRun) {
885
885
  printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}`);
886
886
  }
@@ -888,11 +888,13 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
888
888
  }
889
889
  if (config.command === "gitlab-diff-review") {
890
890
  const iteration = nextReviewIterationForTask(config.taskKey);
891
- await runDeclarativeFlowBySpecFile("gitlab-diff-review.json", config, {
891
+ const gitlabDiffIteration = nextArtifactIteration(config.taskKey, "gitlab-diff");
892
+ await runDeclarativeFlowBySpecFile("gitlab/gitlab-diff-review.json", config, {
892
893
  taskKey: config.taskKey,
893
894
  iteration,
895
+ gitlabDiffIteration,
894
896
  extraPrompt: config.extraPrompt,
895
- }, requestUserInput, undefined, launchMode, runtime);
897
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
896
898
  if (!config.dryRun) {
897
899
  printSummary("GitLab Diff Review", `Artifacts:\n${gitlabDiffFile(config.taskKey)}\n${gitlabDiffJsonFile(config.taskKey)}\n${reviewFile(config.taskKey, iteration)}\n${reviewJsonFile(config.taskKey, iteration)}`);
898
900
  }
@@ -907,28 +909,30 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
907
909
  { path: bugFixDesignJsonFile(config.taskKey), schemaId: "bug-fix-design/v1" },
908
910
  { path: bugFixPlanJsonFile(config.taskKey), schemaId: "bug-fix-plan/v1" },
909
911
  ], "Bug-fix mode requires valid structured artifacts from the bug analysis phase.");
910
- await runDeclarativeFlowBySpecFile("bug-fix.json", config, {
912
+ await runDeclarativeFlowBySpecFile("bugz/bug-fix.json", config, {
911
913
  taskKey: config.taskKey,
912
914
  extraPrompt: config.extraPrompt,
913
- }, requestUserInput, undefined, launchMode, runtime);
915
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
914
916
  return false;
915
917
  }
916
918
  if (config.command === "mr-description") {
917
919
  requireJiraConfig(config);
918
920
  requireJiraTaskFile(config.jiraTaskFile);
919
- await runDeclarativeFlowBySpecFile("mr-description.json", config, {
921
+ await runDeclarativeFlowBySpecFile("gitlab/mr-description.json", config, {
920
922
  taskKey: config.taskKey,
923
+ iteration: nextArtifactIteration(config.taskKey, "mr-description"),
921
924
  extraPrompt: config.extraPrompt,
922
- }, requestUserInput, undefined, launchMode, runtime);
925
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
923
926
  return false;
924
927
  }
925
928
  if (config.command === "task-describe") {
926
- requireJiraConfig(config);
929
+ const iteration = nextArtifactIteration(config.taskKey, "jira-description");
927
930
  await runDeclarativeFlowBySpecFile("task-describe.json", config, {
928
931
  jiraApiUrl: config.jiraApiUrl,
929
932
  taskKey: config.taskKey,
933
+ iteration,
930
934
  extraPrompt: config.extraPrompt,
931
- }, requestUserInput, undefined, launchMode, runtime);
935
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
932
936
  return false;
933
937
  }
934
938
  if (config.command === "implement") {
@@ -941,7 +945,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
941
945
  await runDeclarativeFlowBySpecFile("implement.json", config, {
942
946
  taskKey: config.taskKey,
943
947
  extraPrompt: config.extraPrompt,
944
- }, requestUserInput, undefined, launchMode);
948
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode);
945
949
  return false;
946
950
  }
947
951
  if (config.command === "review") {
@@ -952,142 +956,73 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
952
956
  { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
953
957
  { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
954
958
  ], "Review mode requires valid structured plan artifacts from the planning phase.");
955
- await runDeclarativeFlowBySpecFile("review.json", config, {
959
+ await runDeclarativeFlowBySpecFile("review/review.json", config, {
956
960
  taskKey: config.taskKey,
957
961
  iteration,
958
962
  extraPrompt: config.extraPrompt,
959
- }, requestUserInput, undefined, launchMode, runtime);
963
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
960
964
  }
961
965
  else {
962
- await runDeclarativeFlowBySpecFile("review-project.json", config, {
966
+ await runDeclarativeFlowBySpecFile("review/review-project.json", config, {
963
967
  taskKey: config.taskKey,
964
968
  iteration,
965
969
  extraPrompt: config.extraPrompt,
966
- }, requestUserInput, undefined, launchMode, runtime);
970
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
967
971
  }
968
972
  return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
969
973
  }
970
974
  if (config.command === "review-fix") {
971
- const latestIteration = latestReviewReplyIteration(config.taskKey);
975
+ const latestIteration = latestArtifactIteration(config.taskKey, "review");
972
976
  if (latestIteration === null) {
973
- throw new TaskRunnerError("Review-fix mode requires at least one review-reply artifact.");
977
+ throw new TaskRunnerError("Review-fix mode requires at least one review artifact.");
974
978
  }
975
979
  validateStructuredArtifacts([
976
980
  { path: reviewJsonFile(config.taskKey, latestIteration), schemaId: "review-findings/v1" },
977
- { path: reviewReplyJsonFile(config.taskKey, latestIteration), schemaId: "review-reply/v1" },
978
981
  ], "Review-fix mode requires valid structured review artifacts.");
979
- await runDeclarativeFlowBySpecFile("review-fix.json", config, {
982
+ await runDeclarativeFlowBySpecFile("review/review-fix.json", config, {
980
983
  taskKey: config.taskKey,
981
984
  latestIteration,
982
985
  reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
983
986
  extraPrompt: config.extraPrompt,
984
987
  reviewFixPoints: config.reviewFixPoints,
985
- }, requestUserInput, undefined, launchMode, runtime);
988
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
986
989
  return false;
987
990
  }
991
+ if (config.command === "review-loop") {
992
+ const iteration = nextReviewIterationForTask(config.taskKey);
993
+ if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
994
+ requireJiraConfig(config);
995
+ validateStructuredArtifacts([
996
+ { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
997
+ { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
998
+ ], "Review-loop mode requires valid structured plan artifacts from the planning phase.");
999
+ }
1000
+ await runDeclarativeFlowBySpecFile("review/review-loop.json", config, {
1001
+ taskKey: config.taskKey,
1002
+ iteration,
1003
+ extraPrompt: config.extraPrompt,
1004
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1005
+ return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
1006
+ }
988
1007
  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, {
1008
+ await runDeclarativeFlowBySpecFile(config.command === "run-go-tests-loop" ? "go/run-go-tests-loop.json" : "go/run-go-linter-loop.json", config, {
990
1009
  taskKey: config.taskKey,
991
- runGoTestsScript: config.runGoTestsScript,
992
- runGoLinterScript: config.runGoLinterScript,
1010
+ runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
1011
+ runGoLinterScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_linter.py"),
1012
+ runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
1013
+ runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
993
1014
  extraPrompt: config.extraPrompt,
994
- }, requestUserInput, undefined, launchMode, runtime);
1015
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
995
1016
  return false;
996
1017
  }
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
- }
1018
+ if (config.command === "git-commit") {
1019
+ await runDeclarativeFlowBySpecFile("git-commit.json", config, {
1020
+ taskKey: config.taskKey,
1021
+ extraPrompt: config.extraPrompt,
1022
+ }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1023
+ return false;
1090
1024
  }
1025
+ throw new TaskRunnerError(`Unsupported command: ${config.command}`);
1091
1026
  }
1092
1027
  function parseCliArgs(argv) {
1093
1028
  if (argv.includes("--version") || argv.includes("-v")) {
@@ -1114,6 +1049,7 @@ function parseCliArgs(argv) {
1114
1049
  let scopeName;
1115
1050
  let helpPhases = false;
1116
1051
  let jiraRef;
1052
+ let mdLang;
1117
1053
  for (let index = 1; index < argv.length; index += 1) {
1118
1054
  const token = argv[index] ?? "";
1119
1055
  if (token === "--dry") {
@@ -1143,12 +1079,39 @@ function parseCliArgs(argv) {
1143
1079
  index += 1;
1144
1080
  continue;
1145
1081
  }
1082
+ if (token === "--md-lang") {
1083
+ const langValue = argv[index + 1];
1084
+ if (langValue === "en" || langValue === "ru") {
1085
+ mdLang = langValue;
1086
+ }
1087
+ else {
1088
+ process.stderr.write("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
1089
+ process.exit(1);
1090
+ }
1091
+ index += 1;
1092
+ continue;
1093
+ }
1094
+ if (token.startsWith("--md-lang=")) {
1095
+ const langValue = token.slice("--md-lang=".length);
1096
+ if (langValue === "en" || langValue === "ru") {
1097
+ mdLang = langValue;
1098
+ }
1099
+ else {
1100
+ process.stderr.write("Error: --md-lang accepts only 'en' or 'ru' as values.\n");
1101
+ process.exit(1);
1102
+ }
1103
+ continue;
1104
+ }
1146
1105
  jiraRef = token;
1147
1106
  }
1148
- if (command === "auto" && helpPhases) {
1107
+ if (command === "auto-golang" && helpPhases) {
1149
1108
  printAutoPhasesHelp();
1150
1109
  process.exit(0);
1151
1110
  }
1111
+ if (command === "auto-common" && helpPhases) {
1112
+ printAutoCommonPhasesHelp();
1113
+ process.exit(0);
1114
+ }
1152
1115
  return {
1153
1116
  command: command,
1154
1117
  dry,
@@ -1158,6 +1121,7 @@ function parseCliArgs(argv) {
1158
1121
  ...(scopeName !== undefined ? { scopeName } : {}),
1159
1122
  ...(prompt !== undefined ? { prompt } : {}),
1160
1123
  ...(autoFromPhase !== undefined ? { autoFromPhase } : {}),
1124
+ ...(mdLang !== undefined ? { mdLang } : {}),
1161
1125
  };
1162
1126
  }
1163
1127
  function buildConfigFromArgs(args) {
@@ -1166,6 +1130,7 @@ function buildConfigFromArgs(args) {
1166
1130
  ...(args.scopeName !== undefined ? { scopeName: args.scopeName } : {}),
1167
1131
  ...(args.prompt !== undefined ? { extraPrompt: args.prompt } : {}),
1168
1132
  ...(args.autoFromPhase !== undefined ? { autoFromPhase: args.autoFromPhase } : {}),
1133
+ ...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
1169
1134
  dryRun: args.dry,
1170
1135
  verbose: args.verbose,
1171
1136
  });
@@ -1183,34 +1148,13 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1183
1148
  summaryText: "",
1184
1149
  cwd: process.cwd(),
1185
1150
  gitBranchName,
1151
+ version: packageVersion(),
1186
1152
  flows: interactiveFlowDefinitions(flowCatalog),
1187
1153
  getRunConfirmation: async (flowId) => {
1188
1154
  const flowEntry = findCatalogEntry(flowId, flowCatalog);
1189
1155
  if (!flowEntry) {
1190
1156
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
1191
1157
  }
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
1158
  const resumeLookup = lookupInteractiveFlowResume(flowEntry, currentScope);
1215
1159
  return resumeLookup;
1216
1160
  },
@@ -1223,6 +1167,16 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1223
1167
  if (!flowEntry) {
1224
1168
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
1225
1169
  }
1170
+ const resumeState = launchMode === "resume" ? loadFlowRunState(currentScope.scopeKey, flowId) : null;
1171
+ if (resumeState) {
1172
+ currentScope = scopeWithRestoredJiraContext(currentScope, resumeState);
1173
+ }
1174
+ const launchProfile = launchMode === "resume"
1175
+ ? resumeState?.launchProfile
1176
+ : await requestInteractiveLaunchProfile((form) => ui.requestUserInput(form));
1177
+ if (!launchProfile) {
1178
+ throw new TaskRunnerError("Resume is impossible because launch profile was not saved. Use restart.");
1179
+ }
1226
1180
  const previousScopeKey = currentScope.scopeKey;
1227
1181
  const baseConfig = buildBaseConfig(flowId, {
1228
1182
  ...(currentScope.jiraRef ? { jiraRef: currentScope.jiraRef } : {}),
@@ -1240,12 +1194,13 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1240
1194
  if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
1241
1195
  syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
1242
1196
  }
1197
+ printPanel("Effective Launch Config", `executor: ${launchProfile.executor}\nmodel: ${launchProfile.model}\nmode: ${launchMode}`, "cyan");
1243
1198
  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));
1199
+ await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, launchProfile, createRuntimeServices(abortController.signal));
1245
1200
  return;
1246
1201
  }
1247
1202
  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));
1203
+ await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh, { launchProfile }), { launchProfile }, (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode, createRuntimeServices(abortController.signal));
1249
1204
  }
1250
1205
  catch (error) {
1251
1206
  if (error instanceof FlowInterruptedError) {
@@ -1309,7 +1264,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1309
1264
  });
1310
1265
  }
1311
1266
  export async function main(argv = process.argv.slice(2)) {
1312
- loadEnvFile(path.join(process.cwd(), ".env"));
1267
+ loadTieredEnv(process.cwd());
1313
1268
  let forceRefresh = false;
1314
1269
  const args = [...argv];
1315
1270
  if (args[0] === "--force") {