@mytegroupinc/myte-core 0.0.45 → 0.0.47

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
@@ -26,9 +26,9 @@ const DEFAULT_DIFF_LIMIT_CHARS = 500_000;
26
26
  const REMOVED_COMMAND_MESSAGES = {
27
27
  ask: "The `ask` alias has been removed. Use `myte query \"...\"`.",
28
28
  chat: "The `chat` command has been removed. Use repeated `myte query` calls instead.",
29
- "create-prds": "The `create-prds` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
30
- "add-prd": "The `add-prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
31
- prd: "The `prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...]`.",
29
+ "create-prds": "The `create-prds` alias has been removed. Use `myte create-prd <file.md> [more.md ...] --confirm-write --approval-artifact <path>`.",
30
+ "add-prd": "The `add-prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...] --confirm-write --approval-artifact <path>`.",
31
+ prd: "The `prd` alias has been removed. Use `myte create-prd <file.md> [more.md ...] --confirm-write --approval-artifact <path>`.",
32
32
  "qaqc-status-update": "The `qaqc-status-update` command has been removed. Use `myte mission status --mission-ids \"M001,M002\" --status todo|in_progress|done`.",
33
33
  "mission-restore": "The project-key `myte mission restore` command has been removed. Restore archived missions from the Myte web archived missions board.",
34
34
  };
@@ -190,12 +190,12 @@ function printHelp() {
190
190
  " myte mission archive --mission-ids \"M001[,M002...]\" [--reason \"...\"] [--no-sync] [--json]",
191
191
  " myte sync-qaqc [--output-dir ./MyteCommandCenter] [--json]",
192
192
  " myte suggestions sync [--output-dir ./MyteCommandCenter] [--json]",
193
- " myte suggestions create [--file ./payload.yml] [--no-sync] [--json]",
194
- " myte suggestions revise [--file ./payload.yml] [--no-sync] [--json]",
195
- " myte suggestions review [--file ./payload.yml] [--no-sync] [--json]",
196
- " myte update-team \"<content>\" [--json]",
197
- " myte update-owner --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--json]",
198
- " myte update-client --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--target-contact-ids <id1,id2>] [--json]",
193
+ " myte suggestions create [--file ./payload.yml] --confirm-write --approval-artifact <path> [--no-sync] [--json]",
194
+ " myte suggestions revise [--file ./payload.yml] --confirm-write --approval-artifact <path> [--no-sync] [--json]",
195
+ " myte suggestions review [--file ./payload.yml] [--no-sync] [--json]",
196
+ " myte update-team \"<content>\" --confirm-write --approval-artifact <path> [--json]",
197
+ " myte update-owner --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] --confirm-write --approval-artifact <path> [--json]",
198
+ " myte update-client --subject \"<text>\" [--body-markdown \"...\"] [--body-file ./update.md] [--target-contact-ids <id1,id2>] --confirm-write --approval-artifact <path> [--json]",
199
199
  " myte feedback-sync [--status <value>] [--source <value>] [--with-prd-text|--no-with-prd-text] [--output-dir ./MyteCommandCenter] [--json]",
200
200
  " myte feedback get --feedback-id <id> [--json]",
201
201
  " myte feedback history --feedback-id <id> [--limit 50] [--json]",
@@ -203,23 +203,23 @@ function printHelp() {
203
203
  " myte feedback edit --feedback-id <id> [--title \"...\"] [--feedback-text \"...\"] [--priority High] [--reason \"...\"]",
204
204
  " myte feedback assign --feedback-id <id> --user-id <id> [--reason \"...\"]",
205
205
  " myte feedback archive --feedback-id <id> --reason \"...\"",
206
- " myte feedback submit --file ./MyteCommandCenter/reviews/feedback/<id>-edit.yml [--json]",
207
- " myte feedback revise --request-id <id> --file ./MyteCommandCenter/reviews/feedback/<id>-edit.yml [--json]",
206
+ " myte feedback submit --file ./MyteCommandCenter/reviews/feedback/<id>-edit.yml --confirm-write --approval-artifact <path> [--json]",
207
+ " myte feedback revise --request-id <id> --file ./MyteCommandCenter/reviews/feedback/<id>-edit.yml --confirm-write --approval-artifact <path> [--json]",
208
208
  " myte feedback reviews [--status open|terminal|all] [--request-id <id>] [--json]",
209
209
  " myte feedback review --request-id <id> --action approve|reject|request_changes|cancel [--reason \"...\"] [--json]",
210
210
  " myte feedback review --request-ids \"<id1,id2>\" --action approve|reject|request_changes|cancel [--reason \"...\"] [--json]",
211
211
  " myte feedback move --feedback-id <id> --to-state in_progress --from-state todo --reason \"...\" [--json]",
212
212
  " myte feedback move --feedback-ids \"<id1,id2>\" --to-state in_progress --reason \"...\" [--json]",
213
- " myte feedback comment --feedback-id <id> --body \"...\" [--body-file ./comment.md] [--json]",
213
+ " myte feedback comment --feedback-id <id> --body \"...\" [--body-file ./comment.md] --confirm-write --approval-artifact <path> [--json]",
214
214
  " myte feedback undo --feedback-id <id> --event-id <id> --reason \"...\" [--json]",
215
215
  " myte feedback prd-versions --feedback-id <id> [--json]",
216
216
  " myte feedback prd-diff --feedback-id <id> --version-id <id> [--compare-to <id>] [--json]",
217
217
  " myte feedback validate --file ./MyteCommandCenter/reviews/feedback/<id>-status.yml [--json]",
218
218
  " myte feedback apply --file ./MyteCommandCenter/reviews/feedback/<id>-status.yml [--json]",
219
- " myte create-prd <file.md> [more.md ...] [--json] [--title \"...\"] [--description \"...\"]",
220
- " cat file.md | myte create-prd --stdin [--title \"...\"] [--description \"...\"]",
221
- " cat update.md | myte update-owner --stdin --subject \"Owner update\"",
222
- " cat update.md | myte update-client --stdin --subject \"Weekly client update\"",
219
+ " myte create-prd <file.md> [more.md ...] --confirm-write --approval-artifact <path> [--json] [--title \"...\"] [--description \"...\"]",
220
+ " cat file.md | myte create-prd --stdin --confirm-write --approval-artifact <path> [--title \"...\"] [--description \"...\"]",
221
+ " cat update.md | myte update-owner --stdin --subject \"Owner update\" --confirm-write --approval-artifact ./update.md",
222
+ " cat update.md | myte update-client --stdin --subject \"Weekly client update\" --confirm-write --approval-artifact ./update.md",
223
223
  "",
224
224
  "Run forms:",
225
225
  " npm install myte then npx myte query \"...\" --with-diff",
@@ -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",
@@ -334,11 +328,23 @@ function printHelp() {
334
328
  " - project-key API/CLI cannot unarchive archived Feedback; use the web archived Feedback view",
335
329
  " - get/history read current feedback snapshots and audited event history through the project-key API",
336
330
  " - prd-versions/prd-diff expose retained PRD versions and backend-generated text diffs",
337
- " - validate/apply send artifacts to /api/project-assistant/feedback/<id>/refinement/* for validation or owner-direct apply",
338
- " - The backend owns authorization, stale snapshot checks, allowed field/transition rules, and history",
339
- " - apply is idempotent and does not rewrite local feedback.yml; run feedback-sync after apply to refresh local state",
340
- "",
341
- "feedback action map:",
331
+ " - validate/apply send artifacts to /api/project-assistant/feedback/<id>/refinement/* for validation or owner-direct apply",
332
+ " - The backend owns authorization, stale snapshot checks, allowed field/transition rules, and history",
333
+ " - apply is idempotent and does not rewrite local feedback.yml; run feedback-sync after apply to refresh local state",
334
+ "",
335
+ "approval artifact requirements:",
336
+ " - Required: create-prd, update-team, update-owner, update-client, feedback submit/revise/comment, suggestions create/revise",
337
+ " - Not required: run-qaqc, mission status/archive, suggestions review, feedback review/move/undo/apply",
338
+ " - Never required: read/sync/query/validate commands, --print-context, --dry-run, and feedback local draft commands",
339
+ " - For required actions, prepare a .md/.markdown/.yml/.yaml/.json artifact, show it to the user, then rerun with --confirm-write --approval-artifact <path>",
340
+ " - In plain language, the artifact is the human-reviewed record of the exact write the agent is about to send; it is not a permission grant and does not bypass owner/delegate checks, idempotency, stale-state guards, validation, or audit",
341
+ " - Minimum artifact contents: exact command; every target id or PRD item; complete proposed-change summary; reason/approval note; batch_count when one command sends multiple items",
342
+ " - Mission edit/create batches use suggestions create/revise and must enumerate each item: update items include change_type: update, mission_id, and changed fields; create items include change_type: create, title, description, and acceptance details; revisions include suggestion_id and expected_revision",
343
+ " - PRD batches use one create-prd fileA.md fileB.md ...; list each PRD file/title/client_ref and make batch_count match the number of PRDs",
344
+ " - Feedback submit/revise/comment artifacts are usually the same YAML/markdown file being submitted; for inline text or stdin, use an approval memo with feedback_id/request_id and exact content",
345
+ " - Team, owner, and client update artifacts should name the audience and include the exact message body or complete summary; client updates should include target contact ids when used",
346
+ "",
347
+ "feedback action map:",
342
348
  " - feedback-sync: pull board/PRD context into MyteCommandCenter for IDE work",
343
349
  " - get: read one feedback item's current server snapshot and snapshot_hash",
344
350
  " - status/edit/assign/archive/refine: create reviewable local YAML artifacts; no live mutation",
@@ -398,12 +404,12 @@ function printHelp() {
398
404
  " --no-with-prd-text Skip PRD text download and write only feedback metadata/comment turns",
399
405
  " --mission-ids <ids> Comma-separated mission business ids for run-qaqc, mission status, or mission archive (quote multi-id values on PowerShell)",
400
406
  " --reason <text> Optional reason for governed mission archive or feedback changes",
401
- " --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
402
- " --wait Poll batch status until terminal completion for run-qaqc",
407
+ " --actor-scope <id> Actor workspace key inside mission-ops.yml (defaults to machine-cwd slug)",
408
+ " --confirm-write Attach approval metadata for commands that require an approval artifact",
409
+ " --approval-artifact Local .md/.markdown/.yml/.yaml/.json approval artifact for gated write commands",
410
+ " --wait Poll batch status until terminal completion for run-qaqc",
403
411
  " --sync After run-qaqc completes, refresh local QAQC file",
404
412
  " --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
413
  " --no-sync Skip automatic post-mutation sync for suggestions, missions, and feedback mutations",
408
414
  " --print-context Print JSON payload and exit (no query call)",
409
415
  " --no-fetch Don't git fetch origin main/master before diff",
@@ -414,33 +420,33 @@ function printHelp() {
414
420
  " myte ai \"Return a JSON object with risks and next_steps\" --json-response",
415
421
  " myte bootstrap",
416
422
  " myte suggestions sync",
417
- " myte suggestions create",
418
- " myte suggestions revise --no-sync",
423
+ " myte suggestions create --file ./suggestions/create.yml --confirm-write --approval-artifact ./suggestions/create.yml",
424
+ " myte suggestions revise --file ./suggestions/revise.yml --confirm-write --approval-artifact ./suggestions/revise.yml --no-sync",
419
425
  " myte suggestions review --file ./review.yml",
420
426
  " myte run-qaqc --mission-ids \"M001,M002\" --wait --sync",
421
427
  " myte mission status --mission-ids \"M001,M002\" --status done",
422
428
  " myte mission archive --mission-ids \"M001\" --reason \"Duplicate disposable test mission\"",
423
429
  " myte bootstrap --output-dir ./MyteCommandCenter",
424
430
  " myte sync-qaqc",
425
- " myte update-team \"Backend deploy completed; QAQC rerun queued.\"",
426
- " myte update-owner --subject \"QAQC progress\" --body-file ./updates/owner.md",
427
- " myte update-client --subject \"Weekly client update\" --body-file ./updates/week-12.md",
431
+ " myte update-team \"Backend deploy completed; QAQC rerun queued.\" --confirm-write --approval-artifact ./updates/team.md",
432
+ " myte update-owner --subject \"QAQC progress\" --body-file ./updates/owner.md --confirm-write --approval-artifact ./updates/owner.md",
433
+ " myte update-client --subject \"Weekly client update\" --body-file ./updates/week-12.md --confirm-write --approval-artifact ./updates/week-12.md",
428
434
  " myte feedback-sync --json",
429
435
  " myte feedback status --feedback-id 507f1f77bcf86cd799439011 --status in_review --reason \"Ready for owner review\"",
430
- " myte feedback submit --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-edit.yml --json",
436
+ " myte feedback submit --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-edit.yml --confirm-write --approval-artifact ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-edit.yml --json",
431
437
  " myte feedback reviews --status open --json",
432
438
  " myte feedback review --request-id 507f1f77bcf86cd799439022 --action approve --reason \"Looks correct\" --json",
433
439
  " myte feedback move --feedback-id 507f1f77bcf86cd799439011 --to-state in_progress --reason \"Started\" --json",
434
- " myte feedback comment --feedback-id 507f1f77bcf86cd799439011 --body \"Implementation notes are ready for review.\" --json",
440
+ " myte feedback comment --feedback-id 507f1f77bcf86cd799439011 --body-file ./comments/implementation.md --confirm-write --approval-artifact ./comments/implementation.md --json",
435
441
  " myte feedback validate --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-status.yml --json",
436
442
  " myte feedback apply --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-status.yml --json",
437
- " myte suggestions create --file ./suggestions/create.yml",
438
- " myte suggestions revise",
443
+ " myte suggestions create --file ./suggestions/create.yml --confirm-write --approval-artifact ./suggestions/create.yml",
444
+ " myte suggestions revise --file ./suggestions/revise.yml --confirm-write --approval-artifact ./suggestions/revise.yml",
439
445
  " myte suggestions review",
440
- " myte update-client --subject \"Weekly client update\" --body-markdown \"## Progress\\n- Login complete\" --target-contact-ids 507f1f77bcf86cd799439011,507f1f77bcf86cd799439012",
441
- " myte create-prd ./drafts/auth-prd.md --description \"Short card summary\"",
442
- " myte create-prd ./drafts/auth-prd.md ./drafts/billing-prd.md",
443
- " cat ./drafts/auth-prd.md | myte create-prd --stdin",
446
+ " myte update-client --subject \"Weekly client update\" --body-markdown \"## Progress\\n- Login complete\" --target-contact-ids 507f1f77bcf86cd799439011,507f1f77bcf86cd799439012 --confirm-write --approval-artifact ./updates/week-12.md",
447
+ " myte create-prd ./drafts/auth-prd.md --description \"Short card summary\" --confirm-write --approval-artifact ./drafts/auth-prd.md",
448
+ " myte create-prd ./drafts/auth-prd.md ./drafts/billing-prd.md --confirm-write --approval-artifact ./drafts/prd-batch.md",
449
+ " cat ./drafts/auth-prd.md | myte create-prd --stdin --confirm-write --approval-artifact ./drafts/auth-prd.md",
444
450
  " myte config",
445
451
  ].join("\n");
446
452
  console.log(text);
@@ -701,164 +707,6 @@ async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
701
707
  }
702
708
  }
703
709
 
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
710
  function summarizeDiffDiagnosticsForContext(diagnostics) {
863
711
  if (!diagnostics) return null;
864
712
  const repos = Array.isArray(diagnostics.repo_summaries)
@@ -1922,7 +1770,7 @@ async function fetchFeedbackHistory({ apiBase, key, timeoutMs, feedbackId }) {
1922
1770
  return body.data || {};
1923
1771
  }
1924
1772
 
1925
- async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mode, payload, idempotencyKey, clientSessionId, writeApproval }) {
1773
+ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mode, payload, idempotencyKey, clientSessionId }) {
1926
1774
  const fetchFn = await getFetch();
1927
1775
  const action = String(mode || "").trim();
1928
1776
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/refinement/${action}`;
@@ -1930,7 +1778,6 @@ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mod
1930
1778
  "Content-Type": "application/json",
1931
1779
  Authorization: `Bearer ${key}`,
1932
1780
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
1933
- ...writeApprovalHeaders(writeApproval),
1934
1781
  };
1935
1782
  if (idempotencyKey) {
1936
1783
  headers["X-Idempotency-Key"] = String(idempotencyKey).trim();
@@ -1947,7 +1794,7 @@ async function postFeedbackRefinement({ apiBase, key, timeoutMs, feedbackId, mod
1947
1794
  );
1948
1795
 
1949
1796
  if (!resp.ok || body.status !== "success") {
1950
- const msg = formatProjectApiErrorMessage(body, `Feedback refinement ${action} failed (${resp.status})`);
1797
+ const msg = body?.message || `Feedback refinement ${action} failed (${resp.status})`;
1951
1798
  const err = new Error(msg);
1952
1799
  err.status = resp.status;
1953
1800
  err.data = body?.data;
@@ -2026,7 +1873,7 @@ async function fetchFeedbackEvents({ apiBase, key, timeoutMs, feedbackId, limit
2026
1873
  return body.data || {};
2027
1874
  }
2028
1875
 
2029
- async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId, writeApproval }) {
1876
+ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId }) {
2030
1877
  const fetchFn = await getFetch();
2031
1878
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/refinement/requests`;
2032
1879
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2039,7 +1886,6 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
2039
1886
  Authorization: `Bearer ${key}`,
2040
1887
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2041
1888
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2042
- ...writeApprovalHeaders(writeApproval),
2043
1889
  },
2044
1890
  body: JSON.stringify(payload || {}),
2045
1891
  },
@@ -2047,7 +1893,7 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
2047
1893
  );
2048
1894
 
2049
1895
  if (!resp.ok || body.status !== "success") {
2050
- const msg = formatProjectApiErrorMessage(body, `Feedback review submission failed (${resp.status})`);
1896
+ const msg = body?.message || `Feedback review submission failed (${resp.status})`;
2051
1897
  const err = new Error(msg);
2052
1898
  err.status = resp.status;
2053
1899
  err.data = body?.data;
@@ -2056,7 +1902,7 @@ async function postFeedbackReviewSubmission({ apiBase, key, timeoutMs, feedbackI
2056
1902
  return body.data || {};
2057
1903
  }
2058
1904
 
2059
- async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requestId, endpointAction, payload, idempotencyKey, clientSessionId, writeApproval }) {
1905
+ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requestId, endpointAction, payload, idempotencyKey, clientSessionId }) {
2060
1906
  const fetchFn = await getFetch();
2061
1907
  const url = `${apiBase}/project-assistant/feedback-review-requests/${encodeURIComponent(String(requestId || ""))}/${endpointAction}`;
2062
1908
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2069,7 +1915,6 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
2069
1915
  Authorization: `Bearer ${key}`,
2070
1916
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2071
1917
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2072
- ...writeApprovalHeaders(writeApproval),
2073
1918
  },
2074
1919
  body: JSON.stringify(payload || {}),
2075
1920
  },
@@ -2077,7 +1922,7 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
2077
1922
  );
2078
1923
 
2079
1924
  if (!resp.ok || body.status !== "success") {
2080
- const msg = formatProjectApiErrorMessage(body, `Feedback review ${endpointAction} failed (${resp.status})`);
1925
+ const msg = body?.message || `Feedback review ${endpointAction} failed (${resp.status})`;
2081
1926
  const err = new Error(msg);
2082
1927
  err.status = resp.status;
2083
1928
  err.data = body?.data;
@@ -2086,7 +1931,7 @@ async function postFeedbackReviewRequestMutation({ apiBase, key, timeoutMs, requ
2086
1931
  return body.data || {};
2087
1932
  }
2088
1933
 
2089
- async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
1934
+ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2090
1935
  const fetchFn = await getFetch();
2091
1936
  const url = `${apiBase}/project-assistant/feedback-review-requests/batch-review`;
2092
1937
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2099,7 +1944,6 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
2099
1944
  Authorization: `Bearer ${key}`,
2100
1945
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2101
1946
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2102
- ...writeApprovalHeaders(writeApproval),
2103
1947
  },
2104
1948
  body: JSON.stringify(payload || {}),
2105
1949
  },
@@ -2107,7 +1951,7 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
2107
1951
  );
2108
1952
 
2109
1953
  if (!resp.ok || body.status !== "success") {
2110
- const msg = formatProjectApiErrorMessage(body, `Feedback batch review failed (${resp.status})`);
1954
+ const msg = body?.message || `Feedback batch review failed (${resp.status})`;
2111
1955
  const err = new Error(msg);
2112
1956
  err.status = resp.status;
2113
1957
  err.data = body?.data;
@@ -2116,7 +1960,7 @@ async function postFeedbackBatchReviewMutation({ apiBase, key, timeoutMs, payloa
2116
1960
  return body.data || {};
2117
1961
  }
2118
1962
 
2119
- async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
1963
+ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId, endpoint, payload, idempotencyKey, clientSessionId }) {
2120
1964
  const fetchFn = await getFetch();
2121
1965
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/${endpoint}`;
2122
1966
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2129,7 +1973,6 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
2129
1973
  Authorization: `Bearer ${key}`,
2130
1974
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2131
1975
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2132
- ...writeApprovalHeaders(writeApproval),
2133
1976
  },
2134
1977
  body: JSON.stringify(payload || {}),
2135
1978
  },
@@ -2137,7 +1980,7 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
2137
1980
  );
2138
1981
 
2139
1982
  if (!resp.ok || body.status !== "success") {
2140
- const msg = formatProjectApiErrorMessage(body, `Feedback board mutation failed (${resp.status})`);
1983
+ const msg = body?.message || `Feedback board mutation failed (${resp.status})`;
2141
1984
  const err = new Error(msg);
2142
1985
  err.status = resp.status;
2143
1986
  err.data = body?.data;
@@ -2146,7 +1989,7 @@ async function postFeedbackBoardMutation({ apiBase, key, timeoutMs, feedbackId,
2146
1989
  return body.data || {};
2147
1990
  }
2148
1991
 
2149
- async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
1992
+ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2150
1993
  const fetchFn = await getFetch();
2151
1994
  const url = `${apiBase}/project-assistant/feedback/batch-board-move`;
2152
1995
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2159,7 +2002,6 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
2159
2002
  Authorization: `Bearer ${key}`,
2160
2003
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2161
2004
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2162
- ...writeApprovalHeaders(writeApproval),
2163
2005
  },
2164
2006
  body: JSON.stringify(payload || {}),
2165
2007
  },
@@ -2167,7 +2009,7 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
2167
2009
  );
2168
2010
 
2169
2011
  if (!resp.ok || body.status !== "success") {
2170
- const msg = formatProjectApiErrorMessage(body, `Feedback batch board mutation failed (${resp.status})`);
2012
+ const msg = body?.message || `Feedback batch board mutation failed (${resp.status})`;
2171
2013
  const err = new Error(msg);
2172
2014
  err.status = resp.status;
2173
2015
  err.data = body?.data;
@@ -2176,7 +2018,7 @@ async function postFeedbackBatchBoardMutation({ apiBase, key, timeoutMs, payload
2176
2018
  return body.data || {};
2177
2019
  }
2178
2020
 
2179
- async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId, writeApproval }) {
2021
+ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payload, idempotencyKey, clientSessionId }) {
2180
2022
  const fetchFn = await getFetch();
2181
2023
  const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/comments`;
2182
2024
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2189,7 +2031,6 @@ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payloa
2189
2031
  Authorization: `Bearer ${key}`,
2190
2032
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2191
2033
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2192
- ...writeApprovalHeaders(writeApproval),
2193
2034
  },
2194
2035
  body: JSON.stringify(payload || {}),
2195
2036
  },
@@ -2197,7 +2038,7 @@ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payloa
2197
2038
  );
2198
2039
 
2199
2040
  if (!resp.ok || body.status !== "success") {
2200
- const msg = formatProjectApiErrorMessage(body, `Feedback comment failed (${resp.status})`);
2041
+ const msg = body?.message || `Feedback comment failed (${resp.status})`;
2201
2042
  const err = new Error(msg);
2202
2043
  err.status = resp.status;
2203
2044
  err.data = body?.data;
@@ -2303,7 +2144,7 @@ function resolveProjectMutationIdempotencyKey({ args, operation, payload }) {
2303
2144
  return createHash("sha256").update(fingerprint).digest("hex");
2304
2145
  }
2305
2146
 
2306
- async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
2147
+ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId }) {
2307
2148
  const fetchFn = await getFetch();
2308
2149
  const url = `${apiBase}${endpoint}`;
2309
2150
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2316,7 +2157,6 @@ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payl
2316
2157
  Authorization: `Bearer ${key}`,
2317
2158
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2318
2159
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2319
- ...writeApprovalHeaders(writeApproval),
2320
2160
  },
2321
2161
  body: JSON.stringify(payload),
2322
2162
  },
@@ -2324,16 +2164,15 @@ async function postSuggestionsMutation({ apiBase, key, timeoutMs, endpoint, payl
2324
2164
  );
2325
2165
 
2326
2166
  if (!resp.ok || body.status !== "success") {
2327
- const msg = formatProjectApiErrorMessage(body, `Mission suggestions request failed (${resp.status})`);
2167
+ const msg = body?.message || `Mission suggestions request failed (${resp.status})`;
2328
2168
  const err = new Error(msg);
2329
2169
  err.status = resp.status;
2330
- err.data = body?.data;
2331
2170
  throw err;
2332
2171
  }
2333
2172
  return body.data || {};
2334
2173
  }
2335
2174
 
2336
- async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
2175
+ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2337
2176
  const fetchFn = await getFetch();
2338
2177
  const url = `${apiBase}/project-assistant/run-qaqc`;
2339
2178
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2346,7 +2185,6 @@ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotenc
2346
2185
  Authorization: `Bearer ${key}`,
2347
2186
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2348
2187
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2349
- ...writeApprovalHeaders(writeApproval),
2350
2188
  },
2351
2189
  body: JSON.stringify(payload),
2352
2190
  },
@@ -2354,16 +2192,15 @@ async function createRunQaqcBatch({ apiBase, key, timeoutMs, payload, idempotenc
2354
2192
  );
2355
2193
 
2356
2194
  if (!resp.ok || body.status !== "success") {
2357
- const msg = formatProjectApiErrorMessage(body, `Run QAQC request failed (${resp.status})`);
2195
+ const msg = body?.message || `Run QAQC request failed (${resp.status})`;
2358
2196
  const err = new Error(msg);
2359
2197
  err.status = resp.status;
2360
- err.data = body?.data;
2361
2198
  throw err;
2362
2199
  }
2363
2200
  return body.data || {};
2364
2201
  }
2365
2202
 
2366
- async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
2203
+ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
2367
2204
  const fetchFn = await getFetch();
2368
2205
  const url = `${apiBase}/project-assistant/mission-status-update`;
2369
2206
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2376,7 +2213,6 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
2376
2213
  Authorization: `Bearer ${key}`,
2377
2214
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2378
2215
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2379
- ...writeApprovalHeaders(writeApproval),
2380
2216
  },
2381
2217
  body: JSON.stringify(payload),
2382
2218
  },
@@ -2384,16 +2220,15 @@ async function createMissionStatusUpdate({ apiBase, key, timeoutMs, payload, ide
2384
2220
  );
2385
2221
 
2386
2222
  if (!resp.ok || body.status !== "success") {
2387
- const msg = formatProjectApiErrorMessage(body, `Mission status update request failed (${resp.status})`);
2223
+ const msg = body?.message || `Mission status update request failed (${resp.status})`;
2388
2224
  const err = new Error(msg);
2389
2225
  err.status = resp.status;
2390
- err.data = body?.data;
2391
2226
  throw err;
2392
2227
  }
2393
2228
  return body.data || {};
2394
2229
  }
2395
2230
 
2396
- async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId, writeApproval }) {
2231
+ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, payload, idempotencyKey, clientSessionId }) {
2397
2232
  const fetchFn = await getFetch();
2398
2233
  const url = `${apiBase}${endpoint}`;
2399
2234
  const { resp, body } = await fetchJsonWithTimeout(
@@ -2406,7 +2241,6 @@ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, p
2406
2241
  Authorization: `Bearer ${key}`,
2407
2242
  "X-Idempotency-Key": String(idempotencyKey || "").trim(),
2408
2243
  ...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
2409
- ...writeApprovalHeaders(writeApproval),
2410
2244
  },
2411
2245
  body: JSON.stringify(payload),
2412
2246
  },
@@ -2414,10 +2248,9 @@ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, p
2414
2248
  );
2415
2249
 
2416
2250
  if (!resp.ok || body.status !== "success") {
2417
- const msg = formatProjectApiErrorMessage(body, `Mission archive request failed (${resp.status})`);
2251
+ const msg = body?.message || `Mission archive request failed (${resp.status})`;
2418
2252
  const err = new Error(msg);
2419
2253
  err.status = resp.status;
2420
- err.data = body?.data;
2421
2254
  throw err;
2422
2255
  }
2423
2256
  return body.data || {};
@@ -2530,15 +2363,15 @@ async function runRunQaqc(args) {
2530
2363
  process.exit(1);
2531
2364
  }
2532
2365
 
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",
2366
+ let payload = {
2367
+ mission_ids: missionIds,
2368
+ force: Boolean(args.force),
2369
+ };
2370
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2371
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2372
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2373
+ args,
2374
+ operation: "run-qaqc",
2542
2375
  payload,
2543
2376
  });
2544
2377
 
@@ -2551,12 +2384,6 @@ async function runRunQaqc(args) {
2551
2384
  const apiBase = resolveApiBase(args);
2552
2385
  const shouldWait = Boolean(args.wait || args.sync);
2553
2386
  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
2387
 
2561
2388
  let data;
2562
2389
  try {
@@ -2567,7 +2394,6 @@ async function runRunQaqc(args) {
2567
2394
  payload,
2568
2395
  idempotencyKey,
2569
2396
  clientSessionId,
2570
- writeApproval,
2571
2397
  });
2572
2398
  } catch (err) {
2573
2399
  if (err?.name === "AbortError") {
@@ -2692,15 +2518,15 @@ async function runMissionStatus(args) {
2692
2518
  process.exit(1);
2693
2519
  }
2694
2520
 
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",
2521
+ let payload = {
2522
+ mission_ids: missionIds,
2523
+ new_status: newStatus,
2524
+ };
2525
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2526
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2527
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2528
+ args,
2529
+ operation: "mission-status-update",
2704
2530
  payload,
2705
2531
  });
2706
2532
 
@@ -2711,12 +2537,6 @@ async function runMissionStatus(args) {
2711
2537
 
2712
2538
  const timeoutMs = resolveTimeoutMs(args);
2713
2539
  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
2540
 
2721
2541
  let data;
2722
2542
  try {
@@ -2727,7 +2547,6 @@ async function runMissionStatus(args) {
2727
2547
  payload,
2728
2548
  idempotencyKey,
2729
2549
  clientSessionId,
2730
- writeApproval,
2731
2550
  });
2732
2551
  } catch (err) {
2733
2552
  if (err?.name === "AbortError") {
@@ -2802,17 +2621,16 @@ async function runMissionArchiveCommand(args) {
2802
2621
  process.exit(1);
2803
2622
  }
2804
2623
 
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;
2812
-
2813
- const operation = "mission-archive";
2814
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
2815
- args,
2624
+ let payload = {
2625
+ mission_ids: missionIds,
2626
+ };
2627
+ const reason = firstNonEmptyString(args.reason);
2628
+ if (reason) payload.reason = reason;
2629
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2630
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2631
+ const operation = "mission-archive";
2632
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2633
+ args,
2816
2634
  operation,
2817
2635
  payload,
2818
2636
  });
@@ -2825,12 +2643,6 @@ async function runMissionArchiveCommand(args) {
2825
2643
  const timeoutMs = resolveTimeoutMs(args);
2826
2644
  const apiBase = resolveApiBase(args);
2827
2645
  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
2646
 
2835
2647
  let data;
2836
2648
  try {
@@ -2842,7 +2654,6 @@ async function runMissionArchiveCommand(args) {
2842
2654
  payload,
2843
2655
  idempotencyKey,
2844
2656
  clientSessionId,
2845
- writeApproval,
2846
2657
  });
2847
2658
  } catch (err) {
2848
2659
  if (err?.name === "AbortError") {
@@ -2924,9 +2735,9 @@ async function runUpdateTeam(args) {
2924
2735
  process.exit(1);
2925
2736
  }
2926
2737
 
2927
- const payload = {
2928
- content: await resolveTeamUpdateContent(args),
2929
- };
2738
+ let payload = {
2739
+ content: await resolveTeamUpdateContent(args),
2740
+ };
2930
2741
 
2931
2742
  if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
2932
2743
  console.log(JSON.stringify(payload, null, 2));
@@ -2934,18 +2745,18 @@ async function runUpdateTeam(args) {
2934
2745
  }
2935
2746
 
2936
2747
  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({
2748
+ const apiBase = resolveApiBase(args);
2749
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2750
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2751
+ payload = withWriteApproval(args, payload, {
2752
+ artifactKind: "markdown",
2753
+ targets: ["project"],
2754
+ });
2755
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
2941
2756
  args,
2942
2757
  operation: "update_team",
2943
2758
  payload,
2944
2759
  });
2945
- const writeApproval = requireWriteApproval(args, payload, {
2946
- operation: "update_team",
2947
- artifactKind: "markdown",
2948
- });
2949
2760
 
2950
2761
  let data;
2951
2762
  try {
@@ -2957,7 +2768,6 @@ async function runUpdateTeam(args) {
2957
2768
  endpoint: "/project-assistant/project-comment",
2958
2769
  idempotencyKey,
2959
2770
  clientSessionId,
2960
- writeApproval,
2961
2771
  });
2962
2772
  } catch (err) {
2963
2773
  if (err?.name === "AbortError") {
@@ -2997,10 +2807,10 @@ async function runUpdateOwner(args) {
2997
2807
  process.exit(1);
2998
2808
  }
2999
2809
 
3000
- const payload = {
3001
- subject,
3002
- body_markdown: await resolveOwnerUpdateBody(args),
3003
- };
2810
+ let payload = {
2811
+ subject,
2812
+ body_markdown: await resolveOwnerUpdateBody(args),
2813
+ };
3004
2814
 
3005
2815
  if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
3006
2816
  console.log(JSON.stringify(payload, null, 2));
@@ -3008,18 +2818,18 @@ async function runUpdateOwner(args) {
3008
2818
  }
3009
2819
 
3010
2820
  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({
2821
+ const apiBase = resolveApiBase(args);
2822
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2823
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2824
+ payload = withWriteApproval(args, payload, {
2825
+ artifactKind: "markdown",
2826
+ targets: ["project"],
2827
+ });
2828
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
3015
2829
  args,
3016
2830
  operation: "update_owner",
3017
2831
  payload,
3018
2832
  });
3019
- const writeApproval = requireWriteApproval(args, payload, {
3020
- operation: "update_owner",
3021
- artifactKind: "markdown",
3022
- });
3023
2833
 
3024
2834
  let data;
3025
2835
  try {
@@ -3031,7 +2841,6 @@ async function runUpdateOwner(args) {
3031
2841
  endpoint: "/project-assistant/update-owner",
3032
2842
  idempotencyKey,
3033
2843
  clientSessionId,
3034
- writeApproval,
3035
2844
  });
3036
2845
  } catch (err) {
3037
2846
  if (err?.name === "AbortError") {
@@ -3079,11 +2888,11 @@ async function runUpdateClient(args) {
3079
2888
  process.exit(1);
3080
2889
  }
3081
2890
 
3082
- const payload = {
3083
- subject,
3084
- body_markdown: await resolveClientUpdateBody(args),
3085
- target_contact_ids: resolveTargetContactIds(args),
3086
- };
2891
+ let payload = {
2892
+ subject,
2893
+ body_markdown: await resolveClientUpdateBody(args),
2894
+ target_contact_ids: resolveTargetContactIds(args),
2895
+ };
3087
2896
 
3088
2897
  if (args["print-context"] || args.printContext || args["dry-run"] || args.dryRun) {
3089
2898
  console.log(JSON.stringify(payload, null, 2));
@@ -3091,18 +2900,18 @@ async function runUpdateClient(args) {
3091
2900
  }
3092
2901
 
3093
2902
  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({
2903
+ const apiBase = resolveApiBase(args);
2904
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
2905
+ if (clientSessionId) payload.client_session_id = clientSessionId;
2906
+ payload = withWriteApproval(args, payload, {
2907
+ artifactKind: "markdown",
2908
+ targets: ["project"],
2909
+ });
2910
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
3098
2911
  args,
3099
2912
  operation: "update_client",
3100
2913
  payload,
3101
2914
  });
3102
- const writeApproval = requireWriteApproval(args, payload, {
3103
- operation: "update_client",
3104
- artifactKind: "markdown",
3105
- });
3106
2915
 
3107
2916
  let data;
3108
2917
  try {
@@ -3114,7 +2923,6 @@ async function runUpdateClient(args) {
3114
2923
  endpoint: "/project-assistant/client-update-drafts",
3115
2924
  idempotencyKey,
3116
2925
  clientSessionId,
3117
- writeApproval,
3118
2926
  });
3119
2927
  } catch (err) {
3120
2928
  if (err?.name === "AbortError") {
@@ -3210,7 +3018,7 @@ function stringifyYaml(value) {
3210
3018
  function stableJsonStringify(value) {
3211
3019
  if (Array.isArray(value)) {
3212
3020
  return `[${value.map((item) => stableJsonStringify(item)).join(",")}]`;
3213
- }
3021
+ }
3214
3022
  if (value && typeof value === "object") {
3215
3023
  const entries = Object.keys(value)
3216
3024
  .sort()
@@ -3220,38 +3028,137 @@ function stableJsonStringify(value) {
3220
3028
  return JSON.stringify(value);
3221
3029
  }
3222
3030
 
3223
- function jsonStringifyEnsureAscii(value) {
3031
+ function pythonJsonStringify(value) {
3032
+ if (Array.isArray(value)) {
3033
+ return `[${value.map((item) => pythonJsonStringify(item)).join(", ")}]`;
3034
+ }
3035
+ if (value && typeof value === "object") {
3036
+ const entries = Object.keys(value)
3037
+ .filter((key) => value[key] !== undefined)
3038
+ .sort()
3039
+ .map((key) => `${jsonStringEnsureAscii(key)}: ${pythonJsonStringify(value[key])}`);
3040
+ return `{${entries.join(", ")}}`;
3041
+ }
3042
+ if (typeof value === "string") return jsonStringEnsureAscii(value);
3043
+ if (value === undefined) return "null";
3044
+ return JSON.stringify(value);
3045
+ }
3046
+
3047
+ function jsonStringEnsureAscii(value) {
3224
3048
  return JSON.stringify(String(value)).replace(/[^\x00-\x7F]/g, (char) => {
3225
3049
  const code = char.charCodeAt(0);
3226
3050
  return `\\u${code.toString(16).padStart(4, "0")}`;
3227
3051
  });
3228
3052
  }
3229
3053
 
3230
- function pythonJsonDumpsCompatible(value) {
3054
+ function canonicalizeWritePayload(value) {
3231
3055
  if (Array.isArray(value)) {
3232
- return `[${value.map((item) => pythonJsonDumpsCompatible(item)).join(", ")}]`;
3056
+ return value.map((item) => canonicalizeWritePayload(item));
3233
3057
  }
3234
3058
  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(", ")}}`;
3059
+ const next = {};
3060
+ for (const key of Object.keys(value)) {
3061
+ if (key === "write_approval" || key === "client_session_id") continue;
3062
+ if (value[key] === undefined) continue;
3063
+ next[key] = canonicalizeWritePayload(value[key]);
3064
+ }
3065
+ return next;
3066
+ }
3067
+ return value;
3068
+ }
3069
+
3070
+ function projectWritePayloadHash(payload) {
3071
+ const canonical = canonicalizeWritePayload(payload || {});
3072
+ return createHash("sha256").update(pythonJsonStringify(canonical)).digest("hex");
3073
+ }
3074
+
3075
+ function approvalArtifactKind(artifactPath, fallback = "yaml") {
3076
+ const ext = path.extname(String(artifactPath || "")).toLowerCase();
3077
+ if (ext === ".md" || ext === ".markdown") return "markdown";
3078
+ if (ext === ".json") return "json";
3079
+ if (ext === ".yml" || ext === ".yaml") return "yaml";
3080
+ return fallback || "yaml";
3081
+ }
3082
+
3083
+ function resolveWriteApprovalArtifact(args) {
3084
+ const artifactPath = firstNonEmptyString(
3085
+ args["approval-artifact"],
3086
+ args.approvalArtifact,
3087
+ args.approval_artifact
3088
+ );
3089
+ if (!artifactPath) {
3090
+ console.error("Missing --approval-artifact for confirmed live Myte write.");
3091
+ process.exit(1);
3239
3092
  }
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);
3093
+ const absPath = path.resolve(String(artifactPath));
3094
+ if (!fs.existsSync(absPath) || !fs.statSync(absPath).isFile()) {
3095
+ console.error(`Approval artifact not found: ${absPath}`);
3096
+ process.exit(1);
3247
3097
  }
3248
- return jsonStringifyEnsureAscii(value);
3098
+ const ext = path.extname(absPath).toLowerCase();
3099
+ if (![".md", ".markdown", ".yml", ".yaml", ".json"].includes(ext)) {
3100
+ console.error("Approval artifact must be a local .md, .markdown, .yml, .yaml, or .json file.");
3101
+ process.exit(1);
3102
+ }
3103
+ return { displayPath: String(artifactPath), absPath };
3104
+ }
3105
+
3106
+ function approvalTargets(values) {
3107
+ return uniqueNormalizedStrings(Array.isArray(values) ? values : [values]);
3108
+ }
3109
+
3110
+ function hasWriteApprovalArtifactArg(args) {
3111
+ return Boolean(firstNonEmptyString(
3112
+ args["approval-artifact"],
3113
+ args.approvalArtifact,
3114
+ args.approval_artifact
3115
+ ));
3116
+ }
3117
+
3118
+ function withWriteApproval(args, payload, { artifactKind = "yaml", targets = [], batchCount = null, required = true } = {}) {
3119
+ const confirmed = Boolean(args["confirm-write"] || args.confirmWrite || args.confirm_write);
3120
+ if (required && (!confirmed || !hasWriteApprovalArtifactArg(args))) {
3121
+ console.error("Missing --confirm-write --approval-artifact <path> for this live Myte write.");
3122
+ process.exit(1);
3123
+ }
3124
+ if (!confirmed) return payload;
3125
+ const { displayPath } = resolveWriteApprovalArtifact(args);
3126
+ const nextPayload = { ...(payload || {}) };
3127
+ const normalizedTargets = approvalTargets(targets);
3128
+ const approval = {
3129
+ approved: true,
3130
+ source: "explicit_user_approval",
3131
+ artifact_path: displayPath,
3132
+ artifact_kind: approvalArtifactKind(displayPath, artifactKind),
3133
+ payload_sha256: projectWritePayloadHash(nextPayload),
3134
+ };
3135
+ if (normalizedTargets.length) approval.targets = normalizedTargets;
3136
+ if (batchCount !== undefined && batchCount !== null) approval.batch_count = Number(batchCount);
3137
+ nextPayload.write_approval = approval;
3138
+ return nextPayload;
3139
+ }
3140
+
3141
+ function suggestionWriteTargets(items) {
3142
+ return (Array.isArray(items) ? items : [])
3143
+ .map((item, index) => {
3144
+ if (!item || typeof item !== "object") return `index:${index}`;
3145
+ const changeType = String(item.change_type || item.action || "suggestion").trim().toLowerCase() || "suggestion";
3146
+ const changeSet = item.change_set && typeof item.change_set === "object" ? item.change_set : item;
3147
+ return firstNonEmptyString(
3148
+ item.suggestion_id,
3149
+ item.mission_id,
3150
+ item.client_ref,
3151
+ changeSet.title ? `${changeType}:${String(changeSet.title).slice(0, 80)}` : "",
3152
+ `index:${index}`
3153
+ );
3154
+ })
3155
+ .filter(Boolean);
3249
3156
  }
3250
3157
 
3251
3158
  function parseYaml(text) {
3252
- const raw = String(text || "").trim();
3253
- if (!raw) return null;
3254
- if (raw.startsWith("{") || raw.startsWith("[")) {
3159
+ const raw = String(text || "").trim();
3160
+ if (!raw) return null;
3161
+ if (raw.startsWith("{") || raw.startsWith("[")) {
3255
3162
  return JSON.parse(raw);
3256
3163
  }
3257
3164
  return parseSimpleYaml(raw);
@@ -3519,19 +3426,123 @@ function resolvePortableWorkspace(repoNames) {
3519
3426
  };
3520
3427
  }
3521
3428
 
3522
- function resolveCommandCenterRoots(wrapperRoot, outputDir) {
3523
- const targetRoot = outputDir
3524
- ? path.resolve(process.cwd(), String(outputDir))
3525
- : path.join(wrapperRoot, "MyteCommandCenter");
3526
- return {
3527
- targetRoot,
3528
- dataRoot: path.join(targetRoot, "data"),
3529
- };
3530
- }
3531
-
3532
- function pruneLegacyCommandCenterArtifacts(dataRoot, options = {}) {
3533
- const {
3534
- bootstrap = false,
3429
+ function resolveCommandCenterRoots(wrapperRoot, outputDir) {
3430
+ const targetRoot = outputDir
3431
+ ? path.resolve(process.cwd(), String(outputDir))
3432
+ : path.join(wrapperRoot, "MyteCommandCenter");
3433
+ return {
3434
+ targetRoot,
3435
+ dataRoot: path.join(targetRoot, "data"),
3436
+ };
3437
+ }
3438
+
3439
+ function buildAgentsMyteApiMarkdown() {
3440
+ return ensureTrailingNewline([
3441
+ "# AgentsMyteAPI",
3442
+ "",
3443
+ "This file is generated by `myte bootstrap` as contextual guidance for coding agents using a project-scoped Myte API key.",
3444
+ "It does not grant permission, approve a write, or replace project authorization. Prefer the `myte` CLI over direct HTTP.",
3445
+ "",
3446
+ "## Auth And Discovery",
3447
+ "",
3448
+ "- Use `MYTE_API_KEY` or `MYTE_PROJECT_API_KEY` for `/api/project-assistant/*`.",
3449
+ "- Default API base: `https://api.myte.dev/api`.",
3450
+ "- Start with `myte config --json`, then `myte bootstrap --json`.",
3451
+ "- Use local IDs from `MyteCommandCenter/data/missions/*.yml`, `data/mission-ops.yml`, and `data/feedback.yml`.",
3452
+ "- `myte ai` is intentionally outside this project-scoped table. It uses `MYTEAI_API_KEY` and the Myte AI gateway, not `/api/project-assistant/*`.",
3453
+ "",
3454
+ "## Approval Artifact Rule",
3455
+ "",
3456
+ "Only the commands marked `required` below need `--confirm-write --approval-artifact <path>`.",
3457
+ "Approval artifacts are local `.md`, `.markdown`, `.yml`, `.yaml`, or `.json` files that list the operation, exact target IDs, proposed change, and batch count when relevant.",
3458
+ "The artifact is the human-reviewed record of the intended mutation. It does not grant permission, bypass owner/delegate checks, or replace idempotency; it gives the API enough evidence that a human approved this exact write payload.",
3459
+ "",
3460
+ "Minimum artifact contents for required actions:",
3461
+ "",
3462
+ "- `command`: the exact CLI command you plan to run.",
3463
+ "- `targets`: every affected project, feedback, request, suggestion, mission, PRD file, client ref, or contact id.",
3464
+ "- `proposed_change`: a complete summary of the text/status/content being sent.",
3465
+ "- `batch_count`: required when the command sends more than one item in one API call.",
3466
+ "- `reason` or `approval_note`: why the write is being sent now.",
3467
+ "",
3468
+ "Batch guidance:",
3469
+ "",
3470
+ "- Mission edit/create batches use `myte suggestions create` or `myte suggestions revise`; the artifact must list each item. For mission edits include `change_type: update`, the existing `mission_id`, and the changed fields. For new missions include `change_type: create`, the proposed title, description, and acceptance details. For revisions include each `suggestion_id` and expected revision.",
3471
+ "- PRD batches use one `myte create-prd fileA.md fileB.md ...`; the artifact must list each PRD file/title/client ref. `batch_count` must match the number of PRD items.",
3472
+ "- Feedback submit/revise/comment artifacts are usually the same YAML or markdown file being submitted. If the command uses inline text or stdin, create a separate approval memo that includes the feedback id or request id and the exact content.",
3473
+ "- Team, owner, and client update artifacts should name the audience and include the exact message body or a complete summary; client updates should include target contact ids when used.",
3474
+ "- `feedback review --request-ids`, `feedback move --feedback-ids`, `mission status --mission-ids`, `mission archive --mission-ids`, and `run-qaqc --mission-ids` are batched governed writes, but they are marked `no` below and should not receive approval artifact flags.",
3475
+ "",
3476
+ "Example approval artifact:",
3477
+ "",
3478
+ "```yaml",
3479
+ "command: myte suggestions create --file ./mission-suggestions.yml --confirm-write --approval-artifact ./mission-suggestions-approval.yml",
3480
+ "reason: Human approved this mission review batch after inspecting the proposed changes.",
3481
+ "batch_count: 2",
3482
+ "targets:",
3483
+ " - mission_id: M093",
3484
+ " change_type: update",
3485
+ " proposed_change: Mark acceptance criteria complete and clarify final QA note.",
3486
+ " - new_mission: Add deployment verification",
3487
+ " change_type: create",
3488
+ " proposed_change: Create a mission to verify deploy logs, smoke test, and rollback note.",
3489
+ "```",
3490
+ "",
3491
+ "| Action | CLI shape | API route | Class | Artifact approval |",
3492
+ "|---|---|---|---|---|",
3493
+ "| Config | `myte config --json` | `GET /project-assistant/config` | read | no |",
3494
+ "| Query | `myte query \"...\" --with-diff` | `POST /project-assistant/query`; polls `GET /project-assistant/query/<job_id>` when queued; `--with-diff` also reads `GET /project-assistant/config` | query job | no |",
3495
+ "| Bootstrap | `myte bootstrap --json` | `GET /project-assistant/bootstrap` and `GET /project-assistant/suggestions` | local sync | no |",
3496
+ "| QAQC sync | `myte sync-qaqc --json` | `GET /project-assistant/qaqc-sync` | local sync | no |",
3497
+ "| Run QAQC | `myte run-qaqc --mission-ids \"M001,M002\" --wait --sync --json` | `POST /project-assistant/run-qaqc`; `--wait` polls `GET /project-assistant/run-qaqc/<batch_id>`; `--sync` reads `GET /project-assistant/qaqc-sync` | governed write | no |",
3498
+ "| Mission status | `myte mission status --mission-ids \"M001\" --status done --json` | `POST /project-assistant/mission-status-update` | governed write | no |",
3499
+ "| Mission archive | `myte mission archive --mission-ids \"M001\" --reason \"...\" --json` | `POST /project-assistant/mission-archive` | governed write | no |",
3500
+ "| Suggestions sync | `myte suggestions sync --json` | `GET /project-assistant/suggestions` | local sync | no |",
3501
+ "| Suggestions create | `myte suggestions create --file ./create.yml --confirm-write --approval-artifact ./create.yml --json` | `POST /project-assistant/suggestions` | review-thread write | required |",
3502
+ "| Suggestions revise | `myte suggestions revise --file ./revise.yml --confirm-write --approval-artifact ./revise.yml --json` | `POST /project-assistant/suggestions/revise` | review-thread write | required |",
3503
+ "| Suggestions review | `myte suggestions review --file ./review.yml --json` | `POST /project-assistant/suggestions/review` | governed review write | no |",
3504
+ "| Feedback sync | `myte feedback-sync --json` | `GET /project-assistant/feedback-sync` | local sync | no |",
3505
+ "| Feedback get | `myte feedback get --feedback-id <id> --json` | `GET /project-assistant/feedback/<id>` | read | no |",
3506
+ "| Feedback history | `myte feedback history --feedback-id <id> --json` | `GET /project-assistant/feedback/<id>/events` | read | no |",
3507
+ "| Feedback local drafts | `myte feedback status/edit/assign/archive/refine ...` | none | local artifact only | no |",
3508
+ "| Feedback validate | `myte feedback validate --file ./review.yml --json` | `POST /project-assistant/feedback/<id>/refinement/validate` | validation | no |",
3509
+ "| Feedback submit | `myte feedback submit --file ./review.yml --confirm-write --approval-artifact ./review.yml --json` | `POST /project-assistant/feedback/<id>/refinement/requests` | review-request write | required |",
3510
+ "| Feedback revise | `myte feedback revise --request-id <id> --file ./review.yml --confirm-write --approval-artifact ./review.yml --json` | `POST /project-assistant/feedback-review-requests/<id>/revise` | review-request write | required |",
3511
+ "| Feedback reviews | `myte feedback reviews [--status open/terminal/all] [--request-id <id>] --json` (`requests` alias) | `GET /project-assistant/feedback-review-requests` or `GET /project-assistant/feedback-review-requests/<id>` | read | no |",
3512
+ "| Feedback review | `myte feedback review --request-id <id> --action approve/reject/request_changes/cancel [--reason \"...\"] --json` | `POST /project-assistant/feedback-review-requests/<id>/review`, `/request-changes`, or `/cancel` | governed review write | no |",
3513
+ "| Feedback batch review | `myte feedback review --request-ids \"<id1,id2>\" --action approve/reject/request_changes/cancel --json` | `POST /project-assistant/feedback-review-requests/batch-review` | governed review write | no |",
3514
+ "| Feedback move | `myte feedback move --feedback-id <id> --to-state in_progress --reason \"...\" --json` | `POST /project-assistant/feedback/<id>/board-move` | governed write | no |",
3515
+ "| Feedback batch move | `myte feedback move --feedback-ids \"<id1,id2>\" --to-state in_progress --json` | `POST /project-assistant/feedback/batch-board-move` | governed write | no |",
3516
+ "| Feedback comment | `myte feedback comment --feedback-id <id> --body-file ./comment.md --confirm-write --approval-artifact ./comment.md --json` | `POST /project-assistant/feedback/<id>/comments` | comment write | required |",
3517
+ "| Feedback undo | `myte feedback undo --feedback-id <id> --event-id <id> --reason \"...\" --json` | `POST /project-assistant/feedback/<id>/events/<event_id>/undo` | governed write | no |",
3518
+ "| Feedback PRD versions | `myte feedback prd-versions --feedback-id <id> --json` | `GET /project-assistant/feedback/<id>/prd/versions` | read | no |",
3519
+ "| Feedback PRD diff | `myte feedback prd-diff --feedback-id <id> --version-id <id>` | `GET /project-assistant/feedback/<id>/prd/versions/<version_id>/diff` | read | no |",
3520
+ "| Feedback apply | `myte feedback apply --file ./review.yml --json` | `POST /project-assistant/feedback/<id>/refinement/apply` | governed write | no |",
3521
+ "| Create PRD | `myte create-prd ./prd.md --confirm-write --approval-artifact ./prd.md --json` | `POST /project-assistant/create-prd` | PRD write | required |",
3522
+ "| Batch create PRDs | `myte create-prd ./a.md ./b.md --confirm-write --approval-artifact ./approval.md --json` | `POST /project-assistant/create-prds` | PRD write | required |",
3523
+ "| Team update | `myte update-team \"...\" --confirm-write --approval-artifact ./team.md --json` | `POST /project-assistant/project-comment` | project comment | required |",
3524
+ "| Owner update | `myte update-owner --subject \"...\" --body-file ./owner.md --confirm-write --approval-artifact ./owner.md --json` | `POST /project-assistant/update-owner` | owner email | required |",
3525
+ "| Client update | `myte update-client --subject \"...\" --body-file ./client.md --confirm-write --approval-artifact ./client.md --json` | `POST /project-assistant/client-update-drafts` | client draft | required |",
3526
+ "",
3527
+ "## Agent Rules",
3528
+ "",
3529
+ "- For `required` actions, draft the artifact, show it to the user, get explicit approval, then rerun with the approval flags.",
3530
+ "- For `no` actions, do not add approval flags. The API still enforces key binding, project permissions, idempotency, stale-state checks, and route validation.",
3531
+ "- For local draft actions, inspect the generated YAML before submitting or applying it.",
3532
+ "- Use `--print-context` or `--dry-run` where available to inspect payloads without sending a write.",
3533
+ ].join("\n"));
3534
+ }
3535
+
3536
+ function writeAgentsMyteApiGuide({ wrapperRoot, outputDir }) {
3537
+ const { targetRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
3538
+ const guidePath = path.join(targetRoot, "AgentsMyteAPI.md");
3539
+ writeTextFile(guidePath, buildAgentsMyteApiMarkdown());
3540
+ return guidePath;
3541
+ }
3542
+
3543
+ function pruneLegacyCommandCenterArtifacts(dataRoot, options = {}) {
3544
+ const {
3545
+ bootstrap = false,
3535
3546
  qaqc = false,
3536
3547
  feedback = false,
3537
3548
  missionOps = false,
@@ -3609,11 +3620,12 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
3609
3620
  writeYamlFile(path.join(missionsDir, `${missionPathId}.yml`), mission);
3610
3621
  });
3611
3622
 
3612
- if (snapshot.project && typeof snapshot.project === "object") {
3613
- writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
3614
- }
3615
-
3616
- const manifest = {
3623
+ if (snapshot.project && typeof snapshot.project === "object") {
3624
+ writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
3625
+ }
3626
+ const agentsMyteApiPath = writeAgentsMyteApiGuide({ wrapperRoot, outputDir });
3627
+
3628
+ const manifest = {
3617
3629
  schema_version: snapshot.schema_version || 1,
3618
3630
  generated_at: snapshot.generated_at || null,
3619
3631
  snapshot_hash: snapshot.snapshot_hash || null,
@@ -3626,12 +3638,13 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
3626
3638
  missions: missions.length,
3627
3639
  },
3628
3640
  };
3629
- return {
3630
- targetRoot,
3631
- dataRoot,
3632
- manifest,
3633
- };
3634
- }
3641
+ return {
3642
+ targetRoot,
3643
+ dataRoot,
3644
+ agentsMyteApiPath,
3645
+ manifest,
3646
+ };
3647
+ }
3635
3648
 
3636
3649
  function writeQaqcSnapshot({ snapshot, wrapperRoot, outputDir }) {
3637
3650
  const { targetRoot, dataRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
@@ -4514,7 +4527,7 @@ async function fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScop
4514
4527
  return body.data || {};
4515
4528
  }
4516
4529
 
4517
- async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4530
+ async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4518
4531
  return postSuggestionsMutation({
4519
4532
  apiBase,
4520
4533
  key,
@@ -4523,11 +4536,10 @@ async function createProjectSuggestions({ apiBase, key, timeoutMs, payload, idem
4523
4536
  payload,
4524
4537
  idempotencyKey,
4525
4538
  clientSessionId,
4526
- writeApproval,
4527
4539
  });
4528
4540
  }
4529
4541
 
4530
- async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4542
+ async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4531
4543
  return postSuggestionsMutation({
4532
4544
  apiBase,
4533
4545
  key,
@@ -4536,11 +4548,10 @@ async function reviseProjectSuggestions({ apiBase, key, timeoutMs, payload, idem
4536
4548
  payload,
4537
4549
  idempotencyKey,
4538
4550
  clientSessionId,
4539
- writeApproval,
4540
4551
  });
4541
4552
  }
4542
4553
 
4543
- async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId, writeApproval }) {
4554
+ async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idempotencyKey, clientSessionId }) {
4544
4555
  return postSuggestionsMutation({
4545
4556
  apiBase,
4546
4557
  key,
@@ -4549,7 +4560,6 @@ async function reviewProjectSuggestions({ apiBase, key, timeoutMs, payload, idem
4549
4560
  payload,
4550
4561
  idempotencyKey,
4551
4562
  clientSessionId,
4552
- writeApproval,
4553
4563
  });
4554
4564
  }
4555
4565
 
@@ -4658,20 +4668,19 @@ async function runCreatePrd(args) {
4658
4668
 
4659
4669
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
4660
4670
 
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",
4671
+ if (payloads.length === 1) {
4672
+ let data;
4673
+ let payload = payloads[0];
4674
+ if (clientSessionId) payload.client_session_id = clientSessionId;
4675
+ payload = withWriteApproval(args, payload, {
4676
+ artifactKind: "markdown",
4677
+ targets: [firstNonEmptyString(payload.client_ref, payload.title, "create-prd")],
4678
+ });
4679
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
4680
+ args,
4681
+ operation: "create_prd",
4668
4682
  payload,
4669
4683
  });
4670
- const writeApproval = requireWriteApproval(args, payload, {
4671
- operation: "create_prd",
4672
- artifactKind: "markdown",
4673
- targets: prdApprovalTargets(payload),
4674
- });
4675
4684
  try {
4676
4685
  data = await postSuggestionsMutation({
4677
4686
  apiBase,
@@ -4681,7 +4690,6 @@ async function runCreatePrd(args) {
4681
4690
  endpoint: "/project-assistant/create-prd",
4682
4691
  idempotencyKey,
4683
4692
  clientSessionId,
4684
- writeApproval,
4685
4693
  });
4686
4694
  } catch (err) {
4687
4695
  if (err?.name === "AbortError") {
@@ -4723,22 +4731,20 @@ async function runCreatePrd(args) {
4723
4731
  failed_count: 0,
4724
4732
  items: [],
4725
4733
  };
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",
4734
+
4735
+ try {
4736
+ let payload = { items: payloads };
4737
+ if (clientSessionId) payload.client_session_id = clientSessionId;
4738
+ payload = withWriteApproval(args, payload, {
4739
+ artifactKind: "markdown",
4740
+ targets: payloads.map((item, index) => firstNonEmptyString(item.client_ref, item.title, `index:${index}`)),
4741
+ batchCount: payloads.length,
4742
+ });
4743
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
4744
+ args,
4745
+ operation: "create_prds",
4733
4746
  payload,
4734
4747
  });
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
4748
  const data = await postSuggestionsMutation({
4743
4749
  apiBase,
4744
4750
  key,
@@ -4747,7 +4753,6 @@ async function runCreatePrd(args) {
4747
4753
  endpoint: "/project-assistant/create-prds",
4748
4754
  idempotencyKey,
4749
4755
  clientSessionId,
4750
- writeApproval,
4751
4756
  });
4752
4757
  aggregated.project_id = data.project_id || null;
4753
4758
  aggregated.created_count = Number(data.created_count || 0);
@@ -4932,10 +4937,11 @@ async function runBootstrap(args) {
4932
4937
  return;
4933
4938
  }
4934
4939
 
4935
- const writeResult = writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir });
4936
- summary.data_root = writeResult.dataRoot;
4937
-
4938
- if (includeSuggestions) {
4940
+ const writeResult = writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir });
4941
+ summary.data_root = writeResult.dataRoot;
4942
+ summary.agents_myte_api_path = writeResult.agentsMyteApiPath;
4943
+
4944
+ if (includeSuggestions) {
4939
4945
  let missionOpsSnapshot;
4940
4946
  try {
4941
4947
  missionOpsSnapshot = await fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScope });
@@ -4966,9 +4972,10 @@ async function runBootstrap(args) {
4966
4972
  console.log(`Output root: ${summary.output_root}`);
4967
4973
  console.log(`Configured repos: ${summary.repo_names.join(", ") || "(none)"}`);
4968
4974
  console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
4969
- if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
4970
- console.log(`Wrote: phases=${summary.counts.phases}, epics=${summary.counts.epics}, stories=${summary.counts.stories}, missions=${summary.counts.missions}`);
4971
- if (summary.mission_ops?.included) {
4975
+ if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
4976
+ console.log(`Wrote: phases=${summary.counts.phases}, epics=${summary.counts.epics}, stories=${summary.counts.stories}, missions=${summary.counts.missions}`);
4977
+ if (summary.agents_myte_api_path) console.log(`Wrote AgentsMyteAPI: ${summary.agents_myte_api_path}`);
4978
+ if (summary.mission_ops?.included) {
4972
4979
  console.log(`Wrote mission ops: total_threads=${summary.mission_ops.total_threads}, actionable_threads=${summary.mission_ops.actionable_threads}`);
4973
4980
  } else {
4974
4981
  console.log("Skipped mission ops sync.");
@@ -5304,7 +5311,8 @@ async function runFeedbackValidateOrApply(args, mode) {
5304
5311
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5305
5312
  process.exit(1);
5306
5313
  }
5307
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5314
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5315
+ let payload = artifactPayload;
5308
5316
  const feedbackId = firstNonEmptyString(args["feedback-id"], args.feedbackId, args.feedback_id, payload.feedback_id);
5309
5317
  if (!feedbackId) {
5310
5318
  console.error("Feedback refinement artifact is missing feedback_id.");
@@ -5314,26 +5322,19 @@ async function runFeedbackValidateOrApply(args, mode) {
5314
5322
 
5315
5323
  const timeoutMs = resolveTimeoutMs(args);
5316
5324
  const apiBase = resolveApiBase(args);
5317
- const clientSessionId = firstNonEmptyString(
5325
+ const clientSessionId = firstNonEmptyString(
5318
5326
  args["client-session-id"],
5319
5327
  args.clientSessionId,
5320
5328
  args.client_session_id,
5321
5329
  payload.client_session_id
5322
- );
5323
- const idempotencyKey = mode === "apply"
5324
- ? resolveProjectMutationIdempotencyKey({
5325
- args,
5330
+ );
5331
+ const idempotencyKey = mode === "apply"
5332
+ ? resolveProjectMutationIdempotencyKey({
5333
+ args,
5326
5334
  operation: `feedback_refinement_apply:${feedbackId}`,
5327
5335
  payload,
5328
5336
  })
5329
5337
  : 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
5338
 
5338
5339
  let data;
5339
5340
  try {
@@ -5346,7 +5347,6 @@ async function runFeedbackValidateOrApply(args, mode) {
5346
5347
  payload,
5347
5348
  idempotencyKey,
5348
5349
  clientSessionId,
5349
- writeApproval,
5350
5350
  });
5351
5351
  } catch (err) {
5352
5352
  if (args.json) {
@@ -5458,7 +5458,8 @@ async function runFeedbackSubmit(args) {
5458
5458
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
5459
5459
  process.exit(1);
5460
5460
  }
5461
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5461
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5462
+ let payload = artifactPayload;
5462
5463
  const feedbackId = firstNonEmptyString(args["feedback-id"], args.feedbackId, args.feedback_id, payload.feedback_id);
5463
5464
  if (!feedbackId) {
5464
5465
  console.error("Feedback refinement artifact is missing feedback_id.");
@@ -5466,22 +5467,21 @@ async function runFeedbackSubmit(args) {
5466
5467
  }
5467
5468
  const timeoutMs = resolveTimeoutMs(args);
5468
5469
  const apiBase = resolveApiBase(args);
5469
- const clientSessionId = firstNonEmptyString(
5470
+ const clientSessionId = firstNonEmptyString(
5470
5471
  args["client-session-id"],
5471
5472
  args.clientSessionId,
5472
5473
  args.client_session_id,
5473
5474
  payload.client_session_id
5474
- );
5475
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5475
+ );
5476
+ payload = withWriteApproval(args, payload, {
5477
+ artifactKind: "yaml",
5478
+ targets: [feedbackId],
5479
+ });
5480
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5476
5481
  args,
5477
5482
  operation: `feedback_refinement_request_submit:${feedbackId}`,
5478
5483
  payload,
5479
5484
  });
5480
- const writeApproval = requireWriteApproval(args, payload, {
5481
- operation: `feedback_refinement_request_submit:${feedbackId}`,
5482
- artifactKind: "yaml",
5483
- targets: [feedbackId],
5484
- });
5485
5485
 
5486
5486
  let data;
5487
5487
  try {
@@ -5493,7 +5493,6 @@ async function runFeedbackSubmit(args) {
5493
5493
  payload,
5494
5494
  idempotencyKey,
5495
5495
  clientSessionId,
5496
- writeApproval,
5497
5496
  });
5498
5497
  } catch (err) {
5499
5498
  if (args.json) {
@@ -5535,25 +5534,25 @@ async function runFeedbackRevise(args) {
5535
5534
  console.error("Missing --request-id.");
5536
5535
  process.exit(1);
5537
5536
  }
5538
- const { absPath, payload } = readFeedbackRefinementArtifact(args);
5537
+ const { absPath, payload: artifactPayload } = readFeedbackRefinementArtifact(args);
5538
+ let payload = artifactPayload;
5539
5539
  const timeoutMs = resolveTimeoutMs(args);
5540
5540
  const apiBase = resolveApiBase(args);
5541
- const clientSessionId = firstNonEmptyString(
5541
+ const clientSessionId = firstNonEmptyString(
5542
5542
  args["client-session-id"],
5543
5543
  args.clientSessionId,
5544
5544
  args.client_session_id,
5545
5545
  payload.client_session_id
5546
- );
5547
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5546
+ );
5547
+ payload = withWriteApproval(args, payload, {
5548
+ artifactKind: "yaml",
5549
+ targets: [requestId],
5550
+ });
5551
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5548
5552
  args,
5549
5553
  operation: `feedback_refinement_request_revise:${requestId}`,
5550
5554
  payload,
5551
5555
  });
5552
- const writeApproval = requireWriteApproval(args, payload, {
5553
- operation: `feedback_refinement_request_revise:${requestId}`,
5554
- artifactKind: "yaml",
5555
- targets: [requestId],
5556
- });
5557
5556
 
5558
5557
  let data;
5559
5558
  try {
@@ -5566,7 +5565,6 @@ async function runFeedbackRevise(args) {
5566
5565
  payload,
5567
5566
  idempotencyKey,
5568
5567
  clientSessionId,
5569
- writeApproval,
5570
5568
  });
5571
5569
  } catch (err) {
5572
5570
  if (args.json) {
@@ -5684,29 +5682,20 @@ async function runFeedbackReviewDecision(args) {
5684
5682
  process.exit(1);
5685
5683
  }
5686
5684
  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);
5694
- const apiBase = resolveApiBase(args);
5685
+ let payload = {
5686
+ ...(isPlainObject(filePayload) && !Array.isArray(filePayload) ? filePayload : {}),
5687
+ items,
5688
+ action,
5689
+ reason: reason || undefined,
5690
+ };
5691
+ const timeoutMs = resolveTimeoutMs(args);
5692
+ const apiBase = resolveApiBase(args);
5695
5693
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5696
5694
  const idempotencyKey = resolveProjectMutationIdempotencyKey({
5697
5695
  args,
5698
5696
  operation: `feedback_review_batch:${action}:${items.map((item) => item.request_id || item.id || "").join(",")}`,
5699
5697
  payload,
5700
5698
  });
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
5699
 
5711
5700
  let data;
5712
5701
  try {
@@ -5717,7 +5706,6 @@ async function runFeedbackReviewDecision(args) {
5717
5706
  payload,
5718
5707
  idempotencyKey,
5719
5708
  clientSessionId,
5720
- writeApproval,
5721
5709
  });
5722
5710
  } catch (err) {
5723
5711
  if (args.json) {
@@ -5745,8 +5733,8 @@ async function runFeedbackReviewDecision(args) {
5745
5733
  process.exit(1);
5746
5734
  }
5747
5735
 
5748
- let payload;
5749
- try {
5736
+ let payload;
5737
+ try {
5750
5738
  payload = filePayload && !Array.isArray(filePayload?.items)
5751
5739
  ? buildFeedbackReviewDecisionPayload({ ...args, file: "" }, action)
5752
5740
  : buildFeedbackReviewDecisionPayload(args, action);
@@ -5757,9 +5745,9 @@ async function runFeedbackReviewDecision(args) {
5757
5745
  console.error(err?.message || err);
5758
5746
  process.exit(1);
5759
5747
  }
5760
- const endpointAction = action === "request_changes" ? "request-changes" : action === "cancel" ? "cancel" : "review";
5761
- const timeoutMs = resolveTimeoutMs(args);
5762
- const apiBase = resolveApiBase(args);
5748
+ const endpointAction = action === "request_changes" ? "request-changes" : action === "cancel" ? "cancel" : "review";
5749
+ const timeoutMs = resolveTimeoutMs(args);
5750
+ const apiBase = resolveApiBase(args);
5763
5751
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id, payload.client_session_id);
5764
5752
  const operationAction = action === "request_changes" ? "request_changes" : action;
5765
5753
  const idempotencyKey = resolveProjectMutationIdempotencyKey({
@@ -5767,11 +5755,6 @@ async function runFeedbackReviewDecision(args) {
5767
5755
  operation: `feedback_review_request_${operationAction}:${requestId}`,
5768
5756
  payload,
5769
5757
  });
5770
- const writeApproval = requireWriteApproval(args, payload, {
5771
- operation: `feedback_review_request_${operationAction}:${requestId}`,
5772
- artifactKind: "yaml",
5773
- targets: [requestId],
5774
- });
5775
5758
 
5776
5759
  let data;
5777
5760
  try {
@@ -5784,7 +5767,6 @@ async function runFeedbackReviewDecision(args) {
5784
5767
  payload,
5785
5768
  idempotencyKey,
5786
5769
  clientSessionId,
5787
- writeApproval,
5788
5770
  });
5789
5771
  } catch (err) {
5790
5772
  if (args.json) {
@@ -5823,32 +5805,26 @@ async function runFeedbackMove(args) {
5823
5805
  process.exit(1);
5824
5806
  }
5825
5807
  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
- };
5808
+ let payload = {
5809
+ to_state: toState,
5810
+ reason: firstNonEmptyString(args.reason) || undefined,
5811
+ };
5830
5812
  const timeoutMs = resolveTimeoutMs(args);
5831
5813
  const apiBase = resolveApiBase(args);
5832
5814
  const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5833
5815
  const isBatch = feedbackIds.length > 1 || Boolean(firstNonEmptyString(args["feedback-ids"], args.feedbackIds, args.feedback_ids));
5834
- if (isBatch) {
5835
- payload.feedback_ids = feedbackIds;
5816
+ if (isBatch) {
5817
+ payload.feedback_ids = feedbackIds;
5836
5818
  if (fromState) {
5837
5819
  payload.from_states = Object.fromEntries(feedbackIds.map((id) => [id, fromState]));
5838
5820
  }
5839
5821
  const batchId = firstNonEmptyString(args["batch-id"], args.batchId, args.batch_id);
5840
- if (batchId) payload.batch_id = batchId;
5841
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5842
- args,
5843
- operation: `feedback_board_batch_move:${toState}:${feedbackIds.join(",")}`,
5822
+ if (batchId) payload.batch_id = batchId;
5823
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5824
+ args,
5825
+ operation: `feedback_board_batch_move:${toState}:${feedbackIds.join(",")}`,
5844
5826
  payload,
5845
5827
  });
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
5828
  let data;
5853
5829
  try {
5854
5830
  data = await postFeedbackBatchBoardMutation({
@@ -5858,7 +5834,6 @@ async function runFeedbackMove(args) {
5858
5834
  payload,
5859
5835
  idempotencyKey,
5860
5836
  clientSessionId,
5861
- writeApproval,
5862
5837
  });
5863
5838
  } catch (err) {
5864
5839
  if (args.json) {
@@ -5879,18 +5854,13 @@ async function runFeedbackMove(args) {
5879
5854
  if (postSync?.error) console.error(`Feedback sync warning: ${postSync.error}`);
5880
5855
  return;
5881
5856
  }
5882
-
5883
- payload.from_state = fromState;
5884
- const idempotencyKey = resolveProjectMutationIdempotencyKey({
5885
- args,
5886
- operation: `feedback_board_move:${feedbackId}`,
5857
+
5858
+ payload.from_state = fromState;
5859
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5860
+ args,
5861
+ operation: `feedback_board_move:${feedbackId}`,
5887
5862
  payload,
5888
5863
  });
5889
- const writeApproval = requireWriteApproval(args, payload, {
5890
- operation: `feedback_board_move:${feedbackId}`,
5891
- artifactKind: "yaml",
5892
- targets: [feedbackId],
5893
- });
5894
5864
 
5895
5865
  let data;
5896
5866
  try {
@@ -5903,7 +5873,6 @@ async function runFeedbackMove(args) {
5903
5873
  payload,
5904
5874
  idempotencyKey,
5905
5875
  clientSessionId,
5906
- writeApproval,
5907
5876
  });
5908
5877
  } catch (err) {
5909
5878
  if (args.json) {
@@ -5944,12 +5913,16 @@ async function runFeedbackComment(args) {
5944
5913
  process.exit(1);
5945
5914
  }
5946
5915
 
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({
5916
+ let payload = { content };
5917
+ const timeoutMs = resolveTimeoutMs(args);
5918
+ const apiBase = resolveApiBase(args);
5919
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5920
+ if (clientSessionId) payload.client_session_id = clientSessionId;
5921
+ payload = withWriteApproval(args, payload, {
5922
+ artifactKind: "markdown",
5923
+ targets: [feedbackId],
5924
+ });
5925
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5953
5926
  args,
5954
5927
  operation: `feedback_comment_create:${feedbackId}`,
5955
5928
  payload,
@@ -5960,12 +5933,6 @@ async function runFeedbackComment(args) {
5960
5933
  return;
5961
5934
  }
5962
5935
 
5963
- const writeApproval = requireWriteApproval(args, payload, {
5964
- operation: `feedback_comment_create:${feedbackId}`,
5965
- artifactKind: "markdown",
5966
- targets: [feedbackId],
5967
- });
5968
-
5969
5936
  let data;
5970
5937
  try {
5971
5938
  data = await postFeedbackComment({
@@ -5976,7 +5943,6 @@ async function runFeedbackComment(args) {
5976
5943
  payload,
5977
5944
  idempotencyKey,
5978
5945
  clientSessionId,
5979
- writeApproval,
5980
5946
  });
5981
5947
  } catch (err) {
5982
5948
  if (args.json) {
@@ -6025,27 +5991,16 @@ async function runFeedbackUndo(args) {
6025
5991
  console.error("Missing --event-id.");
6026
5992
  process.exit(1);
6027
5993
  }
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({
6033
- args,
6034
- operation: `feedback_event_undo:${feedbackId}:${eventId}`,
5994
+ let payload = { reason: firstNonEmptyString(args.reason) || undefined };
5995
+ const timeoutMs = resolveTimeoutMs(args);
5996
+ const apiBase = resolveApiBase(args);
5997
+ const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
5998
+ const idempotencyKey = resolveProjectMutationIdempotencyKey({
5999
+ args,
6000
+ operation: `feedback_event_undo:${feedbackId}:${eventId}`,
6035
6001
  payload,
6036
6002
  });
6037
6003
 
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
6004
  let data;
6050
6005
  try {
6051
6006
  data = await postFeedbackBoardMutation({
@@ -6057,7 +6012,6 @@ async function runFeedbackUndo(args) {
6057
6012
  payload,
6058
6013
  idempotencyKey,
6059
6014
  clientSessionId,
6060
- writeApproval,
6061
6015
  });
6062
6016
  } catch (err) {
6063
6017
  if (args.json) {
@@ -6343,14 +6297,22 @@ async function buildSuggestionsMutationContext(args, mode) {
6343
6297
  if (clientSessionId && !payload.client_session_id) {
6344
6298
  payload.client_session_id = clientSessionId;
6345
6299
  }
6346
- if (mode === "create") {
6347
- validateSuggestionsCreatePayload(payload);
6348
- } else if (mode === "revise") {
6349
- validateSuggestionsRevisePayload(payload);
6350
- }
6351
- const idempotencyKey = resolveSuggestionsIdempotencyKey({
6352
- args,
6353
- mode,
6300
+ if (mode === "create") {
6301
+ validateSuggestionsCreatePayload(payload);
6302
+ } else if (mode === "revise") {
6303
+ validateSuggestionsRevisePayload(payload);
6304
+ }
6305
+ const items = Array.isArray(payload?.items) ? payload.items : [];
6306
+ if (mode === "create" || mode === "revise") {
6307
+ payload = withWriteApproval(args, payload, {
6308
+ artifactKind: "yaml",
6309
+ targets: suggestionWriteTargets(items),
6310
+ batchCount: items.length > 1 ? items.length : null,
6311
+ });
6312
+ }
6313
+ const idempotencyKey = resolveSuggestionsIdempotencyKey({
6314
+ args,
6315
+ mode,
6354
6316
  actorScope,
6355
6317
  payload,
6356
6318
  });
@@ -6436,14 +6398,6 @@ async function runSuggestionsCreate(args) {
6436
6398
  return;
6437
6399
  }
6438
6400
 
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
6401
  let data;
6448
6402
  try {
6449
6403
  data = await createProjectSuggestions({
@@ -6453,7 +6407,6 @@ async function runSuggestionsCreate(args) {
6453
6407
  payload: context.payload,
6454
6408
  idempotencyKey: context.idempotencyKey,
6455
6409
  clientSessionId: context.clientSessionId,
6456
- writeApproval,
6457
6410
  });
6458
6411
  } catch (err) {
6459
6412
  console.error("Suggestions create failed:", err?.message || err);
@@ -6530,14 +6483,6 @@ async function runSuggestionsRevise(args) {
6530
6483
  return;
6531
6484
  }
6532
6485
 
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
6486
  let data;
6542
6487
  try {
6543
6488
  data = await reviseProjectSuggestions({
@@ -6547,7 +6492,6 @@ async function runSuggestionsRevise(args) {
6547
6492
  payload: context.payload,
6548
6493
  idempotencyKey: context.idempotencyKey,
6549
6494
  clientSessionId: context.clientSessionId,
6550
- writeApproval,
6551
6495
  });
6552
6496
  } catch (err) {
6553
6497
  console.error("Suggestions revise failed:", err?.message || err);
@@ -6622,14 +6566,6 @@ async function runSuggestionsReview(args) {
6622
6566
  return;
6623
6567
  }
6624
6568
 
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
6569
  let data;
6634
6570
  try {
6635
6571
  data = await reviewProjectSuggestions({
@@ -6639,7 +6575,6 @@ async function runSuggestionsReview(args) {
6639
6575
  payload: context.payload,
6640
6576
  idempotencyKey: context.idempotencyKey,
6641
6577
  clientSessionId: context.clientSessionId,
6642
- writeApproval,
6643
6578
  });
6644
6579
  } catch (err) {
6645
6580
  console.error("Suggestions review failed:", err?.message || err);