@mytegroupinc/myte-core 0.0.42 → 0.0.44

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
@@ -231,10 +231,16 @@ 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
- "",
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
+ "",
238
244
  "bootstrap contract:",
239
245
  " - Run from any workspace where you want local MyteCommandCenter data written",
240
246
  " - Writes MyteCommandCenter/data/project.yml plus phases, epics, stories, and missions locally",
@@ -395,8 +401,10 @@ function printHelp() {
395
401
  " --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
396
402
  " --wait Poll batch status until terminal completion for run-qaqc",
397
403
  " --sync After run-qaqc completes, refresh local QAQC file",
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",
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",
400
408
  " --print-context Print JSON payload and exit (no query call)",
401
409
  " --no-fetch Don't git fetch origin main/master before diff",
402
410
  "",
@@ -670,10 +678,10 @@ function normalizeApiBase(baseRaw) {
670
678
  return base.endsWith("/api") ? base : `${base}/api`;
671
679
  }
672
680
 
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;
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;
677
685
  try {
678
686
  const resp = await fetchFn(url, { ...options, signal: controller?.signal });
679
687
  const text = await resp.text();
@@ -689,13 +697,171 @@ async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
689
697
  }
690
698
  return { resp, body };
691
699
  } finally {
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)
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)
699
865
  ? diagnostics.repo_summaries
700
866
  : [];
701
867
  return {
@@ -1756,15 +1922,16 @@ async function fetchFeedbackHistory({ apiBase, key, timeoutMs, feedbackId }) {
1756
1922
  return body.data || {};
1757
1923
  }
1758
1924
 
1759
- async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mode, payload, idempotencyKey, clientSessionId }) {
1925
+ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mode, payload, idempotencyKey, clientSessionId, writeApproval }) {
1760
1926
  const fetchFn = await getFetch();
1761
1927
  const action = String(mode || "").trim();
1762
1928
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/refinement/${action}`;
1763
- const headers = {
1764
- "Content-Type": "application/json",
1765
- Authorization: `Bearer ${key}`,
1766
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1767
- };
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
+ };
1768
1935
  if (idempotencyKey) {
1769
1936
  headers["X-Idempotency-Key"] = String(idempotencyKey).trim();
1770
1937
  }
@@ -1780,7 +1947,7 @@ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mod
1780
1947
  );
1781
1948
 
1782
1949
  if (!resp.ok || body.status !== "success") {
1783
- const msg = body?.message || `Feedback refinement ${action} failed (${resp.status})`;
1950
+ const msg = formatProjectApiErrorMessage(body, `Feedback refinement ${action} failed (${resp.status})`);
1784
1951
  const err = new Error(msg);
1785
1952
  err.status = resp.status;
1786
1953
  err.data = body?.data;
@@ -1859,7 +2026,7 @@ async function fetchFeedbackEvents({ apiBase, key, timeoutMs, feedbackId, limit
1859
2026
  return body.data || {};
1860
2027
  }
1861
2028
 
1862
- async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId }) {
2029
+ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId, writeApproval }) {
1863
2030
  const fetchFn = await getFetch();
1864
2031
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/refinement/requests`;
1865
2032
  const { resp, body } = await fetchJsonWithTimeout(
@@ -1869,17 +2036,18 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
1869
2036
  method: "POST",
1870
2037
  headers: {
1871
2038
  "Content-Type": "application/json",
1872
- Authorization: `Bearer ${key}`,
1873
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1874
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1875
- },
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
+ },
1876
2044
  body: JSON.stringify(payload || {}),
1877
2045
  },
1878
2046
  timeoutMs
1879
2047
  );
1880
2048
 
1881
2049
  if (!resp.ok || body.status !== "success") {
1882
- const msg = body?.message || `Feedback review submission failed (${resp.status})`;
2050
+ const msg = formatProjectApiErrorMessage(body, `Feedback review submission failed (${resp.status})`);
1883
2051
  const err = new Error(msg);
1884
2052
  err.status = resp.status;
1885
2053
  err.data = body?.data;
@@ -1888,7 +2056,7 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
1888
2056
  return body.data || {};
1889
2057
  }
1890
2058
 
1891
- async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requestId, endpointAction, payload, idempotencyKey, clientSessionId }) {
2059
+ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requestId, endpointAction, payload, idempotencyKey, clientSessionId, writeApproval }) {
1892
2060
  const fetchFn = await getFetch();
1893
2061
  const url = `${apiBase}/project-assistant/feedback-review-requests/${encodeURIComponent(String(requestId || ""))}/${endpointAction}`;
1894
2062
  const { resp, body } = await fetchJsonWithTimeout(
@@ -1898,17 +2066,18 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
1898
2066
  method: "POST",
1899
2067
  headers: {
1900
2068
  "Content-Type": "application/json",
1901
- Authorization: `Bearer ${key}`,
1902
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1903
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1904
- },
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
+ },
1905
2074
  body: JSON.stringify(payload || {}),
1906
2075
  },
1907
2076
  timeoutMs
1908
2077
  );
1909
2078
 
1910
2079
  if (!resp.ok || body.status !== "success") {
1911
- const msg = body?.message || `Feedback review ${endpointAction} failed (${resp.status})`;
2080
+ const msg = formatProjectApiErrorMessage(body, `Feedback review ${endpointAction} failed (${resp.status})`);
1912
2081
  const err = new Error(msg);
1913
2082
  err.status = resp.status;
1914
2083
  err.data = body?.data;
@@ -1917,7 +2086,7 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
1917
2086
  return body.data || {};
1918
2087
  }
1919
2088
 
1920
- async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2089
+ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
1921
2090
  const fetchFn = await getFetch();
1922
2091
  const url = `${apiBase}/project-assistant/feedback-review-requests/batch-review`;
1923
2092
  const { resp, body } = await fetchJsonWithTimeout(
@@ -1927,17 +2096,18 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
1927
2096
  method: "POST",
1928
2097
  headers: {
1929
2098
  "Content-Type": "application/json",
1930
- Authorization: `Bearer ${key}`,
1931
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1932
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1933
- },
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
+ },
1934
2104
  body: JSON.stringify(payload || {}),
1935
2105
  },
1936
2106
  timeoutMs
1937
2107
  );
1938
2108
 
1939
2109
  if (!resp.ok || body.status !== "success") {
1940
- const msg = body?.message || `Feedback batch review failed (${resp.status})`;
2110
+ const msg = formatProjectApiErrorMessage(body, `Feedback batch review failed (${resp.status})`);
1941
2111
  const err = new Error(msg);
1942
2112
  err.status = resp.status;
1943
2113
  err.data = body?.data;
@@ -1946,7 +2116,7 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
1946
2116
  return body.data || {};
1947
2117
  }
1948
2118
 
1949
- async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId, endpoint, payload, idempotencyKey, clientSessionId }) {
2119
+ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
1950
2120
  const fetchFn = await getFetch();
1951
2121
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/${endpoint}`;
1952
2122
  const { resp, body } = await fetchJsonWithTimeout(
@@ -1956,17 +2126,18 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
1956
2126
  method: "POST",
1957
2127
  headers: {
1958
2128
  "Content-Type": "application/json",
1959
- Authorization: `Bearer ${key}`,
1960
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1961
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1962
- },
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
+ },
1963
2134
  body: JSON.stringify(payload || {}),
1964
2135
  },
1965
2136
  timeoutMs
1966
2137
  );
1967
2138
 
1968
2139
  if (!resp.ok || body.status !== "success") {
1969
- const msg = body?.message || `Feedback board mutation failed (${resp.status})`;
2140
+ const msg = formatProjectApiErrorMessage(body, `Feedback board mutation failed (${resp.status})`);
1970
2141
  const err = new Error(msg);
1971
2142
  err.status = resp.status;
1972
2143
  err.data = body?.data;
@@ -1975,7 +2146,7 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
1975
2146
  return body.data || {};
1976
2147
  }
1977
2148
 
1978
- async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2149
+ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
1979
2150
  const fetchFn = await getFetch();
1980
2151
  const url = `${apiBase}/project-assistant/feedback/batch-board-move`;
1981
2152
  const { resp, body } = await fetchJsonWithTimeout(
@@ -1985,17 +2156,18 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
1985
2156
  method: "POST",
1986
2157
  headers: {
1987
2158
  "Content-Type": "application/json",
1988
- Authorization: `Bearer ${key}`,
1989
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
1990
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1991
- },
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
+ },
1992
2164
  body: JSON.stringify(payload || {}),
1993
2165
  },
1994
2166
  timeoutMs
1995
2167
  );
1996
2168
 
1997
2169
  if (!resp.ok || body.status !== "success") {
1998
- const msg = body?.message || `Feedback batch board mutation failed (${resp.status})`;
2170
+ const msg = formatProjectApiErrorMessage(body, `Feedback batch board mutation failed (${resp.status})`);
1999
2171
  const err = new Error(msg);
2000
2172
  err.status = resp.status;
2001
2173
  err.data = body?.data;
@@ -2004,7 +2176,7 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
2004
2176
  return body.data || {};
2005
2177
  }
2006
2178
 
2007
- async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId }) {
2179
+ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId, writeApproval }) {
2008
2180
  const fetchFn = await getFetch();
2009
2181
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/comments`;
2010
2182
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2017,6 +2189,7 @@ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payloa
2017
2189
  Authorization: `Bearer ${key}`,
2018
2190
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2019
2191
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2192
+ ...writeApprovalHeaders(writeApproval),
2020
2193
  },
2021
2194
  body: JSON.stringify(payload || {}),
2022
2195
  },
@@ -2024,7 +2197,7 @@ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payloa
2024
2197
  );
2025
2198
 
2026
2199
  if (!resp.ok || body.status !== "success") {
2027
- const msg = body?.message || `Feedback comment failed (${resp.status})`;
2200
+ const msg = formatProjectApiErrorMessage(body, `Feedback comment failed (${resp.status})`);
2028
2201
  const err = new Error(msg);
2029
2202
  err.status = resp.status;
2030
2203
  err.data = body?.data;
@@ -2130,7 +2303,7 @@ function resolveProjectMutationIdempotencyKey({ args, operation, payload }) {
2130
2303
  return createHash("sha256").update(fingerprint).digest("hex");
2131
2304
  }
2132
2305
 
2133
- async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId }) {
2306
+ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
2134
2307
  const fetchFn = await getFetch();
2135
2308
  const url = `${apiBase}${endpoint}`;
2136
2309
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2140,25 +2313,27 @@ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payl
2140
2313
  method: "POST",
2141
2314
  headers: {
2142
2315
  "Content-Type": "application/json",
2143
- Authorization: `Bearer ${key}`,
2144
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2145
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2146
- },
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
+ },
2147
2321
  body: JSON.stringify(payload),
2148
2322
  },
2149
2323
  timeoutMs
2150
2324
  );
2151
2325
 
2152
2326
  if (!resp.ok || body.status !== "success") {
2153
- const msg = body?.message || `Mission suggestions request failed (${resp.status})`;
2154
- const err = new Error(msg);
2155
- err.status = resp.status;
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;
2156
2331
  throw err;
2157
2332
  }
2158
2333
  return body.data || {};
2159
2334
  }
2160
2335
 
2161
- async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2336
+ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
2162
2337
  const fetchFn = await getFetch();
2163
2338
  const url = `${apiBase}/project-assistant/run-qaqc`;
2164
2339
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2168,25 +2343,27 @@ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotenc
2168
2343
  method: "POST",
2169
2344
  headers: {
2170
2345
  "Content-Type": "application/json",
2171
- Authorization: `Bearer ${key}`,
2172
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2173
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2174
- },
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
+ },
2175
2351
  body: JSON.stringify(payload),
2176
2352
  },
2177
2353
  timeoutMs
2178
2354
  );
2179
2355
 
2180
2356
  if (!resp.ok || body.status !== "success") {
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
- }
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
+ }
2186
2363
  return body.data || {};
2187
2364
  }
2188
2365
 
2189
- async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2366
+ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
2190
2367
  const fetchFn = await getFetch();
2191
2368
  const url = `${apiBase}/project-assistant/mission-status-update`;
2192
2369
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2196,25 +2373,27 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
2196
2373
  method: "POST",
2197
2374
  headers: {
2198
2375
  "Content-Type": "application/json",
2199
- Authorization: `Bearer ${key}`,
2200
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2201
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2202
- },
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
+ },
2203
2381
  body: JSON.stringify(payload),
2204
2382
  },
2205
2383
  timeoutMs
2206
2384
  );
2207
2385
 
2208
2386
  if (!resp.ok || body.status !== "success") {
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
- }
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
+ }
2214
2393
  return body.data || {};
2215
2394
  }
2216
2395
 
2217
- async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId }) {
2396
+ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
2218
2397
  const fetchFn = await getFetch();
2219
2398
  const url = `${apiBase}${endpoint}`;
2220
2399
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2224,19 +2403,21 @@ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, p
2224
2403
  method: "POST",
2225
2404
  headers: {
2226
2405
  "Content-Type": "application/json",
2227
- Authorization: `Bearer ${key}`,
2228
- "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2229
- ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2230
- },
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
+ },
2231
2411
  body: JSON.stringify(payload),
2232
2412
  },
2233
2413
  timeoutMs
2234
2414
  );
2235
2415
 
2236
2416
  if (!resp.ok || body.status !== "success") {
2237
- const msg = body?.message || `Mission archive request failed (${resp.status})`;
2238
- const err = new Error(msg);
2239
- err.status = resp.status;
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;
2240
2421
  throw err;
2241
2422
  }
2242
2423
  return body.data || {};
@@ -2366,21 +2547,28 @@ async function runRunQaqc(args) {
2366
2547
  return;
2367
2548
  }
2368
2549
 
2369
- const timeoutMs = resolveTimeoutMs(args);
2370
- const apiBase = resolveApiBase(args);
2371
- const shouldWait = Boolean(args.wait || args.sync);
2372
- const shouldSync = Boolean(args.sync);
2373
-
2374
- let data;
2375
- try {
2376
- data = await createRunQaqcBatch({
2377
- apiBase,
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,
2378
2565
  key,
2379
2566
  timeoutMs,
2380
- payload,
2381
- idempotencyKey,
2382
- clientSessionId,
2383
- });
2567
+ payload,
2568
+ idempotencyKey,
2569
+ clientSessionId,
2570
+ writeApproval,
2571
+ });
2384
2572
  } catch (err) {
2385
2573
  if (err?.name === "AbortError") {
2386
2574
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2521,19 +2709,26 @@ async function runMissionStatus(args) {
2521
2709
  return;
2522
2710
  }
2523
2711
 
2524
- const timeoutMs = resolveTimeoutMs(args);
2525
- const apiBase = resolveApiBase(args);
2526
-
2527
- let data;
2528
- try {
2529
- data = await createMissionStatusUpdate({
2530
- apiBase,
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,
2531
2725
  key,
2532
2726
  timeoutMs,
2533
- payload,
2534
- idempotencyKey,
2535
- clientSessionId,
2536
- });
2727
+ payload,
2728
+ idempotencyKey,
2729
+ clientSessionId,
2730
+ writeApproval,
2731
+ });
2537
2732
  } catch (err) {
2538
2733
  if (err?.name === "AbortError") {
2539
2734
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2627,21 +2822,28 @@ async function runMissionArchiveCommand(args) {
2627
2822
  return;
2628
2823
  }
2629
2824
 
2630
- const timeoutMs = resolveTimeoutMs(args);
2631
- const apiBase = resolveApiBase(args);
2632
- const endpoint = "/project-assistant/mission-archive";
2633
-
2634
- let data;
2635
- try {
2636
- data = await createMissionArchiveUpdate({
2637
- apiBase,
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,
2638
2839
  key,
2639
2840
  timeoutMs,
2640
2841
  endpoint,
2641
- payload,
2642
- idempotencyKey,
2643
- clientSessionId,
2644
- });
2842
+ payload,
2843
+ idempotencyKey,
2844
+ clientSessionId,
2845
+ writeApproval,
2846
+ });
2645
2847
  } catch (err) {
2646
2848
  if (err?.name === "AbortError") {
2647
2849
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2735,23 +2937,28 @@ async function runUpdateTeam(args) {
2735
2937
  const apiBase = resolveApiBase(args);
2736
2938
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2737
2939
  if (clientSessionId) payload.client_session_id = clientSessionId;
2738
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
2739
- args,
2740
- operation: "update_team",
2741
- payload,
2742
- });
2743
-
2744
- let data;
2745
- try {
2746
- data = await postSuggestionsMutation({
2940
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2941
+ args,
2942
+ operation: "update_team",
2943
+ payload,
2944
+ });
2945
+ const writeApproval = requireWriteApproval(args, payload, {
2946
+ operation: "update_team",
2947
+ artifactKind: "markdown",
2948
+ });
2949
+
2950
+ let data;
2951
+ try {
2952
+ data = await postSuggestionsMutation({
2747
2953
  apiBase,
2748
2954
  key,
2749
2955
  payload,
2750
2956
  timeoutMs,
2751
- endpoint: "/project-assistant/project-comment",
2752
- idempotencyKey,
2753
- clientSessionId,
2754
- });
2957
+ endpoint: "/project-assistant/project-comment",
2958
+ idempotencyKey,
2959
+ clientSessionId,
2960
+ writeApproval,
2961
+ });
2755
2962
  } catch (err) {
2756
2963
  if (err?.name === "AbortError") {
2757
2964
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2804,23 +3011,28 @@ async function runUpdateOwner(args) {
2804
3011
  const apiBase = resolveApiBase(args);
2805
3012
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2806
3013
  if (clientSessionId) payload.client_session_id = clientSessionId;
2807
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
2808
- args,
2809
- operation: "update_owner",
2810
- payload,
2811
- });
2812
-
2813
- let data;
2814
- try {
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",
3022
+ });
3023
+
3024
+ let data;
3025
+ try {
2815
3026
  data = await postSuggestionsMutation({
2816
3027
  apiBase,
2817
3028
  key,
2818
3029
  payload,
2819
3030
  timeoutMs,
2820
- endpoint: "/project-assistant/update-owner",
2821
- idempotencyKey,
2822
- clientSessionId,
2823
- });
3031
+ endpoint: "/project-assistant/update-owner",
3032
+ idempotencyKey,
3033
+ clientSessionId,
3034
+ writeApproval,
3035
+ });
2824
3036
  } catch (err) {
2825
3037
  if (err?.name === "AbortError") {
2826
3038
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -2882,23 +3094,28 @@ async function runUpdateClient(args) {
2882
3094
  const apiBase = resolveApiBase(args);
2883
3095
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2884
3096
  if (clientSessionId) payload.client_session_id = clientSessionId;
2885
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
2886
- args,
2887
- operation: "update_client",
2888
- payload,
2889
- });
2890
-
2891
- let data;
2892
- try {
3097
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
3098
+ args,
3099
+ operation: "update_client",
3100
+ payload,
3101
+ });
3102
+ const writeApproval = requireWriteApproval(args, payload, {
3103
+ operation: "update_client",
3104
+ artifactKind: "markdown",
3105
+ });
3106
+
3107
+ let data;
3108
+ try {
2893
3109
  data = await postSuggestionsMutation({
2894
3110
  apiBase,
2895
3111
  key,
2896
3112
  payload,
2897
3113
  timeoutMs,
2898
- endpoint: "/project-assistant/client-update-drafts",
2899
- idempotencyKey,
2900
- clientSessionId,
2901
- });
3114
+ endpoint: "/project-assistant/client-update-drafts",
3115
+ idempotencyKey,
3116
+ clientSessionId,
3117
+ writeApproval,
3118
+ });
2902
3119
  } catch (err) {
2903
3120
  if (err?.name === "AbortError") {
2904
3121
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -4269,41 +4486,44 @@ async function fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScop
4269
4486
  return body.data || {};
4270
4487
  }
4271
4488
 
4272
- async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4273
- return postSuggestionsMutation({
4274
- apiBase,
4275
- key,
4276
- timeoutMs,
4277
- endpoint: "/project-assistant/suggestions",
4278
- payload,
4279
- idempotencyKey,
4280
- clientSessionId,
4281
- });
4282
- }
4283
-
4284
- async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4285
- return postSuggestionsMutation({
4286
- apiBase,
4287
- key,
4288
- timeoutMs,
4289
- endpoint: "/project-assistant/suggestions/revise",
4290
- payload,
4291
- idempotencyKey,
4292
- clientSessionId,
4293
- });
4294
- }
4295
-
4296
- async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4297
- return postSuggestionsMutation({
4298
- apiBase,
4299
- key,
4300
- timeoutMs,
4301
- endpoint: "/project-assistant/suggestions/review",
4302
- payload,
4303
- idempotencyKey,
4304
- clientSessionId,
4305
- });
4306
- }
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
+ }
4307
4527
 
4308
4528
  async function runCreatePrd(args) {
4309
4529
  const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
@@ -4414,21 +4634,27 @@ async function runCreatePrd(args) {
4414
4634
  let data;
4415
4635
  const payload = payloads[0];
4416
4636
  if (clientSessionId) payload.client_session_id = clientSessionId;
4417
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
4418
- args,
4419
- operation: "create_prd",
4420
- payload,
4421
- });
4422
- try {
4423
- data = await postSuggestionsMutation({
4424
- apiBase,
4425
- key,
4426
- payload,
4637
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
4638
+ args,
4639
+ 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,
4427
4652
  timeoutMs,
4428
- endpoint: "/project-assistant/create-prd",
4429
- idempotencyKey,
4430
- clientSessionId,
4431
- });
4653
+ endpoint: "/project-assistant/create-prd",
4654
+ idempotencyKey,
4655
+ clientSessionId,
4656
+ writeApproval,
4657
+ });
4432
4658
  } catch (err) {
4433
4659
  if (err?.name === "AbortError") {
4434
4660
  console.error(`Request timed out after ${timeoutMs}ms`);
@@ -4473,20 +4699,28 @@ async function runCreatePrd(args) {
4473
4699
  try {
4474
4700
  const payload = { items: payloads };
4475
4701
  if (clientSessionId) payload.client_session_id = clientSessionId;
4476
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
4477
- args,
4478
- operation: "create_prds",
4479
- payload,
4480
- });
4481
- const data = await postSuggestionsMutation({
4482
- apiBase,
4483
- key,
4484
- payload,
4485
- timeoutMs,
4486
- endpoint: "/project-assistant/create-prds",
4487
- idempotencyKey,
4488
- clientSessionId,
4489
- });
4702
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
4703
+ args,
4704
+ 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
+ });
4490
4724
  aggregated.project_id = data.project_id || null;
4491
4725
  aggregated.created_count = Number(data.created_count || 0);
4492
4726
  aggregated.failed_count = Number(data.failed_count || 0);
@@ -5058,26 +5292,34 @@ async function runFeedbackValidateOrApply(args, mode) {
5058
5292
  args.client_session_id,
5059
5293
  payload.client_session_id
5060
5294
  );
5061
- const idempotencyKey = mode === "apply"
5062
- ? resolveProjectMutationIdempotencyKey({
5063
- args,
5064
- operation: `feedback_refinement_apply:${feedbackId}`,
5065
- payload,
5066
- })
5067
- : null;
5068
-
5069
- let data;
5070
- try {
5071
- data = await postFeedbackRefinement({
5072
- apiBase,
5295
+ 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,
5073
5314
  key,
5074
5315
  timeoutMs,
5075
5316
  feedbackId,
5076
5317
  mode,
5077
- payload,
5078
- idempotencyKey,
5079
- clientSessionId,
5080
- });
5318
+ payload,
5319
+ idempotencyKey,
5320
+ clientSessionId,
5321
+ writeApproval,
5322
+ });
5081
5323
  } catch (err) {
5082
5324
  if (args.json) {
5083
5325
  console.log(JSON.stringify({
@@ -5202,23 +5444,29 @@ async function runFeedbackSubmit(args) {
5202
5444
  args.client_session_id,
5203
5445
  payload.client_session_id
5204
5446
  );
5205
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5206
- args,
5207
- operation: `feedback_refinement_request_submit:${feedbackId}`,
5208
- payload,
5209
- });
5210
-
5211
- let data;
5212
- try {
5213
- data = await postFeedbackReviewSubmission({
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}`,
5454
+ artifactKind: "yaml",
5455
+ targets: [feedbackId],
5456
+ });
5457
+
5458
+ let data;
5459
+ try {
5460
+ data = await postFeedbackReviewSubmission({
5214
5461
  apiBase,
5215
5462
  key,
5216
5463
  timeoutMs,
5217
5464
  feedbackId,
5218
- payload,
5219
- idempotencyKey,
5220
- clientSessionId,
5221
- });
5465
+ payload,
5466
+ idempotencyKey,
5467
+ clientSessionId,
5468
+ writeApproval,
5469
+ });
5222
5470
  } catch (err) {
5223
5471
  if (args.json) {
5224
5472
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null, artifact_path: absPath }, null, 2));
@@ -5268,24 +5516,30 @@ async function runFeedbackRevise(args) {
5268
5516
  args.client_session_id,
5269
5517
  payload.client_session_id
5270
5518
  );
5271
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5272
- args,
5273
- operation: `feedback_refinement_request_revise:${requestId}`,
5274
- payload,
5275
- });
5276
-
5277
- let data;
5278
- try {
5279
- data = await postFeedbackReviewRequestMutation({
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}`,
5526
+ artifactKind: "yaml",
5527
+ targets: [requestId],
5528
+ });
5529
+
5530
+ let data;
5531
+ try {
5532
+ data = await postFeedbackReviewRequestMutation({
5280
5533
  apiBase,
5281
5534
  key,
5282
5535
  timeoutMs,
5283
5536
  requestId,
5284
5537
  endpointAction: "revise",
5285
- payload,
5286
- idempotencyKey,
5287
- clientSessionId,
5288
- });
5538
+ payload,
5539
+ idempotencyKey,
5540
+ clientSessionId,
5541
+ writeApproval,
5542
+ });
5289
5543
  } catch (err) {
5290
5544
  if (args.json) {
5291
5545
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null, artifact_path: absPath }, null, 2));
@@ -5411,22 +5665,32 @@ async function runFeedbackReviewDecision(args) {
5411
5665
  const timeoutMs = resolveTimeoutMs(args);
5412
5666
  const apiBase = resolveApiBase(args);
5413
5667
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5414
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5415
- args,
5416
- operation: `feedback_review_batch:${action}:${items.map((item) => item.request_id || item.id || "").join(",")}`,
5417
- payload,
5418
- });
5419
-
5420
- let data;
5421
- try {
5422
- data = await postFeedbackBatchReviewMutation({
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}`,
5678
+ artifactKind: "yaml",
5679
+ targets: requestTargets,
5680
+ batchCount: items.length,
5681
+ });
5682
+
5683
+ let data;
5684
+ try {
5685
+ data = await postFeedbackBatchReviewMutation({
5423
5686
  apiBase,
5424
5687
  key,
5425
5688
  timeoutMs,
5426
- payload,
5427
- idempotencyKey,
5428
- clientSessionId,
5429
- });
5689
+ payload,
5690
+ idempotencyKey,
5691
+ clientSessionId,
5692
+ writeApproval,
5693
+ });
5430
5694
  } catch (err) {
5431
5695
  if (args.json) {
5432
5696
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5470,24 +5734,30 @@ async function runFeedbackReviewDecision(args) {
5470
5734
  const apiBase = resolveApiBase(args);
5471
5735
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5472
5736
  const operationAction = action === "request_changes" ? "request_changes" : action;
5473
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5474
- args,
5475
- operation: `feedback_review_request_${operationAction}:${requestId}`,
5476
- payload,
5477
- });
5478
-
5479
- let data;
5480
- try {
5481
- data = await postFeedbackReviewRequestMutation({
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}`,
5744
+ artifactKind: "yaml",
5745
+ targets: [requestId],
5746
+ });
5747
+
5748
+ let data;
5749
+ try {
5750
+ data = await postFeedbackReviewRequestMutation({
5482
5751
  apiBase,
5483
5752
  key,
5484
5753
  timeoutMs,
5485
5754
  requestId,
5486
5755
  endpointAction,
5487
- payload,
5488
- idempotencyKey,
5489
- clientSessionId,
5490
- });
5756
+ payload,
5757
+ idempotencyKey,
5758
+ clientSessionId,
5759
+ writeApproval,
5760
+ });
5491
5761
  } catch (err) {
5492
5762
  if (args.json) {
5493
5763
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5540,21 +5810,28 @@ async function runFeedbackMove(args) {
5540
5810
  }
5541
5811
  const batchId = firstNonEmptyString(args["batch-id"], args.batchId, args.batch_id);
5542
5812
  if (batchId) payload.batch_id = batchId;
5543
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5544
- args,
5545
- operation: `feedback_board_batch_move:${toState}:${feedbackIds.join(",")}`,
5546
- payload,
5547
- });
5548
- let data;
5549
- try {
5550
- data = await postFeedbackBatchBoardMutation({
5551
- apiBase,
5552
- key,
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}`,
5820
+ artifactKind: "yaml",
5821
+ targets: feedbackIds,
5822
+ batchCount: feedbackIds.length,
5823
+ });
5824
+ let data;
5825
+ try {
5826
+ data = await postFeedbackBatchBoardMutation({
5827
+ apiBase,
5828
+ key,
5553
5829
  timeoutMs,
5554
- payload,
5555
- idempotencyKey,
5556
- clientSessionId,
5557
- });
5830
+ payload,
5831
+ idempotencyKey,
5832
+ clientSessionId,
5833
+ writeApproval,
5834
+ });
5558
5835
  } catch (err) {
5559
5836
  if (args.json) {
5560
5837
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5576,24 +5853,30 @@ async function runFeedbackMove(args) {
5576
5853
  }
5577
5854
 
5578
5855
  payload.from_state = fromState;
5579
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5580
- args,
5581
- operation: `feedback_board_move:${feedbackId}`,
5582
- payload,
5583
- });
5584
-
5585
- let data;
5586
- try {
5587
- data = await postFeedbackBoardMutation({
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}`,
5863
+ artifactKind: "yaml",
5864
+ targets: [feedbackId],
5865
+ });
5866
+
5867
+ let data;
5868
+ try {
5869
+ data = await postFeedbackBoardMutation({
5588
5870
  apiBase,
5589
5871
  key,
5590
5872
  timeoutMs,
5591
5873
  feedbackId,
5592
5874
  endpoint: "board-move",
5593
- payload,
5594
- idempotencyKey,
5595
- clientSessionId,
5596
- });
5875
+ payload,
5876
+ idempotencyKey,
5877
+ clientSessionId,
5878
+ writeApproval,
5879
+ });
5597
5880
  } catch (err) {
5598
5881
  if (args.json) {
5599
5882
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -5649,6 +5932,12 @@ async function runFeedbackComment(args) {
5649
5932
  return;
5650
5933
  }
5651
5934
 
5935
+ const writeApproval = requireWriteApproval(args, payload, {
5936
+ operation: `feedback_comment_create:${feedbackId}`,
5937
+ artifactKind: "markdown",
5938
+ targets: [feedbackId],
5939
+ });
5940
+
5652
5941
  let data;
5653
5942
  try {
5654
5943
  data = await postFeedbackComment({
@@ -5659,6 +5948,7 @@ async function runFeedbackComment(args) {
5659
5948
  payload,
5660
5949
  idempotencyKey,
5661
5950
  clientSessionId,
5951
+ writeApproval,
5662
5952
  });
5663
5953
  } catch (err) {
5664
5954
  if (args.json) {
@@ -5711,24 +6001,36 @@ async function runFeedbackUndo(args) {
5711
6001
  const timeoutMs = resolveTimeoutMs(args);
5712
6002
  const apiBase = resolveApiBase(args);
5713
6003
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5714
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5715
- args,
5716
- operation: `feedback_event_undo:${feedbackId}:${eventId}`,
5717
- payload,
5718
- });
5719
-
5720
- let data;
5721
- try {
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}`,
6017
+ artifactKind: "yaml",
6018
+ targets: [feedbackId, eventId],
6019
+ });
6020
+
6021
+ let data;
6022
+ try {
5722
6023
  data = await postFeedbackBoardMutation({
5723
6024
  apiBase,
5724
6025
  key,
5725
6026
  timeoutMs,
5726
6027
  feedbackId,
5727
6028
  endpoint: `events/${encodeURIComponent(String(eventId))}/undo`,
5728
- payload,
5729
- idempotencyKey,
5730
- clientSessionId,
5731
- });
6029
+ payload,
6030
+ idempotencyKey,
6031
+ clientSessionId,
6032
+ writeApproval,
6033
+ });
5732
6034
  } catch (err) {
5733
6035
  if (args.json) {
5734
6036
  console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
@@ -6101,21 +6403,30 @@ async function runSuggestionsCreate(args) {
6101
6403
  process.exit(1);
6102
6404
  }
6103
6405
 
6104
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6105
- console.log(JSON.stringify(context.payload, null, 2));
6106
- return;
6107
- }
6108
-
6109
- let data;
6110
- try {
6111
- data = await createProjectSuggestions({
6112
- apiBase: context.apiBase,
6113
- key: context.key,
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,
6114
6424
  timeoutMs: context.timeoutMs,
6115
- payload: context.payload,
6116
- idempotencyKey: context.idempotencyKey,
6117
- clientSessionId: context.clientSessionId,
6118
- });
6425
+ payload: context.payload,
6426
+ idempotencyKey: context.idempotencyKey,
6427
+ clientSessionId: context.clientSessionId,
6428
+ writeApproval,
6429
+ });
6119
6430
  } catch (err) {
6120
6431
  console.error("Suggestions create failed:", err?.message || err);
6121
6432
  process.exit(1);
@@ -6186,21 +6497,30 @@ async function runSuggestionsRevise(args) {
6186
6497
  process.exit(1);
6187
6498
  }
6188
6499
 
6189
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6190
- console.log(JSON.stringify(context.payload, null, 2));
6191
- return;
6192
- }
6193
-
6194
- let data;
6195
- try {
6196
- data = await reviseProjectSuggestions({
6197
- apiBase: context.apiBase,
6198
- key: context.key,
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,
6199
6518
  timeoutMs: context.timeoutMs,
6200
- payload: context.payload,
6201
- idempotencyKey: context.idempotencyKey,
6202
- clientSessionId: context.clientSessionId,
6203
- });
6519
+ payload: context.payload,
6520
+ idempotencyKey: context.idempotencyKey,
6521
+ clientSessionId: context.clientSessionId,
6522
+ writeApproval,
6523
+ });
6204
6524
  } catch (err) {
6205
6525
  console.error("Suggestions revise failed:", err?.message || err);
6206
6526
  process.exit(1);
@@ -6269,21 +6589,30 @@ async function runSuggestionsReview(args) {
6269
6589
  process.exit(1);
6270
6590
  }
6271
6591
 
6272
- if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
6273
- console.log(JSON.stringify(context.payload, null, 2));
6274
- return;
6275
- }
6276
-
6277
- let data;
6278
- try {
6279
- data = await reviewProjectSuggestions({
6280
- apiBase: context.apiBase,
6281
- key: context.key,
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,
6282
6610
  timeoutMs: context.timeoutMs,
6283
- payload: context.payload,
6284
- idempotencyKey: context.idempotencyKey,
6285
- clientSessionId: context.clientSessionId,
6286
- });
6611
+ payload: context.payload,
6612
+ idempotencyKey: context.idempotencyKey,
6613
+ clientSessionId: context.clientSessionId,
6614
+ writeApproval,
6615
+ });
6287
6616
  } catch (err) {
6288
6617
  console.error("Suggestions review failed:", err?.message || err);
6289
6618
  process.exit(1);