@mytegroupinc/myte-core 0.0.17 → 0.0.19

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 (3) hide show
  1. package/README.md +8 -0
  2. package/cli.js +188 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,6 +7,7 @@ Most users should install the unscoped wrapper instead:
7
7
  - `npm install myte` then `npx myte ai "Return a JSON object with risks and next_steps" --json-response`
8
8
  - `npm install myte` then `npx myte bootstrap`
9
9
  - `npm install myte` then `npx myte run-qaqc --mission-ids M001 --wait --sync`
10
+ - `npm install myte` then `npx myte mission status --mission-ids M001 --status done`
10
11
  - `npm install myte` then `npx myte sync-qaqc`
11
12
  - `npm install myte` then `npx myte feedback-sync`
12
13
  - `npm install myte` then `npx myte suggestions sync`
@@ -20,6 +21,7 @@ Most users should install the unscoped wrapper instead:
20
21
  - `npm install myte` then `npx myte update-client --subject "Weekly client update" --body-file ./updates/week-12.md`
21
22
  - `npm i -g myte` then `myte bootstrap`
22
23
  - `npm i -g myte` then `myte run-qaqc --mission-ids M001 --wait --sync`
24
+ - `npm i -g myte` then `myte mission status --mission-ids M001 --status done`
23
25
  - `npm i -g myte` then `myte sync-qaqc`
24
26
  - `npm i -g myte` then `myte feedback-sync`
25
27
  - `npm i -g myte` then `myte suggestions sync`
@@ -32,6 +34,7 @@ Most users should install the unscoped wrapper instead:
32
34
  - `npm i -g myte` then `myte update-client --subject "Weekly client update" --body-file ./updates/week-12.md`
33
35
  - `npx myte@latest bootstrap`
34
36
  - `npx myte@latest run-qaqc --mission-ids M001 --wait --sync`
37
+ - `npx myte@latest mission status --mission-ids M001 --status done`
35
38
  - `npx myte@latest sync-qaqc`
36
39
  - `npx myte@latest feedback-sync`
37
40
  - `npx myte@latest suggestions sync`
@@ -68,6 +71,9 @@ Notes:
68
71
  - On PowerShell, quote comma-separated multi-id values: `--mission-ids "M001,M002"`.
69
72
  - `run-qaqc --wait` polls `/api/project-assistant/run-qaqc/<batch_id>` until the batch is terminal.
70
73
  - `run-qaqc --sync` refreshes `MyteCommandCenter/data/qaqc.yml` after a completed batch.
74
+ - `mission status` updates one or many mission business ids through `/api/project-assistant/mission-status-update`.
75
+ - `mission status` normalizes status aliases like `todo`, `in_progress`, and `done`, then sends the canonical mission status values `Todo`, `In Progress`, or `Done`.
76
+ - `mission status` updates only the mission `status` field used by the app. It does not run QAQC and it does not rewrite `MyteCommandCenter/data/qaqc.yml`.
71
77
  - project-key QAQC runs through the dedicated `project_api_qaqc` queue inside the existing Celery service, with a global budget of `20` dispatch starts/minute and `20` live jobs.
72
78
  - a saturated `run-qaqc --wait` batch can take roughly `5-10` minutes before `--sync` has final data to write.
73
79
  - `sync-qaqc` works without `bootstrap`; it creates `MyteCommandCenter/data/qaqc.yml` automatically if missing.
@@ -76,6 +82,7 @@ Notes:
76
82
  - `sync-qaqc` keeps QAQC state in one deterministic file so the working set grows and shrinks with current active-mission reality.
77
83
  - `sync-qaqc` fully rewrites `MyteCommandCenter/data/qaqc.yml` on every sync and does not delete `MyteCommandCenter/data/missions/*.yml`.
78
84
  - `feedback-sync` writes one deterministic feedback snapshot under `MyteCommandCenter/data/feedback.yml`.
85
+ - `feedback-sync` defaults to pending feedback unless `--status` is provided.
79
86
  - `feedback-sync` keeps feedback metadata plus comment turns in `MyteCommandCenter/data/feedback.yml`.
80
87
  - `feedback-sync` writes full PRD context into `MyteCommandCenter/PRD/feedback-sync/*.md` and points to those files from `feedback.yml`.
81
88
  - `feedback-sync` fully replaces the feedback-owned sync file to avoid stale local feedback noise.
@@ -122,6 +129,7 @@ Examples:
122
129
  - `npx myte ai "Return a JSON object with risks and next_steps" --json-response`
123
130
  - `npx myte bootstrap`
124
131
  - `npx myte run-qaqc --mission-ids "M001,M002" --wait --sync`
132
+ - `npx myte mission status --mission-ids "M001,M002" --status done`
125
133
  - `npx myte sync-qaqc`
126
134
  - `npx myte feedback-sync`
127
135
  - `npx myte suggestions sync`
package/cli.js CHANGED
@@ -28,6 +28,7 @@ const REMOVED_COMMAND_MESSAGES = {
28
28
  "create-prds": "The `create-prds` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
29
29
  "add-prd": "The `add-prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
30
30
  prd: "The `prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
31
+ "qaqc-status-update": "The `qaqc-status-update` command has been removed. Use `myte mission status --mission-ids \"M001,M002\" --status todo|in_progress|done`.",
31
32
  };
32
33
 
33
34
  function findEnvPath(startDir) {
@@ -66,6 +67,16 @@ function loadEnv() {
66
67
  }
67
68
 
68
69
  function splitCommand(argv) {
70
+ if (argv[0] === "mission") {
71
+ if (argv[1] === "status" && argv[2] === "update") {
72
+ return { command: "mission-status", rest: argv.slice(3) };
73
+ }
74
+ if (argv[1] === "status") {
75
+ return { command: "mission-status", rest: argv.slice(2) };
76
+ }
77
+ return { command: "mission", rest: argv.slice(1) };
78
+ }
79
+
69
80
  const known = new Set([
70
81
  "query",
71
82
  "ai",
@@ -75,6 +86,8 @@ function splitCommand(argv) {
75
86
  "bootstrap",
76
87
  "suggestions",
77
88
  "run-qaqc",
89
+ "qaqc-status-update",
90
+ "mission",
78
91
  "sync-qaqc",
79
92
  "qaqc-sync",
80
93
  "create-prd",
@@ -189,6 +202,7 @@ function printHelp() {
189
202
  " myte config [--json]",
190
203
  " myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
191
204
  " myte run-qaqc --mission-ids \"M001[,M002...]\" [--wait] [--sync] [--force] [--json]",
205
+ " myte mission status --mission-ids \"M001[,M002...]\" --status todo|in_progress|done [--json]",
192
206
  " myte sync-qaqc [--output-dir ./MyteCommandCenter] [--json]",
193
207
  " myte suggestions sync [--output-dir ./MyteCommandCenter] [--json]",
194
208
  " myte suggestions create [--file ./payload.yml] [--no-sync] [--json]",
@@ -197,7 +211,7 @@ function printHelp() {
197
211
  " myte update-team \"<content>\" [--json]",
198
212
  " myte update-owner --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--json]",
199
213
  " myte update-client --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--target-contact-ids <id1,id2>] [--json]",
200
- " myte feedback-sync [--status Pending] [--source User] [--output-dir ./MyteCommandCenter] [--json]",
214
+ " myte feedback-sync [--status <value>] [--source <value>] [--with-prd-text|--no-with-prd-text] [--output-dir ./MyteCommandCenter] [--json]",
201
215
  " myte create-prd <file.md> [more.md ...] [--json] [--title \"...\"] [--description \"...\"]",
202
216
  " cat file.md | myte create-prd --stdin [--title \"...\"] [--description \"...\"]",
203
217
  " cat update.md | myte update-owner --stdin --subject \"Owner update\"",
@@ -241,6 +255,12 @@ function printHelp() {
241
255
  " - Use --wait to poll batch status and --sync to refresh MyteCommandCenter/data/qaqc.yml after completion",
242
256
  " - On PowerShell, quote comma-separated mission ids: --mission-ids \"M001,M002\"",
243
257
  "",
258
+ "mission status contract:",
259
+ " - Updates the same mission `status` field used by the product board through /api/project-assistant/mission-status-update",
260
+ " - Accepts mission business ids and normalizes status aliases like todo, in_progress, and done",
261
+ " - Canonical mission statuses are exactly: Todo, In Progress, Done",
262
+ " - This only changes mission state. It does not queue QAQC and it does not sync MyteCommandCenter/data/qaqc.yml",
263
+ "",
244
264
  "create-prd contract:",
245
265
  " - Required: valid MYTE_API_KEY, PRD markdown body, title",
246
266
  " - Accepts one file or many files per command; multi-file uploads are sent in one deterministic batch request",
@@ -266,7 +286,8 @@ function printHelp() {
266
286
  "",
267
287
  "feedback-sync contract:",
268
288
  " - Runs from the wrapper root that contains the project's configured repo folders",
269
- " - Writes open project feedback metadata and conversation turns into MyteCommandCenter/data/feedback.yml",
289
+ " - Syncs pending feedback by default so local Command Center data stays focused on active work",
290
+ " - Writes project feedback metadata and conversation turns into MyteCommandCenter/data/feedback.yml",
270
291
  " - Stores full PRD context in MyteCommandCenter/PRD/feedback-sync/*.md and points to those files from feedback.yml",
271
292
  "",
272
293
  "Options:",
@@ -289,10 +310,11 @@ function printHelp() {
289
310
  " --body-file <path> Read update-owner or update-client markdown body from a file",
290
311
  " --target-contact-id Add one client contact ObjectId (repeatable)",
291
312
  " --target-contact-ids Comma-separated client contact ObjectIds",
292
- " --status <value> Feedback status filter for feedback-sync (default: Pending)",
313
+ " --status <value> For mission status: required target status (todo|in_progress|done). For feedback-sync: optional filter (default: Pending).",
293
314
  " --source <value> Feedback source filter for feedback-sync",
294
- " --with-prd-text Include extracted PRD text so local PRD files can be materialized during feedback-sync (default: on)",
295
- " --mission-ids <ids> Comma-separated mission business ids for run-qaqc (quote multi-id values on PowerShell)",
315
+ " --with-prd-text Include extracted PRD text so local PRD files are materialized during feedback-sync (default: on)",
316
+ " --no-with-prd-text Skip PRD text download and write only feedback metadata/comment turns",
317
+ " --mission-ids <ids> Comma-separated mission business ids for run-qaqc or mission status (quote multi-id values on PowerShell)",
296
318
  " --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
297
319
  " --wait Poll batch status until terminal completion for run-qaqc",
298
320
  " --sync After run-qaqc completes, refresh local QAQC file",
@@ -311,12 +333,13 @@ function printHelp() {
311
333
  " myte suggestions revise --no-sync",
312
334
  " myte suggestions review --file ./review.yml",
313
335
  " myte run-qaqc --mission-ids \"M001,M002\" --wait --sync",
336
+ " myte mission status --mission-ids \"M001,M002\" --status done",
314
337
  " myte bootstrap --output-dir ./MyteCommandCenter",
315
338
  " myte sync-qaqc",
316
339
  " myte update-team \"Backend deploy completed; QAQC rerun queued.\"",
317
340
  " myte update-owner --subject \"QAQC progress\" --body-file ./updates/owner.md",
318
341
  " myte update-client --subject \"Weekly client update\" --body-file ./updates/week-12.md",
319
- " myte feedback-sync --status Pending --source User",
342
+ " myte feedback-sync --json",
320
343
  " myte suggestions create --file ./suggestions/create.yml",
321
344
  " myte suggestions revise",
322
345
  " myte suggestions review",
@@ -397,6 +420,16 @@ function dedupeStrings(values) {
397
420
  return unique;
398
421
  }
399
422
 
423
+ function normalizeMissionStatusInput(value) {
424
+ const raw = String(value || "").trim();
425
+ if (!raw) return "";
426
+ const compact = raw.replace(/[\s_-]+/g, "").toLowerCase();
427
+ if (compact === "todo" || compact === "pending") return "Todo";
428
+ if (compact === "inprogress" || compact === "doing") return "In Progress";
429
+ if (compact === "done" || compact === "complete" || compact === "completed") return "Done";
430
+ return "";
431
+ }
432
+
400
433
  function resolveTimeoutMs(args) {
401
434
  const timeoutRaw = args["timeout-ms"] || args.timeoutMs || args.timeout_ms;
402
435
  const timeoutParsed = timeoutRaw !== undefined ? Number(timeoutRaw) : 300_000;
@@ -1219,6 +1252,34 @@ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotenc
1219
1252
  return body.data || {};
1220
1253
  }
1221
1254
 
1255
+ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
1256
+ const fetchFn = await getFetch();
1257
+ const url = `${apiBase}/project-assistant/mission-status-update`;
1258
+ const { resp, body } = await fetchJsonWithTimeout(
1259
+ fetchFn,
1260
+ url,
1261
+ {
1262
+ method: "POST",
1263
+ headers: {
1264
+ "Content-Type": "application/json",
1265
+ Authorization: `Bearer ${key}`,
1266
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1267
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1268
+ },
1269
+ body: JSON.stringify(payload),
1270
+ },
1271
+ timeoutMs
1272
+ );
1273
+
1274
+ if (!resp.ok || body.status !== "success") {
1275
+ const msg = body?.message || `Mission status update request failed (${resp.status})`;
1276
+ const err = new Error(msg);
1277
+ err.status = resp.status;
1278
+ throw err;
1279
+ }
1280
+ return body.data || {};
1281
+ }
1282
+
1222
1283
  async function fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId }) {
1223
1284
  const fetchFn = await getFetch();
1224
1285
  const url = `${apiBase}/project-assistant/run-qaqc/${encodeURIComponent(String(batchId || ""))}`;
@@ -1456,6 +1517,99 @@ async function runRunQaqc(args) {
1456
1517
  }
1457
1518
  }
1458
1519
 
1520
+ async function runMissionStatus(args) {
1521
+ const key = getProjectApiKey();
1522
+ if (!key) {
1523
+ console.error("Missing MYTE_API_KEY (project key) in environment/.env");
1524
+ process.exit(1);
1525
+ }
1526
+
1527
+ const missionIds = parseMissionIdsArg(args);
1528
+ if (!missionIds.length) {
1529
+ console.error("Missing --mission-ids for `myte mission status`.");
1530
+ printHelp();
1531
+ process.exit(1);
1532
+ }
1533
+
1534
+ const newStatus = normalizeMissionStatusInput(firstNonEmptyString(args.status));
1535
+ if (!newStatus) {
1536
+ console.error("Missing or invalid --status for `myte mission status`. Use todo, in_progress, or done.");
1537
+ printHelp();
1538
+ process.exit(1);
1539
+ }
1540
+
1541
+ const payload = {
1542
+ mission_ids: missionIds,
1543
+ new_status: newStatus,
1544
+ };
1545
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
1546
+ if (clientSessionId) payload.client_session_id = clientSessionId;
1547
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
1548
+ args,
1549
+ operation: "mission-status-update",
1550
+ payload,
1551
+ });
1552
+
1553
+ if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
1554
+ console.log(JSON.stringify(payload, null, 2));
1555
+ return;
1556
+ }
1557
+
1558
+ const timeoutMs = resolveTimeoutMs(args);
1559
+ const apiBase = resolveApiBase(args);
1560
+
1561
+ let data;
1562
+ try {
1563
+ data = await createMissionStatusUpdate({
1564
+ apiBase,
1565
+ key,
1566
+ timeoutMs,
1567
+ payload,
1568
+ idempotencyKey,
1569
+ clientSessionId,
1570
+ });
1571
+ } catch (err) {
1572
+ if (err?.name === "AbortError") {
1573
+ console.error(`Request timed out after ${timeoutMs}ms`);
1574
+ } else {
1575
+ console.error("Mission status update failed:", err?.message || err);
1576
+ }
1577
+ process.exit(1);
1578
+ }
1579
+
1580
+ const output = {
1581
+ project_id: data.project_id || null,
1582
+ requested_count: data.requested_count || missionIds.length,
1583
+ matched_count: data.matched_count || 0,
1584
+ updated_count: data.updated_count || 0,
1585
+ unchanged_count: data.unchanged_count || 0,
1586
+ rejected_count: data.rejected_count || 0,
1587
+ new_status: data.new_status || newStatus,
1588
+ missions: Array.isArray(data.missions) ? data.missions : [],
1589
+ };
1590
+
1591
+ if (args.json) {
1592
+ console.log(JSON.stringify(output, null, 2));
1593
+ return;
1594
+ }
1595
+
1596
+ if (output.project_id) console.log(`Project ID: ${output.project_id}`);
1597
+ console.log(`Target Status: ${output.new_status}`);
1598
+ console.log(`Requested: ${output.requested_count}`);
1599
+ console.log(`Matched: ${output.matched_count}`);
1600
+ console.log(`Updated: ${output.updated_count}`);
1601
+ console.log(`Unchanged: ${output.unchanged_count}`);
1602
+ console.log(`Rejected: ${output.rejected_count}`);
1603
+ for (const mission of output.missions) {
1604
+ const missionId = String(mission?.mission_id || "").trim() || "(unknown)";
1605
+ const status = String(mission?.status || "").trim() || "unknown";
1606
+ const detailParts = [];
1607
+ if (mission?.current_status) detailParts.push(`current=${mission.current_status}`);
1608
+ if (mission?.new_status) detailParts.push(`target=${mission.new_status}`);
1609
+ console.log(`- ${missionId}: ${status}${detailParts.length ? ` (${detailParts.join(", ")})` : ""}`);
1610
+ }
1611
+ }
1612
+
1459
1613
  function formatTargetContacts(contacts) {
1460
1614
  const items = Array.isArray(contacts) ? contacts : [];
1461
1615
  const formatted = items
@@ -2298,6 +2452,24 @@ function shouldSyncSuggestionsAfterMutation(args) {
2298
2452
  return !rawArgs.includes("--no-sync");
2299
2453
  }
2300
2454
 
2455
+ function resolveBooleanFlag(args, dashedName, defaultValue) {
2456
+ const rawArgs = Array.isArray(args?.__raw) ? args.__raw : [];
2457
+ const enabledToken = `--${dashedName}`;
2458
+ const disabledToken = `--no-${dashedName}`;
2459
+
2460
+ for (const token of rawArgs) {
2461
+ if (token === disabledToken) return false;
2462
+ if (token === enabledToken) return true;
2463
+ if (token.startsWith(`${enabledToken}=`)) {
2464
+ const rawValue = token.slice(enabledToken.length + 1).trim().toLowerCase();
2465
+ if (["1", "true", "yes", "on"].includes(rawValue)) return true;
2466
+ if (["0", "false", "no", "off"].includes(rawValue)) return false;
2467
+ }
2468
+ }
2469
+
2470
+ return defaultValue;
2471
+ }
2472
+
2301
2473
  function readMissionOpsWorkspace({ wrapperRoot, outputDir }) {
2302
2474
  const { targetRoot, dataRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
2303
2475
  const missionOpsPath = path.join(dataRoot, "mission-ops.yml");
@@ -3202,11 +3374,7 @@ async function runFeedbackSync(args) {
3202
3374
 
3203
3375
  const timeoutMs = resolveTimeoutMs(args);
3204
3376
  const apiBase = resolveApiBase(args);
3205
- const includePrdText = args["with-prd-text"] !== undefined
3206
- ? Boolean(args["with-prd-text"])
3207
- : args.withPrdText !== undefined
3208
- ? Boolean(args.withPrdText)
3209
- : true;
3377
+ const includePrdText = resolveBooleanFlag(args, "with-prd-text", true);
3210
3378
  const filters = {
3211
3379
  status: firstNonEmptyString(args.status) || "Pending",
3212
3380
  source: firstNonEmptyString(args.source) || "",
@@ -3980,6 +4148,10 @@ async function main() {
3980
4148
  console.error(REMOVED_COMMAND_MESSAGES[command]);
3981
4149
  process.exit(1);
3982
4150
  }
4151
+ if (command === "mission") {
4152
+ console.error("Unknown mission command. Use `myte mission status --mission-ids \"M001,M002\" --status todo|in_progress|done`.");
4153
+ process.exit(1);
4154
+ }
3983
4155
  const args = parseArgs(rest);
3984
4156
  if (args.help || command === "help") {
3985
4157
  printHelp();
@@ -4006,6 +4178,11 @@ async function main() {
4006
4178
  return;
4007
4179
  }
4008
4180
 
4181
+ if (command === "mission-status") {
4182
+ await runMissionStatus(args);
4183
+ return;
4184
+ }
4185
+
4009
4186
  if (command === "sync-qaqc" || command === "qaqc-sync") {
4010
4187
  await runSyncQaqc(args);
4011
4188
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mytegroupinc/myte-core",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Myte CLI core implementation (Project Assistant + Myte AI gateway).",
5
5
  "type": "commonjs",
6
6
  "main": "cli.js",