@mytegroupinc/myte-core 0.0.44 → 0.0.46

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 +64 -60
  2. package/cli.js +814 -943
  3. package/package.json +14 -14
package/cli.js CHANGED
@@ -208,10 +208,10 @@ function printHelp() {
208
208
  " myte feedback reviews [--status open|terminal|all] [--request-id <id>] [--json]",
209
209
  " myte feedback review --request-id <id> --action approve|reject|request_changes|cancel [--reason \"...\"] [--json]",
210
210
  " myte feedback review --request-ids \"<id1,id2>\" --action approve|reject|request_changes|cancel [--reason \"...\"] [--json]",
211
- " myte feedback move --feedback-id <id> --to-state in_progress --from-state todo --reason \"...\" [--json]",
212
- " myte feedback move --feedback-ids \"<id1,id2>\" --to-state in_progress --reason \"...\" [--json]",
213
- " myte feedback comment --feedback-id <id> --body \"...\" [--body-file ./comment.md] [--json]",
214
- " myte feedback undo --feedback-id <id> --event-id <id> --reason \"...\" [--json]",
211
+ " myte feedback move --feedback-id <id> --to-state in_progress --from-state todo --reason \"...\" [--json]",
212
+ " myte feedback move --feedback-ids \"<id1,id2>\" --to-state in_progress --reason \"...\" [--json]",
213
+ " myte feedback comment --feedback-id <id> --body \"...\" [--body-file ./comment.md] [--json]",
214
+ " myte feedback undo --feedback-id <id> --event-id <id> --reason \"...\" [--json]",
215
215
  " myte feedback prd-versions --feedback-id <id> [--json]",
216
216
  " myte feedback prd-diff --feedback-id <id> --version-id <id> [--compare-to <id>] [--json]",
217
217
  " myte feedback validate --file ./MyteCommandCenter/reviews/feedback/<id>-status.yml [--json]",
@@ -231,16 +231,10 @@ function printHelp() {
231
231
  " npx myte@latest query \"What changed in logging?\" --with-diff",
232
232
  " npx myte@latest ai \"Return a JSON checklist\" --json-response",
233
233
  "",
234
- "Auth:",
235
- " - Set MYTE_API_KEY in a workspace .env (or env var)",
236
- " - Set MYTEAI_API_KEY in a workspace .env (or env var) for `myte ai`",
237
- "",
238
- "Live write approval:",
239
- " - Live project-key mutations require --confirm-write --approval-artifact <path>",
240
- " - The artifact must be a local .md, .yml/.yaml, or .json file shown to and approved by the user before the command is rerun",
241
- " - Batch artifacts must enumerate every feedback id, mission id, suggestion id, request id, or PRD item",
242
- " - Read/sync/query/validate, --print-context, and --dry-run commands do not require write approval",
243
- "",
234
+ "Auth:",
235
+ " - Set MYTE_API_KEY in a workspace .env (or env var)",
236
+ " - Set MYTEAI_API_KEY in a workspace .env (or env var) for `myte ai`",
237
+ "",
244
238
  "bootstrap contract:",
245
239
  " - Run from any workspace where you want local MyteCommandCenter data written",
246
240
  " - Writes MyteCommandCenter/data/project.yml plus phases, epics, stories, and missions locally",
@@ -319,7 +313,7 @@ function printHelp() {
319
313
  " - Archived feedback is intentionally excluded from normal sync; returning it to active work is web UI only",
320
314
  " - Writes project feedback metadata and conversation turns into MyteCommandCenter/data/feedback.yml",
321
315
  " - Stores full PRD context in MyteCommandCenter/PRD/feedback-sync/*.md and points to those files from feedback.yml",
322
- " - Reads existing feedback comments and can create text-only feedback-specific comments through the project-key API",
316
+ " - Reads existing feedback comments and can create text-only feedback-specific comments through the project-key API",
323
317
  "",
324
318
  "feedback review contract:",
325
319
  " - Draft commands write review artifacts under MyteCommandCenter/reviews/feedback/*.yml for local IDE diff review",
@@ -328,9 +322,9 @@ function printHelp() {
328
322
  " - reviews/review expose owner/delegate approval, rejection, request-changes, and cancel decisions",
329
323
  " - review with --request-ids sends one batch review request; it does not loop single-review calls",
330
324
  " - live lifecycle moves return pending_feedback_review while an active linked review request exists",
331
- " - move/undo operate safe board states directly; governed states remain backend-authorized",
332
- " - move with --feedback-ids sends one batch board move request; it supports active-state moves and authorized archive",
333
- " - comment creates text-only feedback-specific comments; attachments remain web UI only for this endpoint",
325
+ " - move/undo operate safe board states directly; governed states remain backend-authorized",
326
+ " - move with --feedback-ids sends one batch board move request; it supports active-state moves and authorized archive",
327
+ " - comment creates text-only feedback-specific comments; attachments remain web UI only for this endpoint",
334
328
  " - project-key API/CLI cannot unarchive archived Feedback; use the web archived Feedback view",
335
329
  " - get/history read current feedback snapshots and audited event history through the project-key API",
336
330
  " - prd-versions/prd-diff expose retained PRD versions and backend-generated text diffs",
@@ -346,9 +340,9 @@ function printHelp() {
346
340
  " - revise: resubmit the original submitter's request after request_changes",
347
341
  " - reviews: list open/terminal/all requests or inspect one request",
348
342
  " - review: approve, reject, request_changes, or cancel; backend restricts this to owner/delegate capability",
349
- " - move: direct audited board movement for allowed states; governed transitions remain backend-authorized",
350
- " - comment: create a text-only comment on one Feedback item",
351
- " - undo: reverse an audited board event when conflict checks allow it",
343
+ " - move: direct audited board movement for allowed states; governed transitions remain backend-authorized",
344
+ " - comment: create a text-only comment on one Feedback item",
345
+ " - undo: reverse an audited board event when conflict checks allow it",
352
346
  " - prd-versions: list retained PRD baselines/revisions for a feedback item",
353
347
  " - prd-diff: fetch backend-generated text diff between PRD versions",
354
348
  " - history: list board/refinement events for audit review",
@@ -369,11 +363,11 @@ function printHelp() {
369
363
  " --stdin Read supported command content from stdin instead of inline text or a file path",
370
364
  " --title <text> Override PRD title for raw markdown uploads",
371
365
  " --description <text> Set feedback description/card summary for raw markdown uploads",
372
- " --content <text> Team update content for update-team",
373
- " --body <text> Feedback comment body for feedback comment",
374
- " --subject <text> Subject for update-owner or update-client",
366
+ " --content <text> Team update content for update-team",
367
+ " --body <text> Feedback comment body for feedback comment",
368
+ " --subject <text> Subject for update-owner or update-client",
375
369
  " --body-markdown <md> Markdown body for update-owner or update-client",
376
- " --body-file <path> Read feedback comment, update-owner, or update-client markdown body from a file",
370
+ " --body-file <path> Read feedback comment, update-owner, or update-client markdown body from a file",
377
371
  " --target-contact-id Add one client contact ObjectId (repeatable)",
378
372
  " --target-contact-ids Comma-separated client contact ObjectIds",
379
373
  " --feedback-id <id> Feedback ObjectId for feedback review commands",
@@ -401,10 +395,8 @@ function printHelp() {
401
395
  " --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
402
396
  " --wait Poll batch status until terminal completion for run-qaqc",
403
397
  " --sync After run-qaqc completes, refresh local QAQC file",
404
- " --force Allow run-qaqc to bypass stale-state protection when supported",
405
- " --confirm-write Confirm the user explicitly approved a live Myte project-key mutation",
406
- " --approval-artifact Local .md/.yml/.json approval artifact shown to and approved by the user",
407
- " --no-sync Skip automatic post-mutation sync for suggestions, missions, and feedback mutations",
398
+ " --force Allow run-qaqc to bypass stale-state protection when supported",
399
+ " --no-sync Skip automatic post-mutation sync for suggestions, missions, and feedback mutations",
408
400
  " --print-context Print JSON payload and exit (no query call)",
409
401
  " --no-fetch Don't git fetch origin main/master before diff",
410
402
  "",
@@ -429,10 +421,10 @@ function printHelp() {
429
421
  " myte feedback status --feedback-id 507f1f77bcf86cd799439011 --status in_review --reason \"Ready for owner review\"",
430
422
  " myte feedback submit --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-edit.yml --json",
431
423
  " myte feedback reviews --status open --json",
432
- " myte feedback review --request-id 507f1f77bcf86cd799439022 --action approve --reason \"Looks correct\" --json",
433
- " myte feedback move --feedback-id 507f1f77bcf86cd799439011 --to-state in_progress --reason \"Started\" --json",
434
- " myte feedback comment --feedback-id 507f1f77bcf86cd799439011 --body \"Implementation notes are ready for review.\" --json",
435
- " myte feedback validate --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-status.yml --json",
424
+ " myte feedback review --request-id 507f1f77bcf86cd799439022 --action approve --reason \"Looks correct\" --json",
425
+ " myte feedback move --feedback-id 507f1f77bcf86cd799439011 --to-state in_progress --reason \"Started\" --json",
426
+ " myte feedback comment --feedback-id 507f1f77bcf86cd799439011 --body \"Implementation notes are ready for review.\" --json",
427
+ " myte feedback validate --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-status.yml --json",
436
428
  " myte feedback apply --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-status.yml --json",
437
429
  " myte suggestions create --file ./suggestions/create.yml",
438
430
  " myte suggestions revise",
@@ -678,10 +670,10 @@ function normalizeApiBase(baseRaw) {
678
670
  return base.endsWith("/api") ? base : `${base}/api`;
679
671
  }
680
672
 
681
- async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
682
- const controller = typeof AbortController !== "undefined" ? new AbortController() : undefined;
683
- const timeoutId =
684
- controller && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
673
+ async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
674
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : undefined;
675
+ const timeoutId =
676
+ controller && timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
685
677
  try {
686
678
  const resp = await fetchFn(url, { ...options, signal: controller?.signal });
687
679
  const text = await resp.text();
@@ -697,171 +689,13 @@ async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
697
689
  }
698
690
  return { resp, body };
699
691
  } finally {
700
- if (timeoutId) clearTimeout(timeoutId);
701
- }
702
- }
703
-
704
- const WRITE_APPROVAL_TRANSPORT_KEYS = new Set(["write_approval", "client_session_id"]);
705
-
706
- function canonicalizeWritePayload(value) {
707
- if (Array.isArray(value)) {
708
- return value.map((item) => canonicalizeWritePayload(item));
709
- }
710
- if (value && typeof value === "object") {
711
- return Object.fromEntries(
712
- Object.keys(value)
713
- .filter((key) => !WRITE_APPROVAL_TRANSPORT_KEYS.has(String(key)))
714
- .sort()
715
- .map((key) => [key, canonicalizeWritePayload(value[key])])
716
- );
717
- }
718
- return value;
719
- }
720
-
721
- function projectApiWritePayloadHash(payload) {
722
- return createHash("sha256")
723
- .update(stableJsonStringify(canonicalizeWritePayload(payload || {})))
724
- .digest("hex");
725
- }
726
-
727
- function isConfirmWrite(args) {
728
- return Boolean(args["confirm-write"] || args.confirmWrite || args.confirm_write);
729
- }
730
-
731
- function resolveWriteApprovalArtifactPath(args) {
732
- const explicit = firstNonEmptyString(args["approval-artifact"], args.approvalArtifact, args.approval_artifact);
733
- if (!explicit) return "";
734
- const resolved = path.resolve(process.cwd(), explicit);
735
- if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
736
- console.error(`Approval artifact not found: ${resolved}`);
737
- process.exit(1);
738
- }
739
- return resolved;
740
- }
741
-
742
- function inferApprovalArtifactKind(filePath, fallbackKind) {
743
- const ext = path.extname(String(filePath || "")).toLowerCase();
744
- if (ext === ".md" || ext === ".markdown") return "markdown";
745
- if (ext === ".yml" || ext === ".yaml") return "yaml";
746
- if (ext === ".json") return "json";
747
- return fallbackKind || "artifact";
748
- }
749
-
750
- function normalizeApprovalTargets(targets) {
751
- return toStringArray(targets).map((item) => item.trim()).filter(Boolean);
752
- }
753
-
754
- function writePayloadItems(payload) {
755
- if (Array.isArray(payload?.items)) return payload.items;
756
- if (Array.isArray(payload)) return payload;
757
- return payload && typeof payload === "object" ? [payload] : [];
758
- }
759
-
760
- function suggestionApprovalTargets(payload) {
761
- return writePayloadItems(payload).map((item, index) => {
762
- if (!item || typeof item !== "object") return `index:${index}`;
763
- return firstNonEmptyString(
764
- item.suggestion_id,
765
- item.id,
766
- item._id,
767
- item.mission_id,
768
- item.target_mission_id,
769
- item.title ? `${item.change_type || "suggestion"}:${item.title}` : ""
770
- ) || `index:${index}`;
771
- });
772
- }
773
-
774
- function prdApprovalTargets(payload) {
775
- return writePayloadItems(payload).map((item, index) => {
776
- if (!item || typeof item !== "object") return `index:${index}`;
777
- return firstNonEmptyString(item.client_ref, item.title) || `index:${index}`;
778
- });
779
- }
780
-
781
- function requireWriteApproval(args, payload, { operation, artifactKind, targets = [], batchCount = null } = {}) {
782
- const payloadSha256 = projectApiWritePayloadHash(payload || {});
783
- const targetValues = normalizeApprovalTargets(targets);
784
- const expectedBatchCount = batchCount !== null && batchCount !== undefined
785
- ? Number(batchCount)
786
- : targetValues.length > 1
787
- ? targetValues.length
788
- : null;
789
-
790
- if (!isConfirmWrite(args)) {
791
- console.error("Refusing live Myte write without explicit human approval.");
792
- console.error(`Operation: ${operation || "unknown"}`);
793
- console.error(`Payload SHA256: ${payloadSha256}`);
794
- if (targetValues.length) console.error(`Targets: ${targetValues.join(", ")}`);
795
- if (expectedBatchCount !== null) console.error(`Batch count: ${expectedBatchCount}`);
796
- console.error("Create a local .md/.yml/.json approval artifact that lists the exact operation, payload summary, and every target.");
797
- console.error("Show that artifact to the user and ask for explicit approval.");
798
- console.error("Then rerun with --confirm-write --approval-artifact <path>.");
799
- process.exit(1);
800
- }
801
-
802
- const artifactPath = resolveWriteApprovalArtifactPath(args);
803
- if (!artifactPath) {
804
- console.error("Missing --approval-artifact <path> for approved Myte write.");
805
- console.error(`Payload SHA256: ${payloadSha256}`);
806
- console.error("The artifact must be a local .md/.yml/.json file shown to and explicitly approved by the user.");
807
- process.exit(1);
808
- }
809
-
810
- return {
811
- approved: true,
812
- source: "explicit_user_approval",
813
- artifact_path: artifactPath,
814
- artifact_kind: inferApprovalArtifactKind(artifactPath, artifactKind),
815
- payload_sha256: payloadSha256,
816
- targets: targetValues,
817
- batch_count: expectedBatchCount,
818
- };
819
- }
820
-
821
- function writeApprovalHeaders(writeApproval) {
822
- if (!writeApproval) return {};
823
- const headers = {
824
- "X-Myte-Write-Approval": "explicit_user_approval",
825
- "X-Myte-Approval-Artifact": String(writeApproval.artifact_path || ""),
826
- "X-Myte-Approval-Artifact-Kind": String(writeApproval.artifact_kind || ""),
827
- "X-Myte-Approval-Payload-SHA256": String(writeApproval.payload_sha256 || ""),
828
- };
829
- if (writeApproval.batch_count !== null && writeApproval.batch_count !== undefined) {
830
- headers["X-Myte-Approval-Batch-Count"] = String(writeApproval.batch_count);
831
- }
832
- if (Array.isArray(writeApproval.targets) && writeApproval.targets.length) {
833
- headers["X-Myte-Approval-Targets"] = writeApproval.targets.join(",");
834
- }
835
- return headers;
836
- }
837
-
838
- function formatProjectApiErrorMessage(body, fallback) {
839
- const message = body?.message || fallback;
840
- const data = body?.data || {};
841
- const code = String(data?.code || "");
842
- if (!code.startsWith("myte_write_approval")) {
843
- return message;
844
- }
845
- const lines = [
846
- message,
847
- `Approval error: ${code}`,
848
- ];
849
- if (data.operation) lines.push(`Operation: ${data.operation}`);
850
- if (data.payload_sha256) lines.push(`Payload SHA256: ${data.payload_sha256}`);
851
- if (Array.isArray(data.targets) && data.targets.length) lines.push(`Targets: ${data.targets.join(", ")}`);
852
- if (data.batch_count !== undefined && data.batch_count !== null) lines.push(`Batch count: ${data.batch_count}`);
853
- if (Array.isArray(data.required_agent_steps) && data.required_agent_steps.length) {
854
- lines.push("Required agent steps:");
855
- for (const step of data.required_agent_steps) {
856
- lines.push(`- ${step}`);
857
- }
858
- }
859
- return lines.join("\n");
860
- }
861
-
862
- function summarizeDiffDiagnosticsForContext(diagnostics) {
863
- if (!diagnostics) return null;
864
- const repos = Array.isArray(diagnostics.repo_summaries)
692
+ if (timeoutId) clearTimeout(timeoutId);
693
+ }
694
+ }
695
+
696
+ function summarizeDiffDiagnosticsForContext(diagnostics) {
697
+ if (!diagnostics) return null;
698
+ const repos = Array.isArray(diagnostics.repo_summaries)
865
699
  ? diagnostics.repo_summaries
866
700
  : [];
867
701
  return {
@@ -1922,16 +1756,15 @@ async function fetchFeedbackHistory({ apiBase, key, timeoutMs, feedbackId }) {
1922
1756
  return body.data || {};
1923
1757
  }
1924
1758
 
1925
- async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mode, payload, idempotencyKey, clientSessionId, writeApproval }) {
1759
+ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mode, payload, idempotencyKey, clientSessionId }) {
1926
1760
  const fetchFn = await getFetch();
1927
1761
  const action = String(mode || "").trim();
1928
1762
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/refinement/${action}`;
1929
- const headers = {
1930
- "Content-Type": "application/json",
1931
- Authorization: `Bearer ${key}`,
1932
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1933
- ...writeApprovalHeaders(writeApproval),
1934
- };
1763
+ const headers = {
1764
+ "Content-Type": "application/json",
1765
+ Authorization: `Bearer ${key}`,
1766
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1767
+ };
1935
1768
  if (idempotencyKey) {
1936
1769
  headers["X-Idempotency-Key"] = String(idempotencyKey).trim();
1937
1770
  }
@@ -1947,7 +1780,7 @@ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mod
1947
1780
  );
1948
1781
 
1949
1782
  if (!resp.ok || body.status !== "success") {
1950
- const msg = formatProjectApiErrorMessage(body, `Feedback refinement ${action} failed (${resp.status})`);
1783
+ const msg = body?.message || `Feedback refinement ${action} failed (${resp.status})`;
1951
1784
  const err = new Error(msg);
1952
1785
  err.status = resp.status;
1953
1786
  err.data = body?.data;
@@ -2026,7 +1859,7 @@ async function fetchFeedbackEvents({ apiBase, key, timeoutMs, feedbackId, limit
2026
1859
  return body.data || {};
2027
1860
  }
2028
1861
 
2029
- async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId, writeApproval }) {
1862
+ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId }) {
2030
1863
  const fetchFn = await getFetch();
2031
1864
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/refinement/requests`;
2032
1865
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2036,18 +1869,17 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
2036
1869
  method: "POST",
2037
1870
  headers: {
2038
1871
  "Content-Type": "application/json",
2039
- Authorization: `Bearer ${key}`,
2040
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2041
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2042
- ...writeApprovalHeaders(writeApproval),
2043
- },
1872
+ Authorization: `Bearer ${key}`,
1873
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1874
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1875
+ },
2044
1876
  body: JSON.stringify(payload || {}),
2045
1877
  },
2046
1878
  timeoutMs
2047
1879
  );
2048
1880
 
2049
1881
  if (!resp.ok || body.status !== "success") {
2050
- const msg = formatProjectApiErrorMessage(body, `Feedback review submission failed (${resp.status})`);
1882
+ const msg = body?.message || `Feedback review submission failed (${resp.status})`;
2051
1883
  const err = new Error(msg);
2052
1884
  err.status = resp.status;
2053
1885
  err.data = body?.data;
@@ -2056,7 +1888,7 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
2056
1888
  return body.data || {};
2057
1889
  }
2058
1890
 
2059
- async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requestId, endpointAction, payload, idempotencyKey, clientSessionId, writeApproval }) {
1891
+ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requestId, endpointAction, payload, idempotencyKey, clientSessionId }) {
2060
1892
  const fetchFn = await getFetch();
2061
1893
  const url = `${apiBase}/project-assistant/feedback-review-requests/${encodeURIComponent(String(requestId || ""))}/${endpointAction}`;
2062
1894
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2066,18 +1898,17 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
2066
1898
  method: "POST",
2067
1899
  headers: {
2068
1900
  "Content-Type": "application/json",
2069
- Authorization: `Bearer ${key}`,
2070
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2071
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2072
- ...writeApprovalHeaders(writeApproval),
2073
- },
1901
+ Authorization: `Bearer ${key}`,
1902
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1903
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1904
+ },
2074
1905
  body: JSON.stringify(payload || {}),
2075
1906
  },
2076
1907
  timeoutMs
2077
1908
  );
2078
1909
 
2079
1910
  if (!resp.ok || body.status !== "success") {
2080
- const msg = formatProjectApiErrorMessage(body, `Feedback review ${endpointAction} failed (${resp.status})`);
1911
+ const msg = body?.message || `Feedback review ${endpointAction} failed (${resp.status})`;
2081
1912
  const err = new Error(msg);
2082
1913
  err.status = resp.status;
2083
1914
  err.data = body?.data;
@@ -2086,7 +1917,7 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
2086
1917
  return body.data || {};
2087
1918
  }
2088
1919
 
2089
- async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
1920
+ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2090
1921
  const fetchFn = await getFetch();
2091
1922
  const url = `${apiBase}/project-assistant/feedback-review-requests/batch-review`;
2092
1923
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2096,18 +1927,17 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
2096
1927
  method: "POST",
2097
1928
  headers: {
2098
1929
  "Content-Type": "application/json",
2099
- Authorization: `Bearer ${key}`,
2100
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2101
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2102
- ...writeApprovalHeaders(writeApproval),
2103
- },
1930
+ Authorization: `Bearer ${key}`,
1931
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1932
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1933
+ },
2104
1934
  body: JSON.stringify(payload || {}),
2105
1935
  },
2106
1936
  timeoutMs
2107
1937
  );
2108
1938
 
2109
1939
  if (!resp.ok || body.status !== "success") {
2110
- const msg = formatProjectApiErrorMessage(body, `Feedback batch review failed (${resp.status})`);
1940
+ const msg = body?.message || `Feedback batch review failed (${resp.status})`;
2111
1941
  const err = new Error(msg);
2112
1942
  err.status = resp.status;
2113
1943
  err.data = body?.data;
@@ -2116,7 +1946,7 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
2116
1946
  return body.data || {};
2117
1947
  }
2118
1948
 
2119
- async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
1949
+ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId, endpoint, payload, idempotencyKey, clientSessionId }) {
2120
1950
  const fetchFn = await getFetch();
2121
1951
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/${endpoint}`;
2122
1952
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2126,18 +1956,17 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
2126
1956
  method: "POST",
2127
1957
  headers: {
2128
1958
  "Content-Type": "application/json",
2129
- Authorization: `Bearer ${key}`,
2130
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2131
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2132
- ...writeApprovalHeaders(writeApproval),
2133
- },
1959
+ Authorization: `Bearer ${key}`,
1960
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1961
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1962
+ },
2134
1963
  body: JSON.stringify(payload || {}),
2135
1964
  },
2136
1965
  timeoutMs
2137
1966
  );
2138
1967
 
2139
1968
  if (!resp.ok || body.status !== "success") {
2140
- const msg = formatProjectApiErrorMessage(body, `Feedback board mutation failed (${resp.status})`);
1969
+ const msg = body?.message || `Feedback board mutation failed (${resp.status})`;
2141
1970
  const err = new Error(msg);
2142
1971
  err.status = resp.status;
2143
1972
  err.data = body?.data;
@@ -2146,7 +1975,7 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
2146
1975
  return body.data || {};
2147
1976
  }
2148
1977
 
2149
- async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
1978
+ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2150
1979
  const fetchFn = await getFetch();
2151
1980
  const url = `${apiBase}/project-assistant/feedback/batch-board-move`;
2152
1981
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2156,57 +1985,55 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
2156
1985
  method: "POST",
2157
1986
  headers: {
2158
1987
  "Content-Type": "application/json",
2159
- Authorization: `Bearer ${key}`,
2160
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2161
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2162
- ...writeApprovalHeaders(writeApproval),
2163
- },
1988
+ Authorization: `Bearer ${key}`,
1989
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1990
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1991
+ },
2164
1992
  body: JSON.stringify(payload || {}),
2165
1993
  },
2166
1994
  timeoutMs
2167
1995
  );
2168
1996
 
2169
1997
  if (!resp.ok || body.status !== "success") {
2170
- const msg = formatProjectApiErrorMessage(body, `Feedback batch board mutation failed (${resp.status})`);
1998
+ const msg = body?.message || `Feedback batch board mutation failed (${resp.status})`;
2171
1999
  const err = new Error(msg);
2172
2000
  err.status = resp.status;
2173
2001
  err.data = body?.data;
2174
2002
  throw err;
2175
2003
  }
2176
- return body.data || {};
2177
- }
2178
-
2179
- async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId, writeApproval }) {
2180
- const fetchFn = await getFetch();
2181
- const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/comments`;
2182
- const { resp, body } = await fetchJsonWithTimeout(
2183
- fetchFn,
2184
- url,
2185
- {
2186
- method: "POST",
2187
- headers: {
2188
- "Content-Type": "application/json",
2189
- Authorization: `Bearer ${key}`,
2190
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2191
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2192
- ...writeApprovalHeaders(writeApproval),
2193
- },
2194
- body: JSON.stringify(payload || {}),
2195
- },
2196
- timeoutMs
2197
- );
2198
-
2199
- if (!resp.ok || body.status !== "success") {
2200
- const msg = formatProjectApiErrorMessage(body, `Feedback comment failed (${resp.status})`);
2201
- const err = new Error(msg);
2202
- err.status = resp.status;
2203
- err.data = body?.data;
2204
- throw err;
2205
- }
2206
- return body.data || {};
2207
- }
2208
-
2209
- async function fetchFeedbackPrdVersions({ apiBase, key, timeoutMs, feedbackId }) {
2004
+ return body.data || {};
2005
+ }
2006
+
2007
+ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId }) {
2008
+ const fetchFn = await getFetch();
2009
+ const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/comments`;
2010
+ const { resp, body } = await fetchJsonWithTimeout(
2011
+ fetchFn,
2012
+ url,
2013
+ {
2014
+ method: "POST",
2015
+ headers: {
2016
+ "Content-Type": "application/json",
2017
+ Authorization: `Bearer ${key}`,
2018
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2019
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2020
+ },
2021
+ body: JSON.stringify(payload || {}),
2022
+ },
2023
+ timeoutMs
2024
+ );
2025
+
2026
+ if (!resp.ok || body.status !== "success") {
2027
+ const msg = body?.message || `Feedback comment failed (${resp.status})`;
2028
+ const err = new Error(msg);
2029
+ err.status = resp.status;
2030
+ err.data = body?.data;
2031
+ throw err;
2032
+ }
2033
+ return body.data || {};
2034
+ }
2035
+
2036
+ async function fetchFeedbackPrdVersions({ apiBase, key, timeoutMs, feedbackId }) {
2210
2037
  const fetchFn = await getFetch();
2211
2038
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/prd/versions`;
2212
2039
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2303,7 +2130,7 @@ function resolveProjectMutationIdempotencyKey({ args, operation, payload }) {
2303
2130
  return createHash("sha256").update(fingerprint).digest("hex");
2304
2131
  }
2305
2132
 
2306
- async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
2133
+ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId }) {
2307
2134
  const fetchFn = await getFetch();
2308
2135
  const url = `${apiBase}${endpoint}`;
2309
2136
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2313,27 +2140,25 @@ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payl
2313
2140
  method: "POST",
2314
2141
  headers: {
2315
2142
  "Content-Type": "application/json",
2316
- Authorization: `Bearer ${key}`,
2317
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2318
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2319
- ...writeApprovalHeaders(writeApproval),
2320
- },
2143
+ Authorization: `Bearer ${key}`,
2144
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2145
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2146
+ },
2321
2147
  body: JSON.stringify(payload),
2322
2148
  },
2323
2149
  timeoutMs
2324
2150
  );
2325
2151
 
2326
2152
  if (!resp.ok || body.status !== "success") {
2327
- const msg = formatProjectApiErrorMessage(body, `Mission suggestions request failed (${resp.status})`);
2328
- const err = new Error(msg);
2329
- err.status = resp.status;
2330
- err.data = body?.data;
2153
+ const msg = body?.message || `Mission suggestions request failed (${resp.status})`;
2154
+ const err = new Error(msg);
2155
+ err.status = resp.status;
2331
2156
  throw err;
2332
2157
  }
2333
2158
  return body.data || {};
2334
2159
  }
2335
2160
 
2336
- async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
2161
+ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2337
2162
  const fetchFn = await getFetch();
2338
2163
  const url = `${apiBase}/project-assistant/run-qaqc`;
2339
2164
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2343,27 +2168,25 @@ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotenc
2343
2168
  method: "POST",
2344
2169
  headers: {
2345
2170
  "Content-Type": "application/json",
2346
- Authorization: `Bearer ${key}`,
2347
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2348
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2349
- ...writeApprovalHeaders(writeApproval),
2350
- },
2171
+ Authorization: `Bearer ${key}`,
2172
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2173
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2174
+ },
2351
2175
  body: JSON.stringify(payload),
2352
2176
  },
2353
2177
  timeoutMs
2354
2178
  );
2355
2179
 
2356
2180
  if (!resp.ok || body.status !== "success") {
2357
- const msg = formatProjectApiErrorMessage(body, `Run QAQC request failed (${resp.status})`);
2358
- const err = new Error(msg);
2359
- err.status = resp.status;
2360
- err.data = body?.data;
2361
- throw err;
2362
- }
2181
+ const msg = body?.message || `Run QAQC request failed (${resp.status})`;
2182
+ const err = new Error(msg);
2183
+ err.status = resp.status;
2184
+ throw err;
2185
+ }
2363
2186
  return body.data || {};
2364
2187
  }
2365
2188
 
2366
- async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
2189
+ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2367
2190
  const fetchFn = await getFetch();
2368
2191
  const url = `${apiBase}/project-assistant/mission-status-update`;
2369
2192
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2373,27 +2196,25 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
2373
2196
  method: "POST",
2374
2197
  headers: {
2375
2198
  "Content-Type": "application/json",
2376
- Authorization: `Bearer ${key}`,
2377
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2378
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2379
- ...writeApprovalHeaders(writeApproval),
2380
- },
2199
+ Authorization: `Bearer ${key}`,
2200
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2201
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2202
+ },
2381
2203
  body: JSON.stringify(payload),
2382
2204
  },
2383
2205
  timeoutMs
2384
2206
  );
2385
2207
 
2386
2208
  if (!resp.ok || body.status !== "success") {
2387
- const msg = formatProjectApiErrorMessage(body, `Mission status update request failed (${resp.status})`);
2388
- const err = new Error(msg);
2389
- err.status = resp.status;
2390
- err.data = body?.data;
2391
- throw err;
2392
- }
2209
+ const msg = body?.message || `Mission status update request failed (${resp.status})`;
2210
+ const err = new Error(msg);
2211
+ err.status = resp.status;
2212
+ throw err;
2213
+ }
2393
2214
  return body.data || {};
2394
2215
  }
2395
2216
 
2396
- async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
2217
+ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId }) {
2397
2218
  const fetchFn = await getFetch();
2398
2219
  const url = `${apiBase}${endpoint}`;
2399
2220
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2403,21 +2224,19 @@ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, p
2403
2224
  method: "POST",
2404
2225
  headers: {
2405
2226
  "Content-Type": "application/json",
2406
- Authorization: `Bearer ${key}`,
2407
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2408
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2409
- ...writeApprovalHeaders(writeApproval),
2410
- },
2227
+ Authorization: `Bearer ${key}`,
2228
+ "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2229
+ ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2230
+ },
2411
2231
  body: JSON.stringify(payload),
2412
2232
  },
2413
2233
  timeoutMs
2414
2234
  );
2415
2235
 
2416
2236
  if (!resp.ok || body.status !== "success") {
2417
- const msg = formatProjectApiErrorMessage(body, `Mission archive request failed (${resp.status})`);
2418
- const err = new Error(msg);
2419
- err.status = resp.status;
2420
- err.data = body?.data;
2237
+ const msg = body?.message || `Mission archive request failed (${resp.status})`;
2238
+ const err = new Error(msg);
2239
+ err.status = resp.status;
2421
2240
  throw err;
2422
2241
  }
2423
2242
  return body.data || {};
@@ -2530,15 +2349,20 @@ async function runRunQaqc(args) {
2530
2349
  process.exit(1);
2531
2350
  }
2532
2351
 
2533
- const payload = {
2534
- mission_ids: missionIds,
2535
- force: Boolean(args.force),
2536
- };
2537
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2538
- if (clientSessionId) payload.client_session_id = clientSessionId;
2539
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
2540
- args,
2541
- operation: "run-qaqc",
2352
+ let payload = {
2353
+ mission_ids: missionIds,
2354
+ force: Boolean(args.force),
2355
+ };
2356
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2357
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2358
+ payload = withWriteApproval(args, payload, {
2359
+ artifactKind: "yaml",
2360
+ targets: missionIds,
2361
+ batchCount: missionIds.length > 1 ? missionIds.length : null,
2362
+ });
2363
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2364
+ args,
2365
+ operation: "run-qaqc",
2542
2366
  payload,
2543
2367
  });
2544
2368
 
@@ -2547,28 +2371,21 @@ async function runRunQaqc(args) {
2547
2371
  return;
2548
2372
  }
2549
2373
 
2550
- const timeoutMs = resolveTimeoutMs(args);
2551
- const apiBase = resolveApiBase(args);
2552
- const shouldWait = Boolean(args.wait || args.sync);
2553
- const shouldSync = Boolean(args.sync);
2554
- const writeApproval = requireWriteApproval(args, payload, {
2555
- operation: "run-qaqc",
2556
- artifactKind: "yaml",
2557
- targets: missionIds,
2558
- batchCount: missionIds.length,
2559
- });
2560
-
2561
- let data;
2562
- try {
2563
- data = await createRunQaqcBatch({
2564
- apiBase,
2374
+ const timeoutMs = resolveTimeoutMs(args);
2375
+ const apiBase = resolveApiBase(args);
2376
+ const shouldWait = Boolean(args.wait || args.sync);
2377
+ const shouldSync = Boolean(args.sync);
2378
+
2379
+ let data;
2380
+ try {
2381
+ data = await createRunQaqcBatch({
2382
+ apiBase,
2565
2383
  key,
2566
2384
  timeoutMs,
2567
- payload,
2568
- idempotencyKey,
2569
- clientSessionId,
2570
- writeApproval,
2571
- });
2385
+ payload,
2386
+ idempotencyKey,
2387
+ clientSessionId,
2388
+ });
2572
2389
  } catch (err) {
2573
2390
  if (err?.name === "AbortError") {
2574
2391
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2692,15 +2509,20 @@ async function runMissionStatus(args) {
2692
2509
  process.exit(1);
2693
2510
  }
2694
2511
 
2695
- const payload = {
2696
- mission_ids: missionIds,
2697
- new_status: newStatus,
2698
- };
2699
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2700
- if (clientSessionId) payload.client_session_id = clientSessionId;
2701
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
2702
- args,
2703
- operation: "mission-status-update",
2512
+ let payload = {
2513
+ mission_ids: missionIds,
2514
+ new_status: newStatus,
2515
+ };
2516
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2517
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2518
+ payload = withWriteApproval(args, payload, {
2519
+ artifactKind: "yaml",
2520
+ targets: missionIds,
2521
+ batchCount: missionIds.length > 1 ? missionIds.length : null,
2522
+ });
2523
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2524
+ args,
2525
+ operation: "mission-status-update",
2704
2526
  payload,
2705
2527
  });
2706
2528
 
@@ -2709,26 +2531,19 @@ async function runMissionStatus(args) {
2709
2531
  return;
2710
2532
  }
2711
2533
 
2712
- const timeoutMs = resolveTimeoutMs(args);
2713
- const apiBase = resolveApiBase(args);
2714
- const writeApproval = requireWriteApproval(args, payload, {
2715
- operation: "mission-status-update",
2716
- artifactKind: "yaml",
2717
- targets: missionIds,
2718
- batchCount: missionIds.length,
2719
- });
2720
-
2721
- let data;
2722
- try {
2723
- data = await createMissionStatusUpdate({
2724
- apiBase,
2534
+ const timeoutMs = resolveTimeoutMs(args);
2535
+ const apiBase = resolveApiBase(args);
2536
+
2537
+ let data;
2538
+ try {
2539
+ data = await createMissionStatusUpdate({
2540
+ apiBase,
2725
2541
  key,
2726
2542
  timeoutMs,
2727
- payload,
2728
- idempotencyKey,
2729
- clientSessionId,
2730
- writeApproval,
2731
- });
2543
+ payload,
2544
+ idempotencyKey,
2545
+ clientSessionId,
2546
+ });
2732
2547
  } catch (err) {
2733
2548
  if (err?.name === "AbortError") {
2734
2549
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2802,13 +2617,18 @@ async function runMissionArchiveCommand(args) {
2802
2617
  process.exit(1);
2803
2618
  }
2804
2619
 
2805
- const payload = {
2806
- mission_ids: missionIds,
2807
- };
2808
- const reason = firstNonEmptyString(args.reason);
2809
- if (reason) payload.reason = reason;
2810
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2811
- if (clientSessionId) payload.client_session_id = clientSessionId;
2620
+ let payload = {
2621
+ mission_ids: missionIds,
2622
+ };
2623
+ const reason = firstNonEmptyString(args.reason);
2624
+ if (reason) payload.reason = reason;
2625
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2626
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2627
+ payload = withWriteApproval(args, payload, {
2628
+ artifactKind: "yaml",
2629
+ targets: missionIds,
2630
+ batchCount: missionIds.length > 1 ? missionIds.length : null,
2631
+ });
2812
2632
 
2813
2633
  const operation = "mission-archive";
2814
2634
  const idempotencyKey = resolveProjectMutationIdempotencyKey({
@@ -2822,28 +2642,21 @@ async function runMissionArchiveCommand(args) {
2822
2642
  return;
2823
2643
  }
2824
2644
 
2825
- const timeoutMs = resolveTimeoutMs(args);
2826
- const apiBase = resolveApiBase(args);
2827
- const endpoint = "/project-assistant/mission-archive";
2828
- const writeApproval = requireWriteApproval(args, payload, {
2829
- operation: "mission-archive",
2830
- artifactKind: "yaml",
2831
- targets: missionIds,
2832
- batchCount: missionIds.length,
2833
- });
2834
-
2835
- let data;
2836
- try {
2837
- data = await createMissionArchiveUpdate({
2838
- apiBase,
2645
+ const timeoutMs = resolveTimeoutMs(args);
2646
+ const apiBase = resolveApiBase(args);
2647
+ const endpoint = "/project-assistant/mission-archive";
2648
+
2649
+ let data;
2650
+ try {
2651
+ data = await createMissionArchiveUpdate({
2652
+ apiBase,
2839
2653
  key,
2840
2654
  timeoutMs,
2841
2655
  endpoint,
2842
- payload,
2843
- idempotencyKey,
2844
- clientSessionId,
2845
- writeApproval,
2846
- });
2656
+ payload,
2657
+ idempotencyKey,
2658
+ clientSessionId,
2659
+ });
2847
2660
  } catch (err) {
2848
2661
  if (err?.name === "AbortError") {
2849
2662
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2924,9 +2737,9 @@ async function runUpdateTeam(args) {
2924
2737
  process.exit(1);
2925
2738
  }
2926
2739
 
2927
- const payload = {
2928
- content: await resolveTeamUpdateContent(args),
2929
- };
2740
+ let payload = {
2741
+ content: await resolveTeamUpdateContent(args),
2742
+ };
2930
2743
 
2931
2744
  if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
2932
2745
  console.log(JSON.stringify(payload, null, 2));
@@ -2934,31 +2747,30 @@ async function runUpdateTeam(args) {
2934
2747
  }
2935
2748
 
2936
2749
  const timeoutMs = resolveTimeoutMs(args);
2937
- const apiBase = resolveApiBase(args);
2938
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2939
- if (clientSessionId) payload.client_session_id = clientSessionId;
2940
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
2941
- args,
2942
- operation: "update_team",
2943
- payload,
2944
- });
2945
- const writeApproval = requireWriteApproval(args, payload, {
2946
- operation: "update_team",
2750
+ const apiBase = resolveApiBase(args);
2751
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2752
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2753
+ payload = withWriteApproval(args, payload, {
2947
2754
  artifactKind: "markdown",
2755
+ targets: ["project"],
2948
2756
  });
2949
-
2950
- let data;
2951
- try {
2952
- data = await postSuggestionsMutation({
2757
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2758
+ args,
2759
+ operation: "update_team",
2760
+ payload,
2761
+ });
2762
+
2763
+ let data;
2764
+ try {
2765
+ data = await postSuggestionsMutation({
2953
2766
  apiBase,
2954
2767
  key,
2955
2768
  payload,
2956
2769
  timeoutMs,
2957
- endpoint: "/project-assistant/project-comment",
2958
- idempotencyKey,
2959
- clientSessionId,
2960
- writeApproval,
2961
- });
2770
+ endpoint: "/project-assistant/project-comment",
2771
+ idempotencyKey,
2772
+ clientSessionId,
2773
+ });
2962
2774
  } catch (err) {
2963
2775
  if (err?.name === "AbortError") {
2964
2776
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2997,10 +2809,10 @@ async function runUpdateOwner(args) {
2997
2809
  process.exit(1);
2998
2810
  }
2999
2811
 
3000
- const payload = {
3001
- subject,
3002
- body_markdown: await resolveOwnerUpdateBody(args),
3003
- };
2812
+ let payload = {
2813
+ subject,
2814
+ body_markdown: await resolveOwnerUpdateBody(args),
2815
+ };
3004
2816
 
3005
2817
  if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
3006
2818
  console.log(JSON.stringify(payload, null, 2));
@@ -3008,31 +2820,30 @@ async function runUpdateOwner(args) {
3008
2820
  }
3009
2821
 
3010
2822
  const timeoutMs = resolveTimeoutMs(args);
3011
- const apiBase = resolveApiBase(args);
3012
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
3013
- if (clientSessionId) payload.client_session_id = clientSessionId;
3014
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
3015
- args,
3016
- operation: "update_owner",
3017
- payload,
3018
- });
3019
- const writeApproval = requireWriteApproval(args, payload, {
3020
- operation: "update_owner",
3021
- artifactKind: "markdown",
2823
+ const apiBase = resolveApiBase(args);
2824
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2825
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2826
+ payload = withWriteApproval(args, payload, {
2827
+ artifactKind: "markdown",
2828
+ targets: ["project"],
3022
2829
  });
3023
-
3024
- let data;
3025
- try {
2830
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2831
+ args,
2832
+ operation: "update_owner",
2833
+ payload,
2834
+ });
2835
+
2836
+ let data;
2837
+ try {
3026
2838
  data = await postSuggestionsMutation({
3027
2839
  apiBase,
3028
2840
  key,
3029
2841
  payload,
3030
2842
  timeoutMs,
3031
- endpoint: "/project-assistant/update-owner",
3032
- idempotencyKey,
3033
- clientSessionId,
3034
- writeApproval,
3035
- });
2843
+ endpoint: "/project-assistant/update-owner",
2844
+ idempotencyKey,
2845
+ clientSessionId,
2846
+ });
3036
2847
  } catch (err) {
3037
2848
  if (err?.name === "AbortError") {
3038
2849
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -3079,11 +2890,11 @@ async function runUpdateClient(args) {
3079
2890
  process.exit(1);
3080
2891
  }
3081
2892
 
3082
- const payload = {
3083
- subject,
3084
- body_markdown: await resolveClientUpdateBody(args),
3085
- target_contact_ids: resolveTargetContactIds(args),
3086
- };
2893
+ let payload = {
2894
+ subject,
2895
+ body_markdown: await resolveClientUpdateBody(args),
2896
+ target_contact_ids: resolveTargetContactIds(args),
2897
+ };
3087
2898
 
3088
2899
  if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
3089
2900
  console.log(JSON.stringify(payload, null, 2));
@@ -3091,31 +2902,30 @@ async function runUpdateClient(args) {
3091
2902
  }
3092
2903
 
3093
2904
  const timeoutMs = resolveTimeoutMs(args);
3094
- const apiBase = resolveApiBase(args);
3095
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
3096
- if (clientSessionId) payload.client_session_id = clientSessionId;
3097
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
3098
- args,
3099
- operation: "update_client",
3100
- payload,
3101
- });
3102
- const writeApproval = requireWriteApproval(args, payload, {
3103
- operation: "update_client",
2905
+ const apiBase = resolveApiBase(args);
2906
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2907
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2908
+ payload = withWriteApproval(args, payload, {
3104
2909
  artifactKind: "markdown",
2910
+ targets: ["project"],
3105
2911
  });
3106
-
3107
- let data;
3108
- try {
2912
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2913
+ args,
2914
+ operation: "update_client",
2915
+ payload,
2916
+ });
2917
+
2918
+ let data;
2919
+ try {
3109
2920
  data = await postSuggestionsMutation({
3110
2921
  apiBase,
3111
2922
  key,
3112
2923
  payload,
3113
2924
  timeoutMs,
3114
- endpoint: "/project-assistant/client-update-drafts",
3115
- idempotencyKey,
3116
- clientSessionId,
3117
- writeApproval,
3118
- });
2925
+ endpoint: "/project-assistant/client-update-drafts",
2926
+ idempotencyKey,
2927
+ clientSessionId,
2928
+ });
3119
2929
  } catch (err) {
3120
2930
  if (err?.name === "AbortError") {
3121
2931
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -3207,23 +3017,138 @@ function stringifyYaml(value) {
3207
3017
  return `${JSON.stringify(value, null, 2)}\n`;
3208
3018
  }
3209
3019
 
3210
- function stableJsonStringify(value) {
3211
- if (Array.isArray(value)) {
3212
- return `[${value.map((item) => stableJsonStringify(item)).join(",")}]`;
3213
- }
3020
+ function stableJsonStringify(value) {
3021
+ if (Array.isArray(value)) {
3022
+ return `[${value.map((item) => stableJsonStringify(item)).join(",")}]`;
3023
+ }
3214
3024
  if (value && typeof value === "object") {
3215
3025
  const entries = Object.keys(value)
3216
3026
  .sort()
3217
3027
  .map((key) => `${JSON.stringify(key)}:${stableJsonStringify(value[key])}`);
3218
3028
  return `{${entries.join(",")}}`;
3219
- }
3220
- return JSON.stringify(value);
3221
- }
3222
-
3223
- function parseYaml(text) {
3224
- const raw = String(text || "").trim();
3225
- if (!raw) return null;
3226
- if (raw.startsWith("{") || raw.startsWith("[")) {
3029
+ }
3030
+ return JSON.stringify(value);
3031
+ }
3032
+
3033
+ function pythonJsonStringify(value) {
3034
+ if (Array.isArray(value)) {
3035
+ return `[${value.map((item) => pythonJsonStringify(item)).join(", ")}]`;
3036
+ }
3037
+ if (value && typeof value === "object") {
3038
+ const entries = Object.keys(value)
3039
+ .filter((key) => value[key] !== undefined)
3040
+ .sort()
3041
+ .map((key) => `${jsonStringEnsureAscii(key)}: ${pythonJsonStringify(value[key])}`);
3042
+ return `{${entries.join(", ")}}`;
3043
+ }
3044
+ if (typeof value === "string") return jsonStringEnsureAscii(value);
3045
+ if (value === undefined) return "null";
3046
+ return JSON.stringify(value);
3047
+ }
3048
+
3049
+ function jsonStringEnsureAscii(value) {
3050
+ return JSON.stringify(String(value)).replace(/[^\x00-\x7F]/g, (char) => {
3051
+ const code = char.charCodeAt(0);
3052
+ return `\\u${code.toString(16).padStart(4, "0")}`;
3053
+ });
3054
+ }
3055
+
3056
+ function canonicalizeWritePayload(value) {
3057
+ if (Array.isArray(value)) {
3058
+ return value.map((item) => canonicalizeWritePayload(item));
3059
+ }
3060
+ if (value && typeof value === "object") {
3061
+ const next = {};
3062
+ for (const key of Object.keys(value)) {
3063
+ if (key === "write_approval" || key === "client_session_id") continue;
3064
+ if (value[key] === undefined) continue;
3065
+ next[key] = canonicalizeWritePayload(value[key]);
3066
+ }
3067
+ return next;
3068
+ }
3069
+ return value;
3070
+ }
3071
+
3072
+ function projectWritePayloadHash(payload) {
3073
+ const canonical = canonicalizeWritePayload(payload || {});
3074
+ return createHash("sha256").update(pythonJsonStringify(canonical)).digest("hex");
3075
+ }
3076
+
3077
+ function approvalArtifactKind(artifactPath, fallback = "yaml") {
3078
+ const ext = path.extname(String(artifactPath || "")).toLowerCase();
3079
+ if (ext === ".md" || ext === ".markdown") return "markdown";
3080
+ if (ext === ".json") return "json";
3081
+ if (ext === ".yml" || ext === ".yaml") return "yaml";
3082
+ return fallback || "yaml";
3083
+ }
3084
+
3085
+ function resolveWriteApprovalArtifact(args) {
3086
+ const artifactPath = firstNonEmptyString(
3087
+ args["approval-artifact"],
3088
+ args.approvalArtifact,
3089
+ args.approval_artifact
3090
+ );
3091
+ if (!artifactPath) {
3092
+ console.error("Missing --approval-artifact for confirmed live Myte write.");
3093
+ process.exit(1);
3094
+ }
3095
+ const absPath = path.resolve(String(artifactPath));
3096
+ if (!fs.existsSync(absPath) || !fs.statSync(absPath).isFile()) {
3097
+ console.error(`Approval artifact not found: ${absPath}`);
3098
+ process.exit(1);
3099
+ }
3100
+ const ext = path.extname(absPath).toLowerCase();
3101
+ if (![".md", ".markdown", ".yml", ".yaml", ".json"].includes(ext)) {
3102
+ console.error("Approval artifact must be a local .md, .yml, .yaml, or .json file.");
3103
+ process.exit(1);
3104
+ }
3105
+ return { displayPath: String(artifactPath), absPath };
3106
+ }
3107
+
3108
+ function approvalTargets(values) {
3109
+ return uniqueNormalizedStrings(Array.isArray(values) ? values : [values]);
3110
+ }
3111
+
3112
+ function withWriteApproval(args, payload, { artifactKind = "yaml", targets = [], batchCount = null } = {}) {
3113
+ const confirmed = Boolean(args["confirm-write"] || args.confirmWrite || args.confirm_write);
3114
+ if (!confirmed) return payload;
3115
+ const { displayPath } = resolveWriteApprovalArtifact(args);
3116
+ const nextPayload = { ...(payload || {}) };
3117
+ const normalizedTargets = approvalTargets(targets);
3118
+ const approval = {
3119
+ approved: true,
3120
+ source: "explicit_user_approval",
3121
+ artifact_path: displayPath,
3122
+ artifact_kind: approvalArtifactKind(displayPath, artifactKind),
3123
+ payload_sha256: projectWritePayloadHash(nextPayload),
3124
+ };
3125
+ if (normalizedTargets.length) approval.targets = normalizedTargets;
3126
+ if (batchCount !== undefined && batchCount !== null) approval.batch_count = Number(batchCount);
3127
+ nextPayload.write_approval = approval;
3128
+ return nextPayload;
3129
+ }
3130
+
3131
+ function suggestionWriteTargets(items) {
3132
+ return (Array.isArray(items) ? items : [])
3133
+ .map((item, index) => {
3134
+ if (!item || typeof item !== "object") return `index:${index}`;
3135
+ const changeType = String(item.change_type || item.action || "suggestion").trim().toLowerCase() || "suggestion";
3136
+ const changeSet = item.change_set && typeof item.change_set === "object" ? item.change_set : item;
3137
+ return firstNonEmptyString(
3138
+ item.suggestion_id,
3139
+ item.mission_id,
3140
+ item.client_ref,
3141
+ changeSet.title ? `${changeType}:${String(changeSet.title).slice(0, 80)}` : "",
3142
+ `index:${index}`
3143
+ );
3144
+ })
3145
+ .filter(Boolean);
3146
+ }
3147
+
3148
+ function parseYaml(text) {
3149
+ const raw = String(text || "").trim();
3150
+ if (!raw) return null;
3151
+ if (raw.startsWith("{") || raw.startsWith("[")) {
3227
3152
  return JSON.parse(raw);
3228
3153
  }
3229
3154
  return parseSimpleYaml(raw);
@@ -4486,44 +4411,41 @@ async function fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScop
4486
4411
  return body.data || {};
4487
4412
  }
4488
4413
 
4489
- async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4490
- return postSuggestionsMutation({
4491
- apiBase,
4492
- key,
4493
- timeoutMs,
4494
- endpoint: "/project-assistant/suggestions",
4495
- payload,
4496
- idempotencyKey,
4497
- clientSessionId,
4498
- writeApproval,
4499
- });
4500
- }
4501
-
4502
- async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4503
- return postSuggestionsMutation({
4504
- apiBase,
4505
- key,
4506
- timeoutMs,
4507
- endpoint: "/project-assistant/suggestions/revise",
4508
- payload,
4509
- idempotencyKey,
4510
- clientSessionId,
4511
- writeApproval,
4512
- });
4513
- }
4514
-
4515
- async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4516
- return postSuggestionsMutation({
4517
- apiBase,
4518
- key,
4519
- timeoutMs,
4520
- endpoint: "/project-assistant/suggestions/review",
4521
- payload,
4522
- idempotencyKey,
4523
- clientSessionId,
4524
- writeApproval,
4525
- });
4526
- }
4414
+ async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4415
+ return postSuggestionsMutation({
4416
+ apiBase,
4417
+ key,
4418
+ timeoutMs,
4419
+ endpoint: "/project-assistant/suggestions",
4420
+ payload,
4421
+ idempotencyKey,
4422
+ clientSessionId,
4423
+ });
4424
+ }
4425
+
4426
+ async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4427
+ return postSuggestionsMutation({
4428
+ apiBase,
4429
+ key,
4430
+ timeoutMs,
4431
+ endpoint: "/project-assistant/suggestions/revise",
4432
+ payload,
4433
+ idempotencyKey,
4434
+ clientSessionId,
4435
+ });
4436
+ }
4437
+
4438
+ async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4439
+ return postSuggestionsMutation({
4440
+ apiBase,
4441
+ key,
4442
+ timeoutMs,
4443
+ endpoint: "/project-assistant/suggestions/review",
4444
+ payload,
4445
+ idempotencyKey,
4446
+ clientSessionId,
4447
+ });
4448
+ }
4527
4449
 
4528
4450
  async function runCreatePrd(args) {
4529
4451
  const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
@@ -4630,31 +4552,29 @@ async function runCreatePrd(args) {
4630
4552
 
4631
4553
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
4632
4554
 
4633
- if (payloads.length === 1) {
4634
- let data;
4635
- const payload = payloads[0];
4636
- if (clientSessionId) payload.client_session_id = clientSessionId;
4555
+ if (payloads.length === 1) {
4556
+ let data;
4557
+ let payload = payloads[0];
4558
+ if (clientSessionId) payload.client_session_id = clientSessionId;
4559
+ payload = withWriteApproval(args, payload, {
4560
+ artifactKind: "markdown",
4561
+ targets: [firstNonEmptyString(payload.client_ref, payload.title, "create-prd")],
4562
+ });
4637
4563
  const idempotencyKey = resolveProjectMutationIdempotencyKey({
4638
4564
  args,
4639
4565
  operation: "create_prd",
4640
- payload,
4641
- });
4642
- const writeApproval = requireWriteApproval(args, payload, {
4643
- operation: "create_prd",
4644
- artifactKind: "markdown",
4645
- targets: prdApprovalTargets(payload),
4646
- });
4647
- try {
4648
- data = await postSuggestionsMutation({
4649
- apiBase,
4650
- key,
4651
- payload,
4566
+ payload,
4567
+ });
4568
+ try {
4569
+ data = await postSuggestionsMutation({
4570
+ apiBase,
4571
+ key,
4572
+ payload,
4652
4573
  timeoutMs,
4653
- endpoint: "/project-assistant/create-prd",
4654
- idempotencyKey,
4655
- clientSessionId,
4656
- writeApproval,
4657
- });
4574
+ endpoint: "/project-assistant/create-prd",
4575
+ idempotencyKey,
4576
+ clientSessionId,
4577
+ });
4658
4578
  } catch (err) {
4659
4579
  if (err?.name === "AbortError") {
4660
4580
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -4695,32 +4615,29 @@ async function runCreatePrd(args) {
4695
4615
  failed_count: 0,
4696
4616
  items: [],
4697
4617
  };
4698
-
4699
- try {
4700
- const payload = { items: payloads };
4701
- if (clientSessionId) payload.client_session_id = clientSessionId;
4618
+
4619
+ try {
4620
+ let payload = { items: payloads };
4621
+ if (clientSessionId) payload.client_session_id = clientSessionId;
4622
+ payload = withWriteApproval(args, payload, {
4623
+ artifactKind: "markdown",
4624
+ targets: payloads.map((item, index) => firstNonEmptyString(item.client_ref, item.title, `index:${index}`)),
4625
+ batchCount: payloads.length,
4626
+ });
4702
4627
  const idempotencyKey = resolveProjectMutationIdempotencyKey({
4703
4628
  args,
4704
4629
  operation: "create_prds",
4705
- payload,
4706
- });
4707
- const prdTargets = prdApprovalTargets(payload);
4708
- const writeApproval = requireWriteApproval(args, payload, {
4709
- operation: "create_prds",
4710
- artifactKind: "markdown",
4711
- targets: prdTargets,
4712
- batchCount: prdTargets.length,
4713
- });
4714
- const data = await postSuggestionsMutation({
4715
- apiBase,
4716
- key,
4717
- payload,
4718
- timeoutMs,
4719
- endpoint: "/project-assistant/create-prds",
4720
- idempotencyKey,
4721
- clientSessionId,
4722
- writeApproval,
4723
- });
4630
+ payload,
4631
+ });
4632
+ const data = await postSuggestionsMutation({
4633
+ apiBase,
4634
+ key,
4635
+ payload,
4636
+ timeoutMs,
4637
+ endpoint: "/project-assistant/create-prds",
4638
+ idempotencyKey,
4639
+ clientSessionId,
4640
+ });
4724
4641
  aggregated.project_id = data.project_id || null;
4725
4642
  aggregated.created_count = Number(data.created_count || 0);
4726
4643
  aggregated.failed_count = Number(data.failed_count || 0);
@@ -5276,7 +5193,8 @@ async function runFeedbackValidateOrApply(args, mode) {
5276
5193
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5277
5194
  process.exit(1);
5278
5195
  }
5279
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5196
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5197
+ let payload = artifactPayload;
5280
5198
  const feedbackId = firstNonEmptyString(args["feedback-id"], args.feedbackId, args.feedback_id, payload.feedback_id);
5281
5199
  if (!feedbackId) {
5282
5200
  console.error("Feedback refinement artifact is missing feedback_id.");
@@ -5286,40 +5204,38 @@ async function runFeedbackValidateOrApply(args, mode) {
5286
5204
 
5287
5205
  const timeoutMs = resolveTimeoutMs(args);
5288
5206
  const apiBase = resolveApiBase(args);
5289
- const clientSessionId = firstNonEmptyString(
5207
+ const clientSessionId = firstNonEmptyString(
5290
5208
  args["client-session-id"],
5291
5209
  args.clientSessionId,
5292
5210
  args.client_session_id,
5293
5211
  payload.client_session_id
5294
- );
5212
+ );
5213
+ if (mode === "apply") {
5214
+ payload = withWriteApproval(args, payload, {
5215
+ artifactKind: "yaml",
5216
+ targets: [feedbackId],
5217
+ });
5218
+ }
5295
5219
  const idempotencyKey = mode === "apply"
5296
- ? resolveProjectMutationIdempotencyKey({
5297
- args,
5298
- operation: `feedback_refinement_apply:${feedbackId}`,
5299
- payload,
5300
- })
5301
- : null;
5302
- const writeApproval = mode === "apply"
5303
- ? requireWriteApproval(args, payload, {
5304
- operation: `feedback_refinement_apply:${feedbackId}`,
5305
- artifactKind: "yaml",
5306
- targets: [feedbackId],
5307
- })
5308
- : null;
5309
-
5310
- let data;
5311
- try {
5312
- data = await postFeedbackRefinement({
5313
- apiBase,
5220
+ ? resolveProjectMutationIdempotencyKey({
5221
+ args,
5222
+ operation: `feedback_refinement_apply:${feedbackId}`,
5223
+ payload,
5224
+ })
5225
+ : null;
5226
+
5227
+ let data;
5228
+ try {
5229
+ data = await postFeedbackRefinement({
5230
+ apiBase,
5314
5231
  key,
5315
5232
  timeoutMs,
5316
5233
  feedbackId,
5317
5234
  mode,
5318
- payload,
5319
- idempotencyKey,
5320
- clientSessionId,
5321
- writeApproval,
5322
- });
5235
+ payload,
5236
+ idempotencyKey,
5237
+ clientSessionId,
5238
+ });
5323
5239
  } catch (err) {
5324
5240
  if (args.json) {
5325
5241
  console.log(JSON.stringify({
@@ -5364,33 +5280,33 @@ function resolveFeedbackRequestIdArg(args) {
5364
5280
  return firstNonEmptyString(args["request-id"], args.requestId, args.request_id, args._?.[1]);
5365
5281
  }
5366
5282
 
5367
- function resolveFeedbackEventIdArg(args) {
5368
- return firstNonEmptyString(args["event-id"], args.eventId, args.event_id, args._?.[1]);
5369
- }
5283
+ function resolveFeedbackEventIdArg(args) {
5284
+ return firstNonEmptyString(args["event-id"], args.eventId, args.event_id, args._?.[1]);
5285
+ }
5370
5286
 
5371
- function resolveFeedbackVersionIdArg(args) {
5372
- return firstNonEmptyString(args["version-id"], args.versionId, args.version_id, args._?.[1]);
5373
- }
5374
-
5375
- async function resolveFeedbackCommentContent(args, feedbackId) {
5376
- const explicit = firstNonEmptyString(args.body, args.content, args["body-markdown"], args.bodyMarkdown, args.body_markdown);
5377
- if (explicit) return explicit;
5378
-
5379
- const bodyFile = firstNonEmptyString(args["body-file"], args.bodyFile, args.body_file);
5380
- if (bodyFile) {
5381
- const bodyPath = resolveInputFile(bodyFile, "Feedback comment body");
5382
- return fs.readFileSync(bodyPath, "utf8");
5383
- }
5384
-
5385
- if (args.stdin) {
5386
- return readStdinText();
5387
- }
5388
-
5389
- const positionals = Array.isArray(args._) ? args._.slice(1) : [];
5390
- const feedbackIdFromPositional = positionals[0] && String(positionals[0]) === String(feedbackId);
5391
- const textParts = feedbackIdFromPositional ? positionals.slice(1) : positionals;
5392
- return textParts.join(" ");
5393
- }
5287
+ function resolveFeedbackVersionIdArg(args) {
5288
+ return firstNonEmptyString(args["version-id"], args.versionId, args.version_id, args._?.[1]);
5289
+ }
5290
+
5291
+ async function resolveFeedbackCommentContent(args, feedbackId) {
5292
+ const explicit = firstNonEmptyString(args.body, args.content, args["body-markdown"], args.bodyMarkdown, args.body_markdown);
5293
+ if (explicit) return explicit;
5294
+
5295
+ const bodyFile = firstNonEmptyString(args["body-file"], args.bodyFile, args.body_file);
5296
+ if (bodyFile) {
5297
+ const bodyPath = resolveInputFile(bodyFile, "Feedback comment body");
5298
+ return fs.readFileSync(bodyPath, "utf8");
5299
+ }
5300
+
5301
+ if (args.stdin) {
5302
+ return readStdinText();
5303
+ }
5304
+
5305
+ const positionals = Array.isArray(args._) ? args._.slice(1) : [];
5306
+ const feedbackIdFromPositional = positionals[0] && String(positionals[0]) === String(feedbackId);
5307
+ const textParts = feedbackIdFromPositional ? positionals.slice(1) : positionals;
5308
+ return textParts.join(" ");
5309
+ }
5394
5310
 
5395
5311
  function summarizeFeedbackReviewRequestPayload(data) {
5396
5312
  const request = data?.request || data || {};
@@ -5430,7 +5346,8 @@ async function runFeedbackSubmit(args) {
5430
5346
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5431
5347
  process.exit(1);
5432
5348
  }
5433
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5349
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5350
+ let payload = artifactPayload;
5434
5351
  const feedbackId = firstNonEmptyString(args["feedback-id"], args.feedbackId, args.feedback_id, payload.feedback_id);
5435
5352
  if (!feedbackId) {
5436
5353
  console.error("Feedback refinement artifact is missing feedback_id.");
@@ -5438,35 +5355,33 @@ async function runFeedbackSubmit(args) {
5438
5355
  }
5439
5356
  const timeoutMs = resolveTimeoutMs(args);
5440
5357
  const apiBase = resolveApiBase(args);
5441
- const clientSessionId = firstNonEmptyString(
5358
+ const clientSessionId = firstNonEmptyString(
5442
5359
  args["client-session-id"],
5443
5360
  args.clientSessionId,
5444
5361
  args.client_session_id,
5445
5362
  payload.client_session_id
5446
- );
5447
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5448
- args,
5449
- operation: `feedback_refinement_request_submit:${feedbackId}`,
5450
- payload,
5451
- });
5452
- const writeApproval = requireWriteApproval(args, payload, {
5453
- operation: `feedback_refinement_request_submit:${feedbackId}`,
5363
+ );
5364
+ payload = withWriteApproval(args, payload, {
5454
5365
  artifactKind: "yaml",
5455
5366
  targets: [feedbackId],
5456
5367
  });
5457
-
5458
- let data;
5459
- try {
5460
- data = await postFeedbackReviewSubmission({
5368
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5369
+ args,
5370
+ operation: `feedback_refinement_request_submit:${feedbackId}`,
5371
+ payload,
5372
+ });
5373
+
5374
+ let data;
5375
+ try {
5376
+ data = await postFeedbackReviewSubmission({
5461
5377
  apiBase,
5462
5378
  key,
5463
5379
  timeoutMs,
5464
5380
  feedbackId,
5465
- payload,
5466
- idempotencyKey,
5467
- clientSessionId,
5468
- writeApproval,
5469
- });
5381
+ payload,
5382
+ idempotencyKey,
5383
+ clientSessionId,
5384
+ });
5470
5385
  } catch (err) {
5471
5386
  if (args.json) {
5472
5387
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null, artifact_path: absPath }, null, 2));
@@ -5507,39 +5422,38 @@ async function runFeedbackRevise(args) {
5507
5422
  console.error("Missing --request-id.");
5508
5423
  process.exit(1);
5509
5424
  }
5510
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5425
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5426
+ let payload = artifactPayload;
5511
5427
  const timeoutMs = resolveTimeoutMs(args);
5512
5428
  const apiBase = resolveApiBase(args);
5513
- const clientSessionId = firstNonEmptyString(
5429
+ const clientSessionId = firstNonEmptyString(
5514
5430
  args["client-session-id"],
5515
5431
  args.clientSessionId,
5516
5432
  args.client_session_id,
5517
5433
  payload.client_session_id
5518
- );
5519
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5520
- args,
5521
- operation: `feedback_refinement_request_revise:${requestId}`,
5522
- payload,
5523
- });
5524
- const writeApproval = requireWriteApproval(args, payload, {
5525
- operation: `feedback_refinement_request_revise:${requestId}`,
5434
+ );
5435
+ payload = withWriteApproval(args, payload, {
5526
5436
  artifactKind: "yaml",
5527
5437
  targets: [requestId],
5528
5438
  });
5529
-
5530
- let data;
5531
- try {
5532
- data = await postFeedbackReviewRequestMutation({
5439
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5440
+ args,
5441
+ operation: `feedback_refinement_request_revise:${requestId}`,
5442
+ payload,
5443
+ });
5444
+
5445
+ let data;
5446
+ try {
5447
+ data = await postFeedbackReviewRequestMutation({
5533
5448
  apiBase,
5534
5449
  key,
5535
5450
  timeoutMs,
5536
5451
  requestId,
5537
5452
  endpointAction: "revise",
5538
- payload,
5539
- idempotencyKey,
5540
- clientSessionId,
5541
- writeApproval,
5542
- });
5453
+ payload,
5454
+ idempotencyKey,
5455
+ clientSessionId,
5456
+ });
5543
5457
  } catch (err) {
5544
5458
  if (args.json) {
5545
5459
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null, artifact_path: absPath }, null, 2));
@@ -5656,41 +5570,36 @@ async function runFeedbackReviewDecision(args) {
5656
5570
  process.exit(1);
5657
5571
  }
5658
5572
  const reason = firstNonEmptyString(args.reason, args["review-note"], args.reviewNote, args.review_note);
5659
- const payload = {
5660
- ...(isPlainObject(filePayload) && !Array.isArray(filePayload) ? filePayload : {}),
5661
- items,
5662
- action,
5663
- reason: reason || undefined,
5664
- };
5665
- const timeoutMs = resolveTimeoutMs(args);
5666
- const apiBase = resolveApiBase(args);
5667
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5668
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5669
- args,
5670
- operation: `feedback_review_batch:${action}:${items.map((item) => item.request_id || item.id || "").join(",")}`,
5671
- payload,
5672
- });
5673
- const requestTargets = items
5674
- .map((item, index) => firstNonEmptyString(item.request_id, item.id, item._id) || `index:${index}`)
5675
- .filter(Boolean);
5676
- const writeApproval = requireWriteApproval(args, payload, {
5677
- operation: `feedback_review_batch:${action}`,
5573
+ let payload = {
5574
+ ...(isPlainObject(filePayload) && !Array.isArray(filePayload) ? filePayload : {}),
5575
+ items,
5576
+ action,
5577
+ reason: reason || undefined,
5578
+ };
5579
+ payload = withWriteApproval(args, payload, {
5678
5580
  artifactKind: "yaml",
5679
- targets: requestTargets,
5581
+ targets: items.map((item) => item.request_id || item.id).filter(Boolean),
5680
5582
  batchCount: items.length,
5681
5583
  });
5682
-
5683
- let data;
5684
- try {
5685
- data = await postFeedbackBatchReviewMutation({
5584
+ const timeoutMs = resolveTimeoutMs(args);
5585
+ const apiBase = resolveApiBase(args);
5586
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5587
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5588
+ args,
5589
+ operation: `feedback_review_batch:${action}:${items.map((item) => item.request_id || item.id || "").join(",")}`,
5590
+ payload,
5591
+ });
5592
+
5593
+ let data;
5594
+ try {
5595
+ data = await postFeedbackBatchReviewMutation({
5686
5596
  apiBase,
5687
5597
  key,
5688
5598
  timeoutMs,
5689
- payload,
5690
- idempotencyKey,
5691
- clientSessionId,
5692
- writeApproval,
5693
- });
5599
+ payload,
5600
+ idempotencyKey,
5601
+ clientSessionId,
5602
+ });
5694
5603
  } catch (err) {
5695
5604
  if (args.json) {
5696
5605
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5717,8 +5626,8 @@ async function runFeedbackReviewDecision(args) {
5717
5626
  process.exit(1);
5718
5627
  }
5719
5628
 
5720
- let payload;
5721
- try {
5629
+ let payload;
5630
+ try {
5722
5631
  payload = filePayload && !Array.isArray(filePayload?.items)
5723
5632
  ? buildFeedbackReviewDecisionPayload({ ...args, file: "" }, action)
5724
5633
  : buildFeedbackReviewDecisionPayload(args, action);
@@ -5729,35 +5638,33 @@ async function runFeedbackReviewDecision(args) {
5729
5638
  console.error(err?.message || err);
5730
5639
  process.exit(1);
5731
5640
  }
5732
- const endpointAction = action === "request_changes" ? "request-changes" : action === "cancel" ? "cancel" : "review";
5733
- const timeoutMs = resolveTimeoutMs(args);
5734
- const apiBase = resolveApiBase(args);
5735
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5736
- const operationAction = action === "request_changes" ? "request_changes" : action;
5737
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5738
- args,
5739
- operation: `feedback_review_request_${operationAction}:${requestId}`,
5740
- payload,
5741
- });
5742
- const writeApproval = requireWriteApproval(args, payload, {
5743
- operation: `feedback_review_request_${operationAction}:${requestId}`,
5641
+ const endpointAction = action === "request_changes" ? "request-changes" : action === "cancel" ? "cancel" : "review";
5642
+ payload = withWriteApproval(args, payload, {
5744
5643
  artifactKind: "yaml",
5745
5644
  targets: [requestId],
5746
5645
  });
5747
-
5748
- let data;
5749
- try {
5750
- data = await postFeedbackReviewRequestMutation({
5646
+ const timeoutMs = resolveTimeoutMs(args);
5647
+ const apiBase = resolveApiBase(args);
5648
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5649
+ const operationAction = action === "request_changes" ? "request_changes" : action;
5650
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5651
+ args,
5652
+ operation: `feedback_review_request_${operationAction}:${requestId}`,
5653
+ payload,
5654
+ });
5655
+
5656
+ let data;
5657
+ try {
5658
+ data = await postFeedbackReviewRequestMutation({
5751
5659
  apiBase,
5752
5660
  key,
5753
5661
  timeoutMs,
5754
5662
  requestId,
5755
5663
  endpointAction,
5756
- payload,
5757
- idempotencyKey,
5758
- clientSessionId,
5759
- writeApproval,
5760
- });
5664
+ payload,
5665
+ idempotencyKey,
5666
+ clientSessionId,
5667
+ });
5761
5668
  } catch (err) {
5762
5669
  if (args.json) {
5763
5670
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5777,7 +5684,7 @@ async function runFeedbackReviewDecision(args) {
5777
5684
  if (postSync?.error) console.error(`Feedback sync warning: ${postSync.error}`);
5778
5685
  }
5779
5686
 
5780
- async function runFeedbackMove(args) {
5687
+ async function runFeedbackMove(args) {
5781
5688
  const key = getProjectApiKey();
5782
5689
  if (!key) {
5783
5690
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
@@ -5795,43 +5702,41 @@ async function runFeedbackMove(args) {
5795
5702
  process.exit(1);
5796
5703
  }
5797
5704
  const fromState = firstNonEmptyString(args["from-state"], args.fromState, args.from_state) || undefined;
5798
- const payload = {
5799
- to_state: toState,
5800
- reason: firstNonEmptyString(args.reason) || undefined,
5801
- };
5705
+ let payload = {
5706
+ to_state: toState,
5707
+ reason: firstNonEmptyString(args.reason) || undefined,
5708
+ };
5802
5709
  const timeoutMs = resolveTimeoutMs(args);
5803
5710
  const apiBase = resolveApiBase(args);
5804
5711
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5805
5712
  const isBatch = feedbackIds.length > 1 || Boolean(firstNonEmptyString(args["feedback-ids"], args.feedbackIds, args.feedback_ids));
5806
- if (isBatch) {
5807
- payload.feedback_ids = feedbackIds;
5713
+ if (isBatch) {
5714
+ payload.feedback_ids = feedbackIds;
5808
5715
  if (fromState) {
5809
5716
  payload.from_states = Object.fromEntries(feedbackIds.map((id) => [id, fromState]));
5810
5717
  }
5811
5718
  const batchId = firstNonEmptyString(args["batch-id"], args.batchId, args.batch_id);
5812
- if (batchId) payload.batch_id = batchId;
5813
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5814
- args,
5815
- operation: `feedback_board_batch_move:${toState}:${feedbackIds.join(",")}`,
5816
- payload,
5817
- });
5818
- const writeApproval = requireWriteApproval(args, payload, {
5819
- operation: `feedback_board_batch_move:${toState}`,
5719
+ if (batchId) payload.batch_id = batchId;
5720
+ payload = withWriteApproval(args, payload, {
5820
5721
  artifactKind: "yaml",
5821
5722
  targets: feedbackIds,
5822
5723
  batchCount: feedbackIds.length,
5823
5724
  });
5824
- let data;
5825
- try {
5826
- data = await postFeedbackBatchBoardMutation({
5827
- apiBase,
5828
- key,
5725
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5726
+ args,
5727
+ operation: `feedback_board_batch_move:${toState}:${feedbackIds.join(",")}`,
5728
+ payload,
5729
+ });
5730
+ let data;
5731
+ try {
5732
+ data = await postFeedbackBatchBoardMutation({
5733
+ apiBase,
5734
+ key,
5829
5735
  timeoutMs,
5830
- payload,
5831
- idempotencyKey,
5832
- clientSessionId,
5833
- writeApproval,
5834
- });
5736
+ payload,
5737
+ idempotencyKey,
5738
+ clientSessionId,
5739
+ });
5835
5740
  } catch (err) {
5836
5741
  if (args.json) {
5837
5742
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5851,32 +5756,30 @@ async function runFeedbackMove(args) {
5851
5756
  if (postSync?.error) console.error(`Feedback sync warning: ${postSync.error}`);
5852
5757
  return;
5853
5758
  }
5854
-
5855
- payload.from_state = fromState;
5856
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5857
- args,
5858
- operation: `feedback_board_move:${feedbackId}`,
5859
- payload,
5860
- });
5861
- const writeApproval = requireWriteApproval(args, payload, {
5862
- operation: `feedback_board_move:${feedbackId}`,
5759
+
5760
+ payload.from_state = fromState;
5761
+ payload = withWriteApproval(args, payload, {
5863
5762
  artifactKind: "yaml",
5864
5763
  targets: [feedbackId],
5865
5764
  });
5866
-
5867
- let data;
5868
- try {
5869
- data = await postFeedbackBoardMutation({
5765
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5766
+ args,
5767
+ operation: `feedback_board_move:${feedbackId}`,
5768
+ payload,
5769
+ });
5770
+
5771
+ let data;
5772
+ try {
5773
+ data = await postFeedbackBoardMutation({
5870
5774
  apiBase,
5871
5775
  key,
5872
5776
  timeoutMs,
5873
5777
  feedbackId,
5874
5778
  endpoint: "board-move",
5875
- payload,
5876
- idempotencyKey,
5877
- clientSessionId,
5878
- writeApproval,
5879
- });
5779
+ payload,
5780
+ idempotencyKey,
5781
+ clientSessionId,
5782
+ });
5880
5783
  } catch (err) {
5881
5784
  if (args.json) {
5882
5785
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5896,92 +5799,89 @@ async function runFeedbackMove(args) {
5896
5799
  console.log(`State: ${data.feedback_state || toState}`);
5897
5800
  if (data.event?.event_id) console.log(`Event: ${data.event.event_id}`);
5898
5801
  if (postSync?.error) console.error(`Feedback sync warning: ${postSync.error}`);
5899
- }
5900
-
5901
- async function runFeedbackComment(args) {
5902
- const key = getProjectApiKey();
5903
- if (!key) {
5904
- console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5905
- process.exit(1);
5906
- }
5907
- const feedbackId = resolveFeedbackIdArg(args);
5908
- if (!feedbackId) {
5909
- console.error("Missing --feedback-id.");
5910
- process.exit(1);
5911
- }
5912
-
5913
- const content = String(await resolveFeedbackCommentContent(args, feedbackId) || "").trim();
5914
- if (!content) {
5915
- console.error("Missing feedback comment body. Use --body, --body-file, --stdin, or positional text.");
5916
- process.exit(1);
5917
- }
5918
-
5919
- const payload = { content };
5802
+ }
5803
+
5804
+ async function runFeedbackComment(args) {
5805
+ const key = getProjectApiKey();
5806
+ if (!key) {
5807
+ console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5808
+ process.exit(1);
5809
+ }
5810
+ const feedbackId = resolveFeedbackIdArg(args);
5811
+ if (!feedbackId) {
5812
+ console.error("Missing --feedback-id.");
5813
+ process.exit(1);
5814
+ }
5815
+
5816
+ const content = String(await resolveFeedbackCommentContent(args, feedbackId) || "").trim();
5817
+ if (!content) {
5818
+ console.error("Missing feedback comment body. Use --body, --body-file, --stdin, or positional text.");
5819
+ process.exit(1);
5820
+ }
5821
+
5822
+ let payload = { content };
5920
5823
  const timeoutMs = resolveTimeoutMs(args);
5921
5824
  const apiBase = resolveApiBase(args);
5922
5825
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5923
5826
  if (clientSessionId) payload.client_session_id = clientSessionId;
5924
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5925
- args,
5926
- operation: `feedback_comment_create:${feedbackId}`,
5927
- payload,
5928
- });
5929
-
5930
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
5931
- console.log(JSON.stringify(payload, null, 2));
5932
- return;
5933
- }
5934
-
5935
- const writeApproval = requireWriteApproval(args, payload, {
5936
- operation: `feedback_comment_create:${feedbackId}`,
5827
+ payload = withWriteApproval(args, payload, {
5937
5828
  artifactKind: "markdown",
5938
5829
  targets: [feedbackId],
5939
5830
  });
5940
-
5941
- let data;
5942
- try {
5943
- data = await postFeedbackComment({
5944
- apiBase,
5945
- key,
5946
- timeoutMs,
5947
- feedbackId,
5948
- payload,
5949
- idempotencyKey,
5950
- clientSessionId,
5951
- writeApproval,
5952
- });
5953
- } catch (err) {
5954
- if (args.json) {
5955
- console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
5956
- } else {
5957
- console.error("Feedback comment failed:", err?.message || err);
5958
- if (err?.data) console.error(JSON.stringify(err.data, null, 2));
5959
- }
5960
- process.exit(1);
5961
- }
5962
-
5963
- const postSync = await refreshFeedbackSnapshotAfterMutation(args, { apiBase, key, timeoutMs });
5964
- if (args.json) {
5965
- console.log(JSON.stringify({ ok: true, ...data, post_sync: postSync }, null, 2));
5966
- return;
5967
- }
5968
-
5969
- if (data.comment_id) console.log(`Comment ID: ${data.comment_id}`);
5970
- console.log(`Feedback: ${data.feedback_id || feedbackId}`);
5971
- if (data.project_id) console.log(`Project ID: ${data.project_id}`);
5972
- if (data.user_name) console.log(`Author: ${data.user_name}`);
5973
- if (data.created_at) console.log(`Created At: ${data.created_at}`);
5974
- if (data.content) {
5975
- const body = String(data.content);
5976
- const truncated = body.length > 2000 ? `${body.slice(0, 2000)}\n...[truncated]` : body;
5977
- console.log("Content:");
5978
- console.log(truncated);
5979
- }
5980
- if (postSync?.data_root) console.log(`Synced feedback: ${postSync.data_root}`);
5981
- if (postSync?.error) console.error(`Feedback sync warning: ${postSync.error}`);
5982
- }
5983
-
5984
- async function runFeedbackUndo(args) {
5831
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5832
+ args,
5833
+ operation: `feedback_comment_create:${feedbackId}`,
5834
+ payload,
5835
+ });
5836
+
5837
+ if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
5838
+ console.log(JSON.stringify(payload, null, 2));
5839
+ return;
5840
+ }
5841
+
5842
+ let data;
5843
+ try {
5844
+ data = await postFeedbackComment({
5845
+ apiBase,
5846
+ key,
5847
+ timeoutMs,
5848
+ feedbackId,
5849
+ payload,
5850
+ idempotencyKey,
5851
+ clientSessionId,
5852
+ });
5853
+ } catch (err) {
5854
+ if (args.json) {
5855
+ console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
5856
+ } else {
5857
+ console.error("Feedback comment failed:", err?.message || err);
5858
+ if (err?.data) console.error(JSON.stringify(err.data, null, 2));
5859
+ }
5860
+ process.exit(1);
5861
+ }
5862
+
5863
+ const postSync = await refreshFeedbackSnapshotAfterMutation(args, { apiBase, key, timeoutMs });
5864
+ if (args.json) {
5865
+ console.log(JSON.stringify({ ok: true, ...data, post_sync: postSync }, null, 2));
5866
+ return;
5867
+ }
5868
+
5869
+ if (data.comment_id) console.log(`Comment ID: ${data.comment_id}`);
5870
+ console.log(`Feedback: ${data.feedback_id || feedbackId}`);
5871
+ if (data.project_id) console.log(`Project ID: ${data.project_id}`);
5872
+ if (data.user_name) console.log(`Author: ${data.user_name}`);
5873
+ if (data.created_at) console.log(`Created At: ${data.created_at}`);
5874
+ if (data.content) {
5875
+ const body = String(data.content);
5876
+ const truncated = body.length > 2000 ? `${body.slice(0, 2000)}\n...[truncated]` : body;
5877
+ console.log("Content:");
5878
+ console.log(truncated);
5879
+ }
5880
+ if (postSync?.data_root) console.log(`Synced feedback: ${postSync.data_root}`);
5881
+ if (postSync?.error) console.error(`Feedback sync warning: ${postSync.error}`);
5882
+ }
5883
+
5884
+ async function runFeedbackUndo(args) {
5985
5885
  const key = getProjectApiKey();
5986
5886
  if (!key) {
5987
5887
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
@@ -5997,40 +5897,32 @@ async function runFeedbackUndo(args) {
5997
5897
  console.error("Missing --event-id.");
5998
5898
  process.exit(1);
5999
5899
  }
6000
- const payload = { reason: firstNonEmptyString(args.reason) || undefined };
6001
- const timeoutMs = resolveTimeoutMs(args);
6002
- const apiBase = resolveApiBase(args);
6003
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
6004
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
6005
- args,
6006
- operation: `feedback_event_undo:${feedbackId}:${eventId}`,
6007
- payload,
6008
- });
6009
-
6010
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6011
- console.log(JSON.stringify(payload, null, 2));
6012
- return;
6013
- }
6014
-
6015
- const writeApproval = requireWriteApproval(args, payload, {
6016
- operation: `feedback_event_undo:${feedbackId}:${eventId}`,
5900
+ let payload = { reason: firstNonEmptyString(args.reason) || undefined };
5901
+ const timeoutMs = resolveTimeoutMs(args);
5902
+ const apiBase = resolveApiBase(args);
5903
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5904
+ payload = withWriteApproval(args, payload, {
6017
5905
  artifactKind: "yaml",
6018
5906
  targets: [feedbackId, eventId],
6019
5907
  });
6020
-
6021
- let data;
6022
- try {
5908
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5909
+ args,
5910
+ operation: `feedback_event_undo:${feedbackId}:${eventId}`,
5911
+ payload,
5912
+ });
5913
+
5914
+ let data;
5915
+ try {
6023
5916
  data = await postFeedbackBoardMutation({
6024
5917
  apiBase,
6025
5918
  key,
6026
5919
  timeoutMs,
6027
5920
  feedbackId,
6028
5921
  endpoint: `events/${encodeURIComponent(String(eventId))}/undo`,
6029
- payload,
6030
- idempotencyKey,
6031
- clientSessionId,
6032
- writeApproval,
6033
- });
5922
+ payload,
5923
+ idempotencyKey,
5924
+ clientSessionId,
5925
+ });
6034
5926
  } catch (err) {
6035
5927
  if (args.json) {
6036
5928
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -6188,15 +6080,15 @@ async function runFeedback(args) {
6188
6080
  await runFeedbackReviewDecision(args);
6189
6081
  return;
6190
6082
  }
6191
- if (subcommand === "move") {
6192
- await runFeedbackMove(args);
6193
- return;
6194
- }
6195
- if (subcommand === "comment" || subcommand === "comments") {
6196
- await runFeedbackComment(args);
6197
- return;
6198
- }
6199
- if (subcommand === "undo") {
6083
+ if (subcommand === "move") {
6084
+ await runFeedbackMove(args);
6085
+ return;
6086
+ }
6087
+ if (subcommand === "comment" || subcommand === "comments") {
6088
+ await runFeedbackComment(args);
6089
+ return;
6090
+ }
6091
+ if (subcommand === "undo") {
6200
6092
  await runFeedbackUndo(args);
6201
6093
  return;
6202
6094
  }
@@ -6212,7 +6104,7 @@ async function runFeedback(args) {
6212
6104
  await runFeedbackGetOrHistory(args, subcommand);
6213
6105
  return;
6214
6106
  }
6215
- console.error("Unknown feedback command. Use status, edit, assign, archive, submit, revise, reviews, review, move, comment, undo, prd-versions, prd-diff, get, history, validate, or apply.");
6107
+ console.error("Unknown feedback command. Use status, edit, assign, archive, submit, revise, reviews, review, move, comment, undo, prd-versions, prd-diff, get, history, validate, or apply.");
6216
6108
  process.exit(1);
6217
6109
  }
6218
6110
 
@@ -6315,14 +6207,20 @@ async function buildSuggestionsMutationContext(args, mode) {
6315
6207
  if (clientSessionId && !payload.client_session_id) {
6316
6208
  payload.client_session_id = clientSessionId;
6317
6209
  }
6318
- if (mode === "create") {
6319
- validateSuggestionsCreatePayload(payload);
6320
- } else if (mode === "revise") {
6321
- validateSuggestionsRevisePayload(payload);
6322
- }
6323
- const idempotencyKey = resolveSuggestionsIdempotencyKey({
6324
- args,
6325
- mode,
6210
+ if (mode === "create") {
6211
+ validateSuggestionsCreatePayload(payload);
6212
+ } else if (mode === "revise") {
6213
+ validateSuggestionsRevisePayload(payload);
6214
+ }
6215
+ const items = Array.isArray(payload?.items) ? payload.items : [];
6216
+ payload = withWriteApproval(args, payload, {
6217
+ artifactKind: "yaml",
6218
+ targets: suggestionWriteTargets(items),
6219
+ batchCount: items.length > 1 ? items.length : null,
6220
+ });
6221
+ const idempotencyKey = resolveSuggestionsIdempotencyKey({
6222
+ args,
6223
+ mode,
6326
6224
  actorScope,
6327
6225
  payload,
6328
6226
  });
@@ -6403,30 +6301,21 @@ async function runSuggestionsCreate(args) {
6403
6301
  process.exit(1);
6404
6302
  }
6405
6303
 
6406
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6407
- console.log(JSON.stringify(context.payload, null, 2));
6408
- return;
6409
- }
6410
-
6411
- const createTargets = suggestionApprovalTargets(context.payload);
6412
- const writeApproval = requireWriteApproval(args, context.payload, {
6413
- operation: "suggestions.create",
6414
- artifactKind: "yaml",
6415
- targets: createTargets,
6416
- batchCount: createTargets.length > 1 ? createTargets.length : null,
6417
- });
6418
-
6419
- let data;
6420
- try {
6421
- data = await createProjectSuggestions({
6422
- apiBase: context.apiBase,
6423
- key: context.key,
6304
+ if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6305
+ console.log(JSON.stringify(context.payload, null, 2));
6306
+ return;
6307
+ }
6308
+
6309
+ let data;
6310
+ try {
6311
+ data = await createProjectSuggestions({
6312
+ apiBase: context.apiBase,
6313
+ key: context.key,
6424
6314
  timeoutMs: context.timeoutMs,
6425
- payload: context.payload,
6426
- idempotencyKey: context.idempotencyKey,
6427
- clientSessionId: context.clientSessionId,
6428
- writeApproval,
6429
- });
6315
+ payload: context.payload,
6316
+ idempotencyKey: context.idempotencyKey,
6317
+ clientSessionId: context.clientSessionId,
6318
+ });
6430
6319
  } catch (err) {
6431
6320
  console.error("Suggestions create failed:", err?.message || err);
6432
6321
  process.exit(1);
@@ -6497,30 +6386,21 @@ async function runSuggestionsRevise(args) {
6497
6386
  process.exit(1);
6498
6387
  }
6499
6388
 
6500
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6501
- console.log(JSON.stringify(context.payload, null, 2));
6502
- return;
6503
- }
6504
-
6505
- const reviseTargets = suggestionApprovalTargets(context.payload);
6506
- const writeApproval = requireWriteApproval(args, context.payload, {
6507
- operation: "suggestions.revise",
6508
- artifactKind: "yaml",
6509
- targets: reviseTargets,
6510
- batchCount: reviseTargets.length > 1 ? reviseTargets.length : null,
6511
- });
6512
-
6513
- let data;
6514
- try {
6515
- data = await reviseProjectSuggestions({
6516
- apiBase: context.apiBase,
6517
- key: context.key,
6389
+ if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6390
+ console.log(JSON.stringify(context.payload, null, 2));
6391
+ return;
6392
+ }
6393
+
6394
+ let data;
6395
+ try {
6396
+ data = await reviseProjectSuggestions({
6397
+ apiBase: context.apiBase,
6398
+ key: context.key,
6518
6399
  timeoutMs: context.timeoutMs,
6519
- payload: context.payload,
6520
- idempotencyKey: context.idempotencyKey,
6521
- clientSessionId: context.clientSessionId,
6522
- writeApproval,
6523
- });
6400
+ payload: context.payload,
6401
+ idempotencyKey: context.idempotencyKey,
6402
+ clientSessionId: context.clientSessionId,
6403
+ });
6524
6404
  } catch (err) {
6525
6405
  console.error("Suggestions revise failed:", err?.message || err);
6526
6406
  process.exit(1);
@@ -6589,30 +6469,21 @@ async function runSuggestionsReview(args) {
6589
6469
  process.exit(1);
6590
6470
  }
6591
6471
 
6592
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6593
- console.log(JSON.stringify(context.payload, null, 2));
6594
- return;
6595
- }
6596
-
6597
- const reviewTargets = suggestionApprovalTargets(context.payload);
6598
- const writeApproval = requireWriteApproval(args, context.payload, {
6599
- operation: "suggestions.review",
6600
- artifactKind: "yaml",
6601
- targets: reviewTargets,
6602
- batchCount: reviewTargets.length > 1 ? reviewTargets.length : null,
6603
- });
6604
-
6605
- let data;
6606
- try {
6607
- data = await reviewProjectSuggestions({
6608
- apiBase: context.apiBase,
6609
- key: context.key,
6472
+ if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6473
+ console.log(JSON.stringify(context.payload, null, 2));
6474
+ return;
6475
+ }
6476
+
6477
+ let data;
6478
+ try {
6479
+ data = await reviewProjectSuggestions({
6480
+ apiBase: context.apiBase,
6481
+ key: context.key,
6610
6482
  timeoutMs: context.timeoutMs,
6611
- payload: context.payload,
6612
- idempotencyKey: context.idempotencyKey,
6613
- clientSessionId: context.clientSessionId,
6614
- writeApproval,
6615
- });
6483
+ payload: context.payload,
6484
+ idempotencyKey: context.idempotencyKey,
6485
+ clientSessionId: context.clientSessionId,
6486
+ });
6616
6487
  } catch (err) {
6617
6488
  console.error("Suggestions review failed:", err?.message || err);
6618
6489
  process.exit(1);