agentweaver 0.1.18 → 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 +54 -6
  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 +454 -108
  11. package/dist/interactive/auto-flow.js +644 -0
  12. package/dist/interactive/controller.js +489 -7
  13. package/dist/interactive/progress.js +194 -1
  14. package/dist/interactive/state.js +34 -0
  15. package/dist/interactive/web/index.js +237 -5
  16. package/dist/interactive/web/protocol.js +222 -1
  17. package/dist/interactive/web/server.js +497 -3
  18. package/dist/interactive/web/static/app.js +2462 -37
  19. package/dist/interactive/web/static/index.html +113 -11
  20. package/dist/interactive/web/static/styles.css +1 -1
  21. package/dist/interactive/web/static/styles.input.css +1383 -149
  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 +403 -0
  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,11 +36,13 @@ 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 = [
45
+ "auto",
41
46
  "auto-golang",
42
47
  "auto-common-guided",
43
48
  "auto-common",
@@ -76,6 +81,17 @@ function writeStdoutSync(text) {
76
81
  function writeStderrSync(text) {
77
82
  writeSync(process.stderr.fd, text);
78
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
+ }
79
95
  function createRuntimeServices(signal) {
80
96
  return {
81
97
  resolveCmd,
@@ -174,16 +190,18 @@ function usage() {
174
190
  agentweaver review-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [--blocking-severities <list>] [<jira-browse-url|jira-issue-key>]
175
191
  agentweaver run-go-tests-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<jira-browse-url|jira-issue-key>]
176
192
  agentweaver run-go-linter-loop [--dry] [--verbose] [--prompt <text>] [--scope <name>] [<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>]
194
+ agentweaver auto --help-phases
177
195
  agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
178
196
  agentweaver auto-golang [--dry] [--verbose] [--prompt <text>] --from <phase> [<jira-browse-url|jira-issue-key>]
179
197
  agentweaver auto-golang --help-phases
180
- agentweaver auto-common-guided [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [--accept-playbook-draft] <jira-browse-url|jira-issue-key>
181
- 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>]
182
200
  agentweaver auto-common --help-phases
183
- 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>]
184
202
  agentweaver auto-simple --help-phases
185
- agentweaver auto-status [<jira-browse-url|jira-issue-key>]
186
- 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>]
187
205
 
188
206
  Interactive Mode:
189
207
  When started without a command, the script opens an interactive UI.
@@ -196,13 +214,16 @@ Flags:
196
214
  --no-open Web command only: print the Web UI URL without opening a browser
197
215
  --host Web command only: bind Web UI to this host (default: 127.0.0.1)
198
216
  --listen-all Web command only: bind Web UI to 0.0.0.0
199
- --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
200
221
  --verbose Show live stdout/stderr of launched commands
201
222
  --scope Explicit workflow scope name for non-Jira runs except instant-task
202
223
  --prompt Extra prompt text appended to the base prompt
203
224
  --resume Resume an interrupted run when valid
204
225
  --continue Continue a terminated iterative run when valid
205
- --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
206
227
  --blocking-severities Comma-separated severities that block merge and drive review-fix auto-selection
207
228
  --md-lang Language for workflow markdown artifacts only: en (English) or ru (Russian, default)
208
229
  --accept-playbook-draft Non-interactively accept generated playbook content for playbook-init or auto-common-guided missing-manifest runs
@@ -226,7 +247,10 @@ Optional environment variables:
226
247
  ${WEB_AUTH_PASSWORD_ENV} Web UI Basic auth password; required for external Web UI binding
227
248
 
228
249
  Notes:
229
- - 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.
230
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.
231
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}.
232
256
  - Web UI Basic auth over plain HTTP is suitable only on trusted networks; use TLS termination or a reverse proxy on untrusted networks.
@@ -406,8 +430,15 @@ function scopeWithRestoredJiraContext(scope, state) {
406
430
  return resolveProjectScope(null, state.jiraRef);
407
431
  }
408
432
  function buildInteractiveBaseConfig(flowId, scope) {
409
- 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, {
410
440
  ...(flowId !== "instant-task" && scope.jiraRef ? { jiraRef: scope.jiraRef } : {}),
441
+ ...(autoFlowSelection ? { autoFlowSelection } : {}),
411
442
  });
412
443
  }
413
444
  async function lookupInteractiveFlowResume(flowEntry, currentScope) {
@@ -445,9 +476,31 @@ async function lookupInteractiveFlowResume(flowEntry, currentScope) {
445
476
  ...availability,
446
477
  };
447
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
+ }
448
501
  async function printAutoPhasesHelp() {
449
502
  const phaseLines = ["Available auto-golang phases:", "", ...(await autoPhaseIds())];
450
- 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>");
451
504
  printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
452
505
  }
453
506
  async function autoCommonPhaseIds(fileName = "auto-common.json") {
@@ -455,7 +508,7 @@ async function autoCommonPhaseIds(fileName = "auto-common.json") {
455
508
  }
456
509
  async function printAutoCommonPhasesHelp(command = "auto-common", fileName = "auto-common.json") {
457
510
  const phaseLines = [`Available ${command} phases:`, "", ...(await autoCommonPhaseIds(fileName))];
458
- phaseLines.push("", `You can run ${command} with:`, `agentweaver ${command} <jira>`);
511
+ phaseLines.push("", `You can run ${command} with:`, `agentweaver ${command} [<jira>]`);
459
512
  printPanel(command === "auto-common-guided" ? "Auto-Common Guided Phases" : "Auto-Common Phases", phaseLines.join("\n"), "magenta");
460
513
  }
461
514
  async function autoSimplePhaseIds() {
@@ -463,7 +516,7 @@ async function autoSimplePhaseIds() {
463
516
  }
464
517
  async function printAutoSimplePhasesHelp() {
465
518
  const phaseLines = ["Available auto-simple phases:", "", ...(await autoSimplePhaseIds())];
466
- 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>]");
467
520
  printPanel("Auto-Simple Phases", phaseLines.join("\n"), "magenta");
468
521
  }
469
522
  function nextReviewIterationForTask(taskKey) {
@@ -483,13 +536,16 @@ function buildBaseConfig(command, options = {}) {
483
536
  autoFromPhase: options.autoFromPhase ?? null,
484
537
  mdLang: options.mdLang ?? null,
485
538
  dryRun: options.dryRun ?? false,
539
+ dryRunFlow: options.dryRunFlow ?? false,
486
540
  verbose: options.verbose ?? false,
487
541
  ...(options.doctorArgs !== undefined ? { doctorArgs: options.doctorArgs } : {}),
488
542
  ...(options.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: options.acceptPlaybookDraft } : {}),
543
+ ...(options.autoFlowSelection !== undefined ? { autoFlowSelection: options.autoFlowSelection } : {}),
489
544
  };
490
545
  }
491
546
  function commandRequiresTask(command) {
492
- return (command === "plan-revise" ||
547
+ return (command === "auto" ||
548
+ command === "plan-revise" ||
493
549
  command === "bug-analyze" ||
494
550
  command === "bug-fix" ||
495
551
  command === "design-review" ||
@@ -501,6 +557,13 @@ function commandRequiresTask(command) {
501
557
  command === "auto-status" ||
502
558
  command === "auto-reset");
503
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
+ }
504
567
  function commandSupportsProjectScope(command) {
505
568
  return (command === "plan" ||
506
569
  command === "git-commit" ||
@@ -516,7 +579,21 @@ function commandSupportsProjectScope(command) {
516
579
  command === "run-go-tests-loop" ||
517
580
  command === "run-go-linter-loop");
518
581
  }
519
- 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) {
520
597
  if (config.command === "instant-task") {
521
598
  if (config.scopeName?.trim()) {
522
599
  throw new TaskRunnerError("Command 'instant-task' rejects explicit scope overrides. The current branch-derived scope is the only supported lineage identity.");
@@ -534,13 +611,23 @@ async function resolveScopeForCommand(config, requestUserInput) {
534
611
  }
535
612
  if (commandRequiresTask(config.command)) {
536
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
+ }
537
621
  const jiraContext = await requestJiraContext(requestUserInput);
538
622
  return resolveProjectScope(config.scopeName, jiraContext.jiraRef);
539
623
  }
540
624
  catch (error) {
541
625
  if (error instanceof TaskRunnerError && error.message.includes("no TTY is available")) {
542
- throw new TaskRunnerError(`Command '${config.command}' requires a Jira task.\n` +
543
- "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.");
544
631
  }
545
632
  throw error;
546
633
  }
@@ -608,9 +695,12 @@ function resolveExecutorPrerequisite(executor, registryContext) {
608
695
  }
609
696
  }
610
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) {
611
702
  const registryContext = await createPipelineRegistryContext(process.cwd());
612
703
  const routing = routingForPrerequisites(launchProfile, executionRouting);
613
- const groups = await commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
614
704
  for (const executor of executorsForRoutingGroups(routing, groups)) {
615
705
  resolveExecutorPrerequisite(executor, registryContext);
616
706
  }
@@ -618,6 +708,124 @@ async function checkPrerequisites(config, launchProfile, executionRouting) {
618
708
  async function checkAutoPrerequisites(config, launchProfile, executionRouting) {
619
709
  await checkPrerequisites(config, launchProfile, executionRouting);
620
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
+ }
621
829
  function autoFlowParams(config, forceRefreshSummary = false) {
622
830
  return {
623
831
  jiraApiUrl: config.jiraApiUrl,
@@ -708,6 +916,10 @@ function loadInstantTaskInputDefaults(taskKey) {
708
916
  }
709
917
  function interactiveFlowDefinition(entry) {
710
918
  const flow = entry.flow;
919
+ const autoFlowSelection = autoFlowSelectionForFlowId(entry.id);
920
+ const autoFlow = autoFlowSelection?.kind === "preset"
921
+ ? createPresetAutoFlowDefinition(autoFlowSelection.preset)
922
+ : null;
711
923
  return {
712
924
  id: entry.id,
713
925
  label: entry.id,
@@ -715,6 +927,7 @@ function interactiveFlowDefinition(entry) {
715
927
  source: entry.source,
716
928
  treePath: [...entry.treePath],
717
929
  ...(entry.source !== "built-in" ? { sourcePath: entry.absolutePath } : {}),
930
+ ...(autoFlow ? { autoFlow } : {}),
718
931
  phases: flow.phases.map((phase) => ({
719
932
  id: phase.id,
720
933
  repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
@@ -724,8 +937,40 @@ function interactiveFlowDefinition(entry) {
724
937
  })),
725
938
  };
726
939
  }
727
- function interactiveFlowDefinitions(catalog) {
728
- 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
+ ];
729
974
  }
730
975
  function publishFlowState(flowId, executionState) {
731
976
  setFlowExecutionState(flowId, stripExecutionStatePayload(executionState));
@@ -763,7 +1008,7 @@ function findCurrentFlowExecutionStep(state) {
763
1008
  }
764
1009
  return null;
765
1010
  }
766
- 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) {
767
1012
  const context = await createPipelineContext({
768
1013
  issueKey: config.taskKey,
769
1014
  jiraRef: config.jiraRef,
@@ -774,8 +1019,8 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
774
1019
  ...(setSummary ? { setSummary } : {}),
775
1020
  requestUserInput,
776
1021
  ...(overrides.executionRouting ? { executionRouting: overrides.executionRouting } : {}),
1022
+ ...(inMemoryFlows ? { inMemoryFlows } : {}),
777
1023
  });
778
- const flow = await loadDeclarativeFlow(flowRef);
779
1024
  const initialExecutionState = {
780
1025
  flowKind: flow.kind,
781
1026
  flowVersion: flow.version,
@@ -790,7 +1035,7 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
790
1035
  if (persistedState && launchMode === "resume") {
791
1036
  await validateDeclarativeFlowResumeState({
792
1037
  id: flowId,
793
- source: flow.source,
1038
+ source: flow.source === "generated" ? "built-in" : flow.source,
794
1039
  fileName: flow.fileName,
795
1040
  absolutePath: flow.absolutePath,
796
1041
  treePath: [],
@@ -805,14 +1050,14 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
805
1050
  persistedState = prepareFlowStateForContinue(persistedState, flow.phases);
806
1051
  }
807
1052
  else if (launchMode === "restart") {
808
- if (existingStateForRestart) {
1053
+ if (existingStateForRestart && isRestartArchivingFlowId(flowId)) {
809
1054
  archiveActiveAttempt(config.scope.scopeKey);
810
1055
  }
811
1056
  resetFlowRunState(config.scope.scopeKey, flowId);
812
1057
  }
813
1058
  const executionState = persistedState?.executionState ?? initialExecutionState;
814
1059
  const state = persistedState
815
- ?? 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);
816
1061
  if (overrides.executionRouting) {
817
1062
  state.executionRouting = overrides.executionRouting;
818
1063
  state.routingFingerprint = overrides.executionRouting.fingerprint;
@@ -874,6 +1119,10 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
874
1119
  throw error;
875
1120
  }
876
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
+ }
877
1126
  async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
878
1127
  const mergedFlowParams = {
879
1128
  ...defaultDeclarativeFlowParams(config, false, overrides),
@@ -1017,7 +1266,7 @@ async function summarizeBuildFailure(output) {
1017
1266
  }), output);
1018
1267
  }
1019
1268
  function requireJiraConfig(config) {
1020
- if (!config.jiraBrowseUrl || !config.jiraApiUrl || !config.jiraTaskFile) {
1269
+ if (!hasJiraConfig(config)) {
1021
1270
  throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
1022
1271
  }
1023
1272
  }
@@ -1026,7 +1275,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1026
1275
  const exitCode = await runDoctorCommand(baseConfig.doctorArgs ?? []);
1027
1276
  return exitCode === 0;
1028
1277
  }
1029
- const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
1278
+ const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput, explicitLaunchMode)));
1030
1279
  const flowOverrides = executionRouting
1031
1280
  ? {
1032
1281
  launchProfile: executionRouting.defaultRoute,
@@ -1036,9 +1285,45 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1036
1285
  : launchProfile
1037
1286
  ? { launchProfile }
1038
1287
  : {};
1039
- const launchMode = config.command === "auto-status" || config.command === "auto-reset"
1040
- ? "restart"
1041
- : 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
+ }
1042
1327
  if (config.command === "instant-task") {
1043
1328
  await checkPrerequisites(config, launchProfile, executionRouting);
1044
1329
  const hasPersistedInstantTaskState = loadFlowRunState(config.scope.scopeKey, "instant-task") !== null;
@@ -1061,10 +1346,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1061
1346
  return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
1062
1347
  }
1063
1348
  if (config.command === "auto-golang") {
1064
- requireJiraConfig(config);
1065
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
1066
- process.env.JIRA_API_URL = config.jiraApiUrl;
1067
- process.env.JIRA_TASK_FILE = config.jiraTaskFile;
1349
+ syncJiraEnv(config);
1068
1350
  let effectiveLaunchMode = launchMode;
1069
1351
  let effectiveLaunchProfile = launchProfile;
1070
1352
  let effectiveExecutionRouting = executionRouting;
@@ -1095,81 +1377,22 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1095
1377
  return false;
1096
1378
  }
1097
1379
  if (config.command === "auto-common" || config.command === "auto-common-guided") {
1098
- requireJiraConfig(config);
1099
1380
  await checkAutoPrerequisites(config, launchProfile, executionRouting);
1100
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
1101
- process.env.JIRA_API_URL = config.jiraApiUrl;
1102
- process.env.JIRA_TASK_FILE = config.jiraTaskFile;
1381
+ syncJiraEnv(config);
1103
1382
  await runDeclarativeFlowBySpecFile(config.command === "auto-common-guided" ? "auto-common-guided.json" : "auto-common.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1104
1383
  return false;
1105
1384
  }
1106
1385
  if (config.command === "auto-simple") {
1107
- requireJiraConfig(config);
1108
1386
  await checkAutoPrerequisites(config, launchProfile, executionRouting);
1109
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
1110
- process.env.JIRA_API_URL = config.jiraApiUrl;
1111
- process.env.JIRA_TASK_FILE = config.jiraTaskFile;
1387
+ syncJiraEnv(config);
1112
1388
  await runDeclarativeFlowBySpecFile("auto-simple.json", config, autoFlowParams(config, forceRefreshSummary), flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1113
1389
  return false;
1114
1390
  }
1115
- if (config.command === "auto-status") {
1116
- const state = loadFlowRunState(config.scope.scopeKey, "auto-golang");
1117
- if (!state) {
1118
- printPanel("Auto-Golang Status", `No flow state file found for ${config.taskKey}.`, "yellow");
1119
- return false;
1120
- }
1121
- const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
1122
- const phaseOrder = (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" })).phases;
1123
- const lines = [
1124
- `Issue: ${config.taskKey}`,
1125
- `Status: ${state.status}`,
1126
- `Current step: ${currentStep}`,
1127
- `Updated: ${state.updatedAt}`,
1128
- ];
1129
- if (state.executionRouting) {
1130
- lines.push(`Default route: ${state.executionRouting.defaultRoute.executor} / ${state.executionRouting.defaultRoute.model}`);
1131
- lines.push(`Routing fingerprint: ${state.executionRouting.fingerprint}`);
1132
- }
1133
- else if (state.launchProfile) {
1134
- lines.push(`Launch profile: ${state.launchProfile.executor} / ${state.launchProfile.model}`);
1135
- }
1136
- if (state.lastError) {
1137
- lines.push(`Last error: ${state.lastError.step ?? "-"} (exit ${state.lastError.returnCode ?? "-"}, ${state.lastError.message ?? "-"})`);
1138
- }
1139
- lines.push("");
1140
- for (const phase of phaseOrder) {
1141
- const phaseState = state.executionState.phases.find((candidate) => candidate.id === phase.id);
1142
- lines.push(`[${phaseState?.status ?? "pending"}] ${phase.id}`);
1143
- for (const step of phase.steps) {
1144
- const stepState = phaseState?.steps.find((candidate) => candidate.id === step.id);
1145
- lines.push(` - [${stepState?.status ?? "pending"}] ${step.id}`);
1146
- }
1147
- }
1148
- if (state.executionState.terminated) {
1149
- lines.push("", `Execution terminated: ${state.executionState.terminationReason ?? "yes"}`);
1150
- }
1151
- printPanel("Auto-Golang Status", lines.join("\n"), "cyan");
1152
- return false;
1153
- }
1154
- if (config.command === "auto-reset") {
1155
- const removed = resetFlowRunState(config.scope.scopeKey, "auto-golang");
1156
- printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
1157
- return false;
1158
- }
1159
1391
  await checkPrerequisites(config, launchProfile, executionRouting);
1160
- if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
1161
- process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl ?? "";
1162
- process.env.JIRA_API_URL = config.jiraApiUrl ?? "";
1163
- process.env.JIRA_TASK_FILE = config.jiraTaskFile ?? "";
1164
- }
1165
- else {
1166
- delete process.env.JIRA_BROWSE_URL;
1167
- delete process.env.JIRA_API_URL;
1168
- delete process.env.JIRA_TASK_FILE;
1169
- }
1392
+ syncJiraEnv(config);
1170
1393
  if (config.command === "plan") {
1171
1394
  let taskContextIteration;
1172
- if (config.jiraRef) {
1395
+ if (hasJiraConfig(config)) {
1173
1396
  requireJiraConfig(config);
1174
1397
  if (config.verbose) {
1175
1398
  process.stdout.write(`Fetching Jira issue from browse URL: ${config.jiraBrowseUrl}\n`);
@@ -1188,7 +1411,21 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1188
1411
  }, flowOverrides, requestUserInput, setSummary, launchMode, runtime);
1189
1412
  }
1190
1413
  else {
1191
- 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
+ }
1192
1429
  }
1193
1430
  await runDeclarativeFlowBySpecFile("plan.json", config, {
1194
1431
  taskKey: config.taskKey,
@@ -1481,12 +1718,15 @@ async function parseCliArgs(argv) {
1481
1718
  writeStderrSync(`${usage()}\n`);
1482
1719
  process.exit(1);
1483
1720
  }
1484
- const command = argv[0];
1485
- if (!COMMANDS.includes(command)) {
1721
+ const rawCommand = argv[0];
1722
+ if (!COMMANDS.includes(rawCommand)) {
1486
1723
  writeStderrSync(`${usage()}\n`);
1487
1724
  process.exit(1);
1488
1725
  }
1726
+ const isConfigurableAutoCommand = rawCommand === "auto";
1727
+ const supportsAutoFlowSelection = rawCommand === "auto" || rawCommand === "auto-status" || rawCommand === "auto-reset";
1489
1728
  let dry = false;
1729
+ let dryRunFlow = false;
1490
1730
  let verbose = false;
1491
1731
  let prompt;
1492
1732
  let autoFromPhase;
@@ -1497,9 +1737,26 @@ async function parseCliArgs(argv) {
1497
1737
  let mdLang;
1498
1738
  let launchMode;
1499
1739
  let acceptPlaybookDraft = false;
1740
+ let autoPreset;
1741
+ let autoConfigName;
1500
1742
  let webNoOpen = process.env.AGENTWEAVER_WEB_NO_OPEN === "1";
1501
1743
  let webHost;
1502
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
+ };
1503
1760
  for (let index = 1; index < argv.length; index += 1) {
1504
1761
  const token = argv[index] ?? "";
1505
1762
  if (token === "--dry") {
@@ -1510,6 +1767,58 @@ async function parseCliArgs(argv) {
1510
1767
  verbose = true;
1511
1768
  continue;
1512
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
+ }
1513
1822
  if (token === "--help-phases") {
1514
1823
  helpPhases = true;
1515
1824
  continue;
@@ -1519,7 +1828,7 @@ async function parseCliArgs(argv) {
1519
1828
  continue;
1520
1829
  }
1521
1830
  if (token === "--no-open") {
1522
- if (command !== "web") {
1831
+ if (rawCommand !== "web") {
1523
1832
  writeStderrSync("Error: --no-open is only supported after the web command.\n");
1524
1833
  process.exit(1);
1525
1834
  }
@@ -1527,7 +1836,7 @@ async function parseCliArgs(argv) {
1527
1836
  continue;
1528
1837
  }
1529
1838
  if (token === "--listen-all") {
1530
- if (command !== "web") {
1839
+ if (rawCommand !== "web") {
1531
1840
  writeStderrSync("Error: --listen-all is only supported after the web command.\n");
1532
1841
  process.exit(1);
1533
1842
  }
@@ -1535,7 +1844,7 @@ async function parseCliArgs(argv) {
1535
1844
  continue;
1536
1845
  }
1537
1846
  if (token === "--host") {
1538
- if (command !== "web") {
1847
+ if (rawCommand !== "web") {
1539
1848
  writeStderrSync("Error: --host is only supported after the web command.\n");
1540
1849
  process.exit(1);
1541
1850
  }
@@ -1549,7 +1858,7 @@ async function parseCliArgs(argv) {
1549
1858
  continue;
1550
1859
  }
1551
1860
  if (token.startsWith("--host=")) {
1552
- if (command !== "web") {
1861
+ if (rawCommand !== "web") {
1553
1862
  writeStderrSync("Error: --host is only supported after the web command.\n");
1554
1863
  process.exit(1);
1555
1864
  }
@@ -1616,13 +1925,29 @@ async function parseCliArgs(argv) {
1616
1925
  }
1617
1926
  continue;
1618
1927
  }
1619
- if (command === "doctor") {
1928
+ if (rawCommand === "doctor") {
1620
1929
  doctorArgs.push(token);
1621
1930
  }
1622
1931
  else {
1623
1932
  jiraRef = token;
1624
1933
  }
1625
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;
1626
1951
  if (command === "auto-golang" && helpPhases) {
1627
1952
  await printAutoPhasesHelp();
1628
1953
  process.exit(0);
@@ -1638,6 +1963,7 @@ async function parseCliArgs(argv) {
1638
1963
  return {
1639
1964
  command: command,
1640
1965
  dry,
1966
+ dryRunFlow,
1641
1967
  verbose,
1642
1968
  helpPhases,
1643
1969
  ...(jiraRef !== undefined ? { jiraRef } : {}),
@@ -1649,6 +1975,7 @@ async function parseCliArgs(argv) {
1649
1975
  ...(doctorArgs.length > 0 ? { doctorArgs } : {}),
1650
1976
  ...(launchMode !== undefined ? { launchMode } : {}),
1651
1977
  ...(acceptPlaybookDraft ? { acceptPlaybookDraft } : {}),
1978
+ ...(autoFlowSelection !== undefined ? { autoFlowSelection } : {}),
1652
1979
  ...(command === "web" ? { webNoOpen } : {}),
1653
1980
  ...(command === "web" && webHost !== undefined ? { webHost } : {}),
1654
1981
  };
@@ -1662,9 +1989,11 @@ function buildConfigFromArgs(args) {
1662
1989
  ...(args.autoFromPhase !== undefined ? { autoFromPhase: args.autoFromPhase } : {}),
1663
1990
  ...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
1664
1991
  dryRun: args.dry,
1992
+ dryRunFlow: args.dryRunFlow,
1665
1993
  verbose: args.verbose,
1666
1994
  ...(args.doctorArgs !== undefined ? { doctorArgs: args.doctorArgs } : {}),
1667
1995
  ...(args.acceptPlaybookDraft !== undefined ? { acceptPlaybookDraft: args.acceptPlaybookDraft } : {}),
1996
+ ...(args.autoFlowSelection !== undefined ? { autoFlowSelection: args.autoFlowSelection } : {}),
1668
1997
  });
1669
1998
  }
1670
1999
  async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRefresh = false, scopeName, installSignalCleanup = false) {
@@ -1724,6 +2053,10 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
1724
2053
  flows: interactiveFlowDefinitions(flowCatalog),
1725
2054
  getRunConfirmation: async (flowId) => {
1726
2055
  refreshScopeFromGit("git scope refresh before launch confirmation");
2056
+ const autoFlowSelection = autoFlowSelectionForFlowId(flowId);
2057
+ if (autoFlowSelection) {
2058
+ return await lookupInteractiveAutoFlowResume(autoFlowSelection, currentScope);
2059
+ }
1727
2060
  const flowEntry = findCatalogEntry(flowId, flowCatalog);
1728
2061
  if (!flowEntry) {
1729
2062
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
@@ -1737,6 +2070,19 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
1737
2070
  activeAbortController = abortController;
1738
2071
  activeFlowId = flowId;
1739
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
+ }
1740
2086
  const flowEntry = findCatalogEntry(flowId, flowCatalog);
1741
2087
  if (!flowEntry) {
1742
2088
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
@@ -1761,7 +2107,7 @@ async function runInteractiveWithSessionFactory(createSession, jiraRef, forceRef
1761
2107
  const previousScopeKey = currentScope.scopeKey;
1762
2108
  const baseConfig = buildInteractiveBaseConfig(flowId, currentScope);
1763
2109
  if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
1764
- const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form));
2110
+ const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form), launchMode);
1765
2111
  currentScope = nextScope;
1766
2112
  }
1767
2113
  else if (flowRequiresTaskScope(flowEntry) && !currentScope.jiraRef) {