@sireai/optimus 0.1.17 → 0.1.20

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 (36) hide show
  1. package/README.md +4 -5
  2. package/dist/cli/optimus.js +374 -282
  3. package/dist/cli/optimus.js.map +1 -1
  4. package/dist/config/load-config.js +1 -1
  5. package/dist/config/load-config.js.map +1 -1
  6. package/dist/integrations/jira/jira-cli.js +13 -1
  7. package/dist/integrations/jira/jira-cli.js.map +1 -1
  8. package/dist/integrations/jira/jira-client.d.ts +3 -0
  9. package/dist/integrations/jira/jira-client.js +27 -1
  10. package/dist/integrations/jira/jira-client.js.map +1 -1
  11. package/dist/integrations/jira/jira-submit.js +14 -7
  12. package/dist/integrations/jira/jira-submit.js.map +1 -1
  13. package/dist/problem-solving-core/codex/codex-auth-resolver.js +1 -1
  14. package/dist/problem-solving-core/codex/codex-auth-resolver.js.map +1 -1
  15. package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.d.ts +1 -1
  16. package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js +14 -3
  17. package/dist/task-environment/delivery/commit-message/bugfix-commit-message-template.js.map +1 -1
  18. package/dist/task-environment/delivery/task-delivery-service.d.ts +5 -0
  19. package/dist/task-environment/delivery/task-delivery-service.js +7 -2
  20. package/dist/task-environment/delivery/task-delivery-service.js.map +1 -1
  21. package/dist/task-environment/delivery/task-publication-service.js +1 -0
  22. package/dist/task-environment/delivery/task-publication-service.js.map +1 -1
  23. package/dist/task-environment/evidence/evidence-preparation-service.d.ts +5 -1
  24. package/dist/task-environment/evidence/evidence-preparation-service.js +170 -21
  25. package/dist/task-environment/evidence/evidence-preparation-service.js.map +1 -1
  26. package/dist/task-environment/observability/logger.d.ts +1 -0
  27. package/dist/task-environment/observability/logger.js +31 -1
  28. package/dist/task-environment/observability/logger.js.map +1 -1
  29. package/dist/task-environment/orchestration/execution-context-assembler.js +1 -1
  30. package/dist/task-environment/orchestration/execution-context-assembler.js.map +1 -1
  31. package/dist/task-environment/orchestration/task-orchestrator.js +3 -1
  32. package/dist/task-environment/orchestration/task-orchestrator.js.map +1 -1
  33. package/optimus.config.template.json +1 -1
  34. package/package.json +1 -1
  35. package/embedded-skills/task/bugfix/android-debug-protocol/SKILL.md +0 -10
  36. package/embedded-skills/task/bugfix/android-debug-protocol/skill.json +0 -6
@@ -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": [
@@ -940,7 +1008,7 @@ async function buildFeishuDoctorStatus(config) {
940
1008
  : "Real @mentions require Feishu app availability.",
941
1009
  ...(mentionReady
942
1010
  ? {}
943
- : { recommendation: "Rerun `optimus doctor --quick` after restoring Feishu app availability." })
1011
+ : { recommendation: "Rerun `optimus doctor` after restoring Feishu app availability." })
944
1012
  }
945
1013
  };
946
1014
  }
@@ -1065,16 +1133,16 @@ async function promptSetupAnswers(defaults) {
1065
1133
  try {
1066
1134
  console.log("Optimus setup");
1067
1135
  console.log("This usually takes about 2 minutes.");
1068
- console.log("Required: repository + Codex auth. Optional: Feishu webhook, Jira, Sentry.");
1136
+ console.log("Required: Codex auth. Optional: repository, Feishu webhook, Jira, Sentry.");
1069
1137
  console.log("You can rerun `optimus setup` later to change anything.");
1070
1138
  if (await pathExists(configPath)) {
1071
1139
  console.log(`Existing config found at ${configPath}. Press Enter to keep current values.`);
1072
1140
  }
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;
1141
+ printSetupSection("Repository", "Optional. Point Optimus at the main local repository it should work in, or leave both fields empty and add one later.");
1142
+ printSetupHint("Repository path: the local code directory Optimus will read and edit. Leave empty if you only want to finish setup first.");
1143
+ const repoPath = (await ask(renderSetupPrompt("Optional", "Repository path", defaults.repoPath))).trim() || defaults.repoPath;
1144
+ printSetupHint("Repository alias: the short name used later in commands like --repo ohos-pre. Leave empty if no repository should be registered now.");
1145
+ const repoAlias = (await ask(renderSetupPrompt("Optional", "Repository alias", defaults.repoAlias))).trim() || defaults.repoAlias;
1078
1146
  printSetupSection("Codex", "Choose one model authentication mode. This is required before tasks can run.");
1079
1147
  printSetupHint("Codex auth mode: choose how Optimus reaches the model you want to use.");
1080
1148
  const codexAuthMode = await askSetupCodexAuthMode({ ask, defaultMode: defaults.codexAuthMode });
@@ -1103,8 +1171,6 @@ async function promptSetupAnswers(defaults) {
1103
1171
  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
1172
  printSetupHint("Feishu webhook: optional; leave empty to use direct Feishu app delivery only.");
1105
1173
  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
1174
  printSetupSection("Jira", "Optional. Turn this on only if you want this machine to read Jira issues or write Jira comments.");
1109
1175
  printSetupHint("If disabled, Optimus still works; Jira commands just stay unavailable on this machine.");
1110
1176
  const enableJiraInput = (await ask(renderSetupPrompt("Optional integration", "Enable Jira integration", defaults.enableJira ? "Y/n" : "y/N"))).trim().toLowerCase();
@@ -1145,7 +1211,6 @@ async function promptSetupAnswers(defaults) {
1145
1211
  ...(codexProviderBaseUrl ? { codexProviderBaseUrl } : {}),
1146
1212
  ...(codexProviderApiKeyEnvName ? { codexProviderApiKeyEnvName } : {}),
1147
1213
  ...(feishuWebhook !== undefined ? { feishuWebhook } : {}),
1148
- ...(feishuSecret !== undefined ? { feishuSecret } : {}),
1149
1214
  enableJira,
1150
1215
  ...(jiraBaseUrl ? { jiraBaseUrl } : {}),
1151
1216
  ...(jiraPersonalToken ? { jiraPersonalToken } : {}),
@@ -1196,7 +1261,6 @@ async function resolveDefaultSetupAnswers() {
1196
1261
  ...(provider?.baseUrl ? { codexProviderBaseUrl: provider.baseUrl } : {}),
1197
1262
  ...(provider?.apiKeyEnvName ? { codexProviderApiKeyEnvName: provider.apiKeyEnvName } : {}),
1198
1263
  ...(config.delivery.feishu.webhook ? { feishuWebhook: config.delivery.feishu.webhook } : {}),
1199
- ...(config.delivery.feishu.secret ? { feishuSecret: config.delivery.feishu.secret } : {}),
1200
1264
  enableJira: config.jira.enabled,
1201
1265
  ...(config.jira.baseUrl ? { jiraBaseUrl: config.jira.baseUrl } : {}),
1202
1266
  ...(config.jira.personalToken ? { jiraPersonalToken: config.jira.personalToken } : {}),
@@ -1248,11 +1312,10 @@ async function askSetupCodexAuthMode(input) {
1248
1312
  }
1249
1313
  }
1250
1314
  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.";
1315
+ const repoPath = answers.repoPath.trim();
1316
+ const repoAlias = answers.repoAlias.trim();
1317
+ if ((repoPath && !repoAlias) || (!repoPath && repoAlias)) {
1318
+ return "setup repository registration requires both repository path and repository alias, or neither.";
1256
1319
  }
1257
1320
  if (answers.codexAuthMode === "model_provider" && !answers.codexProviderId?.trim()) {
1258
1321
  return "setup requires provider id when Codex auth mode is model_provider.";
@@ -1384,16 +1447,15 @@ function buildSetupConfig(answers, rawConfig) {
1384
1447
  config.delivery.channels = ["console", "feishu"];
1385
1448
  config.delivery.feishu = {
1386
1449
  ...config.delivery.feishu,
1387
- ...(typeof answers.feishuWebhook === "string" ? { webhook: answers.feishuWebhook } : {}),
1388
- ...(typeof answers.feishuSecret === "string" ? { secret: answers.feishuSecret } : {})
1450
+ ...(typeof rawConfig?.delivery?.feishu?.secret === "string" && rawConfig.delivery.feishu.secret.trim()
1451
+ ? { secret: rawConfig.delivery.feishu.secret.trim() }
1452
+ : {}),
1453
+ ...(typeof answers.feishuWebhook === "string" ? { webhook: answers.feishuWebhook } : {})
1389
1454
  };
1390
1455
  if (answers.feishuWebhook === null) {
1391
1456
  delete config.delivery.feishu.webhook;
1392
1457
  delete config.delivery.feishu.webhooks;
1393
1458
  }
1394
- if (answers.feishuSecret === null) {
1395
- delete config.delivery.feishu.secret;
1396
- }
1397
1459
  config.jira.enabled = answers.enableJira;
1398
1460
  if (answers.enableJira) {
1399
1461
  config.jira.baseUrl = answers.jiraBaseUrl ?? config.jira.baseUrl;
@@ -1551,12 +1613,12 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1551
1613
  await store.init();
1552
1614
  const registeredRepositories = await store.listRepositoryRoots();
1553
1615
  if (registeredRepositories.length === 0) {
1554
- blocking.push({
1616
+ warnings.push({
1555
1617
  code: "repository_missing",
1556
1618
  message: "No repository is registered.",
1557
- fix: "Rerun `optimus setup` and provide a repository path and alias."
1619
+ fix: "Register one with `optimus repo add <path> --alias <name>` or rerun `optimus setup`."
1558
1620
  });
1559
- next.add("optimus setup");
1621
+ next.add("optimus repo add <path> --alias <name>");
1560
1622
  }
1561
1623
  for (const repository of registeredRepositories) {
1562
1624
  const inspection = await inspectRepositoryRoot(repository.path);
@@ -1587,7 +1649,7 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1587
1649
  blocking.push({
1588
1650
  code: "git_user_name_missing",
1589
1651
  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`."
1652
+ fix: "Run `git config user.name \"Your Name\"` in the repository, then rerun `optimus doctor`."
1591
1653
  });
1592
1654
  next.add("git config user.name \"Your Name\"");
1593
1655
  }
@@ -1595,7 +1657,7 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1595
1657
  blocking.push({
1596
1658
  code: "git_user_email_missing",
1597
1659
  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`."
1660
+ fix: "Run `git config user.email \"you@example.com\"` in the repository, then rerun `optimus doctor`."
1599
1661
  });
1600
1662
  next.add("git config user.email \"you@example.com\"");
1601
1663
  }
@@ -1640,6 +1702,196 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
1640
1702
  next: [...next]
1641
1703
  };
1642
1704
  }
1705
+ function summarizeIssue(issue, fallback) {
1706
+ if (!issue?.message) {
1707
+ return fallback;
1708
+ }
1709
+ return issue.message.replace(/\.$/u, "");
1710
+ }
1711
+ function doctorNextActions(quick, healthCheck, delivery) {
1712
+ const actions = new Set();
1713
+ for (const step of quick.next) {
1714
+ actions.add(step);
1715
+ }
1716
+ if (!healthCheck.ok) {
1717
+ const fixHint = healthCheck.diagnostics.auth?.fixHint?.trim();
1718
+ if (fixHint) {
1719
+ actions.add(fixHint);
1720
+ }
1721
+ else {
1722
+ actions.add("Rerun `optimus setup`.");
1723
+ }
1724
+ }
1725
+ if (delivery.status === "not_ok" && delivery.fix) {
1726
+ actions.add(delivery.fix);
1727
+ }
1728
+ if (actions.size === 0) {
1729
+ actions.add("Run `optimus start`.");
1730
+ actions.add("Submit a task with `optimus submit --title \"...\" --description \"...\"`.");
1731
+ }
1732
+ return [...actions].slice(0, 3);
1733
+ }
1734
+ async function runDoctor(configPath = resolveDefaultConfigPath()) {
1735
+ const quick = await runQuickDoctor(configPath);
1736
+ const configIssue = quick.blocking.find((issue) => issue.code === "config_missing" || issue.code === "config_parse_failed" || issue.code === "config_load_failed");
1737
+ if (configIssue) {
1738
+ const repoIssue = quick.blocking.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"));
1739
+ const codexIssue = quick.blocking.find((issue) => issue.code.startsWith("codex_"));
1740
+ return {
1741
+ overall: "not_ready",
1742
+ repository: {
1743
+ status: repoIssue ? "not_ok" : "warning",
1744
+ ...(repoIssue ? { reason: summarizeIssue(repoIssue, "Repository setup is incomplete.") } : { reason: "Repository checks could not complete yet." }),
1745
+ ...(repoIssue?.fix ? { fix: repoIssue.fix } : {})
1746
+ },
1747
+ codex: {
1748
+ status: codexIssue ? "not_ok" : "warning",
1749
+ ...(codexIssue ? { reason: summarizeIssue(codexIssue, "Codex setup is incomplete.") } : { reason: "Codex checks could not complete yet." }),
1750
+ ...(codexIssue?.fix ? { fix: codexIssue.fix } : {}),
1751
+ auth: {
1752
+ status: codexIssue ? "not_ok" : "warning",
1753
+ ...(codexIssue ? { reason: summarizeIssue(codexIssue, "Codex authentication is not ready.") } : { reason: "Authentication checks could not complete yet." }),
1754
+ ...(codexIssue?.fix ? { fix: codexIssue.fix } : {})
1755
+ },
1756
+ connectivity: {
1757
+ status: "disabled",
1758
+ reason: "Connectivity check is deferred until authentication is ready."
1759
+ }
1760
+ },
1761
+ delivery: {
1762
+ status: "warning",
1763
+ reason: "Delivery checks are deferred until setup is complete."
1764
+ },
1765
+ integrations: [
1766
+ {
1767
+ name: "Jira",
1768
+ status: "disabled"
1769
+ },
1770
+ {
1771
+ name: "Sentry",
1772
+ status: "disabled"
1773
+ }
1774
+ ],
1775
+ nextActions: quick.next.slice(0, 3)
1776
+ };
1777
+ }
1778
+ const config = await loadConfig(configPath);
1779
+ const codexRunner = new CodexRunner(config);
1780
+ const feishuStatus = await buildFeishuDoctorStatus(config);
1781
+ const healthCheck = await codexRunner.runHealthCheck();
1782
+ const repositoryIssue = quick.blocking.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"))
1783
+ ?? quick.warnings.find((issue) => issue.code.startsWith("repository_") || issue.code.startsWith("git_user_"));
1784
+ const repositoryStatus = repositoryIssue
1785
+ ? quick.blocking.includes(repositoryIssue)
1786
+ ? "not_ok"
1787
+ : "warning"
1788
+ : "ok";
1789
+ const repository = repositoryIssue
1790
+ ? {
1791
+ status: repositoryStatus,
1792
+ reason: summarizeIssue(repositoryIssue, "Repository is not ready."),
1793
+ ...(repositoryIssue.fix ? { fix: repositoryIssue.fix } : {}),
1794
+ ...(quick.repositories[0]?.alias ?? quick.repositories[0]?.path ? { using: quick.repositories[0]?.alias ?? quick.repositories[0]?.path } : {})
1795
+ }
1796
+ : {
1797
+ status: "ok",
1798
+ ...(quick.repositories[0]?.alias ?? quick.repositories[0]?.path ? { using: quick.repositories[0]?.alias ?? quick.repositories[0]?.path } : {})
1799
+ };
1800
+ const authDiagnostics = healthCheck.diagnostics.auth;
1801
+ const codexIssue = quick.blocking.find((issue) => issue.code.startsWith("codex_"));
1802
+ const codexAuth = authDiagnostics?.authenticated
1803
+ ? { status: "ok" }
1804
+ : {
1805
+ status: "not_ok",
1806
+ reason: summarizeIssue(codexIssue, healthCheck.diagnostics.failureCategory === "auth_missing"
1807
+ ? "authentication is not usable."
1808
+ : "Codex authentication is not ready."),
1809
+ fix: authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`."
1810
+ };
1811
+ const codexConnectivity = !authDiagnostics?.authenticated
1812
+ ? {
1813
+ status: "disabled",
1814
+ reason: "Connectivity check is deferred until authentication is ready."
1815
+ }
1816
+ : healthCheck.ok
1817
+ ? { status: "ok" }
1818
+ : {
1819
+ status: "not_ok",
1820
+ reason: healthCheck.diagnostics.failureCategory === "provider_error"
1821
+ ? "model provider is unreachable."
1822
+ : healthCheck.diagnostics.failureCategory === "network_error"
1823
+ ? "network connectivity to Codex is unavailable."
1824
+ : "Codex request could not complete.",
1825
+ fix: authDiagnostics.fixHint?.trim() || "Retry `optimus doctor` after fixing the Codex connection."
1826
+ };
1827
+ const codex = healthCheck.ok
1828
+ ? {
1829
+ status: "ok",
1830
+ auth: codexAuth,
1831
+ connectivity: codexConnectivity
1832
+ }
1833
+ : {
1834
+ status: "not_ok",
1835
+ reason: summarizeIssue(codexIssue, !authDiagnostics?.authenticated
1836
+ ? "authentication is not usable."
1837
+ : healthCheck.diagnostics.failureCategory === "provider_error"
1838
+ ? "model provider is unreachable."
1839
+ : "Codex is not ready."),
1840
+ fix: authDiagnostics?.fixHint?.trim() || "Rerun `optimus setup`.",
1841
+ auth: codexAuth,
1842
+ connectivity: codexConnectivity
1843
+ };
1844
+ const feishuIssue = quick.blocking.find((issue) => issue.code.startsWith("feishu_"))
1845
+ ?? quick.warnings.find((issue) => issue.code.startsWith("feishu_"));
1846
+ const delivery = !config.delivery.enabled
1847
+ ? {
1848
+ status: "warning",
1849
+ reason: "task delivery is disabled.",
1850
+ fix: "Enable delivery in config if this machine should send notifications."
1851
+ }
1852
+ : feishuIssue
1853
+ ? {
1854
+ status: "not_ok",
1855
+ reason: summarizeIssue(feishuIssue, "Feishu delivery is not usable."),
1856
+ ...(feishuIssue.fix ? { fix: feishuIssue.fix } : {})
1857
+ }
1858
+ : feishuStatus.auth.configured
1859
+ ? {
1860
+ status: "ok"
1861
+ }
1862
+ : {
1863
+ status: "warning",
1864
+ reason: "Feishu app delivery is unavailable.",
1865
+ fix: "Rerun `optimus setup` and verify Feishu configuration."
1866
+ };
1867
+ const jiraIssues = config.jira.enabled ? resolveJiraQuickDoctorIssues(config.jira) : [];
1868
+ const sentryIssues = config.sentry.enabled ? resolveSentryQuickDoctorIssues(config.sentry) : [];
1869
+ const integrations = [
1870
+ config.jira.enabled
1871
+ ? jiraIssues.length > 0
1872
+ ? { name: "Jira", status: "not_ok", reason: summarizeIssue(jiraIssues[0], "Jira is not ready."), ...(jiraIssues[0]?.fix ? { fix: jiraIssues[0].fix } : {}) }
1873
+ : { name: "Jira", status: "ok" }
1874
+ : { name: "Jira", status: "disabled" },
1875
+ config.sentry.enabled
1876
+ ? sentryIssues.length > 0
1877
+ ? { name: "Sentry", status: "not_ok", reason: summarizeIssue(sentryIssues[0], "Sentry is not ready."), ...(sentryIssues[0]?.fix ? { fix: sentryIssues[0].fix } : {}) }
1878
+ : { name: "Sentry", status: "ok" }
1879
+ : { name: "Sentry", status: "disabled" }
1880
+ ];
1881
+ const overall = repository.status === "not_ok" || codex.status === "not_ok"
1882
+ ? "not_ready"
1883
+ : delivery.status === "warning" || integrations.some((integration) => integration.status === "not_ok")
1884
+ ? "warning"
1885
+ : "ready";
1886
+ return {
1887
+ overall,
1888
+ repository,
1889
+ codex,
1890
+ delivery,
1891
+ integrations,
1892
+ nextActions: doctorNextActions(quick, healthCheck, delivery)
1893
+ };
1894
+ }
1643
1895
  async function runSetup(args) {
1644
1896
  const configPath = resolveDefaultConfigPath();
1645
1897
  const envPath = resolveDefaultEnvPath();
@@ -1653,27 +1905,34 @@ async function runSetup(args) {
1653
1905
  if (validationError) {
1654
1906
  throw new Error(validationError);
1655
1907
  }
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
1908
  const preflightConfig = configExists ? await loadConfig(configPath) : buildDefaultConfig();
1662
1909
  const preflightStore = new SQLiteTaskStore(preflightConfig.storage.rootDir);
1663
1910
  await preflightStore.init();
1664
1911
  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);
1912
+ const hasRepositorySelection = answers.repoPath.trim().length > 0 && answers.repoAlias.trim().length > 0;
1913
+ let normalizedRepoPath;
1914
+ let resolvedExecutionMode;
1915
+ let previewExecutionPlan;
1916
+ let reviewSystem;
1917
+ if (hasRepositorySelection) {
1918
+ normalizedRepoPath = resolve(answers.repoPath);
1919
+ const inspection = await inspectRepositoryRoot(normalizedRepoPath);
1920
+ if (!inspection.ok) {
1921
+ throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
1922
+ }
1923
+ const aliasConflict = existingRepositories.find((repository) => repository.alias === answers.repoAlias && repository.path !== normalizedRepoPath);
1924
+ if (aliasConflict) {
1925
+ throw new Error(`Repository alias already exists for another path: ${answers.repoAlias} -> ${aliasConflict.path}. `
1926
+ + "Use `optimus repo update` or `optimus repo remove` before re-running setup.");
1927
+ }
1928
+ const existingRepository = existingRepositories.find((repository) => repository.path === normalizedRepoPath);
1929
+ resolvedExecutionMode = existingRepository?.executionMode ?? describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection).resolvedDefaultMode;
1930
+ previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: resolvedExecutionMode }, inspection);
1931
+ reviewSystem = await detectRepositoryReviewSystem(normalizedRepoPath, inspection);
1932
+ }
1674
1933
  const rawConfig = configExists ? await readRawProjectConfig(configPath) : undefined;
1675
1934
  await mkdir(dirname(configPath), { recursive: true });
1676
- await writeFile(configPath, `${buildSetupConfig({ ...answers, repoPath: normalizedRepoPath }, rawConfig)}\n`, "utf8");
1935
+ await writeFile(configPath, `${buildSetupConfig({ ...answers, ...(normalizedRepoPath ? { repoPath: normalizedRepoPath } : {}) }, rawConfig)}\n`, "utf8");
1677
1936
  const codexAuthEnvName = resolveSetupCodexApiKeyEnvName(answers);
1678
1937
  if (answers.codexApiKey && codexAuthEnvName) {
1679
1938
  await mkdir(dirname(envPath), { recursive: true });
@@ -1682,11 +1941,13 @@ async function runSetup(args) {
1682
1941
  const config = await loadConfig(configPath);
1683
1942
  const store = new SQLiteTaskStore(config.storage.rootDir);
1684
1943
  await store.init();
1685
- await store.addRepositoryRoot({
1686
- path: normalizedRepoPath,
1687
- alias: answers.repoAlias,
1688
- executionMode: resolvedExecutionMode
1689
- });
1944
+ if (normalizedRepoPath && resolvedExecutionMode) {
1945
+ await store.addRepositoryRoot({
1946
+ path: normalizedRepoPath,
1947
+ alias: answers.repoAlias,
1948
+ executionMode: resolvedExecutionMode
1949
+ });
1950
+ }
1690
1951
  const doctorQuick = await runQuickDoctor(configPath);
1691
1952
  const codexAuth = new CodexAuthResolver(config).resolve().diagnostics;
1692
1953
  return {
@@ -1694,13 +1955,15 @@ async function runSetup(args) {
1694
1955
  configPath,
1695
1956
  envPath,
1696
1957
  configStatus: configExists ? "overwritten" : "created",
1697
- repository: {
1698
- path: normalizedRepoPath,
1699
- alias: answers.repoAlias,
1700
- workspaceKind: previewExecutionPlan.workspaceKind,
1701
- executionMode: resolvedExecutionMode,
1702
- reviewSystem
1703
- },
1958
+ ...(normalizedRepoPath && previewExecutionPlan && resolvedExecutionMode && reviewSystem ? {
1959
+ repository: {
1960
+ path: normalizedRepoPath,
1961
+ alias: answers.repoAlias,
1962
+ workspaceKind: previewExecutionPlan.workspaceKind,
1963
+ executionMode: resolvedExecutionMode,
1964
+ reviewSystem
1965
+ }
1966
+ } : {}),
1704
1967
  deliveryChannels: config.delivery.channels,
1705
1968
  jiraEnabled: config.jira.enabled,
1706
1969
  sentryEnabled: config.sentry.enabled,
@@ -1934,7 +2197,7 @@ async function main() {
1934
2197
  if (command === "repo") {
1935
2198
  const repoSubcommand = commandArgs.find((token) => !token.startsWith("--"))?.trim();
1936
2199
  console.log(renderCommandHelp("repo") ?? renderCliHelp());
1937
- process.exitCode = repoSubcommand && !["add", "remove", "update", "list", "doctor"].includes(repoSubcommand) ? 1 : 0;
2200
+ process.exitCode = repoSubcommand && !["add", "remove", "update", "list"].includes(repoSubcommand) ? 1 : 0;
1938
2201
  return;
1939
2202
  }
1940
2203
  console.log(renderCommandHelp(command) ?? renderCliHelp());
@@ -2018,15 +2281,15 @@ async function main() {
2018
2281
  return;
2019
2282
  }
2020
2283
  const parsedCommandArgs = parseArgs(commandArgs);
2021
- if (command === "doctor" && parsedCommandArgs.quick === "true") {
2022
- const result = await runQuickDoctor();
2284
+ if (command === "doctor") {
2285
+ const result = await runDoctor();
2023
2286
  if (parsedCommandArgs.json === "true") {
2024
2287
  console.log(JSON.stringify(result, null, 2));
2025
2288
  }
2026
2289
  else {
2027
- console.log(renderQuickDoctorReport(result));
2290
+ console.log(renderDoctorTextReport(result));
2028
2291
  }
2029
- process.exitCode = result.summary === "blocked" ? 1 : 0;
2292
+ process.exitCode = result.overall === "not_ready" ? 1 : 0;
2030
2293
  return;
2031
2294
  }
2032
2295
  let config;
@@ -2380,7 +2643,9 @@ async function main() {
2380
2643
  artifacts,
2381
2644
  deliveryBundle: existingBundle
2382
2645
  });
2383
- const deliveryService = new TaskDeliveryService();
2646
+ const deliveryService = new TaskDeliveryService({
2647
+ jiraBaseUrl: config.jira.baseUrl
2648
+ });
2384
2649
  const analysisDocService = new FeishuAnalysisDocService({ config, refStore: store });
2385
2650
  const publicationService = new TaskPublicationService(logger);
2386
2651
  const deliveryDispatcher = new TaskDeliveryDispatcher(config);
@@ -2628,22 +2893,6 @@ async function main() {
2628
2893
  console.log(JSON.stringify({ repositories }, null, 2));
2629
2894
  return;
2630
2895
  }
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
2896
  console.error(`Unknown repo command: ${repoSubcommand}`);
2648
2897
  process.exitCode = 1;
2649
2898
  return;
@@ -2992,163 +3241,6 @@ async function main() {
2992
3241
  return;
2993
3242
  }
2994
3243
  }
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
3244
  console.error(`Unknown command: ${command}`);
3153
3245
  process.exitCode = 1;
3154
3246
  }