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.
- package/README.md +50 -10
- package/dist/artifacts.js +73 -3
- package/dist/doctor/checks/executors.js +2 -2
- package/dist/flow-state.js +138 -1
- package/dist/index.js +175 -61
- package/dist/interactive/controller.js +56 -23
- package/dist/interactive/ink/index.js +22 -1
- package/dist/interactive/tree.js +2 -2
- package/dist/pipeline/auto-flow.js +9 -6
- package/dist/pipeline/context.js +6 -5
- package/dist/pipeline/declarative-flows.js +39 -20
- package/dist/pipeline/flow-catalog.js +36 -14
- package/dist/pipeline/flow-specs/auto-common.json +1 -0
- package/dist/pipeline/flow-specs/auto-golang.json +27 -1
- package/dist/pipeline/flow-specs/design-review/design-review-loop.json +13 -1
- package/dist/pipeline/flow-specs/plan.json +4 -2
- package/dist/pipeline/launch-profile-config.js +30 -18
- package/dist/pipeline/node-contract.js +1 -0
- package/dist/pipeline/node-registry.js +74 -5
- package/dist/pipeline/nodes/flow-run-node.js +188 -173
- package/dist/pipeline/nodes/llm-prompt-node.js +15 -33
- package/dist/pipeline/plugin-loader.js +389 -0
- package/dist/pipeline/plugin-types.js +1 -0
- package/dist/pipeline/registry.js +71 -4
- package/dist/pipeline/spec-compiler.js +1 -0
- package/dist/pipeline/spec-loader.js +14 -0
- package/dist/pipeline/spec-validator.js +6 -0
- package/dist/pipeline/value-resolver.js +2 -1
- package/dist/plugin-sdk.js +1 -0
- package/dist/runtime/artifact-registry.js +3 -0
- package/dist/runtime/execution-routing.js +25 -19
- package/dist/runtime/interactive-execution-routing.js +66 -57
- package/docs/example/.flows/examples/claude-example.json +50 -0
- package/docs/example/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/example/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/examples/.flows/claude-example.json +50 -0
- package/docs/examples/.plugins/claude-example-plugin/index.js +149 -0
- package/docs/examples/.plugins/claude-example-plugin/plugin.json +8 -0
- package/docs/plugin-sdk.md +731 -0
- 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,
|
|
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
|
-
|
|
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
|
-
|
|
351
|
-
hasExistingState: true,
|
|
366
|
+
...availability,
|
|
352
367
|
details: buildFlowResumeDetails(directState),
|
|
353
368
|
};
|
|
354
369
|
}
|
|
355
370
|
catch (error) {
|
|
356
371
|
return {
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
}
|