agentweaver 0.1.11 → 0.1.13

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 (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/dist/artifacts.js +9 -0
  4. package/dist/doctor/checks/agentweaver-home.js +57 -0
  5. package/dist/doctor/checks/category.js +9 -0
  6. package/dist/doctor/checks/cwd-context.js +69 -0
  7. package/dist/doctor/checks/env-diagnostics.js +171 -0
  8. package/dist/doctor/checks/executors.js +106 -0
  9. package/dist/doctor/checks/flow-readiness.js +305 -0
  10. package/dist/doctor/checks/node-version.js +79 -0
  11. package/dist/doctor/checks/register.js +18 -0
  12. package/dist/doctor/checks/system.js +91 -0
  13. package/dist/doctor/index.js +4 -0
  14. package/dist/doctor/orchestrator.js +78 -0
  15. package/dist/doctor/registry.js +50 -0
  16. package/dist/doctor/runner.js +89 -0
  17. package/dist/doctor/types.js +12 -0
  18. package/dist/executors/codex-executor.js +2 -1
  19. package/dist/executors/jira-fetch-executor.js +1 -0
  20. package/dist/executors/opencode-executor.js +22 -11
  21. package/dist/executors/process-executor.js +3 -0
  22. package/dist/index.js +59 -35
  23. package/dist/interactive-ui.js +579 -159
  24. package/dist/jira.js +57 -0
  25. package/dist/pipeline/flow-specs/gitlab/gitlab-review.json +105 -21
  26. package/dist/pipeline/flow-specs/review/review-fix.json +1 -0
  27. package/dist/pipeline/flow-specs/review/review-loop.json +2 -0
  28. package/dist/pipeline/flow-specs/task-describe.json +64 -3
  29. package/dist/pipeline/nodes/jira-fetch-node.js +3 -0
  30. package/dist/pipeline/nodes/review-findings-form-node.js +33 -3
  31. package/dist/pipeline/nodes/user-input-node.js +18 -4
  32. package/dist/pipeline/prompt-registry.js +2 -1
  33. package/dist/pipeline/spec-types.js +2 -0
  34. package/dist/pipeline/value-resolver.js +11 -1
  35. package/dist/prompts.js +17 -2
  36. package/dist/runtime/process-runner.js +9 -3
  37. package/dist/structured-artifact-schema-registry.js +1 -0
  38. package/dist/structured-artifact-schemas.json +22 -0
  39. package/dist/user-input.js +8 -1
  40. package/package.json +4 -2
  41. package/dist/pipeline/flow-model-settings.js +0 -77
@@ -0,0 +1,89 @@
1
+ import { DoctorStatus, ReadinessStatus } from "./types.js";
2
+ import { REGISTRY } from "./registry.js";
3
+ import { DoctorOrchestrator } from "./orchestrator.js";
4
+ import { CATEGORY } from "./checks/category.js";
5
+ import "./checks/register.js";
6
+ const STATUS_ICONS = {
7
+ [DoctorStatus.Ok]: "✓",
8
+ [DoctorStatus.Warn]: "⚠",
9
+ [DoctorStatus.Fail]: "✗",
10
+ };
11
+ const READINESS_LABELS = {
12
+ [ReadinessStatus.Ready]: "Ready",
13
+ [ReadinessStatus.ReadyWithWarnings]: "Ready with warnings",
14
+ [ReadinessStatus.NotReady]: "Not ready",
15
+ };
16
+ const CATEGORY_LABELS = {
17
+ [CATEGORY.SYSTEM]: "System",
18
+ [CATEGORY.EXECUTORS]: "Executors",
19
+ [CATEGORY.ENV_DIAGNOSTICS]: "Environment",
20
+ [CATEGORY.FLOW_READINESS]: "Flow Readiness",
21
+ };
22
+ async function runDoctorCommand(args) {
23
+ const jsonMode = args.includes("--json");
24
+ const filter = args.find((arg) => arg !== "--json");
25
+ const orchestrator = new DoctorOrchestrator();
26
+ const report = await orchestrator.run(undefined, filter);
27
+ if (jsonMode) {
28
+ process.stdout.write(JSON.stringify(report, null, 2));
29
+ return report.overall === ReadinessStatus.NotReady ? 1 : 0;
30
+ }
31
+ const grouped = new Map();
32
+ for (const result of report.checks) {
33
+ const check = REGISTRY.getById(result.id);
34
+ const cat = check?.category ?? "unknown";
35
+ if (!grouped.has(cat)) {
36
+ grouped.set(cat, []);
37
+ }
38
+ grouped.get(cat).push(result);
39
+ }
40
+ const categoryOrder = [
41
+ CATEGORY.SYSTEM,
42
+ CATEGORY.EXECUTORS,
43
+ CATEGORY.ENV_DIAGNOSTICS,
44
+ CATEGORY.FLOW_READINESS,
45
+ ];
46
+ for (const cat of categoryOrder) {
47
+ const items = grouped.get(cat);
48
+ if (!items || items.length === 0)
49
+ continue;
50
+ const label = CATEGORY_LABELS[cat] ?? cat;
51
+ console.log(`## ${label}`);
52
+ console.log();
53
+ for (const result of items) {
54
+ const icon = STATUS_ICONS[result.status];
55
+ const line = `[${icon}] ${result.title} - ${result.message}`;
56
+ console.log(line);
57
+ if (result.hint) {
58
+ console.log(` Hint: ${result.hint}`);
59
+ }
60
+ if (result.details) {
61
+ console.log(` Details: ${result.details}`);
62
+ }
63
+ }
64
+ console.log();
65
+ }
66
+ for (const [cat, items] of grouped) {
67
+ if (categoryOrder.includes(cat))
68
+ continue;
69
+ const label = CATEGORY_LABELS[cat] ?? cat;
70
+ console.log(`## ${label}`);
71
+ console.log();
72
+ for (const result of items) {
73
+ const icon = STATUS_ICONS[result.status];
74
+ const line = `[${icon}] ${result.title} - ${result.message}`;
75
+ console.log(line);
76
+ if (result.hint) {
77
+ console.log(` Hint: ${result.hint}`);
78
+ }
79
+ if (result.details) {
80
+ console.log(` Details: ${result.details}`);
81
+ }
82
+ }
83
+ console.log();
84
+ }
85
+ console.log(`Overall: ${READINESS_LABELS[report.overall]}`);
86
+ console.log(`Timestamp: ${report.timestamp}`);
87
+ return report.overall === ReadinessStatus.NotReady ? 1 : 0;
88
+ }
89
+ export { runDoctorCommand };
@@ -0,0 +1,12 @@
1
+ export var DoctorStatus;
2
+ (function (DoctorStatus) {
3
+ DoctorStatus["Ok"] = "ok";
4
+ DoctorStatus["Warn"] = "warn";
5
+ DoctorStatus["Fail"] = "fail";
6
+ })(DoctorStatus || (DoctorStatus = {}));
7
+ export var ReadinessStatus;
8
+ (function (ReadinessStatus) {
9
+ ReadinessStatus["Ready"] = "ready";
10
+ ReadinessStatus["ReadyWithWarnings"] = "ready_with_warnings";
11
+ ReadinessStatus["NotReady"] = "not_ready";
12
+ })(ReadinessStatus || (ReadinessStatus = {}));
@@ -12,9 +12,10 @@ export const codexExecutor = {
12
12
  const command = input.command ?? context.runtime.resolveCmd(config.defaultCommand, config.commandEnvVar);
13
13
  const model = input.model?.trim() || resolveModel(config, env);
14
14
  const result = await processExecutor.execute(context, {
15
- argv: [command, config.subcommand, "--model", model, config.fullAutoFlag, input.prompt],
15
+ argv: [command, config.subcommand, "--model", model, config.fullAutoFlag, "-"],
16
16
  env,
17
17
  label: `codex:${model}`,
18
+ stdin: input.prompt,
18
19
  }, processExecutor.defaultConfig);
19
20
  return {
20
21
  output: result.output,
@@ -12,6 +12,7 @@ export const jiraFetchExecutor = {
12
12
  planningContextAttachments: artifacts.planningContextAttachments,
13
13
  ...(artifacts.attachmentsManifestFile ? { attachmentsManifestFile: artifacts.attachmentsManifestFile } : {}),
14
14
  ...(artifacts.attachmentsContextFile ? { attachmentsContextFile: artifacts.attachmentsContextFile } : {}),
15
+ ...(artifacts.enrichedFile ? { enrichedFile: artifacts.enrichedFile } : {}),
15
16
  };
16
17
  },
17
18
  };
@@ -1,3 +1,6 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
1
4
  import { opencodeExecutorDefaultConfig } from "./configs/opencode-config.js";
2
5
  import { processExecutor } from "./process-executor.js";
3
6
  function resolveModel(config, inputModel, env) {
@@ -17,19 +20,27 @@ export const opencodeExecutor = {
17
20
  const command = input.command ?? context.runtime.resolveCmd(config.defaultCommand, config.commandEnvVar);
18
21
  const model = resolveModel(config, input.model, env);
19
22
  const argv = [command, config.subcommand];
23
+ const tempDir = mkdtempSync(path.join(os.tmpdir(), "agentweaver-opencode-"));
24
+ const promptFile = path.join(tempDir, "prompt.md");
25
+ writeFileSync(promptFile, input.prompt, "utf8");
20
26
  if (model) {
21
27
  argv.push("--model", model);
22
28
  }
23
- argv.push(input.prompt);
24
- const result = await processExecutor.execute(context, {
25
- argv,
26
- env,
27
- label: model ? `opencode:${model}` : "opencode",
28
- }, processExecutor.defaultConfig);
29
- return {
30
- output: result.output,
31
- command,
32
- ...(model ? { model } : {}),
33
- };
29
+ argv.push("--file", promptFile, "--", "Use the attached file as the full task prompt. Follow it exactly.");
30
+ try {
31
+ const result = await processExecutor.execute(context, {
32
+ argv,
33
+ env,
34
+ label: model ? `opencode:${model}` : "opencode",
35
+ }, processExecutor.defaultConfig);
36
+ return {
37
+ output: result.output,
38
+ command,
39
+ ...(model ? { model } : {}),
40
+ };
41
+ }
42
+ finally {
43
+ rmSync(tempDir, { recursive: true, force: true });
44
+ }
34
45
  },
35
46
  };
@@ -15,6 +15,9 @@ export const processExecutor = {
15
15
  if (input.label) {
16
16
  options.label = input.label;
17
17
  }
18
+ if (input.stdin !== undefined) {
19
+ options.stdin = input.stdin;
20
+ }
18
21
  const output = await context.runtime.runCommand(input.argv, options);
19
22
  return { output };
20
23
  },
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync, readdirSync, 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 { REVIEW_FILE_RE, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, latestArtifactIteration, nextArtifactIteration, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
6
+ import { REVIEW_FILE_RE, bugAnalyzeArtifacts, bugAnalyzeJsonFile, bugFixDesignJsonFile, bugFixPlanJsonFile, designJsonFile, gitlabDiffFile, gitlabDiffJsonFile, ensureScopeWorkspaceDir, gitlabReviewFile, gitlabReviewJsonFile, latestArtifactIteration, nextArtifactIteration, planJsonFile, planArtifacts, qaJsonFile, readyToMergeFile, requireArtifacts, reviewAssessmentFile, reviewAssessmentJsonFile, reviewFile, reviewFixSelectionJsonFile, reviewJsonFile, scopeWorkspaceDir, flowStateFile, taskSummaryFile, } from "./artifacts.js";
7
7
  import { FlowInterruptedError, TaskRunnerError } from "./errors.js";
8
8
  import { createFlowRunState, hasResumableFlowState, loadFlowRunState, prepareFlowStateForResume, resetFlowRunState, rewindFlowRunStateToPhase, saveFlowRunState, stripExecutionStatePayload, } from "./flow-state.js";
9
9
  import { requireJiraTaskFile } from "./jira.js";
@@ -23,6 +23,7 @@ import { runCommand } from "./runtime/process-runner.js";
23
23
  import { InteractiveUi } from "./interactive-ui.js";
24
24
  import { bye, printError, printInfo, printPanel, printSummary, setFlowExecutionState, stripAnsi, } from "./tui.js";
25
25
  import { requestUserInputInTerminal } from "./user-input.js";
26
+ import { runDoctorCommand } from "./doctor/index.js";
26
27
  import { attachJiraContext, detectGitBranchName, requestJiraContext, resolveProjectScope, } from "./scope.js";
27
28
  const COMMANDS = [
28
29
  "auto-golang",
@@ -31,6 +32,7 @@ const COMMANDS = [
31
32
  "auto-reset",
32
33
  "bug-analyze",
33
34
  "bug-fix",
35
+ "doctor",
34
36
  "git-commit",
35
37
  "gitlab-diff-review",
36
38
  "gitlab-review",
@@ -97,6 +99,7 @@ function usage() {
97
99
  agentweaver gitlab-review [--dry] [--verbose] [--prompt <text>] [--scope <name>]
98
100
  agentweaver bug-analyze [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
99
101
  agentweaver bug-fix [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
102
+ agentweaver doctor [<category>|<check-id>] [--json]
100
103
  agentweaver mr-description [--dry] [--verbose] [--prompt <text>] <jira-browse-url|jira-issue-key>
101
104
  agentweaver plan [--dry] [--verbose] [--prompt <text>] [--md-lang <en|ru>] [<jira-browse-url|jira-issue-key>]
102
105
  agentweaver task-describe [--dry] [--verbose] [--prompt <text>] [<jira-browse-url|jira-issue-key>]
@@ -194,14 +197,11 @@ function launchProfileSelectionForm() {
194
197
  type: "single-select",
195
198
  label: "Executor",
196
199
  required: true,
197
- default: "default",
198
- options: [
199
- { value: "default", label: `default (${defaultExecutor})` },
200
- ...LLM_EXECUTOR_IDS.map((id) => ({
201
- value: id,
202
- label: id === defaultExecutor ? `${id} [default]` : id,
203
- })),
204
- ],
200
+ default: defaultExecutor,
201
+ options: LLM_EXECUTOR_IDS.map((id) => ({
202
+ value: id,
203
+ label: id === defaultExecutor ? `${id} [default]` : id,
204
+ })),
205
205
  },
206
206
  ],
207
207
  };
@@ -209,15 +209,10 @@ function launchProfileSelectionForm() {
209
209
  function launchModelSelectionForm(executor) {
210
210
  const resolvedExecutor = executor === "default" ? DEFAULT_LAUNCH_PROFILE.executor : executor;
211
211
  const defaultModel = defaultModelForExecutor(resolvedExecutor);
212
- const options = executor === "default"
213
- ? [{ value: "default", label: `default (${DEFAULT_LAUNCH_PROFILE.model})` }]
214
- : [
215
- { value: "default", label: `default (${defaultModel})` },
216
- ...ALLOWED_MODELS_BY_EXECUTOR[executor].map((model) => ({
217
- value: model,
218
- label: model === defaultModel ? `${model} [default]` : model,
219
- })),
220
- ];
212
+ const options = ALLOWED_MODELS_BY_EXECUTOR[resolvedExecutor].map((model) => ({
213
+ value: model,
214
+ label: model === defaultModel ? `${model} [default]` : model,
215
+ }));
221
216
  return {
222
217
  formId: "flow-launch-model",
223
218
  title: "Настройки запуска LLM",
@@ -229,25 +224,38 @@ function launchModelSelectionForm(executor) {
229
224
  type: "single-select",
230
225
  label: "Model",
231
226
  required: true,
232
- default: "default",
227
+ default: defaultModel,
233
228
  options,
234
229
  },
235
230
  ],
236
231
  };
237
232
  }
233
+ function isFormCancellation(error, formId) {
234
+ return error instanceof TaskRunnerError && error.message === `User cancelled form '${formId}'.`;
235
+ }
238
236
  async function requestInteractiveLaunchProfile(requestUserInput) {
239
- const executorFormResult = await requestUserInput(launchProfileSelectionForm());
240
- const rawExecutor = String(executorFormResult.values.executor ?? "default");
241
- const executor = rawExecutor === "default" ? "default" : LLM_EXECUTOR_IDS.find((id) => id === rawExecutor);
242
- if (!executor) {
243
- throw new TaskRunnerError(`Unsupported launch executor '${rawExecutor}'.`);
244
- }
245
- const modelFormResult = await requestUserInput(launchModelSelectionForm(executor));
246
- const rawModel = String(modelFormResult.values.model ?? "default").trim();
247
- return resolveLaunchProfile({
248
- executor,
249
- model: rawModel.length > 0 ? rawModel : "default",
250
- }, DEFAULT_LAUNCH_PROFILE);
237
+ for (;;) {
238
+ const executorFormResult = await requestUserInput(launchProfileSelectionForm());
239
+ const rawExecutor = String(executorFormResult.values.executor ?? DEFAULT_LAUNCH_PROFILE.executor);
240
+ const executor = LLM_EXECUTOR_IDS.find((id) => id === rawExecutor);
241
+ if (!executor) {
242
+ throw new TaskRunnerError(`Unsupported launch executor '${rawExecutor}'.`);
243
+ }
244
+ try {
245
+ const modelFormResult = await requestUserInput(launchModelSelectionForm(executor));
246
+ const rawModel = String(modelFormResult.values.model ?? defaultModelForExecutor(executor)).trim();
247
+ return resolveLaunchProfile({
248
+ executor,
249
+ model: rawModel.length > 0 ? rawModel : defaultModelForExecutor(executor),
250
+ }, DEFAULT_LAUNCH_PROFILE);
251
+ }
252
+ catch (error) {
253
+ if (isFormCancellation(error, "flow-launch-model")) {
254
+ continue;
255
+ }
256
+ throw error;
257
+ }
258
+ }
251
259
  }
252
260
  function buildResolverContext(pipelineContext, flowParams, flowConstants, repeatVars, executionState) {
253
261
  return {
@@ -429,6 +437,7 @@ function buildBaseConfig(command, options = {}) {
429
437
  mdLang: options.mdLang ?? null,
430
438
  dryRun: options.dryRun ?? false,
431
439
  verbose: options.verbose ?? false,
440
+ ...(options.doctorArgs !== undefined ? { doctorArgs: options.doctorArgs } : {}),
432
441
  };
433
442
  }
434
443
  function commandRequiresTask(command) {
@@ -521,13 +530,13 @@ const FLOW_DESCRIPTIONS = {
521
530
  "bug-analyze": "Analyzes bug from Jira and creates structured artifacts: root cause hypothesis, fix design, and implementation plan.",
522
531
  "git-commit": "Collects git status/diff, generates commit message via LLM, allows file selection and commit confirmation.",
523
532
  "gitlab-diff-review": "Requests GitLab MR URL via user-input, downloads merge request diff via API, and runs code review with markdown and structured JSON artifacts.",
524
- "gitlab-review": "Requests GitLab MR URL via user-input, downloads code review comments via API, and saves markdown plus structured JSON artifact.",
533
+ "gitlab-review": "Requests GitLab MR URL via user-input, downloads code review comments via API, assesses which findings are fair and proposes fixes, then runs review-fix for the selected findings.",
525
534
  "bug-fix": "Takes bug-analyze results as source of truth and implements the bug fix in code.",
526
535
  "mr-description": "Prepares a brief intent description for a merge request based on the task and current changes.",
527
536
  plan: "Loads task from Jira and creates design, implementation plan, and QA plan in structured JSON and markdown.",
528
537
  "task-describe": "Builds a brief task description either from Jira or from quick user-input without Jira.",
529
538
  implement: "Implements the task from approved design/plan artifacts and runs post-verify builds if needed.",
530
- review: "Runs code review of current changes, validates structured findings, then prepares a reply to comments.",
539
+ review: "Runs code review of current changes and writes structured findings artifacts.",
531
540
  "review-fix": "Fixes issues after review-reply, updates code, and runs mandatory checks after modifications.",
532
541
  "review-loop": "Iteratively runs review and review-fix cycles up to 5 times until ready-to-merge is achieved.",
533
542
  "run-go-tests-loop": "Cycles through `./run_go_tests.py` locally, analyzes the last error, and fixes code until successful or attempts exhausted.",
@@ -750,6 +759,10 @@ function requireJiraConfig(config) {
750
759
  }
751
760
  }
752
761
  async function executeCommand(baseConfig, runFollowupVerify = true, requestUserInput = requestUserInputInTerminal, resolvedScope, setSummary, forceRefreshSummary = false, launchMode = "restart", launchProfile, runtime = runtimeServices) {
762
+ if (baseConfig.command === "doctor") {
763
+ const exitCode = await runDoctorCommand(baseConfig.doctorArgs ?? []);
764
+ return exitCode === 0;
765
+ }
753
766
  const config = buildRuntimeConfig(baseConfig, resolvedScope ?? (await resolveScopeForCommand(baseConfig, requestUserInput)));
754
767
  if (config.command === "auto-golang") {
755
768
  requireJiraConfig(config);
@@ -880,9 +893,11 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
880
893
  iteration,
881
894
  gitlabReviewIteration,
882
895
  extraPrompt: config.extraPrompt,
896
+ reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, iteration),
897
+ reviewFixPoints: config.reviewFixPoints,
883
898
  }, launchProfile ? { launchProfile } : {}, requestUserInput, undefined, launchMode, runtime);
884
899
  if (!config.dryRun) {
885
- printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}`);
900
+ printSummary("GitLab Review", `Artifacts:\n${gitlabReviewFile(config.taskKey)}\n${gitlabReviewJsonFile(config.taskKey)}\n${reviewFile(config.taskKey, iteration)}\n${reviewJsonFile(config.taskKey, iteration)}\n${reviewAssessmentFile(config.taskKey, iteration)}\n${reviewAssessmentJsonFile(config.taskKey, iteration)}`);
886
901
  }
887
902
  return false;
888
903
  }
@@ -982,6 +997,7 @@ async function executeCommand(baseConfig, runFollowupVerify = true, requestUserI
982
997
  await runDeclarativeFlowBySpecFile("review/review-fix.json", config, {
983
998
  taskKey: config.taskKey,
984
999
  latestIteration,
1000
+ reviewAssessmentJsonFile: null,
985
1001
  reviewFixSelectionJsonFile: reviewFixSelectionJsonFile(config.taskKey, latestIteration),
986
1002
  extraPrompt: config.extraPrompt,
987
1003
  reviewFixPoints: config.reviewFixPoints,
@@ -1050,6 +1066,7 @@ function parseCliArgs(argv) {
1050
1066
  let helpPhases = false;
1051
1067
  let jiraRef;
1052
1068
  let mdLang;
1069
+ const doctorArgs = [];
1053
1070
  for (let index = 1; index < argv.length; index += 1) {
1054
1071
  const token = argv[index] ?? "";
1055
1072
  if (token === "--dry") {
@@ -1102,7 +1119,12 @@ function parseCliArgs(argv) {
1102
1119
  }
1103
1120
  continue;
1104
1121
  }
1105
- jiraRef = token;
1122
+ if (command === "doctor") {
1123
+ doctorArgs.push(token);
1124
+ }
1125
+ else {
1126
+ jiraRef = token;
1127
+ }
1106
1128
  }
1107
1129
  if (command === "auto-golang" && helpPhases) {
1108
1130
  printAutoPhasesHelp();
@@ -1122,6 +1144,7 @@ function parseCliArgs(argv) {
1122
1144
  ...(prompt !== undefined ? { prompt } : {}),
1123
1145
  ...(autoFromPhase !== undefined ? { autoFromPhase } : {}),
1124
1146
  ...(mdLang !== undefined ? { mdLang } : {}),
1147
+ ...(doctorArgs.length > 0 ? { doctorArgs } : {}),
1125
1148
  };
1126
1149
  }
1127
1150
  function buildConfigFromArgs(args) {
@@ -1133,6 +1156,7 @@ function buildConfigFromArgs(args) {
1133
1156
  ...(args.mdLang !== undefined ? { mdLang: args.mdLang } : {}),
1134
1157
  dryRun: args.dry,
1135
1158
  verbose: args.verbose,
1159
+ ...(args.doctorArgs !== undefined ? { doctorArgs: args.doctorArgs } : {}),
1136
1160
  });
1137
1161
  }
1138
1162
  async function runInteractive(jiraRef, forceRefresh = false, scopeName) {