@sireai/optimus 0.1.18 → 0.1.21

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 (34) hide show
  1. package/README.md +7 -5
  2. package/dist/cli/optimus.js +377 -283
  3. package/dist/cli/optimus.js.map +1 -1
  4. package/dist/config/load-config.js +1 -0
  5. package/dist/config/load-config.js.map +1 -1
  6. package/dist/integrations/jira/jira-access-manager.d.ts +34 -0
  7. package/dist/integrations/jira/jira-access-manager.js +140 -0
  8. package/dist/integrations/jira/jira-access-manager.js.map +1 -0
  9. package/dist/integrations/jira/jira-cli.js +13 -23
  10. package/dist/integrations/jira/jira-cli.js.map +1 -1
  11. package/dist/integrations/jira/jira-client.js +2 -0
  12. package/dist/integrations/jira/jira-client.js.map +1 -1
  13. package/dist/integrations/jira/jira-submit.d.ts +2 -1
  14. package/dist/integrations/jira/jira-submit.js +15 -14
  15. package/dist/integrations/jira/jira-submit.js.map +1 -1
  16. package/dist/problem-solving-core/codex/codex-auth-resolver.js +1 -1
  17. package/dist/problem-solving-core/codex/codex-auth-resolver.js.map +1 -1
  18. package/dist/problem-solving-core/codex/codex-failure-classifier.js +1 -1
  19. package/dist/task-environment/delivery/task-delivery-service.d.ts +5 -0
  20. package/dist/task-environment/delivery/task-delivery-service.js +7 -2
  21. package/dist/task-environment/delivery/task-delivery-service.js.map +1 -1
  22. package/dist/task-environment/delivery/task-publication-service.d.ts +1 -0
  23. package/dist/task-environment/delivery/task-publication-service.js +52 -44
  24. package/dist/task-environment/delivery/task-publication-service.js.map +1 -1
  25. package/dist/task-environment/intake/triage-rejection-feedback-service.d.ts +0 -1
  26. package/dist/task-environment/intake/triage-rejection-feedback-service.js +38 -32
  27. package/dist/task-environment/intake/triage-rejection-feedback-service.js.map +1 -1
  28. package/dist/task-environment/orchestration/task-orchestrator.js +4 -2
  29. package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
  30. package/dist/task-environment/orchestration/triage-runner.d.ts +1 -0
  31. package/dist/task-environment/orchestration/triage-runner.js +10 -2
  32. package/dist/task-environment/orchestration/triage-runner.js.map +1 -1
  33. package/dist/types.d.ts +1 -0
  34. package/package.json +1 -1
@@ -12,10 +12,8 @@ import { CodexRunner } from "../problem-solving-core/codex/codex-runner.js";
12
12
  import { CodexAuthResolver } from "../problem-solving-core/codex/codex-auth-resolver.js";
13
13
  import { SkillSyncService } from "../problem-solving-core/codex/skill-sync-service.js";
14
14
  import { RepoMemoryService } from "../problem-solving-core/codex/repo-memory-service.js";
15
- import { resolvePrimaryCodexApiKeyEnvName, resolveRequiredCodexEnvVars } from "../problem-solving-core/codex/codex-required-env.js";
16
- import { resolveCodexAuthMode, resolveConfiguredCodexModelProvider, resolveConfiguredCodexProvider, resolveEffectiveCodexModel } from "../problem-solving-core/codex/codex-provider-profile.js";
15
+ import { resolveCodexAuthMode, resolveConfiguredCodexProvider } from "../problem-solving-core/codex/codex-provider-profile.js";
17
16
  import { classifyCodexFailureCategory } from "../problem-solving-core/codex/codex-failure-classifier.js";
18
- import { CodexPreflight } from "../problem-solving-core/codex/codex-preflight.js";
19
17
  import { createManualEventContent, createManualProblemEvent } from "../task-environment/intake/manual-problem-intake.js";
20
18
  import { OptimusLogger } from "../task-environment/observability/logger.js";
21
19
  import { TaskOrchestrator } from "../task-environment/orchestration/task-orchestrator.js";
@@ -33,8 +31,8 @@ import { FeishuClient } from "../integrations/feishu/feishu-client.js";
33
31
  import { FeishuUserService } from "../integrations/feishu/feishu-user-service.js";
34
32
  import { createFeedbackReport } from "./feedback.js";
35
33
  import { DEFAULT_FEEDBACK_RECIPIENT_EMAIL, FeishuFeedbackDeliveryService, persistFeedbackDeliveryResult, resolveDefaultFeedbackRecipientFallbackOpenId } from "./feedback-delivery.js";
36
- import { resolveDefaultConfigPath, resolveDefaultEnvPath, resolveOptimusHomeDir } from "../config/optimus-paths.js";
37
- import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate, readSelfUpdateState, shouldPromptForCachedStartupUpdate } from "./self-update.js";
34
+ import { resolveDefaultConfigPath, resolveDefaultEnvPath } from "../config/optimus-paths.js";
35
+ import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate, shouldPromptForCachedStartupUpdate } from "./self-update.js";
38
36
  const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
39
37
  const PACKAGE_JSON_PATH = join(CLI_ROOT_DIR, "package.json");
40
38
  const execFileAsync = promisify(execFile);
@@ -42,16 +40,28 @@ const PACKAGE_NAME = "@sireai/optimus";
42
40
  const JIRA_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "jira", "jira-cli.js");
43
41
  const SENTRY_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "sentry", "sentry-cli.js");
44
42
  const RELEASE_NOTES_URL = "https://github.com/SireAI/optimus/releases/latest";
43
+ const DOCTOR_ANSI = {
44
+ reset: "\u001B[0m",
45
+ green: "\u001B[32m",
46
+ yellow: "\u001B[33m",
47
+ red: "\u001B[31m",
48
+ gray: "\u001B[90m"
49
+ };
45
50
  function renderSetupResult(result) {
46
51
  const lines = [];
47
52
  lines.push("Setup Complete");
48
53
  lines.push(`Config: ${result.configPath}`);
49
54
  lines.push(`Env: ${result.envPath}`);
50
55
  lines.push(`Status: ${result.configStatus}`);
51
- lines.push(`Repository: ${result.repository.alias} (${result.repository.path})`);
52
- lines.push(`Workspace: ${result.repository.workspaceKind}`);
53
- lines.push(`Review: ${result.repository.reviewSystem}`);
54
- lines.push(`Mode: ${result.repository.executionMode}`);
56
+ if (result.repository) {
57
+ lines.push(`Repository: ${result.repository.alias} (${result.repository.path})`);
58
+ lines.push(`Workspace: ${result.repository.workspaceKind}`);
59
+ lines.push(`Review: ${result.repository.reviewSystem}`);
60
+ lines.push(`Mode: ${result.repository.executionMode}`);
61
+ }
62
+ else {
63
+ lines.push("Repository: not configured");
64
+ }
55
65
  lines.push(`Delivery: ${result.deliveryChannels.join(", ")}`);
56
66
  lines.push(`Jira: ${result.jiraEnabled ? "enabled" : "disabled"}`);
57
67
  lines.push(`Sentry: ${result.sentryEnabled ? "enabled" : "disabled"}`);
@@ -67,7 +77,7 @@ function renderSetupResult(result) {
67
77
  lines.push("Next");
68
78
  lines.push("- optimus start");
69
79
  lines.push("- optimus submit --title \"Login crash\" --description \"Crash on startup\"");
70
- lines.push("- optimus doctor --quick");
80
+ lines.push("- optimus doctor");
71
81
  lines.push("");
72
82
  lines.push(renderQuickDoctorReport(result.doctorQuick));
73
83
  return lines.join("\n");
@@ -112,28 +122,96 @@ function renderQuickDoctorReport(report) {
112
122
  }
113
123
  function renderDoctorTextReport(doctor) {
114
124
  const lines = [];
115
- lines.push("Doctor");
116
- lines.push(`Runtime: tasks=${doctor.runtime.totalTasks} active=${doctor.runtime.activeRuns} stalled=${doctor.runtime.stalledRuns}`);
117
- lines.push(`Repositories: registered=${doctor.registeredRepositories.length}`);
118
- lines.push(`Codex: model=${doctor.codex.model ?? "unknown"} auth=${doctor.codex.authMode ?? "unknown"}`);
119
- if (doctor.codex.provider) {
120
- const provider = typeof doctor.codex.provider === "string"
121
- ? doctor.codex.provider
122
- : `${doctor.codex.provider.displayName ?? doctor.codex.provider.id ?? "unknown"}`;
123
- lines.push(`Provider: ${provider}`);
124
- }
125
- lines.push(`Triage: ${doctor.triage.status?.ok === false ? "not_ready" : "ready_or_unknown"}`);
126
- if (doctor.selfUpdate) {
127
- lines.push(`Self Update: current=${doctor.selfUpdate.currentVersion}`
128
- + ` latest=${doctor.selfUpdate.cachedState?.latestVersion ?? "unknown"}`
129
- + ` channel=${doctor.selfUpdate.cachedState?.channel ?? "latest"}`
130
- + ` mode=${doctor.selfUpdate.config.mode}`
131
- + ` source=${doctor.selfUpdate.cachedState?.installSource ?? "unknown"}`
132
- + ` status=${doctor.selfUpdate.cachedState?.lastCheckStatus ?? "never_checked"}`);
133
- }
134
- lines.push(`Runtime Root: ${doctor.runtimeRootDir}`);
125
+ lines.push("Optimus Doctor");
126
+ lines.push("");
127
+ lines.push(`Overall: ${doctor.overall}`);
128
+ lines.push("");
129
+ lines.push(...renderDoctorSection("Repository", doctor.repository, doctor.repository.using ? [`Using: ${doctor.repository.using}`] : []));
130
+ lines.push("");
131
+ lines.push(...renderDoctorSection("Codex", doctor.codex, renderDoctorSubstatusLines(doctor.codex)));
132
+ lines.push("");
133
+ lines.push(...renderDoctorSection("Delivery", doctor.delivery));
134
+ lines.push("");
135
+ lines.push("Integrations:");
136
+ for (const integration of doctor.integrations) {
137
+ lines.push(` ${formatDoctorStatusHeading(integration.name, integration.status)}`);
138
+ if (integration.reason) {
139
+ lines.push(` Reason: ${integration.reason}`);
140
+ }
141
+ if (integration.fix) {
142
+ lines.push(` Fix: ${integration.fix}`);
143
+ }
144
+ }
145
+ if (doctor.nextActions.length > 0) {
146
+ lines.push("");
147
+ lines.push("Next actions:");
148
+ doctor.nextActions.forEach((action, index) => {
149
+ lines.push(`${index + 1}. ${action}`);
150
+ });
151
+ }
135
152
  return lines.join("\n");
136
153
  }
154
+ function renderDoctorSection(title, section, extraLines = []) {
155
+ const lines = [formatDoctorStatusHeading(title, section.status)];
156
+ if (section.reason) {
157
+ lines.push(`Reason: ${section.reason}`);
158
+ }
159
+ if (section.fix) {
160
+ lines.push(`Fix: ${section.fix}`);
161
+ }
162
+ lines.push(...extraLines);
163
+ return lines;
164
+ }
165
+ function renderDoctorSubstatusLines(section) {
166
+ const lines = [];
167
+ lines.push(...renderDoctorSubstatus("Auth", section.auth));
168
+ lines.push(...renderDoctorSubstatus("Connectivity", section.connectivity));
169
+ return lines;
170
+ }
171
+ function renderDoctorSubstatus(label, section) {
172
+ const lines = [`${label}: ${formatDoctorStatusMarker(section.status)}`];
173
+ if (section.reason) {
174
+ lines.push(` Reason: ${section.reason}`);
175
+ }
176
+ if (section.fix) {
177
+ lines.push(` Fix: ${section.fix}`);
178
+ }
179
+ return lines;
180
+ }
181
+ function formatDoctorStatusHeading(title, status) {
182
+ return `${formatDoctorStatusMarker(status)} ${title}`;
183
+ }
184
+ function formatDoctorStatusMarker(status) {
185
+ const marker = resolveDoctorStatusMarker(status);
186
+ if (!output.isTTY) {
187
+ return marker;
188
+ }
189
+ return `${resolveDoctorStatusColor(status)}${marker}${DOCTOR_ANSI.reset}`;
190
+ }
191
+ function resolveDoctorStatusMarker(status) {
192
+ switch (status) {
193
+ case "ok":
194
+ return "[✓]";
195
+ case "warning":
196
+ return "[!]";
197
+ case "not_ok":
198
+ return "[x]";
199
+ case "disabled":
200
+ return "[-]";
201
+ }
202
+ }
203
+ function resolveDoctorStatusColor(status) {
204
+ switch (status) {
205
+ case "ok":
206
+ return DOCTOR_ANSI.green;
207
+ case "warning":
208
+ return DOCTOR_ANSI.yellow;
209
+ case "not_ok":
210
+ return DOCTOR_ANSI.red;
211
+ case "disabled":
212
+ return DOCTOR_ANSI.gray;
213
+ }
214
+ }
137
215
  const SETUP_CODEX_AUTH_MODE_OPTIONS = [
138
216
  { index: "1", mode: "model_provider", label: "Model provider" },
139
217
  { index: "2", mode: "openai_api_key", label: "OpenAI API key" },
@@ -148,25 +226,19 @@ function renderCliHelp() {
148
226
  "",
149
227
  "First time here:",
150
228
  " 1. optimus setup",
151
- " 2. optimus start",
152
- " 3. optimus submit --title \"Login crash\" --description \"Crash on startup\"",
229
+ " 2. optimus doctor",
230
+ " 3. optimus start",
153
231
  "",
154
- "Everyday commands:",
155
- " setup First-time setup or update config",
156
- " start Start the resident runtime",
232
+ "Common commands:",
157
233
  " submit Submit a problem for Optimus to handle",
158
- " task-status Check what is running now",
159
- " task-result Read the latest result for a task",
160
- " doctor --quick Quick readiness check",
234
+ " jira-submit-issue Submit a Jira issue into Optimus",
235
+ " sentry-submit-event Submit a Sentry event into Optimus",
236
+ " repo list Show registered repositories",
161
237
  " version Show the installed package version",
162
238
  "",
163
239
  "Need more?",
164
240
  " optimus help advanced Show integration, retry, and developer commands",
165
- " optimus help <command> Show details for one command",
166
- "",
167
- "Notes:",
168
- ` - Optimus stores config and runtime data under ${resolveOptimusHomeDir()}.`,
169
- " - Jira and Sentry are optional integrations. You can enable them later in `optimus setup`."
241
+ " optimus help <command> Show details for one command"
170
242
  ].join("\n");
171
243
  }
172
244
  function renderAdvancedCliHelp() {
@@ -186,9 +258,8 @@ function renderAdvancedCliHelp() {
186
258
  " task-status Show task execution status",
187
259
  " task-result Show persisted result, delivery, and publication data",
188
260
  " task-events Show task timeline",
189
- " repo add|remove|update|list|doctor",
190
- " doctor Show runtime, skills, and environment diagnostics",
191
- " health-check Validate Codex/provider readiness",
261
+ " repo add|remove|update|list",
262
+ " doctor Check repository, Codex, delivery, and integrations in one place",
192
263
  " upgrade Check for or install a latest/snapshot Optimus update",
193
264
  " version Show the installed package version",
194
265
  "",
@@ -328,7 +399,6 @@ function renderCommandHelp(command) {
328
399
  " optimus repo remove <path|alias>",
329
400
  " optimus repo update <path|alias> [options]",
330
401
  " optimus repo list",
331
- " optimus repo doctor",
332
402
  "",
333
403
  "Common options:",
334
404
  " --alias <name> Repository alias",
@@ -346,9 +416,7 @@ function renderCommandHelp(command) {
346
416
  " optimus doctor [options]",
347
417
  "",
348
418
  "Optional:",
349
- " --quick Run compact readiness check",
350
- " --json Print JSON when used with --quick",
351
- " --format text Print text report"
419
+ " --json Print JSON output for automation"
352
420
  ].join("\n"),
353
421
  advanced: renderAdvancedCliHelp(),
354
422
  "notify-test": [
@@ -408,7 +476,8 @@ function renderCommandHelp(command) {
408
476
  " --branch <name> Override target branch",
409
477
  " --task-type <type> Task type, default bugfix",
410
478
  " --allow-duplicate Allow duplicate submission",
411
- " --comment-limit <n> Comment lines fetched from Jira, default 3",
479
+ " --with-comments Include recent Jira comments in submit context",
480
+ " --comment-limit <n> Comment lines fetched from Jira when --with-comments is set, default 3",
412
481
  " --dry-run Preview without creating a task",
413
482
  "",
414
483
  "Example:",
@@ -425,6 +494,7 @@ function renderCommandHelp(command) {
425
494
  " --repo <alias> Target registered repository alias",
426
495
  " --projects-filter <csv> Restrict Jira projects",
427
496
  " --limit <n> Max issues to submit, default 50",
497
+ " --with-comments Include recent Jira comments in submit context",
428
498
  " --allow-duplicate Allow duplicate submission",
429
499
  " --dry-run Preview without creating tasks"
430
500
  ].join("\n"),
@@ -940,7 +1010,7 @@ async function buildFeishuDoctorStatus(config) {
940
1010
  : "Real @mentions require Feishu app availability.",
941
1011
  ...(mentionReady
942
1012
  ? {}
943
- : { recommendation: "Rerun `optimus doctor --quick` after restoring Feishu app availability." })
1013
+ : { recommendation: "Rerun `optimus doctor` after restoring Feishu app availability." })
944
1014
  }
945
1015
  };
946
1016
  }
@@ -1065,16 +1135,16 @@ async function promptSetupAnswers(defaults) {
1065
1135
  try {
1066
1136
  console.log("Optimus setup");
1067
1137
  console.log("This usually takes about 2 minutes.");
1068
- console.log("Required: repository + Codex auth. Optional: Feishu webhook, Jira, Sentry.");
1138
+ console.log("Required: Codex auth. Optional: repository, Feishu webhook, Jira, Sentry.");
1069
1139
  console.log("You can rerun `optimus setup` later to change anything.");
1070
1140
  if (await pathExists(configPath)) {
1071
1141
  console.log(`Existing config found at ${configPath}. Press Enter to keep current values.`);
1072
1142
  }
1073
- printSetupSection("Repository", "Point Optimus at the main local repository it should work in.");
1074
- printSetupHint("Repository path: the local code directory Optimus will read and edit.");
1075
- const repoPath = (await ask(renderSetupPrompt("Required", "Repository path", defaults.repoPath))).trim() || defaults.repoPath;
1076
- printSetupHint("Repository alias: the short name used later in commands like --repo ohos-pre.");
1077
- const repoAlias = (await ask(renderSetupPrompt("Required", "Repository alias", defaults.repoAlias))).trim() || defaults.repoAlias;
1143
+ printSetupSection("Repository", "Optional. Point Optimus at the main local repository it should work in, or leave both fields empty and add one later.");
1144
+ printSetupHint("Repository path: the local code directory Optimus will read and edit. Leave empty if you only want to finish setup first.");
1145
+ const repoPath = (await ask(renderSetupPrompt("Optional", "Repository path", defaults.repoPath))).trim() || defaults.repoPath;
1146
+ printSetupHint("Repository alias: the short name used later in commands like --repo ohos-pre. Leave empty if no repository should be registered now.");
1147
+ const repoAlias = (await ask(renderSetupPrompt("Optional", "Repository alias", defaults.repoAlias))).trim() || defaults.repoAlias;
1078
1148
  printSetupSection("Codex", "Choose one model authentication mode. This is required before tasks can run.");
1079
1149
  printSetupHint("Codex auth mode: choose how Optimus reaches the model you want to use.");
1080
1150
  const codexAuthMode = await askSetupCodexAuthMode({ ask, defaultMode: defaults.codexAuthMode });
@@ -1103,8 +1173,6 @@ async function promptSetupAnswers(defaults) {
1103
1173
  printSetupSection("Feishu", "Built-in Feishu app delivery is already available. Add a webhook only if group notifications should go to a chat first. Type \"\" to clear an existing optional value.");
1104
1174
  printSetupHint("Feishu webhook: optional; leave empty to use direct Feishu app delivery only.");
1105
1175
  const feishuWebhook = resolveOptionalTextAnswer(await ask(renderSetupPrompt("Optional", "Feishu webhook", defaults.feishuWebhook ? "configured" : "")), defaults.feishuWebhook ?? undefined);
1106
- printSetupHint("Feishu secret: optional; only needed when the webhook itself requires signature verification.");
1107
- const feishuSecret = resolveOptionalTextAnswer(await ask(renderSetupPrompt("Optional", "Feishu secret", defaults.feishuSecret ? "configured" : "")), defaults.feishuSecret ?? undefined);
1108
1176
  printSetupSection("Jira", "Optional. Turn this on only if you want this machine to read Jira issues or write Jira comments.");
1109
1177
  printSetupHint("If disabled, Optimus still works; Jira commands just stay unavailable on this machine.");
1110
1178
  const enableJiraInput = (await ask(renderSetupPrompt("Optional integration", "Enable Jira integration", defaults.enableJira ? "Y/n" : "y/N"))).trim().toLowerCase();
@@ -1145,7 +1213,6 @@ async function promptSetupAnswers(defaults) {
1145
1213
  ...(codexProviderBaseUrl ? { codexProviderBaseUrl } : {}),
1146
1214
  ...(codexProviderApiKeyEnvName ? { codexProviderApiKeyEnvName } : {}),
1147
1215
  ...(feishuWebhook !== undefined ? { feishuWebhook } : {}),
1148
- ...(feishuSecret !== undefined ? { feishuSecret } : {}),
1149
1216
  enableJira,
1150
1217
  ...(jiraBaseUrl ? { jiraBaseUrl } : {}),
1151
1218
  ...(jiraPersonalToken ? { jiraPersonalToken } : {}),
@@ -1196,7 +1263,6 @@ async function resolveDefaultSetupAnswers() {
1196
1263
  ...(provider?.baseUrl ? { codexProviderBaseUrl: provider.baseUrl } : {}),
1197
1264
  ...(provider?.apiKeyEnvName ? { codexProviderApiKeyEnvName: provider.apiKeyEnvName } : {}),
1198
1265
  ...(config.delivery.feishu.webhook ? { feishuWebhook: config.delivery.feishu.webhook } : {}),
1199
- ...(config.delivery.feishu.secret ? { feishuSecret: config.delivery.feishu.secret } : {}),
1200
1266
  enableJira: config.jira.enabled,
1201
1267
  ...(config.jira.baseUrl ? { jiraBaseUrl: config.jira.baseUrl } : {}),
1202
1268
  ...(config.jira.personalToken ? { jiraPersonalToken: config.jira.personalToken } : {}),
@@ -1248,11 +1314,10 @@ async function askSetupCodexAuthMode(input) {
1248
1314
  }
1249
1315
  }
1250
1316
  function validateSetupAnswers(answers) {
1251
- if (!answers.repoPath.trim()) {
1252
- return "setup requires a repository path.";
1253
- }
1254
- if (!answers.repoAlias.trim()) {
1255
- return "setup requires a repository alias.";
1317
+ const repoPath = answers.repoPath.trim();
1318
+ const repoAlias = answers.repoAlias.trim();
1319
+ if ((repoPath && !repoAlias) || (!repoPath && repoAlias)) {
1320
+ return "setup repository registration requires both repository path and repository alias, or neither.";
1256
1321
  }
1257
1322
  if (answers.codexAuthMode === "model_provider" && !answers.codexProviderId?.trim()) {
1258
1323
  return "setup requires provider id when Codex auth mode is model_provider.";
@@ -1384,16 +1449,15 @@ function buildSetupConfig(answers, rawConfig) {
1384
1449
  config.delivery.channels = ["console", "feishu"];
1385
1450
  config.delivery.feishu = {
1386
1451
  ...config.delivery.feishu,
1387
- ...(typeof answers.feishuWebhook === "string" ? { webhook: answers.feishuWebhook } : {}),
1388
- ...(typeof answers.feishuSecret === "string" ? { secret: answers.feishuSecret } : {})
1452
+ ...(typeof rawConfig?.delivery?.feishu?.secret === "string" && rawConfig.delivery.feishu.secret.trim()
1453
+ ? { secret: rawConfig.delivery.feishu.secret.trim() }
1454
+ : {}),
1455
+ ...(typeof answers.feishuWebhook === "string" ? { webhook: answers.feishuWebhook } : {})
1389
1456
  };
1390
1457
  if (answers.feishuWebhook === null) {
1391
1458
  delete config.delivery.feishu.webhook;
1392
1459
  delete config.delivery.feishu.webhooks;
1393
1460
  }
1394
- if (answers.feishuSecret === null) {
1395
- delete config.delivery.feishu.secret;
1396
- }
1397
1461
  config.jira.enabled = answers.enableJira;
1398
1462
  if (answers.enableJira) {
1399
1463
  config.jira.baseUrl = answers.jiraBaseUrl ?? config.jira.baseUrl;
@@ -1551,12 +1615,12 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1551
1615
  await store.init();
1552
1616
  const registeredRepositories = await store.listRepositoryRoots();
1553
1617
  if (registeredRepositories.length === 0) {
1554
- blocking.push({
1618
+ warnings.push({
1555
1619
  code: "repository_missing",
1556
1620
  message: "No repository is registered.",
1557
- fix: "Rerun `optimus setup` and provide a repository path and alias."
1621
+ fix: "Register one with `optimus repo add <path> --alias <name>` or rerun `optimus setup`."
1558
1622
  });
1559
- next.add("optimus setup");
1623
+ next.add("optimus repo add <path> --alias <name>");
1560
1624
  }
1561
1625
  for (const repository of registeredRepositories) {
1562
1626
  const inspection = await inspectRepositoryRoot(repository.path);
@@ -1587,7 +1651,7 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1587
1651
  blocking.push({
1588
1652
  code: "git_user_name_missing",
1589
1653
  message: "Git user.name is missing for the configured repository.",
1590
- fix: "Run `git config user.name \"Your Name\"` in the repository, then rerun `optimus doctor --quick`."
1654
+ fix: "Run `git config user.name \"Your Name\"` in the repository, then rerun `optimus doctor`."
1591
1655
  });
1592
1656
  next.add("git config user.name \"Your Name\"");
1593
1657
  }
@@ -1595,7 +1659,7 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1595
1659
  blocking.push({
1596
1660
  code: "git_user_email_missing",
1597
1661
  message: "Git user.email is missing for the configured repository.",
1598
- fix: "Run `git config user.email \"you@example.com\"` in the repository, then rerun `optimus doctor --quick`."
1662
+ fix: "Run `git config user.email \"you@example.com\"` in the repository, then rerun `optimus doctor`."
1599
1663
  });
1600
1664
  next.add("git config user.email \"you@example.com\"");
1601
1665
  }
@@ -1640,6 +1704,196 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1640
1704
  next: [...next]
1641
1705
  };
1642
1706
  }
1707
+ function summarizeIssue(issue, fallback) {
1708
+ if (!issue?.message) {
1709
+ return fallback;
1710
+ }
1711
+ return issue.message.replace(/\.$/u, "");
1712
+ }
1713
+ function doctorNextActions(quick, healthCheck, delivery) {
1714
+ const actions = new Set();
1715
+ for (const step of quick.next) {
1716
+ actions.add(step);
1717
+ }
1718
+ if (!healthCheck.ok) {
1719
+ const fixHint = healthCheck.diagnostics.auth?.fixHint?.trim();
1720
+ if (fixHint) {
1721
+ actions.add(fixHint);
1722
+ }
1723
+ else {
1724
+ actions.add("Rerun `optimus setup`.");
1725
+ }
1726
+ }
1727
+ if (delivery.status === "not_ok" && delivery.fix) {
1728
+ actions.add(delivery.fix);
1729
+ }
1730
+ if (actions.size === 0) {
1731
+ actions.add("Run `optimus start`.");
1732
+ actions.add("Submit a task with `optimus submit --title \"...\" --description \"...\"`.");
1733
+ }
1734
+ return [...actions].slice(0, 3);
1735
+ }
1736
+ async function runDoctor(configPath = resolveDefaultConfigPath()) {
1737
+ const quick = await runQuickDoctor(configPath);
1738
+ const configIssue = quick.blocking.find((issue) => issue.code === "config_missing" || issue.code === "config_parse_failed" || issue.code === "config_load_failed");
1739
+ if (configIssue) {
1740
+ const repoIssue = quick.blocking.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"));
1741
+ const codexIssue = quick.blocking.find((issue) => issue.code.startsWith("codex_"));
1742
+ return {
1743
+ overall: "not_ready",
1744
+ repository: {
1745
+ status: repoIssue ? "not_ok" : "warning",
1746
+ ...(repoIssue ? { reason: summarizeIssue(repoIssue, "Repository setup is incomplete.") } : { reason: "Repository checks could not complete yet." }),
1747
+ ...(repoIssue?.fix ? { fix: repoIssue.fix } : {})
1748
+ },
1749
+ codex: {
1750
+ status: codexIssue ? "not_ok" : "warning",
1751
+ ...(codexIssue ? { reason: summarizeIssue(codexIssue, "Codex setup is incomplete.") } : { reason: "Codex checks could not complete yet." }),
1752
+ ...(codexIssue?.fix ? { fix: codexIssue.fix } : {}),
1753
+ auth: {
1754
+ status: codexIssue ? "not_ok" : "warning",
1755
+ ...(codexIssue ? { reason: summarizeIssue(codexIssue, "Codex authentication is not ready.") } : { reason: "Authentication checks could not complete yet." }),
1756
+ ...(codexIssue?.fix ? { fix: codexIssue.fix } : {})
1757
+ },
1758
+ connectivity: {
1759
+ status: "disabled",
1760
+ reason: "Connectivity check is deferred until authentication is ready."
1761
+ }
1762
+ },
1763
+ delivery: {
1764
+ status: "warning",
1765
+ reason: "Delivery checks are deferred until setup is complete."
1766
+ },
1767
+ integrations: [
1768
+ {
1769
+ name: "Jira",
1770
+ status: "disabled"
1771
+ },
1772
+ {
1773
+ name: "Sentry",
1774
+ status: "disabled"
1775
+ }
1776
+ ],
1777
+ nextActions: quick.next.slice(0, 3)
1778
+ };
1779
+ }
1780
+ const config = await loadConfig(configPath);
1781
+ const codexRunner = new CodexRunner(config);
1782
+ const feishuStatus = await buildFeishuDoctorStatus(config);
1783
+ const healthCheck = await codexRunner.runHealthCheck();
1784
+ const repositoryIssue = quick.blocking.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"))
1785
+ ?? quick.warnings.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"));
1786
+ const repositoryStatus = repositoryIssue
1787
+ ? quick.blocking.includes(repositoryIssue)
1788
+ ? "not_ok"
1789
+ : "warning"
1790
+ : "ok";
1791
+ const repository = repositoryIssue
1792
+ ? {
1793
+ status: repositoryStatus,
1794
+ reason: summarizeIssue(repositoryIssue, "Repository is not ready."),
1795
+ ...(repositoryIssue.fix ? { fix: repositoryIssue.fix } : {}),
1796
+ ...(quick.repositories[0]?.alias ?? quick.repositories[0]?.path ? { using: quick.repositories[0]?.alias ?? quick.repositories[0]?.path } : {})
1797
+ }
1798
+ : {
1799
+ status: "ok",
1800
+ ...(quick.repositories[0]?.alias ?? quick.repositories[0]?.path ? { using: quick.repositories[0]?.alias ?? quick.repositories[0]?.path } : {})
1801
+ };
1802
+ const authDiagnostics = healthCheck.diagnostics.auth;
1803
+ const codexIssue = quick.blocking.find((issue) => issue.code.startsWith("codex_"));
1804
+ const codexAuth = authDiagnostics?.authenticated
1805
+ ? { status: "ok" }
1806
+ : {
1807
+ status: "not_ok",
1808
+ reason: summarizeIssue(codexIssue, healthCheck.diagnostics.failureCategory === "auth_missing"
1809
+ ? "authentication is not usable."
1810
+ : "Codex authentication is not ready."),
1811
+ fix: authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`."
1812
+ };
1813
+ const codexConnectivity = !authDiagnostics?.authenticated
1814
+ ? {
1815
+ status: "disabled",
1816
+ reason: "Connectivity check is deferred until authentication is ready."
1817
+ }
1818
+ : healthCheck.ok
1819
+ ? { status: "ok" }
1820
+ : {
1821
+ status: "not_ok",
1822
+ reason: healthCheck.diagnostics.failureCategory === "provider_error"
1823
+ ? "model provider is unreachable."
1824
+ : healthCheck.diagnostics.failureCategory === "network_error"
1825
+ ? "network connectivity to Codex is unavailable."
1826
+ : "Codex request could not complete.",
1827
+ fix: authDiagnostics.fixHint?.trim() || "Retry `optimus doctor` after fixing the Codex connection."
1828
+ };
1829
+ const codex = healthCheck.ok
1830
+ ? {
1831
+ status: "ok",
1832
+ auth: codexAuth,
1833
+ connectivity: codexConnectivity
1834
+ }
1835
+ : {
1836
+ status: "not_ok",
1837
+ reason: summarizeIssue(codexIssue, !authDiagnostics?.authenticated
1838
+ ? "authentication is not usable."
1839
+ : healthCheck.diagnostics.failureCategory === "provider_error"
1840
+ ? "model provider is unreachable."
1841
+ : "Codex is not ready."),
1842
+ fix: authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`.",
1843
+ auth: codexAuth,
1844
+ connectivity: codexConnectivity
1845
+ };
1846
+ const feishuIssue = quick.blocking.find((issue) => issue.code.startsWith("feishu_"))
1847
+ ?? quick.warnings.find((issue) => issue.code.startsWith("feishu_"));
1848
+ const delivery = !config.delivery.enabled
1849
+ ? {
1850
+ status: "warning",
1851
+ reason: "task delivery is disabled.",
1852
+ fix: "Enable delivery in config if this machine should send notifications."
1853
+ }
1854
+ : feishuIssue
1855
+ ? {
1856
+ status: "not_ok",
1857
+ reason: summarizeIssue(feishuIssue, "Feishu delivery is not usable."),
1858
+ ...(feishuIssue.fix ? { fix: feishuIssue.fix } : {})
1859
+ }
1860
+ : feishuStatus.auth.configured
1861
+ ? {
1862
+ status: "ok"
1863
+ }
1864
+ : {
1865
+ status: "warning",
1866
+ reason: "Feishu app delivery is unavailable.",
1867
+ fix: "Rerun `optimus setup` and verify Feishu configuration."
1868
+ };
1869
+ const jiraIssues = config.jira.enabled ? resolveJiraQuickDoctorIssues(config.jira) : [];
1870
+ const sentryIssues = config.sentry.enabled ? resolveSentryQuickDoctorIssues(config.sentry) : [];
1871
+ const integrations = [
1872
+ config.jira.enabled
1873
+ ? jiraIssues.length > 0
1874
+ ? { name: "Jira", status: "not_ok", reason: summarizeIssue(jiraIssues[0], "Jira is not ready."), ...(jiraIssues[0]?.fix ? { fix: jiraIssues[0].fix } : {}) }
1875
+ : { name: "Jira", status: "ok" }
1876
+ : { name: "Jira", status: "disabled" },
1877
+ config.sentry.enabled
1878
+ ? sentryIssues.length > 0
1879
+ ? { name: "Sentry", status: "not_ok", reason: summarizeIssue(sentryIssues[0], "Sentry is not ready."), ...(sentryIssues[0]?.fix ? { fix: sentryIssues[0].fix } : {}) }
1880
+ : { name: "Sentry", status: "ok" }
1881
+ : { name: "Sentry", status: "disabled" }
1882
+ ];
1883
+ const overall = repository.status === "not_ok" || codex.status === "not_ok"
1884
+ ? "not_ready"
1885
+ : delivery.status === "warning" || integrations.some((integration) => integration.status === "not_ok")
1886
+ ? "warning"
1887
+ : "ready";
1888
+ return {
1889
+ overall,
1890
+ repository,
1891
+ codex,
1892
+ delivery,
1893
+ integrations,
1894
+ nextActions: doctorNextActions(quick, healthCheck, delivery)
1895
+ };
1896
+ }
1643
1897
  async function runSetup(args) {
1644
1898
  const configPath = resolveDefaultConfigPath();
1645
1899
  const envPath = resolveDefaultEnvPath();
@@ -1653,27 +1907,34 @@ async function runSetup(args) {
1653
1907
  if (validationError) {
1654
1908
  throw new Error(validationError);
1655
1909
  }
1656
- const normalizedRepoPath = resolve(answers.repoPath);
1657
- const inspection = await inspectRepositoryRoot(normalizedRepoPath);
1658
- if (!inspection.ok) {
1659
- throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
1660
- }
1661
1910
  const preflightConfig = configExists ? await loadConfig(configPath) : buildDefaultConfig();
1662
1911
  const preflightStore = new SQLiteTaskStore(preflightConfig.storage.rootDir);
1663
1912
  await preflightStore.init();
1664
1913
  const existingRepositories = await preflightStore.listRepositoryRoots();
1665
- const aliasConflict = existingRepositories.find((repository) => repository.alias === answers.repoAlias && repository.path !== normalizedRepoPath);
1666
- if (aliasConflict) {
1667
- throw new Error(`Repository alias already exists for another path: ${answers.repoAlias} -> ${aliasConflict.path}. `
1668
- + "Use `optimus repo update` or `optimus repo remove` before re-running setup.");
1669
- }
1670
- const existingRepository = existingRepositories.find((repository) => repository.path === normalizedRepoPath);
1671
- const resolvedExecutionMode = existingRepository?.executionMode ?? describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection).resolvedDefaultMode;
1672
- const previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: resolvedExecutionMode }, inspection);
1673
- const reviewSystem = await detectRepositoryReviewSystem(normalizedRepoPath, inspection);
1914
+ const hasRepositorySelection = answers.repoPath.trim().length > 0 && answers.repoAlias.trim().length > 0;
1915
+ let normalizedRepoPath;
1916
+ let resolvedExecutionMode;
1917
+ let previewExecutionPlan;
1918
+ let reviewSystem;
1919
+ if (hasRepositorySelection) {
1920
+ normalizedRepoPath = resolve(answers.repoPath);
1921
+ const inspection = await inspectRepositoryRoot(normalizedRepoPath);
1922
+ if (!inspection.ok) {
1923
+ throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
1924
+ }
1925
+ const aliasConflict = existingRepositories.find((repository) => repository.alias === answers.repoAlias && repository.path !== normalizedRepoPath);
1926
+ if (aliasConflict) {
1927
+ throw new Error(`Repository alias already exists for another path: ${answers.repoAlias} -> ${aliasConflict.path}. `
1928
+ + "Use `optimus repo update` or `optimus repo remove` before re-running setup.");
1929
+ }
1930
+ const existingRepository = existingRepositories.find((repository) => repository.path === normalizedRepoPath);
1931
+ resolvedExecutionMode = existingRepository?.executionMode ?? describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection).resolvedDefaultMode;
1932
+ previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: resolvedExecutionMode }, inspection);
1933
+ reviewSystem = await detectRepositoryReviewSystem(normalizedRepoPath, inspection);
1934
+ }
1674
1935
  const rawConfig = configExists ? await readRawProjectConfig(configPath) : undefined;
1675
1936
  await mkdir(dirname(configPath), { recursive: true });
1676
- await writeFile(configPath, `${buildSetupConfig({ ...answers, repoPath: normalizedRepoPath }, rawConfig)}\n`, "utf8");
1937
+ await writeFile(configPath, `${buildSetupConfig({ ...answers, ...(normalizedRepoPath ? { repoPath: normalizedRepoPath } : {}) }, rawConfig)}\n`, "utf8");
1677
1938
  const codexAuthEnvName = resolveSetupCodexApiKeyEnvName(answers);
1678
1939
  if (answers.codexApiKey && codexAuthEnvName) {
1679
1940
  await mkdir(dirname(envPath), { recursive: true });
@@ -1682,11 +1943,13 @@ async function runSetup(args) {
1682
1943
  const config = await loadConfig(configPath);
1683
1944
  const store = new SQLiteTaskStore(config.storage.rootDir);
1684
1945
  await store.init();
1685
- await store.addRepositoryRoot({
1686
- path: normalizedRepoPath,
1687
- alias: answers.repoAlias,
1688
- executionMode: resolvedExecutionMode
1689
- });
1946
+ if (normalizedRepoPath && resolvedExecutionMode) {
1947
+ await store.addRepositoryRoot({
1948
+ path: normalizedRepoPath,
1949
+ alias: answers.repoAlias,
1950
+ executionMode: resolvedExecutionMode
1951
+ });
1952
+ }
1690
1953
  const doctorQuick = await runQuickDoctor(configPath);
1691
1954
  const codexAuth = new CodexAuthResolver(config).resolve().diagnostics;
1692
1955
  return {
@@ -1694,13 +1957,15 @@ async function runSetup(args) {
1694
1957
  configPath,
1695
1958
  envPath,
1696
1959
  configStatus: configExists ? "overwritten" : "created",
1697
- repository: {
1698
- path: normalizedRepoPath,
1699
- alias: answers.repoAlias,
1700
- workspaceKind: previewExecutionPlan.workspaceKind,
1701
- executionMode: resolvedExecutionMode,
1702
- reviewSystem
1703
- },
1960
+ ...(normalizedRepoPath && previewExecutionPlan && resolvedExecutionMode && reviewSystem ? {
1961
+ repository: {
1962
+ path: normalizedRepoPath,
1963
+ alias: answers.repoAlias,
1964
+ workspaceKind: previewExecutionPlan.workspaceKind,
1965
+ executionMode: resolvedExecutionMode,
1966
+ reviewSystem
1967
+ }
1968
+ } : {}),
1704
1969
  deliveryChannels: config.delivery.channels,
1705
1970
  jiraEnabled: config.jira.enabled,
1706
1971
  sentryEnabled: config.sentry.enabled,
@@ -1934,7 +2199,7 @@ async function main() {
1934
2199
  if (command === "repo") {
1935
2200
  const repoSubcommand = commandArgs.find((token) => !token.startsWith("--"))?.trim();
1936
2201
  console.log(renderCommandHelp("repo") ?? renderCliHelp());
1937
- process.exitCode = repoSubcommand && !["add", "remove", "update", "list", "doctor"].includes(repoSubcommand) ? 1 : 0;
2202
+ process.exitCode = repoSubcommand && !["add", "remove", "update", "list"].includes(repoSubcommand) ? 1 : 0;
1938
2203
  return;
1939
2204
  }
1940
2205
  console.log(renderCommandHelp(command) ?? renderCliHelp());
@@ -2018,15 +2283,15 @@ async function main() {
2018
2283
  return;
2019
2284
  }
2020
2285
  const parsedCommandArgs = parseArgs(commandArgs);
2021
- if (command === "doctor" && parsedCommandArgs.quick === "true") {
2022
- const result = await runQuickDoctor();
2286
+ if (command === "doctor") {
2287
+ const result = await runDoctor();
2023
2288
  if (parsedCommandArgs.json === "true") {
2024
2289
  console.log(JSON.stringify(result, null, 2));
2025
2290
  }
2026
2291
  else {
2027
- console.log(renderQuickDoctorReport(result));
2292
+ console.log(renderDoctorTextReport(result));
2028
2293
  }
2029
- process.exitCode = result.summary === "blocked" ? 1 : 0;
2294
+ process.exitCode = result.overall === "not_ready" ? 1 : 0;
2030
2295
  return;
2031
2296
  }
2032
2297
  let config;
@@ -2380,7 +2645,9 @@ async function main() {
2380
2645
  artifacts,
2381
2646
  deliveryBundle: existingBundle
2382
2647
  });
2383
- const deliveryService = new TaskDeliveryService();
2648
+ const deliveryService = new TaskDeliveryService({
2649
+ jiraBaseUrl: config.jira.baseUrl
2650
+ });
2384
2651
  const analysisDocService = new FeishuAnalysisDocService({ config, refStore: store });
2385
2652
  const publicationService = new TaskPublicationService(logger);
2386
2653
  const deliveryDispatcher = new TaskDeliveryDispatcher(config);
@@ -2628,22 +2895,6 @@ async function main() {
2628
2895
  console.log(JSON.stringify({ repositories }, null, 2));
2629
2896
  return;
2630
2897
  }
2631
- if (repoSubcommand === "doctor") {
2632
- const repositories = await store.listRepositoryRoots();
2633
- const inspected = await Promise.all(repositories.map(async (repository) => {
2634
- const inspection = await inspectRepositoryRoot(repository.path);
2635
- const executionPlan = describeRepositoryExecutionPlan(repository, inspection);
2636
- return {
2637
- ...repository,
2638
- inspection,
2639
- executionPlan,
2640
- resolvedDefaultMode: executionPlan.resolvedDefaultMode
2641
- };
2642
- }));
2643
- console.log(JSON.stringify({ repositories: inspected }, null, 2));
2644
- process.exitCode = inspected.every((repository) => repository.inspection.ok) ? 0 : 1;
2645
- return;
2646
- }
2647
2898
  console.error(`Unknown repo command: ${repoSubcommand}`);
2648
2899
  process.exitCode = 1;
2649
2900
  return;
@@ -2992,163 +3243,6 @@ async function main() {
2992
3243
  return;
2993
3244
  }
2994
3245
  }
2995
- if (command === "health-check") {
2996
- const result = await codexRunner.runHealthCheck();
2997
- await logger.writeHealthCheckSnapshot(result);
2998
- await logger.info("optimus.health_check", {
2999
- ok: result.ok,
3000
- model: result.diagnostics.model,
3001
- provider: result.diagnostics.provider,
3002
- failureCategory: result.diagnostics.failureCategory,
3003
- connectivityChecks: result.diagnostics.connectivityChecks?.map((check) => `${check.name}:${check.ok ? "ok" : "failed"}${check.host ? `@${check.host}` : ""}`),
3004
- timeoutMs: result.diagnostics.timeoutMs,
3005
- codexHomeDir: result.diagnostics.codexHomeDir,
3006
- approvalPolicy: result.diagnostics.approvalPolicy,
3007
- sandboxMode: result.diagnostics.sandboxMode
3008
- });
3009
- console.log(JSON.stringify(result, null, 2));
3010
- process.exitCode = result.ok ? 0 : 1;
3011
- return;
3012
- }
3013
- if (command === "doctor") {
3014
- const healthCheck = await logger.readHealthCheckSnapshot();
3015
- const triageStatus = await logger.readTriageStatusSnapshot();
3016
- const skillSyncStatus = await logger.readSkillSyncSnapshot();
3017
- const evolutionStatus = await logger.readEvolutionSnapshot();
3018
- const repoMemoryStatus = await logger.readRepoMemorySnapshot();
3019
- const feishuStatus = await buildFeishuDoctorStatus(config);
3020
- const preflight = await new CodexPreflight(config).run();
3021
- const skillSummary = await inspectBuiltinSkills(config, skillSyncService);
3022
- const repoMemorySummary = await new RepoMemoryService(config).readDoctorSummary();
3023
- await store.init();
3024
- const tasks = await store.listTasks();
3025
- const runs = await store.listTaskRuns();
3026
- const recentImportantEvents = await store.listRecentTaskEvents(15, [...DOCTOR_IMPORTANT_EVENT_TYPES]);
3027
- const taskSummary = tasks.reduce((accumulator, task) => {
3028
- accumulator[task.status] = (accumulator[task.status] ?? 0) + 1;
3029
- return accumulator;
3030
- }, {});
3031
- const activeRuns = runs.filter((run) => !run.endedAt);
3032
- const registeredRepositories = await store.listRepositoryRoots();
3033
- const configuredRuntimeRepositories = await inspectConfiguredRuntimeRepositories(config);
3034
- const repositoryDiagnostics = buildRepositoryDiagnostics(registeredRepositories, configuredRuntimeRepositories);
3035
- const doctor = {
3036
- runtimeRootDir: config.storage.rootDir,
3037
- codexHomeDir: config.codex.homeDir,
3038
- cliInboxDir: config.intake.cliInboxDir,
3039
- healthLogDir: join(config.storage.rootDir, "logs"),
3040
- codexHomeReady: await pathExists(config.codex.homeDir),
3041
- inboxReady: await pathExists(config.intake.cliInboxDir),
3042
- taskHarnessRootReady: await pathExists(config.runtime.taskHarnessRootDir),
3043
- runtimeLogFiles: await safeList(join(config.storage.rootDir, "logs")),
3044
- registeredRepositories,
3045
- configuredRuntimeRepositories,
3046
- repositoryDiagnostics,
3047
- runtime: {
3048
- totalTasks: tasks.length,
3049
- totalRuns: runs.length,
3050
- activeRuns: activeRuns.length,
3051
- stalledRuns: activeRuns.filter((run) => run.health === "suspected_stall").length,
3052
- taskSummary,
3053
- recentTasks: tasks.slice(-5).map((task) => ({
3054
- taskId: task.taskId,
3055
- taskPackageId: task.taskPackageId,
3056
- sourceEventId: task.sourceEventId ?? null,
3057
- status: task.status,
3058
- activeRunId: task.activeRunId ?? null,
3059
- updatedAt: task.updatedAt
3060
- })),
3061
- activeRunDetails: activeRuns.map((run) => {
3062
- const timing = buildRunTimingSnapshot(run, config);
3063
- return {
3064
- runId: run.runId,
3065
- taskId: run.taskId,
3066
- taskPackageId: run.taskPackageId,
3067
- status: run.status,
3068
- taskStatus: run.taskStatus,
3069
- health: run.health ?? null,
3070
- startedAt: run.startedAt,
3071
- lastEventAt: run.lastEventAt ?? null,
3072
- lastEventType: run.lastEventType ?? null,
3073
- progressCounter: run.progressCounter ?? 0,
3074
- commandCount: run.commandCount ?? 0,
3075
- fileChangeCount: run.fileChangeCount ?? 0,
3076
- sdkThreadId: run.sdkThreadId ?? null,
3077
- addresses: {
3078
- workspaceDir: run.addresses?.workspaceDir ?? null,
3079
- visibleRepoDir: run.addresses?.visibleRepoDir ?? null,
3080
- configuredExecutionMode: run.addresses?.configuredExecutionMode ?? null,
3081
- resolvedExecutionMode: run.addresses?.resolvedExecutionMode ?? null
3082
- },
3083
- elapsedMs: timing.elapsedMs,
3084
- idleMs: timing.idleMs,
3085
- timeoutKind: timing.timeoutKind,
3086
- failureCategoryLabel: describeFailureCategory(run.failureCategory),
3087
- lastEventExplanation: run.lastEventType ? explainTaskEvent(run.lastEventType) : null
3088
- };
3089
- }),
3090
- recentFailureBreakdown: buildRecentFailureBreakdown(runs),
3091
- deliveryWarningSummary: buildDeliveryWarningSummary(recentImportantEvents),
3092
- deliveryHealthSummary: await buildDeliveryHealthSummary(store, tasks, runs),
3093
- recentImportantEvents: recentImportantEvents.map((event) => ({
3094
- taskId: event.taskId,
3095
- runId: event.runId ?? null,
3096
- type: event.type,
3097
- detail: event.detail,
3098
- explanation: explainTaskEvent(event.type),
3099
- createdAt: event.createdAt,
3100
- sourceEventId: event.sourceEventId ?? null
3101
- })),
3102
- recentFailureSummary: buildRecentFailureSummary(runs, recentImportantEvents)
3103
- },
3104
- triage: {
3105
- status: triageStatus ?? null
3106
- },
3107
- codex: {
3108
- model: resolveEffectiveCodexModel(config) ?? null,
3109
- authMode: resolveCodexAuthMode(config),
3110
- provider: resolveConfiguredCodexProvider(config)
3111
- ? {
3112
- id: resolveConfiguredCodexProvider(config).providerId,
3113
- displayName: resolveConfiguredCodexProvider(config).displayName
3114
- }
3115
- : resolveConfiguredCodexModelProvider(config) ?? null,
3116
- apiKeyEnvName: resolvePrimaryCodexApiKeyEnvName(config) ?? null,
3117
- requiredEnvVars: resolveRequiredCodexEnvVars(config),
3118
- baseUrl: resolveConfiguredCodexProvider(config)?.baseUrl ?? config.codex.baseUrl ?? null,
3119
- sandboxMode: config.codex.sandboxMode,
3120
- networkAccessEnabled: config.runtime.networkAccessEnabled
3121
- },
3122
- skills: {
3123
- ...skillSummary,
3124
- recentSync: skillSyncStatus ?? null,
3125
- recentEvolution: evolutionStatus ?? null
3126
- },
3127
- repoMemory: {
3128
- rootDir: config.memory.rootDir,
3129
- rootReady: await pathExists(config.memory.rootDir),
3130
- totalCount: repoMemorySummary.length,
3131
- entries: repoMemorySummary,
3132
- recentUpdate: repoMemoryStatus ?? null
3133
- },
3134
- feishu: feishuStatus,
3135
- currentPreflight: preflight,
3136
- recentHealthCheck: healthCheck ?? null,
3137
- selfUpdate: {
3138
- currentVersion,
3139
- config: config.selfUpdate ?? buildDefaultConfig().selfUpdate,
3140
- cachedState: await readSelfUpdateState() ?? null
3141
- }
3142
- };
3143
- if (parsedCommandArgs.format === "text") {
3144
- console.log(renderDoctorTextReport(doctor));
3145
- process.exitCode = 0;
3146
- return;
3147
- }
3148
- console.log(JSON.stringify(doctor, null, 2));
3149
- process.exitCode = 0;
3150
- return;
3151
- }
3152
3246
  console.error(`Unknown command: ${command}`);
3153
3247
  process.exitCode = 1;
3154
3248
  }