@mytegroupinc/myte-core 0.0.55 → 0.0.57
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/README.md +44 -19
- package/cli.js +740 -203
- package/lib/build-harness.js +378 -0
- package/lib/certification-manifest.js +2 -0
- package/lib/package-update-check.js +228 -0
- package/mytecody-cli.js +44 -86
- package/package.json +1 -1
- package/scripts/feedback-live-full-harness.js +145 -0
- package/scripts/mission-live-full-harness.js +10 -9
- package/scripts/project-assistant-live-full-harness.js +208 -174
package/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ const net = require("net");
|
|
|
14
14
|
const tls = require("tls");
|
|
15
15
|
const { createHash, randomUUID } = require("crypto");
|
|
16
16
|
const { spawnSync } = require("child_process");
|
|
17
|
-
const {
|
|
17
|
+
const {
|
|
18
18
|
DEFAULT_MYTEAI_BASE,
|
|
19
19
|
buildSimpleAiPayload,
|
|
20
20
|
callMyteAiChat,
|
|
@@ -22,10 +22,23 @@ const {
|
|
|
22
22
|
getMyteAiKey,
|
|
23
23
|
normalizeJsonAssistantText,
|
|
24
24
|
normalizeMyteAiBase,
|
|
25
|
-
} = require("./lib/ai-gateway");
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
} = require("./lib/ai-gateway");
|
|
26
|
+
const {
|
|
27
|
+
buildBuildHarnessMarkdown,
|
|
28
|
+
buildTopology,
|
|
29
|
+
qaqcMissionCoverage,
|
|
30
|
+
verifyTopology,
|
|
31
|
+
} = require("./lib/build-harness");
|
|
32
|
+
const {
|
|
33
|
+
checkPackageUpdate,
|
|
34
|
+
packageUpdateNotice,
|
|
35
|
+
} = require("./lib/package-update-check");
|
|
36
|
+
|
|
37
|
+
const DEFAULT_API_BASE = "https://api.myte.dev";
|
|
38
|
+
const PACKAGE_NAME = "myte";
|
|
39
|
+
const PACKAGE_VERSION = require("./package.json").version;
|
|
40
|
+
const DEFAULT_PACKAGE_LATEST_URL = "https://registry.npmjs.org/myte/latest";
|
|
41
|
+
const DEFAULT_DIFF_LIMIT_CHARS = 500_000;
|
|
29
42
|
const DEFAULT_FEEDBACK_SYNC_PAGE_SIZE = 50;
|
|
30
43
|
const MAX_PRD_DOCUMENT_MARKDOWN_CHARS = 250_000;
|
|
31
44
|
const REMOVED_COMMAND_MESSAGES = {
|
|
@@ -74,7 +87,10 @@ function loadEnv() {
|
|
|
74
87
|
return envPath;
|
|
75
88
|
}
|
|
76
89
|
|
|
77
|
-
function splitCommand(argv) {
|
|
90
|
+
function splitCommand(argv) {
|
|
91
|
+
if (argv[0] === "build" && argv[1] === "verify") {
|
|
92
|
+
return { command: "build-verify", rest: argv.slice(2) };
|
|
93
|
+
}
|
|
78
94
|
if (argv[0] === "mission") {
|
|
79
95
|
if (argv[1] === "status" && argv[2] === "update") {
|
|
80
96
|
return { command: "mission-status", rest: argv.slice(3) };
|
|
@@ -203,7 +219,8 @@ function printHelp() {
|
|
|
203
219
|
" myte doctor [--json] [--timeout-ms <ms>] [--base-url <url>]",
|
|
204
220
|
" myte info [--json]",
|
|
205
221
|
" myte --version [--verbose] [--json]",
|
|
206
|
-
" myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
|
|
222
|
+
" myte bootstrap [--output-dir ./MyteCommandCenter] [--json]",
|
|
223
|
+
" myte build verify [--output-dir ./MyteCommandCenter] [--semantic-review-file <path>] [--repository-gates-file <path>] [--qaqc-file <path>] [--json]",
|
|
207
224
|
" myte run-qaqc --mission-ids \"M001[,M002...]\" [--wait] [--sync] [--force] [--json]",
|
|
208
225
|
" myte mission status --mission-ids \"M001[,M002...]\" --status todo|in_progress|done [--no-sync] [--json]",
|
|
209
226
|
" myte mission archive --mission-ids \"M001[,M002...]\" [--reason \"...\"] [--no-sync] [--json]",
|
|
@@ -231,6 +248,12 @@ function printHelp() {
|
|
|
231
248
|
" myte feedback move --feedback-id <id> --to-state in_progress --from-state todo --reason \"...\" [--json]",
|
|
232
249
|
" myte feedback move --feedback-ids \"<id1,id2>\" --to-state in_progress --reason \"...\" [--json]",
|
|
233
250
|
" myte feedback comment --feedback-id <id> --body \"...\" [--body-file ./comment.md] --confirm-write --approval-artifact <path> [--json]",
|
|
251
|
+
" myte feedback tickets list --feedback-id <id> [--json]",
|
|
252
|
+
" myte feedback tickets create --feedback-id <id> --file ./ticket.yml --confirm-write --approval-artifact <path> [--json]",
|
|
253
|
+
" myte feedback tickets create --feedback-id <id> --file ./ticket-batch.yml --dry-run [--json]",
|
|
254
|
+
" myte feedback tickets update --feedback-id <id> --ticket-ids \"<id1,id2>\" --status in_progress --confirm-write --approval-artifact <path> [--json]",
|
|
255
|
+
" myte feedback tickets update --feedback-id <id> --file ./ticket-updates.yml --dry-run [--json]",
|
|
256
|
+
" myte feedback tickets coverage --feedback-id <id> --ticket-id <id> --version-id <id> --document-id <id> --coverage-status covered --confirm-write --approval-artifact <path> [--json]",
|
|
234
257
|
" myte feedback undo --feedback-id <id> --event-id <id> --reason \"...\" [--json]",
|
|
235
258
|
" myte feedback prd-versions --feedback-id <id> [--json]",
|
|
236
259
|
" myte feedback prd-diff --feedback-id <id> --version-id <id> [--compare-to <id>] [--document-id <id>] [--json]",
|
|
@@ -255,13 +278,26 @@ function printHelp() {
|
|
|
255
278
|
" - Set MYTE_API_KEY in a workspace .env (or env var)",
|
|
256
279
|
" - Set MYTEAI_API_KEY in a workspace .env (or env var) for `myte ai`",
|
|
257
280
|
"",
|
|
258
|
-
"bootstrap contract:",
|
|
281
|
+
"bootstrap contract:",
|
|
259
282
|
" - Run from any workspace where you want local MyteCommandCenter data written",
|
|
260
283
|
" - Writes MyteCommandCenter/data/project.yml plus phases, epics, stories, and missions locally",
|
|
261
284
|
" - Also refreshes mission review threads into MyteCommandCenter/data/mission-ops.yml by default",
|
|
262
285
|
" - Uses the project-scoped bootstrap snapshot from the Myte API",
|
|
263
286
|
" - Mission cards include richer execution context like complexity, estimated_hours, due_date, subtasks, technical_requirements, resources_needed, labels, and normalized test_cases",
|
|
264
|
-
" - Use --no-suggestions only when you intentionally want mission cards without mission review-thread state",
|
|
287
|
+
" - Use --no-suggestions only when you intentionally want mission cards without mission review-thread state",
|
|
288
|
+
" - Also writes MyteBuildHarness.md and data/build-topology.yml with the project execution protocol and convergence checks",
|
|
289
|
+
"",
|
|
290
|
+
"build verify contract:",
|
|
291
|
+
" - Verifies the local bootstrap topology and evidence without mutating Myte state",
|
|
292
|
+
" - Requires explicit semantic/UI review and repository-gate evidence files for a passing result",
|
|
293
|
+
" - Reports missing acceptance criteria, test cases, orphan missions, and missing intent topology",
|
|
294
|
+
" - Requires readable terminal QAQC coverage for every active mission, with no failed or pending cases",
|
|
295
|
+
" - Does not run QAQC, repository tests, or semantic review; it verifies the evidence those steps produced",
|
|
296
|
+
"",
|
|
297
|
+
"package update notices:",
|
|
298
|
+
" - Successful commands perform a cached, best-effort check for a newer myte package",
|
|
299
|
+
" - Notices go to stderr so --json stdout remains machine-readable",
|
|
300
|
+
" - Disable with --no-update-check or MYTE_PACKAGE_UPDATE_CHECK=0",
|
|
265
301
|
"",
|
|
266
302
|
"sync-qaqc contract:",
|
|
267
303
|
" - Run from any workspace where you want local MyteCommandCenter data written",
|
|
@@ -311,7 +347,8 @@ function printHelp() {
|
|
|
311
347
|
" - Explicit assignees must be the project owner or an active assigned project collaborator",
|
|
312
348
|
" - Never put the full PRD in description. The complete PRD must live in the markdown file body",
|
|
313
349
|
" - Each item carries the full PRD markdown blob as prd_markdown/ticket_markdown",
|
|
314
|
-
" - Backend stores that blob as the renderable PRD document source, mirrors it for PRD text search/sync, and generates a DOCX attachment",
|
|
350
|
+
" - Backend stores that blob as the renderable PRD document source, mirrors it for PRD text search/sync, and generates a DOCX attachment",
|
|
351
|
+
" - Successful creation returns active_prd_version_id, active_prd_version_number, and primary_document_id for immediate sub-ticket linking",
|
|
315
352
|
" - Renderer-friendly PRDs use one # title, ## sections, bullets, GitHub tables/checklists where useful, and code fences only for real code/config",
|
|
316
353
|
" - Do not wrap the whole PRD in a ```markdown fence; generic .md/.docx/.pdf/.txt uploads are normal attachments unless created as PRDs",
|
|
317
354
|
" - PRD DOCX content: the markdown body is stored verbatim",
|
|
@@ -320,10 +357,10 @@ function printHelp() {
|
|
|
320
357
|
" - Creates a project team comment through /api/project-assistant/project-comment",
|
|
321
358
|
" - Required: content (inline, --content, or --stdin)",
|
|
322
359
|
"",
|
|
323
|
-
"update-owner contract:",
|
|
324
|
-
" - Queues a durable owner email delivery through /api/project-assistant/update-owner",
|
|
360
|
+
"update-owner contract:",
|
|
361
|
+
" - Queues a durable owner email delivery through /api/project-assistant/update-owner",
|
|
325
362
|
" - Required: --subject and body markdown (via --body-markdown, --body-file, positional file/text, or --stdin)",
|
|
326
|
-
" - Resolves the owner synchronously; the existing notification worker delivers with a deterministic delivery id",
|
|
363
|
+
" - Resolves the owner synchronously; the existing notification worker delivers with a deterministic delivery id",
|
|
327
364
|
"",
|
|
328
365
|
"update-client contract:",
|
|
329
366
|
" - Creates a client update draft through /api/project-assistant/client-update-drafts",
|
|
@@ -352,6 +389,9 @@ function printHelp() {
|
|
|
352
389
|
" - move with --feedback-ids sends one batch board move request; it supports active-state moves and authorized archive",
|
|
353
390
|
" - comment creates text-only feedback-specific comments; attachments remain web UI only for this endpoint",
|
|
354
391
|
" - project-key API/CLI cannot unarchive archived Feedback; use the web archived Feedback view",
|
|
392
|
+
" - tickets list/create/update/coverage govern multiple engineering subtickets linked to the same PRD; create/update files accept JSON or YAML and batches are capped at 100",
|
|
393
|
+
" - Each ticket may link at most 25 PRD documents; the backend rejects version/document links outside that exact PRD before mutation",
|
|
394
|
+
" - tickets create/update --dry-run uses the server validation routes without mutating; live writes require revisions, idempotency and a structured approval artifact",
|
|
355
395
|
" - get/history read current feedback snapshots and audited event history through the project-key API",
|
|
356
396
|
" - prd-versions/prd-diff expose retained PRD versions and backend-generated per-document text diffs",
|
|
357
397
|
" - edit/refine with --prd-file updates a review artifact only; --document-id targets one document in a PRD document set",
|
|
@@ -361,7 +401,7 @@ function printHelp() {
|
|
|
361
401
|
" - apply is idempotent and does not rewrite local feedback.yml; run feedback-sync after apply to refresh local state",
|
|
362
402
|
"",
|
|
363
403
|
"approval artifact requirements:",
|
|
364
|
-
" - Required: create-prd, update-team, update-owner, update-client, feedback submit/revise/comment, suggestions create/revise",
|
|
404
|
+
" - Required: create-prd, update-team, update-owner, update-client, feedback submit/revise/comment, feedback tickets create/update/coverage, suggestions create/revise",
|
|
365
405
|
" - Not required: run-qaqc, mission status/archive, suggestions review, feedback review/move/undo/apply",
|
|
366
406
|
" - Never required: read/sync/query/validate commands, --print-context, --dry-run, and feedback local draft commands",
|
|
367
407
|
" - For required actions, prepare a .md/.markdown/.yml/.yaml/.json artifact, show it to the user, then rerun with --confirm-write --approval-artifact <path>",
|
|
@@ -383,6 +423,7 @@ function printHelp() {
|
|
|
383
423
|
" - move: direct audited board movement for allowed states; governed transitions remain backend-authorized",
|
|
384
424
|
" - document-add: create a governed proposal to append one linked PRD document to an existing Feedback item",
|
|
385
425
|
" - comment: create a text-only comment on one Feedback item",
|
|
426
|
+
" - tickets: list, validate, create and revision-fence single or batch PRD subticket lifecycle changes",
|
|
386
427
|
" - undo: reverse an audited board event when conflict checks allow it",
|
|
387
428
|
" - prd-versions: list retained PRD baselines/revisions for a feedback item",
|
|
388
429
|
" - prd-diff: fetch backend-generated text diff between PRD versions",
|
|
@@ -418,6 +459,10 @@ function printHelp() {
|
|
|
418
459
|
" --target-contact-ids Comma-separated client contact ObjectIds",
|
|
419
460
|
" --feedback-id <id> Feedback ObjectId for feedback review commands",
|
|
420
461
|
" --feedback-ids <ids> Comma-separated Feedback ObjectIds for batch feedback move",
|
|
462
|
+
" --ticket-id <id> Feedback PRD subticket ObjectId for single update or coverage",
|
|
463
|
+
" --ticket-ids <ids> Comma-separated subticket ObjectIds for one batch update",
|
|
464
|
+
" --severity <value> Ticket severity target: Critical, High, Medium, Low, or Info",
|
|
465
|
+
" --coverage-status <value> Ticket PRD coverage: not_covered, partially_covered, covered, or not_applicable",
|
|
421
466
|
" --request-id <id> Feedback review request ObjectId for submit/revise/review flows",
|
|
422
467
|
" --request-ids <ids> Comma-separated Feedback review request ObjectIds for batch review",
|
|
423
468
|
" --event-id <id> Feedback board event ObjectId for undo",
|
|
@@ -477,6 +522,9 @@ function printHelp() {
|
|
|
477
522
|
" myte feedback review --request-id 507f1f77bcf86cd799439022 --action approve --reason \"Looks correct\" --json",
|
|
478
523
|
" myte feedback move --feedback-id 507f1f77bcf86cd799439011 --to-state in_progress --reason \"Started\" --json",
|
|
479
524
|
" myte feedback comment --feedback-id 507f1f77bcf86cd799439011 --body-file ./comments/implementation.md --confirm-write --approval-artifact ./comments/implementation.md --json",
|
|
525
|
+
" myte feedback tickets list --feedback-id 507f1f77bcf86cd799439011 --json",
|
|
526
|
+
" myte feedback tickets create --feedback-id 507f1f77bcf86cd799439011 --file ./tickets/auth.yml --dry-run --json",
|
|
527
|
+
" myte feedback tickets update --feedback-id 507f1f77bcf86cd799439011 --ticket-ids \"507f1f77bcf86cd799439021,507f1f77bcf86cd799439022\" --status in_progress --confirm-write --approval-artifact ./tickets/update.yml --json",
|
|
480
528
|
" myte feedback document-add --feedback-id 507f1f77bcf86cd799439011 --prd-file ./drafts/sidebar-branding.md --title \"Individual Use Sidebar Branding\" --reason \"Link governed UX addendum to the Feedback context.\"",
|
|
481
529
|
" myte feedback validate --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-status.yml --json",
|
|
482
530
|
" myte feedback apply --file ./MyteCommandCenter/reviews/feedback/507f1f77bcf86cd799439011-status.yml --json",
|
|
@@ -991,7 +1039,7 @@ function normalizeApiBase(baseRaw) {
|
|
|
991
1039
|
return base.endsWith("/api") ? base : `${base}/api`;
|
|
992
1040
|
}
|
|
993
1041
|
|
|
994
|
-
async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
|
|
1042
|
+
async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
|
|
995
1043
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : undefined;
|
|
996
1044
|
let timedOut = false;
|
|
997
1045
|
const timeoutId =
|
|
@@ -2589,6 +2637,59 @@ async function postFeedbackComment({ apiBase, key, timeoutMs, feedbackId, payloa
|
|
|
2589
2637
|
return body.data || {};
|
|
2590
2638
|
}
|
|
2591
2639
|
|
|
2640
|
+
function parseFeedbackTicketIdsArg(args) {
|
|
2641
|
+
return dedupeStrings(
|
|
2642
|
+
parseCsvValues(
|
|
2643
|
+
args["ticket-ids"],
|
|
2644
|
+
args.ticketIds,
|
|
2645
|
+
args.ticket_ids,
|
|
2646
|
+
args["ticket-id"],
|
|
2647
|
+
args.ticketId,
|
|
2648
|
+
args.ticket_id
|
|
2649
|
+
)
|
|
2650
|
+
);
|
|
2651
|
+
}
|
|
2652
|
+
|
|
2653
|
+
async function requestFeedbackTickets({
|
|
2654
|
+
apiBase,
|
|
2655
|
+
key,
|
|
2656
|
+
timeoutMs,
|
|
2657
|
+
feedbackId,
|
|
2658
|
+
endpoint = "",
|
|
2659
|
+
method = "GET",
|
|
2660
|
+
payload,
|
|
2661
|
+
idempotencyKey,
|
|
2662
|
+
clientSessionId,
|
|
2663
|
+
}) {
|
|
2664
|
+
const fetchFn = await getFetch();
|
|
2665
|
+
const suffix = endpoint ? `/${String(endpoint).replace(/^\/+/, "")}` : "";
|
|
2666
|
+
const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/tickets${suffix}`;
|
|
2667
|
+
const headers = {
|
|
2668
|
+
Authorization: `Bearer ${key}`,
|
|
2669
|
+
...(String(clientSessionId || "").trim() ? { "X-Client-Session-Id": String(clientSessionId).trim() } : {}),
|
|
2670
|
+
};
|
|
2671
|
+
if (payload !== undefined) headers["Content-Type"] = "application/json";
|
|
2672
|
+
if (idempotencyKey) headers["X-Idempotency-Key"] = String(idempotencyKey).trim();
|
|
2673
|
+
const { resp, body } = await fetchJsonWithTimeout(
|
|
2674
|
+
fetchFn,
|
|
2675
|
+
url,
|
|
2676
|
+
{
|
|
2677
|
+
method,
|
|
2678
|
+
headers,
|
|
2679
|
+
...(payload !== undefined ? { body: JSON.stringify(payload || {}) } : {}),
|
|
2680
|
+
},
|
|
2681
|
+
timeoutMs
|
|
2682
|
+
);
|
|
2683
|
+
if (!resp.ok || body.status !== "success") {
|
|
2684
|
+
const msg = body?.message || `Feedback ticket request failed (${resp.status})`;
|
|
2685
|
+
const err = new Error(msg);
|
|
2686
|
+
err.status = resp.status;
|
|
2687
|
+
err.data = body?.data;
|
|
2688
|
+
throw err;
|
|
2689
|
+
}
|
|
2690
|
+
return body.data || {};
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2592
2693
|
async function fetchFeedbackPrdVersions({ apiBase, key, timeoutMs, feedbackId }) {
|
|
2593
2694
|
const fetchFn = await getFetch();
|
|
2594
2695
|
const url = `${apiBase}/project-assistant/feedback/${encodeURIComponent(String(feedbackId || ""))}/prd/versions`;
|
|
@@ -2807,7 +2908,7 @@ async function createMissionArchiveUpdate({ apiBase, key, timeoutMs, endpoint, p
|
|
|
2807
2908
|
return body.data || {};
|
|
2808
2909
|
}
|
|
2809
2910
|
|
|
2810
|
-
async function fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId }) {
|
|
2911
|
+
async function fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId }) {
|
|
2811
2912
|
const fetchFn = await getFetch();
|
|
2812
2913
|
const url = `${apiBase}/project-assistant/run-qaqc/${encodeURIComponent(String(batchId || ""))}`;
|
|
2813
2914
|
const { resp, body } = await fetchJsonWithTimeout(
|
|
@@ -2832,15 +2933,15 @@ async function fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId }) {
|
|
|
2832
2933
|
if (retryAfter) err.retryAfter = retryAfter;
|
|
2833
2934
|
throw err;
|
|
2834
2935
|
}
|
|
2835
|
-
const data = body.data || {};
|
|
2836
|
-
Object.defineProperty(data, "_nextPollAfterMs", {
|
|
2837
|
-
value: successfulPollDelayMs(data, resp),
|
|
2838
|
-
enumerable: false,
|
|
2839
|
-
});
|
|
2840
|
-
return data;
|
|
2936
|
+
const data = body.data || {};
|
|
2937
|
+
Object.defineProperty(data, "_nextPollAfterMs", {
|
|
2938
|
+
value: successfulPollDelayMs(data, resp),
|
|
2939
|
+
enumerable: false,
|
|
2940
|
+
});
|
|
2941
|
+
return data;
|
|
2841
2942
|
}
|
|
2842
2943
|
|
|
2843
|
-
function resolveRetryAfterMs(err, fallbackMs = 5_000) {
|
|
2944
|
+
function resolveRetryAfterMs(err, fallbackMs = 5_000) {
|
|
2844
2945
|
const retryAfterRaw = firstNonEmptyString(err?.retryAfter);
|
|
2845
2946
|
const retryAfterSeconds = Number.parseInt(String(retryAfterRaw || ""), 10);
|
|
2846
2947
|
if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
|
|
@@ -2850,21 +2951,21 @@ function resolveRetryAfterMs(err, fallbackMs = 5_000) {
|
|
|
2850
2951
|
if (Number.isFinite(retryAfterDate)) {
|
|
2851
2952
|
return Math.min(Math.max(0, retryAfterDate - Date.now()), 60_000);
|
|
2852
2953
|
}
|
|
2853
|
-
return fallbackMs;
|
|
2854
|
-
}
|
|
2855
|
-
|
|
2856
|
-
function successfulPollDelayMs(data, response, fallbackMs = 2_000) {
|
|
2857
|
-
const payloadDelay = Number(data?.next_poll_after_ms);
|
|
2858
|
-
const retryAfter = response?.headers?.get?.("retry-after");
|
|
2859
|
-
const headerDelay = retryAfter ? resolveRetryAfterMs({ retryAfter }, 0) : 0;
|
|
2860
|
-
const advertised = Math.max(
|
|
2861
|
-
Number.isFinite(payloadDelay) && payloadDelay > 0 ? payloadDelay : 0,
|
|
2862
|
-
headerDelay,
|
|
2863
|
-
);
|
|
2864
|
-
const base = Math.min(60_000, Math.max(250, advertised || fallbackMs));
|
|
2865
|
-
const jitterLimit = Math.max(0, Math.min(1_000, Number(process.env.MYTE_POLL_JITTER_MS ?? 250) || 0));
|
|
2866
|
-
return Math.min(60_000, base + (jitterLimit ? Math.floor(Math.random() * (jitterLimit + 1)) : 0));
|
|
2867
|
-
}
|
|
2954
|
+
return fallbackMs;
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
function successfulPollDelayMs(data, response, fallbackMs = 2_000) {
|
|
2958
|
+
const payloadDelay = Number(data?.next_poll_after_ms);
|
|
2959
|
+
const retryAfter = response?.headers?.get?.("retry-after");
|
|
2960
|
+
const headerDelay = retryAfter ? resolveRetryAfterMs({ retryAfter }, 0) : 0;
|
|
2961
|
+
const advertised = Math.max(
|
|
2962
|
+
Number.isFinite(payloadDelay) && payloadDelay > 0 ? payloadDelay : 0,
|
|
2963
|
+
headerDelay,
|
|
2964
|
+
);
|
|
2965
|
+
const base = Math.min(60_000, Math.max(250, advertised || fallbackMs));
|
|
2966
|
+
const jitterLimit = Math.max(0, Math.min(1_000, Number(process.env.MYTE_POLL_JITTER_MS ?? 250) || 0));
|
|
2967
|
+
return Math.min(60_000, base + (jitterLimit ? Math.floor(Math.random() * (jitterLimit + 1)) : 0));
|
|
2968
|
+
}
|
|
2868
2969
|
|
|
2869
2970
|
function isTransientQueryStatusError(err) {
|
|
2870
2971
|
return Boolean(
|
|
@@ -2883,9 +2984,22 @@ function createHttpResponseError(resp, body, url, fallbackMessage) {
|
|
|
2883
2984
|
if (status === 401) {
|
|
2884
2985
|
kind = "authentication";
|
|
2885
2986
|
detail = "The API rejected the project key.";
|
|
2886
|
-
} else if (status === 403) {
|
|
2887
|
-
kind = "authorization";
|
|
2888
|
-
|
|
2987
|
+
} else if (status === 403) {
|
|
2988
|
+
kind = "authorization";
|
|
2989
|
+
const responseCode = firstNonEmptyString(body?.data?.code, body?.code);
|
|
2990
|
+
if (responseCode === "policy_consent_required") {
|
|
2991
|
+
const consentPath = firstNonEmptyString(body?.data?.consent_url, body?.data?.mission_board_deeplink);
|
|
2992
|
+
const requiredVersion = firstNonEmptyString(body?.data?.required_policy_version);
|
|
2993
|
+
detail = [
|
|
2994
|
+
firstNonEmptyString(body?.message) || "Myte Policy acceptance is required for this project.",
|
|
2995
|
+
requiredVersion ? `Required policy version: ${requiredVersion}.` : "",
|
|
2996
|
+
consentPath ? `Open the Myte mission board to review and accept: ${consentPath}` : "",
|
|
2997
|
+
]
|
|
2998
|
+
.filter(Boolean)
|
|
2999
|
+
.join(" ");
|
|
3000
|
+
} else {
|
|
3001
|
+
detail = "The authenticated project identity is not allowed to perform this action.";
|
|
3002
|
+
}
|
|
2889
3003
|
} else if (status === 429) {
|
|
2890
3004
|
kind = "rate_limit";
|
|
2891
3005
|
detail = "The API rate limit is temporarily exhausted.";
|
|
@@ -3008,7 +3122,7 @@ async function createAssistantQueryJob({
|
|
|
3008
3122
|
return body.data || {};
|
|
3009
3123
|
}
|
|
3010
3124
|
|
|
3011
|
-
async function fetchAssistantQueryJobStatus({ apiBase, key, timeoutMs, jobId, requestId }) {
|
|
3125
|
+
async function fetchAssistantQueryJobStatus({ apiBase, key, timeoutMs, jobId, requestId }) {
|
|
3012
3126
|
const fetchFn = await getFetch();
|
|
3013
3127
|
const url = `${apiBase}/project-assistant/query/${encodeURIComponent(String(jobId || ""))}`;
|
|
3014
3128
|
const { resp, body } = await fetchJsonWithTimeout(
|
|
@@ -3027,12 +3141,12 @@ async function fetchAssistantQueryJobStatus({ apiBase, key, timeoutMs, jobId, re
|
|
|
3027
3141
|
if (!resp.ok || body.status !== "success") {
|
|
3028
3142
|
throw createHttpResponseError(resp, body, url, "The query status could not be loaded.");
|
|
3029
3143
|
}
|
|
3030
|
-
const data = body.data || {};
|
|
3031
|
-
Object.defineProperty(data, "_nextPollAfterMs", {
|
|
3032
|
-
value: successfulPollDelayMs(data, resp),
|
|
3033
|
-
enumerable: false,
|
|
3034
|
-
});
|
|
3035
|
-
return data;
|
|
3144
|
+
const data = body.data || {};
|
|
3145
|
+
Object.defineProperty(data, "_nextPollAfterMs", {
|
|
3146
|
+
value: successfulPollDelayMs(data, resp),
|
|
3147
|
+
enumerable: false,
|
|
3148
|
+
});
|
|
3149
|
+
return data;
|
|
3036
3150
|
}
|
|
3037
3151
|
|
|
3038
3152
|
async function runRunQaqc(args) {
|
|
@@ -3092,15 +3206,15 @@ async function runRunQaqc(args) {
|
|
|
3092
3206
|
|
|
3093
3207
|
let finalStatus = null;
|
|
3094
3208
|
let syncSummary = null;
|
|
3095
|
-
if (shouldWait && data.batch_id) {
|
|
3096
|
-
const pollTimeoutMs = Math.max(timeoutMs, 900_000);
|
|
3097
|
-
const startedAt = Date.now();
|
|
3098
|
-
let pollDelayMs = 2_000;
|
|
3099
|
-
do {
|
|
3100
|
-
await sleep(pollDelayMs);
|
|
3101
|
-
try {
|
|
3102
|
-
finalStatus = await fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId: data.batch_id });
|
|
3103
|
-
pollDelayMs = Number(finalStatus?._nextPollAfterMs) || Math.min(10_000, Math.ceil(pollDelayMs * 1.25));
|
|
3209
|
+
if (shouldWait && data.batch_id) {
|
|
3210
|
+
const pollTimeoutMs = Math.max(timeoutMs, 900_000);
|
|
3211
|
+
const startedAt = Date.now();
|
|
3212
|
+
let pollDelayMs = 2_000;
|
|
3213
|
+
do {
|
|
3214
|
+
await sleep(pollDelayMs);
|
|
3215
|
+
try {
|
|
3216
|
+
finalStatus = await fetchRunQaqcBatchStatus({ apiBase, key, timeoutMs, batchId: data.batch_id });
|
|
3217
|
+
pollDelayMs = Number(finalStatus?._nextPollAfterMs) || Math.min(10_000, Math.ceil(pollDelayMs * 1.25));
|
|
3104
3218
|
} catch (err) {
|
|
3105
3219
|
if (Number(err?.status) === 429) {
|
|
3106
3220
|
await sleep(resolveRetryAfterMs(err));
|
|
@@ -4221,6 +4335,7 @@ function buildAgentsMyteApiMarkdown() {
|
|
|
4221
4335
|
"- 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.",
|
|
4222
4336
|
"- 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.",
|
|
4223
4337
|
"- 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.",
|
|
4338
|
+
"- Feedback ticket create/update/coverage artifacts must enumerate every ticket key/id, expected revision, PRD document link, and changed lifecycle field. Use --dry-run before the approved live batch.",
|
|
4224
4339
|
"- 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.",
|
|
4225
4340
|
"- `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.",
|
|
4226
4341
|
"",
|
|
@@ -4265,6 +4380,10 @@ function buildAgentsMyteApiMarkdown() {
|
|
|
4265
4380
|
"| Feedback move | `myte feedback move --feedback-id <id> --to-state in_progress --reason \"...\" --json` | `POST /project-assistant/feedback/<id>/board-move` | governed write | no |",
|
|
4266
4381
|
"| 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 |",
|
|
4267
4382
|
"| 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 |",
|
|
4383
|
+
"| Feedback tickets list | `myte feedback tickets list --feedback-id <id> --json` | `GET /project-assistant/feedback/<id>/tickets` | read | no |",
|
|
4384
|
+
"| Feedback tickets create | `myte feedback tickets create --feedback-id <id> --file ./tickets.yml --confirm-write --approval-artifact ./tickets.yml --json` | `POST /project-assistant/feedback/<id>/tickets` or `/tickets/batch`; `--dry-run` uses `/tickets/batch/validate` | synchronous ticket write | required |",
|
|
4385
|
+
"| Feedback tickets update | `myte feedback tickets update --feedback-id <id> --ticket-ids \"<id1,id2>\" --status in_progress --confirm-write --approval-artifact ./update.yml --json` | `PATCH /project-assistant/feedback/<id>/tickets/batch`; `--dry-run` uses `/tickets/batch-update/validate` | revision-fenced ticket write | required |",
|
|
4386
|
+
"| Feedback ticket coverage | `myte feedback tickets coverage --feedback-id <id> --ticket-id <id> ... --confirm-write --approval-artifact ./coverage.yml --json` | `PATCH /project-assistant/feedback/<id>/tickets/<ticket_id>/prd-coverage` | revision-fenced ticket write | required |",
|
|
4268
4387
|
"| 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 |",
|
|
4269
4388
|
"| Feedback PRD versions | `myte feedback prd-versions --feedback-id <id> --json` | `GET /project-assistant/feedback/<id>/prd/versions` | read | no |",
|
|
4270
4389
|
"| 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 |",
|
|
@@ -4313,7 +4432,7 @@ function pruneLegacyCommandCenterArtifacts(dataRoot, options = {}) {
|
|
|
4313
4432
|
}
|
|
4314
4433
|
}
|
|
4315
4434
|
|
|
4316
|
-
function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
4435
|
+
function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir, local }) {
|
|
4317
4436
|
const { targetRoot, dataRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
|
|
4318
4437
|
const phasesDir = path.join(dataRoot, "phases");
|
|
4319
4438
|
const epicsDir = path.join(dataRoot, "epics");
|
|
@@ -4371,10 +4490,19 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4371
4490
|
writeYamlFile(path.join(missionsDir, `${missionPathId}.yml`), mission);
|
|
4372
4491
|
});
|
|
4373
4492
|
|
|
4374
|
-
if (snapshot.project && typeof snapshot.project === "object") {
|
|
4375
|
-
writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
|
|
4376
|
-
}
|
|
4377
|
-
const agentsMyteApiPath = writeAgentsMyteApiGuide({ wrapperRoot, outputDir });
|
|
4493
|
+
if (snapshot.project && typeof snapshot.project === "object") {
|
|
4494
|
+
writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
|
|
4495
|
+
}
|
|
4496
|
+
const agentsMyteApiPath = writeAgentsMyteApiGuide({ wrapperRoot, outputDir });
|
|
4497
|
+
const topology = buildTopology(snapshot, {
|
|
4498
|
+
mode: local?.mode,
|
|
4499
|
+
found: local?.found,
|
|
4500
|
+
missing: local?.missing,
|
|
4501
|
+
});
|
|
4502
|
+
const buildTopologyPath = path.join(dataRoot, "build-topology.yml");
|
|
4503
|
+
const buildHarnessPath = path.join(targetRoot, "MyteBuildHarness.md");
|
|
4504
|
+
writeYamlFile(buildTopologyPath, topology);
|
|
4505
|
+
writeTextFile(buildHarnessPath, buildBuildHarnessMarkdown(topology));
|
|
4378
4506
|
|
|
4379
4507
|
const manifest = {
|
|
4380
4508
|
schema_version: snapshot.schema_version || 1,
|
|
@@ -4391,11 +4519,73 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
|
4391
4519
|
};
|
|
4392
4520
|
return {
|
|
4393
4521
|
targetRoot,
|
|
4394
|
-
dataRoot,
|
|
4395
|
-
agentsMyteApiPath,
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4522
|
+
dataRoot,
|
|
4523
|
+
agentsMyteApiPath,
|
|
4524
|
+
buildTopologyPath,
|
|
4525
|
+
buildHarnessPath,
|
|
4526
|
+
topology,
|
|
4527
|
+
manifest,
|
|
4528
|
+
};
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4531
|
+
function packageUpdateCheckEnabled(args = {}) {
|
|
4532
|
+
if (args["update-check"] === false) return false;
|
|
4533
|
+
return process.env.MYTE_PACKAGE_UPDATE_CHECK !== "0";
|
|
4534
|
+
}
|
|
4535
|
+
|
|
4536
|
+
function packageLatestUrl(args = {}) {
|
|
4537
|
+
return String(
|
|
4538
|
+
args["package-latest-url"] ||
|
|
4539
|
+
process.env.MYTE_PACKAGE_LATEST_URL ||
|
|
4540
|
+
DEFAULT_PACKAGE_LATEST_URL,
|
|
4541
|
+
);
|
|
4542
|
+
}
|
|
4543
|
+
|
|
4544
|
+
function packageUpdateTimeoutMs(args = {}) {
|
|
4545
|
+
const value = Number(
|
|
4546
|
+
args["package-update-timeout-ms"] ||
|
|
4547
|
+
process.env.MYTE_PACKAGE_UPDATE_TIMEOUT_MS ||
|
|
4548
|
+
1500,
|
|
4549
|
+
);
|
|
4550
|
+
return Number.isFinite(value) && value > 0 ? value : 1500;
|
|
4551
|
+
}
|
|
4552
|
+
|
|
4553
|
+
async function checkMytePackageUpdate(args = {}) {
|
|
4554
|
+
const fetchFn = await getFetch();
|
|
4555
|
+
return checkPackageUpdate({
|
|
4556
|
+
packageName: PACKAGE_NAME,
|
|
4557
|
+
installedVersion: PACKAGE_VERSION,
|
|
4558
|
+
latestUrl: packageLatestUrl(args),
|
|
4559
|
+
enabled: packageUpdateCheckEnabled(args),
|
|
4560
|
+
timeoutMs: packageUpdateTimeoutMs(args),
|
|
4561
|
+
cacheDir: process.env.MYTE_PACKAGE_UPDATE_CACHE_DIR || undefined,
|
|
4562
|
+
fetchJson: async (url, { timeoutMs } = {}) => {
|
|
4563
|
+
const { resp, body } = await fetchJsonWithTimeout(
|
|
4564
|
+
fetchFn,
|
|
4565
|
+
url,
|
|
4566
|
+
{ method: "GET", headers: { Accept: "application/json" } },
|
|
4567
|
+
timeoutMs,
|
|
4568
|
+
);
|
|
4569
|
+
return { ok: Boolean(resp.ok), status: resp.status, body };
|
|
4570
|
+
},
|
|
4571
|
+
});
|
|
4572
|
+
}
|
|
4573
|
+
|
|
4574
|
+
async function runWithPackageUpdateNotice(args, run) {
|
|
4575
|
+
const updatePromise = checkMytePackageUpdate(args).catch(() => null);
|
|
4576
|
+
let completed = false;
|
|
4577
|
+
try {
|
|
4578
|
+
const result = await run();
|
|
4579
|
+
completed = true;
|
|
4580
|
+
return result;
|
|
4581
|
+
} finally {
|
|
4582
|
+
if (completed && !process.exitCode) {
|
|
4583
|
+
const status = await updatePromise;
|
|
4584
|
+
const notice = packageUpdateNotice(status);
|
|
4585
|
+
if (notice) console.error(`[myte] ${notice}`);
|
|
4586
|
+
}
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4399
4589
|
|
|
4400
4590
|
function writeQaqcSnapshot({ snapshot, wrapperRoot, outputDir }) {
|
|
4401
4591
|
const { targetRoot, dataRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
|
|
@@ -5541,14 +5731,14 @@ async function runCreatePrd(args) {
|
|
|
5541
5731
|
console.error("--document-set requires one to three markdown file paths; --stdin is not supported.");
|
|
5542
5732
|
process.exit(1);
|
|
5543
5733
|
}
|
|
5544
|
-
if (documentSetMode && (providedPaths.length < 1 || providedPaths.length > 3)) {
|
|
5734
|
+
if (documentSetMode && (providedPaths.length < 1 || providedPaths.length > 3)) {
|
|
5545
5735
|
console.error("--document-set requires between one and three markdown file paths.");
|
|
5546
5736
|
process.exit(1);
|
|
5547
|
-
}
|
|
5548
|
-
if (!documentSetMode && providedPaths.length > 20) {
|
|
5549
|
-
console.error("create-prd accepts at most 20 files per batch.");
|
|
5550
|
-
process.exit(1);
|
|
5551
|
-
}
|
|
5737
|
+
}
|
|
5738
|
+
if (!documentSetMode && providedPaths.length > 20) {
|
|
5739
|
+
console.error("create-prd accepts at most 20 files per batch.");
|
|
5740
|
+
process.exit(1);
|
|
5741
|
+
}
|
|
5552
5742
|
|
|
5553
5743
|
const buildCreatePrdPayload = ({ sourceText, filePath, includeClientRef = false }) => {
|
|
5554
5744
|
const trimmedSource = String(sourceText || "").trim();
|
|
@@ -6195,12 +6385,15 @@ async function runConfig(args) {
|
|
|
6195
6385
|
const apiBase = normalizeApiBase(baseRaw);
|
|
6196
6386
|
|
|
6197
6387
|
let cfg;
|
|
6198
|
-
try {
|
|
6199
|
-
cfg = await fetchProjectConfig({ apiBase, key, timeoutMs });
|
|
6200
|
-
} catch (err) {
|
|
6201
|
-
console.error("Failed to fetch config:", err?.message || err);
|
|
6202
|
-
|
|
6203
|
-
|
|
6388
|
+
try {
|
|
6389
|
+
cfg = await fetchProjectConfig({ apiBase, key, timeoutMs });
|
|
6390
|
+
} catch (err) {
|
|
6391
|
+
console.error("Failed to fetch config:", err?.message || err);
|
|
6392
|
+
// Let Node drain its native fetch handles instead of forcing libuv closed.
|
|
6393
|
+
// Immediate process.exit() can abort on Windows after an HTTP error.
|
|
6394
|
+
process.exitCode = 1;
|
|
6395
|
+
return;
|
|
6396
|
+
}
|
|
6204
6397
|
|
|
6205
6398
|
const repoNames = Array.isArray(cfg.repo_names) ? cfg.repo_names : [];
|
|
6206
6399
|
const repoBindings = Array.isArray(cfg.repo_bindings) ? cfg.repo_bindings : [];
|
|
@@ -6239,7 +6432,7 @@ async function runConfig(args) {
|
|
|
6239
6432
|
}
|
|
6240
6433
|
}
|
|
6241
6434
|
|
|
6242
|
-
async function runBootstrap(args) {
|
|
6435
|
+
async function runBootstrap(args) {
|
|
6243
6436
|
const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
|
|
6244
6437
|
if (!key) {
|
|
6245
6438
|
console.error("Missing MYTE_API_KEY (project key) in environment/.env");
|
|
@@ -6327,9 +6520,38 @@ async function runBootstrap(args) {
|
|
|
6327
6520
|
return;
|
|
6328
6521
|
}
|
|
6329
6522
|
|
|
6330
|
-
const writeResult = writeBootstrapSnapshot({
|
|
6331
|
-
|
|
6332
|
-
|
|
6523
|
+
const writeResult = writeBootstrapSnapshot({
|
|
6524
|
+
snapshot,
|
|
6525
|
+
wrapperRoot,
|
|
6526
|
+
outputDir,
|
|
6527
|
+
local: {
|
|
6528
|
+
mode: resolved.mode,
|
|
6529
|
+
found: (resolved.repos || []).map((repo) => repo.name),
|
|
6530
|
+
missing: resolved.missing || [],
|
|
6531
|
+
},
|
|
6532
|
+
});
|
|
6533
|
+
summary.data_root = writeResult.dataRoot;
|
|
6534
|
+
summary.agents_myte_api_path = writeResult.agentsMyteApiPath;
|
|
6535
|
+
summary.build_harness_path = writeResult.buildHarnessPath;
|
|
6536
|
+
summary.build_topology_path = writeResult.buildTopologyPath;
|
|
6537
|
+
const topologyWarnings = [];
|
|
6538
|
+
if (writeResult.topology.intent.source === "mission_snapshot_only") {
|
|
6539
|
+
topologyWarnings.push("API intent topology was not returned; workflow grouping is fallback-only");
|
|
6540
|
+
}
|
|
6541
|
+
if (!writeResult.topology.intent.workflows.length) {
|
|
6542
|
+
topologyWarnings.push("No explicit workflows were returned; validate workflow boundaries from features and entities");
|
|
6543
|
+
}
|
|
6544
|
+
if (writeResult.topology.mission_groups.some((group) => group.source === "fallback")) {
|
|
6545
|
+
topologyWarnings.push("One or more mission groups use fallback metadata and require semantic review");
|
|
6546
|
+
}
|
|
6547
|
+
summary.topology = {
|
|
6548
|
+
intent_source: writeResult.topology.intent.source,
|
|
6549
|
+
workflow_count: writeResult.topology.intent.workflows.length,
|
|
6550
|
+
feature_count: writeResult.topology.intent.features.length,
|
|
6551
|
+
entity_count: writeResult.topology.intent.entity_graph.entities.length,
|
|
6552
|
+
relationship_count: writeResult.topology.intent.entity_graph.relationships.length,
|
|
6553
|
+
warnings: topologyWarnings,
|
|
6554
|
+
};
|
|
6333
6555
|
|
|
6334
6556
|
if (includeSuggestions) {
|
|
6335
6557
|
let missionOpsSnapshot;
|
|
@@ -6364,16 +6586,103 @@ async function runBootstrap(args) {
|
|
|
6364
6586
|
console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
|
|
6365
6587
|
if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
|
|
6366
6588
|
console.log(`Wrote: phases=${summary.counts.phases}, epics=${summary.counts.epics}, stories=${summary.counts.stories}, missions=${summary.counts.missions}`);
|
|
6367
|
-
if (summary.agents_myte_api_path) console.log(`Wrote AgentsMyteAPI: ${summary.agents_myte_api_path}`);
|
|
6589
|
+
if (summary.agents_myte_api_path) console.log(`Wrote AgentsMyteAPI: ${summary.agents_myte_api_path}`);
|
|
6590
|
+
if (summary.build_harness_path) console.log(`Wrote MyteBuildHarness: ${summary.build_harness_path}`);
|
|
6591
|
+
if (summary.build_topology_path) console.log(`Wrote build topology: ${summary.build_topology_path}`);
|
|
6368
6592
|
if (summary.mission_ops?.included) {
|
|
6369
6593
|
console.log(`Wrote mission ops: total_threads=${summary.mission_ops.total_threads}, actionable_threads=${summary.mission_ops.actionable_threads}`);
|
|
6370
6594
|
} else {
|
|
6371
6595
|
console.log("Skipped mission ops sync.");
|
|
6372
6596
|
}
|
|
6373
|
-
console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
|
|
6374
|
-
}
|
|
6375
|
-
|
|
6376
|
-
|
|
6597
|
+
console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
|
|
6598
|
+
}
|
|
6599
|
+
|
|
6600
|
+
function resolveLocalEvidencePath(candidate, fallback, outputRoot) {
|
|
6601
|
+
const value = candidate ? path.resolve(process.cwd(), String(candidate)) : path.join(outputRoot, fallback);
|
|
6602
|
+
return value;
|
|
6603
|
+
}
|
|
6604
|
+
|
|
6605
|
+
function readEvidenceFile(filePath) {
|
|
6606
|
+
if (!fs.existsSync(filePath)) return null;
|
|
6607
|
+
const text = fs.readFileSync(filePath, "utf8").trim();
|
|
6608
|
+
if (!text) return null;
|
|
6609
|
+
try {
|
|
6610
|
+
return JSON.parse(text);
|
|
6611
|
+
} catch (_) {
|
|
6612
|
+
try {
|
|
6613
|
+
return parseYaml(text);
|
|
6614
|
+
} catch (_) {
|
|
6615
|
+
return { raw: text };
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
|
|
6620
|
+
async function runBuildVerify(args) {
|
|
6621
|
+
const outputDir = args["output-dir"] || args.outputDir || args.output_dir;
|
|
6622
|
+
const outputRoot = path.resolve(process.cwd(), String(outputDir || "MyteCommandCenter"));
|
|
6623
|
+
const dataRoot = path.join(outputRoot, "data");
|
|
6624
|
+
const topologyPath = path.join(dataRoot, "build-topology.yml");
|
|
6625
|
+
const harnessPath = path.join(outputRoot, "MyteBuildHarness.md");
|
|
6626
|
+
const topology = readEvidenceFile(topologyPath);
|
|
6627
|
+
const missionOps = readEvidenceFile(path.join(dataRoot, "mission-ops.yml"));
|
|
6628
|
+
const qaqc = readEvidenceFile(resolveLocalEvidencePath(args["qaqc-file"] || args.qaqcFile, path.join("data", "qaqc.yml"), outputRoot));
|
|
6629
|
+
const semanticReviewPath = resolveLocalEvidencePath(args["semantic-review-file"] || args.semanticReviewFile, "build-evidence/semantic-review.md", outputRoot);
|
|
6630
|
+
const repositoryGatesPath = resolveLocalEvidencePath(args["repository-gates-file"] || args.repositoryGatesFile, "build-evidence/repository-gates.yml", outputRoot);
|
|
6631
|
+
const semanticReview = Boolean(readEvidenceFile(semanticReviewPath));
|
|
6632
|
+
const repositoryGates = Boolean(readEvidenceFile(repositoryGatesPath));
|
|
6633
|
+
const qaqcEvidence = Boolean(qaqc);
|
|
6634
|
+
const qaqcCoverage = topology ? qaqcMissionCoverage(topology, qaqc) : null;
|
|
6635
|
+
|
|
6636
|
+
const result = {
|
|
6637
|
+
ok: false,
|
|
6638
|
+
output_root: outputRoot,
|
|
6639
|
+
topology_path: topologyPath,
|
|
6640
|
+
harness_path: harnessPath,
|
|
6641
|
+
evidence: {
|
|
6642
|
+
semantic_review: { path: semanticReviewPath, present: semanticReview },
|
|
6643
|
+
repository_gates: { path: repositoryGatesPath, present: repositoryGates },
|
|
6644
|
+
qaqc: {
|
|
6645
|
+
path: resolveLocalEvidencePath(args["qaqc-file"] || args.qaqcFile, path.join("data", "qaqc.yml"), outputRoot),
|
|
6646
|
+
present: qaqcEvidence,
|
|
6647
|
+
coverage: qaqcCoverage,
|
|
6648
|
+
},
|
|
6649
|
+
},
|
|
6650
|
+
errors: [],
|
|
6651
|
+
warnings: [],
|
|
6652
|
+
};
|
|
6653
|
+
if (!topology) result.errors.push(`missing ${topologyPath}`);
|
|
6654
|
+
if (!fs.existsSync(harnessPath)) result.errors.push(`missing ${harnessPath}`);
|
|
6655
|
+
if (topology) {
|
|
6656
|
+
const topologyResult = verifyTopology(topology, { semanticReview, repositoryGates, qaqcEvidence, qaqcCoverage });
|
|
6657
|
+
result.errors.push(...topologyResult.errors);
|
|
6658
|
+
result.warnings.push(...topologyResult.warnings);
|
|
6659
|
+
const statusCounts = topology.mission_coverage?.status_counts || {};
|
|
6660
|
+
const totalMissions = Number(topology.mission_coverage?.active_missions || 0);
|
|
6661
|
+
const done = Object.entries(statusCounts)
|
|
6662
|
+
.filter(([status]) => status.trim().toLowerCase() === "done")
|
|
6663
|
+
.reduce((sum, [, count]) => sum + Number(count || 0), 0);
|
|
6664
|
+
const incomplete = Math.max(0, totalMissions - done);
|
|
6665
|
+
result.completion = {
|
|
6666
|
+
active_missions: totalMissions,
|
|
6667
|
+
incomplete_missions: incomplete,
|
|
6668
|
+
all_active_missions_done: incomplete === 0,
|
|
6669
|
+
};
|
|
6670
|
+
if (incomplete > 0) result.errors.push(`${incomplete} active mission(s) are not Done in the last bootstrap`);
|
|
6671
|
+
}
|
|
6672
|
+
const actionable = Number(missionOps?.sync?.counts?.actionable_threads || missionOps?.counts?.actionable_threads || 0);
|
|
6673
|
+
result.mission_ops = { actionable_threads: actionable };
|
|
6674
|
+
if (actionable > 0) result.errors.push(`${actionable} actionable mission-operation thread(s) remain`);
|
|
6675
|
+
result.ok = result.errors.length === 0;
|
|
6676
|
+
if (args.json) console.log(JSON.stringify(result, null, 2));
|
|
6677
|
+
else {
|
|
6678
|
+
console.log(`Build verification: ${result.ok ? "passed" : "failed"}`);
|
|
6679
|
+
if (result.errors.length) console.log(`Errors: ${result.errors.join("; ")}`);
|
|
6680
|
+
if (result.warnings.length) console.log(`Warnings: ${result.warnings.join("; ")}`);
|
|
6681
|
+
}
|
|
6682
|
+
if (!result.ok) process.exitCode = 1;
|
|
6683
|
+
}
|
|
6684
|
+
|
|
6685
|
+
async function runSyncQaqc(args) {
|
|
6377
6686
|
const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
|
|
6378
6687
|
if (!key) {
|
|
6379
6688
|
console.error("Missing MYTE_API_KEY (project key) in environment/.env");
|
|
@@ -7710,6 +8019,213 @@ async function runFeedbackGetOrHistory(args, mode) {
|
|
|
7710
8019
|
console.log(`Snapshot: ${feedback.snapshot_hash || "n/a"}`);
|
|
7711
8020
|
}
|
|
7712
8021
|
|
|
8022
|
+
async function runFeedbackTickets(args) {
|
|
8023
|
+
const key = getProjectApiKey();
|
|
8024
|
+
if (!key) {
|
|
8025
|
+
console.error("Missing MYTE_API_KEY (project key) in environment/.env");
|
|
8026
|
+
process.exit(1);
|
|
8027
|
+
}
|
|
8028
|
+
const action = String(firstNonEmptyString(args._?.[1], "list")).trim().toLowerCase().replace(/_/g, "-");
|
|
8029
|
+
const feedbackId = resolveFeedbackIdArg(args);
|
|
8030
|
+
if (!feedbackId) {
|
|
8031
|
+
console.error("Missing --feedback-id.");
|
|
8032
|
+
process.exit(1);
|
|
8033
|
+
}
|
|
8034
|
+
const apiBase = resolveApiBase(args);
|
|
8035
|
+
const timeoutMs = resolveTimeoutMs(args);
|
|
8036
|
+
const clientSessionId = firstNonEmptyString(args["client-session-id"], args.clientSessionId, args.client_session_id);
|
|
8037
|
+
const dryRun = Boolean(args["dry-run"] || args.dryRun || action.startsWith("validate-"));
|
|
8038
|
+
|
|
8039
|
+
const call = async (requestOptions) => requestFeedbackTickets({
|
|
8040
|
+
apiBase,
|
|
8041
|
+
key,
|
|
8042
|
+
timeoutMs,
|
|
8043
|
+
feedbackId,
|
|
8044
|
+
clientSessionId,
|
|
8045
|
+
...requestOptions,
|
|
8046
|
+
});
|
|
8047
|
+
const print = (data) => {
|
|
8048
|
+
if (args.json) {
|
|
8049
|
+
console.log(JSON.stringify(data, null, 2));
|
|
8050
|
+
return;
|
|
8051
|
+
}
|
|
8052
|
+
const tickets = Array.isArray(data?.tickets) ? data.tickets : [];
|
|
8053
|
+
if (tickets.length) {
|
|
8054
|
+
console.log(`Tickets: ${tickets.length}`);
|
|
8055
|
+
for (const ticket of tickets) {
|
|
8056
|
+
console.log(`- ${ticket.ticket_id || ticket._id || "(unknown)"} r${ticket.revision ?? "?"} ${ticket.status || "unknown"} ${ticket.severity || ""} ${ticket.title || ""}`.trim());
|
|
8057
|
+
}
|
|
8058
|
+
return;
|
|
8059
|
+
}
|
|
8060
|
+
console.log(JSON.stringify(data, null, 2));
|
|
8061
|
+
};
|
|
8062
|
+
|
|
8063
|
+
try {
|
|
8064
|
+
if (action === "list") {
|
|
8065
|
+
print(await call({ method: "GET" }));
|
|
8066
|
+
return;
|
|
8067
|
+
}
|
|
8068
|
+
|
|
8069
|
+
const filePath = firstNonEmptyString(args.file);
|
|
8070
|
+
let rawPayload = filePath ? readStructuredPayloadFile(filePath, "Feedback ticket") : null;
|
|
8071
|
+
|
|
8072
|
+
if (["create", "validate-create"].includes(action)) {
|
|
8073
|
+
if (!rawPayload) {
|
|
8074
|
+
console.error("Missing --file with a JSON/YAML ticket payload.");
|
|
8075
|
+
process.exit(1);
|
|
8076
|
+
}
|
|
8077
|
+
const isBatch = Array.isArray(rawPayload) || (isPlainObject(rawPayload) && Array.isArray(rawPayload.items));
|
|
8078
|
+
let payload = isBatch ? normalizeItemsPayload(rawPayload, "Feedback ticket create") : { ...rawPayload };
|
|
8079
|
+
if (dryRun) {
|
|
8080
|
+
const validationPayload = isBatch ? payload : {
|
|
8081
|
+
items: [{
|
|
8082
|
+
...payload,
|
|
8083
|
+
ticket_key: firstNonEmptyString(payload.ticket_key, args["ticket-key"], `validate-${projectWritePayloadHash(payload).slice(0, 24)}`),
|
|
8084
|
+
}],
|
|
8085
|
+
};
|
|
8086
|
+
print(await call({ endpoint: "batch/validate", method: "POST", payload: validationPayload }));
|
|
8087
|
+
return;
|
|
8088
|
+
}
|
|
8089
|
+
const itemCount = isBatch ? payload.items.length : 1;
|
|
8090
|
+
const targets = isBatch
|
|
8091
|
+
? [feedbackId, ...payload.items.map((item) => firstNonEmptyString(item?.ticket_key)).filter(Boolean)]
|
|
8092
|
+
: [feedbackId];
|
|
8093
|
+
payload = withWriteApproval(args, payload, {
|
|
8094
|
+
artifactKind: "yaml",
|
|
8095
|
+
targets,
|
|
8096
|
+
batchCount: isBatch ? itemCount : null,
|
|
8097
|
+
});
|
|
8098
|
+
const idempotencyKey = resolveProjectMutationIdempotencyKey({
|
|
8099
|
+
args,
|
|
8100
|
+
operation: `feedback_ticket_${isBatch ? "batch_" : ""}create:${feedbackId}`,
|
|
8101
|
+
payload,
|
|
8102
|
+
});
|
|
8103
|
+
print(await call({
|
|
8104
|
+
endpoint: isBatch ? "batch" : "",
|
|
8105
|
+
method: "POST",
|
|
8106
|
+
payload,
|
|
8107
|
+
idempotencyKey,
|
|
8108
|
+
}));
|
|
8109
|
+
return;
|
|
8110
|
+
}
|
|
8111
|
+
|
|
8112
|
+
if (["update", "validate-update"].includes(action)) {
|
|
8113
|
+
const ticketIds = parseFeedbackTicketIdsArg(args);
|
|
8114
|
+
let payload;
|
|
8115
|
+
if (rawPayload) {
|
|
8116
|
+
payload = Array.isArray(rawPayload) ? { items: rawPayload } : { ...rawPayload };
|
|
8117
|
+
} else {
|
|
8118
|
+
if (!ticketIds.length) {
|
|
8119
|
+
console.error("Missing --file or --ticket-ids for ticket update.");
|
|
8120
|
+
process.exit(1);
|
|
8121
|
+
}
|
|
8122
|
+
const updates = {};
|
|
8123
|
+
for (const field of ["status", "severity"]) {
|
|
8124
|
+
const value = firstNonEmptyString(args[field]);
|
|
8125
|
+
if (value) updates[field] = value;
|
|
8126
|
+
}
|
|
8127
|
+
const assignedUserId = firstNonEmptyString(args["assigned-user-id"], args.assignedUserId, args.assigned_user_id);
|
|
8128
|
+
if (assignedUserId) updates.assigned_user_id = assignedUserId;
|
|
8129
|
+
if (!Object.keys(updates).length) {
|
|
8130
|
+
console.error("Missing ticket update fields. Use --status, --severity, --assigned-user-id, or --file.");
|
|
8131
|
+
process.exit(1);
|
|
8132
|
+
}
|
|
8133
|
+
const current = await call({ method: "GET" });
|
|
8134
|
+
const currentById = new Map((current.tickets || []).map((ticket) => [String(ticket.ticket_id || ticket._id), ticket]));
|
|
8135
|
+
const missing = ticketIds.filter((ticketId) => !currentById.has(String(ticketId)));
|
|
8136
|
+
if (missing.length) throw new Error(`Feedback ticket ids were not found: ${missing.join(", ")}`);
|
|
8137
|
+
payload = {
|
|
8138
|
+
items: ticketIds.map((ticketId) => ({
|
|
8139
|
+
ticket_id: ticketId,
|
|
8140
|
+
expected_revision: Number(currentById.get(String(ticketId)).revision),
|
|
8141
|
+
updates,
|
|
8142
|
+
})),
|
|
8143
|
+
};
|
|
8144
|
+
}
|
|
8145
|
+
const isBatch = Array.isArray(payload.items) || Array.isArray(payload.ticket_ids);
|
|
8146
|
+
if (dryRun) {
|
|
8147
|
+
const validationPayload = isBatch ? payload : {
|
|
8148
|
+
items: [{
|
|
8149
|
+
ticket_id: firstNonEmptyString(args["ticket-id"], args.ticketId, args.ticket_id, payload.ticket_id),
|
|
8150
|
+
expected_revision: payload.expected_revision,
|
|
8151
|
+
updates: payload.updates || Object.fromEntries(Object.entries(payload).filter(([key]) => ["title", "summary", "severity", "status", "assigned_user_id"].includes(key))),
|
|
8152
|
+
}],
|
|
8153
|
+
};
|
|
8154
|
+
print(await call({ endpoint: "batch-update/validate", method: "POST", payload: validationPayload }));
|
|
8155
|
+
return;
|
|
8156
|
+
}
|
|
8157
|
+
const itemCount = isBatch ? (payload.items || payload.ticket_ids || []).length : 1;
|
|
8158
|
+
const targetTicketId = firstNonEmptyString(args["ticket-id"], args.ticketId, args.ticket_id, payload.ticket_id);
|
|
8159
|
+
if (!isBatch && !targetTicketId) {
|
|
8160
|
+
console.error("Missing --ticket-id for a single ticket update.");
|
|
8161
|
+
process.exit(1);
|
|
8162
|
+
}
|
|
8163
|
+
const targetIds = isBatch
|
|
8164
|
+
? (payload.items || []).map((item) => item?.ticket_id).filter(Boolean).concat(payload.ticket_ids || [])
|
|
8165
|
+
: [targetTicketId];
|
|
8166
|
+
payload = withWriteApproval(args, payload, {
|
|
8167
|
+
artifactKind: "yaml",
|
|
8168
|
+
targets: [feedbackId, ...targetIds],
|
|
8169
|
+
batchCount: isBatch ? itemCount : null,
|
|
8170
|
+
});
|
|
8171
|
+
const idempotencyKey = resolveProjectMutationIdempotencyKey({
|
|
8172
|
+
args,
|
|
8173
|
+
operation: `feedback_ticket_${isBatch ? "batch_" : ""}update:${feedbackId}:${targetIds.join(",")}`,
|
|
8174
|
+
payload,
|
|
8175
|
+
});
|
|
8176
|
+
print(await call({
|
|
8177
|
+
endpoint: isBatch ? "batch" : encodeURIComponent(targetTicketId),
|
|
8178
|
+
method: "PATCH",
|
|
8179
|
+
payload,
|
|
8180
|
+
idempotencyKey,
|
|
8181
|
+
}));
|
|
8182
|
+
return;
|
|
8183
|
+
}
|
|
8184
|
+
|
|
8185
|
+
if (action === "coverage") {
|
|
8186
|
+
const ticketId = firstNonEmptyString(args["ticket-id"], args.ticketId, args.ticket_id);
|
|
8187
|
+
if (!ticketId) {
|
|
8188
|
+
console.error("Missing --ticket-id.");
|
|
8189
|
+
process.exit(1);
|
|
8190
|
+
}
|
|
8191
|
+
let payload = rawPayload && isPlainObject(rawPayload) ? { ...rawPayload } : {
|
|
8192
|
+
version_id: firstNonEmptyString(args["version-id"], args.versionId, args.version_id),
|
|
8193
|
+
document_id: firstNonEmptyString(args["document-id"], args.documentId, args.document_id),
|
|
8194
|
+
coverage_status: firstNonEmptyString(args["coverage-status"], args.coverageStatus, args.coverage_status),
|
|
8195
|
+
};
|
|
8196
|
+
if (payload.expected_revision === undefined) {
|
|
8197
|
+
const current = await call({ method: "GET" });
|
|
8198
|
+
const ticket = (current.tickets || []).find((item) => String(item.ticket_id || item._id) === String(ticketId));
|
|
8199
|
+
if (!ticket) throw new Error(`Feedback ticket id was not found: ${ticketId}`);
|
|
8200
|
+
payload.expected_revision = Number(ticket.revision);
|
|
8201
|
+
}
|
|
8202
|
+
if (dryRun) {
|
|
8203
|
+
console.log(JSON.stringify({ valid: true, validate_only: true, ticket_id: ticketId, payload }, null, 2));
|
|
8204
|
+
return;
|
|
8205
|
+
}
|
|
8206
|
+
payload = withWriteApproval(args, payload, { artifactKind: "yaml", targets: [feedbackId, ticketId] });
|
|
8207
|
+
const idempotencyKey = resolveProjectMutationIdempotencyKey({
|
|
8208
|
+
args,
|
|
8209
|
+
operation: `feedback_ticket_coverage:${feedbackId}:${ticketId}`,
|
|
8210
|
+
payload,
|
|
8211
|
+
});
|
|
8212
|
+
print(await call({ endpoint: `${encodeURIComponent(ticketId)}/prd-coverage`, method: "PATCH", payload, idempotencyKey }));
|
|
8213
|
+
return;
|
|
8214
|
+
}
|
|
8215
|
+
|
|
8216
|
+
console.error("Unknown feedback tickets command. Use list, create, validate-create, update, validate-update, or coverage.");
|
|
8217
|
+
process.exit(1);
|
|
8218
|
+
} catch (err) {
|
|
8219
|
+
if (args.json) {
|
|
8220
|
+
console.log(JSON.stringify({ ok: false, status: err?.status || null, message: err?.message || String(err), data: err?.data || null }, null, 2));
|
|
8221
|
+
} else {
|
|
8222
|
+
console.error("Feedback tickets failed:", err?.message || err);
|
|
8223
|
+
if (err?.data) console.error(JSON.stringify(err.data, null, 2));
|
|
8224
|
+
}
|
|
8225
|
+
process.exit(1);
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8228
|
+
|
|
7713
8229
|
async function runFeedback(args) {
|
|
7714
8230
|
const subcommand = firstNonEmptyString(args._?.[0]) || "help";
|
|
7715
8231
|
if (subcommand === "help") {
|
|
@@ -7764,7 +8280,11 @@ async function runFeedback(args) {
|
|
|
7764
8280
|
await runFeedbackGetOrHistory(args, subcommand);
|
|
7765
8281
|
return;
|
|
7766
8282
|
}
|
|
7767
|
-
|
|
8283
|
+
if (subcommand === "tickets") {
|
|
8284
|
+
await runFeedbackTickets(args);
|
|
8285
|
+
return;
|
|
8286
|
+
}
|
|
8287
|
+
console.error("Unknown feedback command. Use status, edit, document-add, assign, archive, submit, revise, reviews, review, move, comment, tickets, undo, prd-versions, prd-diff, get, history, validate, or apply.");
|
|
7768
8288
|
process.exit(1);
|
|
7769
8289
|
}
|
|
7770
8290
|
|
|
@@ -7928,7 +8448,16 @@ async function resyncBootstrapAfterMissionMutation({ args, apiBase, key, timeout
|
|
|
7928
8448
|
const snapshot = await fetchBootstrapSnapshot({ apiBase, key, timeoutMs });
|
|
7929
8449
|
const resolved = resolvePortableWorkspace(snapshot.repo_names || []);
|
|
7930
8450
|
const wrapperRoot = !outputDir && existingPaths?.wrapperRoot ? existingPaths.wrapperRoot : resolved.root;
|
|
7931
|
-
const writeResult = writeBootstrapSnapshot({
|
|
8451
|
+
const writeResult = writeBootstrapSnapshot({
|
|
8452
|
+
snapshot,
|
|
8453
|
+
wrapperRoot,
|
|
8454
|
+
outputDir,
|
|
8455
|
+
local: {
|
|
8456
|
+
mode: resolved.mode,
|
|
8457
|
+
found: (resolved.repos || []).map((repo) => repo.name),
|
|
8458
|
+
missing: resolved.missing || [],
|
|
8459
|
+
},
|
|
8460
|
+
});
|
|
7932
8461
|
const missionOpsSnapshot = await fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScope });
|
|
7933
8462
|
const missionOpsWrite = writeMissionOpsSnapshot({ snapshot: missionOpsSnapshot, wrapperRoot, outputDir });
|
|
7934
8463
|
const missionOpsCounts = missionOpsWrite?.manifest?.sync?.counts || {};
|
|
@@ -8437,10 +8966,10 @@ async function runQuery(args) {
|
|
|
8437
8966
|
};
|
|
8438
8967
|
if (diffDiagnostics) payload.diff_diagnostics = diffDiagnostics;
|
|
8439
8968
|
|
|
8440
|
-
if (printContext) {
|
|
8441
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
8442
|
-
|
|
8443
|
-
}
|
|
8969
|
+
if (printContext) {
|
|
8970
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
8971
|
+
return;
|
|
8972
|
+
}
|
|
8444
8973
|
|
|
8445
8974
|
let data;
|
|
8446
8975
|
const requestId = queryRequestId(args);
|
|
@@ -8495,7 +9024,7 @@ async function runQuery(args) {
|
|
|
8495
9024
|
initialDelayMs: Math.min(2_000, pollDelayMs),
|
|
8496
9025
|
},
|
|
8497
9026
|
);
|
|
8498
|
-
pollDelayMs = Number(finalStatus?._nextPollAfterMs) || Math.min(10_000, Math.ceil(pollDelayMs * 1.25));
|
|
9027
|
+
pollDelayMs = Number(finalStatus?._nextPollAfterMs) || Math.min(10_000, Math.ceil(pollDelayMs * 1.25));
|
|
8499
9028
|
if (["completed", "failed"].includes(String(finalStatus.status || "").trim())) {
|
|
8500
9029
|
break;
|
|
8501
9030
|
}
|
|
@@ -8552,112 +9081,120 @@ async function runChat(args) {
|
|
|
8552
9081
|
process.exit(1);
|
|
8553
9082
|
}
|
|
8554
9083
|
|
|
8555
|
-
async function
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
if (command
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
}
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
|
|
8660
|
-
}
|
|
9084
|
+
async function dispatchCommand(command, args, envPath) {
|
|
9085
|
+
if (command === "version") {
|
|
9086
|
+
await runVersion(args);
|
|
9087
|
+
return;
|
|
9088
|
+
}
|
|
9089
|
+
|
|
9090
|
+
if (command === "info") {
|
|
9091
|
+
await runInfo(args, envPath);
|
|
9092
|
+
return;
|
|
9093
|
+
}
|
|
9094
|
+
|
|
9095
|
+
if (command === "doctor") {
|
|
9096
|
+
await runDoctor(args, envPath);
|
|
9097
|
+
return;
|
|
9098
|
+
}
|
|
9099
|
+
|
|
9100
|
+
if (command === "config") {
|
|
9101
|
+
await runConfig(args);
|
|
9102
|
+
return;
|
|
9103
|
+
}
|
|
9104
|
+
|
|
9105
|
+
if (command === "ai") {
|
|
9106
|
+
await runAi(args);
|
|
9107
|
+
return;
|
|
9108
|
+
}
|
|
9109
|
+
|
|
9110
|
+
if (command === "bootstrap") {
|
|
9111
|
+
await runBootstrap(args);
|
|
9112
|
+
return;
|
|
9113
|
+
}
|
|
9114
|
+
if (command === "build-verify") {
|
|
9115
|
+
await runBuildVerify(args);
|
|
9116
|
+
return;
|
|
9117
|
+
}
|
|
9118
|
+
|
|
9119
|
+
if (command === "run-qaqc") {
|
|
9120
|
+
await runRunQaqc(args);
|
|
9121
|
+
return;
|
|
9122
|
+
}
|
|
9123
|
+
|
|
9124
|
+
if (command === "mission-status") {
|
|
9125
|
+
await runMissionStatus(args);
|
|
9126
|
+
return;
|
|
9127
|
+
}
|
|
9128
|
+
|
|
9129
|
+
if (command === "mission-archive") {
|
|
9130
|
+
await runMissionArchiveCommand(args);
|
|
9131
|
+
return;
|
|
9132
|
+
}
|
|
9133
|
+
|
|
9134
|
+
if (command === "sync-qaqc" || command === "qaqc-sync") {
|
|
9135
|
+
await runSyncQaqc(args);
|
|
9136
|
+
return;
|
|
9137
|
+
}
|
|
9138
|
+
|
|
9139
|
+
if (command === "feedback-sync") {
|
|
9140
|
+
await runFeedbackSync(args);
|
|
9141
|
+
return;
|
|
9142
|
+
}
|
|
9143
|
+
|
|
9144
|
+
if (command === "feedback") {
|
|
9145
|
+
await runFeedback(args);
|
|
9146
|
+
return;
|
|
9147
|
+
}
|
|
9148
|
+
|
|
9149
|
+
if (command === "suggestions") {
|
|
9150
|
+
await runSuggestions(args);
|
|
9151
|
+
return;
|
|
9152
|
+
}
|
|
9153
|
+
|
|
9154
|
+
if (command === "create-prd") {
|
|
9155
|
+
await runCreatePrd(args);
|
|
9156
|
+
return;
|
|
9157
|
+
}
|
|
9158
|
+
|
|
9159
|
+
if (command === "update-team") {
|
|
9160
|
+
await runUpdateTeam(args);
|
|
9161
|
+
return;
|
|
9162
|
+
}
|
|
9163
|
+
|
|
9164
|
+
if (command === "update-owner") {
|
|
9165
|
+
await runUpdateOwner(args);
|
|
9166
|
+
return;
|
|
9167
|
+
}
|
|
9168
|
+
|
|
9169
|
+
if (command === "update-client") {
|
|
9170
|
+
await runUpdateClient(args);
|
|
9171
|
+
return;
|
|
9172
|
+
}
|
|
9173
|
+
|
|
9174
|
+
// query default
|
|
9175
|
+
await runQuery(args);
|
|
9176
|
+
}
|
|
9177
|
+
|
|
9178
|
+
async function main() {
|
|
9179
|
+
const envPath = loadEnv();
|
|
9180
|
+
|
|
9181
|
+
const { command, rest } = splitCommand(process.argv.slice(2));
|
|
9182
|
+
if (REMOVED_COMMAND_MESSAGES[command]) {
|
|
9183
|
+
console.error(REMOVED_COMMAND_MESSAGES[command]);
|
|
9184
|
+
process.exit(1);
|
|
9185
|
+
}
|
|
9186
|
+
if (command === "mission") {
|
|
9187
|
+
console.error("Unknown mission command. Use `myte mission status` or `myte mission archive`.");
|
|
9188
|
+
process.exit(1);
|
|
9189
|
+
}
|
|
9190
|
+
const args = parseArgs(rest);
|
|
9191
|
+
if (args.help || command === "help") {
|
|
9192
|
+
printHelp();
|
|
9193
|
+
return;
|
|
9194
|
+
}
|
|
9195
|
+
|
|
9196
|
+
await runWithPackageUpdateNotice(args, () => dispatchCommand(command, args, envPath));
|
|
9197
|
+
}
|
|
8661
9198
|
|
|
8662
9199
|
if (require.main === module) {
|
|
8663
9200
|
main().catch((err) => {
|
|
@@ -8684,8 +9221,8 @@ if (require.main === module) {
|
|
|
8684
9221
|
resolveActorScope,
|
|
8685
9222
|
main,
|
|
8686
9223
|
scrubBootstrapValue,
|
|
8687
|
-
scrubMissionOpsValue,
|
|
8688
|
-
successfulPollDelayMs,
|
|
9224
|
+
scrubMissionOpsValue,
|
|
9225
|
+
successfulPollDelayMs,
|
|
8689
9226
|
writeBootstrapSnapshot,
|
|
8690
9227
|
writeFeedbackSnapshot,
|
|
8691
9228
|
writeApprovedMissionCards,
|