agentweaver 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +47 -7
  2. package/dist/artifacts.js +9 -0
  3. package/dist/executors/git-commit-executor.js +24 -6
  4. package/dist/flow-state.js +3 -8
  5. package/dist/git/git-diff-parser.js +223 -0
  6. package/dist/git/git-service.js +562 -0
  7. package/dist/git/git-stage-selection.js +24 -0
  8. package/dist/git/git-status-parser.js +171 -0
  9. package/dist/git/git-types.js +1 -0
  10. package/dist/index.js +450 -108
  11. package/dist/interactive/auto-flow.js +644 -0
  12. package/dist/interactive/controller.js +417 -9
  13. package/dist/interactive/progress.js +194 -1
  14. package/dist/interactive/state.js +25 -0
  15. package/dist/interactive/web/index.js +97 -12
  16. package/dist/interactive/web/protocol.js +216 -1
  17. package/dist/interactive/web/server.js +72 -14
  18. package/dist/interactive/web/static/app.js +1603 -49
  19. package/dist/interactive/web/static/index.html +76 -11
  20. package/dist/interactive/web/static/styles.css +1 -1
  21. package/dist/interactive/web/static/styles.input.css +901 -47
  22. package/dist/pipeline/auto-flow-blocks.js +307 -0
  23. package/dist/pipeline/auto-flow-config.js +273 -0
  24. package/dist/pipeline/auto-flow-identity.js +49 -0
  25. package/dist/pipeline/auto-flow-presets.js +52 -0
  26. package/dist/pipeline/auto-flow-resolver.js +830 -0
  27. package/dist/pipeline/auto-flow-types.js +17 -0
  28. package/dist/pipeline/context.js +1 -0
  29. package/dist/pipeline/declarative-flows.js +27 -1
  30. package/dist/pipeline/flow-specs/auto-common-guided.json +11 -0
  31. package/dist/pipeline/flow-specs/auto-golang.json +12 -1
  32. package/dist/pipeline/flow-specs/bugz/bug-analyze.json +54 -1
  33. package/dist/pipeline/flow-specs/gitlab/gitlab-diff-review.json +19 -1
  34. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +33 -1
  35. package/dist/pipeline/flow-specs/review/review-project.json +19 -1
  36. package/dist/pipeline/flow-specs/task-source/manual-jira-input.json +70 -0
  37. package/dist/pipeline/node-registry.js +9 -0
  38. package/dist/pipeline/nodes/codex-prompt-node.js +8 -1
  39. package/dist/pipeline/nodes/flow-run-node.js +5 -3
  40. package/dist/pipeline/nodes/git-status-node.js +2 -168
  41. package/dist/pipeline/nodes/manual-jira-task-input-node.js +146 -0
  42. package/dist/pipeline/nodes/opencode-prompt-node.js +8 -1
  43. package/dist/pipeline/nodes/plan-codex-node.js +8 -1
  44. package/dist/pipeline/spec-loader.js +14 -4
  45. package/dist/runtime/artifact-catalog.js +29 -5
  46. package/dist/runtime/settings.js +114 -0
  47. package/dist/scope.js +14 -4
  48. package/package.json +1 -1
  49. package/dist/pipeline/flow-specs/auto-common.json +0 -179
  50. package/dist/pipeline/flow-specs/auto-simple.json +0 -141
package/dist/index.js CHANGED
@@ -12,8 +12,11 @@ import { AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV, parseReviewSeverityCsv, res
12
12
  import { summarizeBuildFailure as summarizeBuildFailureViaPipeline } from "./pipeline/build-failure-summary.js";
13
13
  import { runNodeChecks } from "./pipeline/checks.js";
14
14
  import { createPipelineContext } from "./pipeline/context.js";
15
- import { collectFlowRoutingGroups, loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
15
+ import { collectFlowRoutingGroups, loadDeclarativeFlow, } from "./pipeline/declarative-flows.js";
16
16
  import { runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
17
+ import { listAutoFlowConfigs, loadAutoFlowConfigByName } from "./pipeline/auto-flow-config.js";
18
+ import { formatAutoFlowDryRunPreview, persistResolvedAutoFlowArtifacts, resolveAutoFlow, } from "./pipeline/auto-flow-resolver.js";
19
+ import { autoFlowIdentityForSelection, defaultAutoFlowSelection, isRestartArchivingFlowId, } from "./pipeline/auto-flow-identity.js";
17
20
  import { builtInCommandFlowFile, findCatalogEntry, flowRoutingGroups, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef, } from "./pipeline/flow-catalog.js";
18
21
  import { createPipelineRegistryContext } from "./pipeline/plugin-loader.js";
19
22
  import { DEFAULT_LAUNCH_PROFILE, } from "./pipeline/launch-profile-config.js";
@@ -33,10 +36,11 @@ import { describeExecutionRouting, executorsForRoutingGroups, resolveExecutionRo
33
36
  import { requestInteractiveExecutionRouting } from "./runtime/interactive-execution-routing.js";
34
37
  import { createInteractiveSession } from "./interactive/create-interactive-session.js";
35
38
  import { createWebInteractiveSession } from "./interactive/web/index.js";
36
- import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
39
+ import { autoFlowSelectionForFlowId, createConfigAutoFlowDefinition, createPresetAutoFlowDefinition, } from "./interactive/auto-flow.js";
40
+ import { bye, getOutputAdapter, printError, printInfo, printPanel, renderPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
37
41
  import { requestUserInputInTerminal } from "./user-input.js";
38
42
  import { runDoctorCommand } from "./doctor/index.js";
39
- import { requestJiraContext, resolveProjectScope, } from "./scope.js";
43
+ import { requestJiraContext, requestOptionalJiraContext, resolveProjectScope, } from "./scope.js";
40
44
  const COMMANDS = [
41
45
  "auto",
42
46
  "auto-golang",
@@ -77,6 +81,17 @@ function writeStdoutSync(text) {
77
81
  function writeStderrSync(text) {
78
82
  writeSync(process.stderr.fd, text);
79
83
  }
84
+ function printPanelSync(title, text) {
85
+ const adapter = getOutputAdapter();
86
+ if (adapter.renderAuxiliaryOutput === false) {
87
+ return;
88
+ }
89
+ if (adapter.renderPanelsAsPlainText) {
90
+ writeStdoutSync(`${title}\n${text}\n\n`);
91
+ return;
92
+ }
93
+ writeStdoutSync(`${renderPanel(title, text)}\n`);
94
+ }
80
95
  function createRuntimeServices(signal) {
81
96
  return {
82
97
  resolveCmd,
@@ -175,18 +190,18 @@ function usage() {
175
190
  agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
176
191
  agentweaver run-go-tests-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
177
192
  agentweaver run-go-linter-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
178
- agentweaver auto [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
193
+ agentweaver auto [--preset <simple|standard>|--config <name>] [--dry-run-flow] [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
179
194
  agentweaver auto --help-phases
180
195
  agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
181
196
  agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
182
197
  agentweaver auto-golang --help-phases
183
- agentweaver auto-common-guided [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [--accept-playbook-draft] <jira-browse-url|jira-issue-key>
184
- agentweaver auto-common [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
198
+ agentweaver auto-common-guided [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [--accept-playbook-draft] [<jira-browse-url|jira-issue-key>]
199
+ agentweaver auto-common [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
185
200
  agentweaver auto-common --help-phases
186
- agentweaver auto-simple [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] <jira-browse-url|jira-issue-key>
201
+ agentweaver auto-simple [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
187
202
  agentweaver auto-simple --help-phases
188
- agentweaver auto-status [<jira-browse-url|jira-issue-key>]
189
- agentweaver auto-reset [<jira-browse-url|jira-issue-key>]
203
+ agentweaver auto-status [--preset <simple|standard>|--config <name>] [<jira-browse-url|jira-issue-key>]
204
+ agentweaver auto-reset [--preset <simple|standard>|--config <name>] [<jira-browse-url|jira-issue-key>]
190
205
 
191
206
  Interactive Mode:
192
207
  When started without a command, the script opens an interactive UI.
@@ -199,13 +214,16 @@ Flags:
199
214
  --no-open Web command only: print the Web UI URL without opening a browser
200
215
  --host Web command only: bind Web UI to this host (default: 127.0.0.1)
201
216
  --listen-all Web command only: bind Web UI to 0.0.0.0
202
- --dry Fetch Jira task, but print codex/opencode commands instead of executing them
217
+ --dry Fetch or collect task context, but print codex/opencode commands instead of executing them
218
+ --preset Auto, auto-status, and auto-reset: resolve the simple or standard preset (default: standard)
219
+ --config Auto, auto-status, and auto-reset: load .agentweaver/flow-configs/<name>.yaml or ~/.agentweaver/flow-configs/<name>.yaml
220
+ --dry-run-flow Auto command only: validate and preview flow resolution without running workflow steps or writing resolver artifacts
203
221
  --verbose Show live stdout/stderr of launched commands
204
222
  --scope Explicit workflow scope name for non-Jira runs except instant-task
205
223
  --prompt Extra prompt text appended to the base prompt
206
224
  --resume Resume an interrupted run when valid
207
225
  --continue Continue a terminated iterative run when valid
208
- --restart Archive the active attempt and start a fresh run
226
+ --restart Start a fresh run; end-to-end attempt flows archive the active attempt first
209
227
  --blocking-severities Comma-separated severities that block merge and drive review-fix auto-selection
210
228
  --md-lang Language for workflow markdown artifacts only: en (English) or ru (Russian, default)
211
229
  --accept-playbook-draft Non-interactively accept generated playbook content for playbook-init or auto-common-guided missing-manifest runs
@@ -229,7 +247,10 @@ Optional environment variables:
229
247
  ${WEB_AUTH_PASSWORD_ENV} Web UI Basic auth password; required for external Web UI binding
230
248
 
231
249
  Notes:
232
- - 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.
250
+ - agentweaver auto defaults to --preset standard, equivalent to auto-common. Use --dry-run-flow to inspect the resolved preset or saved config before execution.
251
+ - Saved auto flow configs are YAML files named .agentweaver/flow-configs/<name>.yaml or ~/.agentweaver/flow-configs/<name>.yaml; project configs take precedence over user configs.
252
+ - Successful configurable auto runs write flow-config.yaml, resolved-flow.json, and resolved-flow-summary.json under the current scope .artifacts directory. --dry-run-flow writes none of them.
253
+ - auto-golang, auto-common-guided, auto-common, auto-simple, and configurable auto ask for Jira input when Jira is not passed as an argument; leave it empty to paste the task description manually in the next step. task-describe can also work from a manual task description without Jira.
233
254
  - agentweaver web binds to 127.0.0.1 by default on an operating-system-assigned port and does not require auth unless Web UI credentials are configured.
234
255
  - External Web UI binding through --listen-all, --host 0.0.0.0, --host ::, non-loopback IPs, or hostnames other than localhost requires ${WEB_AUTH_USERNAME_ENV} and ${WEB_AUTH_PASSWORD_ENV}.
235
256
  - Web UI Basic auth over plain HTTP is suitable only on trusted networks; use TLS termination or a reverse proxy on untrusted networks.
@@ -409,8 +430,15 @@ function scopeWithRestoredJiraContext(scope, state) {
409
430
  return resolveProjectScope(null, state.jiraRef);
410
431
  }
411
432
  function buildInteractiveBaseConfig(flowId, scope) {
412
- return buildBaseConfig(flowId, {
433
+ const autoFlowSelection = autoFlowSelectionForFlowId(flowId);
434
+ const command = autoFlowSelection
435
+ ? autoFlowSelection.kind === "preset" && autoFlowSelection.preset === "simple"
436
+ ? "auto-simple"
437
+ : "auto-common"
438
+ : flowId;
439
+ return buildBaseConfig(command, {
413
440
  ...(flowId !== "instant-task" && scope.jiraRef ? { jiraRef: scope.jiraRef } : {}),
441
+ ...(autoFlowSelection ? { autoFlowSelection } : {}),
414
442
  });
415
443
  }
416
444
  async function lookupInteractiveFlowResume(flowEntry, currentScope) {
@@ -448,9 +476,31 @@ async function lookupInteractiveFlowResume(flowEntry, currentScope) {
448
476
  ...availability,
449
477
  };
450
478
  }
479
+ async function lookupInteractiveAutoFlowResume(selection, currentScope) {
480
+ const resolved = await resolveAutoFlow(selection, {
481
+ cwd: process.cwd(),
482
+ scopeKey: currentScope.scopeKey,
483
+ });
484
+ const identity = autoFlowIdentityForSelection(selection, resolved);
485
+ const state = loadFlowRunState(currentScope.scopeKey, identity.flowId);
486
+ const availability = classifyFlowLaunchAvailability(state);
487
+ if (state && availability.resume.available) {
488
+ return {
489
+ ...availability,
490
+ details: buildFlowResumeDetails(state),
491
+ };
492
+ }
493
+ if (state && availability.continue.available) {
494
+ return {
495
+ ...availability,
496
+ details: buildFlowContinueDetails(state),
497
+ };
498
+ }
499
+ return availability;
500
+ }
451
501
  async function printAutoPhasesHelp() {
452
502
  const phaseLines = ["Available auto-golang phases:", "", ...(await autoPhaseIds())];
453
- 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>");
503
+ 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>");
454
504
  printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
455
505
  }
456
506
  async function autoCommonPhaseIds(fileName = "auto-common.json") {
@@ -458,7 +508,7 @@ async function autoCommonPhaseIds(fileName = "auto-common.json") {
458
508
  }
459
509
  async function printAutoCommonPhasesHelp(command = "auto-common", fileName = "auto-common.json") {
460
510
  const phaseLines = [`Available ${command} phases:`, "", ...(await autoCommonPhaseIds(fileName))];
461
- phaseLines.push("", `You can run ${command} with:`, `agentweaver ${command} <jira>`);
511
+ phaseLines.push("", `You can run ${command} with:`, `agentweaver ${command} [<jira>]`);
462
512
  printPanel(command === "auto-common-guided" ? "Auto-Common Guided Phases" : "Auto-Common Phases", phaseLines.join("\n"), "magenta");
463
513
  }
464
514
  async function autoSimplePhaseIds() {
@@ -466,7 +516,7 @@ async function autoSimplePhaseIds() {
466
516
  }
467
517
  async function printAutoSimplePhasesHelp() {
468
518
  const phaseLines = ["Available auto-simple phases:", "", ...(await autoSimplePhaseIds())];
469
- phaseLines.push("", "You can run auto-simple with:", "agentweaver auto-simple <jira>");
519
+ phaseLines.push("", "You can run auto-simple with:", "agentweaver auto-simple [<jira>]");
470
520
  printPanel("Auto-Simple Phases", phaseLines.join("\n"), "magenta");
471
521
  }
472
522
  function nextReviewIterationForTask(taskKey) {
@@ -486,13 +536,16 @@ function buildBaseConfig(command, options = {}) {
486
536
  autoFromPhase: options.autoFromPhase ?? null,
487
537
  mdLang: options.mdLang ?? null,
488
538
  dryRun: options.dryRun ?? false,
539
+ dryRunFlow: options.dryRunFlow ?? false,
489
540
  verbose: options.verbose ?? false,
490
541
  ...(options.doctorArgs !== undefined ? { doctorArgs: options.doctorArgs } : {}),
491
542
  ...(options.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: options.acceptPlaybookDraft } : {}),
543
+ ...(options.autoFlowSelection !== undefined ? { autoFlowSelection: options.autoFlowSelection } : {}),
492
544
  };
493
545
  }
494
546
  function commandRequiresTask(command) {
495
- return (command === "plan-revise" ||
547
+ return (command === "auto" ||
548
+ command === "plan-revise" ||
496
549
  command === "bug-analyze" ||
497
550
  command === "bug-fix" ||
498
551
  command === "design-review" ||
@@ -504,6 +557,13 @@ function commandRequiresTask(command) {
504
557
  command === "auto-status" ||
505
558
  command === "auto-reset");
506
559
  }
560
+ function commandSupportsManualTaskSource(command) {
561
+ return (command === "auto" ||
562
+ command === "auto-golang" ||
563
+ command === "auto-common-guided" ||
564
+ command === "auto-common" ||
565
+ command === "auto-simple");
566
+ }
507
567
  function commandSupportsProjectScope(command) {
508
568
  return (command === "plan" ||
509
569
  command === "git-commit" ||
@@ -519,7 +579,21 @@ function commandSupportsProjectScope(command) {
519
579
  command === "run-go-tests-loop" ||
520
580
  command === "run-go-linter-loop");
521
581
  }
522
- async function resolveScopeForCommand(config, requestUserInput) {
582
+ function hasJiraConfig(config) {
583
+ return Boolean(config.scope.jiraRef && config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile);
584
+ }
585
+ function syncJiraEnv(config) {
586
+ if (hasJiraConfig(config)) {
587
+ process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
588
+ process.env.JIRA_API_URL = config.jiraApiUrl;
589
+ process.env.JIRA_TASK_FILE = config.jiraTaskFile;
590
+ return;
591
+ }
592
+ delete process.env.JIRA_BROWSE_URL;
593
+ delete process.env.JIRA_API_URL;
594
+ delete process.env.JIRA_TASK_FILE;
595
+ }
596
+ async function resolveScopeForCommand(config, requestUserInput, launchMode) {
523
597
  if (config.command === "instant-task") {
524
598
  if (config.scopeName?.trim()) {
525
599
  throw new TaskRunnerError("Command 'instant-task' rejects explicit scope overrides. The current branch-derived scope is the only supported lineage identity.");
@@ -537,13 +611,23 @@ async function resolveScopeForCommand(config, requestUserInput) {
537
611
  }
538
612
  if (commandRequiresTask(config.command)) {
539
613
  try {
614
+ if (commandSupportsManualTaskSource(config.command)) {
615
+ if (launchMode === "resume" || launchMode === "continue") {
616
+ return resolveProjectScope(config.scopeName);
617
+ }
618
+ const jiraContext = await requestOptionalJiraContext(requestUserInput);
619
+ return resolveProjectScope(config.scopeName, jiraContext?.jiraRef ?? null);
620
+ }
540
621
  const jiraContext = await requestJiraContext(requestUserInput);
541
622
  return resolveProjectScope(config.scopeName, jiraContext.jiraRef);
542
623
  }
543
624
  catch (error) {
544
625
  if (error instanceof TaskRunnerError && error.message.includes("no TTY is available")) {
545
- throw new TaskRunnerError(`Command '${config.command}' requires a Jira task.\n` +
546
- "Pass Jira issue key / browse URL as an argument, or run the command in an interactive terminal.");
626
+ throw new TaskRunnerError(commandSupportsManualTaskSource(config.command)
627
+ ? `Command '${config.command}' requires Jira input or a manual task description.\n` +
628
+ "Pass Jira issue key / browse URL as an argument, or run the command in an interactive terminal, leave Jira empty, and paste the task description in the next step."
629
+ : `Command '${config.command}' requires a Jira task.\n` +
630
+ "Pass Jira issue key / browse URL as an argument, or run the command in an interactive terminal.");
547
631
  }
548
632
  throw error;
549
633
  }
@@ -611,9 +695,12 @@ function resolveExecutorPrerequisite(executor, registryContext) {
611
695
  }
612
696
  }
613
697
  async function checkPrerequisites(config, launchProfile, executionRouting) {
698
+ const groups = await commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
699
+ await checkPrerequisitesForRoutingGroups(groups, launchProfile, executionRouting);
700
+ }
701
+ async function checkPrerequisitesForRoutingGroups(groups, launchProfile, executionRouting) {
614
702
  const registryContext = await createPipelineRegistryContext(process.cwd());
615
703
  const routing = routingForPrerequisites(launchProfile, executionRouting);
616
- const groups = await commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
617
704
  for (const executor of executorsForRoutingGroups(routing, groups)) {
618
705
  resolveExecutorPrerequisite(executor, registryContext);
619
706
  }
@@ -621,6 +708,124 @@ async function checkPrerequisites(config, launchProfile, executionRouting) {
621
708
  async function checkAutoPrerequisites(config, launchProfile, executionRouting) {
622
709
  await checkPrerequisites(config, launchProfile, executionRouting);
623
710
  }
711
+ async function checkResolvedAutoPrerequisites(resolved, launchProfile, executionRouting) {
712
+ const groups = resolved.execution.kind === "built-in"
713
+ ? await collectFlowRoutingGroups(await loadDeclarativeFlow({ source: "built-in", fileName: resolved.execution.specFile }), process.cwd())
714
+ : await collectFlowRoutingGroups(resolved.execution.flow, process.cwd(), new Set(), { inMemoryFlows: resolved.execution.inMemoryFlows });
715
+ await checkPrerequisitesForRoutingGroups(groups, launchProfile, executionRouting);
716
+ }
717
+ async function loadResolvedAutoFlowExecution(resolved) {
718
+ if (resolved.execution.kind === "built-in") {
719
+ return {
720
+ flow: await loadDeclarativeFlow({ source: "built-in", fileName: resolved.execution.specFile }),
721
+ };
722
+ }
723
+ return {
724
+ flow: resolved.execution.flow,
725
+ inMemoryFlows: resolved.execution.inMemoryFlows,
726
+ };
727
+ }
728
+ async function runResolvedAutoFlow(resolved, identity, config, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices, forceRefreshSummary = false) {
729
+ const { flow, inMemoryFlows } = await loadResolvedAutoFlowExecution(resolved);
730
+ const mergedFlowParams = {
731
+ ...defaultDeclarativeFlowParams(config, false, overrides),
732
+ ...autoFlowParams(config, forceRefreshSummary),
733
+ };
734
+ await runLoadedDeclarativeFlow(identity.flowId, flow, config, withCanonicalReviewLoopParams(flow.kind, mergedFlowParams), overrides, requestUserInput, setSummary, launchMode, runtime, inMemoryFlows);
735
+ }
736
+ function formatAutoFlowSource(resolved) {
737
+ const source = resolved.document.source;
738
+ if (source.type === "preset") {
739
+ return `preset ${source.preset}`;
740
+ }
741
+ const shadowed = source.shadowedUserPath ? ` (shadowed user config: ${source.shadowedUserPath})` : "";
742
+ return `${source.type} ${source.configName} at ${source.path}${shadowed}`;
743
+ }
744
+ function resolverArtifactStatusLines(resolved) {
745
+ const paths = resolved.document.artifactPolicy.artifactPaths;
746
+ if (!paths) {
747
+ return ["Resolver artifacts: not available for this scope"];
748
+ }
749
+ return [
750
+ "Resolver artifacts:",
751
+ ` - flow-config.yaml: ${paths.flowConfigYaml}${existsSync(paths.flowConfigYaml) ? "" : " (missing)"}`,
752
+ ` - resolved-flow.json: ${paths.resolvedFlowJson}${existsSync(paths.resolvedFlowJson) ? "" : " (missing)"}`,
753
+ ` - resolved-flow-summary.json: ${paths.resolvedFlowSummaryJson}${existsSync(paths.resolvedFlowSummaryJson) ? "" : " (missing)"}`,
754
+ ];
755
+ }
756
+ async function printConfigurableAutoStatus(config, resolved, identity) {
757
+ const stateFile = flowStateFile(config.scope.scopeKey, identity.flowId);
758
+ const state = loadFlowRunState(config.scope.scopeKey, identity.flowId);
759
+ const { flow } = await loadResolvedAutoFlowExecution(resolved);
760
+ const phaseLabels = new Map(resolved.document.phases.map((phase) => [phase.id, phase.label]));
761
+ const lines = [
762
+ `Issue: ${config.taskKey}`,
763
+ `Target: ${identity.displayLabel}`,
764
+ `Selected command: ${identity.selectedCommand}`,
765
+ `Effective flow ID: ${identity.flowId}`,
766
+ `Source: ${formatAutoFlowSource(resolved)}`,
767
+ `Base preset: ${resolved.document.basePreset}`,
768
+ `Execution target: ${resolved.document.executionTarget.kind}`,
769
+ `Resolver fingerprint: ${resolved.document.fingerprint}`,
770
+ `State file: ${stateFile}`,
771
+ ];
772
+ if (!state) {
773
+ lines.push("", "Status: no saved state", "", "Phases:");
774
+ for (const phase of flow.phases) {
775
+ const label = phaseLabels.get(phase.id);
776
+ lines.push(` - ${phase.id}${label && label !== phase.id ? ` - ${label}` : ""}`);
777
+ }
778
+ lines.push("", ...resolverArtifactStatusLines(resolved));
779
+ printPanelSync("Auto Status", lines.join("\n"));
780
+ return;
781
+ }
782
+ const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
783
+ lines.push("", `Status: ${state.status}`, `Current step: ${currentStep}`, `Updated: ${state.updatedAt}`, `Continuation: ${state.continuation?.continueEligible ? "eligible" : "not eligible"}`);
784
+ if (state.continuation?.stopPhaseId && state.continuation?.stopStepId) {
785
+ lines.push(`Stopped at: ${state.continuation.stopPhaseId}:${state.continuation.stopStepId}`);
786
+ }
787
+ if (state.selectedRoutingPreset) {
788
+ lines.push(`Routing preset: ${state.selectedRoutingPreset.label}`);
789
+ }
790
+ if (state.executionRouting) {
791
+ lines.push(`Default route: ${state.executionRouting.defaultRoute.executor} / ${state.executionRouting.defaultRoute.model}`);
792
+ lines.push(`Routing fingerprint: ${state.executionRouting.fingerprint}`);
793
+ }
794
+ else if (state.launchProfile) {
795
+ lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
796
+ }
797
+ if (state.lastError) {
798
+ lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
799
+ }
800
+ lines.push("", "Phases:");
801
+ for (const phase of flow.phases) {
802
+ const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
803
+ const label = phaseLabels.get(phase.id);
804
+ lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}${label && label !== phase.id ? ` - ${label}` : ""}`);
805
+ for (const step of phase.steps) {
806
+ const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
807
+ lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
808
+ }
809
+ }
810
+ if (state.executionState.terminated) {
811
+ lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
812
+ }
813
+ lines.push("", ...resolverArtifactStatusLines(resolved));
814
+ printPanelSync("Auto Status", lines.join("\n"));
815
+ }
816
+ async function printConfigurableAutoReset(config, resolved, identity) {
817
+ const stateFile = flowStateFile(config.scope.scopeKey, identity.flowId);
818
+ const removed = resetFlowRunState(config.scope.scopeKey, identity.flowId);
819
+ const lines = [
820
+ `Issue: ${config.taskKey}`,
821
+ `Target: ${identity.displayLabel}`,
822
+ `Effective flow ID: ${identity.flowId}`,
823
+ `Source: ${formatAutoFlowSource(resolved)}`,
824
+ removed ? `State file ${stateFile} removed.` : `No flow state file found at ${stateFile}.`,
825
+ "Resolver artifacts were left unchanged.",
826
+ ];
827
+ printPanelSync("Auto Reset", lines.join("\n"));
828
+ }
624
829
  function autoFlowParams(config, forceRefreshSummary = false) {
625
830
  return {
626
831
  jiraApiUrl: config.jiraApiUrl,
@@ -711,6 +916,10 @@ function loadInstantTaskInputDefaults(taskKey) {
711
916
  }
712
917
  function interactiveFlowDefinition(entry) {
713
918
  const flow = entry.flow;
919
+ const autoFlowSelection = autoFlowSelectionForFlowId(entry.id);
920
+ const autoFlow = autoFlowSelection?.kind === "preset"
921
+ ? createPresetAutoFlowDefinition(autoFlowSelection.preset)
922
+ : null;
714
923
  return {
715
924
  id: entry.id,
716
925
  label: entry.id,
@@ -718,6 +927,7 @@ function interactiveFlowDefinition(entry) {
718
927
  source: entry.source,
719
928
  treePath: [...entry.treePath],
720
929
  ...(entry.source !== "built-in" ? { sourcePath: entry.absolutePath } : {}),
930
+ ...(autoFlow ? { autoFlow } : {}),
721
931
  phases: flow.phases.map((phase) => ({
722
932
  id: phase.id,
723
933
  repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
@@ -727,8 +937,40 @@ function interactiveFlowDefinition(entry) {
727
937
  })),
728
938
  };
729
939
  }
730
- function interactiveFlowDefinitions(catalog) {
731
- return catalog.map((entry) => interactiveFlowDefinition(entry));
940
+ function savedAutoFlowDefinitions(cwd) {
941
+ const definitions = [];
942
+ for (const item of listAutoFlowConfigs(cwd)) {
943
+ try {
944
+ const loaded = loadAutoFlowConfigByName(item.name, cwd);
945
+ definitions.push({
946
+ id: `auto-config:${loaded.config.name}`,
947
+ label: `auto-config:${loaded.config.name}`,
948
+ description: `Saved configurable auto-flow config '${loaded.config.name}'.`,
949
+ source: "built-in",
950
+ treePath: ["default", "auto-configs", loaded.config.name],
951
+ autoFlow: createConfigAutoFlowDefinition({
952
+ config: loaded.config,
953
+ source: {
954
+ type: loaded.source.type === "project" ? "project-config" : "user-config",
955
+ configName: loaded.config.name,
956
+ path: loaded.source.path,
957
+ ...(loaded.source.shadowedUserPath ? { shadowedUserPath: loaded.source.shadowedUserPath } : {}),
958
+ },
959
+ }),
960
+ phases: [],
961
+ });
962
+ }
963
+ catch (error) {
964
+ printError(error.message);
965
+ }
966
+ }
967
+ return definitions;
968
+ }
969
+ function interactiveFlowDefinitions(catalog, cwd = process.cwd()) {
970
+ return [
971
+ ...catalog.map((entry) => interactiveFlowDefinition(entry)),
972
+ ...savedAutoFlowDefinitions(cwd),
973
+ ];
732
974
  }
733
975
  function publishFlowState(flowId, executionState) {
734
976
  setFlowExecutionState(flowId, stripExecutionStatePayload(executionState));
@@ -766,7 +1008,7 @@ function findCurrentFlowExecutionStep(state) {
766
1008
  }
767
1009
  return null;
768
1010
  }
769
- async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
1011
+ async function runLoadedDeclarativeFlow(flowId, flow, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices, inMemoryFlows) {
770
1012
  const context = await createPipelineContext({
771
1013
  issueKey: config.taskKey,
772
1014
  jiraRef: config.jiraRef,
@@ -777,8 +1019,8 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
777
1019
  ...(setSummary ? { setSummary } : {}),
778
1020
  requestUserInput,
779
1021
  ...(overrides.executionRouting ? { executionRouting: overrides.executionRouting } : {}),
1022
+ ...(inMemoryFlows ? { inMemoryFlows } : {}),
780
1023
  });
781
- const flow = await loadDeclarativeFlow(flowRef);
782
1024
  const initialExecutionState = {
783
1025
  flowKind: flow.kind,
784
1026
  flowVersion: flow.version,
@@ -793,7 +1035,7 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
793
1035
  if (persistedState && launchMode === "resume") {
794
1036
  await validateDeclarativeFlowResumeState({
795
1037
  id: flowId,
796
- source: flow.source,
1038
+ source: flow.source === "generated" ? "built-in" : flow.source,
797
1039
  fileName: flow.fileName,
798
1040
  absolutePath: flow.absolutePath,
799
1041
  treePath: [],
@@ -808,14 +1050,14 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
808
1050
  persistedState = prepareFlowStateForContinue(persistedState, flow.phases);
809
1051
  }
810
1052
  else if (launchMode === "restart") {
811
- if (existingStateForRestart) {
1053
+ if (existingStateForRestart && isRestartArchivingFlowId(flowId)) {
812
1054
  archiveActiveAttempt(config.scope.scopeKey);
813
1055
  }
814
1056
  resetFlowRunState(config.scope.scopeKey, flowId);
815
1057
  }
816
1058
  const executionState = persistedState?.executionState ?? initialExecutionState;
817
1059
  const state = persistedState
818
- ?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.jiraRef, overrides.launchProfile, overrides.executionRouting, overrides.selectedRoutingPreset);
1060
+ ?? createFlowRunState(config.scope.scopeKey, flowId, executionState, config.scope.jiraRef ?? null, overrides.launchProfile, overrides.executionRouting, overrides.selectedRoutingPreset);
819
1061
  if (overrides.executionRouting) {
820
1062
  state.executionRouting = overrides.executionRouting;
821
1063
  state.routingFingerprint = overrides.executionRouting.fingerprint;
@@ -877,6 +1119,10 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
877
1119
  throw error;
878
1120
  }
879
1121
  }
1122
+ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
1123
+ const flow = await loadDeclarativeFlow(flowRef);
1124
+ await runLoadedDeclarativeFlow(flowId, flow, config, flowParams, overrides, requestUserInput, setSummary, launchMode, runtime);
1125
+ }
880
1126
  async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
881
1127
  const mergedFlowParams = {
882
1128
  ...defaultDeclarativeFlowParams(config, false, overrides),
@@ -1020,7 +1266,7 @@ async function summarizeBuildFailure(output) {
1020
1266
  }), output);
1021
1267
  }
1022
1268
  function requireJiraConfig(config) {
1023
- if (!config.jiraBrowseUrl || !config.jiraApiUrl || !config.jiraTaskFile) {
1269
+ if (!hasJiraConfig(config)) {
1024
1270
  throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
1025
1271
  }
1026
1272
  }
@@ -1029,7 +1275,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1029
1275
  const exitCode = await runDoctorCommand(baseConfig.doctorArgs ?? []);
1030
1276
  return exitCode === 0;
1031
1277
  }
1032
- const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
1278
+ const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput, explicitLaunchMode)));
1033
1279
  const flowOverrides = executionRouting
1034
1280
  ? {
1035
1281
  launchProfile: executionRouting.defaultRoute,
@@ -1039,9 +1285,45 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1039
1285
  : launchProfile
1040
1286
  ? { launchProfile }
1041
1287
  : {};
1042
- const launchMode = config.command === "auto-status" || config.command === "auto-reset"
1043
- ? "restart"
1044
- : await chooseLaunchMode(config.command, config.scope.scopeKey, explicitLaunchMode, requestUserInput);
1288
+ const configurableAutoSelection = config.autoFlowSelection
1289
+ ?? (config.command === "auto" || config.command === "auto-status" || config.command === "auto-reset"
1290
+ ? defaultAutoFlowSelection()
1291
+ : undefined);
1292
+ const resolvedConfigurableAuto = configurableAutoSelection
1293
+ ? await resolveAutoFlow(configurableAutoSelection, {
1294
+ cwd: process.cwd(),
1295
+ scopeKey: config.scope.scopeKey,
1296
+ })
1297
+ : null;
1298
+ const configurableAutoIdentity = resolvedConfigurableAuto && configurableAutoSelection
1299
+ ? autoFlowIdentityForSelection(configurableAutoSelection, resolvedConfigurableAuto)
1300
+ : null;
1301
+ if (resolvedConfigurableAuto && configurableAutoIdentity) {
1302
+ if (config.command !== "auto-status" && config.command !== "auto-reset") {
1303
+ config.command = configurableAutoIdentity.selectedCommand;
1304
+ }
1305
+ if (config.dryRunFlow) {
1306
+ writeStdoutSync(formatAutoFlowDryRunPreview(resolvedConfigurableAuto));
1307
+ return false;
1308
+ }
1309
+ }
1310
+ if (resolvedConfigurableAuto && configurableAutoIdentity && config.command === "auto-status") {
1311
+ await printConfigurableAutoStatus(config, resolvedConfigurableAuto, configurableAutoIdentity);
1312
+ return false;
1313
+ }
1314
+ if (resolvedConfigurableAuto && configurableAutoIdentity && config.command === "auto-reset") {
1315
+ await printConfigurableAutoReset(config, resolvedConfigurableAuto, configurableAutoIdentity);
1316
+ return false;
1317
+ }
1318
+ const launchFlowId = configurableAutoIdentity?.flowId ?? config.command;
1319
+ const launchMode = await chooseLaunchMode(launchFlowId, config.scope.scopeKey, explicitLaunchMode, requestUserInput);
1320
+ if (resolvedConfigurableAuto && configurableAutoIdentity) {
1321
+ syncJiraEnv(config);
1322
+ await checkResolvedAutoPrerequisites(resolvedConfigurableAuto, launchProfile, executionRouting);
1323
+ persistResolvedAutoFlowArtifacts(config.scope.scopeKey, resolvedConfigurableAuto);
1324
+ await runResolvedAutoFlow(resolvedConfigurableAuto, configurableAutoIdentity, config, flowOverrides, requestUserInput, setSummary, launchMode, runtime, forceRefreshSummary);
1325
+ return false;
1326
+ }
1045
1327
  if (config.command === "instant-task") {
1046
1328
  await checkPrerequisites(config, launchProfile, executionRouting);
1047
1329
  const hasPersistedInstantTaskState = loadFlowRunState(config.scope.scopeKey, "instant-task") !== null;
@@ -1064,10 +1346,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1064
1346
  return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
1065
1347
  }
1066
1348
  if (config.command === "auto-golang") {
1067
- requireJiraConfig(config);
1068
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
1069
- process.env.JIRA_API_URL = config.jiraApiUrl;
1070
- process.env.JIRA_TASK_FILE = config.jiraTaskFile;
1349
+ syncJiraEnv(config);
1071
1350
  let effectiveLaunchMode = launchMode;
1072
1351
  let effectiveLaunchProfile = launchProfile;
1073
1352
  let effectiveExecutionRouting = executionRouting;
@@ -1098,81 +1377,22 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1098
1377
  return false;
1099
1378
  }
1100
1379
  if (config.command === "auto-common" || config.command === "auto-common-guided") {
1101
- requireJiraConfig(config);
1102
1380
  await checkAutoPrerequisites(config, launchProfile, executionRouting);
1103
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
1104
- process.env.JIRA_API_URL = config.jiraApiUrl;
1105
- process.env.JIRA_TASK_FILE = config.jiraTaskFile;
1381
+ syncJiraEnv(config);
1106
1382
  await runDeclarativeFlowBySpecFile(config.command === "auto-common-guided" ? "auto-common-guided.json" : "auto-common.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1107
1383
  return false;
1108
1384
  }
1109
1385
  if (config.command === "auto-simple") {
1110
- requireJiraConfig(config);
1111
1386
  await checkAutoPrerequisites(config, launchProfile, executionRouting);
1112
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
1113
- process.env.JIRA_API_URL = config.jiraApiUrl;
1114
- process.env.JIRA_TASK_FILE = config.jiraTaskFile;
1387
+ syncJiraEnv(config);
1115
1388
  await runDeclarativeFlowBySpecFile("auto-simple.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1116
1389
  return false;
1117
1390
  }
1118
- if (config.command === "auto-status") {
1119
- const state = loadFlowRunState(config.scope.scopeKey, "auto-golang");
1120
- if (!state) {
1121
- printPanel("Auto-Golang Status", `No flow state file found for ${config.taskKey}.`, "yellow");
1122
- return false;
1123
- }
1124
- const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
1125
- const phaseOrder = (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" })).phases;
1126
- const lines = [
1127
- `Issue: ${config.taskKey}`,
1128
- `Status: ${state.status}`,
1129
- `Current step: ${currentStep}`,
1130
- `Updated: ${state.updatedAt}`,
1131
- ];
1132
- if (state.executionRouting) {
1133
- lines.push(`Default route: ${state.executionRouting.defaultRoute.executor} / ${state.executionRouting.defaultRoute.model}`);
1134
- lines.push(`Routing fingerprint: ${state.executionRouting.fingerprint}`);
1135
- }
1136
- else if (state.launchProfile) {
1137
- lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
1138
- }
1139
- if (state.lastError) {
1140
- lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
1141
- }
1142
- lines.push("");
1143
- for (const phase of phaseOrder) {
1144
- const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
1145
- lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}`);
1146
- for (const step of phase.steps) {
1147
- const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
1148
- lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
1149
- }
1150
- }
1151
- if (state.executionState.terminated) {
1152
- lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
1153
- }
1154
- printPanel("Auto-Golang Status", lines.join("\n"), "cyan");
1155
- return false;
1156
- }
1157
- if (config.command === "auto-reset") {
1158
- const removed = resetFlowRunState(config.scope.scopeKey, "auto-golang");
1159
- printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
1160
- return false;
1161
- }
1162
1391
  await checkPrerequisites(config, launchProfile, executionRouting);
1163
- if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
1164
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl ?? "";
1165
- process.env.JIRA_API_URL = config.jiraApiUrl ?? "";
1166
- process.env.JIRA_TASK_FILE = config.jiraTaskFile ?? "";
1167
- }
1168
- else {
1169
- delete process.env.JIRA_BROWSE_URL;
1170
- delete process.env.JIRA_API_URL;
1171
- delete process.env.JIRA_TASK_FILE;
1172
- }
1392
+ syncJiraEnv(config);
1173
1393
  if (config.command === "plan") {
1174
1394
  let taskContextIteration;
1175
- if (config.jiraRef) {
1395
+ if (hasJiraConfig(config)) {
1176
1396
  requireJiraConfig(config);
1177
1397
  if (config.verbose) {
1178
1398
  process.stdout.write(`Fetching Jira issue from browse URL: ${config.jiraBrowseUrl}\n`);
@@ -1191,7 +1411,21 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1191
1411
  }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1192
1412
  }
1193
1413
  else {
1194
- taskContextIteration = latestTaskContextIteration(config.taskKey);
1414
+ const latestTaskContext = latestArtifactIteration(config.taskKey, "task-context", "json");
1415
+ if (latestTaskContext !== null) {
1416
+ taskContextIteration = latestTaskContext;
1417
+ }
1418
+ else {
1419
+ taskContextIteration = nextArtifactIteration(config.taskKey, "task-context", "json");
1420
+ await runDeclarativeFlowBySpecFile("task-source/manual-jira-input.json", config, {
1421
+ taskKey: config.taskKey,
1422
+ }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1423
+ await runDeclarativeFlowBySpecFile("normalize-task-source.json", config, {
1424
+ taskKey: config.taskKey,
1425
+ iteration: taskContextIteration,
1426
+ extraPrompt: config.extraPrompt,
1427
+ }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1428
+ }
1195
1429
  }
1196
1430
  await runDeclarativeFlowBySpecFile("plan.json", config, {
1197
1431
  taskKey: config.taskKey,
@@ -1489,8 +1723,10 @@ async function parseCliArgs(argv) {
1489
1723
  writeStderrSync(`${usage()}\n`);
1490
1724
  process.exit(1);
1491
1725
  }
1492
- const command = rawCommand === "auto" ? "auto-common" : rawCommand;
1726
+ const isConfigurableAutoCommand = rawCommand === "auto";
1727
+ const supportsAutoFlowSelection = rawCommand === "auto" || rawCommand === "auto-status" || rawCommand === "auto-reset";
1493
1728
  let dry = false;
1729
+ let dryRunFlow = false;
1494
1730
  let verbose = false;
1495
1731
  let prompt;
1496
1732
  let autoFromPhase;
@@ -1501,9 +1737,26 @@ async function parseCliArgs(argv) {
1501
1737
  let mdLang;
1502
1738
  let launchMode;
1503
1739
  let acceptPlaybookDraft = false;
1740
+ let autoPreset;
1741
+ let autoConfigName;
1504
1742
  let webNoOpen = process.env.AGENTWEAVER_WEB_NO_OPEN === "1";
1505
1743
  let webHost;
1506
1744
  const doctorArgs = [];
1745
+ const readRequiredValue = (flag, index) => {
1746
+ const value = argv[index + 1]?.trim();
1747
+ if (!value || value.startsWith("-")) {
1748
+ writeStderrSync(`Error: ${flag} requires a value.\n`);
1749
+ process.exit(1);
1750
+ }
1751
+ return value;
1752
+ };
1753
+ const parsePresetValue = (value) => {
1754
+ if (value === "simple" || value === "standard") {
1755
+ return value;
1756
+ }
1757
+ writeStderrSync("Error: --preset accepts only 'simple' or 'standard'.\n");
1758
+ process.exit(1);
1759
+ };
1507
1760
  for (let index = 1; index < argv.length; index += 1) {
1508
1761
  const token = argv[index] ?? "";
1509
1762
  if (token === "--dry") {
@@ -1514,6 +1767,58 @@ async function parseCliArgs(argv) {
1514
1767
  verbose = true;
1515
1768
  continue;
1516
1769
  }
1770
+ if (token === "--dry-run-flow") {
1771
+ if (!isConfigurableAutoCommand) {
1772
+ writeStderrSync("Error: --dry-run-flow is only supported after the auto command.\n");
1773
+ process.exit(1);
1774
+ }
1775
+ dryRunFlow = true;
1776
+ continue;
1777
+ }
1778
+ if (token === "--preset") {
1779
+ if (!supportsAutoFlowSelection) {
1780
+ writeStderrSync("Error: --preset is only supported after auto, auto-status, or auto-reset.\n");
1781
+ process.exit(1);
1782
+ }
1783
+ autoPreset = parsePresetValue(readRequiredValue("--preset", index));
1784
+ index += 1;
1785
+ continue;
1786
+ }
1787
+ if (token.startsWith("--preset=")) {
1788
+ if (!supportsAutoFlowSelection) {
1789
+ writeStderrSync("Error: --preset is only supported after auto, auto-status, or auto-reset.\n");
1790
+ process.exit(1);
1791
+ }
1792
+ const value = token.slice("--preset=".length).trim();
1793
+ if (!value) {
1794
+ writeStderrSync("Error: --preset requires a value.\n");
1795
+ process.exit(1);
1796
+ }
1797
+ autoPreset = parsePresetValue(value);
1798
+ continue;
1799
+ }
1800
+ if (token === "--config") {
1801
+ if (!supportsAutoFlowSelection) {
1802
+ writeStderrSync("Error: --config is only supported after auto, auto-status, or auto-reset.\n");
1803
+ process.exit(1);
1804
+ }
1805
+ autoConfigName = readRequiredValue("--config", index);
1806
+ index += 1;
1807
+ continue;
1808
+ }
1809
+ if (token.startsWith("--config=")) {
1810
+ if (!supportsAutoFlowSelection) {
1811
+ writeStderrSync("Error: --config is only supported after auto, auto-status, or auto-reset.\n");
1812
+ process.exit(1);
1813
+ }
1814
+ const value = token.slice("--config=".length).trim();
1815
+ if (!value) {
1816
+ writeStderrSync("Error: --config requires a value.\n");
1817
+ process.exit(1);
1818
+ }
1819
+ autoConfigName = value;
1820
+ continue;
1821
+ }
1517
1822
  if (token === "--help-phases") {
1518
1823
  helpPhases = true;
1519
1824
  continue;
@@ -1523,7 +1828,7 @@ async function parseCliArgs(argv) {
1523
1828
  continue;
1524
1829
  }
1525
1830
  if (token === "--no-open") {
1526
- if (command !== "web") {
1831
+ if (rawCommand !== "web") {
1527
1832
  writeStderrSync("Error: --no-open is only supported after the web command.\n");
1528
1833
  process.exit(1);
1529
1834
  }
@@ -1531,7 +1836,7 @@ async function parseCliArgs(argv) {
1531
1836
  continue;
1532
1837
  }
1533
1838
  if (token === "--listen-all") {
1534
- if (command !== "web") {
1839
+ if (rawCommand !== "web") {
1535
1840
  writeStderrSync("Error: --listen-all is only supported after the web command.\n");
1536
1841
  process.exit(1);
1537
1842
  }
@@ -1539,7 +1844,7 @@ async function parseCliArgs(argv) {
1539
1844
  continue;
1540
1845
  }
1541
1846
  if (token === "--host") {
1542
- if (command !== "web") {
1847
+ if (rawCommand !== "web") {
1543
1848
  writeStderrSync("Error: --host is only supported after the web command.\n");
1544
1849
  process.exit(1);
1545
1850
  }
@@ -1553,7 +1858,7 @@ async function parseCliArgs(argv) {
1553
1858
  continue;
1554
1859
  }
1555
1860
  if (token.startsWith("--host=")) {
1556
- if (command !== "web") {
1861
+ if (rawCommand !== "web") {
1557
1862
  writeStderrSync("Error: --host is only supported after the web command.\n");
1558
1863
  process.exit(1);
1559
1864
  }
@@ -1620,13 +1925,29 @@ async function parseCliArgs(argv) {
1620
1925
  }
1621
1926
  continue;
1622
1927
  }
1623
- if (command === "doctor") {
1928
+ if (rawCommand === "doctor") {
1624
1929
  doctorArgs.push(token);
1625
1930
  }
1626
1931
  else {
1627
1932
  jiraRef = token;
1628
1933
  }
1629
1934
  }
1935
+ if (autoPreset && autoConfigName) {
1936
+ writeStderrSync("Error: --preset and --config are mutually exclusive.\n");
1937
+ process.exit(1);
1938
+ }
1939
+ const autoFlowSelection = supportsAutoFlowSelection
1940
+ ? autoConfigName
1941
+ ? { kind: "config", name: autoConfigName }
1942
+ : autoPreset
1943
+ ? { kind: "preset", preset: autoPreset }
1944
+ : defaultAutoFlowSelection()
1945
+ : undefined;
1946
+ const command = isConfigurableAutoCommand
1947
+ ? autoFlowSelection?.kind === "preset" && autoFlowSelection.preset === "simple"
1948
+ ? "auto-simple"
1949
+ : "auto-common"
1950
+ : rawCommand;
1630
1951
  if (command === "auto-golang" && helpPhases) {
1631
1952
  await printAutoPhasesHelp();
1632
1953
  process.exit(0);
@@ -1642,6 +1963,7 @@ async function parseCliArgs(argv) {
1642
1963
  return {
1643
1964
  command: command,
1644
1965
  dry,
1966
+ dryRunFlow,
1645
1967
  verbose,
1646
1968
  helpPhases,
1647
1969
  ...(jiraRef !== undefined ? { jiraRef } : {}),
@@ -1653,6 +1975,7 @@ async function parseCliArgs(argv) {
1653
1975
  ...(doctorArgs.length > 0 ? { doctorArgs } : {}),
1654
1976
  ...(launchMode !== undefined ? { launchMode } : {}),
1655
1977
  ...(acceptPlaybookDraft ? { acceptPlaybookDraft } : {}),
1978
+ ...(autoFlowSelection !== undefined ? { autoFlowSelection } : {}),
1656
1979
  ...(command === "web" ? { webNoOpen } : {}),
1657
1980
  ...(command === "web" && webHost !== undefined ? { webHost } : {}),
1658
1981
  };
@@ -1666,9 +1989,11 @@ function buildConfigFromArgs(args) {
1666
1989
  ...(args.autoFromPhase !== undefined ? { autoFromPhase: args.autoFromPhase } : {}),
1667
1990
  ...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
1668
1991
  dryRun: args.dry,
1992
+ dryRunFlow: args.dryRunFlow,
1669
1993
  verbose: args.verbose,
1670
1994
  ...(args.doctorArgs !== undefined ? { doctorArgs: args.doctorArgs } : {}),
1671
1995
  ...(args.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: args.acceptPlaybookDraft } : {}),
1996
+ ...(args.autoFlowSelection !== undefined ? { autoFlowSelection: args.autoFlowSelection } : {}),
1672
1997
  });
1673
1998
  }
1674
1999
  async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRefresh = false, scopeName, installSignalCleanup = false) {
@@ -1728,6 +2053,10 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
1728
2053
  flows: interactiveFlowDefinitions(flowCatalog),
1729
2054
  getRunConfirmation: async (flowId) => {
1730
2055
  refreshScopeFromGit("git scope refresh before launch confirmation");
2056
+ const autoFlowSelection = autoFlowSelectionForFlowId(flowId);
2057
+ if (autoFlowSelection) {
2058
+ return await lookupInteractiveAutoFlowResume(autoFlowSelection, currentScope);
2059
+ }
1731
2060
  const flowEntry = findCatalogEntry(flowId, flowCatalog);
1732
2061
  if (!flowEntry) {
1733
2062
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
@@ -1741,6 +2070,19 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
1741
2070
  activeAbortController = abortController;
1742
2071
  activeFlowId = flowId;
1743
2072
  try {
2073
+ const autoFlowSelection = autoFlowSelectionForFlowId(flowId);
2074
+ if (autoFlowSelection) {
2075
+ const previousScopeKey = currentScope.scopeKey;
2076
+ const baseConfig = buildInteractiveBaseConfig(flowId, currentScope);
2077
+ const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form), launchMode);
2078
+ currentScope = nextScope;
2079
+ ui.setScope(currentScope.scopeKey, currentScope.jiraIssueKey ?? null, currentScope.gitBranchName);
2080
+ if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
2081
+ syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
2082
+ }
2083
+ await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, undefined, undefined, undefined, createRuntimeServices(abortController.signal));
2084
+ return;
2085
+ }
1744
2086
  const flowEntry = findCatalogEntry(flowId, flowCatalog);
1745
2087
  if (!flowEntry) {
1746
2088
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
@@ -1765,7 +2107,7 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
1765
2107
  const previousScopeKey = currentScope.scopeKey;
1766
2108
  const baseConfig = buildInteractiveBaseConfig(flowId, currentScope);
1767
2109
  if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
1768
- const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form));
2110
+ const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form), launchMode);
1769
2111
  currentScope = nextScope;
1770
2112
  }
1771
2113
  else if (flowRequiresTaskScope(flowEntry) && !currentScope.jiraRef) {