@sireai/optimus 0.1.1 → 0.1.3

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 (30) hide show
  1. package/dist/cli/optimus.js +511 -72
  2. package/dist/cli/optimus.js.map +1 -1
  3. package/dist/cli/self-update.d.ts +8 -1
  4. package/dist/cli/self-update.js +34 -3
  5. package/dist/cli/self-update.js.map +1 -1
  6. package/dist/config/load-config.js +8 -5
  7. package/dist/config/load-config.js.map +1 -1
  8. package/dist/integrations/jira/jira-cli-config.d.ts +15 -0
  9. package/dist/integrations/jira/jira-cli-config.js +100 -0
  10. package/dist/integrations/jira/jira-cli-config.js.map +1 -0
  11. package/dist/integrations/jira/jira-cli.js +193 -0
  12. package/dist/integrations/jira/jira-cli.js.map +1 -1
  13. package/dist/integrations/jira/jira-client.d.ts +16 -3
  14. package/dist/integrations/jira/jira-client.js +71 -14
  15. package/dist/integrations/jira/jira-client.js.map +1 -1
  16. package/dist/integrations/jira/jira-submit.js +2 -0
  17. package/dist/integrations/jira/jira-submit.js.map +1 -1
  18. package/dist/task-environment/intake/manual-problem-intake.js +3 -0
  19. package/dist/task-environment/intake/manual-problem-intake.js.map +1 -1
  20. package/dist/task-environment/intake/triage-rejection-feedback-service.d.ts +21 -0
  21. package/dist/task-environment/intake/triage-rejection-feedback-service.js +150 -0
  22. package/dist/task-environment/intake/triage-rejection-feedback-service.js.map +1 -0
  23. package/dist/task-environment/orchestration/triage-runner.js +4 -2
  24. package/dist/task-environment/orchestration/triage-runner.js.map +1 -1
  25. package/dist/task-environment/runtime/optimus-runtime.d.ts +3 -0
  26. package/dist/task-environment/runtime/optimus-runtime.js +5 -0
  27. package/dist/task-environment/runtime/optimus-runtime.js.map +1 -1
  28. package/dist/types.d.ts +1 -1
  29. package/package.json +5 -3
  30. package/task-harnesses/bugfix/ACCEPT.md +57 -21
@@ -28,11 +28,13 @@ import { TaskDeliveryService } from "../task-environment/delivery/task-delivery-
28
28
  import { FeishuAnalysisDocService } from "../task-environment/delivery/feishu-analysis-doc-service.js";
29
29
  import { TaskPublicationService } from "../task-environment/delivery/task-publication-service.js";
30
30
  import { resolveDefaultConfigPath, resolveDefaultEnvPath, resolveOptimusHomeDir } from "../config/optimus-paths.js";
31
- import { checkForSelfUpdate, installSelfUpdate, readSelfUpdateState } from "./self-update.js";
31
+ import { checkForSelfUpdate, installSelfUpdate, recordSkippedSelfUpdate, readSelfUpdateState } from "./self-update.js";
32
32
  const CLI_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
33
33
  const PACKAGE_JSON_PATH = join(CLI_ROOT_DIR, "package.json");
34
34
  const execFileAsync = promisify(execFile);
35
35
  const PACKAGE_NAME = "@sireai/optimus";
36
+ const JIRA_CLI_ENTRY = join(CLI_ROOT_DIR, "dist", "integrations", "jira", "jira-cli.js");
37
+ const RELEASE_NOTES_URL = "https://github.com/SireAI/optimus/releases/latest";
36
38
  function renderSetupResult(result) {
37
39
  const lines = [];
38
40
  lines.push("Setup Complete");
@@ -130,39 +132,41 @@ function renderCliHelp() {
130
132
  "Usage:",
131
133
  " optimus <command> [options]",
132
134
  "",
133
- "First-time setup:",
135
+ "Get started:",
134
136
  " optimus setup",
135
- " optimus doctor --quick",
137
+ " optimus start",
136
138
  " optimus submit --title \"Login crash\" --description \"Crash on startup\"",
137
139
  "",
138
- "Core commands:",
139
- " start Start the resident runtime",
140
- " submit Submit a manual task",
141
- " upgrade Check for or install the latest Optimus version",
142
- " retry-task Requeue an existing task",
143
- " cancel-task Cancel a queued or running task",
144
- " triage-only Run resident triage without enqueuing a task",
145
- "",
146
- "Repository commands:",
140
+ "User commands:",
141
+ " help [command] Show CLI help or command help",
142
+ " setup Configure Optimus for first use or update config",
143
+ " start Start the resident runtime",
144
+ " submit Submit a manual task",
145
+ " retry-task Requeue an existing task",
146
+ " cancel-task Cancel a queued or running task",
147
+ " task-status Show task execution status",
148
+ " task-result Show persisted result, delivery, and publication data",
149
+ " task-events Show task timeline",
147
150
  " repo add|remove|update|list|doctor",
151
+ " doctor Show runtime, skills, and environment diagnostics",
152
+ " health-check Validate Codex/provider readiness",
153
+ " upgrade Check for or install the latest Optimus version",
154
+ " version Show the installed package version",
148
155
  "",
149
- "Inspection commands:",
150
- " health-check Validate Codex/provider readiness",
151
- " doctor Show runtime, skills, and environment diagnostics",
152
- " task-status Show task execution status",
153
- " task-result Show persisted result, delivery, and publication data",
156
+ "Advanced commands:",
154
157
  " delivery-status Show focused delivery/publication state",
155
- " task-events Show task timeline",
156
- " db-inspect Show raw persisted task data",
157
- "",
158
- "Delivery commands:",
159
158
  " notify-test Send or preview a Feishu test notification",
160
159
  " delivery-retry Replay notification delivery from persisted bundle data",
161
160
  " postrun-retry Rebuild postrun artifacts and optionally replay publication",
162
161
  "",
163
162
  "Integration commands:",
163
+ " jira-search|jira-get-issue|jira-search-assigned-open|jira-submit-issue|jira-submit-assigned-open|jira-add-comment|jira-poll-once|jira-poll-daemon",
164
+ " Example: optimus help jira-submit-issue",
165
+ "",
166
+ "Developer commands:",
167
+ " triage-only Run resident triage without enqueuing a task",
164
168
  " intake-status|intake-run|intake-disable|intake-enable|intake-reset-checkpoint",
165
- " jira-search|jira-get-issue|jira-search-assigned-open|jira-submit-issue|jira-submit-assigned-open|jira-poll-once|jira-poll-daemon",
169
+ " db-inspect Show raw persisted task data",
166
170
  "",
167
171
  "Global options:",
168
172
  " -h, --help Show this help",
@@ -173,24 +177,370 @@ function renderCliHelp() {
173
177
  ` - Run \`optimus setup\` once; Optimus stores config and runtime data under ${resolveOptimusHomeDir()}.`
174
178
  ].join("\n");
175
179
  }
180
+ function renderCommandHelp(command) {
181
+ const normalized = command.trim();
182
+ const helpByCommand = {
183
+ submit: [
184
+ "optimus submit",
185
+ "",
186
+ "Usage:",
187
+ " optimus submit --title <title> --description <text> [options]",
188
+ "",
189
+ "Required:",
190
+ " --title <title> Task title",
191
+ " --description <text> Task description",
192
+ "",
193
+ "Common options:",
194
+ " --repo <alias> Target registered repository alias",
195
+ " --task-type <type> Task type, default bugfix",
196
+ " --source-ref <id> External issue key or source reference",
197
+ " --allow-duplicate Allow duplicate submission",
198
+ "",
199
+ "Example:",
200
+ " optimus submit --title \"Login crash\" --description \"Crash on startup\" --repo ohos-pre"
201
+ ].join("\n"),
202
+ "retry-task": [
203
+ "optimus retry-task",
204
+ "",
205
+ "Usage:",
206
+ " optimus retry-task --task-id <task-id> [options]",
207
+ "",
208
+ "Required:",
209
+ " --task-id <task-id> Existing task id",
210
+ "",
211
+ "Optional:",
212
+ " --reason <text> Retry reason"
213
+ ].join("\n"),
214
+ "cancel-task": [
215
+ "optimus cancel-task",
216
+ "",
217
+ "Usage:",
218
+ " optimus cancel-task --task-id <task-id> [options]",
219
+ "",
220
+ "Required:",
221
+ " --task-id <task-id> Existing task id",
222
+ "",
223
+ "Optional:",
224
+ " --reason <text> Cancel reason",
225
+ " --keep-artifacts Keep task artifacts after cancel",
226
+ " --force Force cancel metadata flag"
227
+ ].join("\n"),
228
+ "task-status": [
229
+ "optimus task-status",
230
+ "",
231
+ "Usage:",
232
+ " optimus task-status [options]",
233
+ "",
234
+ "Optional:",
235
+ " --task-id <task-id> Filter by task id",
236
+ " --status <status> Filter by task status",
237
+ " --active-only Show active tasks only",
238
+ " --limit <n> Max tasks to show, default 20"
239
+ ].join("\n"),
240
+ "task-result": [
241
+ "optimus task-result",
242
+ "",
243
+ "Usage:",
244
+ " optimus task-result --task-id <task-id> [options]",
245
+ "",
246
+ "Required:",
247
+ " --task-id <task-id> Existing task id",
248
+ "",
249
+ "Optional:",
250
+ " --format text Print compact text report",
251
+ " --print Alias for text output"
252
+ ].join("\n"),
253
+ "delivery-status": [
254
+ "optimus delivery-status",
255
+ "",
256
+ "Usage:",
257
+ " optimus delivery-status --task-id <task-id> [options]",
258
+ "",
259
+ "Required:",
260
+ " --task-id <task-id> Existing task id",
261
+ "",
262
+ "Optional:",
263
+ " --format text Print compact text report",
264
+ " --print Alias for text output"
265
+ ].join("\n"),
266
+ repo: [
267
+ "optimus repo",
268
+ "",
269
+ "Usage:",
270
+ " optimus repo add <path> [options]",
271
+ " optimus repo remove <path|alias>",
272
+ " optimus repo update <path|alias> [options]",
273
+ " optimus repo list",
274
+ " optimus repo doctor",
275
+ "",
276
+ "Common options:",
277
+ " --alias <name> Repository alias",
278
+ " --mode <copy|inplace> Execution mode",
279
+ "",
280
+ "Examples:",
281
+ " optimus repo add /path/to/repo --alias ohos-pre",
282
+ " optimus repo update ohos-pre --mode inplace",
283
+ " optimus repo remove ohos-pre"
284
+ ].join("\n"),
285
+ doctor: [
286
+ "optimus doctor",
287
+ "",
288
+ "Usage:",
289
+ " optimus doctor [options]",
290
+ "",
291
+ "Optional:",
292
+ " --quick Run compact readiness check",
293
+ " --json Print JSON when used with --quick",
294
+ " --format text Print text report"
295
+ ].join("\n"),
296
+ "jira-submit-issue": [
297
+ "optimus jira-submit-issue",
298
+ "",
299
+ "Usage:",
300
+ " optimus jira-submit-issue --issue-key <key> [options]",
301
+ "",
302
+ "Required:",
303
+ " --issue-key <key> Jira issue key, for example MICARAPPI-13178",
304
+ "",
305
+ "Common options:",
306
+ " --repo <alias> Target registered repository alias",
307
+ " --branch <name> Override target branch",
308
+ " --task-type <type> Task type, default bugfix",
309
+ " --allow-duplicate Allow duplicate submission",
310
+ " --comment-limit <n> Comment lines fetched from Jira, default 3",
311
+ " --dry-run Preview without creating a task",
312
+ "",
313
+ "Example:",
314
+ " optimus jira-submit-issue --issue-key MICARAPPI-13178 --repo ohos-pre --allow-duplicate"
315
+ ].join("\n"),
316
+ "jira-submit-assigned-open": [
317
+ "optimus jira-submit-assigned-open",
318
+ "",
319
+ "Usage:",
320
+ " optimus jira-submit-assigned-open [options]",
321
+ "",
322
+ "Common options:",
323
+ " --assignee <jira-user> Jira assignee, default current user",
324
+ " --repo <alias> Target registered repository alias",
325
+ " --projects-filter <csv> Restrict Jira projects",
326
+ " --limit <n> Max issues to submit, default 50",
327
+ " --allow-duplicate Allow duplicate submission",
328
+ " --dry-run Preview without creating tasks"
329
+ ].join("\n"),
330
+ "jira-get-issue": [
331
+ "optimus jira-get-issue",
332
+ "",
333
+ "Usage:",
334
+ " optimus jira-get-issue --issue-key <key> [options]",
335
+ "",
336
+ "Required:",
337
+ " --issue-key <key> Jira issue key",
338
+ "",
339
+ "Optional:",
340
+ " --fields <csv> Requested Jira fields",
341
+ " --expand <csv> Jira expand parameters",
342
+ " --properties <csv> Jira properties",
343
+ " --comment-limit <n> Comment limit, default 3",
344
+ " --update-history true|false Include update history"
345
+ ].join("\n"),
346
+ "jira-search": [
347
+ "optimus jira-search",
348
+ "",
349
+ "Usage:",
350
+ " optimus jira-search --jql <jql> [options]",
351
+ "",
352
+ "Required:",
353
+ " --jql <jql> Jira search JQL",
354
+ "",
355
+ "Optional:",
356
+ " --limit <n> Max issues, default 50",
357
+ " --start-at <n> Start offset, default 0",
358
+ " --fields <csv> Requested Jira fields",
359
+ " --expand <csv> Jira expand parameters",
360
+ " --projects-filter <csv> Restrict Jira projects"
361
+ ].join("\n"),
362
+ "jira-search-assigned-open": [
363
+ "optimus jira-search-assigned-open",
364
+ "",
365
+ "Usage:",
366
+ " optimus jira-search-assigned-open [options]",
367
+ "",
368
+ "Optional:",
369
+ " --assignee <jira-user> Jira assignee, default current user",
370
+ " --limit <n> Max issues, default 50",
371
+ " --start-at <n> Start offset, default 0",
372
+ " --fields <csv> Requested Jira fields"
373
+ ].join("\n"),
374
+ "jira-add-comment": [
375
+ "optimus jira-add-comment",
376
+ "",
377
+ "Usage:",
378
+ " optimus jira-add-comment --issue-key <key> --body <text>",
379
+ "",
380
+ "Required:",
381
+ " --issue-key <key> Jira issue key",
382
+ " --body <text> Comment body"
383
+ ].join("\n"),
384
+ "jira-poll-once": [
385
+ "optimus jira-poll-once",
386
+ "",
387
+ "Usage:",
388
+ " optimus jira-poll-once [options]",
389
+ "",
390
+ "Common options:",
391
+ " --assignee <jira-user> Jira assignee, default current user",
392
+ " --repo <alias> Target registered repository alias",
393
+ " --projects-filter <csv> Restrict Jira projects",
394
+ " --limit <n> Max issues, default 50",
395
+ " --allow-duplicate Allow duplicate submission",
396
+ " --dry-run Preview without creating tasks"
397
+ ].join("\n"),
398
+ "jira-poll-daemon": [
399
+ "optimus jira-poll-daemon",
400
+ "",
401
+ "Usage:",
402
+ " optimus jira-poll-daemon [options]",
403
+ "",
404
+ "Common options:",
405
+ " --interval-ms <ms> Poll interval, default 300000",
406
+ " --assignee <jira-user> Jira assignee, default current user",
407
+ " --repo <alias> Target registered repository alias",
408
+ " --projects-filter <csv> Restrict Jira projects",
409
+ " --limit <n> Max issues per poll, default 50",
410
+ " --allow-duplicate Allow duplicate submission",
411
+ " --dry-run Preview without creating tasks"
412
+ ].join("\n")
413
+ };
414
+ return helpByCommand[normalized];
415
+ }
416
+ function mapJiraCommand(command) {
417
+ switch (command) {
418
+ case "jira-search":
419
+ return "search";
420
+ case "jira-get-issue":
421
+ return "get-issue";
422
+ case "jira-search-assigned-open":
423
+ return "search-assigned-open";
424
+ case "jira-submit-issue":
425
+ return "submit-issue";
426
+ case "jira-submit-assigned-open":
427
+ return "submit-assigned-open";
428
+ case "jira-add-comment":
429
+ return "add-comment";
430
+ case "jira-poll-once":
431
+ return "poll-once";
432
+ case "jira-poll-daemon":
433
+ return "poll-daemon";
434
+ default:
435
+ return undefined;
436
+ }
437
+ }
438
+ function resolveJiraQuickDoctorIssues(config) {
439
+ const issues = [];
440
+ if (!config.baseUrl?.trim() || /jira\.example\.com/i.test(config.baseUrl)) {
441
+ issues.push({
442
+ code: "jira_base_url_missing",
443
+ message: "Jira integration is enabled, but Jira base URL is missing or still using the placeholder value.",
444
+ fix: "Rerun `optimus setup` and provide a real Jira base URL."
445
+ });
446
+ }
447
+ if (config.authType === "bearer") {
448
+ if (!config.bearerToken?.trim()) {
449
+ issues.push({
450
+ code: "jira_token_missing",
451
+ message: "Jira integration is enabled, but Jira bearer token is missing.",
452
+ fix: "Rerun `optimus setup` and provide a Jira token."
453
+ });
454
+ }
455
+ return issues;
456
+ }
457
+ if (config.authType === "basic") {
458
+ if (!config.username?.trim() || !(config.apiToken?.trim() || config.password?.trim())) {
459
+ issues.push({
460
+ code: "jira_basic_auth_missing",
461
+ message: "Jira integration is enabled, but Jira basic auth credentials are incomplete.",
462
+ fix: "Provide Jira username plus api token or password, then rerun `optimus doctor --quick`."
463
+ });
464
+ }
465
+ return issues;
466
+ }
467
+ if (!config.mcpUrl?.trim() || /jira-mcp\.example\.com/i.test(config.mcpUrl)) {
468
+ issues.push({
469
+ code: "jira_mcp_url_missing",
470
+ message: "Jira integration is enabled, but Jira MCP URL is missing or still using the placeholder value.",
471
+ fix: "Provide a real Jira MCP URL, then rerun `optimus doctor --quick`."
472
+ });
473
+ }
474
+ const requiredHeaders = [
475
+ "X-Atlassian-Jira-Url",
476
+ "X-Atlassian-Username",
477
+ "X-Atlassian-Jira-Personal-Token"
478
+ ];
479
+ const missingHeaders = requiredHeaders.filter((name) => !config.httpHeaders[name]?.trim());
480
+ if (missingHeaders.length > 0) {
481
+ issues.push({
482
+ code: "jira_mcp_headers_missing",
483
+ message: `Jira integration is enabled, but Jira MCP headers are incomplete: ${missingHeaders.join(", ")}.`,
484
+ fix: "Provide the required Jira MCP headers, then rerun `optimus doctor --quick`."
485
+ });
486
+ }
487
+ return issues;
488
+ }
489
+ async function forwardJiraCliCommand(command, commandArgs) {
490
+ const mappedCommand = mapJiraCommand(command);
491
+ if (!mappedCommand) {
492
+ throw new Error(`Unsupported Jira command forwarding target: ${command}`);
493
+ }
494
+ const jiraCliEntry = process.env.OPTIMUS_JIRA_CLI_ENTRY?.trim() || JIRA_CLI_ENTRY;
495
+ return await new Promise((resolvePromise, rejectPromise) => {
496
+ const child = spawn(process.execPath, [jiraCliEntry, mappedCommand, ...commandArgs], {
497
+ cwd: process.cwd(),
498
+ env: process.env,
499
+ stdio: "inherit"
500
+ });
501
+ child.on("error", rejectPromise);
502
+ child.on("close", (code) => {
503
+ resolvePromise(code ?? 1);
504
+ });
505
+ });
506
+ }
176
507
  async function readPackageVersion() {
177
508
  const raw = await readFile(PACKAGE_JSON_PATH, "utf8");
178
509
  const parsed = JSON.parse(raw);
179
510
  return parsed.version?.trim() || "0.0.0";
180
511
  }
181
- async function rerunCurrentCommand() {
182
- const scriptPath = process.argv[1];
183
- if (!scriptPath) {
184
- throw new Error("Unable to resolve current Optimus entry script.");
185
- }
186
- return new Promise((resolve, reject) => {
187
- const child = spawn(process.execPath, [scriptPath, ...process.argv.slice(2)], {
188
- stdio: "inherit",
189
- env: process.env
190
- });
191
- child.on("error", reject);
192
- child.on("close", (code) => resolve(code ?? 1));
193
- });
512
+ function isInteractiveSelfUpdatePromptAllowed() {
513
+ return Boolean(input.isTTY && output.isTTY);
514
+ }
515
+ async function promptForStartupSelfUpdate(currentVersion, latestVersion) {
516
+ const rl = createInterface({ input, output });
517
+ try {
518
+ console.log("");
519
+ console.log(`Update available! ${currentVersion} -> ${latestVersion}`);
520
+ console.log("");
521
+ console.log(`Release notes: ${RELEASE_NOTES_URL}`);
522
+ console.log("");
523
+ console.log(`1. Update now (runs \`npm install -g ${PACKAGE_NAME}\`)`);
524
+ console.log("2. Skip");
525
+ console.log("3. Skip until next version");
526
+ console.log("");
527
+ while (true) {
528
+ const answer = (await rl.question("Press Enter to continue, or select [1/2/3] (default 2): ")).trim().toLowerCase();
529
+ if (!answer || answer === "2" || answer === "s" || answer === "skip") {
530
+ return "skip";
531
+ }
532
+ if (answer === "3" || answer === "n" || answer === "next") {
533
+ return "skip_until_next_version";
534
+ }
535
+ if (answer === "1" || answer === "u" || answer === "upgrade") {
536
+ return "upgrade";
537
+ }
538
+ console.log("Invalid selection. Enter 1, 2, or 3.");
539
+ }
540
+ }
541
+ finally {
542
+ rl.close();
543
+ }
194
544
  }
195
545
  async function maybeHandleStartupSelfUpdate(input) {
196
546
  const check = await checkForSelfUpdate({
@@ -222,7 +572,49 @@ async function maybeHandleStartupSelfUpdate(input) {
222
572
  if (!check.updateAvailable || !check.latestVersion) {
223
573
  return { handled: false };
224
574
  }
225
- if ((input.config.selfUpdate ?? buildDefaultConfig().selfUpdate).mode !== "auto" || input.command !== "start") {
575
+ if (check.reason === "skipped_version") {
576
+ await input.logger.info("self_update.skipped_version", {
577
+ command: input.command,
578
+ currentVersion: check.currentVersion,
579
+ latestVersion: check.latestVersion
580
+ });
581
+ return { handled: false };
582
+ }
583
+ const selfUpdateConfig = input.config.selfUpdate ?? buildDefaultConfig().selfUpdate;
584
+ if (input.command !== "start") {
585
+ console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
586
+ return { handled: false };
587
+ }
588
+ if (selfUpdateConfig.mode === "prompt") {
589
+ if (!isInteractiveSelfUpdatePromptAllowed()) {
590
+ console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
591
+ return { handled: false };
592
+ }
593
+ const choice = await promptForStartupSelfUpdate(check.currentVersion, check.latestVersion);
594
+ await input.logger.info("self_update.prompt_resolved", {
595
+ command: input.command,
596
+ currentVersion: check.currentVersion,
597
+ latestVersion: check.latestVersion,
598
+ choice
599
+ });
600
+ if (choice === "skip") {
601
+ return { handled: false };
602
+ }
603
+ if (choice === "skip_until_next_version") {
604
+ await recordSkippedSelfUpdate({
605
+ currentVersion: check.currentVersion,
606
+ latestVersion: check.latestVersion,
607
+ packageRoot: CLI_ROOT_DIR
608
+ });
609
+ await input.logger.info("self_update.skip_until_next_version", {
610
+ command: input.command,
611
+ currentVersion: check.currentVersion,
612
+ latestVersion: check.latestVersion
613
+ });
614
+ return { handled: false };
615
+ }
616
+ }
617
+ else if (selfUpdateConfig.mode !== "auto") {
226
618
  console.log(`[optimus] update available ${check.currentVersion} -> ${check.latestVersion}. Run \`optimus upgrade\`.`);
227
619
  return { handled: false };
228
620
  }
@@ -245,7 +637,10 @@ async function maybeHandleStartupSelfUpdate(input) {
245
637
  latestVersion: check.latestVersion,
246
638
  reason: install.state.lastError ?? install.reason ?? "unknown"
247
639
  });
248
- console.log(`[optimus] upgrade failed, continuing with ${check.currentVersion}.`);
640
+ console.log(`[optimus] upgrade failed. Continue with ${check.currentVersion}, or run \`optimus upgrade\` after fixing npm access.`);
641
+ if (selfUpdateConfig.mode === "auto") {
642
+ return { handled: true, exitCode: 1 };
643
+ }
249
644
  return { handled: false };
250
645
  }
251
646
  await input.logger.info("self_update.install_succeeded", {
@@ -253,10 +648,10 @@ async function maybeHandleStartupSelfUpdate(input) {
253
648
  currentVersion: check.currentVersion,
254
649
  latestVersion: check.latestVersion
255
650
  });
256
- console.log(`[optimus] upgrade succeeded, restarting ${input.command}...`);
651
+ console.log(`[optimus] upgrade succeeded. Restart with \`optimus ${input.command}\`.`);
257
652
  return {
258
653
  handled: true,
259
- exitCode: await rerunCurrentCommand()
654
+ exitCode: 0
260
655
  };
261
656
  }
262
657
  async function pathExists(path) {
@@ -430,8 +825,14 @@ async function promptSetupAnswers(defaults) {
430
825
  const jiraBaseUrl = enableJira
431
826
  ? (await ask(`Jira base URL [${defaults.jiraBaseUrl ?? ""}]: `)).trim() || defaults.jiraBaseUrl
432
827
  : undefined;
433
- const jiraToken = enableJira
434
- ? (await ask(`Jira token [${defaults.jiraToken ? "configured" : ""}]: `)).trim() || defaults.jiraToken
828
+ const jiraMcpUrl = enableJira
829
+ ? (await ask(`Jira MCP URL [${defaults.jiraMcpUrl ?? ""}]: `)).trim() || defaults.jiraMcpUrl
830
+ : undefined;
831
+ const jiraUsername = enableJira
832
+ ? (await ask(`Jira username [${defaults.jiraUsername ?? ""}]: `)).trim() || defaults.jiraUsername
833
+ : undefined;
834
+ const jiraPersonalToken = enableJira
835
+ ? (await ask(`Jira personal token [${defaults.jiraPersonalToken ? "configured" : ""}]: `)).trim() || defaults.jiraPersonalToken
435
836
  : undefined;
436
837
  return {
437
838
  repoPath,
@@ -447,7 +848,9 @@ async function promptSetupAnswers(defaults) {
447
848
  ...(feishuSecret ? { feishuSecret } : {}),
448
849
  enableJira,
449
850
  ...(jiraBaseUrl ? { jiraBaseUrl } : {}),
450
- ...(jiraToken ? { jiraToken } : {})
851
+ ...(jiraMcpUrl ? { jiraMcpUrl } : {}),
852
+ ...(jiraUsername ? { jiraUsername } : {}),
853
+ ...(jiraPersonalToken ? { jiraPersonalToken } : {})
451
854
  };
452
855
  }
453
856
  finally {
@@ -492,7 +895,9 @@ async function resolveDefaultSetupAnswers() {
492
895
  ...(config.delivery.feishu.secret ? { feishuSecret: config.delivery.feishu.secret } : {}),
493
896
  enableJira: config.jira.enabled,
494
897
  ...(config.jira.baseUrl ? { jiraBaseUrl: config.jira.baseUrl } : {}),
495
- ...(config.jira.bearerToken ? { jiraToken: config.jira.bearerToken } : {})
898
+ ...(config.jira.mcpUrl ? { jiraMcpUrl: config.jira.mcpUrl } : {}),
899
+ ...(config.jira.httpHeaders["X-Atlassian-Username"] ? { jiraUsername: config.jira.httpHeaders["X-Atlassian-Username"] } : {}),
900
+ ...(config.jira.httpHeaders["X-Atlassian-Jira-Personal-Token"] ? { jiraPersonalToken: config.jira.httpHeaders["X-Atlassian-Jira-Personal-Token"] } : {})
496
901
  };
497
902
  }
498
903
  function normalizeSetupCodexAuthMode(value) {
@@ -559,8 +964,14 @@ function validateSetupAnswers(answers) {
559
964
  if (answers.enableJira && !answers.jiraBaseUrl?.trim()) {
560
965
  return "setup requires a Jira base URL when Jira integration is enabled.";
561
966
  }
562
- if (answers.enableJira && !answers.jiraToken?.trim()) {
563
- return "setup requires a Jira token when Jira integration is enabled.";
967
+ if (answers.enableJira && !answers.jiraMcpUrl?.trim()) {
968
+ return "setup requires a Jira MCP URL when Jira integration is enabled.";
969
+ }
970
+ if (answers.enableJira && !answers.jiraUsername?.trim()) {
971
+ return "setup requires a Jira username when Jira integration is enabled.";
972
+ }
973
+ if (answers.enableJira && !answers.jiraPersonalToken?.trim()) {
974
+ return "setup requires a Jira personal token when Jira integration is enabled.";
564
975
  }
565
976
  return undefined;
566
977
  }
@@ -626,11 +1037,19 @@ function buildSetupConfig(answers) {
626
1037
  config.jira.enabled = answers.enableJira;
627
1038
  if (answers.enableJira) {
628
1039
  config.jira.baseUrl = answers.jiraBaseUrl ?? config.jira.baseUrl;
629
- config.jira.authType = "bearer";
630
- if (answers.jiraToken) {
631
- config.jira.bearerToken = answers.jiraToken;
632
- }
633
- config.jira.httpHeaders = {};
1040
+ if (answers.jiraMcpUrl) {
1041
+ config.jira.mcpUrl = answers.jiraMcpUrl;
1042
+ }
1043
+ config.jira.authType = "http_headers";
1044
+ config.jira.httpHeaders = {
1045
+ "X-Atlassian-Jira-Url": answers.jiraBaseUrl ?? config.jira.baseUrl,
1046
+ "X-Atlassian-Username": answers.jiraUsername ?? "",
1047
+ "X-Atlassian-Jira-Personal-Token": answers.jiraPersonalToken ?? ""
1048
+ };
1049
+ delete config.jira.bearerToken;
1050
+ delete config.jira.username;
1051
+ delete config.jira.apiToken;
1052
+ delete config.jira.password;
634
1053
  }
635
1054
  return JSON.stringify(config, null, 2);
636
1055
  }
@@ -839,20 +1258,9 @@ async function runQuickDoctor(configPath = resolveDefaultConfigPath()) {
839
1258
  });
840
1259
  }
841
1260
  if (config.jira.enabled) {
842
- if (!config.jira.baseUrl?.trim() || /jira\.example\.com/i.test(config.jira.baseUrl)) {
843
- blocking.push({
844
- code: "jira_base_url_missing",
845
- message: "Jira integration is enabled, but Jira base URL is missing or still using the placeholder value.",
846
- fix: "Rerun `optimus setup` and provide a real Jira base URL."
847
- });
848
- next.add("optimus setup");
849
- }
850
- if (!config.jira.bearerToken?.trim()) {
851
- blocking.push({
852
- code: "jira_token_missing",
853
- message: "Jira integration is enabled, but Jira token is missing.",
854
- fix: "Rerun `optimus setup` and provide a Jira token."
855
- });
1261
+ const jiraIssues = resolveJiraQuickDoctorIssues(config.jira);
1262
+ blocking.push(...jiraIssues);
1263
+ if (jiraIssues.length > 0) {
856
1264
  next.add("optimus setup");
857
1265
  }
858
1266
  }
@@ -890,7 +1298,18 @@ async function runSetup(args) {
890
1298
  if (!inspection.ok) {
891
1299
  throw new Error(`Repository path is not usable: ${normalizedRepoPath} (${inspection.reason ?? "unknown"})`);
892
1300
  }
893
- const previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection);
1301
+ const preflightConfig = configExists ? await loadConfig(configPath) : buildDefaultConfig();
1302
+ const preflightStore = new SQLiteTaskStore(preflightConfig.storage.rootDir);
1303
+ await preflightStore.init();
1304
+ const existingRepositories = await preflightStore.listRepositoryRoots();
1305
+ const aliasConflict = existingRepositories.find((repository) => repository.alias === answers.repoAlias && repository.path !== normalizedRepoPath);
1306
+ if (aliasConflict) {
1307
+ throw new Error(`Repository alias already exists for another path: ${answers.repoAlias} -> ${aliasConflict.path}. `
1308
+ + "Use `optimus repo update` or `optimus repo remove` before re-running setup.");
1309
+ }
1310
+ const existingRepository = existingRepositories.find((repository) => repository.path === normalizedRepoPath);
1311
+ const resolvedExecutionMode = existingRepository?.executionMode ?? describeRepositoryExecutionPlan({ executionMode: "copy" }, inspection).resolvedDefaultMode;
1312
+ const previewExecutionPlan = describeRepositoryExecutionPlan({ executionMode: resolvedExecutionMode }, inspection);
894
1313
  const reviewSystem = await detectRepositoryReviewSystem(normalizedRepoPath, inspection);
895
1314
  await mkdir(dirname(configPath), { recursive: true });
896
1315
  await writeFile(configPath, `${buildSetupConfig({ ...answers, repoPath: normalizedRepoPath })}\n`, "utf8");
@@ -902,14 +1321,10 @@ async function runSetup(args) {
902
1321
  const config = await loadConfig(configPath);
903
1322
  const store = new SQLiteTaskStore(config.storage.rootDir);
904
1323
  await store.init();
905
- const existingRepositories = await store.listRepositoryRoots();
906
- for (const repository of existingRepositories) {
907
- await store.removeRepositoryRoot(repository.path);
908
- }
909
1324
  await store.addRepositoryRoot({
910
1325
  path: normalizedRepoPath,
911
1326
  alias: answers.repoAlias,
912
- executionMode: previewExecutionPlan.resolvedDefaultMode
1327
+ executionMode: resolvedExecutionMode
913
1328
  });
914
1329
  const doctorQuick = await runQuickDoctor(configPath);
915
1330
  const codexAuth = new CodexAuthResolver(config).resolve().diagnostics;
@@ -922,7 +1337,7 @@ async function runSetup(args) {
922
1337
  path: normalizedRepoPath,
923
1338
  alias: answers.repoAlias,
924
1339
  workspaceKind: previewExecutionPlan.workspaceKind,
925
- executionMode: previewExecutionPlan.resolvedDefaultMode,
1340
+ executionMode: resolvedExecutionMode,
926
1341
  reviewSystem
927
1342
  },
928
1343
  deliveryChannels: config.delivery.channels,
@@ -1112,6 +1527,9 @@ function parseArgs(argv) {
1112
1527
  }
1113
1528
  return parsed;
1114
1529
  }
1530
+ function parsedCommandHasHelp(argv) {
1531
+ return argv.some((token) => token === "-h" || token === "--help");
1532
+ }
1115
1533
  async function main() {
1116
1534
  const argv = process.argv.slice(2);
1117
1535
  const [firstArg, ...rest] = argv;
@@ -1126,6 +1544,12 @@ async function main() {
1126
1544
  : firstArg;
1127
1545
  const commandArgs = rest;
1128
1546
  if (command === "help") {
1547
+ const requestedCommand = commandArgs[0]?.trim();
1548
+ if (requestedCommand) {
1549
+ console.log(renderCommandHelp(requestedCommand) ?? renderCliHelp());
1550
+ process.exitCode = renderCommandHelp(requestedCommand) ? 0 : 1;
1551
+ return;
1552
+ }
1129
1553
  console.log(renderCliHelp());
1130
1554
  return;
1131
1555
  }
@@ -1133,6 +1557,17 @@ async function main() {
1133
1557
  console.log(await readPackageVersion());
1134
1558
  return;
1135
1559
  }
1560
+ if (parsedCommandHasHelp(commandArgs)) {
1561
+ if (command === "repo") {
1562
+ const repoSubcommand = commandArgs.find((token) => !token.startsWith("--"))?.trim();
1563
+ console.log(renderCommandHelp("repo") ?? renderCliHelp());
1564
+ process.exitCode = repoSubcommand && !["add", "remove", "update", "list", "doctor"].includes(repoSubcommand) ? 1 : 0;
1565
+ return;
1566
+ }
1567
+ console.log(renderCommandHelp(command) ?? renderCliHelp());
1568
+ process.exitCode = renderCommandHelp(command) ? 0 : 1;
1569
+ return;
1570
+ }
1136
1571
  if (command === "upgrade") {
1137
1572
  const args = parseArgs(commandArgs);
1138
1573
  const currentVersion = await readPackageVersion();
@@ -1187,6 +1622,10 @@ async function main() {
1187
1622
  process.exitCode = result.doctorQuick.summary === "blocked" ? 1 : 0;
1188
1623
  return;
1189
1624
  }
1625
+ if (mapJiraCommand(command)) {
1626
+ process.exitCode = await forwardJiraCliCommand(command, commandArgs);
1627
+ return;
1628
+ }
1190
1629
  const parsedCommandArgs = parseArgs(commandArgs);
1191
1630
  if (command === "doctor" && parsedCommandArgs.quick === "true") {
1192
1631
  const result = await runQuickDoctor();
@@ -1205,7 +1644,7 @@ async function main() {
1205
1644
  }
1206
1645
  catch (error) {
1207
1646
  if (error?.code === "ENOENT") {
1208
- console.error(`Config file not found at ${resolveDefaultConfigPath()}. Run \`optimus setup\` first.`);
1647
+ console.error("Configuration is missing. Run `optimus setup` first.");
1209
1648
  process.exitCode = 1;
1210
1649
  return;
1211
1650
  }