@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.
- package/dist/cli/optimus.js +511 -72
- package/dist/cli/optimus.js.map +1 -1
- package/dist/cli/self-update.d.ts +8 -1
- package/dist/cli/self-update.js +34 -3
- package/dist/cli/self-update.js.map +1 -1
- package/dist/config/load-config.js +8 -5
- package/dist/config/load-config.js.map +1 -1
- package/dist/integrations/jira/jira-cli-config.d.ts +15 -0
- package/dist/integrations/jira/jira-cli-config.js +100 -0
- package/dist/integrations/jira/jira-cli-config.js.map +1 -0
- package/dist/integrations/jira/jira-cli.js +193 -0
- package/dist/integrations/jira/jira-cli.js.map +1 -1
- package/dist/integrations/jira/jira-client.d.ts +16 -3
- package/dist/integrations/jira/jira-client.js +71 -14
- package/dist/integrations/jira/jira-client.js.map +1 -1
- package/dist/integrations/jira/jira-submit.js +2 -0
- package/dist/integrations/jira/jira-submit.js.map +1 -1
- package/dist/task-environment/intake/manual-problem-intake.js +3 -0
- package/dist/task-environment/intake/manual-problem-intake.js.map +1 -1
- package/dist/task-environment/intake/triage-rejection-feedback-service.d.ts +21 -0
- package/dist/task-environment/intake/triage-rejection-feedback-service.js +150 -0
- package/dist/task-environment/intake/triage-rejection-feedback-service.js.map +1 -0
- package/dist/task-environment/orchestration/triage-runner.js +4 -2
- package/dist/task-environment/orchestration/triage-runner.js.map +1 -1
- package/dist/task-environment/runtime/optimus-runtime.d.ts +3 -0
- package/dist/task-environment/runtime/optimus-runtime.js +5 -0
- package/dist/task-environment/runtime/optimus-runtime.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/package.json +5 -3
- package/task-harnesses/bugfix/ACCEPT.md +57 -21
package/dist/cli/optimus.js
CHANGED
|
@@ -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
|
-
"
|
|
135
|
+
"Get started:",
|
|
134
136
|
" optimus setup",
|
|
135
|
-
" optimus
|
|
137
|
+
" optimus start",
|
|
136
138
|
" optimus submit --title \"Login crash\" --description \"Crash on startup\"",
|
|
137
139
|
"",
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"",
|
|
146
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
|
|
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 (
|
|
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
|
|
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
|
|
651
|
+
console.log(`[optimus] upgrade succeeded. Restart with \`optimus ${input.command}\`.`);
|
|
257
652
|
return {
|
|
258
653
|
handled: true,
|
|
259
|
-
exitCode:
|
|
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
|
|
434
|
-
? (await ask(`Jira
|
|
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
|
-
...(
|
|
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.
|
|
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.
|
|
563
|
-
return "setup requires a Jira
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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
|
-
|
|
843
|
-
|
|
844
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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(
|
|
1647
|
+
console.error("Configuration is missing. Run `optimus setup` first.");
|
|
1209
1648
|
process.exitCode = 1;
|
|
1210
1649
|
return;
|
|
1211
1650
|
}
|