agentweaver 0.1.15 → 0.1.16

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 (94) hide show
  1. package/README.md +26 -9
  2. package/dist/artifact-manifest.js +219 -0
  3. package/dist/artifacts.js +15 -0
  4. package/dist/doctor/checks/env-diagnostics.js +25 -0
  5. package/dist/doctor/checks/flow-readiness.js +15 -18
  6. package/dist/flow-state.js +75 -15
  7. package/dist/index.js +391 -175
  8. package/dist/interactive/blessed-session.js +361 -0
  9. package/dist/interactive/controller.js +1293 -0
  10. package/dist/interactive/create-interactive-session.js +5 -0
  11. package/dist/interactive/ink/index.js +576 -0
  12. package/dist/interactive/progress.js +245 -0
  13. package/dist/interactive/selectors.js +14 -0
  14. package/dist/interactive/session.js +1 -0
  15. package/dist/interactive/state.js +34 -0
  16. package/dist/interactive/tree.js +155 -0
  17. package/dist/interactive/types.js +1 -0
  18. package/dist/interactive/view-model.js +1 -0
  19. package/dist/interactive-ui.js +159 -194
  20. package/dist/pipeline/context.js +1 -0
  21. package/dist/pipeline/declarative-flow-runner.js +212 -6
  22. package/dist/pipeline/declarative-flows.js +27 -0
  23. package/dist/pipeline/execution-routing-config.js +15 -0
  24. package/dist/pipeline/flow-catalog.js +19 -3
  25. package/dist/pipeline/flow-run-resume.js +29 -0
  26. package/dist/pipeline/flow-specs/auto-common.json +89 -360
  27. package/dist/pipeline/flow-specs/auto-golang.json +58 -363
  28. package/dist/pipeline/flow-specs/auto-simple.json +141 -0
  29. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +2 -0
  30. package/dist/pipeline/flow-specs/bugz/bug-fix.json +1 -0
  31. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +304 -0
  32. package/dist/pipeline/flow-specs/design-review.json +10 -0
  33. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +11 -0
  34. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +2 -0
  35. package/dist/pipeline/flow-specs/gitlab/mr-description.json +1 -0
  36. package/dist/pipeline/flow-specs/go/run-go-linter-loop.json +2 -0
  37. package/dist/pipeline/flow-specs/go/run-go-tests-loop.json +2 -0
  38. package/dist/pipeline/flow-specs/implement.json +13 -6
  39. package/dist/pipeline/flow-specs/instant-task.json +177 -0
  40. package/dist/pipeline/flow-specs/normalize-task-source.json +311 -0
  41. package/dist/pipeline/flow-specs/plan-revise.json +7 -1
  42. package/dist/pipeline/flow-specs/plan.json +48 -70
  43. package/dist/pipeline/flow-specs/review/review-fix.json +24 -4
  44. package/dist/pipeline/flow-specs/review/review-loop.json +351 -45
  45. package/dist/pipeline/flow-specs/review/review-project-loop.json +590 -0
  46. package/dist/pipeline/flow-specs/review/review-project.json +12 -0
  47. package/dist/pipeline/flow-specs/review/review.json +37 -31
  48. package/dist/pipeline/flow-specs/task-describe.json +2 -0
  49. package/dist/pipeline/flow-specs/task-source/jira-fetch.json +70 -0
  50. package/dist/pipeline/flow-specs/task-source/manual-input.json +216 -0
  51. package/dist/pipeline/node-registry.js +41 -1
  52. package/dist/pipeline/node-runner.js +3 -2
  53. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +5 -1
  54. package/dist/pipeline/nodes/clear-ready-to-merge-node.js +11 -0
  55. package/dist/pipeline/nodes/commit-message-form-node.js +8 -0
  56. package/dist/pipeline/nodes/design-review-verdict-node.js +36 -0
  57. package/dist/pipeline/nodes/ensure-summary-json-node.js +13 -2
  58. package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +19 -2
  59. package/dist/pipeline/nodes/fetch-gitlab-review-node.js +19 -2
  60. package/dist/pipeline/nodes/flow-run-node.js +226 -7
  61. package/dist/pipeline/nodes/git-commit-form-node.js +8 -0
  62. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +19 -2
  63. package/dist/pipeline/nodes/jira-fetch-node.js +50 -4
  64. package/dist/pipeline/nodes/llm-prompt-node.js +32 -12
  65. package/dist/pipeline/nodes/planning-bundle-node.js +10 -0
  66. package/dist/pipeline/nodes/review-verdict-node.js +86 -0
  67. package/dist/pipeline/nodes/select-files-form-node.js +8 -0
  68. package/dist/pipeline/nodes/structured-summary-node.js +24 -0
  69. package/dist/pipeline/nodes/user-input-node.js +38 -3
  70. package/dist/pipeline/nodes/write-selection-file-node.js +20 -4
  71. package/dist/pipeline/prompt-registry.js +3 -1
  72. package/dist/pipeline/prompt-runtime.js +4 -1
  73. package/dist/pipeline/review-iteration.js +26 -0
  74. package/dist/pipeline/spec-compiler.js +2 -0
  75. package/dist/pipeline/spec-types.js +3 -0
  76. package/dist/pipeline/spec-validator.js +14 -0
  77. package/dist/pipeline/value-resolver.js +74 -1
  78. package/dist/prompts.js +36 -14
  79. package/dist/review-severity.js +45 -0
  80. package/dist/runtime/artifact-registry.js +402 -0
  81. package/dist/runtime/design-review-input-contract.js +17 -16
  82. package/dist/runtime/env-loader.js +3 -0
  83. package/dist/runtime/execution-routing-store.js +134 -0
  84. package/dist/runtime/execution-routing.js +227 -0
  85. package/dist/runtime/interactive-execution-routing.js +462 -0
  86. package/dist/runtime/plan-revise-input-contract.js +35 -32
  87. package/dist/runtime/planning-bundle.js +123 -0
  88. package/dist/runtime/ready-to-merge.js +22 -1
  89. package/dist/runtime/review-input-contract.js +100 -0
  90. package/dist/structured-artifact-schema-registry.js +9 -0
  91. package/dist/structured-artifact-schemas.json +140 -1
  92. package/dist/structured-artifacts.js +77 -6
  93. package/dist/user-input.js +70 -3
  94. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -3,27 +3,34 @@ import { existsSync, 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 { bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designReviewFile, designReviewJsonFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, latestArtifactIteration, nextArtifactIteration, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
6
+ import { bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designReviewFile, designReviewJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, instantTaskInputJsonFile, latestArtifactIteration, nextArtifactIteration, readyToMergeFile, requireArtifacts, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
7
7
  import { FlowInterruptedError, TaskRunnerError } from "./errors.js";
8
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
+ import { AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV, parseReviewSeverityCsv, resolveReviewBlockingSeveritiesFromEnv, } from "./review-severity.js";
11
12
  import { summarizeBuildFailure as summarizeBuildFailureViaPipeline } from "./pipeline/build-failure-summary.js";
12
13
  import { runNodeChecks } from "./pipeline/checks.js";
13
14
  import { createPipelineContext } from "./pipeline/context.js";
14
- import { loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
15
+ import { collectFlowRoutingGroups, loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
15
16
  import { runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
16
- import { findCatalogEntry, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef } from "./pipeline/flow-catalog.js";
17
- import { ALLOWED_MODELS_BY_EXECUTOR, defaultModelForExecutor, DEFAULT_LAUNCH_PROFILE, LLM_EXECUTOR_IDS, resolveLaunchProfile, } from "./pipeline/launch-profile-config.js";
17
+ import { builtInCommandFlowFile, findCatalogEntry, flowRoutingGroups, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef, } from "./pipeline/flow-catalog.js";
18
+ import { DEFAULT_LAUNCH_PROFILE, } from "./pipeline/launch-profile-config.js";
19
+ import { withCanonicalReviewLoopParams } from "./pipeline/review-iteration.js";
18
20
  import { evaluateCondition, resolveValue } from "./pipeline/value-resolver.js";
19
21
  import { resolveCmd } from "./runtime/command-resolution.js";
20
22
  import { loadTieredEnv } from "./runtime/env-loader.js";
21
23
  import { agentweaverHome } from "./runtime/agentweaver-home.js";
22
24
  import { runCommand } from "./runtime/process-runner.js";
25
+ import { createArtifactRegistry } from "./runtime/artifact-registry.js";
23
26
  import { resolveDesignReviewInputContract } from "./runtime/design-review-input-contract.js";
24
27
  import { resolvePlanReviseInputContract } from "./runtime/plan-revise-input-contract.js";
28
+ import { resolveLatestPlanningBundle } from "./runtime/planning-bundle.js";
29
+ import { inspectReviewInputContract, resolveReviewInputContract } from "./runtime/review-input-contract.js";
25
30
  import { clearReadyToMergeFile } from "./runtime/ready-to-merge.js";
26
- import { InteractiveUi } from "./interactive-ui.js";
31
+ import { describeExecutionRouting, executorsForRoutingGroups, resolveExecutionRouting, } from "./runtime/execution-routing.js";
32
+ import { requestInteractiveExecutionRouting } from "./runtime/interactive-execution-routing.js";
33
+ import { createInteractiveSession } from "./interactive/create-interactive-session.js";
27
34
  import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
28
35
  import { requestUserInputInTerminal } from "./user-input.js";
29
36
  import { runDoctorCommand } from "./doctor/index.js";
@@ -31,6 +38,7 @@ import { detectGitBranchName, requestJiraContext, resolveProjectScope, } from ".
31
38
  const COMMANDS = [
32
39
  "auto-golang",
33
40
  "auto-common",
41
+ "auto-simple",
34
42
  "auto-status",
35
43
  "auto-reset",
36
44
  "bug-analyze",
@@ -40,6 +48,7 @@ const COMMANDS = [
40
48
  "git-commit",
41
49
  "gitlab-diff-review",
42
50
  "gitlab-review",
51
+ "instant-task",
43
52
  "mr-description",
44
53
  "plan",
45
54
  "plan-revise",
@@ -57,6 +66,7 @@ function createRuntimeServices(signal) {
57
66
  return {
58
67
  resolveCmd,
59
68
  runCommand: (argv, options = {}) => runCommand(argv, { ...options, ...(signal ? { signal } : {}) }),
69
+ artifactRegistry: createArtifactRegistry(),
60
70
  };
61
71
  }
62
72
  const runtimeServices = createRuntimeServices();
@@ -106,19 +116,24 @@ function usage() {
106
116
  agentweaver bug-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
107
117
  agentweaver design-review [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
108
118
  agentweaver doctor [<category>|<check-id>] [--json]
119
+ agentweaver instant-task [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>]
109
120
  agentweaver mr-description [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
110
121
  agentweaver plan [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
111
122
  agentweaver plan-revise [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
112
123
  agentweaver task-describe [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
113
124
  agentweaver implement [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
114
- agentweaver review [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
115
- agentweaver review-fix [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
116
- agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
125
+ agentweaver review [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
126
+ agentweaver review-fix [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
127
+ agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
117
128
  agentweaver run-go-tests-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
118
129
  agentweaver run-go-linter-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
119
130
  agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
120
131
  agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
121
132
  agentweaver auto-golang --help-phases
133
+ agentweaver auto-common [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
134
+ agentweaver auto-common --help-phases
135
+ agentweaver auto-simple [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
136
+ agentweaver auto-simple --help-phases
122
137
  agentweaver auto-status [<jira-browse-url|jira-issue-key>]
123
138
  agentweaver auto-reset [<jira-browse-url|jira-issue-key>]
124
139
 
@@ -132,8 +147,9 @@ Flags:
132
147
  --force In interactive mode, regenerate task summary in Jira-backed flows
133
148
  --dry Fetch Jira task, but print codex/opencode commands instead of executing them
134
149
  --verbose Show live stdout/stderr of launched commands
135
- --scope Explicit workflow scope name for non-Jira runs
150
+ --scope Explicit workflow scope name for non-Jira runs except instant-task
136
151
  --prompt Extra prompt text appended to the base prompt
152
+ --blocking-severities Comma-separated severities that block merge and drive review-fix auto-selection
137
153
  --md-lang Language for markdown output files: en (English) or ru (Russian, default)
138
154
 
139
155
  Required environment variables:
@@ -145,6 +161,7 @@ Optional environment variables:
145
161
  JIRA_BASE_URL
146
162
  GITLAB_TOKEN
147
163
  AGENTWEAVER_HOME
164
+ ${AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV}
148
165
  CODEX_BIN
149
166
  CODEX_MODEL
150
167
  OPENCODE_BIN
@@ -152,8 +169,11 @@ Optional environment variables:
152
169
 
153
170
  Notes:
154
171
  - 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.
172
+ - instant-task always uses the current branch-derived project scope and rejects explicit scope overrides or Jira arguments.
155
173
  - All flow state and artifacts are stored in the current project scope by default.
156
- - gitlab-review and gitlab-diff-review ask for GitLab merge request URL via user-input.`;
174
+ - gitlab-review and gitlab-diff-review ask for GitLab merge request URL via user-input.
175
+ - ${AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV} sets the default blocking severities. Default: blocker,critical,high.
176
+ - Interactive mode requires Ink runtime dependencies and a real TTY.`;
157
177
  }
158
178
  function packageVersion() {
159
179
  const packageJsonPath = path.join(PACKAGE_ROOT, "package.json");
@@ -183,7 +203,11 @@ function buildFlowResumeDetails(state) {
183
203
  `Current step: ${currentStep}`,
184
204
  `Updated: ${state.updatedAt}`,
185
205
  ];
186
- if (state.launchProfile) {
206
+ if (state.executionRouting) {
207
+ lines.push(`Default route: ${state.executionRouting.defaultRoute.executor} / ${state.executionRouting.defaultRoute.model}`);
208
+ lines.push(`Routing fingerprint: ${state.executionRouting.fingerprint}`);
209
+ }
210
+ else if (state.launchProfile) {
187
211
  lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
188
212
  }
189
213
  if (state.lastError) {
@@ -191,79 +215,6 @@ function buildFlowResumeDetails(state) {
191
215
  }
192
216
  return lines.join("\n");
193
217
  }
194
- function launchProfileSelectionForm() {
195
- const defaultExecutor = DEFAULT_LAUNCH_PROFILE.executor;
196
- return {
197
- formId: "flow-launch-profile",
198
- title: "LLM Launch Settings",
199
- description: `Select an executor for the flow. Current default: ${defaultExecutor}.`,
200
- submitLabel: "Continue",
201
- fields: [
202
- {
203
- id: "executor",
204
- type: "single-select",
205
- label: "Executor",
206
- required: true,
207
- default: defaultExecutor,
208
- options: LLM_EXECUTOR_IDS.map((id) => ({
209
- value: id,
210
- label: id === defaultExecutor ? `${id} [default]` : id,
211
- })),
212
- },
213
- ],
214
- };
215
- }
216
- function launchModelSelectionForm(executor) {
217
- const resolvedExecutor = executor === "default" ? DEFAULT_LAUNCH_PROFILE.executor : executor;
218
- const defaultModel = defaultModelForExecutor(resolvedExecutor);
219
- const options = ALLOWED_MODELS_BY_EXECUTOR[resolvedExecutor].map((model) => ({
220
- value: model,
221
- label: model === defaultModel ? `${model} [default]` : model,
222
- }));
223
- return {
224
- formId: "flow-launch-model",
225
- title: "LLM Launch Settings",
226
- description: `Select a model for the flow. Current default for ${resolvedExecutor}: ${defaultModel}.`,
227
- submitLabel: "Start",
228
- fields: [
229
- {
230
- id: "model",
231
- type: "single-select",
232
- label: "Model",
233
- required: true,
234
- default: defaultModel,
235
- options,
236
- },
237
- ],
238
- };
239
- }
240
- function isFormCancellation(error, formId) {
241
- return error instanceof TaskRunnerError && error.message === `User cancelled form '${formId}'.`;
242
- }
243
- async function requestInteractiveLaunchProfile(requestUserInput) {
244
- for (;;) {
245
- const executorFormResult = await requestUserInput(launchProfileSelectionForm());
246
- const rawExecutor = String(executorFormResult.values.executor ?? DEFAULT_LAUNCH_PROFILE.executor);
247
- const executor = LLM_EXECUTOR_IDS.find((id) => id === rawExecutor);
248
- if (!executor) {
249
- throw new TaskRunnerError(`Unsupported launch executor '${rawExecutor}'.`);
250
- }
251
- try {
252
- const modelFormResult = await requestUserInput(launchModelSelectionForm(executor));
253
- const rawModel = String(modelFormResult.values.model ?? defaultModelForExecutor(executor)).trim();
254
- return resolveLaunchProfile({
255
- executor,
256
- model: rawModel.length > 0 ? rawModel : defaultModelForExecutor(executor),
257
- }, DEFAULT_LAUNCH_PROFILE);
258
- }
259
- catch (error) {
260
- if (isFormCancellation(error, "flow-launch-model")) {
261
- continue;
262
- }
263
- throw error;
264
- }
265
- }
266
- }
267
218
  function buildResolverContext(pipelineContext, flowParams, flowConstants, repeatVars, executionState) {
268
219
  return {
269
220
  flowParams,
@@ -337,13 +288,21 @@ function validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext,
337
288
  }
338
289
  }
339
290
  }
340
- function validateDeclarativeFlowResumeState(flowEntry, config, state, launchProfile, runtime = runtimeServices) {
341
- if (state.launchProfile) {
342
- if (!launchProfile) {
343
- throw new TaskRunnerError("Resume is impossible because launch profile is missing. Use restart.");
291
+ function validateDeclarativeFlowResumeState(flowEntry, config, state, executionRouting, runtime = runtimeServices) {
292
+ if (state.flowId === "auto-common") {
293
+ const persistedPhaseIds = state.executionState.phases.map((p) => p.id);
294
+ const hasLegacyPlanningGatePhases = persistedPhaseIds.some((id) => ["design_review", "verdict", "plan_revision", "design_review_repeat", "verdict_repeat"].includes(id));
295
+ if (hasLegacyPlanningGatePhases) {
296
+ throw new TaskRunnerError("Resume is impossible because the persisted state was created with the legacy phase graph. Use restart.");
344
297
  }
345
- if (state.launchProfile.fingerprint !== launchProfile.fingerprint) {
346
- throw new TaskRunnerError(`Resume is impossible because launch profile changed (${state.launchProfile.executor}/${state.launchProfile.model} -> ${launchProfile.executor}/${launchProfile.model}). Use restart.`);
298
+ }
299
+ const persistedFingerprint = state.routingFingerprint ?? state.executionRouting?.fingerprint ?? state.launchProfile?.fingerprint;
300
+ if (persistedFingerprint) {
301
+ if (!executionRouting) {
302
+ throw new TaskRunnerError("Resume is impossible because execution routing is missing. Use restart.");
303
+ }
304
+ if (persistedFingerprint !== executionRouting.fingerprint) {
305
+ throw new TaskRunnerError("Resume is impossible because execution routing changed. Use restart.");
347
306
  }
348
307
  }
349
308
  if (flowRequiresTaskScope(flowEntry) && !config.jiraRef) {
@@ -357,8 +316,9 @@ function validateDeclarativeFlowResumeState(flowEntry, config, state, launchProf
357
316
  ...(config.mdLang !== undefined ? { mdLang: config.mdLang } : {}),
358
317
  runtime,
359
318
  requestUserInput: requestUserInputInTerminal,
319
+ ...(executionRouting ? { executionRouting } : {}),
360
320
  });
361
- const flowParams = defaultDeclarativeFlowParams(config, false, launchProfile ? { launchProfile } : {});
321
+ const flowParams = defaultDeclarativeFlowParams(config, false, executionRouting ? { executionRouting, launchProfile: executionRouting.defaultRoute } : {});
362
322
  for (const phase of flowEntry.flow.phases) {
363
323
  const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
364
324
  if (!phaseState) {
@@ -375,7 +335,7 @@ function scopeWithRestoredJiraContext(scope, state) {
375
335
  }
376
336
  function buildInteractiveBaseConfig(flowId, scope) {
377
337
  return buildBaseConfig(flowId, {
378
- ...(scope.jiraRef ? { jiraRef: scope.jiraRef } : {}),
338
+ ...(flowId !== "instant-task" && scope.jiraRef ? { jiraRef: scope.jiraRef } : {}),
379
339
  });
380
340
  }
381
341
  function lookupInteractiveFlowResume(flowEntry, currentScope) {
@@ -385,7 +345,7 @@ function lookupInteractiveFlowResume(flowEntry, currentScope) {
385
345
  const effectiveScope = scopeWithRestoredJiraContext(currentScope, directState);
386
346
  const baseConfig = buildInteractiveBaseConfig(flowEntry.id, effectiveScope);
387
347
  const config = buildRuntimeConfig(baseConfig, effectiveScope);
388
- validateDeclarativeFlowResumeState(flowEntry, config, directState, directState.launchProfile);
348
+ validateDeclarativeFlowResumeState(flowEntry, config, directState, directState.executionRouting);
389
349
  return {
390
350
  resumeAvailable: true,
391
351
  hasExistingState: true,
@@ -418,6 +378,14 @@ function printAutoCommonPhasesHelp() {
418
378
  phaseLines.push("", "You can run auto-common with:", "agentweaver auto-common <jira>");
419
379
  printPanel("Auto-Common Phases", phaseLines.join("\n"), "magenta");
420
380
  }
381
+ function autoSimplePhaseIds() {
382
+ return loadDeclarativeFlow({ source: "built-in", fileName: "auto-simple.json" }).phases.map((phase) => phase.id);
383
+ }
384
+ function printAutoSimplePhasesHelp() {
385
+ const phaseLines = ["Available auto-simple phases:", "", ...autoSimplePhaseIds()];
386
+ phaseLines.push("", "You can run auto-simple with:", "agentweaver auto-simple <jira>");
387
+ printPanel("Auto-Simple Phases", phaseLines.join("\n"), "magenta");
388
+ }
421
389
  function nextReviewIterationForTask(taskKey) {
422
390
  return nextArtifactIteration(taskKey, "review");
423
391
  }
@@ -430,6 +398,7 @@ function buildBaseConfig(command, options = {}) {
430
398
  jiraRef: options.jiraRef ?? null,
431
399
  scopeName: options.scopeName ?? null,
432
400
  reviewFixPoints: options.reviewFixPoints ?? null,
401
+ reviewBlockingSeverities: options.reviewBlockingSeverities ?? resolveReviewBlockingSeveritiesFromEnv(),
433
402
  extraPrompt: options.extraPrompt ?? null,
434
403
  autoFromPhase: options.autoFromPhase ? validateAutoPhaseId(options.autoFromPhase) : null,
435
404
  mdLang: options.mdLang ?? null,
@@ -439,21 +408,23 @@ function buildBaseConfig(command, options = {}) {
439
408
  };
440
409
  }
441
410
  function commandRequiresTask(command) {
442
- return (command === "plan" ||
443
- command === "plan-revise" ||
411
+ return (command === "plan-revise" ||
444
412
  command === "bug-analyze" ||
445
413
  command === "bug-fix" ||
446
414
  command === "design-review" ||
447
415
  command === "mr-description" ||
448
416
  command === "auto-golang" ||
449
417
  command === "auto-common" ||
418
+ command === "auto-simple" ||
450
419
  command === "auto-status" ||
451
420
  command === "auto-reset");
452
421
  }
453
422
  function commandSupportsProjectScope(command) {
454
- return (command === "git-commit" ||
423
+ return (command === "plan" ||
424
+ command === "git-commit" ||
455
425
  command === "gitlab-diff-review" ||
456
426
  command === "gitlab-review" ||
427
+ command === "instant-task" ||
457
428
  command === "task-describe" ||
458
429
  command === "implement" ||
459
430
  command === "review" ||
@@ -463,9 +434,21 @@ function commandSupportsProjectScope(command) {
463
434
  command === "run-go-linter-loop");
464
435
  }
465
436
  async function resolveScopeForCommand(config, requestUserInput) {
437
+ if (config.command === "instant-task") {
438
+ if (config.scopeName?.trim()) {
439
+ throw new TaskRunnerError("Command 'instant-task' rejects explicit scope overrides. The current branch-derived scope is the only supported lineage identity.");
440
+ }
441
+ if (config.jiraRef?.trim()) {
442
+ throw new TaskRunnerError("Command 'instant-task' does not accept a Jira task argument. Start it without a positional Jira reference.");
443
+ }
444
+ return resolveProjectScope();
445
+ }
466
446
  if (config.jiraRef?.trim()) {
467
447
  return resolveProjectScope(config.scopeName, config.jiraRef);
468
448
  }
449
+ if (config.command === "plan") {
450
+ return resolveProjectScope(config.scopeName);
451
+ }
469
452
  if (commandRequiresTask(config.command)) {
470
453
  try {
471
454
  const jiraContext = await requestJiraContext(requestUserInput);
@@ -496,27 +479,61 @@ function buildRuntimeConfig(baseConfig, scope) {
496
479
  ...(scope.jiraTaskFile ? { jiraTaskFile: scope.jiraTaskFile } : {}),
497
480
  };
498
481
  }
499
- function checkPrerequisites(config) {
500
- if (config.command === "bug-analyze" ||
501
- config.command === "bug-fix" ||
502
- config.command === "mr-description" ||
503
- config.command === "plan" ||
504
- config.command === "task-describe" ||
505
- config.command === "review" ||
506
- config.command === "run-go-tests-loop" ||
507
- config.command === "run-go-linter-loop") {
482
+ function routingForPrerequisites(launchProfile, executionRouting) {
483
+ if (executionRouting) {
484
+ return executionRouting;
485
+ }
486
+ return resolveExecutionRouting({
487
+ defaultRoute: launchProfile
488
+ ? {
489
+ executor: launchProfile.executor,
490
+ model: launchProfile.model,
491
+ }
492
+ : {
493
+ executor: DEFAULT_LAUNCH_PROFILE.executor,
494
+ model: DEFAULT_LAUNCH_PROFILE.model,
495
+ },
496
+ });
497
+ }
498
+ function flowSpecFileForPrerequisiteChecks(command) {
499
+ return isBuiltInCommandFlowId(command) ? builtInCommandFlowFile(command) : null;
500
+ }
501
+ function commandRoutingGroupsForPrerequisiteChecks(command, cwd) {
502
+ const fileName = flowSpecFileForPrerequisiteChecks(command);
503
+ if (!fileName) {
504
+ return [];
505
+ }
506
+ return collectFlowRoutingGroups(loadDeclarativeFlow({ source: "built-in", fileName }), cwd);
507
+ }
508
+ function resolveExecutorPrerequisite(executor) {
509
+ if (executor === "codex") {
508
510
  resolveCmd("codex", "CODEX_BIN");
511
+ return;
512
+ }
513
+ resolveCmd("opencode", "OPENCODE_BIN");
514
+ }
515
+ function checkPrerequisites(config, launchProfile, executionRouting) {
516
+ const routing = routingForPrerequisites(launchProfile, executionRouting);
517
+ const groups = commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
518
+ for (const executor of executorsForRoutingGroups(routing, groups)) {
519
+ resolveExecutorPrerequisite(executor);
509
520
  }
510
521
  }
511
- function checkAutoPrerequisites(config) {
512
- resolveCmd("codex", "CODEX_BIN");
522
+ function checkAutoPrerequisites(config, launchProfile, executionRouting) {
523
+ checkPrerequisites(config, launchProfile, executionRouting);
513
524
  }
514
525
  function autoFlowParams(config, forceRefreshSummary = false) {
515
526
  return {
516
527
  jiraApiUrl: config.jiraApiUrl,
517
528
  taskKey: config.taskKey,
529
+ taskContextIteration: nextArtifactIteration(config.taskKey, "task-context", "json"),
530
+ taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
531
+ designIteration: nextArtifactIteration(config.taskKey, "design"),
532
+ planIteration: nextArtifactIteration(config.taskKey, "plan"),
533
+ qaIteration: nextArtifactIteration(config.taskKey, "qa"),
518
534
  extraPrompt: config.extraPrompt,
519
535
  reviewFixPoints: config.reviewFixPoints,
536
+ reviewBlockingSeverities: config.reviewBlockingSeverities,
520
537
  forceRefresh: forceRefreshSummary,
521
538
  mdLang: config.mdLang,
522
539
  runGoTestsScript: path.join(agentweaverHome(PACKAGE_ROOT), "run_go_tests.py"),
@@ -525,6 +542,72 @@ function autoFlowParams(config, forceRefreshSummary = false) {
525
542
  runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
526
543
  };
527
544
  }
545
+ function reviewFlowParamsFromContract(config) {
546
+ const contract = resolveReviewInputContract(config.taskKey);
547
+ return {
548
+ taskKey: config.taskKey,
549
+ planningIteration: contract.planningIteration,
550
+ designFile: contract.designFile,
551
+ designJsonFile: contract.designJsonFile,
552
+ planFile: contract.planFile,
553
+ planJsonFile: contract.planJsonFile,
554
+ hasTaskContextJsonFile: contract.hasTaskContextJsonFile,
555
+ taskContextJsonFilePath: contract.taskContextJsonFilePath,
556
+ taskContextJsonFile: contract.taskContextJsonFile,
557
+ hasJiraTaskFile: contract.hasJiraTaskFile,
558
+ jiraTaskFilePath: contract.jiraTaskFilePath,
559
+ jiraTaskFile: contract.jiraTaskFile,
560
+ hasTaskInputJsonFile: contract.hasTaskInputJsonFile,
561
+ taskInputJsonFilePath: contract.taskInputJsonFilePath,
562
+ taskInputJsonFile: contract.taskInputJsonFile,
563
+ };
564
+ }
565
+ function hasStructuredReviewInputs(taskKey) {
566
+ const inspection = inspectReviewInputContract(taskKey);
567
+ if (inspection.status === "ready") {
568
+ return true;
569
+ }
570
+ if (inspection.status === "missing-planning") {
571
+ return false;
572
+ }
573
+ throw new TaskRunnerError(`Structured review requires a normalized task-context artifact, or legacy Jira/instant-task context, in scope '${taskKey}'.`);
574
+ }
575
+ function latestTaskContextIteration(taskKey) {
576
+ const iteration = latestArtifactIteration(taskKey, "task-context", "json");
577
+ if (iteration === null) {
578
+ throw new TaskRunnerError(`Plan mode requires a normalized task-context artifact in scope '${taskKey}'.`);
579
+ }
580
+ return iteration;
581
+ }
582
+ function loadInstantTaskInputDefaults(taskKey) {
583
+ const artifactPath = instantTaskInputJsonFile(taskKey);
584
+ if (!existsSync(artifactPath)) {
585
+ return null;
586
+ }
587
+ try {
588
+ validateStructuredArtifacts([{ path: artifactPath, schemaId: "user-input/v1" }], "Instant-task source input structured artifact is invalid.");
589
+ const parsed = JSON.parse(readFileSync(artifactPath, "utf8"));
590
+ const values = parsed.values;
591
+ if (!values || typeof values !== "object" || Array.isArray(values)) {
592
+ return null;
593
+ }
594
+ const normalizedEntries = [];
595
+ for (const [key, value] of Object.entries(values)) {
596
+ if (typeof value === "string" || typeof value === "boolean") {
597
+ normalizedEntries.push([key, value]);
598
+ continue;
599
+ }
600
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
601
+ normalizedEntries.push([key, [...value]]);
602
+ continue;
603
+ }
604
+ }
605
+ return Object.fromEntries(normalizedEntries);
606
+ }
607
+ catch {
608
+ return null;
609
+ }
610
+ }
528
611
  function interactiveFlowDefinition(entry) {
529
612
  const flow = entry.flow;
530
613
  return {
@@ -592,12 +675,14 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
592
675
  runtime,
593
676
  ...(setSummary ? { setSummary } : {}),
594
677
  requestUserInput,
678
+ ...(overrides.executionRouting ? { executionRouting: overrides.executionRouting } : {}),
595
679
  });
596
680
  const flow = loadDeclarativeFlow(flowRef);
597
681
  const initialExecutionState = {
598
682
  flowKind: flow.kind,
599
683
  flowVersion: flow.version,
600
684
  terminated: false,
685
+ terminationOutcome: "success",
601
686
  phases: [],
602
687
  };
603
688
  let persistedState = launchMode === "resume" ? loadFlowRunState(config.scope.scopeKey, flowId) : null;
@@ -609,7 +694,10 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
609
694
  absolutePath: flow.absolutePath,
610
695
  treePath: [],
611
696
  flow,
612
- }, config, persistedState, overrides.launchProfile, runtime);
697
+ }, config, persistedState, overrides.executionRouting ?? (overrides.launchProfile ? resolveExecutionRouting({ defaultRoute: {
698
+ executor: overrides.launchProfile.executor,
699
+ model: overrides.launchProfile.model,
700
+ } }) : undefined), runtime);
613
701
  persistedState = prepareFlowStateForResume(persistedState);
614
702
  }
615
703
  else if (launchMode === "restart") {
@@ -617,10 +705,18 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
617
705
  }
618
706
  const executionState = persistedState?.executionState ?? initialExecutionState;
619
707
  const state = persistedState
620
- ?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.jiraRef, overrides.launchProfile);
621
- if (overrides.launchProfile) {
708
+ ?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.jiraRef, overrides.launchProfile, overrides.executionRouting, overrides.selectedRoutingPreset);
709
+ if (overrides.executionRouting) {
710
+ state.executionRouting = overrides.executionRouting;
711
+ state.routingFingerprint = overrides.executionRouting.fingerprint;
712
+ state.launchProfile = overrides.executionRouting.defaultRoute;
713
+ }
714
+ else if (overrides.launchProfile) {
622
715
  state.launchProfile = overrides.launchProfile;
623
716
  }
717
+ if (overrides.selectedRoutingPreset) {
718
+ state.selectedRoutingPreset = overrides.selectedRoutingPreset;
719
+ }
624
720
  state.status = "running";
625
721
  state.lastError = null;
626
722
  state.currentStep = findCurrentFlowExecutionStep(state);
@@ -645,7 +741,12 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
645
741
  },
646
742
  });
647
743
  }
648
- state.status = "completed";
744
+ if (executionState.terminated) {
745
+ state.status = executionState.terminationOutcome === "success" ? "completed" : "blocked";
746
+ }
747
+ else {
748
+ state.status = "completed";
749
+ }
649
750
  state.currentStep = null;
650
751
  state.lastError = null;
651
752
  state.executionState = executionState;
@@ -671,12 +772,24 @@ async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overri
671
772
  ...defaultDeclarativeFlowParams(config, false, overrides),
672
773
  ...flowParams,
673
774
  };
674
- await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, mergedFlowParams, overrides, requestUserInput, setSummary, launchMode, runtime);
775
+ await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, withCanonicalReviewLoopParams(loadDeclarativeFlow({ source: "built-in", fileName }).kind, mergedFlowParams), overrides, requestUserInput, setSummary, launchMode, runtime);
675
776
  }
676
777
  function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overrides = {}) {
677
778
  const iteration = nextReviewIterationForTask(config.taskKey);
678
779
  const latestIteration = latestArtifactIteration(config.taskKey, "review");
679
- const launchProfile = overrides.launchProfile ?? resolveLaunchProfile({ executor: "default", model: "default" }, DEFAULT_LAUNCH_PROFILE);
780
+ const latestTaskContext = latestArtifactIteration(config.taskKey, "task-context", "json");
781
+ const executionRouting = overrides.executionRouting ?? resolveExecutionRouting({
782
+ defaultRoute: overrides.launchProfile
783
+ ? {
784
+ executor: overrides.launchProfile.executor,
785
+ model: overrides.launchProfile.model,
786
+ }
787
+ : {
788
+ executor: DEFAULT_LAUNCH_PROFILE.executor,
789
+ model: DEFAULT_LAUNCH_PROFILE.model,
790
+ },
791
+ });
792
+ const launchProfile = executionRouting.defaultRoute;
680
793
  return {
681
794
  taskKey: config.taskKey,
682
795
  jiraRef: config.jiraRef,
@@ -687,12 +800,16 @@ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overr
687
800
  workspaceDir: scopeWorkspaceDir(config.taskKey),
688
801
  extraPrompt: config.extraPrompt,
689
802
  reviewFixPoints: config.reviewFixPoints,
803
+ reviewBlockingSeverities: config.reviewBlockingSeverities,
690
804
  mdLang: config.mdLang,
691
805
  llmExecutor: launchProfile.executor,
692
806
  llmModel: launchProfile.model,
693
807
  launchProfile,
808
+ executionRouting,
694
809
  iteration,
810
+ baseIteration: iteration,
695
811
  latestIteration,
812
+ taskContextIteration: latestTaskContext ?? nextArtifactIteration(config.taskKey, "task-context", "json"),
696
813
  taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
697
814
  designIteration: nextArtifactIteration(config.taskKey, "design"),
698
815
  planIteration: nextArtifactIteration(config.taskKey, "plan"),
@@ -738,20 +855,50 @@ function requireJiraConfig(config) {
738
855
  throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
739
856
  }
740
857
  }
741
- async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", launchProfile, runtime = runtimeServices) {
858
+ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", launchProfile, executionRouting, selectedRoutingPreset, runtime = runtimeServices) {
742
859
  if (baseConfig.command === "doctor") {
743
860
  const exitCode = await runDoctorCommand(baseConfig.doctorArgs ?? []);
744
861
  return exitCode === 0;
745
862
  }
746
863
  const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
864
+ const flowOverrides = executionRouting
865
+ ? {
866
+ launchProfile: executionRouting.defaultRoute,
867
+ executionRouting,
868
+ ...(selectedRoutingPreset ? { selectedRoutingPreset } : {}),
869
+ }
870
+ : launchProfile
871
+ ? { launchProfile }
872
+ : {};
873
+ if (config.command === "instant-task") {
874
+ checkPrerequisites(config, launchProfile, executionRouting);
875
+ const hasPersistedInstantTaskState = loadFlowRunState(config.scope.scopeKey, "instant-task") !== null;
876
+ const repromptInstantTaskInput = launchMode === "restart"
877
+ && hasPersistedInstantTaskState
878
+ && requestUserInput !== requestUserInputInTerminal;
879
+ await runDeclarativeFlowBySpecFile("instant-task.json", config, {
880
+ taskKey: config.taskKey,
881
+ taskContextIteration: nextArtifactIteration(config.taskKey, "task-context", "json"),
882
+ designIteration: nextArtifactIteration(config.taskKey, "design"),
883
+ planIteration: nextArtifactIteration(config.taskKey, "plan"),
884
+ qaIteration: nextArtifactIteration(config.taskKey, "qa"),
885
+ extraPrompt: config.extraPrompt,
886
+ mdLang: config.mdLang,
887
+ repromptInstantTaskInput,
888
+ ...(repromptInstantTaskInput
889
+ ? { prefilledInstantTaskInputValues: loadInstantTaskInputDefaults(config.taskKey) ?? undefined }
890
+ : {}),
891
+ }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
892
+ return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
893
+ }
747
894
  if (config.command === "auto-golang") {
748
895
  requireJiraConfig(config);
749
- checkAutoPrerequisites(config);
750
896
  process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
751
897
  process.env.JIRA_API_URL = config.jiraApiUrl;
752
898
  process.env.JIRA_TASK_FILE = config.jiraTaskFile;
753
899
  let effectiveLaunchMode = launchMode;
754
900
  let effectiveLaunchProfile = launchProfile;
901
+ let effectiveExecutionRouting = executionRouting;
755
902
  if (config.autoFromPhase) {
756
903
  const flow = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" });
757
904
  const persistedState = loadFlowRunState(config.scope.scopeKey, "auto-golang");
@@ -762,18 +909,37 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
762
909
  saveFlowRunState(persistedState);
763
910
  effectiveLaunchMode = "resume";
764
911
  effectiveLaunchProfile ??= persistedState.launchProfile;
912
+ effectiveExecutionRouting ??= persistedState.executionRouting;
765
913
  printPanel("Auto-Golang Resume", `Auto-golang pipeline will continue from phase: ${config.autoFromPhase}`, "yellow");
766
914
  }
767
- await runDeclarativeFlowBySpecFile("auto-golang.json", config, autoFlowParams(config, forceRefreshSummary), effectiveLaunchProfile ? { launchProfile: effectiveLaunchProfile } : {}, requestUserInput, setSummary, effectiveLaunchMode, runtime);
915
+ checkAutoPrerequisites(config, effectiveLaunchProfile, effectiveExecutionRouting);
916
+ await runDeclarativeFlowBySpecFile("auto-golang.json", config, autoFlowParams(config, forceRefreshSummary), effectiveExecutionRouting
917
+ ? {
918
+ launchProfile: effectiveExecutionRouting.defaultRoute,
919
+ executionRouting: effectiveExecutionRouting,
920
+ ...(selectedRoutingPreset ? { selectedRoutingPreset } : {}),
921
+ }
922
+ : effectiveLaunchProfile
923
+ ? { launchProfile: effectiveLaunchProfile }
924
+ : {}, requestUserInput, setSummary, effectiveLaunchMode, runtime);
768
925
  return false;
769
926
  }
770
927
  if (config.command === "auto-common") {
771
928
  requireJiraConfig(config);
772
- checkAutoPrerequisites(config);
929
+ checkAutoPrerequisites(config, launchProfile, executionRouting);
930
+ process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
931
+ process.env.JIRA_API_URL = config.jiraApiUrl;
932
+ process.env.JIRA_TASK_FILE = config.jiraTaskFile;
933
+ await runDeclarativeFlowBySpecFile("auto-common.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
934
+ return false;
935
+ }
936
+ if (config.command === "auto-simple") {
937
+ requireJiraConfig(config);
938
+ checkAutoPrerequisites(config, launchProfile, executionRouting);
773
939
  process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
774
940
  process.env.JIRA_API_URL = config.jiraApiUrl;
775
941
  process.env.JIRA_TASK_FILE = config.jiraTaskFile;
776
- await runDeclarativeFlowBySpecFile("auto-common.json", config, autoFlowParams(config, forceRefreshSummary), launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
942
+ await runDeclarativeFlowBySpecFile("auto-simple.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
777
943
  return false;
778
944
  }
779
945
  if (config.command === "auto-status") {
@@ -790,7 +956,11 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
790
956
  `Current step: ${currentStep}`,
791
957
  `Updated: ${state.updatedAt}`,
792
958
  ];
793
- if (state.launchProfile) {
959
+ if (state.executionRouting) {
960
+ lines.push(`Default route: ${state.executionRouting.defaultRoute.executor} / ${state.executionRouting.defaultRoute.model}`);
961
+ lines.push(`Routing fingerprint: ${state.executionRouting.fingerprint}`);
962
+ }
963
+ else if (state.launchProfile) {
794
964
  lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
795
965
  }
796
966
  if (state.lastError) {
@@ -816,7 +986,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
816
986
  printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
817
987
  return false;
818
988
  }
819
- checkPrerequisites(config);
989
+ checkPrerequisites(config, launchProfile, executionRouting);
820
990
  if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
821
991
  process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl ?? "";
822
992
  process.env.JIRA_API_URL = config.jiraApiUrl ?? "";
@@ -828,22 +998,37 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
828
998
  delete process.env.JIRA_TASK_FILE;
829
999
  }
830
1000
  if (config.command === "plan") {
831
- requireJiraConfig(config);
832
- if (config.verbose) {
833
- process.stdout.write(`Fetching Jira issue from browse URL: ${config.jiraBrowseUrl}\n`);
834
- process.stdout.write(`Resolved Jira API URL: ${config.jiraApiUrl}\n`);
835
- process.stdout.write(`Saving Jira issue JSON to: ${config.jiraTaskFile}\n`);
1001
+ let taskContextIteration;
1002
+ if (config.jiraRef) {
1003
+ requireJiraConfig(config);
1004
+ if (config.verbose) {
1005
+ process.stdout.write(`Fetching Jira issue from browse URL: ${config.jiraBrowseUrl}\n`);
1006
+ process.stdout.write(`Resolved Jira API URL: ${config.jiraApiUrl}\n`);
1007
+ process.stdout.write(`Saving Jira issue JSON to: ${config.jiraTaskFile}\n`);
1008
+ }
1009
+ taskContextIteration = nextArtifactIteration(config.taskKey, "task-context", "json");
1010
+ await runDeclarativeFlowBySpecFile("task-source/jira-fetch.json", config, {
1011
+ jiraApiUrl: config.jiraApiUrl,
1012
+ taskKey: config.taskKey,
1013
+ }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1014
+ await runDeclarativeFlowBySpecFile("normalize-task-source.json", config, {
1015
+ taskKey: config.taskKey,
1016
+ iteration: taskContextIteration,
1017
+ extraPrompt: config.extraPrompt,
1018
+ }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1019
+ }
1020
+ else {
1021
+ taskContextIteration = latestTaskContextIteration(config.taskKey);
836
1022
  }
837
1023
  await runDeclarativeFlowBySpecFile("plan.json", config, {
838
- jiraApiUrl: config.jiraApiUrl,
839
1024
  taskKey: config.taskKey,
840
- taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
1025
+ taskContextIteration,
841
1026
  designIteration: nextArtifactIteration(config.taskKey, "design"),
842
1027
  planIteration: nextArtifactIteration(config.taskKey, "plan"),
843
1028
  qaIteration: nextArtifactIteration(config.taskKey, "qa"),
844
1029
  extraPrompt: config.extraPrompt,
845
1030
  forceRefresh: forceRefreshSummary,
846
- }, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
1031
+ }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
847
1032
  return false;
848
1033
  }
849
1034
  if (config.command === "bug-analyze") {
@@ -862,7 +1047,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
862
1047
  bugFixPlanIteration: nextArtifactIteration(config.taskKey, "bug-fix-plan"),
863
1048
  extraPrompt: config.extraPrompt,
864
1049
  forceRefresh: forceRefreshSummary,
865
- }, launchProfile ? { launchProfile } : {}, requestUserInput, setSummary, launchMode, runtime);
1050
+ }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
866
1051
  return false;
867
1052
  }
868
1053
  if (config.command === "design-review") {
@@ -884,6 +1069,9 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
884
1069
  qaJsonFilePath: inputContract.qaJsonFilePath,
885
1070
  qaFile: inputContract.qaFile,
886
1071
  qaJsonFile: inputContract.qaJsonFile,
1072
+ hasTaskContextJsonFile: inputContract.hasTaskContextJsonFile,
1073
+ taskContextJsonFilePath: inputContract.taskContextJsonFilePath,
1074
+ taskContextJsonFile: inputContract.taskContextJsonFile,
887
1075
  hasJiraTaskFile: inputContract.hasJiraTaskFile,
888
1076
  jiraTaskFilePath: inputContract.jiraTaskFilePath,
889
1077
  jiraTaskFile: inputContract.jiraTaskFile,
@@ -896,8 +1084,11 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
896
1084
  hasPlanningAnswersJsonFile: inputContract.hasPlanningAnswersJsonFile,
897
1085
  planningAnswersJsonFilePath: inputContract.planningAnswersJsonFilePath,
898
1086
  planningAnswersJsonFile: inputContract.planningAnswersJsonFile,
1087
+ hasTaskInputJsonFile: inputContract.hasTaskInputJsonFile,
1088
+ taskInputJsonFilePath: inputContract.taskInputJsonFilePath,
1089
+ taskInputJsonFile: inputContract.taskInputJsonFile,
899
1090
  extraPrompt: config.extraPrompt,
900
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1091
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
901
1092
  if (!config.dryRun) {
902
1093
  printSummary("Design Review", `Artifacts:\n${designReviewFile(config.taskKey, iteration)}\n${designReviewJsonFile(config.taskKey, iteration)}`);
903
1094
  }
@@ -930,6 +1121,9 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
930
1121
  revisedPlanJsonFile: inputContract.revisedPlanJsonFile,
931
1122
  revisedQaFile: inputContract.revisedQaFile,
932
1123
  revisedQaJsonFile: inputContract.revisedQaJsonFile,
1124
+ hasTaskContextJsonFile: inputContract.hasTaskContextJsonFile,
1125
+ taskContextJsonFilePath: inputContract.taskContextJsonFilePath,
1126
+ taskContextJsonFile: inputContract.taskContextJsonFile,
933
1127
  hasJiraTaskFile: inputContract.hasJiraTaskFile,
934
1128
  jiraTaskFilePath: inputContract.jiraTaskFilePath,
935
1129
  jiraTaskFile: inputContract.jiraTaskFile,
@@ -942,8 +1136,11 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
942
1136
  hasPlanningAnswersJsonFile: inputContract.hasPlanningAnswersJsonFile,
943
1137
  planningAnswersJsonFilePath: inputContract.planningAnswersJsonFilePath,
944
1138
  planningAnswersJsonFile: inputContract.planningAnswersJsonFile,
1139
+ hasTaskInputJsonFile: inputContract.hasTaskInputJsonFile,
1140
+ taskInputJsonFilePath: inputContract.taskInputJsonFilePath,
1141
+ taskInputJsonFile: inputContract.taskInputJsonFile,
945
1142
  extraPrompt: config.extraPrompt,
946
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1143
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
947
1144
  if (!config.dryRun) {
948
1145
  printSummary("Plan Revise", `Artifacts:\n${inputContract.revisedDesignFile}\n${inputContract.revisedDesignJsonFile}\n${inputContract.revisedPlanFile}\n${inputContract.revisedPlanJsonFile}\n${inputContract.revisedQaFile}\n${inputContract.revisedQaJsonFile}`);
949
1146
  }
@@ -959,7 +1156,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
959
1156
  extraPrompt: config.extraPrompt,
960
1157
  reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, iteration),
961
1158
  reviewFixPoints: config.reviewFixPoints,
962
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1159
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
963
1160
  if (!config.dryRun) {
964
1161
  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)}`);
965
1162
  }
@@ -973,7 +1170,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
973
1170
  iteration,
974
1171
  gitlabDiffIteration,
975
1172
  extraPrompt: config.extraPrompt,
976
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1173
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
977
1174
  if (!config.dryRun) {
978
1175
  printSummary("GitLab Diff Review", `Artifacts:\n${gitlabDiffFile(config.taskKey)}\n${gitlabDiffJsonFile(config.taskKey)}\n${reviewFile(config.taskKey, iteration)}\n${reviewJsonFile(config.taskKey, iteration)}`);
979
1176
  }
@@ -991,7 +1188,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
991
1188
  await runDeclarativeFlowBySpecFile("bugz/bug-fix.json", config, {
992
1189
  taskKey: config.taskKey,
993
1190
  extraPrompt: config.extraPrompt,
994
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1191
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
995
1192
  return false;
996
1193
  }
997
1194
  if (config.command === "mr-description") {
@@ -1001,7 +1198,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1001
1198
  taskKey: config.taskKey,
1002
1199
  iteration: nextArtifactIteration(config.taskKey, "mr-description"),
1003
1200
  extraPrompt: config.extraPrompt,
1004
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1201
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1005
1202
  return false;
1006
1203
  }
1007
1204
  if (config.command === "task-describe") {
@@ -1011,42 +1208,33 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1011
1208
  taskKey: config.taskKey,
1012
1209
  iteration,
1013
1210
  extraPrompt: config.extraPrompt,
1014
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1211
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1015
1212
  return false;
1016
1213
  }
1017
1214
  if (config.command === "implement") {
1018
- requireArtifacts(planArtifacts(config.taskKey), "Implement mode requires plan artifacts from the planning phase.");
1019
- validateStructuredArtifacts([
1020
- { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
1021
- { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
1022
- { path: qaJsonFile(config.taskKey), schemaId: "qa-plan/v1" },
1023
- ], "Implement mode requires valid structured plan artifacts from the planning phase.");
1215
+ const planningBundle = resolveLatestPlanningBundle(config.taskKey);
1024
1216
  await runDeclarativeFlowBySpecFile("implement.json", config, {
1025
1217
  taskKey: config.taskKey,
1218
+ planningIteration: planningBundle.planningIteration,
1026
1219
  extraPrompt: config.extraPrompt,
1027
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode);
1220
+ }, flowOverrides, requestUserInput, undefined, launchMode);
1028
1221
  return false;
1029
1222
  }
1030
1223
  if (config.command === "review") {
1031
1224
  const iteration = nextReviewIterationForTask(config.taskKey);
1032
- if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
1033
- requireJiraConfig(config);
1034
- validateStructuredArtifacts([
1035
- { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
1036
- { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
1037
- ], "Review mode requires valid structured plan artifacts from the planning phase.");
1225
+ if (hasStructuredReviewInputs(config.taskKey)) {
1038
1226
  await runDeclarativeFlowBySpecFile("review/review.json", config, {
1039
- taskKey: config.taskKey,
1227
+ ...reviewFlowParamsFromContract(config),
1040
1228
  iteration,
1041
1229
  extraPrompt: config.extraPrompt,
1042
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1230
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1043
1231
  }
1044
1232
  else {
1045
1233
  await runDeclarativeFlowBySpecFile("review/review-project.json", config, {
1046
1234
  taskKey: config.taskKey,
1047
1235
  iteration,
1048
1236
  extraPrompt: config.extraPrompt,
1049
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1237
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1050
1238
  }
1051
1239
  return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
1052
1240
  }
@@ -1065,23 +1253,21 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1065
1253
  reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
1066
1254
  extraPrompt: config.extraPrompt,
1067
1255
  reviewFixPoints: config.reviewFixPoints,
1068
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1256
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1069
1257
  return false;
1070
1258
  }
1071
1259
  if (config.command === "review-loop") {
1072
1260
  const iteration = nextReviewIterationForTask(config.taskKey);
1073
- if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
1074
- requireJiraConfig(config);
1075
- validateStructuredArtifacts([
1076
- { path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
1077
- { path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
1078
- ], "Review-loop mode requires valid structured plan artifacts from the planning phase.");
1079
- }
1080
- await runDeclarativeFlowBySpecFile("review/review-loop.json", config, {
1081
- taskKey: config.taskKey,
1082
- iteration,
1261
+ const reviewLoopSpec = hasStructuredReviewInputs(config.taskKey)
1262
+ ? "review/review-loop.json"
1263
+ : "review/review-project-loop.json";
1264
+ await runDeclarativeFlowBySpecFile(reviewLoopSpec, config, {
1265
+ ...(reviewLoopSpec === "review/review-loop.json"
1266
+ ? reviewFlowParamsFromContract(config)
1267
+ : { taskKey: config.taskKey }),
1268
+ baseIteration: iteration,
1083
1269
  extraPrompt: config.extraPrompt,
1084
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1270
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1085
1271
  return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
1086
1272
  }
1087
1273
  if (config.command === "run-go-tests-loop" || config.command === "run-go-linter-loop") {
@@ -1092,14 +1278,14 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1092
1278
  runGoTestsIteration: nextArtifactIteration(config.taskKey, "run-go-tests-result", "json"),
1093
1279
  runGoLinterIteration: nextArtifactIteration(config.taskKey, "run-go-linter-result", "json"),
1094
1280
  extraPrompt: config.extraPrompt,
1095
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1281
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1096
1282
  return false;
1097
1283
  }
1098
1284
  if (config.command === "git-commit") {
1099
1285
  await runDeclarativeFlowBySpecFile("git-commit.json", config, {
1100
1286
  taskKey: config.taskKey,
1101
1287
  extraPrompt: config.extraPrompt,
1102
- }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
1288
+ }, flowOverrides, requestUserInput, undefined, launchMode, runtime);
1103
1289
  return false;
1104
1290
  }
1105
1291
  throw new TaskRunnerError(`Unsupported command: ${config.command}`);
@@ -1127,6 +1313,7 @@ function parseCliArgs(argv) {
1127
1313
  let prompt;
1128
1314
  let autoFromPhase;
1129
1315
  let scopeName;
1316
+ let reviewBlockingSeverities;
1130
1317
  let helpPhases = false;
1131
1318
  let jiraRef;
1132
1319
  let mdLang;
@@ -1155,6 +1342,15 @@ function parseCliArgs(argv) {
1155
1342
  index += 1;
1156
1343
  continue;
1157
1344
  }
1345
+ if (token === "--blocking-severities") {
1346
+ reviewBlockingSeverities = parseReviewSeverityCsv(argv[index + 1] ?? "");
1347
+ index += 1;
1348
+ continue;
1349
+ }
1350
+ if (token.startsWith("--blocking-severities=")) {
1351
+ reviewBlockingSeverities = parseReviewSeverityCsv(token.slice("--blocking-severities=".length));
1352
+ continue;
1353
+ }
1158
1354
  if (token === "--from") {
1159
1355
  autoFromPhase = argv[index + 1];
1160
1356
  index += 1;
@@ -1198,6 +1394,10 @@ function parseCliArgs(argv) {
1198
1394
  printAutoCommonPhasesHelp();
1199
1395
  process.exit(0);
1200
1396
  }
1397
+ if (command === "auto-simple" && helpPhases) {
1398
+ printAutoSimplePhasesHelp();
1399
+ process.exit(0);
1400
+ }
1201
1401
  return {
1202
1402
  command: command,
1203
1403
  dry,
@@ -1205,6 +1405,7 @@ function parseCliArgs(argv) {
1205
1405
  helpPhases,
1206
1406
  ...(jiraRef !== undefined ? { jiraRef } : {}),
1207
1407
  ...(scopeName !== undefined ? { scopeName } : {}),
1408
+ ...(reviewBlockingSeverities !== undefined ? { reviewBlockingSeverities } : {}),
1208
1409
  ...(prompt !== undefined ? { prompt } : {}),
1209
1410
  ...(autoFromPhase !== undefined ? { autoFromPhase } : {}),
1210
1411
  ...(mdLang !== undefined ? { mdLang } : {}),
@@ -1215,6 +1416,7 @@ function buildConfigFromArgs(args) {
1215
1416
  return buildBaseConfig(args.command, {
1216
1417
  ...(args.jiraRef !== undefined ? { jiraRef: args.jiraRef } : {}),
1217
1418
  ...(args.scopeName !== undefined ? { scopeName: args.scopeName } : {}),
1419
+ ...(args.reviewBlockingSeverities !== undefined ? { reviewBlockingSeverities: args.reviewBlockingSeverities } : {}),
1218
1420
  ...(args.prompt !== undefined ? { extraPrompt: args.prompt } : {}),
1219
1421
  ...(args.autoFromPhase !== undefined ? { autoFromPhase: args.autoFromPhase } : {}),
1220
1422
  ...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
@@ -1230,7 +1432,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1230
1432
  let activeAbortController = null;
1231
1433
  let activeFlowId = null;
1232
1434
  let exiting = false;
1233
- const ui = new InteractiveUi({
1435
+ const ui = createInteractiveSession({
1234
1436
  scopeKey: currentScope.scopeKey,
1235
1437
  jiraIssueKey: currentScope.jiraIssueKey ?? null,
1236
1438
  summaryText: "",
@@ -1255,16 +1457,23 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1255
1457
  if (!flowEntry) {
1256
1458
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
1257
1459
  }
1460
+ const routingGroups = flowRoutingGroups(flowEntry, process.cwd());
1258
1461
  const resumeState = launchMode === "resume" ? loadFlowRunState(currentScope.scopeKey, flowId) : null;
1259
1462
  if (resumeState) {
1260
1463
  currentScope = scopeWithRestoredJiraContext(currentScope, resumeState);
1261
1464
  }
1262
- const launchProfile = launchMode === "resume"
1263
- ? resumeState?.launchProfile
1264
- : await requestInteractiveLaunchProfile((form) => ui.requestUserInput(form));
1265
- if (!launchProfile) {
1266
- throw new TaskRunnerError("Resume is impossible because launch profile was not saved. Use restart.");
1465
+ const routingSelection = launchMode === "resume"
1466
+ ? (resumeState?.executionRouting
1467
+ ? {
1468
+ routing: resumeState.executionRouting,
1469
+ selectedPreset: resumeState.selectedRoutingPreset ?? { kind: "custom", label: "Saved routing" },
1470
+ }
1471
+ : null)
1472
+ : await requestInteractiveExecutionRouting(flowEntry, (form) => ui.requestUserInput(form));
1473
+ if (launchMode === "resume" && !routingSelection?.routing) {
1474
+ throw new TaskRunnerError("Resume is impossible because execution routing was not saved. Use restart.");
1267
1475
  }
1476
+ const launchProfile = routingSelection?.routing?.defaultRoute;
1268
1477
  const previousScopeKey = currentScope.scopeKey;
1269
1478
  const baseConfig = buildInteractiveBaseConfig(flowId, currentScope);
1270
1479
  if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
@@ -1279,13 +1488,20 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1279
1488
  if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
1280
1489
  syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
1281
1490
  }
1282
- printPanel("Effective Launch Config", `executor: ${launchProfile.executor}\nmodel: ${launchProfile.model}\nmode: ${launchMode}`, "cyan");
1491
+ if (routingSelection?.routing) {
1492
+ printPanel("Effective Launch Config", `preset: ${routingSelection.selectedPreset.label}\nmode: ${launchMode}\n${describeExecutionRouting(routingSelection.routing, routingGroups)}`, "cyan");
1493
+ }
1283
1494
  if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
1284
- await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, launchProfile, createRuntimeServices(abortController.signal));
1495
+ await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, launchProfile, routingSelection?.routing, routingSelection?.selectedPreset, createRuntimeServices(abortController.signal));
1285
1496
  return;
1286
1497
  }
1287
1498
  const runtimeConfig = buildRuntimeConfig(baseConfig, currentScope);
1288
- await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh, { launchProfile }), { launchProfile }, (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode, createRuntimeServices(abortController.signal));
1499
+ const flowOverrides = {
1500
+ ...(launchProfile ? { launchProfile } : {}),
1501
+ ...(routingSelection?.routing ? { executionRouting: routingSelection.routing } : {}),
1502
+ ...(routingSelection?.selectedPreset ? { selectedRoutingPreset: routingSelection.selectedPreset } : {}),
1503
+ };
1504
+ await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh, flowOverrides), flowOverrides, (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode, createRuntimeServices(abortController.signal));
1289
1505
  }
1290
1506
  catch (error) {
1291
1507
  if (error instanceof FlowInterruptedError) {