@mytegroupinc/myte-core 0.0.56 → 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 CHANGED
@@ -33,7 +33,10 @@ This package exists so the public wrapper can stay small and versioned cleanly.
33
33
  authorization, rate-limit, and server failures.
34
34
  - Safe transient reads and query polling use bounded retries. Query creation
35
35
  reuses one idempotency key across ambiguous retries.
36
- - Snapshot-style commands such as `bootstrap`, `sync-qaqc`, `feedback-sync`, and `suggestions sync` write local `MyteCommandCenter` data.
36
+ - Snapshot-style commands such as `bootstrap`, `sync-qaqc`, `feedback-sync`, and `suggestions sync` write local `MyteCommandCenter` data.
37
+ - `bootstrap` also writes `MyteCommandCenter/MyteBuildHarness.md` and `MyteCommandCenter/data/build-topology.yml`; these capture the project execution protocol and normalized topology without granting write permission.
38
+ - `build verify --json` is a read-only local convergence check. It verifies the last bootstrap, active mission completion, actionable mission-operation state, per-mission terminal QA/QC coverage with no failed or pending cases, semantic/UI review evidence, and repository-gate evidence. It does not run QA/QC, repository tests, or semantic review itself.
39
+ - Successful `myte` commands perform a cached, best-effort check for a newer package version and print any notice to `stderr`; `--json` output on `stdout` remains valid JSON. Disable it with `--no-update-check` or `MYTE_PACKAGE_UPDATE_CHECK=0`.
37
40
  - `feedback-sync` prefers stable ObjectId cursor pagination so concurrent
38
41
  inserts cannot shift later pages. It remains compatible with older
39
42
  offset-only API versions and rejects duplicate cross-page records instead of
@@ -106,7 +109,10 @@ Coding agents should treat `MYTE_API_KEY` as a project-scoped Project Assistant
106
109
 
107
110
  - Load it from the workspace `.env` or environment. `MYTE_PROJECT_API_KEY` is accepted as a compatibility fallback.
108
111
  - Call `myte config --json` first when project identity or local repo detection is uncertain.
109
- - Hydrate mission context with `bootstrap` first instead of scraping the web UI. `bootstrap` writes mission cards, mission suggestion thread state, and `MyteCommandCenter/AgentsMyteAPI.md`.
112
+ - Hydrate mission context with `bootstrap` first instead of scraping the web UI. `bootstrap` writes mission cards, mission suggestion thread state, and `MyteCommandCenter/AgentsMyteAPI.md`.
113
+ - Read `MyteCommandCenter/MyteBuildHarness.md` and `MyteCommandCenter/data/build-topology.yml` before implementing a new project. Organize work by workflow/domain topology and map frontend/backend ownership before changing code.
114
+ - Run `myte build verify --json` only after the final bootstrap and preserve its QA/QC, semantic-review, and repository-gate evidence files for review.
115
+ - The build verifier does not run QAQC, repository tests, or semantic review; run those steps first and preserve their evidence for verification.
110
116
  - Use `suggestions sync` only when you need to refresh mission review-thread state without refreshing the full board.
111
117
  - Use `query --with-diff` for project-scoped code review, planning, and implementation questions.
112
118
  - Use `create-prd` only with reviewed markdown files and `--confirm-write --approval-artifact <path>`. The markdown file body is the PRD document; `description` is only the short board/card summary.
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
- const DEFAULT_API_BASE = "https://api.myte.dev";
28
- const DEFAULT_DIFF_LIMIT_CHARS = 500_000;
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]",
@@ -261,13 +278,26 @@ function printHelp() {
261
278
  " - Set MYTE_API_KEY in a workspace .env (or env var)",
262
279
  " - Set MYTEAI_API_KEY in a workspace .env (or env var) for `myte ai`",
263
280
  "",
264
- "bootstrap contract:",
281
+ "bootstrap contract:",
265
282
  " - Run from any workspace where you want local MyteCommandCenter data written",
266
283
  " - Writes MyteCommandCenter/data/project.yml plus phases, epics, stories, and missions locally",
267
284
  " - Also refreshes mission review threads into MyteCommandCenter/data/mission-ops.yml by default",
268
285
  " - Uses the project-scoped bootstrap snapshot from the Myte API",
269
286
  " - Mission cards include richer execution context like complexity, estimated_hours, due_date, subtasks, technical_requirements, resources_needed, labels, and normalized test_cases",
270
- " - 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",
271
301
  "",
272
302
  "sync-qaqc contract:",
273
303
  " - Run from any workspace where you want local MyteCommandCenter data written",
@@ -1009,7 +1039,7 @@ function normalizeApiBase(baseRaw) {
1009
1039
  return base.endsWith("/api") ? base : `${base}/api`;
1010
1040
  }
1011
1041
 
1012
- async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
1042
+ async function fetchJsonWithTimeout(fetchFn, url, options, timeoutMs) {
1013
1043
  const controller = typeof AbortController !== "undefined" ? new AbortController() : undefined;
1014
1044
  let timedOut = false;
1015
1045
  const timeoutId =
@@ -4402,7 +4432,7 @@ function pruneLegacyCommandCenterArtifacts(dataRoot, options = {}) {
4402
4432
  }
4403
4433
  }
4404
4434
 
4405
- function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
4435
+ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir, local }) {
4406
4436
  const { targetRoot, dataRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
4407
4437
  const phasesDir = path.join(dataRoot, "phases");
4408
4438
  const epicsDir = path.join(dataRoot, "epics");
@@ -4460,10 +4490,19 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
4460
4490
  writeYamlFile(path.join(missionsDir, `${missionPathId}.yml`), mission);
4461
4491
  });
4462
4492
 
4463
- if (snapshot.project && typeof snapshot.project === "object") {
4464
- writeYamlFile(path.join(dataRoot, "project.yml"), scrubBootstrapValue(snapshot.project));
4465
- }
4466
- 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));
4467
4506
 
4468
4507
  const manifest = {
4469
4508
  schema_version: snapshot.schema_version || 1,
@@ -4480,11 +4519,73 @@ function writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir }) {
4480
4519
  };
4481
4520
  return {
4482
4521
  targetRoot,
4483
- dataRoot,
4484
- agentsMyteApiPath,
4485
- manifest,
4486
- };
4487
- }
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
+ }
4488
4589
 
4489
4590
  function writeQaqcSnapshot({ snapshot, wrapperRoot, outputDir }) {
4490
4591
  const { targetRoot, dataRoot } = resolveCommandCenterRoots(wrapperRoot, outputDir);
@@ -6331,7 +6432,7 @@ async function runConfig(args) {
6331
6432
  }
6332
6433
  }
6333
6434
 
6334
- async function runBootstrap(args) {
6435
+ async function runBootstrap(args) {
6335
6436
  const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
6336
6437
  if (!key) {
6337
6438
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
@@ -6419,9 +6520,38 @@ async function runBootstrap(args) {
6419
6520
  return;
6420
6521
  }
6421
6522
 
6422
- const writeResult = writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir });
6423
- summary.data_root = writeResult.dataRoot;
6424
- summary.agents_myte_api_path = writeResult.agentsMyteApiPath;
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
+ };
6425
6555
 
6426
6556
  if (includeSuggestions) {
6427
6557
  let missionOpsSnapshot;
@@ -6456,16 +6586,103 @@ async function runBootstrap(args) {
6456
6586
  console.log(`Found locally: ${summary.local.found.join(", ") || "(none)"}`);
6457
6587
  if (summary.local.missing.length) console.log(`Missing locally: ${summary.local.missing.join(", ")}`);
6458
6588
  console.log(`Wrote: phases=${summary.counts.phases}, epics=${summary.counts.epics}, stories=${summary.counts.stories}, missions=${summary.counts.missions}`);
6459
- 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}`);
6460
6592
  if (summary.mission_ops?.included) {
6461
6593
  console.log(`Wrote mission ops: total_threads=${summary.mission_ops.total_threads}, actionable_threads=${summary.mission_ops.actionable_threads}`);
6462
6594
  } else {
6463
6595
  console.log("Skipped mission ops sync.");
6464
6596
  }
6465
- console.log(`Snapshot: ${summary.snapshot_hash || "n/a"}`);
6466
- }
6467
-
6468
- async function runSyncQaqc(args) {
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) {
6469
6686
  const key = (process.env.MYTE_API_KEY || process.env.MYTE_PROJECT_API_KEY || "").trim();
6470
6687
  if (!key) {
6471
6688
  console.error("Missing MYTE_API_KEY (project key) in environment/.env");
@@ -8231,7 +8448,16 @@ async function resyncBootstrapAfterMissionMutation({ args, apiBase, key, timeout
8231
8448
  const snapshot = await fetchBootstrapSnapshot({ apiBase, key, timeoutMs });
8232
8449
  const resolved = resolvePortableWorkspace(snapshot.repo_names || []);
8233
8450
  const wrapperRoot = !outputDir && existingPaths?.wrapperRoot ? existingPaths.wrapperRoot : resolved.root;
8234
- const writeResult = writeBootstrapSnapshot({ snapshot, wrapperRoot, outputDir });
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
+ });
8235
8461
  const missionOpsSnapshot = await fetchSuggestionsSyncSnapshot({ apiBase, key, timeoutMs, actorScope });
8236
8462
  const missionOpsWrite = writeMissionOpsSnapshot({ snapshot: missionOpsSnapshot, wrapperRoot, outputDir });
8237
8463
  const missionOpsCounts = missionOpsWrite?.manifest?.sync?.counts || {};
@@ -8740,10 +8966,10 @@ async function runQuery(args) {
8740
8966
  };
8741
8967
  if (diffDiagnostics) payload.diff_diagnostics = diffDiagnostics;
8742
8968
 
8743
- if (printContext) {
8744
- console.log(JSON.stringify(payload, null, 2));
8745
- process.exit(0);
8746
- }
8969
+ if (printContext) {
8970
+ console.log(JSON.stringify(payload, null, 2));
8971
+ return;
8972
+ }
8747
8973
 
8748
8974
  let data;
8749
8975
  const requestId = queryRequestId(args);
@@ -8855,112 +9081,120 @@ async function runChat(args) {
8855
9081
  process.exit(1);
8856
9082
  }
8857
9083
 
8858
- async function main() {
8859
- const envPath = loadEnv();
8860
-
8861
- const { command, rest } = splitCommand(process.argv.slice(2));
8862
- if (REMOVED_COMMAND_MESSAGES[command]) {
8863
- console.error(REMOVED_COMMAND_MESSAGES[command]);
8864
- process.exit(1);
8865
- }
8866
- if (command === "mission") {
8867
- console.error("Unknown mission command. Use `myte mission status` or `myte mission archive`.");
8868
- process.exit(1);
8869
- }
8870
- const args = parseArgs(rest);
8871
- if (args.help || command === "help") {
8872
- printHelp();
8873
- return;
8874
- }
8875
-
8876
- if (command === "version") {
8877
- await runVersion(args);
8878
- return;
8879
- }
8880
-
8881
- if (command === "info") {
8882
- await runInfo(args, envPath);
8883
- return;
8884
- }
8885
-
8886
- if (command === "doctor") {
8887
- await runDoctor(args, envPath);
8888
- return;
8889
- }
8890
-
8891
- if (command === "config") {
8892
- await runConfig(args);
8893
- return;
8894
- }
8895
-
8896
- if (command === "ai") {
8897
- await runAi(args);
8898
- return;
8899
- }
8900
-
8901
- if (command === "bootstrap") {
8902
- await runBootstrap(args);
8903
- return;
8904
- }
8905
-
8906
- if (command === "run-qaqc") {
8907
- await runRunQaqc(args);
8908
- return;
8909
- }
8910
-
8911
- if (command === "mission-status") {
8912
- await runMissionStatus(args);
8913
- return;
8914
- }
8915
-
8916
- if (command === "mission-archive") {
8917
- await runMissionArchiveCommand(args);
8918
- return;
8919
- }
8920
-
8921
- if (command === "sync-qaqc" || command === "qaqc-sync") {
8922
- await runSyncQaqc(args);
8923
- return;
8924
- }
8925
-
8926
- if (command === "feedback-sync") {
8927
- await runFeedbackSync(args);
8928
- return;
8929
- }
8930
-
8931
- if (command === "feedback") {
8932
- await runFeedback(args);
8933
- return;
8934
- }
8935
-
8936
- if (command === "suggestions") {
8937
- await runSuggestions(args);
8938
- return;
8939
- }
8940
-
8941
- if (command === "create-prd") {
8942
- await runCreatePrd(args);
8943
- return;
8944
- }
8945
-
8946
- if (command === "update-team") {
8947
- await runUpdateTeam(args);
8948
- return;
8949
- }
8950
-
8951
- if (command === "update-owner") {
8952
- await runUpdateOwner(args);
8953
- return;
8954
- }
8955
-
8956
- if (command === "update-client") {
8957
- await runUpdateClient(args);
8958
- return;
8959
- }
8960
-
8961
- // query default
8962
- await runQuery(args);
8963
- }
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
+ }
8964
9198
 
8965
9199
  if (require.main === module) {
8966
9200
  main().catch((err) => {