@mytegroupinc/myte-core 0.0.45 → 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.
package/cli.js CHANGED
@@ -235,12 +235,6 @@ function printHelp() {
235
235
  " - Set MYTE_API_KEY in a workspace .env (or env var)",
236
236
  " - Set MYTEAI_API_KEY in a workspace .env (or env var) for `myte ai`",
237
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
- "",
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",
@@ -402,8 +396,6 @@ function printHelp() {
402
396
  " --wait Poll batch status until terminal completion for run-qaqc",
403
397
  " --sync After run-qaqc completes, refresh local QAQC file",
404
398
  " --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
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",
@@ -701,164 +693,6 @@ async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
701
693
  }
702
694
  }
703
695
 
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(pythonJsonDumpsCompatible(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
696
  function summarizeDiffDiagnosticsForContext(diagnostics) {
863
697
  if (!diagnostics) return null;
864
698
  const repos = Array.isArray(diagnostics.repo_summaries)
@@ -1922,7 +1756,7 @@ 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}`;
@@ -1930,7 +1764,6 @@ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mod
1930
1764
  "Content-Type": "application/json",
1931
1765
  Authorization: `Bearer ${key}`,
1932
1766
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1933
- ...writeApprovalHeaders(writeApproval),
1934
1767
  };
1935
1768
  if (idempotencyKey) {
1936
1769
  headers["X-Idempotency-Key"] = String(idempotencyKey).trim();
@@ -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(
@@ -2039,7 +1872,6 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
2039
1872
  Authorization: `Bearer ${key}`,
2040
1873
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2041
1874
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2042
- ...writeApprovalHeaders(writeApproval),
2043
1875
  },
2044
1876
  body: JSON.stringify(payload || {}),
2045
1877
  },
@@ -2047,7 +1879,7 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
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(
@@ -2069,7 +1901,6 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
2069
1901
  Authorization: `Bearer ${key}`,
2070
1902
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2071
1903
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2072
- ...writeApprovalHeaders(writeApproval),
2073
1904
  },
2074
1905
  body: JSON.stringify(payload || {}),
2075
1906
  },
@@ -2077,7 +1908,7 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
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(
@@ -2099,7 +1930,6 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
2099
1930
  Authorization: `Bearer ${key}`,
2100
1931
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2101
1932
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2102
- ...writeApprovalHeaders(writeApproval),
2103
1933
  },
2104
1934
  body: JSON.stringify(payload || {}),
2105
1935
  },
@@ -2107,7 +1937,7 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
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(
@@ -2129,7 +1959,6 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
2129
1959
  Authorization: `Bearer ${key}`,
2130
1960
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2131
1961
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2132
- ...writeApprovalHeaders(writeApproval),
2133
1962
  },
2134
1963
  body: JSON.stringify(payload || {}),
2135
1964
  },
@@ -2137,7 +1966,7 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
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(
@@ -2159,7 +1988,6 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
2159
1988
  Authorization: `Bearer ${key}`,
2160
1989
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2161
1990
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2162
- ...writeApprovalHeaders(writeApproval),
2163
1991
  },
2164
1992
  body: JSON.stringify(payload || {}),
2165
1993
  },
@@ -2167,7 +1995,7 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
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;
@@ -2176,7 +2004,7 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
2176
2004
  return body.data || {};
2177
2005
  }
2178
2006
 
2179
- async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId, writeApproval }) {
2007
+ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId }) {
2180
2008
  const fetchFn = await getFetch();
2181
2009
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/comments`;
2182
2010
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2189,7 +2017,6 @@ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payloa
2189
2017
  Authorization: `Bearer ${key}`,
2190
2018
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2191
2019
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2192
- ...writeApprovalHeaders(writeApproval),
2193
2020
  },
2194
2021
  body: JSON.stringify(payload || {}),
2195
2022
  },
@@ -2197,7 +2024,7 @@ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payloa
2197
2024
  );
2198
2025
 
2199
2026
  if (!resp.ok || body.status !== "success") {
2200
- const msg = formatProjectApiErrorMessage(body, `Feedback comment failed (${resp.status})`);
2027
+ const msg = body?.message || `Feedback comment failed (${resp.status})`;
2201
2028
  const err = new Error(msg);
2202
2029
  err.status = resp.status;
2203
2030
  err.data = body?.data;
@@ -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(
@@ -2316,7 +2143,6 @@ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payl
2316
2143
  Authorization: `Bearer ${key}`,
2317
2144
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2318
2145
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2319
- ...writeApprovalHeaders(writeApproval),
2320
2146
  },
2321
2147
  body: JSON.stringify(payload),
2322
2148
  },
@@ -2324,16 +2150,15 @@ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payl
2324
2150
  );
2325
2151
 
2326
2152
  if (!resp.ok || body.status !== "success") {
2327
- const msg = formatProjectApiErrorMessage(body, `Mission suggestions request failed (${resp.status})`);
2153
+ const msg = body?.message || `Mission suggestions request failed (${resp.status})`;
2328
2154
  const err = new Error(msg);
2329
2155
  err.status = resp.status;
2330
- err.data = body?.data;
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(
@@ -2346,7 +2171,6 @@ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotenc
2346
2171
  Authorization: `Bearer ${key}`,
2347
2172
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2348
2173
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2349
- ...writeApprovalHeaders(writeApproval),
2350
2174
  },
2351
2175
  body: JSON.stringify(payload),
2352
2176
  },
@@ -2354,16 +2178,15 @@ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotenc
2354
2178
  );
2355
2179
 
2356
2180
  if (!resp.ok || body.status !== "success") {
2357
- const msg = formatProjectApiErrorMessage(body, `Run QAQC request failed (${resp.status})`);
2181
+ const msg = body?.message || `Run QAQC request failed (${resp.status})`;
2358
2182
  const err = new Error(msg);
2359
2183
  err.status = resp.status;
2360
- err.data = body?.data;
2361
2184
  throw err;
2362
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(
@@ -2376,7 +2199,6 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
2376
2199
  Authorization: `Bearer ${key}`,
2377
2200
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2378
2201
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2379
- ...writeApprovalHeaders(writeApproval),
2380
2202
  },
2381
2203
  body: JSON.stringify(payload),
2382
2204
  },
@@ -2384,16 +2206,15 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
2384
2206
  );
2385
2207
 
2386
2208
  if (!resp.ok || body.status !== "success") {
2387
- const msg = formatProjectApiErrorMessage(body, `Mission status update request failed (${resp.status})`);
2209
+ const msg = body?.message || `Mission status update request failed (${resp.status})`;
2388
2210
  const err = new Error(msg);
2389
2211
  err.status = resp.status;
2390
- err.data = body?.data;
2391
2212
  throw err;
2392
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(
@@ -2406,7 +2227,6 @@ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, p
2406
2227
  Authorization: `Bearer ${key}`,
2407
2228
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2408
2229
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2409
- ...writeApprovalHeaders(writeApproval),
2410
2230
  },
2411
2231
  body: JSON.stringify(payload),
2412
2232
  },
@@ -2414,10 +2234,9 @@ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, p
2414
2234
  );
2415
2235
 
2416
2236
  if (!resp.ok || body.status !== "success") {
2417
- const msg = formatProjectApiErrorMessage(body, `Mission archive request failed (${resp.status})`);
2237
+ const msg = body?.message || `Mission archive request failed (${resp.status})`;
2418
2238
  const err = new Error(msg);
2419
2239
  err.status = resp.status;
2420
- err.data = body?.data;
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
 
@@ -2551,12 +2375,6 @@ async function runRunQaqc(args) {
2551
2375
  const apiBase = resolveApiBase(args);
2552
2376
  const shouldWait = Boolean(args.wait || args.sync);
2553
2377
  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
2378
 
2561
2379
  let data;
2562
2380
  try {
@@ -2567,7 +2385,6 @@ async function runRunQaqc(args) {
2567
2385
  payload,
2568
2386
  idempotencyKey,
2569
2387
  clientSessionId,
2570
- writeApproval,
2571
2388
  });
2572
2389
  } catch (err) {
2573
2390
  if (err?.name === "AbortError") {
@@ -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
 
@@ -2711,12 +2533,6 @@ async function runMissionStatus(args) {
2711
2533
 
2712
2534
  const timeoutMs = resolveTimeoutMs(args);
2713
2535
  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
2536
 
2721
2537
  let data;
2722
2538
  try {
@@ -2727,7 +2543,6 @@ async function runMissionStatus(args) {
2727
2543
  payload,
2728
2544
  idempotencyKey,
2729
2545
  clientSessionId,
2730
- writeApproval,
2731
2546
  });
2732
2547
  } catch (err) {
2733
2548
  if (err?.name === "AbortError") {
@@ -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({
@@ -2825,12 +2645,6 @@ async function runMissionArchiveCommand(args) {
2825
2645
  const timeoutMs = resolveTimeoutMs(args);
2826
2646
  const apiBase = resolveApiBase(args);
2827
2647
  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
2648
 
2835
2649
  let data;
2836
2650
  try {
@@ -2842,7 +2656,6 @@ async function runMissionArchiveCommand(args) {
2842
2656
  payload,
2843
2657
  idempotencyKey,
2844
2658
  clientSessionId,
2845
- writeApproval,
2846
2659
  });
2847
2660
  } catch (err) {
2848
2661
  if (err?.name === "AbortError") {
@@ -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,18 +2747,18 @@ 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({
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, {
2754
+ artifactKind: "markdown",
2755
+ targets: ["project"],
2756
+ });
2757
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2941
2758
  args,
2942
2759
  operation: "update_team",
2943
2760
  payload,
2944
2761
  });
2945
- const writeApproval = requireWriteApproval(args, payload, {
2946
- operation: "update_team",
2947
- artifactKind: "markdown",
2948
- });
2949
2762
 
2950
2763
  let data;
2951
2764
  try {
@@ -2957,7 +2770,6 @@ async function runUpdateTeam(args) {
2957
2770
  endpoint: "/project-assistant/project-comment",
2958
2771
  idempotencyKey,
2959
2772
  clientSessionId,
2960
- writeApproval,
2961
2773
  });
2962
2774
  } catch (err) {
2963
2775
  if (err?.name === "AbortError") {
@@ -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,18 +2820,18 @@ 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({
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"],
2829
+ });
2830
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
3015
2831
  args,
3016
2832
  operation: "update_owner",
3017
2833
  payload,
3018
2834
  });
3019
- const writeApproval = requireWriteApproval(args, payload, {
3020
- operation: "update_owner",
3021
- artifactKind: "markdown",
3022
- });
3023
2835
 
3024
2836
  let data;
3025
2837
  try {
@@ -3031,7 +2843,6 @@ async function runUpdateOwner(args) {
3031
2843
  endpoint: "/project-assistant/update-owner",
3032
2844
  idempotencyKey,
3033
2845
  clientSessionId,
3034
- writeApproval,
3035
2846
  });
3036
2847
  } catch (err) {
3037
2848
  if (err?.name === "AbortError") {
@@ -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,18 +2902,18 @@ 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({
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, {
2909
+ artifactKind: "markdown",
2910
+ targets: ["project"],
2911
+ });
2912
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
3098
2913
  args,
3099
2914
  operation: "update_client",
3100
2915
  payload,
3101
2916
  });
3102
- const writeApproval = requireWriteApproval(args, payload, {
3103
- operation: "update_client",
3104
- artifactKind: "markdown",
3105
- });
3106
2917
 
3107
2918
  let data;
3108
2919
  try {
@@ -3114,7 +2925,6 @@ async function runUpdateClient(args) {
3114
2925
  endpoint: "/project-assistant/client-update-drafts",
3115
2926
  idempotencyKey,
3116
2927
  clientSessionId,
3117
- writeApproval,
3118
2928
  });
3119
2929
  } catch (err) {
3120
2930
  if (err?.name === "AbortError") {
@@ -3210,7 +3020,7 @@ function stringifyYaml(value) {
3210
3020
  function stableJsonStringify(value) {
3211
3021
  if (Array.isArray(value)) {
3212
3022
  return `[${value.map((item) => stableJsonStringify(item)).join(",")}]`;
3213
- }
3023
+ }
3214
3024
  if (value && typeof value === "object") {
3215
3025
  const entries = Object.keys(value)
3216
3026
  .sort()
@@ -3220,38 +3030,125 @@ function stableJsonStringify(value) {
3220
3030
  return JSON.stringify(value);
3221
3031
  }
3222
3032
 
3223
- function jsonStringifyEnsureAscii(value) {
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) {
3224
3050
  return JSON.stringify(String(value)).replace(/[^\x00-\x7F]/g, (char) => {
3225
3051
  const code = char.charCodeAt(0);
3226
3052
  return `\\u${code.toString(16).padStart(4, "0")}`;
3227
3053
  });
3228
3054
  }
3229
3055
 
3230
- function pythonJsonDumpsCompatible(value) {
3056
+ function canonicalizeWritePayload(value) {
3231
3057
  if (Array.isArray(value)) {
3232
- return `[${value.map((item) => pythonJsonDumpsCompatible(item)).join(", ")}]`;
3058
+ return value.map((item) => canonicalizeWritePayload(item));
3233
3059
  }
3234
3060
  if (value && typeof value === "object") {
3235
- const entries = Object.keys(value)
3236
- .sort()
3237
- .map((key) => `${jsonStringifyEnsureAscii(key)}: ${pythonJsonDumpsCompatible(value[key])}`);
3238
- return `{${entries.join(", ")}}`;
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);
3239
3099
  }
3240
- if (value === null) return "null";
3241
- if (typeof value === "boolean") return value ? "true" : "false";
3242
- if (typeof value === "number") {
3243
- if (Number.isNaN(value)) return "NaN";
3244
- if (value === Infinity) return "Infinity";
3245
- if (value === -Infinity) return "-Infinity";
3246
- return JSON.stringify(value);
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);
3247
3104
  }
3248
- return jsonStringifyEnsureAscii(value);
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);
3249
3146
  }
3250
3147
 
3251
3148
  function parseYaml(text) {
3252
- const raw = String(text || "").trim();
3253
- if (!raw) return null;
3254
- if (raw.startsWith("{") || raw.startsWith("[")) {
3149
+ const raw = String(text || "").trim();
3150
+ if (!raw) return null;
3151
+ if (raw.startsWith("{") || raw.startsWith("[")) {
3255
3152
  return JSON.parse(raw);
3256
3153
  }
3257
3154
  return parseSimpleYaml(raw);
@@ -4514,7 +4411,7 @@ async function fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScop
4514
4411
  return body.data || {};
4515
4412
  }
4516
4413
 
4517
- async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4414
+ async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4518
4415
  return postSuggestionsMutation({
4519
4416
  apiBase,
4520
4417
  key,
@@ -4523,11 +4420,10 @@ async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idem
4523
4420
  payload,
4524
4421
  idempotencyKey,
4525
4422
  clientSessionId,
4526
- writeApproval,
4527
4423
  });
4528
4424
  }
4529
4425
 
4530
- async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4426
+ async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4531
4427
  return postSuggestionsMutation({
4532
4428
  apiBase,
4533
4429
  key,
@@ -4536,11 +4432,10 @@ async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idem
4536
4432
  payload,
4537
4433
  idempotencyKey,
4538
4434
  clientSessionId,
4539
- writeApproval,
4540
4435
  });
4541
4436
  }
4542
4437
 
4543
- async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4438
+ async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4544
4439
  return postSuggestionsMutation({
4545
4440
  apiBase,
4546
4441
  key,
@@ -4549,7 +4444,6 @@ async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idem
4549
4444
  payload,
4550
4445
  idempotencyKey,
4551
4446
  clientSessionId,
4552
- writeApproval,
4553
4447
  });
4554
4448
  }
4555
4449
 
@@ -4658,20 +4552,19 @@ async function runCreatePrd(args) {
4658
4552
 
4659
4553
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
4660
4554
 
4661
- if (payloads.length === 1) {
4662
- let data;
4663
- const payload = payloads[0];
4664
- if (clientSessionId) payload.client_session_id = clientSessionId;
4665
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
4666
- args,
4667
- operation: "create_prd",
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
+ });
4563
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
4564
+ args,
4565
+ operation: "create_prd",
4668
4566
  payload,
4669
4567
  });
4670
- const writeApproval = requireWriteApproval(args, payload, {
4671
- operation: "create_prd",
4672
- artifactKind: "markdown",
4673
- targets: prdApprovalTargets(payload),
4674
- });
4675
4568
  try {
4676
4569
  data = await postSuggestionsMutation({
4677
4570
  apiBase,
@@ -4681,7 +4574,6 @@ async function runCreatePrd(args) {
4681
4574
  endpoint: "/project-assistant/create-prd",
4682
4575
  idempotencyKey,
4683
4576
  clientSessionId,
4684
- writeApproval,
4685
4577
  });
4686
4578
  } catch (err) {
4687
4579
  if (err?.name === "AbortError") {
@@ -4723,22 +4615,20 @@ async function runCreatePrd(args) {
4723
4615
  failed_count: 0,
4724
4616
  items: [],
4725
4617
  };
4726
-
4727
- try {
4728
- const payload = { items: payloads };
4729
- if (clientSessionId) payload.client_session_id = clientSessionId;
4730
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
4731
- args,
4732
- operation: "create_prds",
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
+ });
4627
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
4628
+ args,
4629
+ operation: "create_prds",
4733
4630
  payload,
4734
4631
  });
4735
- const prdTargets = prdApprovalTargets(payload);
4736
- const writeApproval = requireWriteApproval(args, payload, {
4737
- operation: "create_prds",
4738
- artifactKind: "markdown",
4739
- targets: prdTargets,
4740
- batchCount: prdTargets.length,
4741
- });
4742
4632
  const data = await postSuggestionsMutation({
4743
4633
  apiBase,
4744
4634
  key,
@@ -4747,7 +4637,6 @@ async function runCreatePrd(args) {
4747
4637
  endpoint: "/project-assistant/create-prds",
4748
4638
  idempotencyKey,
4749
4639
  clientSessionId,
4750
- writeApproval,
4751
4640
  });
4752
4641
  aggregated.project_id = data.project_id || null;
4753
4642
  aggregated.created_count = Number(data.created_count || 0);
@@ -5304,7 +5193,8 @@ async function runFeedbackValidateOrApply(args, mode) {
5304
5193
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5305
5194
  process.exit(1);
5306
5195
  }
5307
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5196
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5197
+ let payload = artifactPayload;
5308
5198
  const feedbackId = firstNonEmptyString(args["feedback-id"], args.feedbackId, args.feedback_id, payload.feedback_id);
5309
5199
  if (!feedbackId) {
5310
5200
  console.error("Feedback refinement artifact is missing feedback_id.");
@@ -5314,26 +5204,25 @@ async function runFeedbackValidateOrApply(args, mode) {
5314
5204
 
5315
5205
  const timeoutMs = resolveTimeoutMs(args);
5316
5206
  const apiBase = resolveApiBase(args);
5317
- const clientSessionId = firstNonEmptyString(
5207
+ const clientSessionId = firstNonEmptyString(
5318
5208
  args["client-session-id"],
5319
5209
  args.clientSessionId,
5320
5210
  args.client_session_id,
5321
5211
  payload.client_session_id
5322
- );
5323
- const idempotencyKey = mode === "apply"
5212
+ );
5213
+ if (mode === "apply") {
5214
+ payload = withWriteApproval(args, payload, {
5215
+ artifactKind: "yaml",
5216
+ targets: [feedbackId],
5217
+ });
5218
+ }
5219
+ const idempotencyKey = mode === "apply"
5324
5220
  ? resolveProjectMutationIdempotencyKey({
5325
5221
  args,
5326
5222
  operation: `feedback_refinement_apply:${feedbackId}`,
5327
5223
  payload,
5328
5224
  })
5329
5225
  : null;
5330
- const writeApproval = mode === "apply"
5331
- ? requireWriteApproval(args, payload, {
5332
- operation: `feedback_refinement_apply:${feedbackId}`,
5333
- artifactKind: "yaml",
5334
- targets: [feedbackId],
5335
- })
5336
- : null;
5337
5226
 
5338
5227
  let data;
5339
5228
  try {
@@ -5346,7 +5235,6 @@ async function runFeedbackValidateOrApply(args, mode) {
5346
5235
  payload,
5347
5236
  idempotencyKey,
5348
5237
  clientSessionId,
5349
- writeApproval,
5350
5238
  });
5351
5239
  } catch (err) {
5352
5240
  if (args.json) {
@@ -5458,7 +5346,8 @@ async function runFeedbackSubmit(args) {
5458
5346
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5459
5347
  process.exit(1);
5460
5348
  }
5461
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5349
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5350
+ let payload = artifactPayload;
5462
5351
  const feedbackId = firstNonEmptyString(args["feedback-id"], args.feedbackId, args.feedback_id, payload.feedback_id);
5463
5352
  if (!feedbackId) {
5464
5353
  console.error("Feedback refinement artifact is missing feedback_id.");
@@ -5466,22 +5355,21 @@ async function runFeedbackSubmit(args) {
5466
5355
  }
5467
5356
  const timeoutMs = resolveTimeoutMs(args);
5468
5357
  const apiBase = resolveApiBase(args);
5469
- const clientSessionId = firstNonEmptyString(
5358
+ const clientSessionId = firstNonEmptyString(
5470
5359
  args["client-session-id"],
5471
5360
  args.clientSessionId,
5472
5361
  args.client_session_id,
5473
5362
  payload.client_session_id
5474
- );
5475
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5363
+ );
5364
+ payload = withWriteApproval(args, payload, {
5365
+ artifactKind: "yaml",
5366
+ targets: [feedbackId],
5367
+ });
5368
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5476
5369
  args,
5477
5370
  operation: `feedback_refinement_request_submit:${feedbackId}`,
5478
5371
  payload,
5479
5372
  });
5480
- const writeApproval = requireWriteApproval(args, payload, {
5481
- operation: `feedback_refinement_request_submit:${feedbackId}`,
5482
- artifactKind: "yaml",
5483
- targets: [feedbackId],
5484
- });
5485
5373
 
5486
5374
  let data;
5487
5375
  try {
@@ -5493,7 +5381,6 @@ async function runFeedbackSubmit(args) {
5493
5381
  payload,
5494
5382
  idempotencyKey,
5495
5383
  clientSessionId,
5496
- writeApproval,
5497
5384
  });
5498
5385
  } catch (err) {
5499
5386
  if (args.json) {
@@ -5535,25 +5422,25 @@ async function runFeedbackRevise(args) {
5535
5422
  console.error("Missing --request-id.");
5536
5423
  process.exit(1);
5537
5424
  }
5538
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5425
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5426
+ let payload = artifactPayload;
5539
5427
  const timeoutMs = resolveTimeoutMs(args);
5540
5428
  const apiBase = resolveApiBase(args);
5541
- const clientSessionId = firstNonEmptyString(
5429
+ const clientSessionId = firstNonEmptyString(
5542
5430
  args["client-session-id"],
5543
5431
  args.clientSessionId,
5544
5432
  args.client_session_id,
5545
5433
  payload.client_session_id
5546
- );
5547
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5434
+ );
5435
+ payload = withWriteApproval(args, payload, {
5436
+ artifactKind: "yaml",
5437
+ targets: [requestId],
5438
+ });
5439
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5548
5440
  args,
5549
5441
  operation: `feedback_refinement_request_revise:${requestId}`,
5550
5442
  payload,
5551
5443
  });
5552
- const writeApproval = requireWriteApproval(args, payload, {
5553
- operation: `feedback_refinement_request_revise:${requestId}`,
5554
- artifactKind: "yaml",
5555
- targets: [requestId],
5556
- });
5557
5444
 
5558
5445
  let data;
5559
5446
  try {
@@ -5566,7 +5453,6 @@ async function runFeedbackRevise(args) {
5566
5453
  payload,
5567
5454
  idempotencyKey,
5568
5455
  clientSessionId,
5569
- writeApproval,
5570
5456
  });
5571
5457
  } catch (err) {
5572
5458
  if (args.json) {
@@ -5684,13 +5570,18 @@ async function runFeedbackReviewDecision(args) {
5684
5570
  process.exit(1);
5685
5571
  }
5686
5572
  const reason = firstNonEmptyString(args.reason, args["review-note"], args.reviewNote, args.review_note);
5687
- const payload = {
5688
- ...(isPlainObject(filePayload) && !Array.isArray(filePayload) ? filePayload : {}),
5689
- items,
5690
- action,
5691
- reason: reason || undefined,
5692
- };
5693
- const timeoutMs = resolveTimeoutMs(args);
5573
+ let payload = {
5574
+ ...(isPlainObject(filePayload) && !Array.isArray(filePayload) ? filePayload : {}),
5575
+ items,
5576
+ action,
5577
+ reason: reason || undefined,
5578
+ };
5579
+ payload = withWriteApproval(args, payload, {
5580
+ artifactKind: "yaml",
5581
+ targets: items.map((item) => item.request_id || item.id).filter(Boolean),
5582
+ batchCount: items.length,
5583
+ });
5584
+ const timeoutMs = resolveTimeoutMs(args);
5694
5585
  const apiBase = resolveApiBase(args);
5695
5586
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5696
5587
  const idempotencyKey = resolveProjectMutationIdempotencyKey({
@@ -5698,15 +5589,6 @@ async function runFeedbackReviewDecision(args) {
5698
5589
  operation: `feedback_review_batch:${action}:${items.map((item) => item.request_id || item.id || "").join(",")}`,
5699
5590
  payload,
5700
5591
  });
5701
- const requestTargets = items
5702
- .map((item, index) => firstNonEmptyString(item.request_id, item.id, item._id) || `index:${index}`)
5703
- .filter(Boolean);
5704
- const writeApproval = requireWriteApproval(args, payload, {
5705
- operation: `feedback_review_batch:${action}`,
5706
- artifactKind: "yaml",
5707
- targets: requestTargets,
5708
- batchCount: items.length,
5709
- });
5710
5592
 
5711
5593
  let data;
5712
5594
  try {
@@ -5717,7 +5599,6 @@ async function runFeedbackReviewDecision(args) {
5717
5599
  payload,
5718
5600
  idempotencyKey,
5719
5601
  clientSessionId,
5720
- writeApproval,
5721
5602
  });
5722
5603
  } catch (err) {
5723
5604
  if (args.json) {
@@ -5745,8 +5626,8 @@ async function runFeedbackReviewDecision(args) {
5745
5626
  process.exit(1);
5746
5627
  }
5747
5628
 
5748
- let payload;
5749
- try {
5629
+ let payload;
5630
+ try {
5750
5631
  payload = filePayload && !Array.isArray(filePayload?.items)
5751
5632
  ? buildFeedbackReviewDecisionPayload({ ...args, file: "" }, action)
5752
5633
  : buildFeedbackReviewDecisionPayload(args, action);
@@ -5757,8 +5638,12 @@ async function runFeedbackReviewDecision(args) {
5757
5638
  console.error(err?.message || err);
5758
5639
  process.exit(1);
5759
5640
  }
5760
- const endpointAction = action === "request_changes" ? "request-changes" : action === "cancel" ? "cancel" : "review";
5761
- const timeoutMs = resolveTimeoutMs(args);
5641
+ const endpointAction = action === "request_changes" ? "request-changes" : action === "cancel" ? "cancel" : "review";
5642
+ payload = withWriteApproval(args, payload, {
5643
+ artifactKind: "yaml",
5644
+ targets: [requestId],
5645
+ });
5646
+ const timeoutMs = resolveTimeoutMs(args);
5762
5647
  const apiBase = resolveApiBase(args);
5763
5648
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5764
5649
  const operationAction = action === "request_changes" ? "request_changes" : action;
@@ -5767,11 +5652,6 @@ async function runFeedbackReviewDecision(args) {
5767
5652
  operation: `feedback_review_request_${operationAction}:${requestId}`,
5768
5653
  payload,
5769
5654
  });
5770
- const writeApproval = requireWriteApproval(args, payload, {
5771
- operation: `feedback_review_request_${operationAction}:${requestId}`,
5772
- artifactKind: "yaml",
5773
- targets: [requestId],
5774
- });
5775
5655
 
5776
5656
  let data;
5777
5657
  try {
@@ -5784,7 +5664,6 @@ async function runFeedbackReviewDecision(args) {
5784
5664
  payload,
5785
5665
  idempotencyKey,
5786
5666
  clientSessionId,
5787
- writeApproval,
5788
5667
  });
5789
5668
  } catch (err) {
5790
5669
  if (args.json) {
@@ -5823,32 +5702,31 @@ async function runFeedbackMove(args) {
5823
5702
  process.exit(1);
5824
5703
  }
5825
5704
  const fromState = firstNonEmptyString(args["from-state"], args.fromState, args.from_state) || undefined;
5826
- const payload = {
5827
- to_state: toState,
5828
- reason: firstNonEmptyString(args.reason) || undefined,
5829
- };
5705
+ let payload = {
5706
+ to_state: toState,
5707
+ reason: firstNonEmptyString(args.reason) || undefined,
5708
+ };
5830
5709
  const timeoutMs = resolveTimeoutMs(args);
5831
5710
  const apiBase = resolveApiBase(args);
5832
5711
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5833
5712
  const isBatch = feedbackIds.length > 1 || Boolean(firstNonEmptyString(args["feedback-ids"], args.feedbackIds, args.feedback_ids));
5834
- if (isBatch) {
5835
- payload.feedback_ids = feedbackIds;
5713
+ if (isBatch) {
5714
+ payload.feedback_ids = feedbackIds;
5836
5715
  if (fromState) {
5837
5716
  payload.from_states = Object.fromEntries(feedbackIds.map((id) => [id, fromState]));
5838
5717
  }
5839
5718
  const batchId = firstNonEmptyString(args["batch-id"], args.batchId, args.batch_id);
5840
- if (batchId) payload.batch_id = batchId;
5841
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5719
+ if (batchId) payload.batch_id = batchId;
5720
+ payload = withWriteApproval(args, payload, {
5721
+ artifactKind: "yaml",
5722
+ targets: feedbackIds,
5723
+ batchCount: feedbackIds.length,
5724
+ });
5725
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5842
5726
  args,
5843
5727
  operation: `feedback_board_batch_move:${toState}:${feedbackIds.join(",")}`,
5844
5728
  payload,
5845
5729
  });
5846
- const writeApproval = requireWriteApproval(args, payload, {
5847
- operation: `feedback_board_batch_move:${toState}`,
5848
- artifactKind: "yaml",
5849
- targets: feedbackIds,
5850
- batchCount: feedbackIds.length,
5851
- });
5852
5730
  let data;
5853
5731
  try {
5854
5732
  data = await postFeedbackBatchBoardMutation({
@@ -5858,7 +5736,6 @@ async function runFeedbackMove(args) {
5858
5736
  payload,
5859
5737
  idempotencyKey,
5860
5738
  clientSessionId,
5861
- writeApproval,
5862
5739
  });
5863
5740
  } catch (err) {
5864
5741
  if (args.json) {
@@ -5879,18 +5756,17 @@ async function runFeedbackMove(args) {
5879
5756
  if (postSync?.error) console.error(`Feedback sync warning: ${postSync.error}`);
5880
5757
  return;
5881
5758
  }
5882
-
5883
- payload.from_state = fromState;
5884
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5759
+
5760
+ payload.from_state = fromState;
5761
+ payload = withWriteApproval(args, payload, {
5762
+ artifactKind: "yaml",
5763
+ targets: [feedbackId],
5764
+ });
5765
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5885
5766
  args,
5886
5767
  operation: `feedback_board_move:${feedbackId}`,
5887
5768
  payload,
5888
5769
  });
5889
- const writeApproval = requireWriteApproval(args, payload, {
5890
- operation: `feedback_board_move:${feedbackId}`,
5891
- artifactKind: "yaml",
5892
- targets: [feedbackId],
5893
- });
5894
5770
 
5895
5771
  let data;
5896
5772
  try {
@@ -5903,7 +5779,6 @@ async function runFeedbackMove(args) {
5903
5779
  payload,
5904
5780
  idempotencyKey,
5905
5781
  clientSessionId,
5906
- writeApproval,
5907
5782
  });
5908
5783
  } catch (err) {
5909
5784
  if (args.json) {
@@ -5944,12 +5819,16 @@ async function runFeedbackComment(args) {
5944
5819
  process.exit(1);
5945
5820
  }
5946
5821
 
5947
- const payload = { content };
5948
- const timeoutMs = resolveTimeoutMs(args);
5949
- const apiBase = resolveApiBase(args);
5950
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5951
- if (clientSessionId) payload.client_session_id = clientSessionId;
5952
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5822
+ let payload = { content };
5823
+ const timeoutMs = resolveTimeoutMs(args);
5824
+ const apiBase = resolveApiBase(args);
5825
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5826
+ if (clientSessionId) payload.client_session_id = clientSessionId;
5827
+ payload = withWriteApproval(args, payload, {
5828
+ artifactKind: "markdown",
5829
+ targets: [feedbackId],
5830
+ });
5831
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5953
5832
  args,
5954
5833
  operation: `feedback_comment_create:${feedbackId}`,
5955
5834
  payload,
@@ -5960,12 +5839,6 @@ async function runFeedbackComment(args) {
5960
5839
  return;
5961
5840
  }
5962
5841
 
5963
- const writeApproval = requireWriteApproval(args, payload, {
5964
- operation: `feedback_comment_create:${feedbackId}`,
5965
- artifactKind: "markdown",
5966
- targets: [feedbackId],
5967
- });
5968
-
5969
5842
  let data;
5970
5843
  try {
5971
5844
  data = await postFeedbackComment({
@@ -5976,7 +5849,6 @@ async function runFeedbackComment(args) {
5976
5849
  payload,
5977
5850
  idempotencyKey,
5978
5851
  clientSessionId,
5979
- writeApproval,
5980
5852
  });
5981
5853
  } catch (err) {
5982
5854
  if (args.json) {
@@ -6025,27 +5897,20 @@ async function runFeedbackUndo(args) {
6025
5897
  console.error("Missing --event-id.");
6026
5898
  process.exit(1);
6027
5899
  }
6028
- const payload = { reason: firstNonEmptyString(args.reason) || undefined };
6029
- const timeoutMs = resolveTimeoutMs(args);
6030
- const apiBase = resolveApiBase(args);
6031
- const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
6032
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
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, {
5905
+ artifactKind: "yaml",
5906
+ targets: [feedbackId, eventId],
5907
+ });
5908
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
6033
5909
  args,
6034
5910
  operation: `feedback_event_undo:${feedbackId}:${eventId}`,
6035
5911
  payload,
6036
5912
  });
6037
5913
 
6038
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6039
- console.log(JSON.stringify(payload, null, 2));
6040
- return;
6041
- }
6042
-
6043
- const writeApproval = requireWriteApproval(args, payload, {
6044
- operation: `feedback_event_undo:${feedbackId}:${eventId}`,
6045
- artifactKind: "yaml",
6046
- targets: [feedbackId, eventId],
6047
- });
6048
-
6049
5914
  let data;
6050
5915
  try {
6051
5916
  data = await postFeedbackBoardMutation({
@@ -6057,7 +5922,6 @@ async function runFeedbackUndo(args) {
6057
5922
  payload,
6058
5923
  idempotencyKey,
6059
5924
  clientSessionId,
6060
- writeApproval,
6061
5925
  });
6062
5926
  } catch (err) {
6063
5927
  if (args.json) {
@@ -6343,14 +6207,20 @@ async function buildSuggestionsMutationContext(args, mode) {
6343
6207
  if (clientSessionId && !payload.client_session_id) {
6344
6208
  payload.client_session_id = clientSessionId;
6345
6209
  }
6346
- if (mode === "create") {
6347
- validateSuggestionsCreatePayload(payload);
6348
- } else if (mode === "revise") {
6349
- validateSuggestionsRevisePayload(payload);
6350
- }
6351
- const idempotencyKey = resolveSuggestionsIdempotencyKey({
6352
- args,
6353
- 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,
6354
6224
  actorScope,
6355
6225
  payload,
6356
6226
  });
@@ -6436,14 +6306,6 @@ async function runSuggestionsCreate(args) {
6436
6306
  return;
6437
6307
  }
6438
6308
 
6439
- const createTargets = suggestionApprovalTargets(context.payload);
6440
- const writeApproval = requireWriteApproval(args, context.payload, {
6441
- operation: "suggestions.create",
6442
- artifactKind: "yaml",
6443
- targets: createTargets,
6444
- batchCount: createTargets.length > 1 ? createTargets.length : null,
6445
- });
6446
-
6447
6309
  let data;
6448
6310
  try {
6449
6311
  data = await createProjectSuggestions({
@@ -6453,7 +6315,6 @@ async function runSuggestionsCreate(args) {
6453
6315
  payload: context.payload,
6454
6316
  idempotencyKey: context.idempotencyKey,
6455
6317
  clientSessionId: context.clientSessionId,
6456
- writeApproval,
6457
6318
  });
6458
6319
  } catch (err) {
6459
6320
  console.error("Suggestions create failed:", err?.message || err);
@@ -6530,14 +6391,6 @@ async function runSuggestionsRevise(args) {
6530
6391
  return;
6531
6392
  }
6532
6393
 
6533
- const reviseTargets = suggestionApprovalTargets(context.payload);
6534
- const writeApproval = requireWriteApproval(args, context.payload, {
6535
- operation: "suggestions.revise",
6536
- artifactKind: "yaml",
6537
- targets: reviseTargets,
6538
- batchCount: reviseTargets.length > 1 ? reviseTargets.length : null,
6539
- });
6540
-
6541
6394
  let data;
6542
6395
  try {
6543
6396
  data = await reviseProjectSuggestions({
@@ -6547,7 +6400,6 @@ async function runSuggestionsRevise(args) {
6547
6400
  payload: context.payload,
6548
6401
  idempotencyKey: context.idempotencyKey,
6549
6402
  clientSessionId: context.clientSessionId,
6550
- writeApproval,
6551
6403
  });
6552
6404
  } catch (err) {
6553
6405
  console.error("Suggestions revise failed:", err?.message || err);
@@ -6622,14 +6474,6 @@ async function runSuggestionsReview(args) {
6622
6474
  return;
6623
6475
  }
6624
6476
 
6625
- const reviewTargets = suggestionApprovalTargets(context.payload);
6626
- const writeApproval = requireWriteApproval(args, context.payload, {
6627
- operation: "suggestions.review",
6628
- artifactKind: "yaml",
6629
- targets: reviewTargets,
6630
- batchCount: reviewTargets.length > 1 ? reviewTargets.length : null,
6631
- });
6632
-
6633
6477
  let data;
6634
6478
  try {
6635
6479
  data = await reviewProjectSuggestions({
@@ -6639,7 +6483,6 @@ async function runSuggestionsReview(args) {
6639
6483
  payload: context.payload,
6640
6484
  idempotencyKey: context.idempotencyKey,
6641
6485
  clientSessionId: context.clientSessionId,
6642
- writeApproval,
6643
6486
  });
6644
6487
  } catch (err) {
6645
6488
  console.error("Suggestions review failed:", err?.message || err);