agentweaver 0.1.8 → 0.1.10
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.
- package/README.md +71 -25
- package/dist/artifacts.js +25 -1
- package/dist/errors.js +7 -0
- package/dist/executors/configs/fetch-gitlab-diff-config.js +3 -0
- package/dist/executors/configs/opencode-config.js +6 -0
- package/dist/executors/fetch-gitlab-diff-executor.js +26 -0
- package/dist/executors/jira-fetch-executor.js +8 -2
- package/dist/executors/opencode-executor.js +35 -0
- package/dist/gitlab.js +199 -5
- package/dist/index.js +215 -144
- package/dist/interactive-ui.js +363 -37
- package/dist/jira.js +116 -14
- package/dist/pipeline/auto-flow.js +1 -1
- package/dist/pipeline/declarative-flows.js +44 -6
- package/dist/pipeline/flow-catalog.js +73 -0
- package/dist/pipeline/flow-specs/auto.json +183 -1
- package/dist/pipeline/flow-specs/gitlab-diff-review.json +226 -0
- package/dist/pipeline/flow-specs/gitlab-review.json +1 -31
- package/dist/pipeline/flow-specs/opencode/auto-opencode.json +1365 -0
- package/dist/pipeline/flow-specs/opencode/bugz/bug-analyze-opencode.json +382 -0
- package/dist/pipeline/flow-specs/opencode/bugz/bug-fix-opencode.json +56 -0
- package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-diff-review-opencode.json +308 -0
- package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-review-opencode.json +437 -0
- package/dist/pipeline/flow-specs/opencode/gitlab/mr-description-opencode.json +117 -0
- package/dist/pipeline/flow-specs/opencode/go/run-go-linter-loop-opencode.json +321 -0
- package/dist/pipeline/flow-specs/opencode/go/run-go-tests-loop-opencode.json +321 -0
- package/dist/pipeline/flow-specs/opencode/implement-opencode.json +64 -0
- package/dist/pipeline/flow-specs/opencode/plan-opencode.json +603 -0
- package/dist/pipeline/flow-specs/opencode/review/review-fix-opencode.json +209 -0
- package/dist/pipeline/flow-specs/opencode/review/review-opencode.json +452 -0
- package/dist/pipeline/flow-specs/opencode/task-describe-opencode.json +148 -0
- package/dist/pipeline/flow-specs/plan.json +183 -1
- package/dist/pipeline/node-registry.js +80 -8
- package/dist/pipeline/nodes/fetch-gitlab-diff-node.js +34 -0
- package/dist/pipeline/nodes/flow-run-node.js +2 -2
- package/dist/pipeline/nodes/jira-fetch-node.js +26 -2
- package/dist/pipeline/nodes/opencode-prompt-node.js +32 -0
- package/dist/pipeline/nodes/planning-questions-form-node.js +69 -0
- package/dist/pipeline/nodes/user-input-node.js +9 -1
- package/dist/pipeline/prompt-registry.js +3 -1
- package/dist/pipeline/registry.js +10 -0
- package/dist/pipeline/spec-loader.js +48 -3
- package/dist/pipeline/spec-types.js +43 -1
- package/dist/pipeline/spec-validator.js +53 -7
- package/dist/pipeline/value-resolver.js +15 -1
- package/dist/prompts.js +47 -12
- package/dist/runtime/process-runner.js +45 -1
- package/dist/scope.js +24 -32
- package/dist/structured-artifact-schemas.json +154 -1
- package/dist/structured-artifacts.js +2 -0
- package/dist/user-input.js +7 -0
- package/package.json +1 -1
- package/dist/pipeline/flow-specs/preflight.json +0 -206
- package/dist/pipeline/flow-specs/run-linter-loop.json +0 -155
- package/dist/pipeline/flow-specs/run-tests-loop.json +0 -155
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "no
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { REVIEW_FILE_RE, REVIEW_REPLY_FILE_RE, autoStateFile, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewReplyJsonFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, taskSummaryFile, } from "./artifacts.js";
|
|
7
|
-
import { TaskRunnerError } from "./errors.js";
|
|
6
|
+
import { REVIEW_FILE_RE, REVIEW_REPLY_FILE_RE, autoStateFile, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewFile, reviewReplyJsonFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, taskSummaryFile, } from "./artifacts.js";
|
|
7
|
+
import { FlowInterruptedError, TaskRunnerError } from "./errors.js";
|
|
8
8
|
import { createFlowRunState, hasResumableFlowState, loadFlowRunState, prepareFlowStateForResume, resetFlowRunState, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
|
|
9
9
|
import { requireJiraTaskFile } from "./jira.js";
|
|
10
10
|
import { validateStructuredArtifacts } from "./structured-artifacts.js";
|
|
@@ -13,16 +13,18 @@ import { createPipelineContext } from "./pipeline/context.js";
|
|
|
13
13
|
import { loadAutoFlow } from "./pipeline/auto-flow.js";
|
|
14
14
|
import { loadDeclarativeFlow } from "./pipeline/declarative-flows.js";
|
|
15
15
|
import { findPhaseById, runExpandedPhase } from "./pipeline/declarative-flow-runner.js";
|
|
16
|
+
import { findCatalogEntry, isBuiltInCommandFlowId, loadInteractiveFlowCatalog, toDeclarativeFlowRef } from "./pipeline/flow-catalog.js";
|
|
16
17
|
import { resolveCmd, resolveDockerComposeCmd } from "./runtime/command-resolution.js";
|
|
17
18
|
import { agentweaverHome, defaultDockerComposeFile, dockerRuntimeEnv } from "./runtime/docker-runtime.js";
|
|
18
19
|
import { runCommand } from "./runtime/process-runner.js";
|
|
19
20
|
import { InteractiveUi } from "./interactive-ui.js";
|
|
20
21
|
import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
|
|
21
22
|
import { requestUserInputInTerminal } from "./user-input.js";
|
|
22
|
-
import { detectGitBranchName,
|
|
23
|
+
import { attachJiraContext, detectGitBranchName, requestJiraContext, resolveProjectScope, } from "./scope.js";
|
|
23
24
|
const COMMANDS = [
|
|
24
25
|
"bug-analyze",
|
|
25
26
|
"bug-fix",
|
|
27
|
+
"gitlab-diff-review",
|
|
26
28
|
"gitlab-review",
|
|
27
29
|
"mr-description",
|
|
28
30
|
"plan",
|
|
@@ -39,12 +41,15 @@ const COMMANDS = [
|
|
|
39
41
|
const AUTO_STATE_SCHEMA_VERSION = 3;
|
|
40
42
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
41
43
|
const PACKAGE_ROOT = path.resolve(MODULE_DIR, "..");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
44
|
+
function createRuntimeServices(signal) {
|
|
45
|
+
return {
|
|
46
|
+
resolveCmd,
|
|
47
|
+
resolveDockerComposeCmd,
|
|
48
|
+
dockerRuntimeEnv: () => dockerRuntimeEnv(PACKAGE_ROOT),
|
|
49
|
+
runCommand: (argv, options = {}) => runCommand(argv, { ...options, ...(signal ? { signal } : {}) }),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const runtimeServices = createRuntimeServices();
|
|
48
53
|
function buildFailureOutputPreview(output) {
|
|
49
54
|
const normalized = stripAnsi(output).replace(/\r\n/g, "\n").trim();
|
|
50
55
|
if (!normalized) {
|
|
@@ -81,7 +86,8 @@ function usage() {
|
|
|
81
86
|
agentweaver
|
|
82
87
|
agentweaver <jira-browse-url|jira-issue-key>
|
|
83
88
|
agentweaver --force <jira-browse-url|jira-issue-key>
|
|
84
|
-
agentweaver gitlab-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
|
|
89
|
+
agentweaver gitlab-diff-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
|
|
90
|
+
agentweaver gitlab-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
|
|
85
91
|
agentweaver bug-analyze [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
86
92
|
agentweaver bug-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
87
93
|
agentweaver mr-description [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
|
|
@@ -100,8 +106,8 @@ function usage() {
|
|
|
100
106
|
|
|
101
107
|
Interactive Mode:
|
|
102
108
|
When started without a command, the script opens an interactive UI.
|
|
103
|
-
If a Jira task is provided, interactive mode starts in
|
|
104
|
-
Use Up/Down to
|
|
109
|
+
If a Jira task is provided, interactive mode starts in the current project scope with Jira context attached.
|
|
110
|
+
Use Up/Down to move in the flow tree, Left/Right to collapse or expand folders, Enter to toggle a folder or run a flow, h for help, q to exit.
|
|
105
111
|
|
|
106
112
|
Flags:
|
|
107
113
|
--version Show package version
|
|
@@ -121,12 +127,15 @@ Optional environment variables:
|
|
|
121
127
|
DOCKER_COMPOSE_BIN
|
|
122
128
|
CODEX_BIN
|
|
123
129
|
CODEX_MODEL
|
|
130
|
+
OPENCODE_BIN
|
|
131
|
+
OPENCODE_MODEL
|
|
124
132
|
CLAUDE_BIN
|
|
125
133
|
CLAUDE_MODEL
|
|
126
134
|
|
|
127
135
|
Notes:
|
|
128
136
|
- Task-only flows will ask for Jira task via user-input when it is not passed as an argument.
|
|
129
|
-
-
|
|
137
|
+
- All flow state and artifacts are stored in the current project scope by default.
|
|
138
|
+
- gitlab-review and gitlab-diff-review ask for GitLab merge request URL via user-input.`;
|
|
130
139
|
}
|
|
131
140
|
function packageVersion() {
|
|
132
141
|
const packageJsonPath = path.join(PACKAGE_ROOT, "package.json");
|
|
@@ -334,6 +343,20 @@ function buildFlowResumeDetails(state) {
|
|
|
334
343
|
}
|
|
335
344
|
return lines.join("\n");
|
|
336
345
|
}
|
|
346
|
+
function lookupInteractiveFlowResume(flowEntry, currentScope) {
|
|
347
|
+
const directState = loadFlowRunState(currentScope.scopeKey, flowEntry.id);
|
|
348
|
+
if (directState && hasResumableFlowState(directState)) {
|
|
349
|
+
return {
|
|
350
|
+
resumeAvailable: true,
|
|
351
|
+
hasExistingState: true,
|
|
352
|
+
details: buildFlowResumeDetails(directState),
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
return {
|
|
356
|
+
resumeAvailable: false,
|
|
357
|
+
hasExistingState: Boolean(directState),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
337
360
|
function printAutoPhasesHelp() {
|
|
338
361
|
const phaseLines = ["Available auto phases:", "", ...autoPhaseIds()];
|
|
339
362
|
phaseLines.push("", "You can resume auto from a phase with:", "agentweaver auto --from <phase> <jira>", "or in interactive mode:", "/auto --from <phase>");
|
|
@@ -423,7 +446,6 @@ function commandRequiresTask(command) {
|
|
|
423
446
|
return (command === "plan" ||
|
|
424
447
|
command === "bug-analyze" ||
|
|
425
448
|
command === "bug-fix" ||
|
|
426
|
-
command === "gitlab-review" ||
|
|
427
449
|
command === "mr-description" ||
|
|
428
450
|
command === "task-describe" ||
|
|
429
451
|
command === "auto" ||
|
|
@@ -431,7 +453,9 @@ function commandRequiresTask(command) {
|
|
|
431
453
|
command === "auto-reset");
|
|
432
454
|
}
|
|
433
455
|
function commandSupportsProjectScope(command) {
|
|
434
|
-
return (command === "
|
|
456
|
+
return (command === "gitlab-diff-review" ||
|
|
457
|
+
command === "gitlab-review" ||
|
|
458
|
+
command === "implement" ||
|
|
435
459
|
command === "review" ||
|
|
436
460
|
command === "review-fix" ||
|
|
437
461
|
command === "run-go-tests-loop" ||
|
|
@@ -439,12 +463,12 @@ function commandSupportsProjectScope(command) {
|
|
|
439
463
|
}
|
|
440
464
|
async function resolveScopeForCommand(config, requestUserInput) {
|
|
441
465
|
if (config.jiraRef?.trim()) {
|
|
442
|
-
return
|
|
466
|
+
return resolveProjectScope(config.scopeName, config.jiraRef);
|
|
443
467
|
}
|
|
444
468
|
if (commandRequiresTask(config.command)) {
|
|
445
469
|
try {
|
|
446
|
-
const
|
|
447
|
-
return config.scopeName
|
|
470
|
+
const jiraContext = await requestJiraContext(requestUserInput);
|
|
471
|
+
return resolveProjectScope(config.scopeName, jiraContext.jiraRef);
|
|
448
472
|
}
|
|
449
473
|
catch (error) {
|
|
450
474
|
if (error instanceof TaskRunnerError && error.message.includes("no TTY is available")) {
|
|
@@ -461,22 +485,14 @@ async function resolveScopeForCommand(config, requestUserInput) {
|
|
|
461
485
|
}
|
|
462
486
|
function buildRuntimeConfig(baseConfig, scope) {
|
|
463
487
|
ensureScopeWorkspaceDir(scope.scopeKey);
|
|
464
|
-
if (scope.scopeType === "task") {
|
|
465
|
-
return {
|
|
466
|
-
...baseConfig,
|
|
467
|
-
scope,
|
|
468
|
-
taskKey: scope.scopeKey,
|
|
469
|
-
jiraRef: scope.jiraRef,
|
|
470
|
-
jiraBrowseUrl: scope.jiraBrowseUrl,
|
|
471
|
-
jiraApiUrl: scope.jiraApiUrl,
|
|
472
|
-
jiraTaskFile: scope.jiraTaskFile,
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
488
|
return {
|
|
476
489
|
...baseConfig,
|
|
477
490
|
scope,
|
|
478
491
|
taskKey: scope.scopeKey,
|
|
479
|
-
jiraRef: scope.scopeKey,
|
|
492
|
+
jiraRef: scope.jiraRef ?? scope.scopeKey,
|
|
493
|
+
...(scope.jiraBrowseUrl ? { jiraBrowseUrl: scope.jiraBrowseUrl } : {}),
|
|
494
|
+
...(scope.jiraApiUrl ? { jiraApiUrl: scope.jiraApiUrl } : {}),
|
|
495
|
+
...(scope.jiraTaskFile ? { jiraTaskFile: scope.jiraTaskFile } : {}),
|
|
480
496
|
};
|
|
481
497
|
}
|
|
482
498
|
function checkPrerequisites(config) {
|
|
@@ -490,7 +506,7 @@ function checkPrerequisites(config) {
|
|
|
490
506
|
config.command === "run-go-linter-loop") {
|
|
491
507
|
resolveCmd("codex", "CODEX_BIN");
|
|
492
508
|
}
|
|
493
|
-
if (config.command === "review") {
|
|
509
|
+
if (config.command === "review" || config.command === "gitlab-diff-review") {
|
|
494
510
|
resolveCmd("claude", "CLAUDE_BIN");
|
|
495
511
|
}
|
|
496
512
|
}
|
|
@@ -514,6 +530,7 @@ function autoFlowParams(config, forceRefreshSummary = false) {
|
|
|
514
530
|
const FLOW_DESCRIPTIONS = {
|
|
515
531
|
auto: "Полный пайплайн задачи: планирование, реализация, проверки, ревью, ответы на ревью и повторные итерации до готовности к merge.",
|
|
516
532
|
"bug-analyze": "Анализирует баг по Jira и создаёт структурированные артефакты: гипотезу причины, дизайн исправления и план работ.",
|
|
533
|
+
"gitlab-diff-review": "Запрашивает GitLab MR URL через user-input, загружает diff merge request по API и запускает код-ревью через Claude Opus с сохранением markdown и structured JSON artifacts.",
|
|
517
534
|
"gitlab-review": "Запрашивает GitLab MR URL через user-input, загружает комментарии код-ревью по API и сохраняет markdown плюс structured JSON artifact.",
|
|
518
535
|
"bug-fix": "Берёт результаты bug-analyze как source of truth и реализует исправление бага в коде.",
|
|
519
536
|
"mr-description": "Готовит краткое intent-описание для merge request на основе задачи и текущих изменений.",
|
|
@@ -528,12 +545,15 @@ const FLOW_DESCRIPTIONS = {
|
|
|
528
545
|
function flowDescription(id) {
|
|
529
546
|
return FLOW_DESCRIPTIONS[id] ?? "Описание для этого flow пока не задано.";
|
|
530
547
|
}
|
|
531
|
-
function
|
|
532
|
-
const flow =
|
|
548
|
+
function interactiveFlowDefinition(entry) {
|
|
549
|
+
const flow = entry.flow;
|
|
533
550
|
return {
|
|
534
|
-
id,
|
|
535
|
-
label,
|
|
536
|
-
description: flowDescription(id),
|
|
551
|
+
id: entry.id,
|
|
552
|
+
label: entry.id,
|
|
553
|
+
description: flowDescription(entry.id),
|
|
554
|
+
source: entry.source,
|
|
555
|
+
treePath: [...entry.treePath],
|
|
556
|
+
...(entry.source === "project-local" ? { sourcePath: entry.absolutePath } : {}),
|
|
537
557
|
phases: flow.phases.map((phase) => ({
|
|
538
558
|
id: phase.id,
|
|
539
559
|
repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
|
|
@@ -543,36 +563,8 @@ function declarativeFlowDefinition(id, label, fileName) {
|
|
|
543
563
|
})),
|
|
544
564
|
};
|
|
545
565
|
}
|
|
546
|
-
function
|
|
547
|
-
|
|
548
|
-
return {
|
|
549
|
-
id: "auto",
|
|
550
|
-
label: "auto",
|
|
551
|
-
description: flowDescription("auto"),
|
|
552
|
-
phases: flow.phases.map((phase) => ({
|
|
553
|
-
id: phase.id,
|
|
554
|
-
repeatVars: Object.fromEntries(Object.entries(phase.repeatVars).map(([key, value]) => [key, value])),
|
|
555
|
-
steps: phase.steps.map((step) => ({
|
|
556
|
-
id: step.id,
|
|
557
|
-
})),
|
|
558
|
-
})),
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
function interactiveFlowDefinitions() {
|
|
562
|
-
return [
|
|
563
|
-
autoFlowDefinition(),
|
|
564
|
-
declarativeFlowDefinition("bug-analyze", "bug-analyze", "bug-analyze.json"),
|
|
565
|
-
declarativeFlowDefinition("bug-fix", "bug-fix", "bug-fix.json"),
|
|
566
|
-
declarativeFlowDefinition("gitlab-review", "gitlab-review", "gitlab-review.json"),
|
|
567
|
-
declarativeFlowDefinition("mr-description", "mr-description", "mr-description.json"),
|
|
568
|
-
declarativeFlowDefinition("plan", "plan", "plan.json"),
|
|
569
|
-
declarativeFlowDefinition("task-describe", "task-describe", "task-describe.json"),
|
|
570
|
-
declarativeFlowDefinition("implement", "implement", "implement.json"),
|
|
571
|
-
declarativeFlowDefinition("review", "review", "review.json"),
|
|
572
|
-
declarativeFlowDefinition("review-fix", "review-fix", "review-fix.json"),
|
|
573
|
-
declarativeFlowDefinition("run-go-tests-loop", "run-go-tests-loop", "run-go-tests-loop.json"),
|
|
574
|
-
declarativeFlowDefinition("run-go-linter-loop", "run-go-linter-loop", "run-go-linter-loop.json"),
|
|
575
|
-
];
|
|
566
|
+
function interactiveFlowDefinitions(catalog) {
|
|
567
|
+
return catalog.map((entry) => interactiveFlowDefinition(entry));
|
|
576
568
|
}
|
|
577
569
|
function publishFlowState(flowId, executionState) {
|
|
578
570
|
setFlowExecutionState(flowId, stripExecutionStatePayload(executionState));
|
|
@@ -586,7 +578,7 @@ function loadTaskSummaryMarkdown(taskKey) {
|
|
|
586
578
|
return markdown.length > 0 ? markdown : null;
|
|
587
579
|
}
|
|
588
580
|
function syncInteractiveTaskSummary(ui, scope, forceRefresh = false) {
|
|
589
|
-
if (
|
|
581
|
+
if (forceRefresh) {
|
|
590
582
|
ui.clearSummary();
|
|
591
583
|
return;
|
|
592
584
|
}
|
|
@@ -610,38 +602,38 @@ function findCurrentFlowExecutionStep(state) {
|
|
|
610
602
|
}
|
|
611
603
|
return null;
|
|
612
604
|
}
|
|
613
|
-
async function
|
|
605
|
+
async function runDeclarativeFlowByRef(flowId, flowRef, config, flowParams, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
|
|
614
606
|
const context = createPipelineContext({
|
|
615
607
|
issueKey: config.taskKey,
|
|
616
608
|
jiraRef: config.jiraRef,
|
|
617
609
|
dryRun: config.dryRun,
|
|
618
610
|
verbose: config.verbose,
|
|
619
|
-
runtime
|
|
611
|
+
runtime,
|
|
620
612
|
...(setSummary ? { setSummary } : {}),
|
|
621
613
|
requestUserInput,
|
|
622
614
|
});
|
|
623
|
-
const flow = loadDeclarativeFlow(
|
|
615
|
+
const flow = loadDeclarativeFlow(flowRef);
|
|
624
616
|
const initialExecutionState = {
|
|
625
617
|
flowKind: flow.kind,
|
|
626
618
|
flowVersion: flow.version,
|
|
627
619
|
terminated: false,
|
|
628
620
|
phases: [],
|
|
629
621
|
};
|
|
630
|
-
let persistedState = launchMode === "resume" ? loadFlowRunState(config.scope.scopeKey,
|
|
622
|
+
let persistedState = launchMode === "resume" ? loadFlowRunState(config.scope.scopeKey, flowId) : null;
|
|
631
623
|
if (persistedState && launchMode === "resume") {
|
|
632
624
|
persistedState = prepareFlowStateForResume(persistedState);
|
|
633
625
|
}
|
|
634
626
|
else if (launchMode === "restart") {
|
|
635
|
-
resetFlowRunState(config.scope.scopeKey,
|
|
627
|
+
resetFlowRunState(config.scope.scopeKey, flowId);
|
|
636
628
|
}
|
|
637
629
|
const executionState = persistedState?.executionState ?? initialExecutionState;
|
|
638
|
-
const state = persistedState ?? createFlowRunState(config.scope.scopeKey,
|
|
630
|
+
const state = persistedState ?? createFlowRunState(config.scope.scopeKey, flowId, executionState);
|
|
639
631
|
state.status = "running";
|
|
640
632
|
state.lastError = null;
|
|
641
633
|
state.currentStep = findCurrentFlowExecutionStep(state);
|
|
642
634
|
state.executionState = executionState;
|
|
643
635
|
saveFlowRunState(state);
|
|
644
|
-
publishFlowState(
|
|
636
|
+
publishFlowState(flowId, executionState);
|
|
645
637
|
try {
|
|
646
638
|
for (const phase of flow.phases) {
|
|
647
639
|
await runExpandedPhase(phase, context, flowParams, flow.constants, {
|
|
@@ -652,7 +644,7 @@ async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, reques
|
|
|
652
644
|
state.executionState = nextExecutionState;
|
|
653
645
|
state.currentStep = findCurrentFlowExecutionStep(state);
|
|
654
646
|
saveFlowRunState(state);
|
|
655
|
-
publishFlowState(
|
|
647
|
+
publishFlowState(flowId, nextExecutionState);
|
|
656
648
|
},
|
|
657
649
|
onStepStart: async (currentPhase, step) => {
|
|
658
650
|
state.currentStep = `${currentPhase.id}:${step.id}`;
|
|
@@ -681,13 +673,59 @@ async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, reques
|
|
|
681
673
|
throw error;
|
|
682
674
|
}
|
|
683
675
|
}
|
|
684
|
-
async function
|
|
676
|
+
async function runDeclarativeFlowBySpecFile(fileName, config, flowParams, requestUserInput = requestUserInputInTerminal, setSummary, launchMode = "restart", runtime = runtimeServices) {
|
|
677
|
+
await runDeclarativeFlowByRef(config.command, { source: "built-in", fileName }, config, flowParams, requestUserInput, setSummary, launchMode, runtime);
|
|
678
|
+
}
|
|
679
|
+
function defaultDeclarativeFlowParams(config, forceRefreshSummary = false) {
|
|
680
|
+
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
681
|
+
const latestIteration = latestReviewReplyIteration(config.taskKey);
|
|
682
|
+
return {
|
|
683
|
+
taskKey: config.taskKey,
|
|
684
|
+
jiraRef: config.jiraRef,
|
|
685
|
+
jiraBrowseUrl: config.jiraBrowseUrl,
|
|
686
|
+
jiraApiUrl: config.jiraApiUrl,
|
|
687
|
+
jiraTaskFile: config.jiraTaskFile,
|
|
688
|
+
scopeKey: config.scope.scopeKey,
|
|
689
|
+
dockerComposeFile: config.dockerComposeFile,
|
|
690
|
+
runGoTestsScript: config.runGoTestsScript,
|
|
691
|
+
runGoLinterScript: config.runGoLinterScript,
|
|
692
|
+
runGoCoverageScript: config.runGoCoverageScript,
|
|
693
|
+
extraPrompt: config.extraPrompt,
|
|
694
|
+
reviewFixPoints: config.reviewFixPoints,
|
|
695
|
+
iteration,
|
|
696
|
+
latestIteration,
|
|
697
|
+
...(latestIteration !== null ? { reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration) } : {}),
|
|
698
|
+
forceRefresh: forceRefreshSummary,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
const TASK_SCOPE_PARAM_REFS = new Set(["params.jiraApiUrl", "params.jiraBrowseUrl", "params.jiraTaskFile"]);
|
|
702
|
+
function valueReferencesTaskScopeParams(value) {
|
|
703
|
+
if (Array.isArray(value)) {
|
|
704
|
+
return value.some((item) => valueReferencesTaskScopeParams(item));
|
|
705
|
+
}
|
|
706
|
+
if (!value || typeof value !== "object") {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
if ("ref" in value &&
|
|
710
|
+
typeof value.ref === "string" &&
|
|
711
|
+
TASK_SCOPE_PARAM_REFS.has(value.ref)) {
|
|
712
|
+
return true;
|
|
713
|
+
}
|
|
714
|
+
return Object.values(value).some((item) => valueReferencesTaskScopeParams(item));
|
|
715
|
+
}
|
|
716
|
+
function flowRequiresTaskScope(entry) {
|
|
717
|
+
if (entry.source === "built-in" && isBuiltInCommandFlowId(entry.id)) {
|
|
718
|
+
return commandRequiresTask(entry.id);
|
|
719
|
+
}
|
|
720
|
+
return valueReferencesTaskScopeParams(entry.flow.phases);
|
|
721
|
+
}
|
|
722
|
+
async function runAutoPhaseViaSpec(config, phaseId, executionState, state, setSummary, forceRefreshSummary = false, runtime = runtimeServices) {
|
|
685
723
|
const context = createPipelineContext({
|
|
686
724
|
issueKey: config.taskKey,
|
|
687
725
|
jiraRef: config.jiraRef,
|
|
688
726
|
dryRun: config.dryRun,
|
|
689
727
|
verbose: config.verbose,
|
|
690
|
-
runtime
|
|
728
|
+
runtime,
|
|
691
729
|
...(setSummary ? { setSummary } : {}),
|
|
692
730
|
requestUserInput: requestUserInputInTerminal,
|
|
693
731
|
});
|
|
@@ -767,18 +805,18 @@ async function summarizeBuildFailure(output) {
|
|
|
767
805
|
requestUserInput: requestUserInputInTerminal,
|
|
768
806
|
}), output);
|
|
769
807
|
}
|
|
770
|
-
function
|
|
771
|
-
if (
|
|
772
|
-
throw new TaskRunnerError(`Command '${config.command}' requires Jira
|
|
808
|
+
function requireJiraConfig(config) {
|
|
809
|
+
if (!config.jiraBrowseUrl || !config.jiraApiUrl || !config.jiraTaskFile) {
|
|
810
|
+
throw new TaskRunnerError(`Command '${config.command}' requires Jira context in the current project scope.`);
|
|
773
811
|
}
|
|
774
812
|
}
|
|
775
|
-
async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart") {
|
|
813
|
+
async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", runtime = runtimeServices) {
|
|
776
814
|
const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
|
|
777
815
|
if (config.command === "auto") {
|
|
778
816
|
if (launchMode === "restart") {
|
|
779
817
|
resetAutoPipelineState(config);
|
|
780
818
|
}
|
|
781
|
-
await runAutoPipeline(config, setSummary, forceRefreshSummary);
|
|
819
|
+
await runAutoPipeline(config, setSummary, forceRefreshSummary, runtime);
|
|
782
820
|
return false;
|
|
783
821
|
}
|
|
784
822
|
if (config.command === "auto-status") {
|
|
@@ -796,7 +834,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
796
834
|
return false;
|
|
797
835
|
}
|
|
798
836
|
checkPrerequisites(config);
|
|
799
|
-
if (config.
|
|
837
|
+
if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
|
|
800
838
|
process.env.JIRA_BROWSE_URL = config.jiraBrowseUrl ?? "";
|
|
801
839
|
process.env.JIRA_API_URL = config.jiraApiUrl ?? "";
|
|
802
840
|
process.env.JIRA_TASK_FILE = config.jiraTaskFile ?? "";
|
|
@@ -807,7 +845,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
807
845
|
delete process.env.JIRA_TASK_FILE;
|
|
808
846
|
}
|
|
809
847
|
if (config.command === "plan") {
|
|
810
|
-
|
|
848
|
+
requireJiraConfig(config);
|
|
811
849
|
if (config.verbose) {
|
|
812
850
|
process.stdout.write(`Fetching Jira issue from browse URL: ${config.jiraBrowseUrl}\n`);
|
|
813
851
|
process.stdout.write(`Resolved Jira API URL: ${config.jiraApiUrl}\n`);
|
|
@@ -818,11 +856,11 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
818
856
|
taskKey: config.taskKey,
|
|
819
857
|
extraPrompt: config.extraPrompt,
|
|
820
858
|
forceRefresh: forceRefreshSummary,
|
|
821
|
-
}, requestUserInput, setSummary, launchMode);
|
|
859
|
+
}, requestUserInput, setSummary, launchMode, runtime);
|
|
822
860
|
return false;
|
|
823
861
|
}
|
|
824
862
|
if (config.command === "bug-analyze") {
|
|
825
|
-
|
|
863
|
+
requireJiraConfig(config);
|
|
826
864
|
if (config.verbose) {
|
|
827
865
|
process.stdout.write(`Fetching Jira issue from browse URL: ${config.jiraBrowseUrl}\n`);
|
|
828
866
|
process.stdout.write(`Resolved Jira API URL: ${config.jiraApiUrl}\n`);
|
|
@@ -833,29 +871,35 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
833
871
|
taskKey: config.taskKey,
|
|
834
872
|
extraPrompt: config.extraPrompt,
|
|
835
873
|
forceRefresh: forceRefreshSummary,
|
|
836
|
-
}, requestUserInput, setSummary, launchMode);
|
|
874
|
+
}, requestUserInput, setSummary, launchMode, runtime);
|
|
837
875
|
return false;
|
|
838
876
|
}
|
|
839
877
|
if (config.command === "gitlab-review") {
|
|
840
|
-
requireTaskScopeConfig(config);
|
|
841
|
-
requireJiraTaskFile(config.jiraTaskFile);
|
|
842
|
-
validateStructuredArtifacts([
|
|
843
|
-
{ path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
|
|
844
|
-
{ path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
|
|
845
|
-
], "GitLab-review mode requires valid structured plan artifacts from the planning phase.");
|
|
846
878
|
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
847
879
|
await runDeclarativeFlowBySpecFile("gitlab-review.json", config, {
|
|
848
880
|
taskKey: config.taskKey,
|
|
849
881
|
iteration,
|
|
850
882
|
extraPrompt: config.extraPrompt,
|
|
851
|
-
}, requestUserInput, undefined, launchMode);
|
|
883
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
852
884
|
if (!config.dryRun) {
|
|
853
885
|
printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}`);
|
|
854
886
|
}
|
|
855
887
|
return false;
|
|
856
888
|
}
|
|
889
|
+
if (config.command === "gitlab-diff-review") {
|
|
890
|
+
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
891
|
+
await runDeclarativeFlowBySpecFile("gitlab-diff-review.json", config, {
|
|
892
|
+
taskKey: config.taskKey,
|
|
893
|
+
iteration,
|
|
894
|
+
extraPrompt: config.extraPrompt,
|
|
895
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
896
|
+
if (!config.dryRun) {
|
|
897
|
+
printSummary("GitLab Diff Review", `Artifacts:\n${gitlabDiffFile(config.taskKey)}\n${gitlabDiffJsonFile(config.taskKey)}\n${reviewFile(config.taskKey, iteration)}\n${reviewJsonFile(config.taskKey, iteration)}`);
|
|
898
|
+
}
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
857
901
|
if (config.command === "bug-fix") {
|
|
858
|
-
|
|
902
|
+
requireJiraConfig(config);
|
|
859
903
|
requireJiraTaskFile(config.jiraTaskFile);
|
|
860
904
|
requireArtifacts(bugAnalyzeArtifacts(config.taskKey), "Bug-fix mode requires bug-analyze artifacts from the bug analysis phase.");
|
|
861
905
|
validateStructuredArtifacts([
|
|
@@ -866,25 +910,25 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
866
910
|
await runDeclarativeFlowBySpecFile("bug-fix.json", config, {
|
|
867
911
|
taskKey: config.taskKey,
|
|
868
912
|
extraPrompt: config.extraPrompt,
|
|
869
|
-
}, requestUserInput, undefined, launchMode);
|
|
913
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
870
914
|
return false;
|
|
871
915
|
}
|
|
872
916
|
if (config.command === "mr-description") {
|
|
873
|
-
|
|
917
|
+
requireJiraConfig(config);
|
|
874
918
|
requireJiraTaskFile(config.jiraTaskFile);
|
|
875
919
|
await runDeclarativeFlowBySpecFile("mr-description.json", config, {
|
|
876
920
|
taskKey: config.taskKey,
|
|
877
921
|
extraPrompt: config.extraPrompt,
|
|
878
|
-
}, requestUserInput, undefined, launchMode);
|
|
922
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
879
923
|
return false;
|
|
880
924
|
}
|
|
881
925
|
if (config.command === "task-describe") {
|
|
882
|
-
|
|
926
|
+
requireJiraConfig(config);
|
|
883
927
|
await runDeclarativeFlowBySpecFile("task-describe.json", config, {
|
|
884
928
|
jiraApiUrl: config.jiraApiUrl,
|
|
885
929
|
taskKey: config.taskKey,
|
|
886
930
|
extraPrompt: config.extraPrompt,
|
|
887
|
-
}, requestUserInput, undefined, launchMode);
|
|
931
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
888
932
|
return false;
|
|
889
933
|
}
|
|
890
934
|
if (config.command === "implement") {
|
|
@@ -902,8 +946,8 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
902
946
|
}
|
|
903
947
|
if (config.command === "review") {
|
|
904
948
|
const iteration = nextReviewIterationForTask(config.taskKey);
|
|
905
|
-
if (config.
|
|
906
|
-
|
|
949
|
+
if (config.jiraBrowseUrl && config.jiraApiUrl && config.jiraTaskFile) {
|
|
950
|
+
requireJiraConfig(config);
|
|
907
951
|
validateStructuredArtifacts([
|
|
908
952
|
{ path: designJsonFile(config.taskKey), schemaId: "implementation-design/v1" },
|
|
909
953
|
{ path: planJsonFile(config.taskKey), schemaId: "implementation-plan/v1" },
|
|
@@ -912,14 +956,14 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
912
956
|
taskKey: config.taskKey,
|
|
913
957
|
iteration,
|
|
914
958
|
extraPrompt: config.extraPrompt,
|
|
915
|
-
}, requestUserInput, undefined, launchMode);
|
|
959
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
916
960
|
}
|
|
917
961
|
else {
|
|
918
962
|
await runDeclarativeFlowBySpecFile("review-project.json", config, {
|
|
919
963
|
taskKey: config.taskKey,
|
|
920
964
|
iteration,
|
|
921
965
|
extraPrompt: config.extraPrompt,
|
|
922
|
-
}, requestUserInput, undefined, launchMode);
|
|
966
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
923
967
|
}
|
|
924
968
|
return !config.dryRun && existsSync(readyToMergeFile(config.taskKey));
|
|
925
969
|
}
|
|
@@ -938,7 +982,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
938
982
|
reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
|
|
939
983
|
extraPrompt: config.extraPrompt,
|
|
940
984
|
reviewFixPoints: config.reviewFixPoints,
|
|
941
|
-
}, requestUserInput, undefined, launchMode);
|
|
985
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
942
986
|
return false;
|
|
943
987
|
}
|
|
944
988
|
if (config.command === "run-go-tests-loop" || config.command === "run-go-linter-loop") {
|
|
@@ -947,12 +991,12 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
|
|
|
947
991
|
runGoTestsScript: config.runGoTestsScript,
|
|
948
992
|
runGoLinterScript: config.runGoLinterScript,
|
|
949
993
|
extraPrompt: config.extraPrompt,
|
|
950
|
-
}, requestUserInput, undefined, launchMode);
|
|
994
|
+
}, requestUserInput, undefined, launchMode, runtime);
|
|
951
995
|
return false;
|
|
952
996
|
}
|
|
953
997
|
throw new TaskRunnerError(`Unsupported command: ${config.command}`);
|
|
954
998
|
}
|
|
955
|
-
async function runAutoPipelineDryRun(config, setSummary, forceRefreshSummary = false) {
|
|
999
|
+
async function runAutoPipelineDryRun(config, setSummary, forceRefreshSummary = false, runtime = runtimeServices) {
|
|
956
1000
|
checkAutoPrerequisites(config);
|
|
957
1001
|
printInfo("Dry-run auto pipeline from declarative spec");
|
|
958
1002
|
const autoFlow = loadAutoFlow();
|
|
@@ -965,16 +1009,16 @@ async function runAutoPipelineDryRun(config, setSummary, forceRefreshSummary = f
|
|
|
965
1009
|
publishFlowState("auto", executionState);
|
|
966
1010
|
for (const phase of autoFlow.phases) {
|
|
967
1011
|
printInfo(`Dry-run auto phase: ${phase.id}`);
|
|
968
|
-
await runAutoPhaseViaSpec(config, phase.id, executionState, undefined, setSummary, forceRefreshSummary);
|
|
1012
|
+
await runAutoPhaseViaSpec(config, phase.id, executionState, undefined, setSummary, forceRefreshSummary, runtime);
|
|
969
1013
|
if (executionState.terminated) {
|
|
970
1014
|
break;
|
|
971
1015
|
}
|
|
972
1016
|
}
|
|
973
1017
|
}
|
|
974
|
-
async function runAutoPipeline(config, setSummary, forceRefreshSummary = false) {
|
|
975
|
-
|
|
1018
|
+
async function runAutoPipeline(config, setSummary, forceRefreshSummary = false, runtime = runtimeServices) {
|
|
1019
|
+
requireJiraConfig(config);
|
|
976
1020
|
if (config.dryRun) {
|
|
977
|
-
await runAutoPipelineDryRun(config, setSummary, forceRefreshSummary);
|
|
1021
|
+
await runAutoPipelineDryRun(config, setSummary, forceRefreshSummary, runtime);
|
|
978
1022
|
return;
|
|
979
1023
|
}
|
|
980
1024
|
checkAutoPrerequisites(config);
|
|
@@ -1014,7 +1058,7 @@ async function runAutoPipeline(config, setSummary, forceRefreshSummary = false)
|
|
|
1014
1058
|
saveAutoPipelineState(state);
|
|
1015
1059
|
try {
|
|
1016
1060
|
printInfo(`Running auto step: ${step.id}`);
|
|
1017
|
-
const status = await runAutoPhaseViaSpec(config, step.id, state.executionState, state, setSummary, forceRefreshSummary);
|
|
1061
|
+
const status = await runAutoPhaseViaSpec(config, step.id, state.executionState, state, setSummary, forceRefreshSummary, runtime);
|
|
1018
1062
|
step.status = status;
|
|
1019
1063
|
step.finishedAt = nowIso8601();
|
|
1020
1064
|
step.returnCode = 0;
|
|
@@ -1127,24 +1171,31 @@ function buildConfigFromArgs(args) {
|
|
|
1127
1171
|
});
|
|
1128
1172
|
}
|
|
1129
1173
|
async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
1130
|
-
let currentScope =
|
|
1174
|
+
let currentScope = resolveProjectScope(scopeName, jiraRef);
|
|
1131
1175
|
const gitBranchName = detectGitBranchName();
|
|
1176
|
+
const flowCatalog = loadInteractiveFlowCatalog(process.cwd());
|
|
1177
|
+
let activeAbortController = null;
|
|
1178
|
+
let activeFlowId = null;
|
|
1132
1179
|
let exiting = false;
|
|
1133
1180
|
const ui = new InteractiveUi({
|
|
1134
1181
|
scopeKey: currentScope.scopeKey,
|
|
1135
|
-
jiraIssueKey: currentScope.
|
|
1182
|
+
jiraIssueKey: currentScope.jiraIssueKey ?? null,
|
|
1136
1183
|
summaryText: "",
|
|
1137
1184
|
cwd: process.cwd(),
|
|
1138
1185
|
gitBranchName,
|
|
1139
|
-
flows: interactiveFlowDefinitions(),
|
|
1186
|
+
flows: interactiveFlowDefinitions(flowCatalog),
|
|
1140
1187
|
getRunConfirmation: async (flowId) => {
|
|
1188
|
+
const flowEntry = findCatalogEntry(flowId, flowCatalog);
|
|
1189
|
+
if (!flowEntry) {
|
|
1190
|
+
throw new TaskRunnerError(`Unknown flow: ${flowId}`);
|
|
1191
|
+
}
|
|
1141
1192
|
if (flowId === "auto") {
|
|
1142
|
-
if (currentScope.
|
|
1193
|
+
if (!currentScope.jiraRef) {
|
|
1143
1194
|
return { resumeAvailable: false, hasExistingState: false };
|
|
1144
1195
|
}
|
|
1145
1196
|
const baseConfig = buildBaseConfig("auto", {
|
|
1146
1197
|
jiraRef: currentScope.jiraRef,
|
|
1147
|
-
scopeName: currentScope.scopeKey
|
|
1198
|
+
scopeName: currentScope.scopeKey,
|
|
1148
1199
|
});
|
|
1149
1200
|
const state = loadAutoPipelineState(buildRuntimeConfig(baseConfig, currentScope));
|
|
1150
1201
|
if (!state) {
|
|
@@ -1160,39 +1211,48 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1160
1211
|
details: buildAutoResumeDetails(state),
|
|
1161
1212
|
};
|
|
1162
1213
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}
|
|
1166
|
-
const state = loadFlowRunState(currentScope.scopeKey, flowId);
|
|
1167
|
-
if (!state || !hasResumableFlowState(state)) {
|
|
1168
|
-
return { resumeAvailable: false, hasExistingState: Boolean(state) };
|
|
1169
|
-
}
|
|
1170
|
-
return {
|
|
1171
|
-
resumeAvailable: true,
|
|
1172
|
-
hasExistingState: true,
|
|
1173
|
-
details: buildFlowResumeDetails(state),
|
|
1174
|
-
};
|
|
1214
|
+
const resumeLookup = lookupInteractiveFlowResume(flowEntry, currentScope);
|
|
1215
|
+
return resumeLookup;
|
|
1175
1216
|
},
|
|
1176
1217
|
onRun: async (flowId, launchMode) => {
|
|
1218
|
+
const abortController = new AbortController();
|
|
1219
|
+
activeAbortController = abortController;
|
|
1220
|
+
activeFlowId = flowId;
|
|
1177
1221
|
try {
|
|
1178
|
-
const
|
|
1222
|
+
const flowEntry = findCatalogEntry(flowId, flowCatalog);
|
|
1223
|
+
if (!flowEntry) {
|
|
1224
|
+
throw new TaskRunnerError(`Unknown flow: ${flowId}`);
|
|
1225
|
+
}
|
|
1179
1226
|
const previousScopeKey = currentScope.scopeKey;
|
|
1180
1227
|
const baseConfig = buildBaseConfig(flowId, {
|
|
1181
|
-
...(currentScope.
|
|
1182
|
-
|
|
1183
|
-
? { scopeName: currentScope.scopeKey }
|
|
1184
|
-
: {}),
|
|
1185
|
-
...(currentScope.scopeType === "project" ? { scopeName: currentScope.scopeKey } : {}),
|
|
1228
|
+
...(currentScope.jiraRef ? { jiraRef: currentScope.jiraRef } : {}),
|
|
1229
|
+
scopeName: currentScope.scopeKey,
|
|
1186
1230
|
});
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1231
|
+
if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
|
|
1232
|
+
const nextScope = await resolveScopeForCommand(baseConfig, (form) => ui.requestUserInput(form));
|
|
1233
|
+
currentScope = nextScope;
|
|
1234
|
+
}
|
|
1235
|
+
else if (flowRequiresTaskScope(flowEntry) && !currentScope.jiraRef) {
|
|
1236
|
+
const jiraContext = await requestJiraContext((form) => ui.requestUserInput(form));
|
|
1237
|
+
currentScope = attachJiraContext(currentScope, jiraContext.jiraRef);
|
|
1238
|
+
}
|
|
1239
|
+
ui.setScope(currentScope.scopeKey, currentScope.jiraIssueKey ?? null);
|
|
1240
|
+
if (previousScopeKey !== currentScope.scopeKey || currentScope.jiraIssueKey) {
|
|
1191
1241
|
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
1192
1242
|
}
|
|
1193
|
-
|
|
1243
|
+
if (flowEntry.source === "built-in" && isBuiltInCommandFlowId(flowId)) {
|
|
1244
|
+
await executeCommand(baseConfig, true, (form) => ui.requestUserInput(form), currentScope, (markdown) => ui.setSummary(markdown), forceRefresh, launchMode, createRuntimeServices(abortController.signal));
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const runtimeConfig = buildRuntimeConfig(baseConfig, currentScope);
|
|
1248
|
+
await runDeclarativeFlowByRef(flowId, toDeclarativeFlowRef(flowEntry), runtimeConfig, defaultDeclarativeFlowParams(runtimeConfig, forceRefresh), (form) => ui.requestUserInput(form), (markdown) => ui.setSummary(markdown), launchMode, createRuntimeServices(abortController.signal));
|
|
1194
1249
|
}
|
|
1195
1250
|
catch (error) {
|
|
1251
|
+
if (error instanceof FlowInterruptedError) {
|
|
1252
|
+
ui.appendLog(`[interrupt] ${error.message}`);
|
|
1253
|
+
printInfo(error.message);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1196
1256
|
if (error instanceof TaskRunnerError) {
|
|
1197
1257
|
ui.setFlowFailed(flowId);
|
|
1198
1258
|
printError(error.message);
|
|
@@ -1206,6 +1266,19 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1206
1266
|
}
|
|
1207
1267
|
throw error;
|
|
1208
1268
|
}
|
|
1269
|
+
finally {
|
|
1270
|
+
if (activeAbortController === abortController) {
|
|
1271
|
+
activeAbortController = null;
|
|
1272
|
+
activeFlowId = null;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
onInterrupt: async (flowId) => {
|
|
1277
|
+
if (!activeAbortController || activeFlowId !== flowId) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
ui.interruptActiveForm();
|
|
1281
|
+
activeAbortController.abort();
|
|
1209
1282
|
},
|
|
1210
1283
|
onExit: () => {
|
|
1211
1284
|
exiting = true;
|
|
@@ -1214,12 +1287,10 @@ async function runInteractive(jiraRef, forceRefresh = false, scopeName) {
|
|
|
1214
1287
|
ui.mount();
|
|
1215
1288
|
printInfo(`Interactive mode for ${currentScope.scopeKey}`);
|
|
1216
1289
|
printInfo("Use h to see help.");
|
|
1217
|
-
if (currentScope.
|
|
1290
|
+
if (!currentScope.jiraIssueKey) {
|
|
1218
1291
|
ui.appendLog("[scope] project scope active; task summary will appear after a Jira-backed flow runs");
|
|
1219
1292
|
}
|
|
1220
|
-
|
|
1221
|
-
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
1222
|
-
}
|
|
1293
|
+
syncInteractiveTaskSummary(ui, currentScope, forceRefresh);
|
|
1223
1294
|
return await new Promise((resolve, reject) => {
|
|
1224
1295
|
const interval = setInterval(() => {
|
|
1225
1296
|
if (!exiting) {
|