agentweaver 0.1.16 → 0.1.17

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 (40) hide show
  1. package/README.md +50 -10
  2. package/dist/artifacts.js +73 -3
  3. package/dist/doctor/checks/executors.js +2 -2
  4. package/dist/flow-state.js +138 -1
  5. package/dist/index.js +175 -61
  6. package/dist/interactive/controller.js +56 -23
  7. package/dist/interactive/ink/index.js +22 -1
  8. package/dist/interactive/tree.js +2 -2
  9. package/dist/pipeline/auto-flow.js +9 -6
  10. package/dist/pipeline/context.js +6 -5
  11. package/dist/pipeline/declarative-flows.js +39 -20
  12. package/dist/pipeline/flow-catalog.js +36 -14
  13. package/dist/pipeline/flow-specs/auto-common.json +1 -0
  14. package/dist/pipeline/flow-specs/auto-golang.json +27 -1
  15. package/dist/pipeline/flow-specs/design-review/design-review-loop.json +13 -1
  16. package/dist/pipeline/flow-specs/plan.json +4 -2
  17. package/dist/pipeline/launch-profile-config.js +30 -18
  18. package/dist/pipeline/node-contract.js +1 -0
  19. package/dist/pipeline/node-registry.js +74 -5
  20. package/dist/pipeline/nodes/flow-run-node.js +188 -173
  21. package/dist/pipeline/nodes/llm-prompt-node.js +15 -33
  22. package/dist/pipeline/plugin-loader.js +389 -0
  23. package/dist/pipeline/plugin-types.js +1 -0
  24. package/dist/pipeline/registry.js +71 -4
  25. package/dist/pipeline/spec-compiler.js +1 -0
  26. package/dist/pipeline/spec-loader.js +14 -0
  27. package/dist/pipeline/spec-validator.js +6 -0
  28. package/dist/pipeline/value-resolver.js +2 -1
  29. package/dist/plugin-sdk.js +1 -0
  30. package/dist/runtime/artifact-registry.js +3 -0
  31. package/dist/runtime/execution-routing.js +25 -19
  32. package/dist/runtime/interactive-execution-routing.js +66 -57
  33. package/docs/example/.flows/examples/claude-example.json +50 -0
  34. package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
  35. package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
  36. package/docs/examples/.flows/claude-example.json +50 -0
  37. package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
  38. package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
  39. package/docs/plugin-sdk.md +731 -0
  40. package/package.json +6 -2
package/dist/index.js CHANGED
@@ -3,9 +3,9 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
- import { bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designReviewFile, designReviewJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, instantTaskInputJsonFile, latestArtifactIteration, nextArtifactIteration, readyToMergeFile, requireArtifacts, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
6
+ import { archiveActiveAttempt, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designReviewFile, designReviewJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, instantTaskInputJsonFile, latestArtifactIteration, nextArtifactIteration, readyToMergeFile, requireArtifacts, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
7
7
  import { FlowInterruptedError, TaskRunnerError } from "./errors.js";
8
- import { createFlowRunState, hasResumableFlowState, loadFlowRunState, prepareFlowStateForResume, resetFlowRunState, rewindFlowRunStateToPhase, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
8
+ import { createFlowRunState, classifyFlowLaunchAvailability, loadFlowRunState, prepareFlowStateForContinue, prepareFlowStateForResume, resetFlowRunState, rewindFlowRunStateToPhase, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
9
9
  import { requireJiraTaskFile } from "./jira.js";
10
10
  import { validateStructuredArtifacts } from "./structured-artifacts.js";
11
11
  import { AGENTWEAVER_REVIEW_BLOCKING_SEVERITIES_ENV, parseReviewSeverityCsv, resolveReviewBlockingSeveritiesFromEnv, } from "./review-severity.js";
@@ -15,6 +15,7 @@ import { createPipelineContext } from "./pipeline/context.js";
15
15
  import { collectFlowRoutingGroups, loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
16
16
  import { runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
17
17
  import { builtInCommandFlowFile, findCatalogEntry, flowRoutingGroups, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef, } from "./pipeline/flow-catalog.js";
18
+ import { createPipelineRegistryContext } from "./pipeline/plugin-loader.js";
18
19
  import { DEFAULT_LAUNCH_PROFILE, } from "./pipeline/launch-profile-config.js";
19
20
  import { withCanonicalReviewLoopParams } from "./pipeline/review-iteration.js";
20
21
  import { evaluateCondition, resolveValue } from "./pipeline/value-resolver.js";
@@ -149,6 +150,9 @@ Flags:
149
150
  --verbose Show live stdout/stderr of launched commands
150
151
  --scope Explicit workflow scope name for non-Jira runs except instant-task
151
152
  --prompt Extra prompt text appended to the base prompt
153
+ --resume Resume an interrupted run when valid
154
+ --continue Continue a terminated iterative run when valid
155
+ --restart Archive the active attempt and start a fresh run
152
156
  --blocking-severities Comma-separated severities that block merge and drive review-fix auto-selection
153
157
  --md-lang Language for markdown output files: en (English) or ru (Russian, default)
154
158
 
@@ -186,12 +190,12 @@ function packageVersion() {
186
190
  function normalizeAutoPhaseId(phaseId) {
187
191
  return phaseId.trim().toLowerCase().replaceAll("-", "_");
188
192
  }
189
- function autoPhaseIds() {
190
- return loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }).phases.map((phase) => phase.id);
193
+ async function autoPhaseIds() {
194
+ return (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" })).phases.map((phase) => phase.id);
191
195
  }
192
- function validateAutoPhaseId(phaseId) {
196
+ async function validateAutoPhaseId(phaseId) {
193
197
  const normalized = normalizeAutoPhaseId(phaseId);
194
- if (!autoPhaseIds().includes(normalized)) {
198
+ if (!(await autoPhaseIds()).includes(normalized)) {
195
199
  throw new TaskRunnerError(`Unknown auto-golang phase: ${phaseId}\nUse 'agentweaver auto-golang --help-phases' or '/help auto-golang' to list valid phases.`);
196
200
  }
197
201
  return normalized;
@@ -215,6 +219,17 @@ function buildFlowResumeDetails(state) {
215
219
  }
216
220
  return lines.join("\n");
217
221
  }
222
+ function buildFlowContinueDetails(state) {
223
+ const lines = [
224
+ "Continuable loop boundary found.",
225
+ `Updated: ${state.updatedAt}`,
226
+ ];
227
+ if (state.continuation?.stopPhaseId && state.continuation?.stopStepId) {
228
+ lines.push(`Stopped at: ${state.continuation.stopPhaseId}:${state.continuation.stopStepId}`);
229
+ }
230
+ lines.push("Continue will preserve existing artifacts and start the next iteration from active inputs.");
231
+ return lines.join("\n");
232
+ }
218
233
  function buildResolverContext(pipelineContext, flowParams, flowConstants, repeatVars, executionState) {
219
234
  return {
220
235
  flowParams,
@@ -288,7 +303,7 @@ function validateDeclarativePhaseResumeState(phase, phaseState, pipelineContext,
288
303
  }
289
304
  }
290
305
  }
291
- function validateDeclarativeFlowResumeState(flowEntry, config, state, executionRouting, runtime = runtimeServices) {
306
+ async function validateDeclarativeFlowResumeState(flowEntry, config, state, executionRouting, runtime = runtimeServices) {
292
307
  if (state.flowId === "auto-common") {
293
308
  const persistedPhaseIds = state.executionState.phases.map((p) => p.id);
294
309
  const hasLegacyPlanningGatePhases = persistedPhaseIds.some((id) => ["design_review", "verdict", "plan_revision", "design_review_repeat", "verdict_repeat"].includes(id));
@@ -308,7 +323,7 @@ function validateDeclarativeFlowResumeState(flowEntry, config, state, executionR
308
323
  if (flowRequiresTaskScope(flowEntry) && !config.jiraRef) {
309
324
  throw new TaskRunnerError("Resume is impossible because Jira context is missing for this flow state. Use restart.");
310
325
  }
311
- const pipelineContext = createPipelineContext({
326
+ const pipelineContext = await createPipelineContext({
312
327
  issueKey: config.taskKey,
313
328
  jiraRef: config.jiraRef,
314
329
  dryRun: config.dryRun,
@@ -338,51 +353,59 @@ function buildInteractiveBaseConfig(flowId, scope) {
338
353
  ...(flowId !== "instant-task" && scope.jiraRef ? { jiraRef: scope.jiraRef } : {}),
339
354
  });
340
355
  }
341
- function lookupInteractiveFlowResume(flowEntry, currentScope) {
356
+ async function lookupInteractiveFlowResume(flowEntry, currentScope) {
342
357
  const directState = loadFlowRunState(currentScope.scopeKey, flowEntry.id);
343
- if (directState && hasResumableFlowState(directState)) {
358
+ const availability = classifyFlowLaunchAvailability(directState);
359
+ if (directState && availability.resume.available) {
344
360
  try {
345
361
  const effectiveScope = scopeWithRestoredJiraContext(currentScope, directState);
346
362
  const baseConfig = buildInteractiveBaseConfig(flowEntry.id, effectiveScope);
347
363
  const config = buildRuntimeConfig(baseConfig, effectiveScope);
348
- validateDeclarativeFlowResumeState(flowEntry, config, directState, directState.executionRouting);
364
+ await validateDeclarativeFlowResumeState(flowEntry, config, directState, directState.executionRouting);
349
365
  return {
350
- resumeAvailable: true,
351
- hasExistingState: true,
366
+ ...availability,
352
367
  details: buildFlowResumeDetails(directState),
353
368
  };
354
369
  }
355
370
  catch (error) {
356
371
  return {
357
- resumeAvailable: false,
358
- hasExistingState: true,
372
+ ...availability,
373
+ resume: {
374
+ available: false,
375
+ reason: error.message,
376
+ },
359
377
  details: `Interrupted run found, but resume is unavailable.\n${error.message}`,
360
378
  };
361
379
  }
362
380
  }
381
+ if (directState && availability.continue.available) {
382
+ return {
383
+ ...availability,
384
+ details: buildFlowContinueDetails(directState),
385
+ };
386
+ }
363
387
  return {
364
- resumeAvailable: false,
365
- hasExistingState: Boolean(directState),
388
+ ...availability,
366
389
  };
367
390
  }
368
- function printAutoPhasesHelp() {
369
- const phaseLines = ["Available auto-golang phases:", "", ...autoPhaseIds()];
391
+ async function printAutoPhasesHelp() {
392
+ const phaseLines = ["Available auto-golang phases:", "", ...(await autoPhaseIds())];
370
393
  phaseLines.push("", "You can resume auto-golang from a phase with:", "agentweaver auto-golang --from <phase> <jira>", "or in interactive mode:", "/auto-golang --from <phase>");
371
394
  printPanel("Auto-Golang Phases", phaseLines.join("\n"), "magenta");
372
395
  }
373
- function autoCommonPhaseIds() {
374
- return loadDeclarativeFlow({ source: "built-in", fileName: "auto-common.json" }).phases.map((phase) => phase.id);
396
+ async function autoCommonPhaseIds() {
397
+ return (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-common.json" })).phases.map((phase) => phase.id);
375
398
  }
376
- function printAutoCommonPhasesHelp() {
377
- const phaseLines = ["Available auto-common phases:", "", ...autoCommonPhaseIds()];
399
+ async function printAutoCommonPhasesHelp() {
400
+ const phaseLines = ["Available auto-common phases:", "", ...(await autoCommonPhaseIds())];
378
401
  phaseLines.push("", "You can run auto-common with:", "agentweaver auto-common <jira>");
379
402
  printPanel("Auto-Common Phases", phaseLines.join("\n"), "magenta");
380
403
  }
381
- function autoSimplePhaseIds() {
382
- return loadDeclarativeFlow({ source: "built-in", fileName: "auto-simple.json" }).phases.map((phase) => phase.id);
404
+ async function autoSimplePhaseIds() {
405
+ return (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-simple.json" })).phases.map((phase) => phase.id);
383
406
  }
384
- function printAutoSimplePhasesHelp() {
385
- const phaseLines = ["Available auto-simple phases:", "", ...autoSimplePhaseIds()];
407
+ async function printAutoSimplePhasesHelp() {
408
+ const phaseLines = ["Available auto-simple phases:", "", ...(await autoSimplePhaseIds())];
386
409
  phaseLines.push("", "You can run auto-simple with:", "agentweaver auto-simple <jira>");
387
410
  printPanel("Auto-Simple Phases", phaseLines.join("\n"), "magenta");
388
411
  }
@@ -400,7 +423,7 @@ function buildBaseConfig(command, options = {}) {
400
423
  reviewFixPoints: options.reviewFixPoints ?? null,
401
424
  reviewBlockingSeverities: options.reviewBlockingSeverities ?? resolveReviewBlockingSeveritiesFromEnv(),
402
425
  extraPrompt: options.extraPrompt ?? null,
403
- autoFromPhase: options.autoFromPhase ? validateAutoPhaseId(options.autoFromPhase) : null,
426
+ autoFromPhase: options.autoFromPhase ?? null,
404
427
  mdLang: options.mdLang ?? null,
405
428
  dryRun: options.dryRun ?? false,
406
429
  verbose: options.verbose ?? false,
@@ -498,29 +521,42 @@ function routingForPrerequisites(launchProfile, executionRouting) {
498
521
  function flowSpecFileForPrerequisiteChecks(command) {
499
522
  return isBuiltInCommandFlowId(command) ? builtInCommandFlowFile(command) : null;
500
523
  }
501
- function commandRoutingGroupsForPrerequisiteChecks(command, cwd) {
524
+ async function commandRoutingGroupsForPrerequisiteChecks(command, cwd) {
502
525
  const fileName = flowSpecFileForPrerequisiteChecks(command);
503
526
  if (!fileName) {
504
527
  return [];
505
528
  }
506
- return collectFlowRoutingGroups(loadDeclarativeFlow({ source: "built-in", fileName }), cwd);
529
+ return collectFlowRoutingGroups(await loadDeclarativeFlow({ source: "built-in", fileName }), cwd);
507
530
  }
508
- function resolveExecutorPrerequisite(executor) {
531
+ function resolveExecutorPrerequisite(executor, registryContext) {
509
532
  if (executor === "codex") {
510
533
  resolveCmd("codex", "CODEX_BIN");
511
534
  return;
512
535
  }
513
- resolveCmd("opencode", "OPENCODE_BIN");
536
+ if (executor === "opencode") {
537
+ resolveCmd("opencode", "OPENCODE_BIN");
538
+ return;
539
+ }
540
+ const definition = registryContext.executors.get(executor);
541
+ const config = definition.defaultConfig;
542
+ if (config
543
+ && typeof config === "object"
544
+ && !Array.isArray(config)
545
+ && typeof config.defaultCommand === "string"
546
+ && typeof config.commandEnvVar === "string") {
547
+ resolveCmd(config.defaultCommand, config.commandEnvVar);
548
+ }
514
549
  }
515
- function checkPrerequisites(config, launchProfile, executionRouting) {
550
+ async function checkPrerequisites(config, launchProfile, executionRouting) {
551
+ const registryContext = await createPipelineRegistryContext(process.cwd());
516
552
  const routing = routingForPrerequisites(launchProfile, executionRouting);
517
- const groups = commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
553
+ const groups = await commandRoutingGroupsForPrerequisiteChecks(config.command, process.cwd());
518
554
  for (const executor of executorsForRoutingGroups(routing, groups)) {
519
- resolveExecutorPrerequisite(executor);
555
+ resolveExecutorPrerequisite(executor, registryContext);
520
556
  }
521
557
  }
522
- function checkAutoPrerequisites(config, launchProfile, executionRouting) {
523
- checkPrerequisites(config, launchProfile, executionRouting);
558
+ async function checkAutoPrerequisites(config, launchProfile, executionRouting) {
559
+ await checkPrerequisites(config, launchProfile, executionRouting);
524
560
  }
525
561
  function autoFlowParams(config, forceRefreshSummary = false) {
526
562
  return {
@@ -616,7 +652,7 @@ function interactiveFlowDefinition(entry) {
616
652
  description: flow.description ?? "No description available for this flow.",
617
653
  source: entry.source,
618
654
  treePath: [...entry.treePath],
619
- ...(entry.source === "project-local" ? { sourcePath: entry.absolutePath } : {}),
655
+ ...(entry.source !== "built-in" ? { sourcePath: entry.absolutePath } : {}),
620
656
  phases: flow.phases.map((phase) => ({
621
657
  id: phase.id,
622
658
  repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
@@ -666,7 +702,7 @@ function findCurrentFlowExecutionStep(state) {
666
702
  return null;
667
703
  }
668
704
  async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, overrides = {}, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
669
- const context = createPipelineContext({
705
+ const context = await createPipelineContext({
670
706
  issueKey: config.taskKey,
671
707
  jiraRef: config.jiraRef,
672
708
  dryRun: config.dryRun,
@@ -677,7 +713,7 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
677
713
  requestUserInput,
678
714
  ...(overrides.executionRouting ? { executionRouting: overrides.executionRouting } : {}),
679
715
  });
680
- const flow = loadDeclarativeFlow(flowRef);
716
+ const flow = await loadDeclarativeFlow(flowRef);
681
717
  const initialExecutionState = {
682
718
  flowKind: flow.kind,
683
719
  flowVersion: flow.version,
@@ -685,9 +721,12 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
685
721
  terminationOutcome: "success",
686
722
  phases: [],
687
723
  };
688
- let persistedState = launchMode === "resume" ? loadFlowRunState(config.scope.scopeKey, flowId) : null;
724
+ const existingStateForRestart = launchMode === "restart" ? loadFlowRunState(config.scope.scopeKey, flowId) : null;
725
+ let persistedState = launchMode === "resume" || launchMode === "continue"
726
+ ? loadFlowRunState(config.scope.scopeKey, flowId)
727
+ : null;
689
728
  if (persistedState && launchMode === "resume") {
690
- validateDeclarativeFlowResumeState({
729
+ await validateDeclarativeFlowResumeState({
691
730
  id: flowId,
692
731
  source: flow.source,
693
732
  fileName: flow.fileName,
@@ -700,7 +739,13 @@ async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, over
700
739
  } }) : undefined), runtime);
701
740
  persistedState = prepareFlowStateForResume(persistedState);
702
741
  }
742
+ else if (persistedState && launchMode === "continue") {
743
+ persistedState = prepareFlowStateForContinue(persistedState, flow.phases);
744
+ }
703
745
  else if (launchMode === "restart") {
746
+ if (existingStateForRestart) {
747
+ archiveActiveAttempt(config.scope.scopeKey);
748
+ }
704
749
  resetFlowRunState(config.scope.scopeKey, flowId);
705
750
  }
706
751
  const executionState = persistedState?.executionState ?? initialExecutionState;
@@ -772,7 +817,7 @@ async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, overri
772
817
  ...defaultDeclarativeFlowParams(config, false, overrides),
773
818
  ...flowParams,
774
819
  };
775
- await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, withCanonicalReviewLoopParams(loadDeclarativeFlow({ source: "built-in", fileName }).kind, mergedFlowParams), overrides, requestUserInput, setSummary, launchMode, runtime);
820
+ await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, withCanonicalReviewLoopParams((await loadDeclarativeFlow({ source: "built-in", fileName })).kind, mergedFlowParams), overrides, requestUserInput, setSummary, launchMode, runtime);
776
821
  }
777
822
  function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overrides = {}) {
778
823
  const iteration = nextReviewIterationForTask(config.taskKey);
@@ -808,6 +853,7 @@ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overr
808
853
  executionRouting,
809
854
  iteration,
810
855
  baseIteration: iteration,
856
+ designReviewBaseIteration: nextDesignReviewIterationForTask(config.taskKey),
811
857
  latestIteration,
812
858
  taskContextIteration: latestTaskContext ?? nextArtifactIteration(config.taskKey, "task-context", "json"),
813
859
  taskSummaryIteration: nextArtifactIteration(config.taskKey, "task"),
@@ -818,6 +864,60 @@ function defaultDeclarativeFlowParams(config, forceRefreshSummary = false, overr
818
864
  forceRefresh: forceRefreshSummary,
819
865
  };
820
866
  }
867
+ function countAvailableNonRestartActions(availability) {
868
+ return Number(availability.resume.available) + Number(availability.continue.available);
869
+ }
870
+ async function chooseLaunchMode(flowId, scopeKey, explicitLaunchMode, requestUserInput) {
871
+ const state = loadFlowRunState(scopeKey, flowId);
872
+ const availability = classifyFlowLaunchAvailability(state);
873
+ if (explicitLaunchMode) {
874
+ const selectedAvailability = availability[explicitLaunchMode];
875
+ if (!selectedAvailability.available) {
876
+ throw new TaskRunnerError(`${explicitLaunchMode.charAt(0).toUpperCase()}${explicitLaunchMode.slice(1)} is not available for '${flowId}'. ${selectedAvailability.reason}`);
877
+ }
878
+ return explicitLaunchMode;
879
+ }
880
+ if (!availability.hasExistingState) {
881
+ return "restart";
882
+ }
883
+ const availableNonRestart = countAvailableNonRestartActions(availability);
884
+ if (availableNonRestart === 0) {
885
+ return "restart";
886
+ }
887
+ const interactive = requestUserInput !== requestUserInputInTerminal || (process.stdin.isTTY && process.stdout.isTTY);
888
+ if (!interactive) {
889
+ throw new TaskRunnerError(`Multiple actions are valid for '${flowId}'. Re-run with one of: --resume, --continue, --restart.`);
890
+ }
891
+ const result = await requestUserInput({
892
+ formId: `launch-mode-${flowId}`,
893
+ title: "Launch Action",
894
+ description: `Select how to start '${flowId}'.`,
895
+ submitLabel: "Start",
896
+ fields: [
897
+ {
898
+ id: "launchMode",
899
+ type: "single-select",
900
+ label: "Action",
901
+ required: true,
902
+ default: availability.continue.available ? "continue" : availability.resume.available ? "resume" : "restart",
903
+ options: [
904
+ ...(availability.resume.available
905
+ ? [{ value: "resume", label: "Resume", description: availability.resume.reason }]
906
+ : []),
907
+ ...(availability.continue.available
908
+ ? [{ value: "continue", label: "Continue", description: availability.continue.reason }]
909
+ : []),
910
+ { value: "restart", label: "Restart", description: availability.restart.reason },
911
+ ],
912
+ },
913
+ ],
914
+ });
915
+ const selected = result.values.launchMode;
916
+ if (selected !== "resume" && selected !== "continue" && selected !== "restart") {
917
+ throw new TaskRunnerError(`Invalid launch action selected for '${flowId}'.`);
918
+ }
919
+ return selected;
920
+ }
821
921
  const TASK_SCOPE_PARAM_REFS = new Set(["params.jiraApiUrl", "params.jiraBrowseUrl", "params.jiraTaskFile"]);
822
922
  function valueReferencesTaskScopeParams(value) {
823
923
  if (Array.isArray(value)) {
@@ -840,7 +940,7 @@ function flowRequiresTaskScope(entry) {
840
940
  return valueReferencesTaskScopeParams(entry.flow.phases);
841
941
  }
842
942
  async function summarizeBuildFailure(output) {
843
- return summarizeBuildFailureViaPipeline(createPipelineContext({
943
+ return summarizeBuildFailureViaPipeline(await createPipelineContext({
844
944
  issueKey: "build-failure-summary",
845
945
  jiraRef: "build-failure-summary",
846
946
  dryRun: false,
@@ -855,7 +955,7 @@ function requireJiraConfig(config) {
855
955
  throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
856
956
  }
857
957
  }
858
- async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", launchProfile, executionRouting, selectedRoutingPreset, runtime = runtimeServices) {
958
+ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, explicitLaunchMode, launchProfile, executionRouting, selectedRoutingPreset, runtime = runtimeServices) {
859
959
  if (baseConfig.command === "doctor") {
860
960
  const exitCode = await runDoctorCommand(baseConfig.doctorArgs ?? []);
861
961
  return exitCode === 0;
@@ -870,8 +970,11 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
870
970
  : launchProfile
871
971
  ? { launchProfile }
872
972
  : {};
973
+ const launchMode = config.command === "auto-status" || config.command === "auto-reset"
974
+ ? "restart"
975
+ : await chooseLaunchMode(config.command, config.scope.scopeKey, explicitLaunchMode, requestUserInput);
873
976
  if (config.command === "instant-task") {
874
- checkPrerequisites(config, launchProfile, executionRouting);
977
+ await checkPrerequisites(config, launchProfile, executionRouting);
875
978
  const hasPersistedInstantTaskState = loadFlowRunState(config.scope.scopeKey, "instant-task") !== null;
876
979
  const repromptInstantTaskInput = launchMode === "restart"
877
980
  && hasPersistedInstantTaskState
@@ -900,7 +1003,8 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
900
1003
  let effectiveLaunchProfile = launchProfile;
901
1004
  let effectiveExecutionRouting = executionRouting;
902
1005
  if (config.autoFromPhase) {
903
- const flow = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" });
1006
+ config.autoFromPhase = await validateAutoPhaseId(config.autoFromPhase);
1007
+ const flow = await loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" });
904
1008
  const persistedState = loadFlowRunState(config.scope.scopeKey, "auto-golang");
905
1009
  if (!persistedState) {
906
1010
  throw new TaskRunnerError(`Cannot restart auto-golang from phase '${config.autoFromPhase}' because persisted flow state was not found.`);
@@ -912,7 +1016,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
912
1016
  effectiveExecutionRouting ??= persistedState.executionRouting;
913
1017
  printPanel("Auto-Golang Resume", `Auto-golang pipeline will continue from phase: ${config.autoFromPhase}`, "yellow");
914
1018
  }
915
- checkAutoPrerequisites(config, effectiveLaunchProfile, effectiveExecutionRouting);
1019
+ await checkAutoPrerequisites(config, effectiveLaunchProfile, effectiveExecutionRouting);
916
1020
  await runDeclarativeFlowBySpecFile("auto-golang.json", config, autoFlowParams(config, forceRefreshSummary), effectiveExecutionRouting
917
1021
  ? {
918
1022
  launchProfile: effectiveExecutionRouting.defaultRoute,
@@ -926,7 +1030,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
926
1030
  }
927
1031
  if (config.command === "auto-common") {
928
1032
  requireJiraConfig(config);
929
- checkAutoPrerequisites(config, launchProfile, executionRouting);
1033
+ await checkAutoPrerequisites(config, launchProfile, executionRouting);
930
1034
  process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
931
1035
  process.env.JIRA_API_URL = config.jiraApiUrl;
932
1036
  process.env.JIRA_TASK_FILE = config.jiraTaskFile;
@@ -935,7 +1039,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
935
1039
  }
936
1040
  if (config.command === "auto-simple") {
937
1041
  requireJiraConfig(config);
938
- checkAutoPrerequisites(config, launchProfile, executionRouting);
1042
+ await checkAutoPrerequisites(config, launchProfile, executionRouting);
939
1043
  process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl;
940
1044
  process.env.JIRA_API_URL = config.jiraApiUrl;
941
1045
  process.env.JIRA_TASK_FILE = config.jiraTaskFile;
@@ -949,7 +1053,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
949
1053
  return false;
950
1054
  }
951
1055
  const currentStep = findCurrentFlowExecutionStep(state) ?? state.currentStep ?? "-";
952
- const phaseOrder = loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" }).phases;
1056
+ const phaseOrder = (await loadDeclarativeFlow({ source: "built-in", fileName: "auto-golang.json" })).phases;
953
1057
  const lines = [
954
1058
  `Issue: ${config.taskKey}`,
955
1059
  `Status: ${state.status}`,
@@ -986,7 +1090,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
986
1090
  printPanel("Auto-Golang Reset", removed ? `State file ${flowStateFile(config.scope.scopeKey, "auto-golang")} removed.` : "No flow state file found.", "yellow");
987
1091
  return false;
988
1092
  }
989
- checkPrerequisites(config, launchProfile, executionRouting);
1093
+ await checkPrerequisites(config, launchProfile, executionRouting);
990
1094
  if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
991
1095
  process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl ?? "";
992
1096
  process.env.JIRA_API_URL = config.jiraApiUrl ?? "";
@@ -1290,7 +1394,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
1290
1394
  }
1291
1395
  throw new TaskRunnerError(`Unsupported command: ${config.command}`);
1292
1396
  }
1293
- function parseCliArgs(argv) {
1397
+ async function parseCliArgs(argv) {
1294
1398
  if (argv.includes("--version") || argv.includes("-v")) {
1295
1399
  process.stdout.write(`${packageVersion()}\n`);
1296
1400
  process.exit(0);
@@ -1317,6 +1421,7 @@ function parseCliArgs(argv) {
1317
1421
  let helpPhases = false;
1318
1422
  let jiraRef;
1319
1423
  let mdLang;
1424
+ let launchMode;
1320
1425
  const doctorArgs = [];
1321
1426
  for (let index = 1; index < argv.length; index += 1) {
1322
1427
  const token = argv[index] ?? "";
@@ -1332,6 +1437,14 @@ function parseCliArgs(argv) {
1332
1437
  helpPhases = true;
1333
1438
  continue;
1334
1439
  }
1440
+ if (token === "--resume" || token === "--continue" || token === "--restart") {
1441
+ if (launchMode) {
1442
+ process.stderr.write("Error: --resume, --continue, and --restart are mutually exclusive.\n");
1443
+ process.exit(1);
1444
+ }
1445
+ launchMode = token.slice(2);
1446
+ continue;
1447
+ }
1335
1448
  if (token === "--prompt") {
1336
1449
  prompt = argv[index + 1];
1337
1450
  index += 1;
@@ -1387,15 +1500,15 @@ function parseCliArgs(argv) {
1387
1500
  }
1388
1501
  }
1389
1502
  if (command === "auto-golang" && helpPhases) {
1390
- printAutoPhasesHelp();
1503
+ await printAutoPhasesHelp();
1391
1504
  process.exit(0);
1392
1505
  }
1393
1506
  if (command === "auto-common" && helpPhases) {
1394
- printAutoCommonPhasesHelp();
1507
+ await printAutoCommonPhasesHelp();
1395
1508
  process.exit(0);
1396
1509
  }
1397
1510
  if (command === "auto-simple" && helpPhases) {
1398
- printAutoSimplePhasesHelp();
1511
+ await printAutoSimplePhasesHelp();
1399
1512
  process.exit(0);
1400
1513
  }
1401
1514
  return {
@@ -1410,6 +1523,7 @@ function parseCliArgs(argv) {
1410
1523
  ...(autoFromPhase !== undefined ? { autoFromPhase } : {}),
1411
1524
  ...(mdLang !== undefined ? { mdLang } : {}),
1412
1525
  ...(doctorArgs.length > 0 ? { doctorArgs } : {}),
1526
+ ...(launchMode !== undefined ? { launchMode } : {}),
1413
1527
  };
1414
1528
  }
1415
1529
  function buildConfigFromArgs(args) {
@@ -1428,7 +1542,7 @@ function buildConfigFromArgs(args) {
1428
1542
  async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1429
1543
  let currentScope = resolveProjectScope(scopeName, jiraRef);
1430
1544
  const gitBranchName = detectGitBranchName();
1431
- const flowCatalog = loadInteractiveFlowCatalog(process.cwd());
1545
+ const flowCatalog = await loadInteractiveFlowCatalog(process.cwd());
1432
1546
  let activeAbortController = null;
1433
1547
  let activeFlowId = null;
1434
1548
  let exiting = false;
@@ -1445,7 +1559,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1445
1559
  if (!flowEntry) {
1446
1560
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
1447
1561
  }
1448
- const resumeLookup = lookupInteractiveFlowResume(flowEntry, currentScope);
1562
+ const resumeLookup = await lookupInteractiveFlowResume(flowEntry, currentScope);
1449
1563
  return resumeLookup;
1450
1564
  },
1451
1565
  onRun: async (flowId, launchMode) => {
@@ -1457,7 +1571,7 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
1457
1571
  if (!flowEntry) {
1458
1572
  throw new TaskRunnerError(`Unknown flow: ${flowId}`);
1459
1573
  }
1460
- const routingGroups = flowRoutingGroups(flowEntry, process.cwd());
1574
+ const routingGroups = await flowRoutingGroups(flowEntry, process.cwd());
1461
1575
  const resumeState = launchMode === "resume" ? loadFlowRunState(currentScope.scopeKey, flowId) : null;
1462
1576
  if (resumeState) {
1463
1577
  currentScope = scopeWithRestoredJiraContext(currentScope, resumeState);
@@ -1579,8 +1693,8 @@ export async function main(argv = process.argv.slice(2)) {
1579
1693
  if (args.length === 1 && !args[0]?.startsWith("-") && !COMMANDS.includes(args[0])) {
1580
1694
  return await runInteractive(args[0] ?? "", forceRefresh);
1581
1695
  }
1582
- const parsedArgs = parseCliArgs(args);
1583
- const commandCompleted = await executeCommand(buildConfigFromArgs(parsedArgs));
1696
+ const parsedArgs = await parseCliArgs(args);
1697
+ const commandCompleted = await executeCommand(buildConfigFromArgs(parsedArgs), true, requestUserInputInTerminal, undefined, undefined, false, parsedArgs.launchMode);
1584
1698
  if (parsedArgs.command === "doctor") {
1585
1699
  return commandCompleted ? 0 : 1;
1586
1700
  }