@mytegroupinc/myte-core 0.0.26 → 0.0.28

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/README.md CHANGED
@@ -40,7 +40,7 @@ Coding agents should treat `MYTE_API_KEY` as a project-scoped Project Assistant
40
40
  - Use `suggestions create` for both existing-mission edits and new-mission proposals. The only valid `change_type` values are `update` and `create`.
41
41
  - Use `suggestions revise` only against an existing `suggestion_id`; do not send `change_type` on revisions because the backend keeps the thread's original type.
42
42
  - Use `suggestions review` for owner/elevated mission-review decisions: `approve`, `request_changes`, or `reject`.
43
- - Use `mission archive` and `mission restore` for lifecycle archive/restore. Do not send `Archived` through `mission status`.
43
+ - Use `mission archive` for project-key lifecycle archival. Do not send `Archived` through `mission status`; restore archived missions from the web archived-board view.
44
44
  - Use `feedback move` only when a direct audited state move is intended and include a concrete `--reason`.
45
45
  - Use `update-team` when an agent needs to leave a project-level implementation note or verification comment.
46
46
 
@@ -72,9 +72,8 @@ Feedback comment support:
72
72
  | Suggest new mission | `myte suggestions create` with `change_type: create` | Requires `change_set.title` and `change_set.description`; creates a review thread only; the mission card is created only after approval. |
73
73
  | Revise a suggestion | `myte suggestions revise` with `suggestion_id` | Adds another revision to the same thread. Do not include `change_type`. |
74
74
  | Review a suggestion | `myte suggestions review` with `review_action: approve|request_changes|reject` | Project Owner or elevated mission-review delegate required. Approval mutates/creates the mission once; reject archives the thread only; request_changes keeps it actionable. |
75
- | Update mission status | `myte mission status --mission-ids "M001" --status todo|in_progress|done` | Project-key surface updates canonical mission status for active cards. |
76
- | Archive mission | `myte mission archive --mission-ids "M001[,M002]" --reason "..."` | Project Owner or elevated delegate required. Sets `Archived`, removes cards from normal bootstrap/board state, and writes mission activity. |
77
- | Restore mission | `myte mission restore --mission-ids "M001[,M002]" --status todo|in_progress|done --reason "..."` | Project Owner or elevated delegate required. Restores archived cards to an active status and writes mission activity. |
75
+ | Update mission status | `myte mission status --mission-ids "M001" --status todo|in_progress|done` | Project-key surface updates canonical mission status for active cards and refreshes local bootstrap + mission-ops by default. |
76
+ | Archive mission | `myte mission archive --mission-ids "M001[,M002]" --reason "..."` | Project Owner or elevated delegate required. Sets `Archived`, removes cards from normal bootstrap/board state, writes mission activity, and refreshes local bootstrap + mission-ops by default. |
78
77
 
79
78
  Create payloads:
80
79
 
@@ -178,7 +177,6 @@ Mutation routes:
178
177
  - `POST /api/project-assistant/run-qaqc`
179
178
  - `POST /api/project-assistant/mission-status-update`
180
179
  - `POST /api/project-assistant/mission-archive`
181
- - `POST /api/project-assistant/mission-restore`
182
180
  - `POST /api/project-assistant/project-comment`
183
181
  - `POST /api/project-assistant/update-owner`
184
182
  - `POST /api/project-assistant/client-update-drafts`
package/cli.js CHANGED
@@ -30,6 +30,7 @@ const REMOVED_COMMAND_MESSAGES = {
30
30
  "add-prd": "The `add-prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
31
31
  prd: "The `prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
32
32
  "qaqc-status-update": "The `qaqc-status-update` command has been removed. Use `myte mission status --mission-ids \"M001,M002\" --status todo|in_progress|done`.",
33
+ "mission-restore": "The project-key `myte mission restore` command has been removed. Restore archived missions from the Myte web archived missions board.",
33
34
  };
34
35
 
35
36
  function findEnvPath(startDir) {
@@ -185,9 +186,8 @@ function printHelp() {
185
186
  " myte config [--json]",
186
187
  " myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
187
188
  " myte run-qaqc --mission-ids \"M001[,M002...]\" [--wait] [--sync] [--force] [--json]",
188
- " myte mission status --mission-ids \"M001[,M002...]\" --status todo|in_progress|done [--json]",
189
- " myte mission archive --mission-ids \"M001[,M002...]\" [--reason \"...\"] [--json]",
190
- " myte mission restore --mission-ids \"M001[,M002...]\" [--status todo|in_progress|done] [--reason \"...\"] [--json]",
189
+ " myte mission status --mission-ids \"M001[,M002...]\" --status todo|in_progress|done [--no-sync] [--json]",
190
+ " myte mission archive --mission-ids \"M001[,M002...]\" [--reason \"...\"] [--no-sync] [--json]",
191
191
  " myte sync-qaqc [--output-dir ./MyteCommandCenter] [--json]",
192
192
  " myte suggestions sync [--output-dir ./MyteCommandCenter] [--json]",
193
193
  " myte suggestions create [--file ./payload.yml] [--no-sync] [--json]",
@@ -266,13 +266,16 @@ function printHelp() {
266
266
  " - Accepts mission business ids and normalizes status aliases like todo, in_progress, and done",
267
267
  " - Canonical mission statuses are exactly: Todo, In Progress, Done",
268
268
  " - This only changes mission state. It does not queue QAQC and it does not sync MyteCommandCenter/data/qaqc.yml",
269
- " - Use mission archive/restore for lifecycle archival; do not send Archived through mission status",
269
+ " - Status updates refresh bootstrap + mission-ops locally by default after success unless --no-sync is set",
270
+ " - Use mission archive for lifecycle archival; do not send Archived through mission status",
270
271
  "",
271
- "mission archive/restore contract:",
272
- " - Archive calls /api/project-assistant/mission-archive and restore calls /api/project-assistant/mission-restore",
273
- " - Both require Project Owner or elevated owner_delegate authority on the authenticated project key",
274
- " - Archive hides cards from normal bootstrap/board state; restore brings them back to Todo by default",
275
- " - Missions are recoverable. The project-key surface does not hard-delete mission cards",
272
+ "mission archive contract:",
273
+ " - Archive calls /api/project-assistant/mission-archive",
274
+ " - Project Owner or elevated owner_delegate authority is required on the authenticated project key",
275
+ " - Archive hides cards from normal bootstrap/board state and keeps the mission record immutable",
276
+ " - Archive refreshes bootstrap + mission-ops locally by default after success unless --no-sync is set",
277
+ " - Restore is intentionally web-only from the archived missions board",
278
+ " - The project-key surface does not hard-delete mission cards",
276
279
  "",
277
280
  "create-prd contract:",
278
281
  " - Required: valid MYTE_API_KEY, PRD markdown body, title",
@@ -372,13 +375,13 @@ function printHelp() {
372
375
  " --review-note <text> Review note stored in feedback refinement history",
373
376
  " --with-prd-text Include extracted PRD text so local PRD files are materialized during feedback-sync (default: on)",
374
377
  " --no-with-prd-text Skip PRD text download and write only feedback metadata/comment turns",
375
- " --mission-ids <ids> Comma-separated mission business ids for run-qaqc or mission status (quote multi-id values on PowerShell)",
376
- " --reason <text> Optional reason for governed mission archive/restore or feedback changes",
378
+ " --mission-ids <ids> Comma-separated mission business ids for run-qaqc, mission status, or mission archive (quote multi-id values on PowerShell)",
379
+ " --reason <text> Optional reason for governed mission archive or feedback changes",
377
380
  " --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
378
381
  " --wait Poll batch status until terminal completion for run-qaqc",
379
382
  " --sync After run-qaqc completes, refresh local QAQC file",
380
383
  " --force Allow run-qaqc to bypass stale-state protection when supported",
381
- " --no-sync Skip automatic mission-ops resync after suggestions create/revise/review",
384
+ " --no-sync Skip automatic post-mutation sync for suggestions or mission status/archive",
382
385
  " --print-context Print JSON payload and exit (no query call)",
383
386
  " --no-fetch Don't git fetch origin main/master before diff",
384
387
  "",
@@ -394,7 +397,6 @@ function printHelp() {
394
397
  " myte run-qaqc --mission-ids \"M001,M002\" --wait --sync",
395
398
  " myte mission status --mission-ids \"M001,M002\" --status done",
396
399
  " myte mission archive --mission-ids \"M001\" --reason \"Duplicate disposable test mission\"",
397
- " myte mission restore --mission-ids \"M001\" --status todo",
398
400
  " myte bootstrap --output-dir ./MyteCommandCenter",
399
401
  " myte sync-qaqc",
400
402
  " myte update-team \"Backend deploy completed; QAQC rerun queued.\"",
@@ -2412,6 +2414,18 @@ async function runMissionStatus(args) {
2412
2414
  process.exit(1);
2413
2415
  }
2414
2416
 
2417
+ const shouldSync = shouldSyncMissionStateAfterMutation(args);
2418
+ let syncSummary = null;
2419
+ if (shouldSync) {
2420
+ try {
2421
+ syncSummary = await resyncBootstrapAfterMissionMutation({ args, apiBase, key, timeoutMs });
2422
+ } catch (err) {
2423
+ console.error("Mission status update succeeded, but bootstrap sync failed:", err?.message || err);
2424
+ console.error("Run `myte bootstrap` to refresh local Command Center state.");
2425
+ process.exit(1);
2426
+ }
2427
+ }
2428
+
2415
2429
  const output = {
2416
2430
  project_id: data.project_id || null,
2417
2431
  requested_count: data.requested_count || missionIds.length,
@@ -2421,6 +2435,7 @@ async function runMissionStatus(args) {
2421
2435
  rejected_count: data.rejected_count || 0,
2422
2436
  new_status: data.new_status || newStatus,
2423
2437
  missions: Array.isArray(data.missions) ? data.missions : [],
2438
+ sync: syncSummary,
2424
2439
  };
2425
2440
 
2426
2441
  if (args.json) {
@@ -2443,11 +2458,14 @@ async function runMissionStatus(args) {
2443
2458
  if (mission?.new_status) detailParts.push(`target=${mission.new_status}`);
2444
2459
  console.log(`- ${missionId}: ${status}${detailParts.length ? ` (${detailParts.join(", ")})` : ""}`);
2445
2460
  }
2461
+ if (syncSummary?.output_root) {
2462
+ console.log(`Synced Command Center: ${syncSummary.output_root}`);
2463
+ }
2446
2464
  }
2447
2465
 
2448
- async function runMissionArchiveCommand(args, { restore = false } = {}) {
2466
+ async function runMissionArchiveCommand(args) {
2449
2467
  const key = getProjectApiKey();
2450
- const commandLabel = restore ? "mission restore" : "mission archive";
2468
+ const commandLabel = "mission archive";
2451
2469
  if (!key) {
2452
2470
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
2453
2471
  process.exit(1);
@@ -2463,21 +2481,12 @@ async function runMissionArchiveCommand(args, { restore = false } = {}) {
2463
2481
  const payload = {
2464
2482
  mission_ids: missionIds,
2465
2483
  };
2466
- if (restore) {
2467
- const restoreStatus = normalizeMissionStatusInput(firstNonEmptyString(args.status, args["restore-status"], args.restoreStatus, args.restore_status) || "todo");
2468
- if (!restoreStatus) {
2469
- console.error("Missing or invalid --status for `myte mission restore`. Use todo, in_progress, or done.");
2470
- printHelp();
2471
- process.exit(1);
2472
- }
2473
- payload.restore_status = restoreStatus;
2474
- }
2475
2484
  const reason = firstNonEmptyString(args.reason);
2476
2485
  if (reason) payload.reason = reason;
2477
2486
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2478
2487
  if (clientSessionId) payload.client_session_id = clientSessionId;
2479
2488
 
2480
- const operation = restore ? "mission-restore" : "mission-archive";
2489
+ const operation = "mission-archive";
2481
2490
  const idempotencyKey = resolveProjectMutationIdempotencyKey({
2482
2491
  args,
2483
2492
  operation,
@@ -2491,7 +2500,7 @@ async function runMissionArchiveCommand(args, { restore = false } = {}) {
2491
2500
 
2492
2501
  const timeoutMs = resolveTimeoutMs(args);
2493
2502
  const apiBase = resolveApiBase(args);
2494
- const endpoint = restore ? "/project-assistant/mission-restore" : "/project-assistant/mission-archive";
2503
+ const endpoint = "/project-assistant/mission-archive";
2495
2504
 
2496
2505
  let data;
2497
2506
  try {
@@ -2508,11 +2517,23 @@ async function runMissionArchiveCommand(args, { restore = false } = {}) {
2508
2517
  if (err?.name === "AbortError") {
2509
2518
  console.error(`Request timed out after ${timeoutMs}ms`);
2510
2519
  } else {
2511
- console.error(`Mission ${restore ? "restore" : "archive"} failed:`, err?.message || err);
2520
+ console.error("Mission archive failed:", err?.message || err);
2512
2521
  }
2513
2522
  process.exit(1);
2514
2523
  }
2515
2524
 
2525
+ const shouldSync = shouldSyncMissionStateAfterMutation(args);
2526
+ let syncSummary = null;
2527
+ if (shouldSync) {
2528
+ try {
2529
+ syncSummary = await resyncBootstrapAfterMissionMutation({ args, apiBase, key, timeoutMs });
2530
+ } catch (err) {
2531
+ console.error("Mission archive succeeded, but bootstrap sync failed:", err?.message || err);
2532
+ console.error("Run `myte bootstrap` to refresh local Command Center state.");
2533
+ process.exit(1);
2534
+ }
2535
+ }
2536
+
2516
2537
  const output = {
2517
2538
  project_id: data.project_id || null,
2518
2539
  requested_count: data.requested_count || missionIds.length,
@@ -2520,9 +2541,10 @@ async function runMissionArchiveCommand(args, { restore = false } = {}) {
2520
2541
  updated_count: data.updated_count || 0,
2521
2542
  unchanged_count: data.unchanged_count || 0,
2522
2543
  rejected_count: data.rejected_count || 0,
2523
- archive_state: data.archive_state || (restore ? "active" : "archived"),
2524
- new_status: data.new_status || (restore ? "Todo" : "Archived"),
2544
+ archive_state: data.archive_state || "archived",
2545
+ new_status: data.new_status || "Archived",
2525
2546
  missions: Array.isArray(data.missions) ? data.missions : [],
2547
+ sync: syncSummary,
2526
2548
  };
2527
2549
 
2528
2550
  if (args.json) {
@@ -2546,6 +2568,9 @@ async function runMissionArchiveCommand(args, { restore = false } = {}) {
2546
2568
  if (mission?.new_status) detailParts.push(`target=${mission.new_status}`);
2547
2569
  console.log(`- ${missionId}: ${status}${detailParts.length ? ` (${detailParts.join(", ")})` : ""}`);
2548
2570
  }
2571
+ if (syncSummary?.output_root) {
2572
+ console.log(`Synced Command Center: ${syncSummary.output_root}`);
2573
+ }
2549
2574
  }
2550
2575
 
2551
2576
  function formatTargetContacts(contacts) {
@@ -3653,6 +3678,11 @@ function shouldSyncSuggestionsAfterMutation(args) {
3653
3678
  return !rawArgs.includes("--no-sync");
3654
3679
  }
3655
3680
 
3681
+ function shouldSyncMissionStateAfterMutation(args) {
3682
+ const rawArgs = Array.isArray(args?.__raw) ? args.__raw : [];
3683
+ return !rawArgs.includes("--no-sync");
3684
+ }
3685
+
3656
3686
  function resolveBooleanFlag(args, dashedName, defaultValue) {
3657
3687
  const rawArgs = Array.isArray(args?.__raw) ? args.__raw : [];
3658
3688
  const enabledToken = `--${dashedName}`;
@@ -5640,6 +5670,42 @@ async function resyncMissionOpsAfterMutation({ apiBase, key, timeoutMs, actorSco
5640
5670
  };
5641
5671
  }
5642
5672
 
5673
+ async function resyncBootstrapAfterMissionMutation({ args, apiBase, key, timeoutMs }) {
5674
+ const outputDir = args["output-dir"] || args.outputDir || args.output_dir;
5675
+ const existingPaths = resolveMissionOpsPaths(args, { requireExisting: false });
5676
+ const existingPayload = existingPaths && fs.existsSync(existingPaths.missionOpsPath)
5677
+ ? (readYamlFile(existingPaths.missionOpsPath) || {})
5678
+ : {};
5679
+ const actorScope = resolveActorScope(args, existingPayload);
5680
+ const snapshot = await fetchBootstrapSnapshot({ apiBase, key, timeoutMs });
5681
+ const resolved = resolvePortableWorkspace(snapshot.repo_names || []);
5682
+ const wrapperRoot = !outputDir && existingPaths?.wrapperRoot ? existingPaths.wrapperRoot : resolved.root;
5683
+ const writeResult = writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir });
5684
+ const missionOpsSnapshot = await fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScope });
5685
+ const missionOpsWrite = writeMissionOpsSnapshot({ snapshot: missionOpsSnapshot, wrapperRoot, outputDir });
5686
+ const missionOpsCounts = missionOpsWrite?.manifest?.sync?.counts || {};
5687
+ const outputRoot = outputDir ? path.resolve(process.cwd(), String(outputDir)) : path.join(wrapperRoot, "MyteCommandCenter");
5688
+ return {
5689
+ wrapper_root: wrapperRoot,
5690
+ output_root: outputRoot,
5691
+ data_root: writeResult.dataRoot,
5692
+ snapshot_hash: snapshot.snapshot_hash || null,
5693
+ counts: {
5694
+ phases: Array.isArray(snapshot.phases) ? snapshot.phases.length : 0,
5695
+ epics: Array.isArray(snapshot.epics) ? snapshot.epics.length : 0,
5696
+ stories: Array.isArray(snapshot.stories) ? snapshot.stories.length : 0,
5697
+ missions: Array.isArray(snapshot.missions) ? snapshot.missions.length : 0,
5698
+ },
5699
+ mission_ops: {
5700
+ actor_scope: missionOpsWrite?.manifest?.sync?.actor_scope || actorScope,
5701
+ total_threads: Number(missionOpsCounts.total_threads || 0),
5702
+ actionable_threads: Number(missionOpsCounts.actionable_threads || 0),
5703
+ snapshot_hash: missionOpsWrite?.manifest?.snapshot_hash || null,
5704
+ data_root: missionOpsWrite?.dataRoot || writeResult.dataRoot,
5705
+ },
5706
+ };
5707
+ }
5708
+
5643
5709
  async function runSuggestionsCreate(args) {
5644
5710
  let context;
5645
5711
  try {
@@ -6207,7 +6273,7 @@ async function main() {
6207
6273
  process.exit(1);
6208
6274
  }
6209
6275
  if (command === "mission") {
6210
- console.error("Unknown mission command. Use `myte mission status`, `myte mission archive`, or `myte mission restore`.");
6276
+ console.error("Unknown mission command. Use `myte mission status` or `myte mission archive`.");
6211
6277
  process.exit(1);
6212
6278
  }
6213
6279
  const args = parseArgs(rest);
@@ -6242,12 +6308,7 @@ async function main() {
6242
6308
  }
6243
6309
 
6244
6310
  if (command === "mission-archive") {
6245
- await runMissionArchiveCommand(args, { restore: false });
6246
- return;
6247
- }
6248
-
6249
- if (command === "mission-restore") {
6250
- await runMissionArchiveCommand(args, { restore: true });
6311
+ await runMissionArchiveCommand(args);
6251
6312
  return;
6252
6313
  }
6253
6314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mytegroupinc/myte-core",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "Myte CLI core implementation.",
5
5
  "type": "commonjs",
6
6
  "main": "cli.js",
@@ -8,6 +8,7 @@
8
8
  "README.md",
9
9
  "cli.js",
10
10
  "lib",
11
+ "scripts",
11
12
  "package.json"
12
13
  ],
13
14
  "scripts": {
@@ -0,0 +1,226 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const { spawnSync } = require("node:child_process");
8
+
9
+ const CLI_PATH = path.resolve(__dirname, "..", "cli.js");
10
+
11
+ function parseArgs(argv) {
12
+ const args = { _: [] };
13
+ for (let index = 0; index < argv.length; index += 1) {
14
+ const token = argv[index];
15
+ if (!token.startsWith("--")) {
16
+ args._.push(token);
17
+ continue;
18
+ }
19
+ const key = token.slice(2);
20
+ const next = argv[index + 1];
21
+ if (!next || next.startsWith("--")) {
22
+ args[key] = true;
23
+ continue;
24
+ }
25
+ args[key] = next;
26
+ index += 1;
27
+ }
28
+ return args;
29
+ }
30
+
31
+ function requireConfirm(args) {
32
+ if (!args["confirm-live"]) {
33
+ throw new Error("Refusing to run live mission mutations. Re-run with --confirm-live after backend/frontend deploy.");
34
+ }
35
+ if (!process.env.MYTE_API_KEY && !process.env.MYTE_PROJECT_API_KEY) {
36
+ throw new Error("Missing MYTE_API_KEY or MYTE_PROJECT_API_KEY.");
37
+ }
38
+ }
39
+
40
+ function runCli(cliArgs, cwd, envPatch = {}) {
41
+ const result = spawnSync(process.execPath, [CLI_PATH, ...cliArgs], {
42
+ cwd,
43
+ env: { ...process.env, ...envPatch },
44
+ encoding: "utf8",
45
+ stdio: ["ignore", "pipe", "pipe"],
46
+ });
47
+ if (result.status !== 0) {
48
+ throw new Error([
49
+ `Command failed: myte ${cliArgs.join(" ")}`,
50
+ result.stderr || result.stdout || "(no output)",
51
+ ].join("\n"));
52
+ }
53
+ const stdout = String(result.stdout || "").trim();
54
+ if (!stdout) return {};
55
+ try {
56
+ return JSON.parse(stdout);
57
+ } catch (err) {
58
+ throw new Error(`Expected JSON from myte ${cliArgs.join(" ")}:\n${stdout}`);
59
+ }
60
+ }
61
+
62
+ function writeJson(workspace, name, payload) {
63
+ const filePath = path.join(workspace, name);
64
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
65
+ return filePath;
66
+ }
67
+
68
+ function collectMissionFileText(workspace) {
69
+ const missionsDir = path.join(workspace, "MyteCommandCenter", "data", "missions");
70
+ if (!fs.existsSync(missionsDir)) return "";
71
+ return fs.readdirSync(missionsDir)
72
+ .filter((name) => name.endsWith(".yml") || name.endsWith(".yaml") || name.endsWith(".json"))
73
+ .map((name) => fs.readFileSync(path.join(missionsDir, name), "utf8"))
74
+ .join("\n");
75
+ }
76
+
77
+ function assertMissionPresence(workspace, missionId, expectedPresent, label) {
78
+ const text = collectMissionFileText(workspace);
79
+ const present = text.includes(`mission_id: ${missionId}`) || text.includes(`"mission_id": "${missionId}"`);
80
+ if (present !== expectedPresent) {
81
+ throw new Error(`${label}: expected mission ${missionId} ${expectedPresent ? "in" : "absent from"} bootstrap mission state.`);
82
+ }
83
+ }
84
+
85
+ function pickSuggestionId(createOutput) {
86
+ for (const item of createOutput.items || []) {
87
+ const suggestionId = item?.suggestion?.suggestion_id || item?.suggestion_id;
88
+ if (suggestionId) return String(suggestionId);
89
+ }
90
+ return "";
91
+ }
92
+
93
+ function pickAppliedMissionId(reviewOutput) {
94
+ for (const item of reviewOutput.items || []) {
95
+ const missionId = item?.applied_mission?.mission_id || item?.mission_id || item?.suggestion?.mission_id;
96
+ if (missionId) return String(missionId);
97
+ }
98
+ return "";
99
+ }
100
+
101
+ function main() {
102
+ const args = parseArgs(process.argv.slice(2));
103
+ requireConfirm(args);
104
+
105
+ const workspace = path.resolve(
106
+ args.workspace || fs.mkdtempSync(path.join(os.tmpdir(), "myte-live-mission-harness-")),
107
+ );
108
+ fs.mkdirSync(workspace, { recursive: true });
109
+
110
+ const actorScope = String(args["actor-scope"] || `live-disposable-harness-${Date.now()}`);
111
+ const baseArgs = [];
112
+ if (args["base-url"]) {
113
+ baseArgs.push("--base-url", String(args["base-url"]));
114
+ }
115
+
116
+ const title = String(args.title || `Disposable mission harness ${new Date().toISOString()}`);
117
+ const description = String(args.description || "Disposable mission used to verify Myte mission create, approve, archive, and cleanup flows.");
118
+ const reason = String(args.reason || "Disposable live mission harness verification");
119
+
120
+ console.log(`Workspace: ${workspace}`);
121
+ console.log(`Actor scope: ${actorScope}`);
122
+
123
+ runCli(["bootstrap", "--json", "--actor-scope", actorScope, ...baseArgs], workspace);
124
+
125
+ const createFile = writeJson(workspace, "mission-create.json", {
126
+ items: [
127
+ {
128
+ change_type: "create",
129
+ change_description: "Create disposable mission for live harness verification",
130
+ change_set: {
131
+ title,
132
+ description,
133
+ acceptance_criteria: [
134
+ "Mission can be approved from the suggestion review loop.",
135
+ "Mission can be archived without hard delete.",
136
+ "Archived missions disappear from normal bootstrap state.",
137
+ ],
138
+ labels: ["harness", "disposable"],
139
+ },
140
+ },
141
+ ],
142
+ });
143
+ const createOutput = runCli([
144
+ "suggestions",
145
+ "create",
146
+ "--file",
147
+ createFile,
148
+ "--actor-scope",
149
+ actorScope,
150
+ "--no-sync",
151
+ "--json",
152
+ ...baseArgs,
153
+ ], workspace);
154
+ const suggestionId = pickSuggestionId(createOutput);
155
+ if (!suggestionId) {
156
+ throw new Error(`Suggestion create did not return a suggestion id: ${JSON.stringify(createOutput, null, 2)}`);
157
+ }
158
+
159
+ const reviewFile = writeJson(workspace, "mission-review.json", {
160
+ items: [
161
+ {
162
+ suggestion_id: suggestionId,
163
+ action: "approve",
164
+ review_action: "approve",
165
+ final_change_set: {
166
+ title,
167
+ description,
168
+ acceptance_criteria: [
169
+ "Mission can be approved from the suggestion review loop.",
170
+ "Mission can be archived without hard delete.",
171
+ "Archived missions disappear from normal bootstrap state.",
172
+ ],
173
+ labels: ["harness", "disposable"],
174
+ },
175
+ },
176
+ ],
177
+ });
178
+ const reviewOutput = runCli([
179
+ "suggestions",
180
+ "review",
181
+ "--file",
182
+ reviewFile,
183
+ "--actor-scope",
184
+ actorScope,
185
+ "--no-sync",
186
+ "--json",
187
+ ...baseArgs,
188
+ ], workspace);
189
+ const missionId = pickAppliedMissionId(reviewOutput);
190
+ if (!missionId) {
191
+ throw new Error(`Suggestion approval did not return an applied mission id: ${JSON.stringify(reviewOutput, null, 2)}`);
192
+ }
193
+
194
+ const archiveOutput = runCli([
195
+ "mission",
196
+ "archive",
197
+ "--mission-ids",
198
+ missionId,
199
+ "--reason",
200
+ reason,
201
+ "--json",
202
+ ...baseArgs,
203
+ ], workspace);
204
+ if (!Number(archiveOutput.updated_count || 0) && !Number(archiveOutput.unchanged_count || 0)) {
205
+ throw new Error(`Archive did not update or confirm mission state: ${JSON.stringify(archiveOutput, null, 2)}`);
206
+ }
207
+ runCli(["bootstrap", "--json", "--actor-scope", actorScope, ...baseArgs], workspace);
208
+ assertMissionPresence(workspace, missionId, false, "Archived bootstrap check");
209
+
210
+ const finalState = "archived";
211
+
212
+ console.log(JSON.stringify({
213
+ status: "success",
214
+ workspace,
215
+ suggestion_id: suggestionId,
216
+ mission_id: missionId,
217
+ final_state: finalState,
218
+ }, null, 2));
219
+ }
220
+
221
+ try {
222
+ main();
223
+ } catch (err) {
224
+ console.error(err?.message || err);
225
+ process.exit(1);
226
+ }